fix(vite): resolves files with dot suffixes correctly (#28518)

Use `basename` for retrieving the filename in `findFile` function from
`nx-tsconfig-paths.plugin` that previously led to an unresolved error
for files with dot suffixes e.g.`/dir/file.suffix.ext`

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
For files that have the same name but one of them has a suffix, e.g.
```ts
- lib1
  - file.ts
  - file.i18n.ts
- apps
  - app1
    - main.ts
- tsconfig.base.json // with { "paths": { "@lib1/*": ["lib1/*"] }}
```
The resolving process is incorrect because the `import value from
'@lib1/file.i18n'` from `apps/app1/main.ts` every time resolves to
`file.ts,` and the `parse` method from the `node:path` splits away the
suffix from the name.

## Expected Behavior
`import value from '@lib1/file.i18n'` should work correctly if you
import them from another package

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #27852

---------

Co-authored-by: Colum Ferry <cferry09@gmail.com>
This commit is contained in:
Konstantin Kai 2024-12-12 22:08:53 +02:00 committed by GitHub
parent fca739e5b5
commit b6d41b617e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 155 additions and 25 deletions

View File

@ -158,7 +158,7 @@ describe('@nx/vite/plugin', () => {
`generate @nx/react:library libs/${mylib} --bundler=none --unitTestRunner=vitest` `generate @nx/react:library libs/${mylib} --bundler=none --unitTestRunner=vitest`
); );
updateFile(`libs/${mylib}/src/styles.css`, `.foo {}`); updateFile(`libs/${mylib}/src/styles.css`, `.foo {}`);
updateFile(`libs/${mylib}/src/foo.mts`, `export const foo = 'foo';`); updateFile(`libs/${mylib}/src/foo.mjs`, `export const foo = 'foo';`);
updateFile( updateFile(
`libs/${mylib}/src/foo.spec.ts`, `libs/${mylib}/src/foo.spec.ts`,
` `

View File

@ -5,7 +5,7 @@ import {
workspaceRoot, workspaceRoot,
} from '@nx/devkit'; } from '@nx/devkit';
import { copyFileSync, existsSync } from 'node:fs'; import { copyFileSync, existsSync } from 'node:fs';
import { join, parse, relative, resolve } from 'node:path'; import { join, relative, resolve } from 'node:path';
import { import {
loadConfig, loadConfig,
createMatchPath, createMatchPath,
@ -18,6 +18,7 @@ import {
} from '@nx/js/src/utils/buildable-libs-utils'; } from '@nx/js/src/utils/buildable-libs-utils';
import { Plugin } from 'vite'; import { Plugin } from 'vite';
import { nxViteBuildCoordinationPlugin } from './nx-vite-build-coordination.plugin'; import { nxViteBuildCoordinationPlugin } from './nx-vite-build-coordination.plugin';
import { findFile } from '../src/utils/nx-tsconfig-paths-find-file';
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
export interface nxViteTsPathsOptions { export interface nxViteTsPathsOptions {
@ -243,33 +244,12 @@ There should at least be a tsconfig.base.json or tsconfig.json in the root of th
); );
resolvedFile = findFile( resolvedFile = findFile(
importPath.replace(normalizedImport, joinedPath) importPath.replace(normalizedImport, joinedPath),
options.extensions
); );
} }
} }
return resolvedFile; return resolvedFile;
} }
function findFile(path: string): string {
for (const ext of options.extensions) {
// Support file with "." in the name.
let resolvedPath = resolve(path + ext);
if (existsSync(resolvedPath)) {
return resolvedPath;
}
// Support file extensions such as .css and .js in the import path.
const { dir, name } = parse(path);
resolvedPath = resolve(dir, name + ext);
if (existsSync(resolvedPath)) {
return resolvedPath;
}
const resolvedIndexPath = resolve(path, `index${ext}`);
if (existsSync(resolvedIndexPath)) {
return resolvedIndexPath;
}
}
}
} }

View File

@ -0,0 +1,123 @@
import { findFile as findFileMain } from './nx-tsconfig-paths-find-file';
describe('@nx/vite nx-tsconfig-paths-find-file', () => {
const extensions = ['.ts', '.js', '.mts'];
const fs = new Set<string>();
const existsSyncImpl = (path: string) => fs.has(path);
const findFile = (path: string, exts: string[] = extensions): string =>
findFileMain(path, exts, existsSyncImpl);
beforeAll(() => {
[
'/dir1/file.ts',
'/dir1/file.suffix.ts',
'/dir2/inner/index.ts',
'/dir2/inner/index.js',
'/dir3/file.js',
'/dir4/file.css',
'/dir5/file.suffix.ts.js',
'/dir6/inner.suffix/index.ts',
'/file1.mts',
].forEach((item) => fs.add(item));
});
afterAll(() => {
fs.clear();
});
const cases: Array<{
title: string;
path: string;
expected: string | undefined;
extensions?: string[];
}> = [
{
title: 'Should return undefined for missing file',
path: '/dir10/file',
expected: undefined,
},
{
title: 'Should return undefined for missing index file',
path: '/dir10/inner',
expected: undefined,
},
{
title: 'Should return existing file path with extension',
path: '/dir1/file',
expected: '/dir1/file.ts',
},
{
title:
'Should return correct file in case with same filename but one with suffix',
path: '/dir1/file.suffix',
expected: '/dir1/file.suffix.ts',
},
{
title: 'Should return existing file with dir request',
path: '/dir2/inner',
expected: '/dir2/inner/index.ts',
},
{
title: 'Should return existing file with index request',
path: '/dir2/inner/index',
expected: '/dir2/inner/index.ts',
},
{
title: 'Should return existing file with js extension',
path: '/dir3/file',
expected: '/dir3/file.js',
},
{
title: 'Should return undefined for non presented extension',
path: '/dir4/file',
expected: undefined,
},
{
title: 'Should return undefined for unknown file',
path: '/dir5/file.suffix',
expected: undefined,
},
{
title: 'Should return js file with strange suffix filename',
path: '/dir5/file.suffix.ts',
expected: '/dir5/file.suffix.ts.js',
},
{
title: 'Should return index file for dir with suffixed name',
path: '/dir6/inner.suffix',
expected: '/dir6/inner.suffix/index.ts',
},
{
title: 'Should return file for import with extension',
path: '/dir1/file.ts',
expected: '/dir1/file.ts',
},
{
title: 'Should return file with .js ext instead of .ts',
path: '/dir2/inner/index.js',
expected: '/dir2/inner/index.js',
},
{
title: 'Should return css file that imported with query',
path: '/dir4/file.css?inline',
expected: '/dir4/file.css',
extensions: ['.js', '.css'],
},
{
title: 'Should return file with .mts',
path: '/file1.mts',
expected: '/file1.mts',
},
{
title: 'Should return file',
path: '/file1',
expected: '/file1.mts',
},
];
cases.forEach(({ title, path, expected, extensions }) => {
it(title, () => {
expect(findFile(path, extensions)).toEqual(expected);
});
});
});

View File

@ -0,0 +1,27 @@
import { existsSync } from 'node:fs';
import { resolve, basename, dirname } from 'node:path';
export function findFile(
path: string,
extensions: string[],
existsSyncImpl: typeof existsSync = existsSync
): string {
const queryLessPath = path.replace(/\?\S*$/, '');
for (const ext of extensions) {
const dir = dirname(path);
// Support file extensions such as .css and .js in the import path.
// While still allowing for '.suffix'
const name = basename(queryLessPath, ext);
const resolvedPath = resolve(dir, name + ext);
if (existsSyncImpl(resolvedPath)) {
return resolvedPath;
}
const resolvedIndexPath = resolve(path, `index${ext}`);
if (existsSyncImpl(resolvedIndexPath)) {
return resolvedIndexPath;
}
}
}