diff --git a/packages/react/src/generators/application/application.spec.ts b/packages/react/src/generators/application/application.spec.ts index 9939900410..7ba5704f4b 100644 --- a/packages/react/src/generators/application/application.spec.ts +++ b/packages/react/src/generators/application/application.spec.ts @@ -941,7 +941,10 @@ describe('app', () => { it('should create correct tsconfig compilerOptions', () => { const tsconfigJson = readJson(viteAppTree, '/apps/my-app/tsconfig.json'); - expect(tsconfigJson.compilerOptions.types).toMatchObject(['vite/client']); + expect(tsconfigJson.compilerOptions.types).toMatchObject([ + 'vite/client', + 'vitest', + ]); }); it('should create index.html and vite.config file at the root of the app', () => { diff --git a/packages/react/src/utils/create-ts-config.ts b/packages/react/src/utils/create-ts-config.ts index 363ddaa85c..6a1ea02232 100644 --- a/packages/react/src/utils/create-ts-config.ts +++ b/packages/react/src/utils/create-ts-config.ts @@ -11,6 +11,7 @@ export function createTsConfig( style?: string; bundler?: string; rootProject?: boolean; + unitTestRunner?: string; }, relativePathToRootTsConfig: string ) { @@ -36,7 +37,10 @@ export function createTsConfig( } if (options.bundler === 'vite') { - json.compilerOptions.types = ['vite/client']; + json.compilerOptions.types = + options.unitTestRunner === 'vitest' + ? ['vite/client', 'vitest'] + : ['vite/client']; } // inline tsconfig.base.json into the project diff --git a/packages/storybook/src/generators/configuration/configuration-nested.spec.ts b/packages/storybook/src/generators/configuration/configuration-nested.spec.ts index 640c9a2f2a..7f3e0bfd9a 100644 --- a/packages/storybook/src/generators/configuration/configuration-nested.spec.ts +++ b/packages/storybook/src/generators/configuration/configuration-nested.spec.ts @@ -36,7 +36,7 @@ describe('@nrwl/storybook:configuration for workspaces with Root project', () => skipLibCheck: true, strict: true, target: 'ESNext', - types: ['vite/client'], + types: ['vite/client', 'vitest'], useDefineForClassFields: true, noImplicitOverride: true, noPropertyAccessFromIndexSignature: true, 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 c2208d8dac..1072a02cc3 100644 --- a/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap +++ b/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap @@ -2,7 +2,6 @@ exports[`@nrwl/vite:configuration library mode should add config for building library 1`] = ` " - import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import viteTsConfigPaths from 'vite-tsconfig-paths'; @@ -49,7 +48,6 @@ import { join } from 'path'; exports[`@nrwl/vite:configuration library mode should set up non buildable library correctly 1`] = ` " - /// import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import viteTsConfigPaths from 'vite-tsconfig-paths'; @@ -295,7 +293,8 @@ exports[`@nrwl/vite:configuration transform React app to use Vite by providing c \\"builder\\": \\"@nrwl/vite:dev-server\\", \\"defaultConfiguration\\": \\"development\\", \\"options\\": { - \\"buildTarget\\": \\"my-test-mixed-react-app:build\\" + \\"buildTarget\\": \\"my-test-mixed-react-app:build\\", + \\"hmr\\": true }, \\"configurations\\": { \\"development\\": { @@ -339,7 +338,6 @@ exports[`@nrwl/vite:configuration transform React app to use Vite by providing c exports[`@nrwl/vite:configuration transform React app to use Vite should create vite.config file at the root of the app 1`] = ` " - import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import viteTsConfigPaths from 'vite-tsconfig-paths'; @@ -429,7 +427,8 @@ exports[`@nrwl/vite:configuration transform React app to use Vite should transfo \\"builder\\": \\"@nrwl/vite:dev-server\\", \\"defaultConfiguration\\": \\"development\\", \\"options\\": { - \\"buildTarget\\": \\"my-test-react-app:build\\" + \\"buildTarget\\": \\"my-test-react-app:build\\", + \\"hmr\\": true }, \\"configurations\\": { \\"development\\": { @@ -473,7 +472,6 @@ exports[`@nrwl/vite:configuration transform React app to use Vite should transfo exports[`@nrwl/vite:configuration transform Web app to use Vite should create vite.config file at the root of the app 1`] = ` " - import { defineConfig } from 'vite'; import viteTsConfigPaths from 'vite-tsconfig-paths'; @@ -597,7 +595,6 @@ exports[`@nrwl/vite:configuration transform Web app to use Vite should transform exports[`@nrwl/vite:configuration vitest should create a vitest configuration if "includeVitest" is true 1`] = ` " - /// import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import viteTsConfigPaths from 'vite-tsconfig-paths'; diff --git a/packages/vite/src/generators/configuration/configuration.ts b/packages/vite/src/generators/configuration/configuration.ts index b46c359fbb..8c14566c85 100644 --- a/packages/vite/src/generators/configuration/configuration.ts +++ b/packages/vite/src/generators/configuration/configuration.ts @@ -16,6 +16,7 @@ import { handleUnsupportedUserProvidedTargets, handleUnknownExecutors, UserProvidedTargetName, + TargetFlags, } from '../../utils/generator-utils'; import initGenerator from '../init/init'; @@ -39,6 +40,7 @@ export async function viteConfigurationGenerator(tree: Tree, schema: Schema) { * This is for when we are convering an existing project * to use the vite executors. * */ + let projectAlreadyHasViteTargets: TargetFlags; if (!schema.newProject) { const userProvidedTargetName: UserProvidedTargetName = { build: schema.buildTarget, @@ -50,8 +52,9 @@ export async function viteConfigurationGenerator(tree: Tree, schema: Schema) { validFoundTargetName, projectContainsUnsupportedExecutor, userProvidedTargetIsUnsupported, + alreadyHasNxViteTargets, } = findExistingTargetsInProject(targets, userProvidedTargetName); - + projectAlreadyHasViteTargets = alreadyHasNxViteTargets; /** * This means that we only found unsupported build targets in that project. * The only way that buildTarget is defined, means that it is supported. @@ -68,6 +71,20 @@ export async function viteConfigurationGenerator(tree: Tree, schema: Schema) { ); } + if ( + alreadyHasNxViteTargets.build && + (alreadyHasNxViteTargets.serve || + (!alreadyHasNxViteTargets.serve && projectType === 'library')) && + alreadyHasNxViteTargets.test + ) { + throw new Error( + `The project ${schema.project} is aready configured to use the @nrwl/vite executors. + Please try a different project, or remove the existing targets + and re-run this generator to reset the existing Vite Configuration. + ` + ); + } + /** * This means that we did not find any supported executors * so we don't have any valid target names. @@ -84,7 +101,7 @@ export async function viteConfigurationGenerator(tree: Tree, schema: Schema) { !validFoundTargetName.serve && !validFoundTargetName.test ) { - await handleUnknownExecutors(); + await handleUnknownExecutors(schema.project); } /** @@ -127,13 +144,15 @@ export async function viteConfigurationGenerator(tree: Tree, schema: Schema) { }); tasks.push(initTask); - addOrChangeBuildTarget(tree, schema, buildTargetName); + if (!projectAlreadyHasViteTargets?.build) { + addOrChangeBuildTarget(tree, schema, buildTargetName); + } - if (!schema.includeLib) { + if (!schema.includeLib && !projectAlreadyHasViteTargets?.serve) { addOrChangeServeTarget(tree, schema, serveTargetName); } - createOrEditViteConfig(tree, schema); + createOrEditViteConfig(tree, schema, false, projectAlreadyHasViteTargets); if (schema.includeVitest) { const vitestTask = await vitestGenerator(tree, { 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 30a9a740b4..8c214be827 100644 --- a/packages/vite/src/generators/vitest/__snapshots__/vitest.spec.ts.snap +++ b/packages/vite/src/generators/vitest/__snapshots__/vitest.spec.ts.snap @@ -2,7 +2,6 @@ exports[`vitest generator insourceTests should add the insourceSource option in the vite config 1`] = ` " - /// import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import viteTsConfigPaths from 'vite-tsconfig-paths'; @@ -37,7 +36,6 @@ exports[`vitest generator insourceTests should add the insourceSource option in exports[`vitest generator vite.config should create correct vite.config.ts file for apps 1`] = ` " - /// import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import viteTsConfigPaths from 'vite-tsconfig-paths'; @@ -70,7 +68,6 @@ exports[`vitest generator vite.config should create correct vite.config.ts file exports[`vitest generator vite.config should create correct vite.config.ts file for non buildable libs 1`] = ` " - /// import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import viteTsConfigPaths from 'vite-tsconfig-paths'; diff --git a/packages/vite/src/generators/vitest/vitest-generator.ts b/packages/vite/src/generators/vitest/vitest-generator.ts index 6c6e90f545..1900936bee 100644 --- a/packages/vite/src/generators/vitest/vitest-generator.ts +++ b/packages/vite/src/generators/vitest/vitest-generator.ts @@ -96,6 +96,17 @@ function updateTsConfig( path: './tsconfig.spec.json', }); } + + if (!json.compilerOptions?.types?.includes('vitest')) { + if (json.compilerOptions?.types) { + json.compilerOptions.types.push('vitest'); + } else { + if (!json.compilerOptions) { + json.compilerOptions = {}; + } + json.compilerOptions.types = ['vitest']; + } + } return json; }); diff --git a/packages/vite/src/utils/__snapshots__/vite-config-edit-utils.spec.ts.snap b/packages/vite/src/utils/__snapshots__/vite-config-edit-utils.spec.ts.snap index 5af4f8747e..d5711c5162 100644 --- a/packages/vite/src/utils/__snapshots__/vite-config-edit-utils.spec.ts.snap +++ b/packages/vite/src/utils/__snapshots__/vite-config-edit-utils.spec.ts.snap @@ -1,6 +1,54 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`generator utils ensureBuildOptionsInViteConfig should add build options if build options don't exist 1`] = ` +exports[`ensureBuildOptionsInViteConfig should add build and test options if defineConfig is empty 1`] = ` +"import dts from 'vite-plugin-dts'; +import { join } from 'path'; + + import { defineConfig } from 'vite'; + import react from '@vitejs/plugin-react'; + import viteTsConfigPaths from 'vite-tsconfig-paths'; + + export default defineConfig({ + + // Configuration for building your library. + // See: https://vitejs.dev/guide/build.html#library-mode + build: { + lib: { + // Could also be a dictionary or array of multiple entry points. + entry: 'src/index.ts', + name: 'my-app', + fileName: 'index', + // Change this to the formats you want to support. + // Don't forgot to update your package.json as well. + formats: ['es', 'cjs'] + }, + rollupOptions: { + // External packages that should not be bundled into your library. + external: [\\"'react', 'react-dom', 'react/jsx-runtime'\\"] + } + },plugins: [ + dts({ + tsConfigFilePath: join(__dirname, 'tsconfig.lib.json'), + // Faster builds by skipping tests. Set this to false to enable type checking. + skipDiagnostics: true, + }), + react(), + viteTsConfigPaths({ + root: '../../', + }), + ], + test: { + globals: true, + cache: { + dir: '../node_modules/.vitest' + }, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + },}); + " +`; + +exports[`ensureBuildOptionsInViteConfig should add build option but not update test option if test already setup 1`] = ` "import dts from 'vite-plugin-dts'; import { join } from 'path'; import { defineConfig } from 'vite'; @@ -52,16 +100,15 @@ import { defineConfig } from 'vite'; " `; -exports[`generator utils ensureBuildOptionsInViteConfig should add build options if defineConfig is empty 1`] = ` +exports[`ensureBuildOptionsInViteConfig should add build options if build options don't exist 1`] = ` "import dts from 'vite-plugin-dts'; import { join } from 'path'; - - import { defineConfig } from 'vite'; +import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import viteTsConfigPaths from 'vite-tsconfig-paths'; export default defineConfig({ - + // Configuration for building your library. // See: https://vitejs.dev/guide/build.html#library-mode build: { @@ -79,21 +126,33 @@ import { join } from 'path'; external: [\\"'react', 'react-dom', 'react/jsx-runtime'\\"] } },plugins: [ - dts({ + ...[ + react(), + viteTsConfigPaths({ + root: '../../', + }), + ], + dts({ tsConfigFilePath: join(__dirname, 'tsconfig.lib.json'), // Faster builds by skipping tests. Set this to false to enable type checking. skipDiagnostics: true, }), - react(), - viteTsConfigPaths({ - root: '../../', - }), - ], + ], + + test: { + globals: true, + cache: { + dir: '../../node_modules/.vitest', + }, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + }, + }); " `; -exports[`generator utils ensureBuildOptionsInViteConfig should add build options if defineConfig is not used 1`] = ` +exports[`ensureBuildOptionsInViteConfig should add build options if defineConfig is not used 1`] = ` "import dts from 'vite-plugin-dts'; import { join } from 'path'; import { defineConfig } from 'vite'; @@ -117,6 +176,13 @@ import { defineConfig } from 'vite'; // External packages that should not be bundled into your library. external: [\\"'react', 'react-dom', 'react/jsx-runtime'\\"] } + },test: { + globals: true, + cache: { + dir: '../node_modules/.vitest' + }, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], }, plugins: [ ...[ @@ -135,7 +201,7 @@ import { defineConfig } from 'vite'; " `; -exports[`generator utils ensureBuildOptionsInViteConfig should add build options if it is using conditional config 1`] = ` +exports[`ensureBuildOptionsInViteConfig should add build options if it is using conditional config - do nothing for test 1`] = ` " import { defineConfig } from 'vite'; export default defineConfig(({ command, mode, ssrBuild }) => { @@ -157,7 +223,7 @@ exports[`generator utils ensureBuildOptionsInViteConfig should add build options " `; -exports[`generator utils ensureBuildOptionsInViteConfig should add new build options if some build options already exist 1`] = ` +exports[`ensureBuildOptionsInViteConfig should add new build options if some build options already exist 1`] = ` "import dts from 'vite-plugin-dts'; import { join } from 'path'; import { defineConfig } from 'vite'; @@ -199,4 +265,93 @@ import { defineConfig } from 'vite'; " `; -exports[`generator utils ensureBuildOptionsInViteConfig should not do anything if cannot understand syntax of vite config 1`] = `"console.log('Unknown syntax')"`; +exports[`ensureBuildOptionsInViteConfig should not do anything if cannot understand syntax of vite config 1`] = `"console.log('Unknown syntax')"`; + +exports[`ensureBuildOptionsInViteConfig should not do anything if project has everything setup already 1`] = ` +" + import { defineConfig } from 'vite'; + import react from '@vitejs/plugin-react'; + import viteTsConfigPaths from 'vite-tsconfig-paths'; + + export default defineConfig({ + plugins: [ + dts({ + tsConfigFilePath: join(__dirname, 'tsconfig.lib.json'), + // Faster builds by skipping tests. Set this to false to enable type checking. + skipDiagnostics: true, + }), + react(), + viteTsConfigPaths({ + root: '../../../', + }), + ], + + // Configuration for building your library. + // See: https://vitejs.dev/guide/build.html#library-mode + build: { + lib: { + // Could also be a dictionary or array of multiple entry points. + entry: 'src/index.ts', + name: 'pure-libs-react-vite', + fileName: 'index', + // Change this to the formats you want to support. + // Don't forgot to update your package.json as well. + formats: ['es', 'cjs'], + }, + rollupOptions: { + // External packages that should not be bundled into your library. + external: ['react', 'react-dom', 'react/jsx-runtime'], + }, + }, + + test: { + globals: true, + cache: { + dir: '../../../node_modules/.vitest', + }, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + }, + }); + " +`; + +exports[`ensureBuildOptionsInViteConfig should update both test and build options - keep existing settings 1`] = ` +"import dts from 'vite-plugin-dts'; +import { join } from 'path'; +import { defineConfig } from 'vite'; + import react from '@vitejs/plugin-react'; + import viteTsConfigPaths from 'vite-tsconfig-paths'; + + export default defineConfig({ + plugins: [ + ...[ + react(), + viteTsConfigPaths({ + root: '../../', + }), + ], + dts({ + tsConfigFilePath: join(__dirname, 'tsconfig.lib.json'), + // Faster builds by skipping tests. Set this to false to enable type checking. + skipDiagnostics: true, + }), + ], + + test: { + ...{ + my: 'option', + }, + ...{\\"globals\\":true,\\"cache\\":{\\"dir\\":\\"../node_modules/.vitest\\"},\\"environment\\":\\"jsdom\\",\\"include\\":[\\"src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}\\"]} + }, + + build: { + ...{ + my: 'option', + }, + ...{\\"lib\\":{\\"entry\\":\\"src/index.ts\\",\\"name\\":\\"my-app\\",\\"fileName\\":\\"index\\",\\"formats\\":[\\"es\\",\\"cjs\\"]},\\"rollupOptions\\":{\\"external\\":[\\"'react', 'react-dom', 'react/jsx-runtime'\\"]}} + } + + }); + " +`; diff --git a/packages/vite/src/utils/generator-utils.ts b/packages/vite/src/utils/generator-utils.ts index b24ea348ec..ebdffbd3be 100644 --- a/packages/vite/src/utils/generator-utils.ts +++ b/packages/vite/src/utils/generator-utils.ts @@ -15,7 +15,7 @@ import { VitestExecutorOptions } from '../executors/test/schema'; import { Schema } from '../generators/configuration/schema'; import { ensureBuildOptionsInViteConfig } from './vite-config-edit-utils'; -export interface UserProvidedTargetIsUnsupported { +export interface TargetFlags { build?: boolean; serve?: boolean; test?: boolean; @@ -41,7 +41,8 @@ export function findExistingTargetsInProject( ): { validFoundTargetName: ValidFoundTargetName; projectContainsUnsupportedExecutor?: boolean; - userProvidedTargetIsUnsupported?: UserProvidedTargetIsUnsupported; + userProvidedTargetIsUnsupported?: TargetFlags; + alreadyHasNxViteTargets?: TargetFlags; } { let validFoundBuildTarget: string | undefined, validFoundServeTarget: string | undefined, @@ -49,7 +50,10 @@ export function findExistingTargetsInProject( projectContainsUnsupportedExecutor: boolean | undefined, unsupportedUserProvidedTargetBuild: boolean | undefined, unsupportedUserProvidedTargetServe: boolean | undefined, - unsupportedUserProvidedTargetTest: boolean | undefined; + unsupportedUserProvidedTargetTest: boolean | undefined, + alreadyHasNxViteTargetBuild: boolean | undefined, + alreadyHasNxViteTargetServe: boolean | undefined, + alreadyHasNxViteTargetTest: boolean | undefined; const arrayOfSupportedBuilders = [ '@nxext/vite:build', @@ -82,6 +86,12 @@ export function findExistingTargetsInProject( '@nrwl/js:tsc', ]; + const arrayOfNxViteExecutors = [ + '@nrwl/vite:build', + '@nrwl/vite:dev-server', + '@nrwl/vite:test', + ]; + // First, we check if the user has provided a target // If they have, we check if the executor the target is using is supported // If it's not supported, then we set the unsupported flag to true for that target @@ -134,6 +144,17 @@ export function findExistingTargetsInProject( ) { break; } + + if (targets[target].executor === '@nrwl/vite:build') { + alreadyHasNxViteTargetBuild = true; + } + if (targets[target].executor === '@nrwl/vite:dev-server') { + alreadyHasNxViteTargetServe = true; + } + if (targets[target].executor === '@nrwl/vite:test') { + alreadyHasNxViteTargetTest = true; + } + if ( !validFoundBuildTarget && arrayOfSupportedBuilders.includes(targets[target].executor) @@ -152,7 +173,10 @@ export function findExistingTargetsInProject( ) { validFoundTestTarget = target; } - if (arrayofUnsupportedExecutors.includes(targets[target].executor)) { + if ( + !arrayOfNxViteExecutors.includes(targets[target].executor) && + arrayofUnsupportedExecutors.includes(targets[target].executor) + ) { projectContainsUnsupportedExecutor = true; } } @@ -169,6 +193,11 @@ export function findExistingTargetsInProject( serve: unsupportedUserProvidedTargetServe, test: unsupportedUserProvidedTargetTest, }, + alreadyHasNxViteTargets: { + build: alreadyHasNxViteTargetBuild, + serve: alreadyHasNxViteTargetServe, + test: alreadyHasNxViteTargetTest, + }, }; } @@ -270,6 +299,9 @@ export function addOrChangeServeTarget( } project.targets[target].options = { ...serveOptions, + https: project.targets[target].options?.https, + hmr: project.targets[target].options?.hmr, + open: project.targets[target].options?.open, }; project.targets[target].executor = '@nrwl/vite:dev-server'; } else { @@ -308,6 +340,7 @@ export function editTsConfig(tree: Tree, options: Schema) { config.compilerOptions = { target: 'ESNext', useDefineForClassFields: true, + module: 'ESNext', lib: ['DOM', 'DOM.Iterable', 'ESNext'], allowJs: false, skipLibCheck: true, @@ -315,13 +348,14 @@ export function editTsConfig(tree: Tree, options: Schema) { allowSyntheticDefaultImports: true, strict: true, forceConsistentCasingInFileNames: true, - module: 'ESNext', moduleResolution: 'Node', resolveJsonModule: true, isolatedModules: true, noEmit: true, jsx: 'react-jsx', - types: ['vite/client'], + types: options.includeVitest + ? ['vite/client', 'vitest'] + : ['vite/client'], }; config.include = [...config.include, 'src']; break; @@ -331,17 +365,19 @@ export function editTsConfig(tree: Tree, options: Schema) { useDefineForClassFields: true, module: 'ESNext', lib: ['ESNext', 'DOM'], - moduleResolution: 'Node', + skipLibCheck: true, + esModuleInterop: true, strict: true, + moduleResolution: 'Node', resolveJsonModule: true, isolatedModules: true, - esModuleInterop: true, noEmit: true, noUnusedLocals: true, noUnusedParameters: true, noImplicitReturns: true, - skipLibCheck: true, - types: ['vite/client'], + types: options.includeVitest + ? ['vite/client', 'vitest'] + : ['vite/client'], }; config.include = [...config.include, 'src']; break; @@ -418,7 +454,8 @@ export function moveAndEditIndexHtml( export function createOrEditViteConfig( tree: Tree, options: Schema, - onlyVitest?: boolean + onlyVitest?: boolean, + projectAlreadyHasViteTargets?: TargetFlags ) { const projectConfig = readProjectConfiguration(tree, options.project); @@ -484,9 +521,6 @@ export function createOrEditViteConfig( } },` : ''; - const vitestTypes = options.includeVitest - ? `/// ` - : ''; const defineOption = options.inSourceTests ? `define: { @@ -529,13 +563,15 @@ export function createOrEditViteConfig( buildOption, dtsPlugin, dtsImportLine, - pluginOption + pluginOption, + testOption, + offsetFromRoot(projectConfig.root), + projectAlreadyHasViteTargets ); return; } viteConfigContent = ` - ${vitestTypes} import { defineConfig } from 'vite'; ${reactPluginImportLine} import viteTsConfigPaths from 'vite-tsconfig-paths'; @@ -588,7 +624,7 @@ export function getViteConfigPathForProject( } export async function handleUnsupportedUserProvidedTargets( - userProvidedTargetIsUnsupported: UserProvidedTargetIsUnsupported, + userProvidedTargetIsUnsupported: TargetFlags, userProvidedTargetName: UserProvidedTargetName, validFoundTargetName: ValidFoundTargetName ) { @@ -654,10 +690,10 @@ async function handleUnsupportedUserProvidedTargetsErrors( } } -export async function handleUnknownExecutors() { +export async function handleUnknownExecutors(projectName: string) { logger.warn( ` - We could not find any targets in your project that use executors which + We could not find any targets in project ${projectName} that use executors which can be converted to the @nrwl/vite executors. This either means that your project may not have a target @@ -691,8 +727,15 @@ function handleViteConfigFileExists( buildOption: string, dtsPlugin: string, dtsImportLine: string, - pluginOption: string + pluginOption: string, + testOption: string, + offsetFromRoot: string, + projectAlreadyHasViteTargets?: TargetFlags ) { + if (projectAlreadyHasViteTargets.build && projectAlreadyHasViteTargets.test) { + return; + } + logger.info(`vite.config.ts already exists for project ${options.project}.`); const buildOptionObject = { lib: { @@ -709,6 +752,16 @@ function handleViteConfigFileExists( ], }, }; + + const testOptionObject = { + globals: true, + cache: { + dir: `${offsetFromRoot}node_modules/.vitest`, + }, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + }; + const changed = ensureBuildOptionsInViteConfig( tree, viteConfigPath, @@ -716,7 +769,10 @@ function handleViteConfigFileExists( buildOptionObject, dtsPlugin, dtsImportLine, - pluginOption + pluginOption, + testOption, + testOptionObject, + projectAlreadyHasViteTargets ); if (!changed) { @@ -729,7 +785,7 @@ function handleViteConfigFileExists( ); } else { logger.info(` - Vite configuration file (${viteConfigPath}) has been updated with the required settings for build. + Vite configuration file (${viteConfigPath}) has been updated with the required settings for the new target(s). `); } } diff --git a/packages/vite/src/utils/test-files/test-vite-configs.ts b/packages/vite/src/utils/test-files/test-vite-configs.ts new file mode 100644 index 0000000000..b900420038 --- /dev/null +++ b/packages/vite/src/utils/test-files/test-vite-configs.ts @@ -0,0 +1,254 @@ +export const noBuildOptions = ` + import { defineConfig } from 'vite'; + import react from '@vitejs/plugin-react'; + import viteTsConfigPaths from 'vite-tsconfig-paths'; + + export default defineConfig({ + plugins: [ + react(), + viteTsConfigPaths({ + root: '../../', + }), + ], + + test: { + globals: true, + cache: { + dir: '../../node_modules/.vitest', + }, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + }, + + }); + `; + +export const someBuildOptions = ` + import { defineConfig } from 'vite'; + import react from '@vitejs/plugin-react'; + import viteTsConfigPaths from 'vite-tsconfig-paths'; + + export default defineConfig({ + plugins: [ + react(), + viteTsConfigPaths({ + root: '../../', + }), + ], + + test: { + globals: true, + cache: { + dir: '../../node_modules/.vitest', + }, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + }, + + build: { + my: 'option', + } + + }); + `; + +export const noContentDefineConfig = ` + import { defineConfig } from 'vite'; + import react from '@vitejs/plugin-react'; + import viteTsConfigPaths from 'vite-tsconfig-paths'; + + export default defineConfig({}); + `; + +export const conditionalConfig = ` + import { defineConfig } from 'vite'; + export default defineConfig(({ command, mode, ssrBuild }) => { + if (command === 'serve') { + return { + port: 4200, + host: 'localhost', + } + } else { + // command === 'build' + return { + my: 'option', + } + } + }) + `; + +export const configNoDefineConfig = ` + import { defineConfig } from 'vite'; + import react from '@vitejs/plugin-react'; + import viteTsConfigPaths from 'vite-tsconfig-paths'; + + export default { + plugins: [ + react(), + viteTsConfigPaths({ + root: '../../', + }), + ], + }; + `; + +export const noBuildOptionsHasTestOption = ` + import { defineConfig } from 'vite'; + import react from '@vitejs/plugin-react'; + import viteTsConfigPaths from 'vite-tsconfig-paths'; + + export default defineConfig({ + plugins: [ + react(), + viteTsConfigPaths({ + root: '../../', + }), + ], + + test: { + globals: true, + cache: { + dir: '../../node_modules/.vitest', + }, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + }, + + }); + `; + +export const someBuildOptionsSomeTestOption = ` + import { defineConfig } from 'vite'; + import react from '@vitejs/plugin-react'; + import viteTsConfigPaths from 'vite-tsconfig-paths'; + + export default defineConfig({ + plugins: [ + react(), + viteTsConfigPaths({ + root: '../../', + }), + ], + + test: { + my: 'option', + }, + + build: { + my: 'option', + } + + }); + `; + +export const hasEverything = ` + import { defineConfig } from 'vite'; + import react from '@vitejs/plugin-react'; + import viteTsConfigPaths from 'vite-tsconfig-paths'; + + export default defineConfig({ + plugins: [ + dts({ + tsConfigFilePath: join(__dirname, 'tsconfig.lib.json'), + // Faster builds by skipping tests. Set this to false to enable type checking. + skipDiagnostics: true, + }), + react(), + viteTsConfigPaths({ + root: '../../../', + }), + ], + + // Configuration for building your library. + // See: https://vitejs.dev/guide/build.html#library-mode + build: { + lib: { + // Could also be a dictionary or array of multiple entry points. + entry: 'src/index.ts', + name: 'pure-libs-react-vite', + fileName: 'index', + // Change this to the formats you want to support. + // Don't forgot to update your package.json as well. + formats: ['es', 'cjs'], + }, + rollupOptions: { + // External packages that should not be bundled into your library. + external: ['react', 'react-dom', 'react/jsx-runtime'], + }, + }, + + test: { + globals: true, + cache: { + dir: '../../../node_modules/.vitest', + }, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + }, + }); + `; + +export const buildOption = ` + // Configuration for building your library. + // See: https://vitejs.dev/guide/build.html#library-mode + build: { + lib: { + // Could also be a dictionary or array of multiple entry points. + entry: 'src/index.ts', + name: 'my-app', + fileName: 'index', + // Change this to the formats you want to support. + // Don't forgot to update your package.json as well. + formats: ['es', 'cjs'] + }, + rollupOptions: { + // External packages that should not be bundled into your library. + external: ["'react', 'react-dom', 'react/jsx-runtime'"] + } + },`; +export const buildOptionObject = { + lib: { + entry: 'src/index.ts', + name: 'my-app', + fileName: 'index', + formats: ['es', 'cjs'], + }, + rollupOptions: { + external: ["'react', 'react-dom', 'react/jsx-runtime'"], + }, +}; + +export const testOption = `test: { + globals: true, + cache: { + dir: '../node_modules/.vitest' + }, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + },`; + +export const testOptionObject = { + globals: true, + cache: { + dir: `../node_modules/.vitest`, + }, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], +}; + +export const dtsPlugin = `dts({ + tsConfigFilePath: join(__dirname, 'tsconfig.lib.json'), + // Faster builds by skipping tests. Set this to false to enable type checking. + skipDiagnostics: true, + }),`; +export const dtsImportLine = `import dts from 'vite-plugin-dts';\nimport { join } from 'path';`; + +export const pluginOption = ` + plugins: [ + ${dtsPlugin} + react(), + viteTsConfigPaths({ + root: '../../', + }), + ], + `; diff --git a/packages/vite/src/utils/test-utils.ts b/packages/vite/src/utils/test-utils.ts index ed26b92d83..9752bdd1bb 100644 --- a/packages/vite/src/utils/test-utils.ts +++ b/packages/vite/src/utils/test-utils.ts @@ -1,4 +1,4 @@ -import { parseJson, Tree, writeJson } from '@nrwl/devkit'; +import { Tree, writeJson } from '@nrwl/devkit'; import * as reactAppConfig from './test-files/react-project.config.json'; import * as reactViteConfig from './test-files/react-vite-project.config.json'; import * as webAppConfig from './test-files/web-project.config.json'; @@ -90,8 +90,7 @@ export function mockViteReactAppGenerator(tree: Tree): Tree { tree.write( `apps/${appName}/vite.config.ts`, - `/// - import { defineConfig } from 'vite'; + `import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import tsconfigPaths from 'vite-tsconfig-paths'; @@ -534,9 +533,7 @@ export function mockReactLibNonBuildableVitestRunnerGenerator( tree.write( `libs/${libName}/vite.config.ts`, - ` - /// - import { defineConfig } from 'vite'; + `import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import viteTsConfigPaths from 'vite-tsconfig-paths'; diff --git a/packages/vite/src/utils/vite-config-edit-utils.spec.ts b/packages/vite/src/utils/vite-config-edit-utils.spec.ts index c790283851..69aefaadc2 100644 --- a/packages/vite/src/utils/vite-config-edit-utils.spec.ts +++ b/packages/vite/src/utils/vite-config-edit-utils.spec.ts @@ -1,281 +1,221 @@ import { Tree } from '@nrwl/devkit'; import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing'; import { tsquery } from '@phenomnomnominal/tsquery'; - +import { + buildOption, + buildOptionObject, + conditionalConfig, + configNoDefineConfig, + dtsImportLine, + dtsPlugin, + hasEverything, + noBuildOptions, + noBuildOptionsHasTestOption, + noContentDefineConfig, + pluginOption, + someBuildOptions, + someBuildOptionsSomeTestOption, + testOption, + testOptionObject, +} from './test-files/test-vite-configs'; import { ensureBuildOptionsInViteConfig } from './vite-config-edit-utils'; -describe('generator utils', () => { +describe('ensureBuildOptionsInViteConfig', () => { let tree: Tree; beforeEach(() => { tree = createTreeWithEmptyV1Workspace(); }); - describe('ensureBuildOptionsInViteConfig', () => { - let tree: Tree; + it("should add build options if build options don't exist", () => { + tree.write('apps/my-app/vite.config.ts', noBuildOptions); + ensureBuildOptionsInViteConfig( + tree, + 'apps/my-app/vite.config.ts', + buildOption, + buildOptionObject, + dtsPlugin, + dtsImportLine, + pluginOption, + testOption, + testOptionObject, + { build: false, test: true, serve: false } + ); + const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8'); + const file = tsquery.ast(appFileContent); + const buildNode = tsquery.query( + file, + 'PropertyAssignment:has(Identifier[name="build"])' + ); + expect(buildNode).toBeDefined(); + expect(appFileContent).toMatchSnapshot(); + }); - const buildOption = ` - // Configuration for building your library. - // See: https://vitejs.dev/guide/build.html#library-mode - build: { - lib: { - // Could also be a dictionary or array of multiple entry points. - entry: 'src/index.ts', - name: 'my-app', - fileName: 'index', - // Change this to the formats you want to support. - // Don't forgot to update your package.json as well. - formats: ['es', 'cjs'] - }, - rollupOptions: { - // External packages that should not be bundled into your library. - external: ["'react', 'react-dom', 'react/jsx-runtime'"] - } - },`; - const buildOptionObject = { - lib: { - entry: 'src/index.ts', - name: 'my-app', - fileName: 'index', - formats: ['es', 'cjs'], - }, - rollupOptions: { - external: ["'react', 'react-dom', 'react/jsx-runtime'"], - }, - }; - const dtsPlugin = `dts({ - tsConfigFilePath: join(__dirname, 'tsconfig.lib.json'), - // Faster builds by skipping tests. Set this to false to enable type checking. - skipDiagnostics: true, - }),`; - const dtsImportLine = `import dts from 'vite-plugin-dts';\nimport { join } from 'path';`; + it('should add new build options if some build options already exist', () => { + tree.write('apps/my-app/vite.config.ts', someBuildOptions); + ensureBuildOptionsInViteConfig( + tree, + 'apps/my-app/vite.config.ts', + buildOption, + buildOptionObject, + dtsPlugin, + dtsImportLine, + pluginOption, + testOption, + testOptionObject, + { build: false, test: true, serve: false } + ); + const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8'); + const file = tsquery.ast(appFileContent); + const buildNode = tsquery.query( + file, + 'PropertyAssignment:has(Identifier[name="build"])' + ); + expect(buildNode).toBeDefined(); + expect(appFileContent).toMatchSnapshot(); + }); - const pluginOption = ` - plugins: [ - ${dtsPlugin} - react(), - viteTsConfigPaths({ - root: '../../', - }), - ], - `; + it('should add build and test options if defineConfig is empty', () => { + tree.write('apps/my-app/vite.config.ts', noContentDefineConfig); + ensureBuildOptionsInViteConfig( + tree, + 'apps/my-app/vite.config.ts', + buildOption, + buildOptionObject, + dtsPlugin, + dtsImportLine, + pluginOption, + testOption, + testOptionObject, + { build: false, test: false, serve: false } + ); + const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8'); + const file = tsquery.ast(appFileContent); + const buildNode = tsquery.query( + file, + 'PropertyAssignment:has(Identifier[name="build"])' + ); + expect(buildNode).toBeDefined(); + expect(appFileContent).toMatchSnapshot(); + }); - const noBuildOptions = ` - import { defineConfig } from 'vite'; - import react from '@vitejs/plugin-react'; - import viteTsConfigPaths from 'vite-tsconfig-paths'; + it('should add build options if it is using conditional config - do nothing for test', () => { + tree.write('apps/my-app/vite.config.ts', conditionalConfig); + ensureBuildOptionsInViteConfig( + tree, + 'apps/my-app/vite.config.ts', + buildOption, + buildOptionObject, + dtsPlugin, + dtsImportLine, + pluginOption, + testOption, + testOptionObject, + { build: false, test: false, serve: false } + ); + const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8'); + const file = tsquery.ast(appFileContent); + const buildNode = tsquery.query( + file, + 'PropertyAssignment:has(Identifier[name="build"])' + ); + expect(buildNode).toBeDefined(); + expect(appFileContent).toMatchSnapshot(); + }); - export default defineConfig({ - plugins: [ - react(), - viteTsConfigPaths({ - root: '../../', - }), - ], + it('should add build options if defineConfig is not used', () => { + tree.write('apps/my-app/vite.config.ts', configNoDefineConfig); + ensureBuildOptionsInViteConfig( + tree, + 'apps/my-app/vite.config.ts', + buildOption, + buildOptionObject, + dtsPlugin, + dtsImportLine, + pluginOption, + testOption, + testOptionObject, + { build: false, test: false, serve: false } + ); + const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8'); + const file = tsquery.ast(appFileContent); + const buildNode = tsquery.query( + file, + 'PropertyAssignment:has(Identifier[name="build"])' + ); + expect(buildNode).toBeDefined(); + expect(appFileContent).toMatchSnapshot(); + }); - test: { - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - environment: 'jsdom', - include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - }, + it('should not do anything if cannot understand syntax of vite config', () => { + tree.write('apps/my-app/vite.config.ts', `console.log('Unknown syntax')`); + ensureBuildOptionsInViteConfig( + tree, + 'apps/my-app/vite.config.ts', + buildOption, + buildOptionObject, + dtsPlugin, + dtsImportLine, + pluginOption, + testOption, + testOptionObject, + { build: false, test: false, serve: false } + ); + const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8'); + expect(appFileContent).toMatchSnapshot(); + }); - }); - `; + it('should not do anything if project has everything setup already', () => { + tree.write('apps/my-app/vite.config.ts', hasEverything); + ensureBuildOptionsInViteConfig( + tree, + 'apps/my-app/vite.config.ts', + buildOption, + buildOptionObject, + dtsPlugin, + dtsImportLine, + pluginOption, + testOption, + testOptionObject, + { build: true, test: true, serve: true } + ); + const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8'); + expect(appFileContent).toMatchSnapshot(); + }); - const someBuildOptions = ` - import { defineConfig } from 'vite'; - import react from '@vitejs/plugin-react'; - import viteTsConfigPaths from 'vite-tsconfig-paths'; + it('should add build option but not update test option if test already setup', () => { + tree.write('apps/my-app/vite.config.ts', noBuildOptionsHasTestOption); + ensureBuildOptionsInViteConfig( + tree, + 'apps/my-app/vite.config.ts', + buildOption, + buildOptionObject, + dtsPlugin, + dtsImportLine, + pluginOption, + testOption, + testOptionObject, + { build: false, test: true, serve: true } + ); + const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8'); + expect(appFileContent).toMatchSnapshot(); + }); - export default defineConfig({ - plugins: [ - react(), - viteTsConfigPaths({ - root: '../../', - }), - ], - - test: { - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - environment: 'jsdom', - include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - }, - - build: { - my: 'option', - } - - }); - `; - - const noContentDefineConfig = ` - import { defineConfig } from 'vite'; - import react from '@vitejs/plugin-react'; - import viteTsConfigPaths from 'vite-tsconfig-paths'; - - export default defineConfig({}); - `; - - const conditionalConfig = ` - import { defineConfig } from 'vite'; - export default defineConfig(({ command, mode, ssrBuild }) => { - if (command === 'serve') { - return { - port: 4200, - host: 'localhost', - } - } else { - // command === 'build' - return { - my: 'option', - } - } - }) - `; - - const configNoDefineConfig = ` - import { defineConfig } from 'vite'; - import react from '@vitejs/plugin-react'; - import viteTsConfigPaths from 'vite-tsconfig-paths'; - - export default { - plugins: [ - react(), - viteTsConfigPaths({ - root: '../../', - }), - ], - }; - `; - - beforeEach(() => { - tree = createTreeWithEmptyV1Workspace(); - }); - - it("should add build options if build options don't exist", () => { - tree.write('apps/my-app/vite.config.ts', noBuildOptions); - ensureBuildOptionsInViteConfig( - tree, - 'apps/my-app/vite.config.ts', - buildOption, - buildOptionObject, - dtsPlugin, - dtsImportLine, - pluginOption - ); - const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8'); - const file = tsquery.ast(appFileContent); - const buildNode = tsquery.query( - file, - 'PropertyAssignment:has(Identifier[name="build"])' - ); - expect(buildNode).toBeDefined(); - expect(appFileContent).toMatchSnapshot(); - }); - - it('should add new build options if some build options already exist', () => { - tree.write('apps/my-app/vite.config.ts', someBuildOptions); - ensureBuildOptionsInViteConfig( - tree, - 'apps/my-app/vite.config.ts', - buildOption, - buildOptionObject, - dtsPlugin, - dtsImportLine, - pluginOption - ); - const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8'); - const file = tsquery.ast(appFileContent); - const buildNode = tsquery.query( - file, - 'PropertyAssignment:has(Identifier[name="build"])' - ); - expect(buildNode).toBeDefined(); - expect(appFileContent).toMatchSnapshot(); - }); - - it('should add build options if defineConfig is empty', () => { - tree.write('apps/my-app/vite.config.ts', noContentDefineConfig); - ensureBuildOptionsInViteConfig( - tree, - 'apps/my-app/vite.config.ts', - buildOption, - buildOptionObject, - dtsPlugin, - dtsImportLine, - pluginOption - ); - const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8'); - const file = tsquery.ast(appFileContent); - const buildNode = tsquery.query( - file, - 'PropertyAssignment:has(Identifier[name="build"])' - ); - expect(buildNode).toBeDefined(); - expect(appFileContent).toMatchSnapshot(); - }); - - it('should add build options if it is using conditional config', () => { - tree.write('apps/my-app/vite.config.ts', conditionalConfig); - ensureBuildOptionsInViteConfig( - tree, - 'apps/my-app/vite.config.ts', - buildOption, - buildOptionObject, - dtsPlugin, - dtsImportLine, - pluginOption - ); - const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8'); - const file = tsquery.ast(appFileContent); - const buildNode = tsquery.query( - file, - 'PropertyAssignment:has(Identifier[name="build"])' - ); - expect(buildNode).toBeDefined(); - expect(appFileContent).toMatchSnapshot(); - }); - - it('should add build options if defineConfig is not used', () => { - tree.write('apps/my-app/vite.config.ts', configNoDefineConfig); - ensureBuildOptionsInViteConfig( - tree, - 'apps/my-app/vite.config.ts', - buildOption, - buildOptionObject, - dtsPlugin, - dtsImportLine, - pluginOption - ); - const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8'); - const file = tsquery.ast(appFileContent); - const buildNode = tsquery.query( - file, - 'PropertyAssignment:has(Identifier[name="build"])' - ); - expect(buildNode).toBeDefined(); - expect(appFileContent).toMatchSnapshot(); - }); - - it('should not do anything if cannot understand syntax of vite config', () => { - tree.write('apps/my-app/vite.config.ts', `console.log('Unknown syntax')`); - ensureBuildOptionsInViteConfig( - tree, - 'apps/my-app/vite.config.ts', - buildOption, - buildOptionObject, - dtsPlugin, - dtsImportLine, - pluginOption - ); - const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8'); - expect(appFileContent).toMatchSnapshot(); - }); + it('should update both test and build options - keep existing settings', () => { + tree.write('apps/my-app/vite.config.ts', someBuildOptionsSomeTestOption); + ensureBuildOptionsInViteConfig( + tree, + 'apps/my-app/vite.config.ts', + buildOption, + buildOptionObject, + dtsPlugin, + dtsImportLine, + pluginOption, + testOption, + testOptionObject, + { build: false, test: false, serve: true } + ); + const appFileContent = tree.read('apps/my-app/vite.config.ts', 'utf-8'); + expect(appFileContent).toMatchSnapshot(); }); }); diff --git a/packages/vite/src/utils/vite-config-edit-utils.ts b/packages/vite/src/utils/vite-config-edit-utils.ts index c65a8f888d..de446fe71f 100644 --- a/packages/vite/src/utils/vite-config-edit-utils.ts +++ b/packages/vite/src/utils/vite-config-edit-utils.ts @@ -2,59 +2,77 @@ import { applyChangesToString, ChangeType, Tree } from '@nrwl/devkit'; import { findNodes } from 'nx/src/utils/typescript'; import ts = require('typescript'); import { tsquery } from '@phenomnomnominal/tsquery'; +import { TargetFlags } from './generator-utils'; export function ensureBuildOptionsInViteConfig( tree: Tree, path: string, - buildConfigContent: string, + buildConfigString: string, buildConfigObject: {}, dtsPlugin: string, dtsImportLine: string, - pluginOption: string + pluginOption: string, + testConfigString: string, + testConfigObject: {}, + projectAlreadyHasViteTargets?: TargetFlags ): boolean { const fileContent = tree.read(path, 'utf-8'); - const file = tsquery.ast(fileContent); - const newContent = handlePluginNode( - file, - fileContent, - dtsPlugin, - dtsImportLine, - pluginOption - ); - const buildUpdatedContent = handleBuildNode( - newContent ?? fileContent, - buildConfigContent, - buildConfigObject - ); + let updatedContent = undefined; - if (buildUpdatedContent) { - tree.write(path, buildUpdatedContent); + if (!projectAlreadyHasViteTargets?.test && testConfigString?.length) { + updatedContent = handleBuildOrTestNode( + fileContent, + testConfigString, + testConfigObject, + 'test' + ); + } + + if (!projectAlreadyHasViteTargets?.build && buildConfigString?.length) { + updatedContent = handlePluginNode( + updatedContent ?? fileContent, + dtsPlugin, + dtsImportLine, + pluginOption + ); + + updatedContent = handleBuildOrTestNode( + updatedContent ?? fileContent, + buildConfigString, + buildConfigObject, + 'build' + ); + } + + if (updatedContent) { + tree.write(path, updatedContent); return true; } else { return false; } } -function handleBuildNode( +function handleBuildOrTestNode( updatedFileContent: string, - buildConfigContent: string, - buildConfigObject: {} + configContentString: string, + configContentObject: {}, + name: 'build' | 'test' ): string | undefined { const buildNode = tsquery.query( updatedFileContent, - 'PropertyAssignment:has(Identifier[name="build"])' + `PropertyAssignment:has(Identifier[name="${name}"])` ); if (buildNode.length) { return tsquery.replace( updatedFileContent, - 'PropertyAssignment:has(Identifier[name="build"])', + `PropertyAssignment:has(Identifier[name="${name}"])`, (node: ts.Node) => { const found = tsquery.query(node, 'ObjectLiteralExpression'); - return `build: { + return `${name}: { ...${found?.[0].getText()}, - ...${JSON.stringify(buildConfigObject)} + ...${JSON.stringify(configContentObject)} }`; } ); @@ -71,11 +89,16 @@ function handleBuildNode( ); if (conditionalConfig.length) { - return transformConditionalConfig( - conditionalConfig, - updatedFileContent, - buildConfigContent - ); + if (name === 'build') { + return transformConditionalConfig( + conditionalConfig, + updatedFileContent, + configContentString + ); + } else { + // no test config in conditional config + return undefined; + } } else { const propertyAssignments = tsquery.query( foundDefineConfig[0], @@ -87,7 +110,7 @@ function handleBuildNode( { type: ChangeType.Insert, index: propertyAssignments[0].getStart(), - text: buildConfigContent, + text: configContentString, }, ]); } else { @@ -95,7 +118,7 @@ function handleBuildNode( { type: ChangeType.Insert, index: foundDefineConfig[0].getStart() + 14, - text: buildConfigContent, + text: configContentString, }, ]); } @@ -117,7 +140,7 @@ function handleBuildNode( { type: ChangeType.Insert, index: startOfObject + 1, - text: buildConfigContent, + text: configContentString, }, ]); } catch { @@ -131,7 +154,6 @@ function transformCurrentBuildObject( index: number, returnStatements: ts.ReturnStatement[], appFileContent: string, - buildConfigObject: {} ): string | undefined { if (!returnStatements?.[index]) { @@ -244,12 +266,12 @@ function transformConditionalConfig( } function handlePluginNode( - file: ts.SourceFile, appFileContent: string, dtsPlugin: string, dtsImportLine: string, pluginOption: string ): string | undefined { + const file = tsquery.ast(appFileContent); const pluginsNode = tsquery.query( file, 'PropertyAssignment:has(Identifier[name="plugins"])' @@ -341,15 +363,7 @@ function handlePluginNode( if (writeFile) { if (!appFileContent.includes(`import dts from 'vite-plugin-dts'`)) { - if (appFileContent.includes('/// ')) { - return appFileContent.replace( - '/// ', - `/// - ${dtsImportLine}\n` - ); - } else { - return dtsImportLine + '\n' + appFileContent; - } + return dtsImportLine + '\n' + appFileContent; } return appFileContent; } diff --git a/packages/web/src/generators/application/application.spec.ts b/packages/web/src/generators/application/application.spec.ts index 00fab792bb..9589fd2988 100644 --- a/packages/web/src/generators/application/application.spec.ts +++ b/packages/web/src/generators/application/application.spec.ts @@ -160,7 +160,10 @@ describe('app', () => { path: './tsconfig.spec.json', }, ]); - expect(tsconfig.compilerOptions.types).toMatchObject(['vite/client']); + expect(tsconfig.compilerOptions.types).toMatchObject([ + 'vite/client', + 'vitest', + ]); expect(tree.exists('apps/my-app-e2e/cypress.config.ts')).toBeTruthy(); expect(tree.exists('apps/my-app/index.html')).toBeTruthy(); @@ -563,7 +566,10 @@ describe('app', () => { it('should create correct tsconfig compilerOptions', () => { const tsconfigJson = readJson(viteAppTree, '/apps/my-app/tsconfig.json'); - expect(tsconfigJson.compilerOptions.types).toMatchObject(['vite/client']); + expect(tsconfigJson.compilerOptions.types).toMatchObject([ + 'vite/client', + 'vitest', + ]); }); it('should create index.html and vite.config file at the root of the app', () => {