@nx/angular
Version:
252 lines (251 loc) • 12 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.convertToApplicationExecutor = convertToApplicationExecutor;
const devkit_1 = require("@nx/devkit");
const posix_1 = require("node:path/posix");
const targets_1 = require("../../utils/targets");
const setup_ssr_1 = require("../setup-ssr/setup-ssr");
const validations_1 = require("../utils/validations");
const version_utils_1 = require("../utils/version-utils");
const executorsToConvert = new Set([
'@angular-devkit/build-angular:browser',
'@angular-devkit/build-angular:browser-esbuild',
'@nx/angular:webpack-browser',
'@nx/angular:browser-esbuild',
]);
const serverTargetExecutors = new Set([
'@angular-devkit/build-angular:server',
'@nx/angular:webpack-server',
]);
const redundantExecutors = new Set([
'@angular-devkit/build-angular:server',
'@angular-devkit/build-angular:prerender',
'@angular-devkit/build-angular:app-shell',
'@angular-devkit/build-angular:ssr-dev-server',
'@nx/angular:webpack-server',
]);
async function convertToApplicationExecutor(tree, options) {
let didAnySucceed = false;
if (options.project) {
(0, validations_1.validateProject)(tree, options.project);
didAnySucceed = await convertProjectTargets(tree, options.project, true);
}
else {
const projects = (0, devkit_1.getProjects)(tree);
for (const [projectName] of projects) {
devkit_1.logger.info(`Converting project "${projectName}"...`);
const success = await convertProjectTargets(tree, projectName);
if (success) {
devkit_1.logger.info(`Project "${projectName}" converted successfully.`);
}
else {
devkit_1.logger.info(`Project "${projectName}" could not be converted. See above for more information.`);
}
devkit_1.logger.info('');
didAnySucceed = didAnySucceed || success;
}
}
if (!options.skipFormat) {
await (0, devkit_1.formatFiles)(tree);
}
return didAnySucceed ? () => (0, devkit_1.installPackagesTask)(tree) : () => { };
}
async function convertProjectTargets(tree, projectName, isProvidedProject = false) {
function warnIfProvided(message) {
if (isProvidedProject) {
devkit_1.logger.warn(message);
}
}
let project = (0, devkit_1.readProjectConfiguration)(tree, projectName);
if (project.projectType !== 'application') {
warnIfProvided(`The provided project "${projectName}" is not an application. Skipping conversion.`);
return false;
}
const { buildTargetName, serverTargetName } = getTargetsToConvert(project.targets);
if (!buildTargetName) {
warnIfProvided(`The provided project "${projectName}" does not have any targets using on of the ` +
`'@angular-devkit/build-angular:browser', '@angular-devkit/build-angular:browser-esbuild', ` +
`'@nx/angular:browser' and '@nx/angular:browser-esbuild' executors. Skipping conversion.`);
return false;
}
const useNxExecutor = project.targets[buildTargetName].executor.startsWith('@nx/angular:');
let newExecutor;
if (useNxExecutor) {
newExecutor = '@nx/angular:application';
}
else {
const { major: angularMajorVersion } = (0, version_utils_1.getInstalledAngularVersionInfo)(tree);
newExecutor =
angularMajorVersion >= 20
? '@angular/build:application'
: '@angular-devkit/build-angular:application';
}
const buildTarget = project.targets[buildTargetName];
buildTarget.executor = newExecutor;
if (buildTarget.outputs) {
buildTarget.outputs = buildTarget.outputs.map((output) => output === '{options.outputPath}' ? '{options.outputPath.base}' : output);
}
for (const [, options] of (0, targets_1.allTargetOptions)(buildTarget)) {
if (options['index'] === '') {
options['index'] = false;
}
// Rename and transform options
options['browser'] = options['main'];
if (serverTargetName && typeof options['browser'] === 'string') {
options['server'] = (0, posix_1.dirname)(options['browser']) + '/main.server.ts';
}
options['serviceWorker'] =
options['ngswConfigPath'] ?? options['serviceWorker'];
if (typeof options['polyfills'] === 'string') {
options['polyfills'] = [options['polyfills']];
}
let outputPath = options['outputPath'];
if (typeof outputPath === 'string') {
if (!/\/browser\/?$/.test(outputPath)) {
devkit_1.logger.warn(`The output location of the browser build has been updated from "${outputPath}" to ` +
`"${(0, posix_1.join)(outputPath, 'browser')}". ` +
'You might need to adjust your deployment pipeline or, as an alternative, ' +
'set outputPath.browser to "" in order to maintain the previous functionality.');
}
else {
outputPath = outputPath.replace(/\/browser\/?$/, '');
}
options['outputPath'] = {
base: outputPath,
};
if (typeof options['resourcesOutputPath'] === 'string') {
const media = options['resourcesOutputPath'].replaceAll('/', '');
if (media && media !== 'media') {
options['outputPath'] = {
base: outputPath,
media: media,
};
}
}
}
// Delete removed options
delete options['vendorChunk'];
delete options['commonChunk'];
delete options['resourcesOutputPath'];
delete options['buildOptimizer'];
delete options['main'];
delete options['ngswConfigPath'];
}
// Merge browser and server tsconfig
if (serverTargetName) {
const browserTsConfigPath = buildTarget?.options?.tsConfig;
const serverTsConfigPath = project.targets['server']?.options?.tsConfig;
if (typeof browserTsConfigPath !== 'string') {
devkit_1.logger.warn(`Cannot update project "${projectName}" to use the application executor ` +
`as the browser tsconfig cannot be located.`);
}
if (typeof serverTsConfigPath !== 'string') {
devkit_1.logger.warn(`Cannot update project "${projectName}" to use the application executor ` +
`as the server tsconfig cannot be located.`);
}
const browserTsConfigJson = (0, devkit_1.readJson)(tree, browserTsConfigPath);
const serverTsConfigJson = (0, devkit_1.readJson)(tree, serverTsConfigPath);
const serverFiles = ['src/main.server.ts', 'src/server.ts'];
if (tree.exists((0, posix_1.join)(project.root, 'src/app/app.config.server.ts'))) {
serverFiles.push('src/app/app.config.server.ts');
}
const files = new Set([
...(browserTsConfigJson.files ?? []),
...(serverTsConfigJson.files ?? []),
]);
// Server files will be added later if needed by the setup-ssr generator
files.delete('server.ts');
files.delete('src/server.ts');
files.delete('src/main.server.ts');
if (files.size) {
browserTsConfigJson.files = Array.from(files);
}
else if (browserTsConfigJson.files) {
delete browserTsConfigJson.files;
}
browserTsConfigJson.compilerOptions ?? {};
browserTsConfigJson.compilerOptions.types = Array.from(new Set([
...(browserTsConfigJson.compilerOptions.types ?? []),
...(serverTsConfigJson.compilerOptions?.types ?? []),
]));
if (browserTsConfigJson.exclude?.length) {
const normalizeExclude = (exclude) => exclude.startsWith('./') ? exclude.slice(2) : exclude;
browserTsConfigJson.exclude = browserTsConfigJson.exclude.filter((exclude) => !serverFiles.includes(normalizeExclude(exclude)));
}
(0, devkit_1.writeJson)(tree, browserTsConfigPath, browserTsConfigJson);
// Delete server tsconfig
tree.delete(serverTsConfigPath);
}
// Update project main tsconfig
const projectRootTsConfigPath = (0, posix_1.join)(project.root, 'tsconfig.json');
if (tree.exists(projectRootTsConfigPath)) {
const rootTsConfigJson = (0, devkit_1.readJson)(tree, projectRootTsConfigPath);
rootTsConfigJson.compilerOptions ?? {};
rootTsConfigJson.compilerOptions.esModuleInterop = true;
rootTsConfigJson.compilerOptions.downlevelIteration = undefined;
rootTsConfigJson.compilerOptions.allowSyntheticDefaultImports = undefined;
(0, devkit_1.writeJson)(tree, projectRootTsConfigPath, rootTsConfigJson);
}
// Update server file
const ssrMainFile = project.targets['server']?.options?.['main'];
if (typeof ssrMainFile === 'string') {
tree.delete(ssrMainFile);
// apply changes so the setup-ssr generator can access the updated project
(0, devkit_1.updateProjectConfiguration)(tree, projectName, project);
await (0, setup_ssr_1.setupSsr)(tree, { project: projectName, skipFormat: true });
// re-read project configuration as it might have changed
project = (0, devkit_1.readProjectConfiguration)(tree, projectName);
}
// Delete all redundant targets
for (const [targetName, target] of Object.entries(project.targets)) {
if (redundantExecutors.has(target.executor)) {
delete project.targets[targetName];
}
}
(0, devkit_1.updateProjectConfiguration)(tree, projectName, project);
return true;
}
function getTargetsToConvert(targets) {
let buildTargetName;
let serverTargetName;
for (const target of Object.keys(targets)) {
if (targets[target].executor === '@nx/angular:application' ||
targets[target].executor === '@angular/build:application' ||
targets[target].executor === '@angular-devkit/build-angular:application') {
devkit_1.logger.warn('The project is already using the application builder. Skipping conversion.');
return {};
}
// build target
if (executorsToConvert.has(targets[target].executor)) {
for (const [, options] of (0, targets_1.allTargetOptions)(targets[target])) {
if (options.customWebpackConfig) {
devkit_1.logger.warn(`The project is using a custom webpack configuration which is not supported by the esbuild-based application executor. Skipping conversion.`);
return {};
}
}
if (buildTargetName) {
devkit_1.logger.warn('The project has more than one build target. Skipping conversion.');
return {};
}
buildTargetName = target;
}
// server target
if (serverTargetExecutors.has(targets[target].executor)) {
if (targets[target].executor === '@nx/angular:webpack-server') {
for (const [, options] of (0, targets_1.allTargetOptions)(targets[target])) {
if (options.customWebpackConfig) {
devkit_1.logger.warn(`The project is using a custom webpack configuration which is not supported by the esbuild-based application executor. Skipping conversion.`);
return {};
}
}
}
if (serverTargetName) {
devkit_1.logger.warn('The project has more than one server target. Skipping conversion.');
return {};
}
serverTargetName = target;
}
}
return { buildTargetName, serverTargetName };
}
exports.default = convertToApplicationExecutor;