diff --git a/e2e/vite/src/vite.test.ts b/e2e/vite/src/vite.test.ts
index d1087fd689919f..40bbdb626e1321 100644
--- a/e2e/vite/src/vite.test.ts
+++ b/e2e/vite/src/vite.test.ts
@@ -1,3 +1,4 @@
+import { names } from '@nx/devkit';
import {
cleanupProject,
createFile,
@@ -240,6 +241,74 @@ describe('Vite Plugin', () => {
100_000;
});
+ describe.only('incremental building', () => {
+ const app = uniq('demo');
+ const lib = uniq('my-lib');
+ beforeAll(() => {
+ proj = newProject({ name: uniq('vite-incr-build') });
+ runCLI(`generate @nx/react:app ${app} --bundler=vite --no-interactive`);
+
+ // only this project will be directly used from dist
+ runCLI(
+ `generate @nx/react:lib ${lib}-buildable --unitTestRunner=none --bundler=vite --importPath="@acme/buildable" --no-interactive`
+ );
+
+ runCLI(
+ `generate @nx/react:lib ${lib} --unitTestRunner=none --bundler=none --importPath="@acme/non-buildable" --no-interactive`
+ );
+
+ // because the default js lib builds as cjs it cannot be loaded from dist
+ // so the paths plugin should always resolve to the libs source
+ runCLI(
+ `generate @nx/js:lib ${lib}-js --bundler=tsc --importPath="@acme/js-lib" --no-interactive`
+ );
+ const buildableLibCmp = names(`${lib}-buildable`).className;
+ const nonBuildableLibCmp = names(lib).className;
+ const buildableJsLibFn = names(`${lib}-js`).propertyName;
+
+ updateFile(`apps/${app}/src/app/app.tsx`, () => {
+ return `// eslint-disable-next-line @typescript-eslint/no-unused-vars
+import styles from './app.module.css';
+
+import NxWelcome from './nx-welcome';
+import { ${buildableLibCmp} } from '@acme/buildable';
+import { ${buildableJsLibFn} } from '@acme/js-lib';
+import { ${nonBuildableLibCmp} } from '@acme/non-buildable';
+
+export function App() {
+ return (
+
+ <${buildableLibCmp} />
+ <${nonBuildableLibCmp} />
+
{${buildableJsLibFn}()}
+
+
+ );
+}
+export default App;
+`;
+ });
+ });
+
+ afterAll(() => {
+ cleanupProject();
+ });
+
+ it('should build app from libs source', () => {
+ const results = runCLI(`build ${app} --buildLibsFromSource=true`);
+ expect(results).toContain('Successfully ran target build for project');
+ // this should be more modules than build from dist
+ expect(results).toContain('40 modules transformed');
+ });
+
+ it('should build app from libs dist', () => {
+ const results = runCLI(`build ${app} --buildLibsFromSource=false`);
+ expect(results).toContain('Successfully ran target build for project');
+ // this should be less modules than building from source
+ expect(results).toContain('38 modules transformed');
+ });
+ });
+
describe('should be able to create libs that use vitest', () => {
const lib = uniq('my-lib');
beforeEach(() => {
@@ -255,7 +324,6 @@ describe('Vite Plugin', () => {
`Successfully ran target test for project ${lib}`
);
- // TODO(caleb): run tests from project root and make sure they still work
const nestedResults = await runCLIAsync(`test ${lib} --skip-nx-cache`, {
cwd: `${tmpProjPath()}/libs/${lib}`,
});
diff --git a/packages/vite/package.json b/packages/vite/package.json
index 4a4eb6a9324dfd..59a2e6e432f2ec 100644
--- a/packages/vite/package.json
+++ b/packages/vite/package.json
@@ -34,7 +34,8 @@
"dotenv": "~10.0.0",
"enquirer": "~2.3.6",
"@nx/devkit": "file:../devkit",
- "@nx/js": "file:../js"
+ "@nx/js": "file:../js",
+ "tsconfig-paths": "^4.1.2"
},
"peerDependencies": {
"vite": "^4.3.4",
@@ -53,6 +54,7 @@
"./executors": "./executors.js",
"./src/executors/*/schema.json": "./src/executors/*/schema.json",
"./src/executors/*.impl": "./src/executors/*.impl.js",
- "./src/executors/*/compat": "./src/executors/*/compat.js"
+ "./src/executors/*/compat": "./src/executors/*/compat.js",
+ "./plugins/nx-tsconfig-paths.plugin": "./plugins/nx-tsconfig-paths.plugin.js"
}
}
diff --git a/packages/vite/plugins/nx-tsconfig-paths.plugin.ts b/packages/vite/plugins/nx-tsconfig-paths.plugin.ts
new file mode 100644
index 00000000000000..3bbc0c91929bd0
--- /dev/null
+++ b/packages/vite/plugins/nx-tsconfig-paths.plugin.ts
@@ -0,0 +1,91 @@
+import { stripIndents, workspaceRoot } from '@nx/devkit';
+import { existsSync } from 'node:fs';
+import { relative, join, resolve, posix } from 'node:path';
+import { loadConfig, createMatchPath, MatchPath } from 'tsconfig-paths';
+
+// TODO(caleb): should we provide a way to override anything for the plugin?
+
+export function nxViteTsPaths() {
+ let matchTsPathEsm: MatchPath;
+ let matchTsPathFallback: MatchPath | undefined;
+
+ return {
+ name: 'nx-vite-ts-paths',
+ configResolved(config: any) {
+ const projectRoot = config.root;
+ // TODO(caleb): verify on windows to see what type of paths vite returns posix vs win32
+ const projectRootFromWorkspaceRoot = relative(workspaceRoot, projectRoot);
+
+ const foundTsConfigPath = getTsConfig(
+ join(
+ workspaceRoot,
+ 'tmp',
+ projectRootFromWorkspaceRoot,
+ 'tsconfig.generated.json'
+ )
+ );
+ if (!foundTsConfigPath) {
+ throw new Error(stripIndents`Unable to find a tsconfig in the workspace!
+There should at least be a tsconfig.base.json or tsconfig.json in the root of the workspace ${workspaceRoot}`);
+ }
+ const parsed = loadConfig(foundTsConfigPath);
+
+ logIt('first parsed tsconfig: ', parsed);
+ if (parsed.resultType === 'failed') {
+ throw new Error(`Failed loading tsonfig at ${foundTsConfigPath}`);
+ }
+
+ matchTsPathEsm = createMatchPath(parsed.absoluteBaseUrl, parsed.paths, [
+ ['exports', '.', 'import'],
+ 'main',
+ ]);
+
+ const rootLevelTsConfig = getTsConfig(
+ join(workspaceRoot, 'tsconfig.base.json')
+ );
+ const rootLevelParsed = loadConfig(rootLevelTsConfig);
+ logIt('fallback parsed tsconfig: ', rootLevelParsed);
+ if (rootLevelParsed.resultType === 'success') {
+ matchTsPathFallback = createMatchPath(
+ rootLevelParsed.absoluteBaseUrl,
+ rootLevelParsed.paths,
+ ['main', 'module']
+ );
+ }
+ },
+ resolveId(source: string) {
+ let resolvedFile: string;
+ try {
+ resolvedFile = matchTsPathEsm(source);
+ } catch (e) {
+ logIt('Using fallback path matching.');
+ resolvedFile = matchTsPathFallback?.(source);
+ }
+
+ if (!resolvedFile) {
+ logIt(`Unable to resolve ${source} with tsconfig paths`);
+ }
+
+ return resolvedFile;
+ },
+ };
+}
+
+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') {
+ console.debug('[Nx Vite TsPaths]', ...msg);
+ }
+}
diff --git a/packages/vite/src/executors/build/build.impl.ts b/packages/vite/src/executors/build/build.impl.ts
index 015177879c8dda..8f3313c46c91a0 100644
--- a/packages/vite/src/executors/build/build.impl.ts
+++ b/packages/vite/src/executors/build/build.impl.ts
@@ -25,6 +25,7 @@ export async function* viteBuildExecutor(
const projectRoot =
context.projectsConfigurations.projects[context.projectName].root;
+ // TODO(caleb): do we need to register the paths anymore or just make tsconfig?
registerPaths(projectRoot, options, context);
const normalizedOptions = normalizeOptions(options);
diff --git a/packages/vite/src/executors/dev-server/dev-server.impl.ts b/packages/vite/src/executors/dev-server/dev-server.impl.ts
index f43342fa396627..7507400e57a240 100644
--- a/packages/vite/src/executors/dev-server/dev-server.impl.ts
+++ b/packages/vite/src/executors/dev-server/dev-server.impl.ts
@@ -20,6 +20,7 @@ export async function* viteDevServerExecutor(
const projectRoot =
context.projectsConfigurations.projects[context.projectName].root;
+ // TODO(caleb): do we need to register the paths anymore or just make tsconfig?
registerPaths(projectRoot, options, context);
// Retrieve the option for the configured buildTarget.
diff --git a/packages/vite/src/utils/executor-utils.ts b/packages/vite/src/utils/executor-utils.ts
index 470f1ff73d8038..11308e1e2f0d45 100644
--- a/packages/vite/src/utils/executor-utils.ts
+++ b/packages/vite/src/utils/executor-utils.ts
@@ -35,7 +35,9 @@ export function registerPaths(
const tsConfig = resolve(projectRoot, 'tsconfig.json');
options.buildLibsFromSource ??= true;
- if (!options.buildLibsFromSource) {
+ if (options.buildLibsFromSource) {
+ registerTsConfigPaths(tsConfig);
+ } else {
const { dependencies } = calculateProjectDependencies(
context.projectGraph,
context.root,
@@ -49,9 +51,6 @@ export function registerPaths(
projectRoot,
dependencies
);
-
registerTsConfigPaths(tmpTsConfig);
- } else {
- registerTsConfigPaths(tsConfig);
}
}
diff --git a/packages/vite/src/utils/generator-utils.ts b/packages/vite/src/utils/generator-utils.ts
index 7c5ee1deec5703..4430ec762888d8 100644
--- a/packages/vite/src/utils/generator-utils.ts
+++ b/packages/vite/src/utils/generator-utils.ts
@@ -583,13 +583,14 @@ export function createOrEditViteConfig(
host: 'localhost',
},`;
+ // viteTsConfigPaths({
+ // root: '${offsetFromRoot(projectConfig.root)}',
+ // }),
const pluginOption = `
plugins: [
${dtsPlugin}
${reactPlugin}
- viteTsConfigPaths({
- root: '${offsetFromRoot(projectConfig.root)}',
- }),
+ nxViteTsPaths(),
],
`;
@@ -628,7 +629,7 @@ export function createOrEditViteConfig(
///
import { defineConfig } from 'vite';
${reactPluginImportLine}
- import viteTsConfigPaths from 'vite-tsconfig-paths';
+ import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
${dtsImportLine}
export default defineConfig({