diff --git a/e2e/vite/src/vite.test.ts b/e2e/vite/src/vite.test.ts index cb8e31f36874e..e940d9117dc3c 100644 --- a/e2e/vite/src/vite.test.ts +++ b/e2e/vite/src/vite.test.ts @@ -158,7 +158,7 @@ describe('@nx/vite/plugin', () => { `generate @nx/react:library libs/${mylib} --bundler=none --unitTestRunner=vitest` ); 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( `libs/${mylib}/src/foo.spec.ts`, ` diff --git a/packages/vite/plugins/nx-tsconfig-paths.plugin.ts b/packages/vite/plugins/nx-tsconfig-paths.plugin.ts index cf8d9b3f0c707..18b2098062934 100644 --- a/packages/vite/plugins/nx-tsconfig-paths.plugin.ts +++ b/packages/vite/plugins/nx-tsconfig-paths.plugin.ts @@ -5,7 +5,7 @@ import { workspaceRoot, } from '@nx/devkit'; import { copyFileSync, existsSync } from 'node:fs'; -import { join, parse, relative, resolve } from 'node:path'; +import { join, relative, resolve } from 'node:path'; import { loadConfig, createMatchPath, @@ -18,6 +18,7 @@ import { } from '@nx/js/src/utils/buildable-libs-utils'; import { Plugin } from 'vite'; 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'; 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( - importPath.replace(normalizedImport, joinedPath) + importPath.replace(normalizedImport, joinedPath), + options.extensions ); } } 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; - } - } - } } diff --git a/packages/vite/src/utils/nx-tsconfig-paths-find-file.spec.ts b/packages/vite/src/utils/nx-tsconfig-paths-find-file.spec.ts new file mode 100644 index 0000000000000..4886e8bc53e45 --- /dev/null +++ b/packages/vite/src/utils/nx-tsconfig-paths-find-file.spec.ts @@ -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(); + 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); + }); + }); +}); diff --git a/packages/vite/src/utils/nx-tsconfig-paths-find-file.ts b/packages/vite/src/utils/nx-tsconfig-paths-find-file.ts new file mode 100644 index 0000000000000..06a9fcb53b0bb --- /dev/null +++ b/packages/vite/src/utils/nx-tsconfig-paths-find-file.ts @@ -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; + } + } +}