diff --git a/e2e/vite/src/vite.test.ts b/e2e/vite/src/vite.test.ts
index b946d883e499e..925a9104ea845 100644
--- a/e2e/vite/src/vite.test.ts
+++ b/e2e/vite/src/vite.test.ts
@@ -11,6 +11,7 @@ import {
promisifiedTreeKill,
readFile,
readJson,
+ removeFile,
rmDist,
runCLI,
runCLIAsync,
@@ -267,9 +268,8 @@ describe('Vite Plugin', () => {
const buildableJsLibFn = names(`${lib}-js`).propertyName;
updateFile(`apps/${app}/src/app/app.tsx`, () => {
- return `// eslint-disable-next-line @typescript-eslint/no-unused-vars
+ return `
import styles from './app.module.css';
-
import NxWelcome from './nx-welcome';
import { ${buildableLibCmp} } from '@acme/buildable';
import { ${buildableJsLibFn} } from '@acme/js-lib';
@@ -277,12 +277,12 @@ import { ${nonBuildableLibCmp} } from '@acme/non-buildable';
export function App() {
return (
-
- <${buildableLibCmp} />
- <${nonBuildableLibCmp} />
-
{${buildableJsLibFn}()}
-
-
+
+ <${buildableLibCmp} />
+ <${nonBuildableLibCmp} />
+
{${buildableJsLibFn}()}
+
+
);
}
export default App;
@@ -307,6 +307,24 @@ export default App;
// this should be less modules than building from source
expect(results).toContain('38 modules transformed');
});
+
+ it('should build app from libs without package.json in lib', () => {
+ removeFile(`libs/${lib}-buildable/package.json`);
+
+ const buildFromSourceResults = runCLI(
+ `build ${app} --buildLibsFromSource=true`
+ );
+ expect(buildFromSourceResults).toContain(
+ 'Successfully ran target build for project'
+ );
+
+ const noBuildFromSourceResults = runCLI(
+ `build ${app} --buildLibsFromSource=false`
+ );
+ expect(noBuildFromSourceResults).toContain(
+ 'Successfully ran target build for project'
+ );
+ });
});
describe('should be able to create libs that use vitest', () => {
diff --git a/packages/vite/plugins/nx-tsconfig-paths.plugin.ts b/packages/vite/plugins/nx-tsconfig-paths.plugin.ts
index afe9f478c0773..987b2995b0ba6 100644
--- a/packages/vite/plugins/nx-tsconfig-paths.plugin.ts
+++ b/packages/vite/plugins/nx-tsconfig-paths.plugin.ts
@@ -1,11 +1,49 @@
import { stripIndents, workspaceRoot } from '@nx/devkit';
import { existsSync } from 'node:fs';
import { relative, join, resolve } from 'node:path';
-import { loadConfig, createMatchPath, MatchPath } from 'tsconfig-paths';
+import {
+ loadConfig,
+ createMatchPath,
+ MatchPath,
+ ConfigLoaderSuccessResult,
+} from 'tsconfig-paths';
-export function nxViteTsPaths() {
+export interface nxViteTsPathsOptions {
+ /**
+ * Enable debug logging
+ * @default false
+ **/
+ debug?: boolean;
+ /**
+ * export fields in package.json to use for resolving
+ * @default [['exports', '.', 'import'], 'module', 'main']
+ *
+ * fallback resolution will use ['main', 'module']
+ **/
+ mainFields?: (string | string[])[];
+ /**
+ * extensions to check when resolving files when package.json resolution fails
+ * @default ['.ts', '.tsx', '.js', '.jsx', '.json', '.mjs', '.cjs']
+ **/
+ extensions?: string[];
+}
+
+export function nxViteTsPaths(options: nxViteTsPathsOptions = {}) {
let matchTsPathEsm: MatchPath;
let matchTsPathFallback: MatchPath | undefined;
+ let tsConfigPathsEsm: ConfigLoaderSuccessResult;
+ let tsConfigPathsFallback: ConfigLoaderSuccessResult;
+
+ options.extensions ??= [
+ '.ts',
+ '.tsx',
+ '.js',
+ '.jsx',
+ '.json',
+ '.mjs',
+ '.cjs',
+ ];
+ options.mainFields ??= [['exports', '.', 'import'], 'module', 'main'];
return {
name: 'nx-vite-ts-paths',
@@ -31,12 +69,13 @@ There should at least be a tsconfig.base.json or tsconfig.json in the root of th
if (parsed.resultType === 'failed') {
throw new Error(`Failed loading tsonfig at ${foundTsConfigPath}`);
}
+ tsConfigPathsEsm = parsed;
- matchTsPathEsm = createMatchPath(parsed.absoluteBaseUrl, parsed.paths, [
- ['exports', '.', 'import'],
- 'module',
- 'main',
- ]);
+ matchTsPathEsm = createMatchPath(
+ parsed.absoluteBaseUrl,
+ parsed.paths,
+ options.mainFields
+ );
const rootLevelTsConfig = getTsConfig(
join(workspaceRoot, 'tsconfig.base.json')
@@ -44,6 +83,7 @@ There should at least be a tsconfig.base.json or tsconfig.json in the root of th
const rootLevelParsed = loadConfig(rootLevelTsConfig);
logIt('fallback parsed tsconfig: ', rootLevelParsed);
if (rootLevelParsed.resultType === 'success') {
+ tsConfigPathsFallback = rootLevelParsed;
matchTsPathFallback = createMatchPath(
rootLevelParsed.absoluteBaseUrl,
rootLevelParsed.paths,
@@ -51,39 +91,82 @@ There should at least be a tsconfig.base.json or tsconfig.json in the root of th
);
}
},
- resolveId(source: string) {
+ resolveId(importPath: string) {
let resolvedFile: string;
try {
- resolvedFile = matchTsPathEsm(source);
+ resolvedFile = matchTsPathEsm(importPath);
} catch (e) {
logIt('Using fallback path matching.');
- resolvedFile = matchTsPathFallback?.(source);
+ resolvedFile = matchTsPathFallback?.(importPath);
}
if (!resolvedFile) {
- logIt(`Unable to resolve ${source} with tsconfig paths`);
+ if (tsConfigPathsEsm || tsConfigPathsFallback) {
+ logIt(
+ `Unable to resolve ${importPath} with tsconfig paths. Using fallback file matching.`
+ );
+ resolvedFile =
+ loadFileFromPaths(tsConfigPathsEsm, importPath) ||
+ loadFileFromPaths(tsConfigPathsFallback, importPath);
+ } else {
+ logIt(`Unable to resolve ${importPath} with tsconfig paths`);
+ }
}
- return resolvedFile;
+ logIt(`Resolved ${importPath} to ${resolvedFile}`);
+ // Returning null defers to other resolveId functions and eventually the default resolution behavior
+ // https://rollupjs.org/plugin-development/#resolveid
+ return resolvedFile || null;
},
};
-}
-function getTsConfig(preferredTsConfigPath: string): string {
- return [
- resolve(preferredTsConfigPath),
- resolve(join(workspaceRoot, 'tsconfig.base.json')),
- resolve(join(workspaceRoot, 'tsconfig.json')),
- ].find((tsPath) => {
- if (existsSync(tsPath)) {
- logIt('Found tsconfig at', tsPath);
- return tsPath;
+ function getTsConfig(preferredTsConfigPath: string): string {
+ return [
+ resolve(preferredTsConfigPath),
+ resolve(join(workspaceRoot, 'tsconfig.base.json')),
+ resolve(join(workspaceRoot, 'tsconfig.json')),
+ ].find((tsPath) => {
+ if (existsSync(tsPath)) {
+ logIt('Found tsconfig at', tsPath);
+ return tsPath;
+ }
+ });
+ }
+
+ function logIt(...msg: any[]) {
+ if (process.env.NX_VERBOSE_LOGGING === 'true' || options?.debug) {
+ console.debug('\n[Nx Vite TsPaths]', ...msg);
}
- });
-}
+ }
-function logIt(...msg: any[]) {
- if (process.env.NX_VERBOSE_LOGGING === 'true') {
- console.debug('[Nx Vite TsPaths]', ...msg);
+ function loadFileFromPaths(
+ tsconfig: ConfigLoaderSuccessResult,
+ importPath: string
+ ) {
+ logIt(
+ `Trying to resolve file from config in ${tsconfig.configFileAbsolutePath}`
+ );
+ let resolvedFile: string;
+ for (const alias in tsconfig.paths) {
+ const paths = tsconfig.paths[alias];
+
+ const normalizedImport = alias.replace(/\/\*$/, '');
+
+ if (importPath.startsWith(normalizedImport)) {
+ const path = (tsconfig.absoluteBaseUrl, paths[0].replace(/\/\*$/, ''));
+ resolvedFile = findFile(importPath.replace(normalizedImport, path));
+ }
+ }
+
+ return resolvedFile;
+ }
+
+ function findFile(path: string): string {
+ for (const ext of options.extensions) {
+ const r = resolve(path + ext);
+ if (existsSync(r)) {
+ return r;
+ }
+ }
}
}