fix(angular): keep extra target metadata when needed in convert-to-rspack generator (#31309)
## Current Behavior When converting an Angular project to use Rspack with the `@nx/angular:convert-to-rspack` generator, some target top-level options can be lost (e.g. custom `dependsOn`, `outputs`, etc.). ## Expected Behavior When converting an Angular project to use Rspack with the `@nx/angular:convert-to-rspack` generator, relevant target top-level options that wouldn't be inferred need to be kept in the converted project.
This commit is contained in:
parent
f02cc49b06
commit
2cf519a654
@ -743,4 +743,468 @@ describe('convert-to-rspack', () => {
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
describe('top-level target options', () => {
|
||||
describe('build target', () => {
|
||||
it('should remove the target when there are no relevant top-level options', async () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
addProjectConfiguration(tree, 'app', {
|
||||
root: 'apps/app',
|
||||
sourceRoot: 'apps/app/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
build: {
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
options: {
|
||||
outputPath: 'dist/apps/app',
|
||||
index: 'apps/app/src/index.html',
|
||||
main: 'apps/app/src/main.ts',
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {});
|
||||
|
||||
await convertToRspack(tree, { project: 'app' });
|
||||
|
||||
const updatedProject = readProjectConfiguration(tree, 'app');
|
||||
expect(updatedProject.targets.build).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should remove the target when all the top-level options match what would be inferred', async () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
updateJson(tree, 'nx.json', (json) => {
|
||||
json.namedInputs = {
|
||||
...json.namedInputs,
|
||||
production: [
|
||||
'default',
|
||||
'!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)',
|
||||
],
|
||||
};
|
||||
return json;
|
||||
});
|
||||
addProjectConfiguration(tree, 'app', {
|
||||
root: 'apps/app',
|
||||
sourceRoot: 'apps/app/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
build: {
|
||||
dependsOn: ['^build'],
|
||||
cache: true,
|
||||
inputs: ['production', '^production'],
|
||||
outputs: ['{options.outputPath}'],
|
||||
syncGenerators: ['@nx/js:typescript-sync'],
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
options: {
|
||||
outputPath: 'dist/apps/app',
|
||||
index: 'apps/app/src/index.html',
|
||||
main: 'apps/app/src/main.ts',
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {});
|
||||
|
||||
await convertToRspack(tree, { project: 'app' });
|
||||
|
||||
const updatedProject = readProjectConfiguration(tree, 'app');
|
||||
expect(updatedProject.targets.build).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should remove the target when the normalized output matches what would be inferred', async () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
addProjectConfiguration(tree, 'app', {
|
||||
root: 'apps/app',
|
||||
sourceRoot: 'apps/app/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
build: {
|
||||
outputs: ['{workspaceRoot}/dist/{projectRoot}'],
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
options: {
|
||||
outputPath: 'dist/apps/app',
|
||||
index: 'apps/app/src/index.html',
|
||||
main: 'apps/app/src/main.ts',
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {});
|
||||
|
||||
await convertToRspack(tree, { project: 'app' });
|
||||
|
||||
const updatedProject = readProjectConfiguration(tree, 'app');
|
||||
expect(updatedProject.targets.build).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should remove the target when the transformed output matches what would be inferred', async () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
addProjectConfiguration(tree, 'app', {
|
||||
root: 'apps/app',
|
||||
sourceRoot: 'apps/app/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
build: {
|
||||
outputs: ['{workspaceRoot}/dist/{projectRoot}/browser'],
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
options: {
|
||||
outputPath: 'dist/apps/app/browser',
|
||||
index: 'apps/app/src/index.html',
|
||||
main: 'apps/app/src/main.ts',
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {});
|
||||
|
||||
await convertToRspack(tree, { project: 'app' });
|
||||
|
||||
const updatedProject = readProjectConfiguration(tree, 'app');
|
||||
expect(updatedProject.targets.build).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should keep the target with updated outputs when they would not match what would be inferred', async () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
addProjectConfiguration(tree, 'app', {
|
||||
root: 'apps/app',
|
||||
sourceRoot: 'apps/app/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
build: {
|
||||
outputs: [
|
||||
// will be replaced with a explicit output path because the
|
||||
// inferred task won't have an outputPath option
|
||||
'{options.outputPath}',
|
||||
'{workspaceRoot}/some-other-output',
|
||||
],
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
options: {
|
||||
outputPath: 'dist/apps/app/browser',
|
||||
index: 'apps/app/src/index.html',
|
||||
main: 'apps/app/src/main.ts',
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {});
|
||||
|
||||
await convertToRspack(tree, { project: 'app' });
|
||||
|
||||
const updatedProject = readProjectConfiguration(tree, 'app');
|
||||
expect(updatedProject.targets.build).toStrictEqual({
|
||||
outputs: [
|
||||
'{workspaceRoot}/dist/apps/app',
|
||||
'{workspaceRoot}/some-other-output',
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove the target when the dependsOn option matches what would be inferred', async () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
addProjectConfiguration(tree, 'app', {
|
||||
root: 'apps/app',
|
||||
sourceRoot: 'apps/app/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
build: {
|
||||
dependsOn: ['^build'],
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
options: {
|
||||
outputPath: 'dist/apps/app',
|
||||
index: 'apps/app/src/index.html',
|
||||
main: 'apps/app/src/main.ts',
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {});
|
||||
|
||||
await convertToRspack(tree, { project: 'app' });
|
||||
|
||||
const updatedProject = readProjectConfiguration(tree, 'app');
|
||||
expect(updatedProject.targets.build).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should keep the target with dependsOn when they would not match what would be inferred', async () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
addProjectConfiguration(tree, 'app', {
|
||||
root: 'apps/app',
|
||||
sourceRoot: 'apps/app/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
build: {
|
||||
dependsOn: ['pre-build', '^build'],
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
options: {
|
||||
outputPath: 'dist/apps/app/browser',
|
||||
index: 'apps/app/src/index.html',
|
||||
main: 'apps/app/src/main.ts',
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {});
|
||||
|
||||
await convertToRspack(tree, { project: 'app' });
|
||||
|
||||
const updatedProject = readProjectConfiguration(tree, 'app');
|
||||
expect(updatedProject.targets.build).toStrictEqual({
|
||||
dependsOn: ['pre-build', '^build'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove the target when the syncGenerators option matches what would be inferred', async () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
addProjectConfiguration(tree, 'app', {
|
||||
root: 'apps/app',
|
||||
sourceRoot: 'apps/app/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
build: {
|
||||
syncGenerators: ['@nx/js:typescript-sync'],
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
options: {
|
||||
outputPath: 'dist/apps/app',
|
||||
index: 'apps/app/src/index.html',
|
||||
main: 'apps/app/src/main.ts',
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {});
|
||||
|
||||
await convertToRspack(tree, { project: 'app' });
|
||||
|
||||
const updatedProject = readProjectConfiguration(tree, 'app');
|
||||
expect(updatedProject.targets.build).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should keep the target with syncGenerators when they would not match what would be inferred', async () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
addProjectConfiguration(tree, 'app', {
|
||||
root: 'apps/app',
|
||||
sourceRoot: 'apps/app/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
build: {
|
||||
syncGenerators: ['@foo/bar:baz'],
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
options: {
|
||||
outputPath: 'dist/apps/app/browser',
|
||||
index: 'apps/app/src/index.html',
|
||||
main: 'apps/app/src/main.ts',
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {});
|
||||
|
||||
await convertToRspack(tree, { project: 'app' });
|
||||
|
||||
const updatedProject = readProjectConfiguration(tree, 'app');
|
||||
expect(updatedProject.targets.build).toStrictEqual({
|
||||
syncGenerators: ['@foo/bar:baz', '@nx/js:typescript-sync'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should keep the target with any other extra top-level option that would not be inferred', async () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
addProjectConfiguration(tree, 'app', {
|
||||
root: 'apps/app',
|
||||
sourceRoot: 'apps/app/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
build: {
|
||||
parallelism: false,
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
options: {
|
||||
outputPath: 'dist/apps/app/browser',
|
||||
index: 'apps/app/src/index.html',
|
||||
main: 'apps/app/src/main.ts',
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {});
|
||||
|
||||
await convertToRspack(tree, { project: 'app' });
|
||||
|
||||
const updatedProject = readProjectConfiguration(tree, 'app');
|
||||
expect(updatedProject.targets.build).toStrictEqual({
|
||||
parallelism: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('serve target', () => {
|
||||
it('should remove the target when there are no relevant top-level options', async () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
addProjectConfiguration(tree, 'app', {
|
||||
root: 'apps/app',
|
||||
sourceRoot: 'apps/app/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
build: {
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
options: {
|
||||
outputPath: 'dist/apps/app',
|
||||
index: 'apps/app/src/index.html',
|
||||
main: 'apps/app/src/main.ts',
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
},
|
||||
},
|
||||
serve: {
|
||||
executor: '@angular-devkit/build-angular:dev-server',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {});
|
||||
|
||||
await convertToRspack(tree, { project: 'app' });
|
||||
|
||||
const updatedProject = readProjectConfiguration(tree, 'app');
|
||||
expect(updatedProject.targets.serve).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should remove the target when all the top-level options match what would be inferred', async () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
addProjectConfiguration(tree, 'app', {
|
||||
root: 'apps/app',
|
||||
sourceRoot: 'apps/app/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
build: {
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
options: {
|
||||
outputPath: 'dist/apps/app',
|
||||
index: 'apps/app/src/index.html',
|
||||
main: 'apps/app/src/main.ts',
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
},
|
||||
},
|
||||
serve: {
|
||||
continuous: true,
|
||||
syncGenerators: ['@nx/js:typescript-sync'],
|
||||
executor: '@angular-devkit/build-angular:dev-server',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {});
|
||||
|
||||
await convertToRspack(tree, { project: 'app' });
|
||||
|
||||
const updatedProject = readProjectConfiguration(tree, 'app');
|
||||
expect(updatedProject.targets.serve).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should remove the target when the syncGenerators option matches what would be inferred', async () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
addProjectConfiguration(tree, 'app', {
|
||||
root: 'apps/app',
|
||||
sourceRoot: 'apps/app/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
build: {
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
options: {
|
||||
outputPath: 'dist/apps/app',
|
||||
index: 'apps/app/src/index.html',
|
||||
main: 'apps/app/src/main.ts',
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
},
|
||||
},
|
||||
serve: {
|
||||
syncGenerators: ['@nx/js:typescript-sync'],
|
||||
executor: '@angular-devkit/build-angular:dev-server',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {});
|
||||
|
||||
await convertToRspack(tree, { project: 'app' });
|
||||
|
||||
const updatedProject = readProjectConfiguration(tree, 'app');
|
||||
expect(updatedProject.targets.serve).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should keep the target with syncGenerators when they would not match what would be inferred', async () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
addProjectConfiguration(tree, 'app', {
|
||||
root: 'apps/app',
|
||||
sourceRoot: 'apps/app/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
build: {
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
options: {
|
||||
outputPath: 'dist/apps/app/browser',
|
||||
index: 'apps/app/src/index.html',
|
||||
main: 'apps/app/src/main.ts',
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
},
|
||||
},
|
||||
serve: {
|
||||
syncGenerators: ['@foo/bar:baz'],
|
||||
executor: '@angular-devkit/build-angular:dev-server',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {});
|
||||
|
||||
await convertToRspack(tree, { project: 'app' });
|
||||
|
||||
const updatedProject = readProjectConfiguration(tree, 'app');
|
||||
expect(updatedProject.targets.serve).toStrictEqual({
|
||||
syncGenerators: ['@foo/bar:baz', '@nx/js:typescript-sync'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should keep the target with any other extra top-level option that would not be inferred', async () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
addProjectConfiguration(tree, 'app', {
|
||||
root: 'apps/app',
|
||||
sourceRoot: 'apps/app/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
build: {
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
options: {
|
||||
outputPath: 'dist/apps/app/browser',
|
||||
index: 'apps/app/src/index.html',
|
||||
main: 'apps/app/src/main.ts',
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
},
|
||||
},
|
||||
serve: {
|
||||
parallelism: false,
|
||||
executor: '@angular-devkit/build-angular:dev-server',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {});
|
||||
|
||||
await convertToRspack(tree, { project: 'app' });
|
||||
|
||||
const updatedProject = readProjectConfiguration(tree, 'app');
|
||||
expect(updatedProject.targets.serve).toStrictEqual({
|
||||
parallelism: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,18 +1,27 @@
|
||||
import {
|
||||
type Tree,
|
||||
readProjectConfiguration,
|
||||
addDependenciesToPackageJson,
|
||||
formatFiles,
|
||||
GeneratorCallback,
|
||||
runTasksInSerial,
|
||||
ensurePackage,
|
||||
formatFiles,
|
||||
joinPathFragments,
|
||||
normalizePath,
|
||||
readJson,
|
||||
readNxJson,
|
||||
readProjectConfiguration,
|
||||
runTasksInSerial,
|
||||
updateProjectConfiguration,
|
||||
workspaceRoot,
|
||||
joinPathFragments,
|
||||
readJson,
|
||||
writeJson,
|
||||
type ExpandedPluginConfiguration,
|
||||
type GeneratorCallback,
|
||||
type TargetConfiguration,
|
||||
type Tree,
|
||||
} from '@nx/devkit';
|
||||
import type { ConvertToRspackSchema } from './schema';
|
||||
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
|
||||
import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
|
||||
import type { RspackPluginOptions } from '@nx/rspack/plugins/plugin';
|
||||
import { prompt } from 'enquirer';
|
||||
import { relative, resolve } from 'path';
|
||||
import { join } from 'path/posix';
|
||||
import {
|
||||
angularRspackVersion,
|
||||
nxVersion,
|
||||
@ -23,11 +32,7 @@ import { createConfig } from './lib/create-config';
|
||||
import { getCustomWebpackConfig } from './lib/get-custom-webpack-config';
|
||||
import { updateTsconfig } from './lib/update-tsconfig';
|
||||
import { validateSupportedBuildExecutor } from './lib/validate-supported-executor';
|
||||
import { join } from 'path/posix';
|
||||
import { relative } from 'path';
|
||||
import { existsSync } from 'fs';
|
||||
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
|
||||
import { prompt } from 'enquirer';
|
||||
import type { ConvertToRspackSchema } from './schema';
|
||||
|
||||
const SUPPORTED_EXECUTORS = [
|
||||
'@angular-devkit/build-angular:browser',
|
||||
@ -242,11 +247,6 @@ function handleBuildTargetOptions(
|
||||
delete options.customWebpackConfig;
|
||||
}
|
||||
|
||||
if (options.outputs) {
|
||||
// handled by the Rspack inference plugin
|
||||
delete options.outputs;
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(options)) {
|
||||
let optionName = key;
|
||||
let optionValue =
|
||||
@ -348,8 +348,9 @@ export async function convertToRspack(
|
||||
root: project.root,
|
||||
};
|
||||
const configurationOptions: Record<string, Record<string, any>> = {};
|
||||
const buildTargetNames: string[] = [];
|
||||
const serveTargetNames: string[] = [];
|
||||
let buildTarget: { name: string; config: TargetConfiguration } | undefined;
|
||||
let serveTarget: { name: string; config: TargetConfiguration } | undefined;
|
||||
const targetsToRemove: string[] = [];
|
||||
let customWebpackConfigPath: string | undefined;
|
||||
|
||||
validateSupportedBuildExecutor(Object.values(project.targets));
|
||||
@ -380,7 +381,8 @@ export async function convertToRspack(
|
||||
);
|
||||
}
|
||||
}
|
||||
buildTargetNames.push(targetName);
|
||||
buildTarget = { name: targetName, config: target };
|
||||
targetsToRemove.push(targetName);
|
||||
} else if (
|
||||
target.executor === '@angular-devkit/build-angular:server' ||
|
||||
target.executor === '@nx/angular:webpack-server'
|
||||
@ -392,7 +394,7 @@ export async function convertToRspack(
|
||||
project.root
|
||||
);
|
||||
createConfigOptions.server = './src/main.server.ts';
|
||||
buildTargetNames.push(targetName);
|
||||
targetsToRemove.push(targetName);
|
||||
} else if (
|
||||
target.executor === '@angular-devkit/build-angular:dev-server' ||
|
||||
target.executor === '@nx/angular:dev-server' ||
|
||||
@ -407,7 +409,7 @@ export async function convertToRspack(
|
||||
project.root
|
||||
);
|
||||
|
||||
if (target.options.port !== DEFAULT_PORT) {
|
||||
if (target.options.port && target.options.port !== DEFAULT_PORT) {
|
||||
projectServePort = target.options.port;
|
||||
}
|
||||
}
|
||||
@ -425,7 +427,8 @@ export async function convertToRspack(
|
||||
);
|
||||
}
|
||||
}
|
||||
serveTargetNames.push(targetName);
|
||||
serveTarget = { name: targetName, config: target };
|
||||
targetsToRemove.push(targetName);
|
||||
} else if (target.executor === '@angular-devkit/build-angular:prerender') {
|
||||
if (target.options) {
|
||||
const prerenderOptions = {
|
||||
@ -447,10 +450,10 @@ export async function convertToRspack(
|
||||
}
|
||||
}
|
||||
}
|
||||
buildTargetNames.push(targetName);
|
||||
targetsToRemove.push(targetName);
|
||||
} else if (target.executor === '@angular-devkit/build-angular:app-shell') {
|
||||
createConfigOptions.appShell = true;
|
||||
buildTargetNames.push(targetName);
|
||||
targetsToRemove.push(targetName);
|
||||
}
|
||||
}
|
||||
|
||||
@ -467,28 +470,229 @@ export async function convertToRspack(
|
||||
);
|
||||
updateTsconfig(tree, project.root);
|
||||
|
||||
for (const targetName of [...buildTargetNames, ...serveTargetNames]) {
|
||||
for (const targetName of targetsToRemove) {
|
||||
delete project.targets[targetName];
|
||||
}
|
||||
|
||||
if (projectServePort !== DEFAULT_PORT) {
|
||||
project.targets.serve ??= {};
|
||||
project.targets.serve.options ??= {};
|
||||
project.targets.serve.options.port = projectServePort;
|
||||
}
|
||||
|
||||
updateProjectConfiguration(tree, projectName, project);
|
||||
|
||||
// ensure plugin is registered
|
||||
const { rspackInitGenerator } = ensurePackage<typeof import('@nx/rspack')>(
|
||||
'@nx/rspack',
|
||||
nxVersion
|
||||
);
|
||||
|
||||
await rspackInitGenerator(tree, {
|
||||
addPlugin: true,
|
||||
framework: 'angular',
|
||||
});
|
||||
|
||||
// find the inferred target names
|
||||
const nxJson = readNxJson(tree);
|
||||
let inferredBuildTargetName = 'build';
|
||||
let inferredServeTargetName = 'serve';
|
||||
const pluginRegistration = nxJson.plugins.find(
|
||||
(p): p is ExpandedPluginConfiguration<RspackPluginOptions> =>
|
||||
typeof p === 'string' ? false : p.plugin === '@nx/rspack/plugin'
|
||||
);
|
||||
if (pluginRegistration) {
|
||||
inferredBuildTargetName =
|
||||
pluginRegistration.options.buildTargetName ?? inferredBuildTargetName;
|
||||
inferredServeTargetName =
|
||||
pluginRegistration.options.serveTargetName ?? inferredServeTargetName;
|
||||
}
|
||||
|
||||
if (buildTarget) {
|
||||
// these are all replaced by the inferred task
|
||||
delete buildTarget.config.options;
|
||||
delete buildTarget.config.configurations;
|
||||
delete buildTarget.config.defaultConfiguration;
|
||||
delete buildTarget.config.executor;
|
||||
|
||||
const shouldOverrideInputs = (inputs: TargetConfiguration['inputs']) => {
|
||||
if (!inputs?.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inputs.length === 2) {
|
||||
// check whether the existing inputs would match the inferred task
|
||||
// inputs with the exception of the @rspack/cli external dependency
|
||||
// which webpack tasks wouldn't have
|
||||
const namedInputs = getNamedInputs(project.root, {
|
||||
nxJsonConfiguration: nxJson,
|
||||
configFiles: [],
|
||||
workspaceRoot,
|
||||
});
|
||||
|
||||
if ('production' in namedInputs) {
|
||||
return !['production', '^production'].every((input) =>
|
||||
inputs.includes(input)
|
||||
);
|
||||
}
|
||||
|
||||
return !['default', '^default'].every((input) =>
|
||||
inputs.includes(input)
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
if (shouldOverrideInputs(buildTarget.config.inputs)) {
|
||||
// keep existing inputs and add the @rspack/cli external dependency
|
||||
buildTarget.config.inputs = [
|
||||
...buildTarget.config.inputs,
|
||||
{ externalDependencies: ['@rspack/cli'] },
|
||||
];
|
||||
} else {
|
||||
delete buildTarget.config.inputs;
|
||||
}
|
||||
|
||||
if (buildTarget.config.cache) {
|
||||
delete buildTarget.config.cache;
|
||||
}
|
||||
|
||||
if (
|
||||
buildTarget.config.dependsOn?.length === 1 &&
|
||||
buildTarget.config.dependsOn[0] === `^${buildTarget.name}`
|
||||
) {
|
||||
delete buildTarget.config.dependsOn;
|
||||
} else if (buildTarget.config.dependsOn) {
|
||||
buildTarget.config.dependsOn = buildTarget.config.dependsOn.map((dep) =>
|
||||
dep === `^${buildTarget.name}` ? `^${inferredBuildTargetName}` : dep
|
||||
);
|
||||
}
|
||||
|
||||
const newOutputPath = joinPathFragments(
|
||||
project.root,
|
||||
createConfigOptions.outputPath.base
|
||||
);
|
||||
const shouldOverrideOutputs = (outputs: TargetConfiguration['outputs']) => {
|
||||
if (!outputs?.length) {
|
||||
// this means the target was wrongly configured, so, we don't override
|
||||
// anything and let the inferred outputs be used
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outputs.length === 1) {
|
||||
if (outputs[0] === '{options.outputPath}') {
|
||||
// the inferred task output is created after the createConfig
|
||||
// outputPath option, so we don't need to keep this
|
||||
return false;
|
||||
}
|
||||
|
||||
const normalizedOutputPath = outputs[0]
|
||||
.replace('{workspaceRoot}/', '')
|
||||
.replace('{projectRoot}', project.root)
|
||||
.replace('{projectName}', '');
|
||||
if (
|
||||
normalizedOutputPath === newOutputPath ||
|
||||
normalizedOutputPath.replace(/\/browser\/?$/, '') === newOutputPath
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
const normalizeOutput = (
|
||||
path: string,
|
||||
workspaceRoot: string,
|
||||
projectRoot: string
|
||||
) => {
|
||||
const fullProjectRoot = resolve(workspaceRoot, projectRoot);
|
||||
const fullPath = resolve(workspaceRoot, path);
|
||||
const pathRelativeToProjectRoot = normalizePath(
|
||||
relative(fullProjectRoot, fullPath)
|
||||
);
|
||||
if (pathRelativeToProjectRoot.startsWith('..')) {
|
||||
return joinPathFragments(
|
||||
'{workspaceRoot}',
|
||||
relative(workspaceRoot, fullPath)
|
||||
);
|
||||
}
|
||||
|
||||
return joinPathFragments('{projectRoot}', pathRelativeToProjectRoot);
|
||||
};
|
||||
|
||||
if (shouldOverrideOutputs(buildTarget.config.outputs)) {
|
||||
buildTarget.config.outputs = buildTarget.config.outputs.map((output) => {
|
||||
if (output === '{options.outputPath}') {
|
||||
// the target won't have an outputPath option, so we replace it with the new output path
|
||||
return normalizeOutput(newOutputPath, workspaceRoot, project.root);
|
||||
}
|
||||
|
||||
const normalizedOutputPath = output
|
||||
.replace('{workspaceRoot}/', '')
|
||||
.replace('{projectRoot}', project.root)
|
||||
.replace('{projectName}', '');
|
||||
if (
|
||||
/\/browser\/?$/.test(normalizedOutputPath) &&
|
||||
normalizedOutputPath.replace(/\/browser\/?$/, '') === newOutputPath
|
||||
) {
|
||||
return normalizeOutput(newOutputPath, workspaceRoot, project.root);
|
||||
}
|
||||
|
||||
return output;
|
||||
});
|
||||
} else {
|
||||
delete buildTarget.config.outputs;
|
||||
}
|
||||
|
||||
if (
|
||||
buildTarget.config.syncGenerators?.length === 1 &&
|
||||
buildTarget.config.syncGenerators[0] === '@nx/js:typescript-sync'
|
||||
) {
|
||||
delete buildTarget.config.syncGenerators;
|
||||
} else if (buildTarget.config.syncGenerators?.length) {
|
||||
buildTarget.config.syncGenerators = Array.from(
|
||||
new Set([
|
||||
...buildTarget.config.syncGenerators,
|
||||
'@nx/js:typescript-sync',
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
if (Object.keys(buildTarget.config).length) {
|
||||
// there's extra target metadata left that wouldn't be inferred, we keep it
|
||||
project.targets[inferredBuildTargetName] = buildTarget.config;
|
||||
}
|
||||
}
|
||||
if (serveTarget) {
|
||||
delete serveTarget.config.options;
|
||||
delete serveTarget.config.configurations;
|
||||
delete serveTarget.config.defaultConfiguration;
|
||||
delete serveTarget.config.executor;
|
||||
|
||||
if (serveTarget.config.continuous) {
|
||||
delete serveTarget.config.continuous;
|
||||
}
|
||||
if (
|
||||
serveTarget.config.syncGenerators?.length === 1 &&
|
||||
serveTarget.config.syncGenerators[0] === '@nx/js:typescript-sync'
|
||||
) {
|
||||
delete serveTarget.config.syncGenerators;
|
||||
} else if (serveTarget.config.syncGenerators?.length) {
|
||||
serveTarget.config.syncGenerators = Array.from(
|
||||
new Set([
|
||||
...serveTarget.config.syncGenerators,
|
||||
'@nx/js:typescript-sync',
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
if (projectServePort !== DEFAULT_PORT) {
|
||||
serveTarget.config.options = {};
|
||||
serveTarget.config.options.port = projectServePort;
|
||||
}
|
||||
|
||||
if (Object.keys(serveTarget.config).length) {
|
||||
// there's extra target metadata left that wouldn't be inferred, we keep it
|
||||
project.targets[inferredServeTargetName] = serveTarget.config;
|
||||
}
|
||||
}
|
||||
|
||||
updateProjectConfiguration(tree, projectName, project);
|
||||
|
||||
// This is needed to prevent a circular execution of the build target
|
||||
const rootPkgJson = readJson(tree, 'package.json');
|
||||
if (rootPkgJson.scripts?.build === 'nx build') {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user