import { joinPathFragments, logger, offsetFromRoot, readJson, readProjectConfiguration, TargetConfiguration, Tree, updateProjectConfiguration, writeJson, } from '@nrwl/devkit'; import { ViteBuildExecutorOptions } from '../executors/build/schema'; import { ViteDevServerExecutorOptions } from '../executors/dev-server/schema'; import { VitestExecutorOptions } from '../executors/test/schema'; import { Schema } from '../generators/configuration/schema'; import { ensureBuildOptionsInViteConfig } from './vite-config-edit-utils'; export interface TargetFlags { build?: boolean; serve?: boolean; test?: boolean; } export interface UserProvidedTargetName { build?: string; serve?: string; test?: string; } export interface ValidFoundTargetName { build?: string; serve?: string; test?: string; } export function findExistingTargetsInProject( targets: { [targetName: string]: TargetConfiguration; }, userProvidedTargets?: UserProvidedTargetName ): { validFoundTargetName: ValidFoundTargetName; projectContainsUnsupportedExecutor?: boolean; userProvidedTargetIsUnsupported?: TargetFlags; alreadyHasNxViteTargets?: TargetFlags; } { let validFoundBuildTarget: string | undefined, validFoundServeTarget: string | undefined, validFoundTestTarget: string | undefined, projectContainsUnsupportedExecutor: boolean | undefined, unsupportedUserProvidedTargetBuild: boolean | undefined, unsupportedUserProvidedTargetServe: boolean | undefined, unsupportedUserProvidedTargetTest: boolean | undefined, alreadyHasNxViteTargetBuild: boolean | undefined, alreadyHasNxViteTargetServe: boolean | undefined, alreadyHasNxViteTargetTest: boolean | undefined; const arrayOfSupportedBuilders = [ '@nxext/vite:build', '@nrwl/js:babel', '@nrwl/js:swc', '@nrwl/webpack:webpack', '@nrwl/rollup:rollup', '@nrwl/web:rollup', ]; const arrayOfSupportedServers = [ '@nxext/vite:dev', '@nrwl/webpack:dev-server', ]; const arrayOfSupportedTesters = ['@nrwl/jest:jest', '@nxext/vitest:vitest']; const arrayofUnsupportedExecutors = [ '@nrwl/angular:ng-packagr-lite', '@nrwl/angular:package', '@nrwl/angular:webpack-browser', '@angular-devkit/build-angular:browser', '@angular-devkit/build-angular:dev-server', '@nrwl/esbuild:esbuild', '@nrwl/react-native:run-ios', '@nrwl/react-native:start', '@nrwl/react-native:run-android', '@nrwl/react-native:bundle', '@nrwl/react-native:build-android', '@nrwl/react-native:bundle', '@nrwl/next:build', '@nrwl/next:server', '@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 if (userProvidedTargets?.build) { if ( arrayOfSupportedBuilders.includes( targets[userProvidedTargets.build]?.executor ) ) { validFoundBuildTarget = userProvidedTargets.build; } else { unsupportedUserProvidedTargetBuild = true; } } if (userProvidedTargets?.serve) { if ( arrayOfSupportedServers.includes( targets[userProvidedTargets.serve]?.executor ) ) { validFoundServeTarget = userProvidedTargets.serve; } else { unsupportedUserProvidedTargetServe = true; } } if (userProvidedTargets?.test) { if ( arrayOfSupportedServers.includes( targets[userProvidedTargets.test]?.executor ) ) { validFoundTestTarget = userProvidedTargets.test; } else { unsupportedUserProvidedTargetTest = true; } } // Then, we try to find the targets that are using the supported executors // for build, serve and test, since these are the ones we will be converting for (const target in targets) { // If we have a value for each one of the targets, we can break out of the loop if ( validFoundBuildTarget && validFoundServeTarget && validFoundTestTarget ) { 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) ) { validFoundBuildTarget = target; } if ( !validFoundServeTarget && arrayOfSupportedServers.includes(targets[target].executor) ) { validFoundServeTarget = target; } if ( !validFoundTestTarget && arrayOfSupportedTesters.includes(targets[target].executor) ) { validFoundTestTarget = target; } if ( !arrayOfNxViteExecutors.includes(targets[target].executor) && arrayofUnsupportedExecutors.includes(targets[target].executor) ) { projectContainsUnsupportedExecutor = true; } } return { validFoundTargetName: { build: validFoundBuildTarget, serve: validFoundServeTarget, test: validFoundTestTarget, }, projectContainsUnsupportedExecutor, userProvidedTargetIsUnsupported: { build: unsupportedUserProvidedTargetBuild, serve: unsupportedUserProvidedTargetServe, test: unsupportedUserProvidedTargetTest, }, alreadyHasNxViteTargets: { build: alreadyHasNxViteTargetBuild, serve: alreadyHasNxViteTargetServe, test: alreadyHasNxViteTargetTest, }, }; } export function addOrChangeTestTarget( tree: Tree, options: Schema, target: string ) { const project = readProjectConfiguration(tree, options.project); const coveragePath = joinPathFragments( '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 ), }; if (project.targets?.[target]) { project.targets[target].executor = '@nrwl/vite:test'; delete project.targets[target].options?.jestConfig; } else { if (!project.targets) { project.targets = {}; } project.targets[target] = { executor: '@nrwl/vite:test', outputs: [coveragePath], options: testOptions, }; } updateProjectConfiguration(tree, options.project, project); } export function addOrChangeBuildTarget( tree: Tree, options: Schema, target: string ) { const project = readProjectConfiguration(tree, options.project); const buildOptions: ViteBuildExecutorOptions = { outputPath: joinPathFragments( 'dist', project.root != '.' ? project.root : options.project ), }; if (project.targets?.[target]) { buildOptions.fileReplacements = 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; } project.targets[target].options = { ...buildOptions, }; project.targets[target].executor = '@nrwl/vite:build'; } else { if (!project.targets) { project.targets = {}; } project.targets[`${target}`] = { executor: '@nrwl/vite:build', outputs: ['{options.outputPath}'], defaultConfiguration: 'production', options: buildOptions, configurations: { development: { mode: 'development', }, production: { mode: 'production', }, }, }; } updateProjectConfiguration(tree, options.project, project); } export function addOrChangeServeTarget( tree: Tree, options: Schema, target: string ) { const project = readProjectConfiguration(tree, options.project); const serveOptions: ViteDevServerExecutorOptions = { buildTarget: `${options.project}:build`, }; if (project.targets?.[target]) { if (target === '@nxext/vite:dev') { serveOptions.proxyConfig = project.targets[target].options.proxyConfig; } 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 { if (!project.targets) { project.targets = {}; } project.targets[`${target}`] = { executor: '@nrwl/vite:dev-server', defaultConfiguration: 'development', options: { buildTarget: `${options.project}:build`, }, configurations: { development: { buildTarget: `${options.project}:build:development`, hmr: true, }, production: { buildTarget: `${options.project}:build:production`, hmr: false, }, }, }; } updateProjectConfiguration(tree, options.project, project); } export function editTsConfig(tree: Tree, options: Schema) { const projectConfig = readProjectConfiguration(tree, options.project); const config = readJson(tree, `${projectConfig.root}/tsconfig.json`); switch (options.uiFramework) { case 'react': config.compilerOptions = { target: 'ESNext', useDefineForClassFields: true, module: 'ESNext', lib: ['DOM', 'DOM.Iterable', 'ESNext'], allowJs: false, skipLibCheck: true, esModuleInterop: false, allowSyntheticDefaultImports: true, strict: true, forceConsistentCasingInFileNames: true, moduleResolution: 'Node', resolveJsonModule: true, isolatedModules: true, noEmit: true, jsx: 'react-jsx', types: options.includeVitest ? ['vite/client', 'vitest'] : ['vite/client'], }; config.include = [...config.include, 'src']; break; case 'none': config.compilerOptions = { target: 'ESNext', useDefineForClassFields: true, module: 'ESNext', lib: ['ESNext', 'DOM'], skipLibCheck: true, esModuleInterop: true, strict: true, moduleResolution: 'Node', resolveJsonModule: true, isolatedModules: true, noEmit: true, noUnusedLocals: true, noUnusedParameters: true, noImplicitReturns: true, types: options.includeVitest ? ['vite/client', 'vitest'] : ['vite/client'], }; config.include = [...config.include, 'src']; break; default: break; } writeJson(tree, `${projectConfig.root}/tsconfig.json`, config); } export function moveAndEditIndexHtml( tree: Tree, options: Schema, buildTarget: string ) { const projectConfig = readProjectConfiguration(tree, options.project); let indexHtmlPath = projectConfig.targets[buildTarget].options?.index ?? `${projectConfig.root}/src/index.html`; const mainPath = ( projectConfig.targets[buildTarget].options?.main ?? `${projectConfig.root}/src/main.ts${ options.uiFramework === 'react' ? 'x' : '' }` ).replace(projectConfig.root, ''); if ( !tree.exists(indexHtmlPath) && tree.exists(`${projectConfig.root}/index.html`) ) { indexHtmlPath = `${projectConfig.root}/index.html`; } if (tree.exists(indexHtmlPath)) { const indexHtmlContent = tree.read(indexHtmlPath, 'utf8'); if ( !indexHtmlContent.includes( `` ) ) { tree.write( `${projectConfig.root}/index.html`, indexHtmlContent.replace( '
', ` ` ) ); if (tree.exists(`${projectConfig.root}/src/index.html`)) tree.delete(`${projectConfig.root}/src/index.html`); } } else { tree.write( `${projectConfig.root}/index.html`, `