import {
joinPathFragments,
logger,
offsetFromRoot,
readJson,
readNxJson,
readProjectConfiguration,
TargetConfiguration,
Tree,
updateProjectConfiguration,
writeJson,
} from '@nx/devkit';
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
import { ViteBuildExecutorOptions } from '../executors/build/schema';
import { VitePreviewServerExecutorOptions } from '../executors/preview-server/schema';
import { VitestExecutorOptions } from '../executors/test/schema';
import { ViteConfigurationGeneratorSchema } from '../generators/configuration/schema';
import { ensureViteConfigIsCorrect } from './vite-config-edit-utils';
import { VitestGeneratorSchema } from '../generators/vitest/schema';
export type Target = 'build' | 'serve' | 'test' | 'preview';
export type TargetFlags = Partial>;
export type UserProvidedTargetName = Partial>;
export type ValidFoundTargetName = Partial>;
export function findExistingJsBuildTargetInProject(targets: {
[targetName: string]: TargetConfiguration;
}): {
supported?: string;
unsupported?: string;
} {
const output: {
supported?: string;
unsupported?: string;
} = {};
const supportedExecutors = {
build: ['@nx/js:babel', '@nx/js:swc', '@nx/rollup:rollup'],
};
const unsupportedExecutors = [
'@nx/angular:ng-packagr-lite',
'@nx/angular:package',
'@nx/angular:webpack-browser',
'@nx/esbuild:esbuild',
'@nx/react-native:run-ios',
'@nx/react-native:start',
'@nx/react-native:run-android',
'@nx/react-native:bundle',
'@nx/react-native:build-android',
'@nx/react-native:bundle',
'@nx/next:build',
'@nx/js:tsc',
'@nrwl/angular:ng-packagr-lite',
'@nrwl/angular:package',
'@nrwl/angular:webpack-browser',
'@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/js:tsc',
'@angular-devkit/build-angular:browser',
'@angular-devkit/build-angular:browser-esbuild',
'@angular-devkit/build-angular:application',
];
// We try to find the target that is using the supported executors
// for build since this is the one we will be converting
for (const target in targets) {
const executorName = targets[target].executor;
if (supportedExecutors.build.includes(executorName)) {
output.supported = target;
} else if (unsupportedExecutors.includes(executorName)) {
output.unsupported = target;
}
}
return output;
}
export function addOrChangeTestTarget(
tree: Tree,
options: VitestGeneratorSchema,
hasPlugin: boolean
) {
const nxJson = readNxJson(tree);
hasPlugin = nxJson.plugins?.some((p) =>
typeof p === 'string'
? p === '@nx/vite/plugin'
: p.plugin === '@nx/vite/plugin' || hasPlugin
);
if (hasPlugin) {
return;
}
const project = readProjectConfiguration(tree, options.project);
const target = options.testTarget ?? 'test';
const reportsDirectory = joinPathFragments(
offsetFromRoot(project.root),
'coverage',
project.root === '.' ? options.project : project.root
);
const testOptions: VitestExecutorOptions = {
reportsDirectory,
};
project.targets ??= {};
if (project.targets[target]) {
throw new Error(`Target "${target}" already exists in the project.`);
} else {
project.targets[target] = {
executor: '@nx/vite:test',
outputs: ['{options.reportsDirectory}'],
options: testOptions,
};
}
updateProjectConfiguration(tree, options.project, project);
}
export function addBuildTarget(
tree: Tree,
options: ViteConfigurationGeneratorSchema,
target: string
) {
addBuildTargetDefaults(tree, '@nx/vite:build');
const project = readProjectConfiguration(tree, options.project);
const isTsSolutionSetup = isUsingTsSolutionSetup(tree);
const buildOptions: ViteBuildExecutorOptions = {
outputPath: isTsSolutionSetup
? joinPathFragments(project.root, 'dist')
: joinPathFragments(
'dist',
project.root != '.' ? project.root : options.project
),
};
project.targets ??= {};
project.targets[target] = {
executor: '@nx/vite:build',
outputs: ['{options.outputPath}'],
defaultConfiguration: 'production',
options: buildOptions,
configurations: {
development: {
mode: 'development',
},
production: {
mode: 'production',
},
},
};
updateProjectConfiguration(tree, options.project, project);
}
export function addServeTarget(
tree: Tree,
options: ViteConfigurationGeneratorSchema,
target: string
) {
const project = readProjectConfiguration(tree, options.project);
project.targets ??= {};
project.targets[target] = {
executor: '@nx/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);
}
/**
* Adds a target for the preview server.
*
* @param tree
* @param options
* @param serveTarget An existing serve target.
*/
export function addPreviewTarget(
tree: Tree,
options: ViteConfigurationGeneratorSchema,
serveTarget: string
) {
const project = readProjectConfiguration(tree, options.project);
const previewOptions: VitePreviewServerExecutorOptions = {
buildTarget: `${options.project}:build`,
};
project.targets ??= {};
// Update the options from the passed serve target.
if (project.targets[serveTarget]) {
const target = project.targets[serveTarget];
if (target.executor === '@nxext/vite:dev') {
previewOptions.proxyConfig = target.options.proxyConfig;
}
previewOptions['https'] = target.options?.https;
previewOptions['open'] = target.options?.open;
}
// Adds a preview target.
project.targets.preview = {
dependsOn: ['build'],
executor: '@nx/vite:preview-server',
defaultConfiguration: 'development',
options: previewOptions,
configurations: {
development: {
buildTarget: `${options.project}:build:development`,
},
production: {
buildTarget: `${options.project}:build:production`,
},
},
};
updateProjectConfiguration(tree, options.project, project);
}
export function editTsConfig(
tree: Tree,
options: ViteConfigurationGeneratorSchema
) {
const projectConfig = readProjectConfiguration(tree, options.project);
let tsconfigPath = joinPathFragments(projectConfig.root, 'tsconfig.json');
const isTsSolutionSetup = isUsingTsSolutionSetup(tree);
if (isTsSolutionSetup) {
tsconfigPath = [
joinPathFragments(projectConfig.root, 'tsconfig.app.json'),
joinPathFragments(projectConfig.root, 'tsconfig.lib.json'),
].find((p) => tree.exists(p));
}
const config = readJson(tree, tsconfigPath);
switch (options.uiFramework) {
case 'react':
config.compilerOptions = {
jsx: 'react-jsx',
allowJs: false,
esModuleInterop: false,
allowSyntheticDefaultImports: true,
strict: true,
};
break;
case 'none':
if (!isTsSolutionSetup) {
config.compilerOptions = {
module: 'commonjs',
forceConsistentCasingInFileNames: true,
strict: true,
noImplicitOverride: true,
noPropertyAccessFromIndexSignature: true,
noImplicitReturns: true,
noFallthroughCasesInSwitch: true,
};
}
break;
default:
break;
}
writeJson(tree, tsconfigPath, config);
}
export function deleteWebpackConfig(
tree: Tree,
projectRoot: string,
webpackConfigFilePath?: string
) {
const webpackConfigPath =
webpackConfigFilePath && tree.exists(webpackConfigFilePath)
? webpackConfigFilePath
: tree.exists(`${projectRoot}/webpack.config.js`)
? `${projectRoot}/webpack.config.js`
: tree.exists(`${projectRoot}/webpack.config.ts`)
? `${projectRoot}/webpack.config.ts`
: null;
if (webpackConfigPath) {
tree.delete(webpackConfigPath);
}
}
export function moveAndEditIndexHtml(
tree: Tree,
options: ViteConfigurationGeneratorSchema
) {
const projectConfig = readProjectConfiguration(tree, options.project);
let indexHtmlPath = `${projectConfig.root}/src/index.html`;
let mainPath = `${projectConfig.root}/src/main.ts${
options.uiFramework === 'react' ? 'x' : ''
}`;
if (projectConfig.root !== '.') {
mainPath = mainPath.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`,
`