diff --git a/docs/generated/packages/vite/executors/build.json b/docs/generated/packages/vite/executors/build.json
index cd55464e5841c..76f1b2f215374 100644
--- a/docs/generated/packages/vite/executors/build.json
+++ b/docs/generated/packages/vite/executors/build.json
@@ -26,11 +26,6 @@
"description": "Skip type-checking via TypeScript. Skipping type-checking speeds up the build but type errors are not caught.",
"default": false
},
- "base": {
- "type": "string",
- "description": "Base public path when served in development or production.",
- "alias": "baseHref"
- },
"configFile": {
"type": "string",
"description": "The name of the Vite.js configuration file.",
@@ -59,49 +54,6 @@
},
"default": []
},
- "emptyOutDir": {
- "description": "When set to false, outputPath will not be emptied during the build process.",
- "type": "boolean",
- "default": true
- },
- "sourcemap": {
- "description": "Output sourcemaps. Use 'hidden' for use with error reporting tools without generating sourcemap comment.",
- "oneOf": [{ "type": "boolean" }, { "type": "string" }]
- },
- "target": {
- "description": "Browser compatibility target for the final bundle. For more info: https://vitejs.dev/config/build-options.html#build-target",
- "type": "string"
- },
- "minify": {
- "description": "Output sourcemaps. Use 'hidden' for use with error reporting tools without generating sourcemap comment.",
- "oneOf": [{ "type": "boolean" }, { "type": "string" }]
- },
- "manifest": {
- "description": "Output sourcemaps. Use 'hidden' for use with error reporting tools without generating sourcemap comment.",
- "oneOf": [{ "type": "boolean" }, { "type": "string" }]
- },
- "ssrManifest": {
- "description": "When set to true, the build will also generate an SSR manifest for determining style links and asset preload directives in production. When the value is a string, it will be used as the manifest file name.",
- "oneOf": [{ "type": "boolean" }, { "type": "string" }]
- },
- "ssr": {
- "description": "Produce SSR-oriented build. The value can be a string to directly specify the SSR entry, or true, which requires specifying the SSR entry via rollupOptions.input.",
- "oneOf": [{ "type": "boolean" }, { "type": "string" }]
- },
- "logLevel": {
- "type": "string",
- "description": "Adjust console output verbosity.",
- "enum": ["info", "warn", "error", "silent"]
- },
- "mode": { "type": "string", "description": "Mode to run the build in." },
- "force": {
- "description": "Force the optimizer to ignore the cache and re-bundle",
- "type": "boolean"
- },
- "cssCodeSplit": {
- "description": "Enable/disable CSS code splitting. When enabled, CSS imported in async chunks will be inlined into the async chunk itself and inserted when the chunk is loaded.",
- "type": "boolean"
- },
"watch": {
"description": "Enable re-building when files change.",
"oneOf": [{ "type": "boolean" }, { "type": "object" }],
diff --git a/docs/generated/packages/vite/executors/dev-server.json b/docs/generated/packages/vite/executors/dev-server.json
index 9c25f72d7e9af..7cba326d7d13b 100644
--- a/docs/generated/packages/vite/executors/dev-server.json
+++ b/docs/generated/packages/vite/executors/dev-server.json
@@ -27,45 +27,6 @@
"type": "string",
"description": "Path to the proxy configuration file.",
"x-completion-type": "file"
- },
- "port": {
- "type": "number",
- "description": "Port to listen on.",
- "x-priority": "important"
- },
- "host": {
- "description": "Specify which IP addresses the server should listen on.",
- "oneOf": [{ "type": "boolean" }, { "type": "string" }]
- },
- "https": {
- "oneOf": [{ "type": "boolean" }, { "type": "object" }],
- "description": "Serve using HTTPS. https://vitejs.dev/config/server-options.html#server-https"
- },
- "hmr": {
- "description": "Enable hot module replacement. For more options, use the 'hmr' option in the Vite configuration file.",
- "type": "boolean"
- },
- "open": {
- "description": "Automatically open the app in the browser on server start. When the value is a string, it will be used as the URL's pathname.",
- "oneOf": [{ "type": "boolean" }, { "type": "string" }]
- },
- "cors": {
- "description": "Configure CORS for the dev server.",
- "type": "boolean"
- },
- "logLevel": {
- "type": "string",
- "description": "Adjust console output verbosity.",
- "enum": ["info", "warn", "error", "silent"]
- },
- "mode": { "type": "string", "description": "Mode to run the server in." },
- "clearScreen": {
- "description": "Set to false to prevent Vite from clearing the terminal screen when logging certain messages.",
- "type": "boolean"
- },
- "force": {
- "description": "Force the optimizer to ignore the cache and re-bundle",
- "type": "boolean"
}
},
"definitions": {},
diff --git a/docs/generated/packages/vite/executors/preview-server.json b/docs/generated/packages/vite/executors/preview-server.json
index 00eb6fcbd30be..e2081c0e4539e 100644
--- a/docs/generated/packages/vite/executors/preview-server.json
+++ b/docs/generated/packages/vite/executors/preview-server.json
@@ -22,29 +22,6 @@
"description": "Path to the proxy configuration file.",
"x-completion-type": "file"
},
- "port": { "type": "number", "description": "Port to listen on." },
- "host": {
- "description": "Specify which IP addresses the server should listen on.",
- "oneOf": [{ "type": "boolean" }, { "type": "string" }]
- },
- "https": {
- "oneOf": [{ "type": "boolean" }, { "type": "object" }],
- "description": "Serve using HTTPS. https://vitejs.dev/config/server-options.html#server-https"
- },
- "open": {
- "description": "Automatically open the app in the browser on server start. When the value is a string, it will be used as the URL's pathname.",
- "oneOf": [{ "type": "boolean" }, { "type": "string" }]
- },
- "logLevel": {
- "type": "string",
- "description": "Adjust console output verbosity.",
- "enum": ["info", "warn", "error", "silent"]
- },
- "mode": { "type": "string", "description": "Mode to run the server in." },
- "clearScreen": {
- "description": "Set to false to prevent Vite from clearing the terminal screen when logging certain messages.",
- "type": "boolean"
- },
"staticFilePath": {
"type": "string",
"description": "Path where the build artifacts are located. If not provided then it will be infered from the buildTarget executor options as outputPath",
diff --git a/docs/generated/packages/vite/executors/test.json b/docs/generated/packages/vite/executors/test.json
index 4f71b6bb7a155..86c1585700704 100644
--- a/docs/generated/packages/vite/executors/test.json
+++ b/docs/generated/packages/vite/executors/test.json
@@ -9,50 +9,12 @@
"description": "Test using Vitest.",
"type": "object",
"properties": {
- "config": {
+ "configFile": {
"type": "string",
"description": "The path to the local vitest config",
"x-completion-type": "file",
- "x-completion-glob": "@(vitest|vite).config@(.js|.ts)"
- },
- "passWithNoTests": {
- "type": "boolean",
- "default": true,
- "description": "Pass the test even if no tests are found"
- },
- "testNamePattern": {
- "type": "string",
- "description": "Run tests with full names matching the pattern"
- },
- "mode": {
- "type": "string",
- "enum": ["test", "benchmark", "typecheck"],
- "default": "test",
- "description": "The mode that vitest will run on",
- "x-priority": "important"
- },
- "watch": {
- "type": "boolean",
- "default": false,
- "description": "Enable watch mode"
- },
- "reporters": {
- "type": "array",
- "items": { "type": "string" },
- "description": "An array of reporters to pass to vitest"
- },
- "update": {
- "type": "boolean",
- "default": false,
- "alias": "u",
- "description": "Update snapshots",
- "x-priority": "important"
- },
- "coverage": {
- "type": "boolean",
- "default": false,
- "description": "Enable coverage report",
- "x-priority": "important"
+ "x-completion-glob": "@(vitest|vite).config@(.js|.ts)",
+ "aliases": ["config"]
},
"reportsDirectory": {
"type": "string",
@@ -62,6 +24,10 @@
"aliases": ["testFile"],
"type": "array",
"items": { "type": "string" }
+ },
+ "watch": {
+ "description": "Watch files for changes and rerun tests related to changed files.",
+ "type": "boolean"
}
},
"required": [],
diff --git a/e2e/nuxt/src/nuxt.test.ts b/e2e/nuxt/src/nuxt.test.ts
index 9305ce7e3e09c..f7e5cdfe00cdf 100644
--- a/e2e/nuxt/src/nuxt.test.ts
+++ b/e2e/nuxt/src/nuxt.test.ts
@@ -38,7 +38,7 @@ describe('Nuxt Plugin', () => {
it('should test application', async () => {
const result = runCLI(`test ${app}`);
expect(result).toContain(`Successfully ran target test for project ${app}`);
- });
+ }, 150_000);
it('should lint application', async () => {
const result = runCLI(`lint ${app}`);
diff --git a/e2e/vite/src/vite.test.ts b/e2e/vite/src/vite.test.ts
index daa6207d609dc..7b120d0984b5b 100644
--- a/e2e/vite/src/vite.test.ts
+++ b/e2e/vite/src/vite.test.ts
@@ -6,10 +6,8 @@ import {
exists,
fileExists,
getPackageManagerCommand,
- killPorts,
listFiles,
newProject,
- promisifiedTreeKill,
readFile,
readJson,
removeFile,
@@ -17,7 +15,6 @@ import {
runCLI,
runCommand,
runCLIAsync,
- runCommandUntil,
tmpProjPath,
uniq,
updateFile,
@@ -32,87 +29,11 @@ describe('Vite Plugin', () => {
let proj: string;
describe('Vite on React apps', () => {
- describe('convert React webpack app to vite using the vite:configuration generator', () => {
- beforeEach(() => {
- proj = newProject();
- runCLI(`generate @nx/react:app ${myApp} --bundler=webpack`);
- runCLI(`generate @nx/vite:configuration ${myApp}`);
- });
- afterEach(() => cleanupProject());
-
- it('should serve application in dev mode with custom options', async () => {
- const port = 4212;
- const p = await runCommandUntil(
- `run ${myApp}:serve --port=${port} --https=true`,
- (output) => {
- return (
- output.includes('Local:') &&
- output.includes(`:${port}`) &&
- output.includes('https')
- );
- }
- );
- try {
- await promisifiedTreeKill(p.pid, 'SIGKILL');
- await killPorts(port);
- } catch (e) {
- // ignore
- }
- }, 200_000);
-
- it('should test application', async () => {
- const result = await runCLIAsync(`test ${myApp}`);
- expect(result.combinedOutput).toContain(
- `Successfully ran target test for project ${myApp}`
- );
- });
- });
-
describe('set up new React app with --bundler=vite option', () => {
beforeEach(async () => {
proj = newProject();
runCLI(`generate @nx/react:app ${myApp} --bundler=vite`);
createFile(`apps/${myApp}/public/hello.md`, `# Hello World`);
- updateFile(
- `apps/${myApp}/src/environments/environment.prod.ts`,
- `export const environment = {
- production: true,
- myTestVar: 'MyProductionValue',
- };`
- );
- updateFile(
- `apps/${myApp}/src/environments/environment.ts`,
- `export const environment = {
- production: false,
- myTestVar: 'MyDevelopmentValue',
- };`
- );
-
- updateFile(
- `apps/${myApp}/src/app/app.tsx`,
- `
- import { environment } from './../environments/environment';
- export function App() {
- return (
- <>
-
{environment.myTestVar}
- Welcome ${myApp}!
- >
- );
- }
- export default App;
- `
- );
-
- updateJson(join('apps', myApp, 'project.json'), (config) => {
- config.targets.build.options.fileReplacements = [
- {
- replace: `apps/${myApp}/src/environments/environment.ts`,
- with: `apps/${myApp}/src/environments/environment.prod.ts`,
- },
- ];
- return config;
- });
});
afterEach(() => cleanupProject());
it('should build application', async () => {
@@ -120,14 +41,6 @@ describe('Vite Plugin', () => {
expect(readFile(`dist/apps/${myApp}/favicon.ico`)).toBeDefined();
expect(readFile(`dist/apps/${myApp}/hello.md`)).toBeDefined();
expect(readFile(`dist/apps/${myApp}/index.html`)).toBeDefined();
- const fileArray = listFiles(`dist/apps/${myApp}/assets`);
- const mainBundle = fileArray.find((file) => file.endsWith('.js'));
- expect(readFile(`dist/apps/${myApp}/assets/${mainBundle}`)).toContain(
- 'MyProductionValue'
- );
- expect(
- readFile(`dist/apps/${myApp}/assets/${mainBundle}`)
- ).not.toContain('MyDevelopmentValue');
rmDist();
}, 200_000);
});
@@ -202,48 +115,7 @@ describe('Vite Plugin', () => {
}, 200_000);
});
- describe('convert @nx/web webpack app to vite using the vite:configuration generator', () => {
- beforeEach(() => {
- proj = newProject();
- runCLI(`generate @nx/web:app ${myApp} --bundler=webpack`);
- runCLI(`generate @nx/vite:configuration ${myApp}`);
- });
- afterEach(() => cleanupProject());
- it('should build application', async () => {
- runCLI(`build ${myApp}`);
- expect(readFile(`dist/apps/${myApp}/index.html`)).toBeDefined();
- const fileArray = listFiles(`dist/apps/${myApp}/assets`);
- const mainBundle = fileArray.find((file) => file.endsWith('.js'));
- expect(
- readFile(`dist/apps/${myApp}/assets/${mainBundle}`)
- ).toBeDefined();
- rmDist();
- }, 200_000);
-
- it('should serve application in dev mode with custom port', async () => {
- const port = 4212;
- const p = await runCommandUntil(
- `run ${myApp}:serve --port=${port}`,
- (output) => {
- return output.includes('Local:') && output.includes(`:${port}`);
- }
- );
- try {
- await promisifiedTreeKill(p.pid, 'SIGKILL');
- await killPorts(port);
- } catch {
- // ignore
- }
- }, 200_000);
-
- it('should test application', async () => {
- const result = await runCLIAsync(`test ${myApp}`);
- expect(result.combinedOutput).toContain(
- `Successfully ran target test for project ${myApp}`
- );
- });
- }),
- 100_000;
+ 100_000;
});
describe('incremental building', () => {
@@ -363,6 +235,8 @@ export default App;
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
+ root: __dirname,
+ cacheDir: '../../node_modules/.vite/libs/${lib}',
server: {
port: 4200,
host: 'localhost',
@@ -371,14 +245,18 @@ export default App;
react(),
nxViteTsPaths()
],
+ build: {
+ outDir: '../../dist/libs/${lib}',
+ },
test: {
globals: true,
cache: {
- dir: './node_modules/.vitest',
+ dir: '../../node_modules/.vitest',
},
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
coverage: {
+ reportsDirectory: '../../coverage/libs/${lib}',
provider: "v8",
enabled: true,
lines: 100,
@@ -531,7 +409,7 @@ export default defineConfig({
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
- cacheDir: './node_modules/.vite/root-app',
+ cacheDir: '../../node_modules/.vite/root-app',
server: {
port: 4200,
host: 'localhost',
diff --git a/e2e/web/src/web-vite.test.ts b/e2e/web/src/web-vite.test.ts
index e5fbe1f7c6795..4145a59563343 100644
--- a/e2e/web/src/web-vite.test.ts
+++ b/e2e/web/src/web-vite.test.ts
@@ -61,8 +61,8 @@ describe('Web Components Applications with bundler set as vite', () => {
`dist/apps/${appName}/_should_remove.txt`,
`dist/apps/_should_not_remove.txt`
);
- runCLI(`build ${appName}`);
- runCLI(`build ${libName}`);
+ runCLI(`build ${appName} --emptyOutDir`);
+ runCLI(`build ${libName} --emptyOutDir`);
checkFilesDoNotExist(
`dist/apps/${appName}/_should_remove.txt`,
`dist/libs/${libName}/_should_remove.txt`
diff --git a/packages/nuxt/src/generators/application/__snapshots__/application.spec.ts.snap b/packages/nuxt/src/generators/application/__snapshots__/application.spec.ts.snap
index 20dcf5bca6c6a..01182ebb00b26 100644
--- a/packages/nuxt/src/generators/application/__snapshots__/application.spec.ts.snap
+++ b/packages/nuxt/src/generators/application/__snapshots__/application.spec.ts.snap
@@ -129,8 +129,12 @@ export default defineVitestConfig({
cache: {
dir: '../node_modules/.vitest',
},
- include: ['my-app/src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
environment: 'nuxt',
+ include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+ coverage: {
+ reportsDirectory: '../coverage/app5176218',
+ provider: 'v8',
+ },
},
});
"
diff --git a/packages/nuxt/src/generators/application/lib/add-vitest.ts b/packages/nuxt/src/generators/application/lib/add-vitest.ts
index 9eea140959062..69aa66315c550 100644
--- a/packages/nuxt/src/generators/application/lib/add-vitest.ts
+++ b/packages/nuxt/src/generators/application/lib/add-vitest.ts
@@ -54,12 +54,16 @@ export function addVitest(
export default defineVitestConfig({
plugins: [nxViteTsPaths()],
test: {
- globals: true,
- cache: {
+ globals: true,
+ cache: {
dir: '${projectOffsetFromRoot}node_modules/.vitest',
- },
- include: ['${projectRoot}/src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
- environment: 'nuxt',
+ },
+ environment: 'nuxt',
+ include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+ coverage: {
+ reportsDirectory: '${projectOffsetFromRoot}coverage/app5176218',
+ provider: 'v8',
+ },
},
});
`
diff --git a/packages/react/src/generators/library/__snapshots__/library.spec.ts.snap b/packages/react/src/generators/library/__snapshots__/library.spec.ts.snap
index f89fe74f15269..4d4970fca14d7 100644
--- a/packages/react/src/generators/library/__snapshots__/library.spec.ts.snap
+++ b/packages/react/src/generators/library/__snapshots__/library.spec.ts.snap
@@ -6,6 +6,7 @@ import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
+ root: __dirname,
cacheDir: '../node_modules/.vite/my-lib',
plugins: [react(), nxViteTsPaths()],
@@ -20,6 +21,7 @@ export default defineConfig({
cache: { dir: '../node_modules/.vitest' },
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+ coverage: { reportsDirectory: '../coverage/my-lib', provider: 'v8' },
},
});
"
@@ -34,6 +36,7 @@ import * as path from 'path';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
+ root: __dirname,
cacheDir: '../node_modules/.vite/my-lib',
plugins: [
@@ -54,6 +57,7 @@ export default defineConfig({
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
+ outDir: '../dist/my-lib',
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
@@ -76,6 +80,11 @@ export default defineConfig({
},
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+
+ coverage: {
+ reportsDirectory: '../coverage/my-lib',
+ provider: 'v8',
+ },
},
});
"
diff --git a/packages/vite/migrations.json b/packages/vite/migrations.json
index 386ae2925456d..bbca74289a12e 100644
--- a/packages/vite/migrations.json
+++ b/packages/vite/migrations.json
@@ -40,6 +40,11 @@
"version": "17.1.0-beta.2",
"description": "Move target defaults",
"implementation": "./src/migrations/update-17-1-0/move-target-defaults"
+ },
+ "update-vite-config": {
+ "version": "17.2.0-beta.10",
+ "description": "Update vite config.",
+ "implementation": "./src/migrations/update-17-2-0/update-vite-config"
}
},
"packageJsonUpdates": {
diff --git a/packages/vite/package.json b/packages/vite/package.json
index 36c19d83495d1..a86c4f6e6ea6b 100644
--- a/packages/vite/package.json
+++ b/packages/vite/package.json
@@ -53,6 +53,7 @@
"./src/executors/*/schema.json": "./src/executors/*/schema.json",
"./src/executors/*.impl": "./src/executors/*.impl.js",
"./src/executors/*/compat": "./src/executors/*/compat.js",
- "./plugins/nx-tsconfig-paths.plugin": "./plugins/nx-tsconfig-paths.plugin.js"
+ "./plugins/nx-tsconfig-paths.plugin": "./plugins/nx-tsconfig-paths.plugin.js",
+ "./plugins/rollup-replace-files.plugin": "./plugins/rollup-replace-files.plugin.js"
}
}
diff --git a/packages/vite/plugins/rollup-replace-files.plugin.ts b/packages/vite/plugins/rollup-replace-files.plugin.ts
index 9abe528c66215..76e0dffc3a1ad 100644
--- a/packages/vite/plugins/rollup-replace-files.plugin.ts
+++ b/packages/vite/plugins/rollup-replace-files.plugin.ts
@@ -3,9 +3,19 @@
/**
* @function replaceFiles
* @param {FileReplacement[]} replacements
- * @return {({name: "rollup-plugin-replace-files", enforce: "pre", Promise})}
+ * @return {({name: "rollup-plugin-replace-files", enforce: "pre" | "post" | undefined, Promise})}
*/
-export default function replaceFiles(replacements: FileReplacement[]) {
+export default function replaceFiles(replacements: FileReplacement[]): {
+ name: string;
+ enforce: 'pre' | 'post' | undefined;
+ resolveId(
+ source: any,
+ importer: any,
+ options: any
+ ): Promise<{
+ id: string;
+ }>;
+} {
if (!replacements?.length) {
return null;
}
diff --git a/packages/vite/src/executors/build/build.impl.ts b/packages/vite/src/executors/build/build.impl.ts
index 922e508f04291..b287d9871c06b 100644
--- a/packages/vite/src/executors/build/build.impl.ts
+++ b/packages/vite/src/executors/build/build.impl.ts
@@ -1,14 +1,15 @@
import {
detectPackageManager,
ExecutorContext,
+ joinPathFragments,
logger,
+ offsetFromRoot,
stripIndents,
writeJsonFile,
} from '@nx/devkit';
import {
getProjectTsConfigPath,
- getViteBuildOptions,
- getViteSharedConfig,
+ normalizeViteConfigFilePath,
} from '../../utils/options-utils';
import { ViteBuildExecutorOptions } from './schema';
import {
@@ -18,33 +19,63 @@ import {
getLockFileName,
} from '@nx/js';
import { existsSync, writeFileSync } from 'fs';
-import { resolve } from 'path';
+import { relative, resolve } from 'path';
import { createAsyncIterable } from '@nx/devkit/src/utils/async-iterable';
import {
createBuildableTsConfig,
validateTypes,
} from '../../utils/executor-utils';
+import { BuildOptions } from 'vite';
export async function* viteBuildExecutor(
- options: ViteBuildExecutorOptions,
+ options: Record & ViteBuildExecutorOptions,
context: ExecutorContext
) {
// Allows ESM to be required in CJS modules. Vite will be published as ESM in the future.
- const { mergeConfig, build } = await (Function(
+ const { mergeConfig, build, loadConfigFromFile } = await (Function(
'return import("vite")'
)() as Promise);
-
const projectRoot =
context.projectsConfigurations.projects[context.projectName].root;
-
createBuildableTsConfig(projectRoot, options, context);
- const normalizedOptions = normalizeOptions(options);
+ const viteConfigPath = normalizeViteConfigFilePath(
+ context.root,
+ projectRoot,
+ options.configFile
+ );
+ const root =
+ projectRoot === '.'
+ ? process.cwd()
+ : relative(context.cwd, joinPathFragments(context.root, projectRoot));
+
+ const { buildOptions, otherOptions } = await getExtraArgs(options);
+
+ const resolved = await loadConfigFromFile(
+ {
+ mode: otherOptions?.mode ?? 'production',
+ command: 'build',
+ },
+ viteConfigPath
+ );
+
+ const outDir =
+ joinPathFragments(offsetFromRoot(projectRoot), options.outputPath) ??
+ resolved?.config?.build?.outDir;
const buildConfig = mergeConfig(
- getViteSharedConfig(normalizedOptions, false, context),
{
- build: getViteBuildOptions(normalizedOptions, context),
+ // This should not be needed as it's going to be set in vite.config.ts
+ // but leaving it here in case someone did not migrate correctly
+ root: resolved.config.root ?? root,
+ configFile: viteConfigPath,
+ },
+ {
+ build: {
+ outDir,
+ ...buildOptions,
+ },
+ ...otherOptions,
}
);
@@ -60,7 +91,14 @@ export async function* viteBuildExecutor(
const libraryPackageJson = resolve(projectRoot, 'package.json');
const rootPackageJson = resolve(context.root, 'package.json');
- const distPackageJson = resolve(normalizedOptions.outputPath, 'package.json');
+
+ // Here, we want the outdir relative to the workspace root.
+ // So, we calculate the relative path from the workspace root to the outdir.
+ const outDirRelativeToWorkspaceRoot = outDir.replaceAll('../', '');
+ const distPackageJson = resolve(
+ outDirRelativeToWorkspaceRoot,
+ 'package.json'
+ );
// Generate a package.json if option has been set.
if (options.generatePackageJson) {
@@ -83,7 +121,10 @@ export async function* viteBuildExecutor(
builtPackageJson.type = 'module';
- writeJsonFile(`${options.outputPath}/package.json`, builtPackageJson);
+ writeJsonFile(
+ `${outDirRelativeToWorkspaceRoot}/package.json`,
+ builtPackageJson
+ );
const packageManager = detectPackageManager(context.root);
const lockFile = createLockFile(
@@ -92,7 +133,7 @@ export async function* viteBuildExecutor(
packageManager
);
writeFileSync(
- `${options.outputPath}/${getLockFileName(packageManager)}`,
+ `${outDirRelativeToWorkspaceRoot}/${getLockFileName(packageManager)}`,
lockFile,
{
encoding: 'utf-8',
@@ -107,7 +148,7 @@ export async function* viteBuildExecutor(
) {
await copyAssets(
{
- outputPath: normalizedOptions.outputPath,
+ outputPath: outDirRelativeToWorkspaceRoot,
assets: [
{
input: projectRoot,
@@ -142,22 +183,66 @@ export async function* viteBuildExecutor(
} else {
const output = watcherOrOutput?.['output'] || watcherOrOutput?.[0]?.output;
const fileName = output?.[0]?.fileName || 'main.cjs';
- const outfile = resolve(normalizedOptions.outputPath, fileName);
+ const outfile = resolve(outDirRelativeToWorkspaceRoot, fileName);
yield { success: true, outfile };
}
}
-function normalizeOptions(options: ViteBuildExecutorOptions) {
- const normalizedOptions = { ...options };
+async function getExtraArgs(options: ViteBuildExecutorOptions): Promise<{
+ buildOptions: BuildOptions;
+ otherOptions: Record;
+}> {
+ // support passing extra args to vite cli
+ const schema = await import('./schema.json');
+ const extraArgs = {};
+ for (const key of Object.keys(options)) {
+ if (!schema.properties[key]) {
+ extraArgs[key] = options[key];
+ }
+ }
- // coerce watch to null or {} to match with Vite's watch config
- if (options.watch === false) {
- normalizedOptions.watch = null;
- } else if (options.watch === true) {
- normalizedOptions.watch = {};
+ const buildOptions = {} as BuildOptions;
+ const buildSchemaKeys = [
+ 'target',
+ 'polyfillModulePreload',
+ 'modulePreload',
+ 'outDir',
+ 'assetsDir',
+ 'assetsInlineLimit',
+ 'cssCodeSplit',
+ 'cssTarget',
+ 'cssMinify',
+ 'sourcemap',
+ 'minify',
+ 'terserOptions',
+ 'rollupOptions',
+ 'commonjsOptions',
+ 'dynamicImportVarsOptions',
+ 'write',
+ 'emptyOutDir',
+ 'copyPublicDir',
+ 'manifest',
+ 'lib',
+ 'ssr',
+ 'ssrManifest',
+ 'ssrEmitAssets',
+ 'reportCompressedSize',
+ 'chunkSizeWarningLimit',
+ 'watch',
+ ];
+ const otherOptions = {};
+ for (const key of Object.keys(extraArgs)) {
+ if (buildSchemaKeys.includes(key)) {
+ buildOptions[key] = extraArgs[key];
+ } else {
+ otherOptions[key] = extraArgs[key];
+ }
}
- return normalizedOptions;
+ return {
+ buildOptions,
+ otherOptions,
+ };
}
export default viteBuildExecutor;
diff --git a/packages/vite/src/executors/build/schema.d.ts b/packages/vite/src/executors/build/schema.d.ts
index e45684e19cb9c..b585017fd9803 100644
--- a/packages/vite/src/executors/build/schema.d.ts
+++ b/packages/vite/src/executors/build/schema.d.ts
@@ -1,23 +1,11 @@
import type { FileReplacement } from '../../plugins/rollup-replace-files.plugin';
export interface ViteBuildExecutorOptions {
- outputPath: string;
- emptyOutDir?: boolean;
- base?: string;
+ outputPath?: string;
+ skipTypeCheck?: boolean;
configFile?: string;
fileReplacements?: FileReplacement[];
- force?: boolean;
- sourcemap?: boolean | 'inline' | 'hidden';
- minify?: boolean | 'esbuild' | 'terser';
- manifest?: boolean | string;
- ssrManifest?: boolean | string;
- logLevel?: 'info' | 'warn' | 'error' | 'silent';
- mode?: string;
- ssr?: boolean | string;
- watch?: object | boolean;
- target?: string | string[];
+ watch?: boolean;
generatePackageJson?: boolean;
includeDevDependenciesInPackageJson?: boolean;
- cssCodeSplit?: boolean;
buildLibsFromSource?: boolean;
- skipTypeCheck?: boolean;
}
diff --git a/packages/vite/src/executors/build/schema.json b/packages/vite/src/executors/build/schema.json
index 12bbaff653200..dee40e8e5edf2 100644
--- a/packages/vite/src/executors/build/schema.json
+++ b/packages/vite/src/executors/build/schema.json
@@ -28,11 +28,6 @@
"description": "Skip type-checking via TypeScript. Skipping type-checking speeds up the build but type errors are not caught.",
"default": false
},
- "base": {
- "type": "string",
- "description": "Base public path when served in development or production.",
- "alias": "baseHref"
- },
"configFile": {
"type": "string",
"description": "The name of the Vite.js configuration file.",
@@ -61,87 +56,6 @@
},
"default": []
},
- "emptyOutDir": {
- "description": "When set to false, outputPath will not be emptied during the build process.",
- "type": "boolean",
- "default": true
- },
- "sourcemap": {
- "description": "Output sourcemaps. Use 'hidden' for use with error reporting tools without generating sourcemap comment.",
- "oneOf": [
- {
- "type": "boolean"
- },
- {
- "type": "string"
- }
- ]
- },
- "target": {
- "description": "Browser compatibility target for the final bundle. For more info: https://vitejs.dev/config/build-options.html#build-target",
- "type": "string"
- },
- "minify": {
- "description": "Output sourcemaps. Use 'hidden' for use with error reporting tools without generating sourcemap comment.",
- "oneOf": [
- {
- "type": "boolean"
- },
- {
- "type": "string"
- }
- ]
- },
- "manifest": {
- "description": "Output sourcemaps. Use 'hidden' for use with error reporting tools without generating sourcemap comment.",
- "oneOf": [
- {
- "type": "boolean"
- },
- {
- "type": "string"
- }
- ]
- },
- "ssrManifest": {
- "description": "When set to true, the build will also generate an SSR manifest for determining style links and asset preload directives in production. When the value is a string, it will be used as the manifest file name.",
- "oneOf": [
- {
- "type": "boolean"
- },
- {
- "type": "string"
- }
- ]
- },
- "ssr": {
- "description": "Produce SSR-oriented build. The value can be a string to directly specify the SSR entry, or true, which requires specifying the SSR entry via rollupOptions.input.",
- "oneOf": [
- {
- "type": "boolean"
- },
- {
- "type": "string"
- }
- ]
- },
- "logLevel": {
- "type": "string",
- "description": "Adjust console output verbosity.",
- "enum": ["info", "warn", "error", "silent"]
- },
- "mode": {
- "type": "string",
- "description": "Mode to run the build in."
- },
- "force": {
- "description": "Force the optimizer to ignore the cache and re-bundle",
- "type": "boolean"
- },
- "cssCodeSplit": {
- "description": "Enable/disable CSS code splitting. When enabled, CSS imported in async chunks will be inlined into the async chunk itself and inserted when the chunk is loaded.",
- "type": "boolean"
- },
"watch": {
"description": "Enable re-building when files change.",
"oneOf": [
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 f46157a5eabac..c201b137e109d 100644
--- a/packages/vite/src/executors/dev-server/dev-server.impl.ts
+++ b/packages/vite/src/executors/dev-server/dev-server.impl.ts
@@ -1,16 +1,20 @@
-import { ExecutorContext } from '@nx/devkit';
-import type { InlineConfig, ViteDevServer } from 'vite';
+import { ExecutorContext, joinPathFragments } from '@nx/devkit';
+import {
+ loadConfigFromFile,
+ type InlineConfig,
+ type ViteDevServer,
+} from 'vite';
import {
getNxTargetOptions,
- getViteBuildOptions,
getViteServerOptions,
- getViteSharedConfig,
+ normalizeViteConfigFilePath,
} from '../../utils/options-utils';
import { ViteDevServerExecutorOptions } from './schema';
import { ViteBuildExecutorOptions } from '../build/schema';
import { createBuildableTsConfig } from '../../utils/executor-utils';
+import { relative } from 'path';
export async function* viteDevServerExecutor(
options: ViteDevServerExecutorOptions,
@@ -23,7 +27,10 @@ export async function* viteDevServerExecutor(
const projectRoot =
context.projectsConfigurations.projects[context.projectName].root;
-
+ const root =
+ projectRoot === '.'
+ ? process.cwd()
+ : relative(context.cwd, joinPathFragments(context.root, projectRoot));
createBuildableTsConfig(projectRoot, options, context);
// Retrieve the option for the configured buildTarget.
@@ -31,20 +38,33 @@ export async function* viteDevServerExecutor(
options.buildTarget,
context
);
+ const viteConfigPath = normalizeViteConfigFilePath(
+ context.root,
+ projectRoot,
+ buildTargetOptions.configFile
+ );
+ const extraArgs = await getExtraArgs(options);
+ const resolved = await loadConfigFromFile(
+ {
+ mode: extraArgs?.mode ?? 'production',
+ command: 'build',
+ },
+ viteConfigPath
+ );
- // Merge the options from the build and dev-serve targets.
- // The latter takes precedence.
- const mergedOptions = {
- ...buildTargetOptions,
- ...options,
- };
-
- // Add the server specific configuration.
const serverConfig: InlineConfig = mergeConfig(
- getViteSharedConfig(mergedOptions, options.clearScreen, context),
{
- build: getViteBuildOptions(mergedOptions, context),
- server: await getViteServerOptions(mergedOptions, context),
+ // This should not be needed as it's going to be set in vite.config.ts
+ // but leaving it here in case someone did not migrate correctly
+ root: resolved.config.root ?? root,
+ configFile: viteConfigPath,
+ },
+ {
+ server: {
+ ...(await getViteServerOptions(options, context)),
+ ...extraArgs,
+ },
+ ...extraArgs,
}
);
@@ -90,3 +110,18 @@ async function runViteDevServer(server: ViteDevServer): Promise {
}
export default viteDevServerExecutor;
+
+async function getExtraArgs(
+ options: ViteDevServerExecutorOptions
+): Promise {
+ // support passing extra args to vite cli
+ const schema = await import('./schema.json');
+ const extraArgs = {};
+ for (const key of Object.keys(options)) {
+ if (!schema.properties[key]) {
+ extraArgs[key] = options[key];
+ }
+ }
+
+ return extraArgs as InlineConfig;
+}
diff --git a/packages/vite/src/executors/dev-server/schema.d.ts b/packages/vite/src/executors/dev-server/schema.d.ts
index f022f090519a0..cc473d8f9b88d 100644
--- a/packages/vite/src/executors/dev-server/schema.d.ts
+++ b/packages/vite/src/executors/dev-server/schema.d.ts
@@ -2,14 +2,4 @@ export interface ViteDevServerExecutorOptions {
buildTarget: string;
buildLibsFromSource?: boolean;
proxyConfig?: string;
- port?: number;
- host?: string | boolean;
- https?: boolean | Json;
- hmr?: boolean;
- open?: string | boolean;
- cors?: boolean;
- logLevel?: 'info' | 'warn' | 'error' | 'silent';
- mode?: string;
- clearScreen?: boolean;
- force?: boolean;
}
diff --git a/packages/vite/src/executors/dev-server/schema.json b/packages/vite/src/executors/dev-server/schema.json
index 53d5a7ca82c6e..cb8294ad603ef 100644
--- a/packages/vite/src/executors/dev-server/schema.json
+++ b/packages/vite/src/executors/dev-server/schema.json
@@ -30,69 +30,6 @@
"type": "string",
"description": "Path to the proxy configuration file.",
"x-completion-type": "file"
- },
- "port": {
- "type": "number",
- "description": "Port to listen on.",
- "x-priority": "important"
- },
- "host": {
- "description": "Specify which IP addresses the server should listen on.",
- "oneOf": [
- {
- "type": "boolean"
- },
- {
- "type": "string"
- }
- ]
- },
- "https": {
- "oneOf": [
- {
- "type": "boolean"
- },
- {
- "type": "object"
- }
- ],
- "description": "Serve using HTTPS. https://vitejs.dev/config/server-options.html#server-https"
- },
- "hmr": {
- "description": "Enable hot module replacement. For more options, use the 'hmr' option in the Vite configuration file.",
- "type": "boolean"
- },
- "open": {
- "description": "Automatically open the app in the browser on server start. When the value is a string, it will be used as the URL's pathname.",
- "oneOf": [
- {
- "type": "boolean"
- },
- {
- "type": "string"
- }
- ]
- },
- "cors": {
- "description": "Configure CORS for the dev server.",
- "type": "boolean"
- },
- "logLevel": {
- "type": "string",
- "description": "Adjust console output verbosity.",
- "enum": ["info", "warn", "error", "silent"]
- },
- "mode": {
- "type": "string",
- "description": "Mode to run the server in."
- },
- "clearScreen": {
- "description": "Set to false to prevent Vite from clearing the terminal screen when logging certain messages.",
- "type": "boolean"
- },
- "force": {
- "description": "Force the optimizer to ignore the cache and re-bundle",
- "type": "boolean"
}
},
"definitions": {},
diff --git a/packages/vite/src/executors/preview-server/preview-server.impl.ts b/packages/vite/src/executors/preview-server/preview-server.impl.ts
index 57c46cefe699a..3aef6c52b8b49 100644
--- a/packages/vite/src/executors/preview-server/preview-server.impl.ts
+++ b/packages/vite/src/executors/preview-server/preview-server.impl.ts
@@ -1,27 +1,30 @@
-import { ExecutorContext, parseTargetString, runExecutor } from '@nx/devkit';
+import {
+ ExecutorContext,
+ joinPathFragments,
+ offsetFromRoot,
+ parseTargetString,
+ runExecutor,
+} from '@nx/devkit';
import type { InlineConfig, PreviewServer } from 'vite';
import {
getNxTargetOptions,
- getViteBuildOptions,
getVitePreviewOptions,
- getViteSharedConfig,
+ normalizeViteConfigFilePath,
} from '../../utils/options-utils';
import { ViteBuildExecutorOptions } from '../build/schema';
import { VitePreviewServerExecutorOptions } from './schema';
-
-interface CustomBuildTargetOptions {
- outputPath: string;
-}
+import { relative } from 'path';
export async function* vitePreviewServerExecutor(
options: VitePreviewServerExecutorOptions,
context: ExecutorContext
) {
// Allows ESM to be required in CJS modules. Vite will be published as ESM in the future.
- const { mergeConfig, preview } = await (Function(
+ const { mergeConfig, preview, loadConfigFromFile } = await (Function(
'return import("vite")'
)() as Promise);
-
+ const projectRoot =
+ context.projectsConfigurations.projects[context.projectName].root;
const target = parseTargetString(options.buildTarget, context);
const targetConfiguration =
context.projectsConfigurations.projects[target.project]?.targets[
@@ -36,35 +39,63 @@ export async function* vitePreviewServerExecutor(
targetConfiguration.executor !== '@nrwl/vite:build';
// Retrieve the option for the configured buildTarget.
- const buildTargetOptions:
- | ViteBuildExecutorOptions
- | CustomBuildTargetOptions = getNxTargetOptions(
+ const buildTargetOptions: ViteBuildExecutorOptions = getNxTargetOptions(
options.buildTarget,
context
);
+ const viteConfigPath = normalizeViteConfigFilePath(
+ context.root,
+ projectRoot,
+ buildTargetOptions.configFile
+ );
+ const extraArgs = await getExtraArgs(options);
+ const resolved = await loadConfigFromFile(
+ {
+ mode: extraArgs?.mode ?? 'production',
+ command: 'build',
+ },
+ viteConfigPath
+ );
- const outputPath = options.staticFilePath ?? buildTargetOptions.outputPath;
+ const outDir =
+ options.staticFilePath ??
+ joinPathFragments(
+ offsetFromRoot(projectRoot),
+ buildTargetOptions.outputPath
+ ) ??
+ resolved?.config?.build?.outDir;
- if (!outputPath) {
+ if (!outDir) {
throw new Error(
- `Could not infer the "outputPath". It should either be a property of the "${options.buildTarget}" buildTarget or provided explicitly as a "staticFilePath" option.`
+ `Could not infer the "outputPath" or "outDir". It should be set in your vite.config.ts, or as a property of the "${options.buildTarget}" buildTarget or provided explicitly as a "staticFilePath" option.`
);
}
+ const root =
+ projectRoot === '.'
+ ? process.cwd()
+ : relative(context.cwd, joinPathFragments(context.root, projectRoot));
// Merge the options from the build and preview-serve targets.
// The latter takes precedence.
const mergedOptions = {
...{ watch: {} },
+ build: {
+ outDir,
+ },
...(isCustomBuildTarget ? {} : buildTargetOptions),
- ...options,
- outputPath,
+ ...extraArgs,
};
// Retrieve the server configuration.
const serverConfig: InlineConfig = mergeConfig(
- getViteSharedConfig(mergedOptions, options.clearScreen, context),
{
- build: getViteBuildOptions(mergedOptions, context),
+ // This should not be needed as it's going to be set in vite.config.ts
+ // but leaving it here in case someone did not migrate correctly
+ root: resolved.config.root ?? root,
+ configFile: viteConfigPath,
+ },
+ {
+ ...mergedOptions,
preview: getVitePreviewOptions(mergedOptions, context),
}
);
@@ -146,3 +177,18 @@ function closeServer(server?: PreviewServer): Promise {
}
export default vitePreviewServerExecutor;
+
+async function getExtraArgs(
+ options: VitePreviewServerExecutorOptions
+): Promise {
+ // support passing extra args to vite cli
+ const schema = await import('./schema.json');
+ const extraArgs = {};
+ for (const key of Object.keys(options)) {
+ if (!schema.properties[key]) {
+ extraArgs[key] = options[key];
+ }
+ }
+
+ return extraArgs as InlineConfig;
+}
diff --git a/packages/vite/src/executors/preview-server/schema.d.ts b/packages/vite/src/executors/preview-server/schema.d.ts
index af222aa7bfec9..b59e309b5bcf1 100644
--- a/packages/vite/src/executors/preview-server/schema.d.ts
+++ b/packages/vite/src/executors/preview-server/schema.d.ts
@@ -1,12 +1,5 @@
export interface VitePreviewServerExecutorOptions {
buildTarget: string;
proxyConfig?: string;
- port?: number;
- host?: string | boolean;
- https?: boolean | Json;
- open?: string | boolean;
- logLevel?: 'info' | 'warn' | 'error' | 'silent';
- mode?: string;
- clearScreen?: boolean;
staticFilePath?: string;
}
diff --git a/packages/vite/src/executors/preview-server/schema.json b/packages/vite/src/executors/preview-server/schema.json
index 24e6e9cf1988f..f4f6e1270a06f 100644
--- a/packages/vite/src/executors/preview-server/schema.json
+++ b/packages/vite/src/executors/preview-server/schema.json
@@ -25,56 +25,6 @@
"description": "Path to the proxy configuration file.",
"x-completion-type": "file"
},
- "port": {
- "type": "number",
- "description": "Port to listen on."
- },
- "host": {
- "description": "Specify which IP addresses the server should listen on.",
- "oneOf": [
- {
- "type": "boolean"
- },
- {
- "type": "string"
- }
- ]
- },
- "https": {
- "oneOf": [
- {
- "type": "boolean"
- },
- {
- "type": "object"
- }
- ],
- "description": "Serve using HTTPS. https://vitejs.dev/config/server-options.html#server-https"
- },
- "open": {
- "description": "Automatically open the app in the browser on server start. When the value is a string, it will be used as the URL's pathname.",
- "oneOf": [
- {
- "type": "boolean"
- },
- {
- "type": "string"
- }
- ]
- },
- "logLevel": {
- "type": "string",
- "description": "Adjust console output verbosity.",
- "enum": ["info", "warn", "error", "silent"]
- },
- "mode": {
- "type": "string",
- "description": "Mode to run the server in."
- },
- "clearScreen": {
- "description": "Set to false to prevent Vite from clearing the terminal screen when logging certain messages.",
- "type": "boolean"
- },
"staticFilePath": {
"type": "string",
"description": "Path where the build artifacts are located. If not provided then it will be infered from the buildTarget executor options as outputPath",
diff --git a/packages/vite/src/executors/test/lib/nx-reporter.ts b/packages/vite/src/executors/test/lib/nx-reporter.ts
new file mode 100644
index 0000000000000..e38631e1064e4
--- /dev/null
+++ b/packages/vite/src/executors/test/lib/nx-reporter.ts
@@ -0,0 +1,36 @@
+import type { File, Reporter } from 'vitest';
+
+export class NxReporter implements Reporter {
+ deferred: {
+ promise: Promise;
+ resolve: (val: boolean) => void;
+ };
+
+ constructor(private watch: boolean) {
+ this.setupDeferred();
+ }
+
+ async *[Symbol.asyncIterator]() {
+ do {
+ const hasErrors = await this.deferred.promise;
+ yield { hasErrors };
+ this.setupDeferred();
+ } while (this.watch);
+ }
+
+ private setupDeferred() {
+ let resolve: (val: boolean) => void;
+ this.deferred = {
+ promise: new Promise((res) => {
+ resolve = res;
+ }),
+ resolve,
+ };
+ }
+
+ onFinished(files: File[], errors?: unknown[]) {
+ const hasErrors =
+ files.some((f) => f.result?.state === 'fail') || errors?.length > 0;
+ this.deferred.resolve(hasErrors);
+ }
+}
diff --git a/packages/vite/src/executors/test/lib/utils.ts b/packages/vite/src/executors/test/lib/utils.ts
new file mode 100644
index 0000000000000..f7a258f661faa
--- /dev/null
+++ b/packages/vite/src/executors/test/lib/utils.ts
@@ -0,0 +1,88 @@
+import {
+ ExecutorContext,
+ joinPathFragments,
+ logger,
+ stripIndents,
+} from '@nx/devkit';
+import { VitestExecutorOptions } from '../schema';
+import { normalizeViteConfigFilePath } from '../../../utils/options-utils';
+import { relative } from 'path';
+import { NxReporter } from './nx-reporter';
+
+export async function getOptions(
+ options: VitestExecutorOptions,
+ context: ExecutorContext,
+ projectRoot: string,
+ extraArgs: Record
+) {
+ // Allows ESM to be required in CJS modules. Vite will be published as ESM in the future.
+ const { loadConfigFromFile, mergeConfig } = await (Function(
+ 'return import("vite")'
+ )() as Promise);
+
+ const viteConfigPath = normalizeViteConfigFilePath(
+ context.root,
+ projectRoot,
+ options.configFile
+ );
+
+ if (!viteConfigPath) {
+ throw new Error(
+ stripIndents`
+ Unable to load test config from config file ${viteConfigPath}.
+
+ Please make sure that vitest is configured correctly,
+ or use the @nx/vite:vitest generator to configure it for you.
+ You can read more here: https://nx.dev/nx-api/vite/generators/vitest
+ `
+ );
+ }
+
+ const resolved = await loadConfigFromFile(
+ {
+ mode: extraArgs?.mode ?? 'production',
+ command: 'serve',
+ },
+ viteConfigPath
+ );
+
+ if (!viteConfigPath || !resolved?.config?.['test']) {
+ logger.warn(stripIndents`Unable to load test config from config file ${
+ resolved?.path ?? viteConfigPath
+ }
+ Some settings may not be applied as expected.
+ You can manually set the config in the project, ${
+ context.projectName
+ }, configuration.
+ `);
+ }
+ const root =
+ projectRoot === '.'
+ ? process.cwd()
+ : relative(context.cwd, joinPathFragments(context.root, projectRoot));
+
+ const settings = {
+ ...extraArgs,
+ // This should not be needed as it's going to be set in vite.config.ts
+ // but leaving it here in case someone did not migrate correctly
+ root: resolved.config.root ?? root,
+ configFile: viteConfigPath,
+ };
+
+ return mergeConfig(resolved?.config?.['test'] ?? {}, settings);
+}
+
+export async function getExtraArgs(
+ options: VitestExecutorOptions
+): Promise> {
+ // support passing extra args to vite cli
+ const schema = await import('../schema.json');
+ const extraArgs: Record = {};
+ for (const key of Object.keys(options)) {
+ if (!schema.properties[key]) {
+ extraArgs[key] = options[key];
+ }
+ }
+
+ return extraArgs;
+}
diff --git a/packages/vite/src/executors/test/schema.d.ts b/packages/vite/src/executors/test/schema.d.ts
index e4ffa4ae4a173..8e2855f945153 100644
--- a/packages/vite/src/executors/test/schema.d.ts
+++ b/packages/vite/src/executors/test/schema.d.ts
@@ -1,12 +1,6 @@
export interface VitestExecutorOptions {
- config?: string;
- passWithNoTests?: boolean;
- testNamePattern?: string;
- mode?: 'test' | 'benchmark' | 'typecheck';
- reporters?: string[];
- watch?: boolean;
- update?: boolean;
+ configFile?: string;
reportsDirectory?: string;
- coverage?: boolean;
testFiles?: string[];
+ watch?: boolean;
}
diff --git a/packages/vite/src/executors/test/schema.json b/packages/vite/src/executors/test/schema.json
index d730f7033a8d7..099111069750f 100644
--- a/packages/vite/src/executors/test/schema.json
+++ b/packages/vite/src/executors/test/schema.json
@@ -6,52 +6,12 @@
"description": "Test using Vitest.",
"type": "object",
"properties": {
- "config": {
+ "configFile": {
"type": "string",
"description": "The path to the local vitest config",
"x-completion-type": "file",
- "x-completion-glob": "@(vitest|vite).config@(.js|.ts)"
- },
- "passWithNoTests": {
- "type": "boolean",
- "default": true,
- "description": "Pass the test even if no tests are found"
- },
- "testNamePattern": {
- "type": "string",
- "description": "Run tests with full names matching the pattern"
- },
- "mode": {
- "type": "string",
- "enum": ["test", "benchmark", "typecheck"],
- "default": "test",
- "description": "The mode that vitest will run on",
- "x-priority": "important"
- },
- "watch": {
- "type": "boolean",
- "default": false,
- "description": "Enable watch mode"
- },
- "reporters": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "An array of reporters to pass to vitest"
- },
- "update": {
- "type": "boolean",
- "default": false,
- "alias": "u",
- "description": "Update snapshots",
- "x-priority": "important"
- },
- "coverage": {
- "type": "boolean",
- "default": false,
- "description": "Enable coverage report",
- "x-priority": "important"
+ "x-completion-glob": "@(vitest|vite).config@(.js|.ts)",
+ "aliases": ["config"]
},
"reportsDirectory": {
"type": "string",
@@ -61,6 +21,10 @@
"aliases": ["testFile"],
"type": "array",
"items": { "type": "string" }
+ },
+ "watch": {
+ "description": "Watch files for changes and rerun tests related to changed files.",
+ "type": "boolean"
}
},
"required": [],
diff --git a/packages/vite/src/executors/test/vitest.impl.ts b/packages/vite/src/executors/test/vitest.impl.ts
index 9761f8884ac8e..0fff9c31300f1 100644
--- a/packages/vite/src/executors/test/vitest.impl.ts
+++ b/packages/vite/src/executors/test/vitest.impl.ts
@@ -1,51 +1,9 @@
-import {
- ExecutorContext,
- joinPathFragments,
- logger,
- readJsonFile,
- stripIndents,
- workspaceRoot,
-} from '@nx/devkit';
-import type { CoverageOptions, File, Reporter } from 'vitest';
+import { ExecutorContext, workspaceRoot } from '@nx/devkit';
import { VitestExecutorOptions } from './schema';
-import { join, relative, resolve } from 'path';
-import { existsSync } from 'fs';
+import { resolve } from 'path';
import { registerTsConfigPaths } from '@nx/js/src/internal';
-
-class NxReporter implements Reporter {
- deferred: {
- promise: Promise;
- resolve: (val: boolean) => void;
- };
-
- constructor(private watch: boolean) {
- this.setupDeferred();
- }
-
- async *[Symbol.asyncIterator]() {
- do {
- const hasErrors = await this.deferred.promise;
- yield { hasErrors };
- this.setupDeferred();
- } while (this.watch);
- }
-
- private setupDeferred() {
- let resolve: (val: boolean) => void;
- this.deferred = {
- promise: new Promise((res) => {
- resolve = res;
- }),
- resolve,
- };
- }
-
- onFinished(files: File[], errors?: unknown[]) {
- const hasErrors =
- files.some((f) => f.result?.state === 'fail') || errors?.length > 0;
- this.deferred.resolve(hasErrors);
- }
-}
+import { NxReporter } from './lib/nx-reporter';
+import { getExtraArgs, getOptions } from './lib/utils';
export async function* vitestExecutor(
options: VitestExecutorOptions,
@@ -53,18 +11,32 @@ export async function* vitestExecutor(
) {
const projectRoot =
context.projectsConfigurations.projects[context.projectName].root;
+
registerTsConfigPaths(resolve(workspaceRoot, projectRoot, 'tsconfig.json'));
const { startVitest } = await (Function(
'return import("vitest/node")'
)() as Promise);
- const nxReporter = new NxReporter(options.watch);
- const settings = await getSettings(options, context, projectRoot);
- settings.reporters.push(nxReporter);
+ const extraArgs = await getExtraArgs(options);
+ const resolvedOptions =
+ (await getOptions(options, context, projectRoot, extraArgs)) ?? {};
+
+ const nxReporter = new NxReporter(resolvedOptions['watch']);
+ if (resolvedOptions['reporters'] === undefined) {
+ resolvedOptions['reporters'] = [];
+ } else if (typeof resolvedOptions['reporters'] === 'string') {
+ resolvedOptions['reporters'] = [resolvedOptions['reporters']];
+ }
+ resolvedOptions['reporters'].push(nxReporter);
+
const cliFilters = options.testFiles ?? [];
- const ctx = await startVitest(options.mode, cliFilters, settings);
+ const ctx = await startVitest(
+ resolvedOptions['mode'] ?? 'test',
+ cliFilters,
+ resolvedOptions
+ );
let hasErrors = false;
@@ -77,7 +49,7 @@ export async function* vitestExecutor(
}
};
- if (options.watch) {
+ if (resolvedOptions['watch'] === true) {
process.on('SIGINT', processExit);
process.on('SIGTERM', processExit);
process.on('exit', processExit);
@@ -94,111 +66,4 @@ export async function* vitestExecutor(
};
}
-async function getSettings(
- options: VitestExecutorOptions,
- context: ExecutorContext,
- projectRoot: string
-) {
- // Allows ESM to be required in CJS modules. Vite will be published as ESM in the future.
- const { loadConfigFromFile } = await (Function(
- 'return import("vite")'
- )() as Promise);
-
- const packageJsonPath = join(workspaceRoot, 'package.json');
- const packageJson = existsSync(packageJsonPath)
- ? readJsonFile(packageJsonPath)
- : undefined;
- let provider: 'v8' | 'istanbul' | 'custom';
- if (
- packageJson?.dependencies?.['@vitest/coverage-istanbul'] ||
- packageJson?.devDependencies?.['@vitest/coverage-istanbul']
- ) {
- provider = 'istanbul';
- } else {
- provider = 'v8';
- }
- const offset = relative(workspaceRoot, context.cwd);
- // if reportsDirectory is not provided vitest will remove all files in the project root
- // when coverage is enabled in the vite.config.ts
- const coverage: CoverageOptions = options.reportsDirectory
- ? {
- enabled: options.coverage,
- reportsDirectory: options.reportsDirectory,
- provider,
- }
- : ({} as CoverageOptions);
-
- const viteConfigPath = options.config
- ? options.config // config is expected to be from the workspace root
- : findViteConfig(joinPathFragments(context.root, projectRoot));
-
- if (!viteConfigPath) {
- throw new Error(
- stripIndents`
- Unable to load test config from config file ${viteConfigPath}.
-
- Please make sure that vitest is configured correctly,
- or use the @nx/vite:vitest generator to configure it for you.
- You can read more here: https://nx.dev/nx-api/vite/generators/vitest
- `
- );
- }
-
- const resolvedProjectRoot = resolve(workspaceRoot, projectRoot);
- const resolvedViteConfigPath = resolve(
- workspaceRoot,
- projectRoot,
- relative(resolvedProjectRoot, viteConfigPath)
- );
-
- const resolved = await loadConfigFromFile(
- {
- mode: options.mode,
- command: 'serve',
- },
- resolvedViteConfigPath,
- resolvedProjectRoot
- );
-
- if (!viteConfigPath || !resolved?.config?.['test']) {
- logger.warn(stripIndents`Unable to load test config from config file ${
- resolved?.path ?? viteConfigPath
- }
-Some settings may not be applied as expected.
-You can manually set the config in the project, ${
- context.projectName
- }, configuration.
- `);
- }
-
- const settings = {
- ...options,
- // when running nx from the project root, the root will get appended to the cwd.
- // creating an invalid path and no tests will be found.
- // instead if we are not at the root, let the cwd be root.
- root: offset === '' ? resolvedProjectRoot : workspaceRoot,
- config: resolvedViteConfigPath,
- reporters: [
- ...(options.reporters ?? []),
- ...((resolved?.config?.['test']?.reporters as string[]) ?? []),
- 'default',
- ] as (string | Reporter)[],
- coverage: { ...coverage, ...resolved?.config?.['test']?.coverage },
- };
-
- return settings;
-}
-
-function findViteConfig(projectRootFullPath: string): string {
- const allowsExt = ['js', 'mjs', 'ts', 'cjs', 'mts', 'cts'];
-
- for (const ext of allowsExt) {
- if (
- existsSync(joinPathFragments(projectRootFullPath, `vite.config.${ext}`))
- ) {
- return joinPathFragments(projectRootFullPath, `vite.config.${ext}`);
- }
- }
-}
-
export default vitestExecutor;
diff --git a/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap b/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap
index c9c9c0fbc1ac6..b5ffd6de1c1b4 100644
--- a/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap
+++ b/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap
@@ -9,7 +9,8 @@ import * as path from 'path';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
- cacheDir: '../../node_modules/.vite/react-lib-nonb-jest',
+ root: __dirname,
+ cacheDir: '../../node_modules/.vite/libs/react-lib-nonb-jest',
plugins: [
react(),
@@ -29,6 +30,7 @@ export default defineConfig({
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
+ outDir: '../../dist/libs/react-lib-nonb-jest',
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
@@ -56,7 +58,8 @@ import * as path from 'path';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
- cacheDir: '../../node_modules/.vite/react-lib-nonb-jest',
+ root: __dirname,
+ cacheDir: '../../node_modules/.vite/libs/react-lib-nonb-jest',
plugins: [
react(),
@@ -76,6 +79,7 @@ export default defineConfig({
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
+ outDir: '../../dist/libs/react-lib-nonb-jest',
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
@@ -98,6 +102,11 @@ export default defineConfig({
},
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+
+ coverage: {
+ reportsDirectory: '../../coverage/libs/react-lib-nonb-jest',
+ provider: 'v8',
+ },
},
});
"
@@ -143,8 +152,9 @@ import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
- cacheDir: '../../node_modules/.vite/react-lib-nonb-vitest',
+ cacheDir: '../../node_modules/.vite/libs/react-lib-nonb-vitest',
build: {
+ outDir: '../../dist/libs/react-lib-nonb-vitest',
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
@@ -174,6 +184,10 @@ export default defineConfig({
cache: { dir: '../../node_modules/.vitest' },
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+ coverage: {
+ reportsDirectory: '../../coverage/libs/react-lib-nonb-vitest',
+ provider: 'v8',
+ },
},
});
"
@@ -271,7 +285,8 @@ import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
- cacheDir: '../../node_modules/.vite/my-test-react-app',
+ root: __dirname,
+ cacheDir: '../../node_modules/.vite/apps/my-test-react-app',
server: {
port: 4200,
@@ -289,6 +304,10 @@ export default defineConfig({
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
+
+ build: {
+ outDir: '../../dist/apps/my-test-react-app',
+ },
});
"
`;
@@ -409,7 +428,8 @@ import { defineConfig } from 'vite';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
- cacheDir: '../../node_modules/.vite/my-test-web-app',
+ root: __dirname,
+ cacheDir: '../../node_modules/.vite/apps/my-test-web-app',
server: {
port: 4200,
@@ -427,6 +447,10 @@ export default defineConfig({
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
+
+ build: {
+ outDir: '../../dist/apps/my-test-web-app',
+ },
});
"
`;
@@ -534,7 +558,8 @@ import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
- cacheDir: '../../node_modules/.vite/my-test-react-app',
+ root: __dirname,
+ cacheDir: '../../node_modules/.vite/apps/my-test-react-app',
server: {
port: 4200,
@@ -553,6 +578,10 @@ export default defineConfig({
// plugins: [ nxViteTsPaths() ],
// },
+ build: {
+ outDir: '../../dist/apps/my-test-react-app',
+ },
+
test: {
globals: true,
cache: {
@@ -560,6 +589,11 @@ export default defineConfig({
},
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+
+ coverage: {
+ reportsDirectory: '../../coverage/apps/my-test-react-app',
+ provider: 'v8',
+ },
},
});
"
diff --git a/packages/vite/src/generators/configuration/configuration.ts b/packages/vite/src/generators/configuration/configuration.ts
index 7c46ca9c20638..454101261a5e2 100644
--- a/packages/vite/src/generators/configuration/configuration.ts
+++ b/packages/vite/src/generators/configuration/configuration.ts
@@ -33,10 +33,11 @@ export async function viteConfigurationGenerator(
) {
const tasks: GeneratorCallback[] = [];
- const { targets, projectType, root } = readProjectConfiguration(
- tree,
- schema.project
- );
+ const {
+ targets,
+ projectType,
+ root: projectRoot,
+ } = readProjectConfiguration(tree, schema.project);
let buildTargetName = 'build';
let serveTargetName = 'serve';
let testTargetName = 'test';
@@ -147,7 +148,7 @@ export async function viteConfigurationGenerator(
deleteWebpackConfig(
tree,
- root,
+ projectRoot,
targets?.[buildTargetName]?.options?.webpackConfig
);
@@ -159,7 +160,7 @@ export async function viteConfigurationGenerator(
includeLib: schema.includeLib,
compiler: schema.compiler,
testEnvironment: schema.testEnvironment,
- rootProject: root === '.',
+ rootProject: projectRoot === '.',
});
tasks.push(initTask);
@@ -178,24 +179,28 @@ export async function viteConfigurationGenerator(
if (projectType === 'library') {
// update tsconfig.lib.json to include vite/client
- updateJson(tree, joinPathFragments(root, 'tsconfig.lib.json'), (json) => {
- if (!json.compilerOptions) {
- json.compilerOptions = {};
- }
- if (!json.compilerOptions.types) {
- json.compilerOptions.types = [];
- }
- if (!json.compilerOptions.types.includes('vite/client')) {
- return {
- ...json,
- compilerOptions: {
- ...json.compilerOptions,
- types: [...json.compilerOptions.types, 'vite/client'],
- },
- };
+ updateJson(
+ tree,
+ joinPathFragments(projectRoot, 'tsconfig.lib.json'),
+ (json) => {
+ if (!json.compilerOptions) {
+ json.compilerOptions = {};
+ }
+ if (!json.compilerOptions.types) {
+ json.compilerOptions.types = [];
+ }
+ if (!json.compilerOptions.types.includes('vite/client')) {
+ return {
+ ...json,
+ compilerOptions: {
+ ...json.compilerOptions,
+ types: [...json.compilerOptions.types, 'vite/client'],
+ },
+ };
+ }
+ return json;
}
- return json;
- });
+ );
}
if (!schema.newProject) {
diff --git a/packages/vite/src/generators/init/init.spec.ts b/packages/vite/src/generators/init/init.spec.ts
index 26fdc56821f33..fae26ee007040 100644
--- a/packages/vite/src/generators/init/init.spec.ts
+++ b/packages/vite/src/generators/init/init.spec.ts
@@ -93,6 +93,10 @@ describe('@nx/vite:init', () => {
expect(vitestDefaults).toEqual({
cache: true,
inputs: ['default', '^production'],
+ options: {
+ passWithNoTests: true,
+ reporters: ['default'],
+ },
});
});
});
diff --git a/packages/vite/src/generators/init/init.ts b/packages/vite/src/generators/init/init.ts
index 72fda07a0b00a..5bd71876211b5 100644
--- a/packages/vite/src/generators/init/init.ts
+++ b/packages/vite/src/generators/init/init.ts
@@ -34,23 +34,8 @@ function checkDependenciesInstalled(host: Tree, schema: InitGeneratorSchema) {
// base deps
devDependencies['@nx/vite'] = nxVersion;
devDependencies['vite'] = viteVersion;
-
- // Do not install latest version if vitest already exists
- // because version 0.32 and newer versions break nuxt-vitest
- // https://github.com/vitest-dev/vitest/issues/3540
- // https://github.com/danielroe/nuxt-vitest/issues/213#issuecomment-1588728111
- if (
- !packageJson.dependencies['vitest'] &&
- !packageJson.devDependencies['vitest']
- ) {
- devDependencies['vitest'] = vitestVersion;
- }
- if (
- !packageJson.dependencies['@vitest/ui'] &&
- !packageJson.devDependencies['@vitest/ui']
- ) {
- devDependencies['@vitest/ui'] = vitestVersion;
- }
+ devDependencies['vitest'] = vitestVersion;
+ devDependencies['@vitest/ui'] = vitestVersion;
if (schema.testEnvironment === 'jsdom') {
devDependencies['jsdom'] = jsdomVersion;
@@ -93,7 +78,7 @@ function moveToDevDependencies(tree: Tree) {
});
}
-export function createVitestConfig(tree: Tree) {
+export function updateNxJsonSettings(tree: Tree) {
const nxJson = readNxJson(tree);
const productionFileSet = nxJson.namedInputs?.production;
@@ -113,13 +98,26 @@ export function createVitestConfig(tree: Tree) {
'default',
productionFileSet ? '^production' : '^default',
];
+ nxJson.targetDefaults['@nx/vite:test'].options ??= {
+ passWithNoTests: true,
+ reporters: ['default'],
+ };
+
+ nxJson.targetDefaults['@nx/vite:build'] ??= {};
+
+ nxJson.targetDefaults['@nx/vite:build'].options ??= {
+ reportCompressedSize: true,
+ commonjsOptions: {
+ transformMixedEsModules: true,
+ },
+ };
updateNxJson(tree, nxJson);
}
export async function initGenerator(tree: Tree, schema: InitGeneratorSchema) {
moveToDevDependencies(tree);
- createVitestConfig(tree);
+ updateNxJsonSettings(tree);
const tasks = [];
tasks.push(
diff --git a/packages/vite/src/generators/vitest/__snapshots__/vitest.spec.ts.snap b/packages/vite/src/generators/vitest/__snapshots__/vitest.spec.ts.snap
index 247c582f92025..38d86a317c4ab 100644
--- a/packages/vite/src/generators/vitest/__snapshots__/vitest.spec.ts.snap
+++ b/packages/vite/src/generators/vitest/__snapshots__/vitest.spec.ts.snap
@@ -7,7 +7,8 @@ import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
- cacheDir: '../../node_modules/.vite/my-test-react-app',
+ root: __dirname,
+ cacheDir: '../../node_modules/.vite/apps/my-test-react-app',
plugins: [react(), nxViteTsPaths()],
@@ -27,6 +28,10 @@ export default defineConfig({
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
includeSource: ['src/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+ coverage: {
+ reportsDirectory: '../../coverage/apps/my-test-react-app',
+ provider: 'v8',
+ },
},
});
"
@@ -39,7 +44,8 @@ import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
- cacheDir: '../../node_modules/.vite/my-test-react-app',
+ root: __dirname,
+ cacheDir: '../../node_modules/.vite/apps/my-test-react-app',
plugins: [react(), nxViteTsPaths()],
@@ -55,6 +61,11 @@ export default defineConfig({
},
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+
+ coverage: {
+ reportsDirectory: '../../coverage/apps/my-test-react-app',
+ provider: 'v8',
+ },
},
});
"
@@ -67,7 +78,8 @@ import react from '@vitejs/plugin-react';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
- cacheDir: '../../node_modules/.vite/react-lib-nonb-jest',
+ root: __dirname,
+ cacheDir: '../../node_modules/.vite/libs/react-lib-nonb-jest',
plugins: [react(), nxViteTsPaths()],
@@ -83,6 +95,11 @@ export default defineConfig({
},
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+
+ coverage: {
+ reportsDirectory: '../../coverage/libs/react-lib-nonb-jest',
+ provider: 'v8',
+ },
},
});
"
diff --git a/packages/vite/src/generators/vitest/files/tsconfig.spec.json__tmpl__ b/packages/vite/src/generators/vitest/files/tsconfig.spec.json__tmpl__
index d865f9cf2e06d..0f177b57ab70b 100644
--- a/packages/vite/src/generators/vitest/files/tsconfig.spec.json__tmpl__
+++ b/packages/vite/src/generators/vitest/files/tsconfig.spec.json__tmpl__
@@ -6,6 +6,7 @@
},
"include": [
"vite.config.ts",
+ "vitest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
diff --git a/packages/vite/src/generators/vitest/vitest-generator.ts b/packages/vite/src/generators/vitest/vitest-generator.ts
index dc3812858248a..f20fbf6e6d30a 100644
--- a/packages/vite/src/generators/vitest/vitest-generator.ts
+++ b/packages/vite/src/generators/vitest/vitest-generator.ts
@@ -65,6 +65,7 @@ export async function vitestGenerator(
],
imports: [`import react from '@vitejs/plugin-react'`],
plugins: ['react()'],
+ coverageProvider: schema.coverageProvider,
},
true
);
diff --git a/packages/vite/src/generators/vitest/vitest.spec.ts b/packages/vite/src/generators/vitest/vitest.spec.ts
index 101b16d38553f..80336aa433bf2 100644
--- a/packages/vite/src/generators/vitest/vitest.spec.ts
+++ b/packages/vite/src/generators/vitest/vitest.spec.ts
@@ -55,7 +55,6 @@ describe('vitest generator', () => {
{
"executor": "@nx/vite:test",
"options": {
- "passWithNoTests": true,
"reportsDirectory": "../../coverage/apps/my-test-react-app",
},
"outputs": [
@@ -102,6 +101,7 @@ describe('vitest generator', () => {
"extends": "./tsconfig.json",
"include": [
"vite.config.ts",
+ "vitest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
diff --git a/packages/vite/src/migrations/update-17-2-0/__snapshots__/update-vite-config.spec.ts.snap b/packages/vite/src/migrations/update-17-2-0/__snapshots__/update-vite-config.spec.ts.snap
new file mode 100644
index 0000000000000..a879a9cab4c23
--- /dev/null
+++ b/packages/vite/src/migrations/update-17-2-0/__snapshots__/update-vite-config.spec.ts.snap
@@ -0,0 +1,156 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`change-vite-ts-paths-plugin migration should add build outDir to vite.config.ts 1`] = `
+"///
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import viteTsConfigPaths from 'vite-tsconfig-paths';
+
+export default defineConfig({
+ root: __dirname,
+ build: {
+ outDir: '../../dist/apps/demo',
+ },
+ cacheDir: '../../node_modules/.vite/demo',
+ server: {
+ port: 4200,
+ host: 'localhost',
+ },
+
+ preview: {
+ port: 4300,
+ host: 'localhost',
+ },
+
+ plugins: [
+ react(),
+ viteTsConfigPaths({
+ root: '../../',
+ }),
+ ],
+
+ // Uncomment this if you are using workers.
+ // worker: {
+ // plugins: [
+ // viteTsConfigPaths({
+ // root: '../../',
+ // }),
+ // ],
+ // },
+
+ test: {
+ coverage: {
+ reportsDirectory: '../../coverage/apps/demo',
+ provider: 'v8',
+ },
+ globals: true,
+ cache: {
+ dir: '../../node_modules/.vitest',
+ },
+ environment: 'jsdom',
+ include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+ },
+});
+"
+`;
+
+exports[`change-vite-ts-paths-plugin migration should add build outDir to vite.config.ts if build exists 1`] = `
+"///
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import viteTsConfigPaths from 'vite-tsconfig-paths';
+
+export default defineConfig({
+ root: __dirname,
+ cacheDir: '../../node_modules/.vite/demo2',
+ server: {
+ port: 4200,
+ host: 'localhost',
+ },
+
+ preview: {
+ port: 4300,
+ host: 'localhost',
+ },
+
+ plugins: [
+ react(),
+ viteTsConfigPaths({
+ root: '../../',
+ }),
+ ],
+
+ build: {
+ outDir: '../dist/demo2',
+ someProperty: 'someValue',
+ },
+
+ test: {
+ coverage: {
+ reportsDirectory: '../coverage/demo2',
+ provider: 'v8',
+ },
+ globals: true,
+ cache: {
+ dir: '../../node_modules/.vitest',
+ },
+ environment: 'jsdom',
+ include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+ },
+});
+"
+`;
+
+exports[`change-vite-ts-paths-plugin migration should add file replacements to vite.config.ts 1`] = `
+"///
+import replaceFiles from '@nx/vite/plugins/rollup-replace-files.plugin';
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import viteTsConfigPaths from 'vite-tsconfig-paths';
+
+export default defineConfig({
+ root: __dirname,
+ cacheDir: '../../node_modules/.vite/demo3',
+ server: {
+ port: 4200,
+ host: 'localhost',
+ },
+
+ preview: {
+ port: 4300,
+ host: 'localhost',
+ },
+
+ plugins: [
+ replaceFiles([
+ {
+ replace: 'demo3/src/environments/environment.ts',
+ with: 'demo3/src/environments/environment.prod.ts',
+ },
+ ]),
+ react(),
+ viteTsConfigPaths({
+ root: '../../',
+ }),
+ ],
+
+ build: {
+ outDir: '../dist/demo3',
+ someProperty: 'someValue',
+ },
+
+ test: {
+ coverage: {
+ reportsDirectory: '../coverage/demo3',
+ provider: 'v8',
+ },
+ globals: true,
+ cache: {
+ dir: '../../node_modules/.vitest',
+ },
+ environment: 'jsdom',
+ include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+ },
+});
+"
+`;
diff --git a/packages/vite/src/migrations/update-17-2-0/lib/add-file-replacements.ts b/packages/vite/src/migrations/update-17-2-0/lib/add-file-replacements.ts
new file mode 100644
index 0000000000000..dcf3fda762ffe
--- /dev/null
+++ b/packages/vite/src/migrations/update-17-2-0/lib/add-file-replacements.ts
@@ -0,0 +1,74 @@
+import { ChangeType, applyChangesToString } from '@nx/devkit';
+import { FileReplacement } from '../../../../plugins/rollup-replace-files.plugin';
+import { tsquery } from '@phenomnomnominal/tsquery';
+
+export function addFileReplacements(
+ configContents: string,
+ fileReplacements: FileReplacement[]
+): string {
+ const pluginsObject = tsquery.query(
+ configContents,
+ `PropertyAssignment:has(Identifier[name="plugins"])`
+ )?.[0];
+ const replaceFilesPlugin = tsquery.query(
+ configContents,
+ `PropertyAssignment:has(Identifier[name="plugins"]) CallExpression:has(Identifier[name="replaceFiles"])`
+ )?.[0];
+
+ const firstImportDeclaration = tsquery.query(
+ configContents,
+ 'ImportDeclaration'
+ )?.[0];
+
+ if (pluginsObject) {
+ if (replaceFilesPlugin) {
+ return configContents;
+ } else {
+ return applyChangesToString(configContents, [
+ {
+ type: ChangeType.Insert,
+ index: pluginsObject.getStart() + `plugins: [`.length + 1,
+ text: `replaceFiles(${JSON.stringify(fileReplacements)}),`,
+ },
+ firstImportDeclaration
+ ? {
+ type: ChangeType.Insert,
+ index: firstImportDeclaration.getStart(),
+ text: `import replaceFiles from '@nx/vite/plugins/rollup-replace-files.plugin';\n`,
+ }
+ : {
+ type: ChangeType.Insert,
+ index: 0,
+ text: `import replaceFiles from '@nx/vite/plugins/rollup-replace-files.plugin';\n`,
+ },
+ ]);
+ }
+ } else {
+ const foundDefineConfig = tsquery.query(
+ configContents,
+ 'CallExpression:has(Identifier[name="defineConfig"])'
+ )?.[0];
+
+ if (!foundDefineConfig) {
+ return;
+ }
+ return applyChangesToString(configContents, [
+ {
+ type: ChangeType.Insert,
+ index: foundDefineConfig.getStart() + 14,
+ text: `plugins: [replaceFiles(${JSON.stringify(fileReplacements)})],`,
+ },
+ firstImportDeclaration
+ ? {
+ type: ChangeType.Insert,
+ index: firstImportDeclaration.getStart(),
+ text: `import replaceFiles from '@nx/vite/plugins/rollup-replace-files.plugin';`,
+ }
+ : {
+ type: ChangeType.Insert,
+ index: 0,
+ text: `import replaceFiles from '@nx/vite/plugins/rollup-replace-files.plugin';`,
+ },
+ ]);
+ }
+}
diff --git a/packages/vite/src/migrations/update-17-2-0/lib/edit-build-config.ts b/packages/vite/src/migrations/update-17-2-0/lib/edit-build-config.ts
new file mode 100644
index 0000000000000..9deb336f80701
--- /dev/null
+++ b/packages/vite/src/migrations/update-17-2-0/lib/edit-build-config.ts
@@ -0,0 +1,149 @@
+import {
+ ChangeType,
+ ProjectConfiguration,
+ Tree,
+ applyChangesToString,
+ joinPathFragments,
+ logger,
+ offsetFromRoot,
+ updateProjectConfiguration,
+} from '@nx/devkit';
+import { tsquery } from '@phenomnomnominal/tsquery';
+import ts = require('typescript');
+
+export function updateBuildOutDirAndRoot(
+ options: Record,
+ configContents: string,
+ projectConfig: ProjectConfiguration,
+ targetName: string,
+ tree: Tree,
+ projectName: string
+): string {
+ const foundDefineConfig = tsquery.query(
+ configContents,
+ 'CallExpression:has(Identifier[name="defineConfig"])'
+ )?.[0];
+
+ if (!foundDefineConfig) {
+ logger.warn(`
+ Could not find defineConfig in your vite.config file.
+ Please add the build.outDir and root options to your vite.config file.
+ `);
+ }
+
+ configContents = fixBuild(
+ options,
+ configContents,
+ projectConfig,
+ targetName,
+ tree,
+ projectName,
+ foundDefineConfig
+ );
+
+ configContents = addRoot(configContents, foundDefineConfig);
+
+ return configContents;
+}
+
+function fixBuild(
+ options: Record,
+ configContents: string,
+ projectConfig: ProjectConfiguration,
+ targetName: string,
+ tree: Tree,
+ projectName: string,
+ foundDefineConfig?: ts.Node
+) {
+ let outputPath = '';
+
+ // In vite.config.ts, we want to keep the path relative to workspace root
+ if (options.outputPath) {
+ outputPath = joinPathFragments(
+ offsetFromRoot(projectConfig.root),
+ options.outputPath
+ );
+ } else {
+ outputPath = joinPathFragments(
+ offsetFromRoot(projectConfig.root),
+ 'dist',
+ projectConfig.root
+ );
+ }
+
+ // In project.json, we want to keep the path starting from workspace root
+ projectConfig.targets[targetName].options.outputPath = options.outputPath
+ ? options.outputPath
+ : joinPathFragments('dist', projectConfig.root);
+ updateProjectConfiguration(tree, projectName, projectConfig);
+
+ const buildObject = tsquery.query(
+ configContents,
+ `PropertyAssignment:has(Identifier[name="build"])`
+ )?.[0];
+ let buildOutDir: ts.Node[];
+ if (buildObject) {
+ buildOutDir = tsquery.query(
+ buildObject,
+ `PropertyAssignment:has(Identifier[name="outDir"])`
+ );
+ }
+
+ if (buildOutDir?.length > 0) {
+ return configContents;
+ } else if (buildObject) {
+ // has build, has no outDir
+ // so add outDir
+ return applyChangesToString(configContents, [
+ {
+ type: ChangeType.Insert,
+ index: buildObject.getStart() + `build: {`.length + 1,
+ text: `outDir: '${outputPath}',`,
+ },
+ ]);
+ } else {
+ return addBuildProperty(configContents, outputPath, foundDefineConfig);
+ }
+}
+
+function addRoot(
+ configFileContents: string,
+ foundDefineConfig?: ts.Node
+): string {
+ const rootOption = tsquery.query(
+ configFileContents,
+ `PropertyAssignment:has(Identifier[name="root"]) Identifier[name="__dirname"]`
+ )?.[0];
+
+ if (rootOption || !foundDefineConfig) {
+ return configFileContents;
+ } else {
+ return applyChangesToString(configFileContents, [
+ {
+ type: ChangeType.Insert,
+ index: foundDefineConfig.getStart() + 14,
+ text: `root: __dirname,`,
+ },
+ ]);
+ }
+}
+
+function addBuildProperty(
+ configFileContents: string,
+ outputPath: string,
+ foundDefineConfig: ts.Node
+): string {
+ if (foundDefineConfig) {
+ return applyChangesToString(configFileContents, [
+ {
+ type: ChangeType.Insert,
+ index: foundDefineConfig.getStart() + 14,
+ text: `build: {
+ outDir: '${outputPath}',
+ },`,
+ },
+ ]);
+ } else {
+ return configFileContents;
+ }
+}
diff --git a/packages/vite/src/migrations/update-17-2-0/lib/edit-test-config.ts b/packages/vite/src/migrations/update-17-2-0/lib/edit-test-config.ts
new file mode 100644
index 0000000000000..75e39660c2856
--- /dev/null
+++ b/packages/vite/src/migrations/update-17-2-0/lib/edit-test-config.ts
@@ -0,0 +1,88 @@
+import {
+ ChangeType,
+ ProjectConfiguration,
+ applyChangesToString,
+ joinPathFragments,
+ offsetFromRoot,
+} from '@nx/devkit';
+import { tsquery } from '@phenomnomnominal/tsquery';
+import ts = require('typescript');
+
+export function updateTestConfig(
+ configContents: string,
+ projectConfig: ProjectConfiguration
+): string {
+ const testObject = tsquery.query(
+ configContents,
+ `PropertyAssignment:has(Identifier[name="test"])`
+ )?.[0];
+ let testCoverageDir: ts.Node;
+ let testCoverage: ts.Node;
+ let provider: ts.Node;
+ if (testObject) {
+ testCoverage = tsquery.query(
+ testObject,
+ `PropertyAssignment:has(Identifier[name="coverage"])`
+ )?.[0];
+ if (testCoverage) {
+ testCoverageDir = tsquery.query(
+ testCoverage,
+ `PropertyAssignment:has(Identifier[name="reportsDirectory"])`
+ )?.[0];
+ provider = tsquery.query(
+ testCoverage,
+ `PropertyAssignment:has(Identifier[name="provider"])`
+ )?.[0];
+ }
+ }
+
+ let coverageDir = '';
+
+ if (projectConfig.targets?.test?.options?.reportsDirectory) {
+ coverageDir = projectConfig.targets?.test?.options?.reportsDirectory;
+ } else {
+ coverageDir = joinPathFragments(
+ offsetFromRoot(projectConfig.root),
+ 'coverage',
+ projectConfig.root
+ );
+ }
+
+ if (testCoverageDir) {
+ // Do nothing
+ } else if (testCoverage) {
+ // has test.coverage, has no reportsDirectory
+ // so add reportsDirectory
+ configContents = applyChangesToString(configContents, [
+ {
+ type: ChangeType.Insert,
+ index: testCoverage.getStart() + `coverage: {`.length + 1,
+ text: `reportsDirectory: '${coverageDir}',`,
+ },
+ ]);
+ if (!provider) {
+ configContents = applyChangesToString(configContents, [
+ {
+ type: ChangeType.Insert,
+ index: testCoverage.getStart() + `coverage: {`.length + 1,
+ text: `provider: 'v8',`,
+ },
+ ]);
+ }
+ } else if (testObject) {
+ configContents = applyChangesToString(configContents, [
+ {
+ type: ChangeType.Insert,
+ index: testObject.getStart() + `test: {`.length + 1,
+ text: `coverage: {
+ reportsDirectory: '${coverageDir}',
+ provider: 'v8',
+ },`,
+ },
+ ]);
+ } else {
+ // has no test so do nothing
+ }
+
+ return configContents;
+}
diff --git a/packages/vite/src/migrations/update-17-2-0/lib/find-vite-config.ts b/packages/vite/src/migrations/update-17-2-0/lib/find-vite-config.ts
new file mode 100644
index 0000000000000..a2e6847b46431
--- /dev/null
+++ b/packages/vite/src/migrations/update-17-2-0/lib/find-vite-config.ts
@@ -0,0 +1,11 @@
+import { Tree, joinPathFragments } from '@nx/devkit';
+
+export function findViteConfig(tree: Tree, searchRoot: string) {
+ const allowsExt = ['js', 'mjs', 'ts', 'cjs', 'mts', 'cts'];
+
+ for (const ext of allowsExt) {
+ if (tree.exists(joinPathFragments(searchRoot, `vite.config.${ext}`))) {
+ return joinPathFragments(searchRoot, `vite.config.${ext}`);
+ }
+ }
+}
diff --git a/packages/vite/src/migrations/update-17-2-0/update-vite-config.spec.ts b/packages/vite/src/migrations/update-17-2-0/update-vite-config.spec.ts
new file mode 100644
index 0000000000000..f49f3e25a4e29
--- /dev/null
+++ b/packages/vite/src/migrations/update-17-2-0/update-vite-config.spec.ts
@@ -0,0 +1,262 @@
+import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
+import {
+ Tree,
+ addProjectConfiguration,
+ readProjectConfiguration,
+} from '@nx/devkit';
+
+import updateBuildDir from './update-vite-config';
+
+describe('change-vite-ts-paths-plugin migration', () => {
+ let tree: Tree;
+
+ beforeEach(() => {
+ tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ });
+
+ it('should add build outDir to vite.config.ts', async () => {
+ addProject1(tree, 'demo');
+ await updateBuildDir(tree);
+ expect(tree.read('apps/demo/vite.config.ts', 'utf-8')).toMatchSnapshot();
+ expect(
+ readProjectConfiguration(tree, 'demo').targets.build.options.outputPath
+ ).toBe('dist/apps/demo');
+ });
+
+ it('should add build outDir to vite.config.ts if build exists', async () => {
+ addProject2(tree, 'demo2');
+ await updateBuildDir(tree);
+ expect(tree.read('demo2/vite.config.ts', 'utf-8')).toMatchSnapshot();
+ expect(
+ readProjectConfiguration(tree, 'demo2').targets.build.options.outputPath
+ ).toBe('dist/demo2');
+ });
+
+ it('should add file replacements to vite.config.ts', async () => {
+ addProject3(tree, 'demo3');
+ await updateBuildDir(tree);
+ expect(tree.read('demo3/vite.config.ts', 'utf-8')).toMatchSnapshot();
+ expect(
+ readProjectConfiguration(tree, 'demo3').targets.build.options.outputPath
+ ).toBe('dist/demo3');
+ });
+});
+
+function addProject1(tree: Tree, name: string) {
+ addProjectConfiguration(tree, name, {
+ root: `apps/${name}`,
+ sourceRoot: `apps/${name}/src`,
+ targets: {
+ build: {
+ executor: '@nx/vite:build',
+ outputs: ['{options.outputPath}'],
+ defaultConfiguration: 'production',
+ options: {
+ outputPath: `dist/apps/${name}`,
+ buildLibsFromSource: false,
+ },
+ configurations: {
+ development: {
+ mode: 'development',
+ },
+ production: {
+ mode: 'production',
+ },
+ },
+ },
+ },
+ });
+
+ tree.write(
+ `apps/${name}/vite.config.ts`,
+ `
+///
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import viteTsConfigPaths from 'vite-tsconfig-paths';
+
+export default defineConfig({
+ cacheDir: '../../node_modules/.vite/${name}',
+ server: {
+ port: 4200,
+ host: 'localhost',
+ },
+
+ preview: {
+ port: 4300,
+ host: 'localhost',
+ },
+
+ plugins: [
+ react(),
+ viteTsConfigPaths({
+ root: '../../'
+ })
+ ],
+
+ // Uncomment this if you are using workers.
+ // worker: {
+ // plugins: [
+ // viteTsConfigPaths({
+ // root: '../../',
+ // }),
+ // ],
+ // },
+
+ test: {
+ globals: true,
+ cache: {
+ dir: '../../node_modules/.vitest',
+ },
+ environment: 'jsdom',
+ include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+ },
+});
+
+`
+ );
+}
+
+function addProject2(tree: Tree, name: string) {
+ addProjectConfiguration(tree, name, {
+ root: `${name}`,
+ sourceRoot: `${name}/src`,
+ targets: {
+ build: {
+ executor: '@nx/vite:build',
+ outputs: ['{options.outputPath}'],
+ defaultConfiguration: 'production',
+ options: {
+ outputPath: `dist/${name}`,
+ },
+ configurations: {
+ development: {
+ mode: 'development',
+ },
+ production: {
+ mode: 'production',
+ },
+ },
+ },
+ },
+ });
+
+ tree.write(
+ `${name}/vite.config.ts`,
+ `
+///
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import viteTsConfigPaths from 'vite-tsconfig-paths';
+
+export default defineConfig({
+ cacheDir: '../../node_modules/.vite/${name}',
+ server: {
+ port: 4200,
+ host: 'localhost',
+ },
+
+ preview: {
+ port: 4300,
+ host: 'localhost',
+ },
+
+ plugins: [
+ react(),
+ viteTsConfigPaths({
+ root: '../../'
+ })
+ ],
+
+ build: {
+ someProperty: 'someValue',
+ },
+
+ test: {
+ globals: true,
+ cache: {
+ dir: '../../node_modules/.vitest',
+ },
+ environment: 'jsdom',
+ include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+ },
+});
+
+`
+ );
+}
+
+function addProject3(tree: Tree, name: string) {
+ addProjectConfiguration(tree, name, {
+ root: `${name}`,
+ sourceRoot: `${name}/src`,
+ targets: {
+ build: {
+ executor: '@nx/vite:build',
+ outputs: ['{options.outputPath}'],
+ defaultConfiguration: 'production',
+ options: {
+ outputPath: `dist/${name}`,
+ },
+ configurations: {
+ development: {
+ mode: 'development',
+ },
+ production: {
+ mode: 'production',
+ fileReplacements: [
+ {
+ replace: `${name}/src/environments/environment.ts`,
+ with: `${name}/src/environments/environment.prod.ts`,
+ },
+ ],
+ },
+ },
+ },
+ },
+ });
+
+ tree.write(
+ `${name}/vite.config.ts`,
+ `
+///
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import viteTsConfigPaths from 'vite-tsconfig-paths';
+
+export default defineConfig({
+ cacheDir: '../../node_modules/.vite/${name}',
+ server: {
+ port: 4200,
+ host: 'localhost',
+ },
+
+ preview: {
+ port: 4300,
+ host: 'localhost',
+ },
+
+ plugins: [
+ react(),
+ viteTsConfigPaths({
+ root: '../../'
+ })
+ ],
+
+ build: {
+ someProperty: 'someValue',
+ },
+
+ test: {
+ globals: true,
+ cache: {
+ dir: '../../node_modules/.vitest',
+ },
+ environment: 'jsdom',
+ include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+ },
+});
+
+`
+ );
+}
diff --git a/packages/vite/src/migrations/update-17-2-0/update-vite-config.ts b/packages/vite/src/migrations/update-17-2-0/update-vite-config.ts
new file mode 100644
index 0000000000000..031403057eedd
--- /dev/null
+++ b/packages/vite/src/migrations/update-17-2-0/update-vite-config.ts
@@ -0,0 +1,55 @@
+import { Tree, formatFiles, getProjects, joinPathFragments } from '@nx/devkit';
+import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
+import { ViteBuildExecutorOptions } from '../../executors/build/schema';
+import { updateBuildOutDirAndRoot } from './lib/edit-build-config';
+import { updateTestConfig } from './lib/edit-test-config';
+import { addFileReplacements } from './lib/add-file-replacements';
+
+export default async function updateBuildDir(tree: Tree) {
+ const projects = getProjects(tree);
+ forEachExecutorOptions(
+ tree,
+ '@nx/vite:build',
+ (options, projectName, targetName) => {
+ const projectConfig = projects.get(projectName);
+ const config =
+ options.configFile || findViteConfig(tree, projectConfig.root);
+ if (!config || !tree.exists(config)) {
+ return;
+ }
+ let configContents = tree.read(config, 'utf-8');
+
+ configContents = updateBuildOutDirAndRoot(
+ options,
+ configContents,
+ projectConfig,
+ targetName,
+ tree,
+ projectName
+ );
+
+ configContents = updateTestConfig(configContents, projectConfig);
+
+ if (options.fileReplacements?.length > 0) {
+ configContents = addFileReplacements(
+ configContents,
+ options.fileReplacements
+ );
+ }
+
+ tree.write(config, configContents);
+ }
+ );
+
+ await formatFiles(tree);
+}
+
+function findViteConfig(tree: Tree, searchRoot: string) {
+ const allowsExt = ['js', 'mjs', 'ts', 'cjs', 'mts', 'cts'];
+
+ for (const ext of allowsExt) {
+ if (tree.exists(joinPathFragments(searchRoot, `vite.config.${ext}`))) {
+ return joinPathFragments(searchRoot, `vite.config.${ext}`);
+ }
+ }
+}
diff --git a/packages/vite/src/utils/executor-utils.ts b/packages/vite/src/utils/executor-utils.ts
index 93ed383ee33a5..5e42a310bd9ae 100644
--- a/packages/vite/src/utils/executor-utils.ts
+++ b/packages/vite/src/utils/executor-utils.ts
@@ -32,9 +32,9 @@ export function createBuildableTsConfig(
context: ExecutorContext
) {
const tsConfig = resolve(projectRoot, 'tsconfig.json');
- options.buildLibsFromSource ??= true;
+ options['buildLibsFromSource'] ??= true;
- if (!options.buildLibsFromSource) {
+ if (!options['buildLibsFromSource']) {
const { dependencies } = calculateProjectBuildableDependencies(
context.taskGraph,
context.projectGraph,
diff --git a/packages/vite/src/utils/generator-utils.ts b/packages/vite/src/utils/generator-utils.ts
index 97ce1e716625f..6c02a8c251c8a 100644
--- a/packages/vite/src/utils/generator-utils.ts
+++ b/packages/vite/src/utils/generator-utils.ts
@@ -170,17 +170,13 @@ export function addOrChangeTestTarget(
) {
const project = readProjectConfiguration(tree, options.project);
- const coveragePath = joinPathFragments(
+ const reportsDirectory = joinPathFragments(
+ offsetFromRoot(project.root),
'coverage',
project.root === '.' ? options.project : project.root
);
const testOptions: VitestExecutorOptions = {
- passWithNoTests: true,
- // vitest runs in the project root so we have to offset to the workspaceRoot
- reportsDirectory: joinPathFragments(
- offsetFromRoot(project.root),
- coveragePath
- ),
+ reportsDirectory,
};
project.targets ??= {};
@@ -205,6 +201,7 @@ export function addOrChangeBuildTarget(
target: string
) {
const project = readProjectConfiguration(tree, options.project);
+
const buildOptions: ViteBuildExecutorOptions = {
outputPath: joinPathFragments(
'dist',
@@ -219,8 +216,8 @@ export function addOrChangeBuildTarget(
project.targets[target].options?.fileReplacements;
if (project.targets[target].executor === '@nxext/vite:build') {
- buildOptions.base = project.targets[target].options?.baseHref;
- buildOptions.sourcemap = project.targets[target].options?.sourcemaps;
+ buildOptions['base'] = project.targets[target].options?.baseHref;
+ buildOptions['sourcemap'] = project.targets[target].options?.sourcemaps;
}
project.targets[target].options = { ...buildOptions };
project.targets[target].executor = '@nx/vite:build';
@@ -257,9 +254,6 @@ export function addOrChangeServeTarget(
const serveTarget = project.targets[target];
const serveOptions: ViteDevServerExecutorOptions = {
buildTarget: `${options.project}:build`,
- https: project.targets[target].options?.https,
- hmr: project.targets[target].options?.hmr,
- open: project.targets[target].options?.open,
};
if (serveTarget.executor === '@nxext/vite:dev') {
serveOptions.proxyConfig = project.targets[target].options.proxyConfig;
@@ -316,8 +310,8 @@ export function addPreviewTarget(
if (target.executor === '@nxext/vite:dev') {
previewOptions.proxyConfig = target.options.proxyConfig;
}
- previewOptions.https = target.options?.https;
- previewOptions.open = target.options?.open;
+ previewOptions['https'] = target.options?.https;
+ previewOptions['open'] = target.options?.open;
}
// Adds a preview target.
@@ -486,17 +480,21 @@ export interface ViteConfigFileOptions {
rollupOptionsExternal?: string[];
imports?: string[];
plugins?: string[];
+ coverageProvider?: 'v8' | 'istanbul' | 'custom';
}
export function createOrEditViteConfig(
tree: Tree,
options: ViteConfigFileOptions,
onlyVitest: boolean,
- projectAlreadyHasViteTargets?: TargetFlags
+ projectAlreadyHasViteTargets?: TargetFlags,
+ vitestFileName?: boolean
) {
- const projectConfig = readProjectConfiguration(tree, options.project);
+ const { root: projectRoot } = readProjectConfiguration(tree, options.project);
- const viteConfigPath = `${projectConfig.root}/vite.config.ts`;
+ const viteConfigPath = vitestFileName
+ ? `${projectRoot}/vitest.config.ts`
+ : `${projectRoot}/vite.config.ts`;
const buildOption = onlyVitest
? ''
@@ -505,6 +503,7 @@ export function createOrEditViteConfig(
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
+ outDir: '${offsetFromRoot(projectRoot)}dist/${projectRoot}',
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
@@ -517,9 +516,13 @@ export function createOrEditViteConfig(
rollupOptions: {
// External packages that should not be bundled into your library.
external: [${options.rollupOptionsExternal ?? ''}]
- }
+ },
},`
- : ``;
+ : `
+ build: {
+ outDir: '${offsetFromRoot(projectRoot)}dist/${projectRoot}',
+ },
+ `;
const imports: string[] = options.imports ? options.imports : [];
@@ -546,15 +549,21 @@ export function createOrEditViteConfig(
? `test: {
globals: true,
cache: {
- dir: '${offsetFromRoot(projectConfig.root)}node_modules/.vitest'
+ dir: '${offsetFromRoot(projectRoot)}node_modules/.vitest'
},
environment: '${options.testEnvironment ?? 'jsdom'}',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
${
options.inSourceTests
- ? `includeSource: ['src/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}']`
+ ? `includeSource: ['src/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],`
: ''
}
+ coverage: {
+ reportsDirectory: '${offsetFromRoot(projectRoot)}coverage/${projectRoot}',
+ provider: ${
+ options.coverageProvider ? `'${options.coverageProvider}'` : `'v8'`
+ },
+ }
},`
: '';
@@ -591,8 +600,8 @@ export function createOrEditViteConfig(
// },`;
const cacheDir = `cacheDir: '${offsetFromRoot(
- projectConfig.root
- )}node_modules/.vite/${options.project}',`;
+ projectRoot
+ )}node_modules/.vite/${projectRoot}',`;
if (tree.exists(viteConfigPath)) {
handleViteConfigFileExists(
@@ -604,7 +613,8 @@ export function createOrEditViteConfig(
plugins,
testOption,
cacheDir,
- offsetFromRoot(projectConfig.root),
+ offsetFromRoot(projectRoot),
+ projectRoot,
projectAlreadyHasViteTargets
);
return;
@@ -617,6 +627,7 @@ export function createOrEditViteConfig(
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
+ root: __dirname,
${cacheDir}
${devServerOption}
${previewServerOption}
@@ -773,6 +784,7 @@ function handleViteConfigFileExists(
testOption: string,
cacheDir: string,
offsetFromRoot: string,
+ projectRoot: string,
projectAlreadyHasViteTargets?: TargetFlags
) {
if (
@@ -788,17 +800,22 @@ function handleViteConfigFileExists(
);
}
- const buildOptionObject = {
- lib: {
- entry: 'src/index.ts',
- name: options.project,
- fileName: 'index',
- formats: ['es', 'cjs'],
- },
- rollupOptions: {
- external: options.rollupOptionsExternal ?? [],
- },
- };
+ const buildOptionObject = options.includeLib
+ ? {
+ lib: {
+ entry: 'src/index.ts',
+ name: options.project,
+ fileName: 'index',
+ formats: ['es', 'cjs'],
+ },
+ rollupOptions: {
+ external: options.rollupOptionsExternal ?? [],
+ },
+ outDir: `${offsetFromRoot}dist/${projectRoot}`,
+ }
+ : {
+ outDir: `${offsetFromRoot}dist/${projectRoot}`,
+ };
const testOptionObject = {
globals: true,
@@ -807,6 +824,10 @@ function handleViteConfigFileExists(
},
environment: options.testEnvironment ?? 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+ coverage: {
+ reportsDirectory: `${offsetFromRoot}coverage/${projectRoot}`,
+ provider: `${options.coverageProvider ?? 'v8'}`,
+ },
};
const changed = ensureViteConfigIsCorrect(
diff --git a/packages/vite/src/utils/options-utils.ts b/packages/vite/src/utils/options-utils.ts
index 05b849f78a681..07d966969b389 100644
--- a/packages/vite/src/utils/options-utils.ts
+++ b/packages/vite/src/utils/options-utils.ts
@@ -6,23 +6,14 @@ import {
readTargetOptions,
} from '@nx/devkit';
import { existsSync } from 'fs';
-import { relative } from 'path';
-import {
- BuildOptions,
- InlineConfig,
- PluginOption,
- PreviewOptions,
- ServerOptions,
-} from 'vite';
+import { PreviewOptions, ServerOptions } from 'vite';
import { ViteDevServerExecutorOptions } from '../executors/dev-server/schema';
-import { VitePreviewServerExecutorOptions } from '../executors/preview-server/schema';
-import replaceFiles from '../../plugins/rollup-replace-files.plugin';
-import { ViteBuildExecutorOptions } from '../executors/build/schema';
/**
* Returns the path to the vite config file or undefined when not found.
*/
export function normalizeViteConfigFilePath(
+ contextRoot: string,
projectRoot: string,
configFile?: string
): string | undefined {
@@ -35,11 +26,28 @@ export function normalizeViteConfigFilePath(
}
return normalized;
}
- return existsSync(joinPathFragments(projectRoot, 'vite.config.ts'))
- ? joinPathFragments(projectRoot, 'vite.config.ts')
- : existsSync(joinPathFragments(projectRoot, 'vite.config.js'))
- ? joinPathFragments(projectRoot, 'vite.config.js')
- : undefined;
+
+ const allowsExt = ['js', 'mjs', 'ts', 'cjs', 'mts', 'cts'];
+
+ for (const ext of allowsExt) {
+ if (
+ existsSync(
+ joinPathFragments(contextRoot, projectRoot, `vite.config.${ext}`)
+ )
+ ) {
+ return joinPathFragments(contextRoot, projectRoot, `vite.config.${ext}`);
+ } else if (
+ existsSync(
+ joinPathFragments(contextRoot, projectRoot, `vitest.config.${ext}`)
+ )
+ ) {
+ return joinPathFragments(
+ contextRoot,
+ projectRoot,
+ `vitest.config.${ext}`
+ );
+ }
+ }
}
export function getProjectTsConfigPath(
@@ -75,36 +83,6 @@ export function getViteServerProxyConfigPath(
}
}
-/**
- * Builds the shared options for vite.
- *
- * Most shared options are derived from the build target.
- */
-export function getViteSharedConfig(
- options: ViteBuildExecutorOptions,
- clearScreen: boolean | undefined,
- context: ExecutorContext
-): InlineConfig {
- const projectRoot =
- context.projectsConfigurations.projects[context.projectName].root;
-
- const root =
- projectRoot === '.'
- ? process.cwd()
- : relative(context.cwd, joinPathFragments(context.root, projectRoot));
-
- return {
- mode: options.mode,
- root,
- base: options.base,
- configFile: normalizeViteConfigFilePath(projectRoot, options.configFile),
- plugins: [replaceFiles(options.fileReplacements) as PluginOption],
- optimizeDeps: { force: options.force },
- clearScreen: clearScreen,
- logLevel: options.logLevel,
- };
-}
-
/**
* Builds the options for the vite dev server.
*/
@@ -119,12 +97,6 @@ export async function getViteServerOptions(
const projectRoot =
context.projectsConfigurations.projects[context.projectName].root;
const serverOptions: ServerOptions = {
- host: options.host,
- port: options.port,
- https: options.https,
- hmr: options.hmr,
- open: options.open,
- cors: options.cors,
fs: {
allow: [
searchForWorkspaceRoot(joinPathFragments(projectRoot)),
@@ -145,53 +117,14 @@ export async function getViteServerOptions(
return serverOptions;
}
-/**
- * Builds the build options for the vite.
- */
-export function getViteBuildOptions(
- options: ViteBuildExecutorOptions,
- context: ExecutorContext
-): BuildOptions {
- const projectRoot =
- context.projectsConfigurations.projects[context.projectName].root;
-
- const outputPath = joinPathFragments(
- 'dist',
- projectRoot != '.' ? projectRoot : context.projectName
- );
-
- return {
- outDir: relative(projectRoot, options.outputPath ?? outputPath),
- emptyOutDir: options.emptyOutDir,
- reportCompressedSize: true,
- cssCodeSplit: options.cssCodeSplit,
- target: options.target,
- commonjsOptions: {
- transformMixedEsModules: true,
- },
- sourcemap: options.sourcemap,
- minify: options.minify,
- manifest: options.manifest,
- ssrManifest: options.ssrManifest,
- ssr: options.ssr,
- watch: options.watch as BuildOptions['watch'],
- };
-}
-
/**
* Builds the options for the vite preview server.
*/
export function getVitePreviewOptions(
- options: VitePreviewServerExecutorOptions,
+ options: Record,
context: ExecutorContext
): PreviewOptions {
- const serverOptions: ServerOptions = {
- host: options.host,
- port: options.port,
- https: options.https,
- open: options.open,
- };
-
+ const serverOptions: ServerOptions = {};
const proxyConfigPath = getViteServerProxyConfigPath(
options.proxyConfig,
context
diff --git a/packages/vite/src/utils/vite-config-edit-utils.ts b/packages/vite/src/utils/vite-config-edit-utils.ts
index ec515a45e9361..9434918fb0af4 100644
--- a/packages/vite/src/utils/vite-config-edit-utils.ts
+++ b/packages/vite/src/utils/vite-config-edit-utils.ts
@@ -85,7 +85,12 @@ function handleBuildOrTestNode(
let updatedPropsString = '';
for (const prop of existingProperties) {
const propName = prop.name.getText();
- if (!configContentObject[propName] && propName !== 'dir') {
+ if (
+ !configContentObject[propName] &&
+ propName !== 'dir' &&
+ propName !== 'reportsDirectory' &&
+ propName !== 'provider'
+ ) {
updatedPropsString += `'${propName}': ${prop.initializer.getText()},\n`;
}
}
diff --git a/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap b/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap
index cdda91ef52e32..0b1a61b415bd1 100644
--- a/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap
+++ b/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap
@@ -46,6 +46,7 @@ import vue from '@vitejs/plugin-vue';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
+ root: __dirname,
cacheDir: '../node_modules/.vite/test',
server: {
@@ -65,6 +66,10 @@ export default defineConfig({
// plugins: [ nxViteTsPaths() ],
// },
+ build: {
+ outDir: '../dist/test',
+ },
+
test: {
globals: true,
cache: {
@@ -72,6 +77,11 @@ export default defineConfig({
},
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+
+ coverage: {
+ reportsDirectory: '../coverage/test',
+ provider: 'v8',
+ },
},
});
"
@@ -141,7 +151,6 @@ exports[`application generator should set up project correctly with given option
"executor": "@nx/vite:test",
"outputs": ["{options.reportsDirectory}"],
"options": {
- "passWithNoTests": true,
"reportsDirectory": "../coverage/test"
}
},
diff --git a/packages/vue/src/generators/library/__snapshots__/library.spec.ts.snap b/packages/vue/src/generators/library/__snapshots__/library.spec.ts.snap
index 251e74f2bae3d..dc9843b800d76 100644
--- a/packages/vue/src/generators/library/__snapshots__/library.spec.ts.snap
+++ b/packages/vue/src/generators/library/__snapshots__/library.spec.ts.snap
@@ -36,6 +36,7 @@ import * as path from 'path';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
+ root: __dirname,
cacheDir: '../node_modules/.vite/my-lib',
plugins: [
@@ -56,6 +57,7 @@ export default defineConfig({
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
+ outDir: '../dist/my-lib',
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
@@ -78,6 +80,11 @@ export default defineConfig({
},
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+
+ coverage: {
+ reportsDirectory: '../coverage/my-lib',
+ provider: 'v8',
+ },
},
});
"
diff --git a/packages/web/src/generators/application/application.spec.ts b/packages/web/src/generators/application/application.spec.ts
index 2cdc8b1891962..6797be2bf7ebc 100644
--- a/packages/web/src/generators/application/application.spec.ts
+++ b/packages/web/src/generators/application/application.spec.ts
@@ -404,7 +404,6 @@ describe('app', () => {
it('should setup the nrwl vite:build builder if bundler is vite', async () => {
await applicationGenerator(tree, {
name: 'my-app',
-
bundler: 'vite',
projectNameAndRootFormat: 'as-provided',
});
diff --git a/packages/web/src/migrations/update-15-9-1/add-dropped-dependencies.ts b/packages/web/src/migrations/update-15-9-1/add-dropped-dependencies.ts
index abca261df8553..e8a6c1f93cb72 100644
--- a/packages/web/src/migrations/update-15-9-1/add-dropped-dependencies.ts
+++ b/packages/web/src/migrations/update-15-9-1/add-dropped-dependencies.ts
@@ -22,7 +22,9 @@ export default async function addDroppedDependencies(tree: Tree) {
projectConfiguration.targets ?? {}
)) {
for (const droppedDependency of droppedDependencies) {
- if (targetConfiguration.executor?.startsWith(droppedDependency + ':')) {
+ if (
+ targetConfiguration?.['executor']?.startsWith(droppedDependency + ':')
+ ) {
devDependencies[droppedDependency] = NX_VERSION;
}
}
@@ -35,7 +37,9 @@ export default async function addDroppedDependencies(tree: Tree) {
nxJson?.targetDefaults ?? {}
)) {
for (const droppedDependency of droppedDependencies) {
- if (targetConfiguration.executor?.startsWith(droppedDependency + ':')) {
+ if (
+ targetConfiguration?.['executor']?.startsWith(droppedDependency + ':')
+ ) {
devDependencies[droppedDependency] = NX_VERSION;
}
}