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; } }