feat(angular): add convert-to-rspack generator (#29860)
## Current Behavior Nx currently does not offer an automated method for switching from an Angular Webpack build to an Angular Rspack build. ## Expected Behavior Provide a generator `convert-to-rspack` in `@nx/angular` that will allow conversion from an Angular Webpack build to an Angular Rspack build. Usage: `nx g convert-to-rspack --project=myapp` ## TODO - [x] handle more builder options - [x] take existing custom webpack configs and migrate into the rspack config that is created
This commit is contained in:
parent
f40873ffbe
commit
0082081d5c
@ -7314,6 +7314,14 @@
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "convert-to-rspack",
|
||||
"path": "/nx-api/angular/generators/convert-to-rspack",
|
||||
"name": "convert-to-rspack",
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "directive",
|
||||
"path": "/nx-api/angular/generators/directive",
|
||||
|
||||
@ -208,6 +208,15 @@
|
||||
"path": "/nx-api/angular/generators/convert-to-application-executor",
|
||||
"type": "generator"
|
||||
},
|
||||
"/nx-api/angular/generators/convert-to-rspack": {
|
||||
"description": "Converts Angular Webpack projects to use Rspack.",
|
||||
"file": "generated/packages/angular/generators/convert-to-rspack.json",
|
||||
"hidden": false,
|
||||
"name": "convert-to-rspack",
|
||||
"originalFilePath": "/packages/angular/src/generators/convert-to-rspack/schema.json",
|
||||
"path": "/nx-api/angular/generators/convert-to-rspack",
|
||||
"type": "generator"
|
||||
},
|
||||
"/nx-api/angular/generators/directive": {
|
||||
"description": "Generate an Angular directive.",
|
||||
"file": "generated/packages/angular/generators/directive.json",
|
||||
|
||||
@ -203,6 +203,15 @@
|
||||
"path": "angular/generators/convert-to-application-executor",
|
||||
"type": "generator"
|
||||
},
|
||||
{
|
||||
"description": "Converts Angular Webpack projects to use Rspack.",
|
||||
"file": "generated/packages/angular/generators/convert-to-rspack.json",
|
||||
"hidden": false,
|
||||
"name": "convert-to-rspack",
|
||||
"originalFilePath": "/packages/angular/src/generators/convert-to-rspack/schema.json",
|
||||
"path": "angular/generators/convert-to-rspack",
|
||||
"type": "generator"
|
||||
},
|
||||
{
|
||||
"description": "Generate an Angular directive.",
|
||||
"file": "generated/packages/angular/generators/directive.json",
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "convert-to-rspack",
|
||||
"factory": "./src/generators/convert-to-rspack/convert-to-rspack",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/schema",
|
||||
"$id": "GeneratorNxApp",
|
||||
"title": "Creates an Angular application.",
|
||||
"description": "Creates an Angular application.",
|
||||
"type": "object",
|
||||
"cli": "nx",
|
||||
"properties": {
|
||||
"project": {
|
||||
"type": "string",
|
||||
"aliases": ["name", "projectName"],
|
||||
"description": "Project for which to convert to rspack.",
|
||||
"$default": { "$source": "argv", "index": 0 },
|
||||
"x-priority": "important"
|
||||
},
|
||||
"skipFormat": {
|
||||
"description": "Skip formatting files.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"skipInstall": {
|
||||
"description": "Skip installing dependencies.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"presets": []
|
||||
},
|
||||
"description": "Converts Angular Webpack projects to use Rspack.",
|
||||
"implementation": "/packages/angular/src/generators/convert-to-rspack/convert-to-rspack.ts",
|
||||
"aliases": [],
|
||||
"hidden": false,
|
||||
"path": "/packages/angular/src/generators/convert-to-rspack/schema.json",
|
||||
"type": "generator"
|
||||
}
|
||||
@ -366,6 +366,7 @@
|
||||
- [component-story](/nx-api/angular/generators/component-story)
|
||||
- [component-test](/nx-api/angular/generators/component-test)
|
||||
- [convert-to-application-executor](/nx-api/angular/generators/convert-to-application-executor)
|
||||
- [convert-to-rspack](/nx-api/angular/generators/convert-to-rspack)
|
||||
- [directive](/nx-api/angular/generators/directive)
|
||||
- [federate-module](/nx-api/angular/generators/federate-module)
|
||||
- [init](/nx-api/angular/generators/init)
|
||||
|
||||
@ -144,3 +144,25 @@ describe('Move Angular Project', () => {
|
||||
expect(lib2File).toContain(`extends ${newModule}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Convert Angular Webpack Project to Rspack', () => {
|
||||
let proj: string;
|
||||
let app1: string;
|
||||
|
||||
beforeAll(() => {
|
||||
proj = newProject({ packages: ['@nx/angular'] });
|
||||
app1 = uniq('app1');
|
||||
runCLI(
|
||||
`generate @nx/angular:app ${app1} --bundler=webpack --no-interactive`
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(() => cleanupProject());
|
||||
|
||||
it('should convert an Angular Webpack project to Rspack', async () => {
|
||||
runCLI(`generate @nx/angular:convert-to-rspack --project=${app1}`);
|
||||
const buildOutput = runCLI(`build ${app1}`);
|
||||
expect(buildOutput).toContain('rspack build');
|
||||
expect(buildOutput).toContain('browser compiled');
|
||||
});
|
||||
});
|
||||
|
||||
@ -38,6 +38,11 @@
|
||||
"schema": "./src/generators/convert-to-application-executor/schema.json",
|
||||
"description": "Converts projects to use the `@nx/angular:application` executor or the `@angular-devkit/build-angular:application` builder."
|
||||
},
|
||||
"convert-to-rspack": {
|
||||
"factory": "./src/generators/convert-to-rspack/convert-to-rspack",
|
||||
"schema": "./src/generators/convert-to-rspack/schema.json",
|
||||
"description": "Converts Angular Webpack projects to use Rspack."
|
||||
},
|
||||
"directive": {
|
||||
"factory": "./src/generators/directive/directive",
|
||||
"schema": "./src/generators/directive/schema.json",
|
||||
|
||||
@ -50,6 +50,7 @@
|
||||
"dependencies": {
|
||||
"@phenomnomnominal/tsquery": "~5.0.1",
|
||||
"@typescript-eslint/type-utils": "^8.0.0",
|
||||
"enquirer": "~2.3.6",
|
||||
"picocolors": "^1.1.0",
|
||||
"magic-string": "~0.30.2",
|
||||
"minimatch": "9.0.3",
|
||||
@ -60,6 +61,7 @@
|
||||
"@nx/js": "file:../js",
|
||||
"@nx/eslint": "file:../eslint",
|
||||
"@nx/webpack": "file:../webpack",
|
||||
"@nx/rspack": "file:../rspack",
|
||||
"@nx/module-federation": "file:../module-federation",
|
||||
"@nx/web": "file:../web",
|
||||
"@nx/workspace": "file:../workspace",
|
||||
|
||||
@ -0,0 +1,487 @@
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { convertToRspack } from './convert-to-rspack';
|
||||
import {
|
||||
addProjectConfiguration,
|
||||
readJson,
|
||||
readNxJson,
|
||||
readProjectConfiguration,
|
||||
updateJson,
|
||||
writeJson,
|
||||
} from '@nx/devkit';
|
||||
import * as _configUtils from '@nx/devkit/src/utils/config-utils';
|
||||
|
||||
jest.mock('@nx/devkit/src/utils/config-utils', () => ({
|
||||
...jest.requireActual('@nx/devkit/src/utils/config-utils'),
|
||||
loadConfigFile: jest.fn().mockImplementation(async (path) => {
|
||||
return () => {
|
||||
return {};
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('convert-to-rspack', () => {
|
||||
it('should convert a basic angular webpack application to rspack', async () => {
|
||||
// ARRANGE
|
||||
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',
|
||||
polyfills: ['tslib'], // zone.js is not in nx repo's node_modules so simulating it with a package that is
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
assets: [
|
||||
'apps/app/src/favicon.ico',
|
||||
'apps/app/src/assets',
|
||||
{ input: 'apps/app/public', glob: '**/*' },
|
||||
],
|
||||
styles: ['apps/app/src/styles.scss'],
|
||||
scripts: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {});
|
||||
updateJson(tree, 'package.json', (json) => {
|
||||
json.scripts ??= {};
|
||||
json.scripts.build = 'nx build';
|
||||
return json;
|
||||
});
|
||||
|
||||
// ACT
|
||||
await convertToRspack(tree, { project: 'app' });
|
||||
|
||||
// ASSERT
|
||||
const updatedProject = readProjectConfiguration(tree, 'app');
|
||||
const pkgJson = readJson(tree, 'package.json');
|
||||
const nxJson = readNxJson(tree);
|
||||
expect(tree.read('apps/app/rspack.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { createConfig } from '@nx/angular-rspack';
|
||||
|
||||
export default createConfig({
|
||||
options: {
|
||||
root: __dirname,
|
||||
|
||||
outputPath: {
|
||||
base: '../../dist/apps/app',
|
||||
},
|
||||
index: './src/index.html',
|
||||
browser: './src/main.ts',
|
||||
polyfills: ['tslib'],
|
||||
tsConfig: './tsconfig.app.json',
|
||||
assets: [
|
||||
'./src/favicon.ico',
|
||||
'./src/assets',
|
||||
{
|
||||
input: './public',
|
||||
glob: '**/*',
|
||||
},
|
||||
],
|
||||
styles: ['./src/styles.scss'],
|
||||
scripts: [],
|
||||
},
|
||||
});
|
||||
"
|
||||
`);
|
||||
expect(pkgJson.devDependencies['@nx/angular-rspack']).toBeDefined();
|
||||
expect(
|
||||
nxJson.plugins.find((p) =>
|
||||
typeof p === 'string' ? false : p.plugin === '@nx/rspack/plugin'
|
||||
)
|
||||
).toBeDefined();
|
||||
expect(pkgJson.scripts?.build).toBeUndefined();
|
||||
expect(updatedProject.targets.build).not.toBeDefined();
|
||||
expect(updatedProject.targets.serve).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should normalize paths to libs in workspace correctly', async () => {
|
||||
// ARRANGE
|
||||
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',
|
||||
polyfills: ['tslib', 'apps/app/src/polyfills.ts'],
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
assets: ['libs/mylib/src/favicon.ico'],
|
||||
styles: ['apps/app/src/styles.scss'],
|
||||
scripts: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {});
|
||||
updateJson(tree, 'package.json', (json) => {
|
||||
json.scripts ??= {};
|
||||
json.scripts.build = 'nx build';
|
||||
return json;
|
||||
});
|
||||
tree.write('libs/mylib/src/favicon.ico', 'libs/mylib/src/favicon.ico');
|
||||
|
||||
// ACT
|
||||
await convertToRspack(tree, { project: 'app' });
|
||||
|
||||
// ASSERT
|
||||
const updatedProject = readProjectConfiguration(tree, 'app');
|
||||
const pkgJson = readJson(tree, 'package.json');
|
||||
const nxJson = readNxJson(tree);
|
||||
expect(tree.read('apps/app/rspack.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { createConfig } from '@nx/angular-rspack';
|
||||
|
||||
export default createConfig({
|
||||
options: {
|
||||
root: __dirname,
|
||||
|
||||
outputPath: {
|
||||
base: '../../dist/apps/app',
|
||||
},
|
||||
index: './src/index.html',
|
||||
browser: './src/main.ts',
|
||||
polyfills: ['tslib', './src/polyfills.ts'],
|
||||
tsConfig: './tsconfig.app.json',
|
||||
assets: ['../../libs/mylib/src/favicon.ico'],
|
||||
styles: ['./src/styles.scss'],
|
||||
scripts: [],
|
||||
},
|
||||
});
|
||||
"
|
||||
`);
|
||||
expect(pkgJson.devDependencies['@nx/angular-rspack']).toBeDefined();
|
||||
expect(
|
||||
nxJson.plugins.find((p) =>
|
||||
typeof p === 'string' ? false : p.plugin === '@nx/rspack/plugin'
|
||||
)
|
||||
).toBeDefined();
|
||||
expect(pkgJson.scripts?.build).toBeUndefined();
|
||||
expect(updatedProject.targets.build).not.toBeDefined();
|
||||
expect(updatedProject.targets.serve).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should convert a basic angular webpack application with configurations to rspack', async () => {
|
||||
// ARRANGE
|
||||
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',
|
||||
polyfills: ['tslib'],
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
assets: [
|
||||
'apps/app/src/favicon.ico',
|
||||
'apps/app/src/assets',
|
||||
{ input: 'apps/app/public', glob: '**/*' },
|
||||
],
|
||||
styles: ['apps/app/src/styles.scss'],
|
||||
scripts: [],
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
outputPath: 'dist/apps/app-prod',
|
||||
index: 'apps/app/src/index.prod.html',
|
||||
main: 'apps/app/src/main.prod.ts',
|
||||
tsConfig: 'apps/app/tsconfig.prod.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {});
|
||||
|
||||
// ACT
|
||||
await convertToRspack(tree, { project: 'app' });
|
||||
|
||||
// ASSERT
|
||||
const updatedProject = readProjectConfiguration(tree, 'app');
|
||||
const pkgJson = readJson(tree, 'package.json');
|
||||
const nxJson = readNxJson(tree);
|
||||
expect(tree.read('apps/app/rspack.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { createConfig } from '@nx/angular-rspack';
|
||||
|
||||
export default createConfig(
|
||||
{
|
||||
options: {
|
||||
root: __dirname,
|
||||
|
||||
outputPath: {
|
||||
base: '../../dist/apps/app',
|
||||
},
|
||||
index: './src/index.html',
|
||||
browser: './src/main.ts',
|
||||
polyfills: ['tslib'],
|
||||
tsConfig: './tsconfig.app.json',
|
||||
assets: [
|
||||
'./src/favicon.ico',
|
||||
'./src/assets',
|
||||
{
|
||||
input: './public',
|
||||
glob: '**/*',
|
||||
},
|
||||
],
|
||||
styles: ['./src/styles.scss'],
|
||||
scripts: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
production: {
|
||||
options: {
|
||||
outputPath: {
|
||||
base: '../../dist/apps/app-prod',
|
||||
},
|
||||
index: './src/index.prod.html',
|
||||
browser: './src/main.prod.ts',
|
||||
tsConfig: './tsconfig.prod.json',
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
"
|
||||
`);
|
||||
expect(pkgJson.devDependencies['@nx/angular-rspack']).toBeDefined();
|
||||
expect(
|
||||
nxJson.plugins.find((p) =>
|
||||
typeof p === 'string' ? false : p.plugin === '@nx/rspack/plugin'
|
||||
)
|
||||
).toBeDefined();
|
||||
expect(updatedProject.targets.build).not.toBeDefined();
|
||||
expect(updatedProject.targets.serve).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should convert an angular webpack application with custom webpack config function to rspack', async () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
|
||||
addProjectConfiguration(tree, 'app', {
|
||||
root: 'apps/app',
|
||||
sourceRoot: 'apps/app/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
build: {
|
||||
executor: '@nx/angular:webpack-browser',
|
||||
options: {
|
||||
outputPath: 'dist/apps/app',
|
||||
index: 'apps/app/src/index.html',
|
||||
main: 'apps/app/src/main.ts',
|
||||
polyfills: ['tslib'],
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
assets: [
|
||||
'apps/app/src/favicon.ico',
|
||||
'apps/app/src/assets',
|
||||
{ input: 'apps/app/public', glob: '**/*' },
|
||||
],
|
||||
styles: ['apps/app/src/styles.scss'],
|
||||
scripts: [],
|
||||
customWebpackConfig: {
|
||||
path: 'apps/app/webpack.config.js',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {});
|
||||
tree.write(
|
||||
'apps/app/module-federation.config.js',
|
||||
`
|
||||
module.exports = {
|
||||
name: 'app',
|
||||
exposes: {
|
||||
'./app': './src/app/index.ts',
|
||||
},
|
||||
remotes: ['remote1', 'remote2'],
|
||||
};
|
||||
`
|
||||
);
|
||||
tree.write(
|
||||
'apps/app/webpack.config.js',
|
||||
`
|
||||
const { withModuleFederation } = require('@nx/module-federation/angular');
|
||||
const config = require('./module-federation.config');
|
||||
module.exports = withModuleFederation(config, { dts: false });
|
||||
`
|
||||
);
|
||||
|
||||
// ACT
|
||||
await convertToRspack(tree, { project: 'app' });
|
||||
|
||||
// ASSERT
|
||||
const updatedProject = readProjectConfiguration(tree, 'app');
|
||||
expect(tree.read('apps/app/rspack.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { createConfig } from '@nx/angular-rspack';
|
||||
import baseWebpackConfig from './webpack.config';
|
||||
import webpackMerge from 'webpack-merge';
|
||||
|
||||
const baseConfig = createConfig({
|
||||
options: {
|
||||
root: __dirname,
|
||||
|
||||
outputPath: {
|
||||
base: '../../dist/apps/app',
|
||||
},
|
||||
index: './src/index.html',
|
||||
browser: './src/main.ts',
|
||||
polyfills: ['tslib'],
|
||||
tsConfig: './tsconfig.app.json',
|
||||
assets: [
|
||||
'./src/favicon.ico',
|
||||
'./src/assets',
|
||||
{
|
||||
input: './public',
|
||||
glob: '**/*',
|
||||
},
|
||||
],
|
||||
styles: ['./src/styles.scss'],
|
||||
scripts: [],
|
||||
},
|
||||
});
|
||||
|
||||
export default webpackMerge(baseConfig[0], baseWebpackConfig);
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/app/webpack.config.js', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"const { NxModuleFederationPlugin } = require('@nx/module-federation/rspack');
|
||||
const config = require('./module-federation.config');
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
new NxModuleFederationPlugin(
|
||||
{ config },
|
||||
{
|
||||
dts: false,
|
||||
}
|
||||
),
|
||||
],
|
||||
};
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should convert an angular webpack application with custom webpack config to rspack', async () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
|
||||
addProjectConfiguration(tree, 'app', {
|
||||
root: 'apps/app',
|
||||
sourceRoot: 'apps/app/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
build: {
|
||||
executor: '@nx/angular:webpack-browser',
|
||||
options: {
|
||||
outputPath: 'dist/apps/app',
|
||||
index: 'apps/app/src/index.html',
|
||||
main: 'apps/app/src/main.ts',
|
||||
polyfills: ['tslib'],
|
||||
tsConfig: 'apps/app/tsconfig.app.json',
|
||||
assets: [
|
||||
'apps/app/src/favicon.ico',
|
||||
'apps/app/src/assets',
|
||||
{ input: 'apps/app/public', glob: '**/*' },
|
||||
],
|
||||
styles: ['apps/app/src/styles.scss'],
|
||||
scripts: [],
|
||||
customWebpackConfig: {
|
||||
path: 'apps/app/webpack.config.js',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(tree, 'apps/app/tsconfig.json', {});
|
||||
tree.write(
|
||||
'apps/app/module-federation.config.js',
|
||||
`
|
||||
module.exports = {
|
||||
name: 'app',
|
||||
exposes: {
|
||||
'./app': './src/app/index.ts',
|
||||
},
|
||||
remotes: ['remote1', 'remote2'],
|
||||
};
|
||||
`
|
||||
);
|
||||
tree.write('apps/app/webpack.config.js', ``);
|
||||
|
||||
jest
|
||||
.spyOn(_configUtils, 'loadConfigFile')
|
||||
.mockImplementation(async (path) => {
|
||||
return {
|
||||
default: {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ['style-loader', 'css-loader'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// ACT
|
||||
await convertToRspack(tree, { project: 'app' });
|
||||
|
||||
// ASSERT
|
||||
const updatedProject = readProjectConfiguration(tree, 'app');
|
||||
expect(tree.read('apps/app/rspack.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { createConfig } from '@nx/angular-rspack';
|
||||
import baseWebpackConfig from './webpack.config';
|
||||
import webpackMerge from 'webpack-merge';
|
||||
|
||||
const baseConfig = createConfig({
|
||||
options: {
|
||||
root: __dirname,
|
||||
|
||||
outputPath: {
|
||||
base: '../../dist/apps/app',
|
||||
},
|
||||
index: './src/index.html',
|
||||
browser: './src/main.ts',
|
||||
polyfills: ['tslib'],
|
||||
tsConfig: './tsconfig.app.json',
|
||||
assets: [
|
||||
'./src/favicon.ico',
|
||||
'./src/assets',
|
||||
{
|
||||
input: './public',
|
||||
glob: '**/*',
|
||||
},
|
||||
],
|
||||
styles: ['./src/styles.scss'],
|
||||
scripts: [],
|
||||
},
|
||||
});
|
||||
|
||||
export default webpackMerge(baseConfig[0], baseWebpackConfig);
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,466 @@
|
||||
import {
|
||||
type Tree,
|
||||
readProjectConfiguration,
|
||||
addDependenciesToPackageJson,
|
||||
formatFiles,
|
||||
GeneratorCallback,
|
||||
runTasksInSerial,
|
||||
ensurePackage,
|
||||
updateProjectConfiguration,
|
||||
workspaceRoot,
|
||||
joinPathFragments,
|
||||
readJson,
|
||||
writeJson,
|
||||
} from '@nx/devkit';
|
||||
import type { ConvertToRspackSchema } from './schema';
|
||||
import { angularRspackVersion, nxVersion } from '../../utils/versions';
|
||||
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';
|
||||
|
||||
const SUPPORTED_EXECUTORS = [
|
||||
'@angular-devkit/build-angular:browser',
|
||||
'@angular-devkit/build-angular:dev-server',
|
||||
'@nx/angular:webpack-browser',
|
||||
'@nx/angular:dev-server',
|
||||
'@nx/angular:module-federation-dev-server',
|
||||
];
|
||||
|
||||
const RENAMED_OPTIONS = {
|
||||
main: 'browser',
|
||||
ngswConfigPath: 'serviceWorker',
|
||||
};
|
||||
|
||||
const REMOVED_OPTIONS = [
|
||||
'publicHost',
|
||||
'disableHostCheck',
|
||||
'resourcesOutputPath',
|
||||
'routesFile',
|
||||
'routes',
|
||||
'discoverRoutes',
|
||||
'appModuleBundle',
|
||||
'inputIndexPath',
|
||||
'outputIndexPath',
|
||||
'buildOptimizer',
|
||||
'deployUrl',
|
||||
'buildTarget',
|
||||
'browserTarget',
|
||||
];
|
||||
|
||||
function normalizeFromProjectRoot(
|
||||
tree: Tree,
|
||||
path: string,
|
||||
projectRoot: string
|
||||
) {
|
||||
if (projectRoot === '.') {
|
||||
if (!path.startsWith('./')) {
|
||||
return `./${path}`;
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
} else if (path.startsWith(projectRoot)) {
|
||||
return path.replace(projectRoot, '.');
|
||||
} else if (!path.startsWith('./')) {
|
||||
if (tree.exists(path)) {
|
||||
const pathWithWorkspaceRoot = joinPathFragments(workspaceRoot, path);
|
||||
const projectRootWithWorkspaceRoot = joinPathFragments(
|
||||
workspaceRoot,
|
||||
projectRoot
|
||||
);
|
||||
return relative(projectRootWithWorkspaceRoot, pathWithWorkspaceRoot);
|
||||
}
|
||||
return `./${path}`;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
const defaultNormalizer = (tree: Tree, path: string, root: string) =>
|
||||
normalizeFromProjectRoot(tree, path, root);
|
||||
|
||||
const PATH_NORMALIZER = {
|
||||
index: (
|
||||
tree: Tree,
|
||||
path: string | { input: string; output: string },
|
||||
root: string
|
||||
) => {
|
||||
if (typeof path === 'string') {
|
||||
return normalizeFromProjectRoot(tree, path, root);
|
||||
}
|
||||
return {
|
||||
input: normalizeFromProjectRoot(tree, path.input, root),
|
||||
output: path.output ?? 'index.html',
|
||||
};
|
||||
},
|
||||
indexHtmlTransformer: defaultNormalizer,
|
||||
main: defaultNormalizer,
|
||||
server: defaultNormalizer,
|
||||
tsConfig: defaultNormalizer,
|
||||
outputPath: (tree: Tree, path: string, root: string) => {
|
||||
const relativePathFromWorkspaceRoot = relative(
|
||||
joinPathFragments(workspaceRoot, root),
|
||||
workspaceRoot
|
||||
);
|
||||
return joinPathFragments(relativePathFromWorkspaceRoot, path);
|
||||
},
|
||||
proxyConfig: defaultNormalizer,
|
||||
polyfills: (tree: Tree, paths: string | string[], root: string) => {
|
||||
const normalizedPaths: string[] = [];
|
||||
const normalizeFn = (path: string) => {
|
||||
try {
|
||||
const resolvedPath = require.resolve(path, {
|
||||
paths: [join(workspaceRoot, 'node_modules')],
|
||||
});
|
||||
normalizedPaths.push(path);
|
||||
} catch {
|
||||
normalizedPaths.push(normalizeFromProjectRoot(tree, path, root));
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof paths === 'string') {
|
||||
normalizeFn(paths);
|
||||
} else {
|
||||
for (const path of paths) {
|
||||
normalizeFn(path);
|
||||
}
|
||||
}
|
||||
return normalizedPaths;
|
||||
},
|
||||
styles: (
|
||||
tree: Tree,
|
||||
paths: Array<
|
||||
string | { input: string; bundleName: string; inject: boolean }
|
||||
>,
|
||||
root: string
|
||||
) => {
|
||||
const normalizedPaths: Array<
|
||||
string | { input: string; bundleName: string; inject: boolean }
|
||||
> = [];
|
||||
for (const path of paths) {
|
||||
if (typeof path === 'string') {
|
||||
normalizedPaths.push(normalizeFromProjectRoot(tree, path, root));
|
||||
} else {
|
||||
normalizedPaths.push({
|
||||
input: normalizeFromProjectRoot(tree, path.input, root),
|
||||
bundleName: path.bundleName,
|
||||
inject: path.inject ?? true,
|
||||
});
|
||||
}
|
||||
}
|
||||
return normalizedPaths;
|
||||
},
|
||||
scripts: (
|
||||
tree: Tree,
|
||||
paths: Array<
|
||||
string | { input: string; bundleName: string; inject: boolean }
|
||||
>,
|
||||
root: string
|
||||
) => {
|
||||
const normalizedPaths: Array<
|
||||
string | { input: string; bundleName: string; inject: boolean }
|
||||
> = [];
|
||||
for (const path of paths) {
|
||||
if (typeof path === 'string') {
|
||||
normalizedPaths.push(normalizeFromProjectRoot(tree, path, root));
|
||||
} else {
|
||||
normalizedPaths.push({
|
||||
input: normalizeFromProjectRoot(tree, path.input, root),
|
||||
bundleName: path.bundleName,
|
||||
inject: path.inject ?? true,
|
||||
});
|
||||
}
|
||||
}
|
||||
return normalizedPaths;
|
||||
},
|
||||
assets: (
|
||||
tree: Tree,
|
||||
paths: Array<string | { input: string; [key: string]: any }>,
|
||||
root: string
|
||||
) => {
|
||||
const normalizedPaths: Array<
|
||||
string | { input: string; [key: string]: any }
|
||||
> = [];
|
||||
for (const path of paths) {
|
||||
if (typeof path === 'string') {
|
||||
normalizedPaths.push(normalizeFromProjectRoot(tree, path, root));
|
||||
} else {
|
||||
normalizedPaths.push({
|
||||
...path,
|
||||
input: normalizeFromProjectRoot(tree, path.input, root),
|
||||
});
|
||||
}
|
||||
}
|
||||
return normalizedPaths;
|
||||
},
|
||||
fileReplacements: (
|
||||
tree: Tree,
|
||||
paths: Array<
|
||||
{ replace: string; with: string } | { src: string; replaceWith: string }
|
||||
>,
|
||||
root: string
|
||||
) => {
|
||||
const normalizedPaths: Array<
|
||||
{ replace: string; with: string } | { src: string; replaceWith: string }
|
||||
> = [];
|
||||
for (const path of paths) {
|
||||
normalizedPaths.push({
|
||||
replace: normalizeFromProjectRoot(
|
||||
tree,
|
||||
'src' in path ? path.src : path.replace,
|
||||
root
|
||||
),
|
||||
with: normalizeFromProjectRoot(
|
||||
tree,
|
||||
'replaceWith' in path ? path.replaceWith : path.with,
|
||||
root
|
||||
),
|
||||
});
|
||||
}
|
||||
return normalizedPaths;
|
||||
},
|
||||
};
|
||||
|
||||
function handleBuildTargetOptions(
|
||||
tree: Tree,
|
||||
options: Record<string, any>,
|
||||
newConfigurationOptions: Record<string, any>,
|
||||
root: string
|
||||
) {
|
||||
let customWebpackConfigPath: string | undefined;
|
||||
if (!options || Object.keys(options).length === 0) {
|
||||
return customWebpackConfigPath;
|
||||
}
|
||||
|
||||
if (options.customWebpackConfig) {
|
||||
customWebpackConfigPath = options.customWebpackConfig.path;
|
||||
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 =
|
||||
key in PATH_NORMALIZER ? PATH_NORMALIZER[key](tree, value, root) : value;
|
||||
if (REMOVED_OPTIONS.includes(key)) {
|
||||
continue;
|
||||
}
|
||||
if (key in RENAMED_OPTIONS) {
|
||||
optionName = RENAMED_OPTIONS[key];
|
||||
}
|
||||
newConfigurationOptions[optionName] = optionValue;
|
||||
}
|
||||
|
||||
if (typeof newConfigurationOptions.polyfills === 'string') {
|
||||
newConfigurationOptions.polyfills = [newConfigurationOptions.polyfills];
|
||||
}
|
||||
let outputPath = newConfigurationOptions.outputPath;
|
||||
if (typeof outputPath === 'string') {
|
||||
if (!/\/browser\/?$/.test(outputPath)) {
|
||||
console.warn(
|
||||
`The output location of the browser build has been updated from "${outputPath}" to ` +
|
||||
`"${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\/?$/, '');
|
||||
}
|
||||
|
||||
newConfigurationOptions['outputPath'] = {
|
||||
base: outputPath,
|
||||
};
|
||||
|
||||
if (typeof newConfigurationOptions.resourcesOutputPath === 'string') {
|
||||
const media = newConfigurationOptions.resourcesOutputPath.replaceAll(
|
||||
'/',
|
||||
''
|
||||
);
|
||||
if (media && media !== 'media') {
|
||||
newConfigurationOptions['outputPath'] = {
|
||||
base: outputPath,
|
||||
media,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return customWebpackConfigPath;
|
||||
}
|
||||
|
||||
function handleDevServerTargetOptions(
|
||||
tree: Tree,
|
||||
options: Record<string, any>,
|
||||
newConfigurationOptions: Record<string, any>,
|
||||
root: string
|
||||
) {
|
||||
for (const [key, value] of Object.entries(options)) {
|
||||
let optionName = key;
|
||||
let optionValue =
|
||||
key in PATH_NORMALIZER ? PATH_NORMALIZER[key](tree, value, root) : value;
|
||||
if (REMOVED_OPTIONS.includes(key)) {
|
||||
continue;
|
||||
}
|
||||
if (key in RENAMED_OPTIONS) {
|
||||
optionName = RENAMED_OPTIONS[key];
|
||||
}
|
||||
newConfigurationOptions[optionName] = optionValue;
|
||||
}
|
||||
}
|
||||
|
||||
async function getProjectToConvert(tree: Tree) {
|
||||
const projects = new Set<string>();
|
||||
for (const executor of SUPPORTED_EXECUTORS) {
|
||||
forEachExecutorOptions(tree, executor, (_, project) => {
|
||||
projects.add(project);
|
||||
});
|
||||
}
|
||||
const { project } = await prompt<{ project: string }>({
|
||||
type: 'select',
|
||||
name: 'project',
|
||||
message: 'Which project would you like to convert to rspack?',
|
||||
choices: Array.from(projects),
|
||||
});
|
||||
|
||||
return project;
|
||||
}
|
||||
|
||||
export async function convertToRspack(
|
||||
tree: Tree,
|
||||
schema: ConvertToRspackSchema
|
||||
) {
|
||||
let { project: projectName } = schema;
|
||||
if (!projectName) {
|
||||
projectName = await getProjectToConvert(tree);
|
||||
}
|
||||
const project = readProjectConfiguration(tree, projectName);
|
||||
const tasks: GeneratorCallback[] = [];
|
||||
|
||||
const createConfigOptions: Record<string, any> = {
|
||||
root: project.root,
|
||||
};
|
||||
const configurationOptions: Record<string, Record<string, any>> = {};
|
||||
const buildTargetNames: string[] = [];
|
||||
const serveTargetNames: string[] = [];
|
||||
let customWebpackConfigPath: string | undefined;
|
||||
|
||||
validateSupportedBuildExecutor(Object.values(project.targets));
|
||||
|
||||
for (const [targetName, target] of Object.entries(project.targets)) {
|
||||
if (
|
||||
target.executor === '@angular-devkit/build-angular:browser' ||
|
||||
target.executor === '@nx/angular:webpack-browser'
|
||||
) {
|
||||
customWebpackConfigPath = handleBuildTargetOptions(
|
||||
tree,
|
||||
target.options,
|
||||
createConfigOptions,
|
||||
project.root
|
||||
);
|
||||
if (target.configurations) {
|
||||
for (const [configurationName, configuration] of Object.entries(
|
||||
target.configurations
|
||||
)) {
|
||||
configurationOptions[configurationName] = {};
|
||||
handleBuildTargetOptions(
|
||||
tree,
|
||||
configuration,
|
||||
configurationOptions[configurationName],
|
||||
project.root
|
||||
);
|
||||
}
|
||||
}
|
||||
buildTargetNames.push(targetName);
|
||||
} else if (
|
||||
target.executor === '@angular-devkit/build-angular:dev-server' ||
|
||||
target.executor === '@nx/angular:dev-server' ||
|
||||
target.executor === '@nx/angular:module-federation-dev-server'
|
||||
) {
|
||||
createConfigOptions.devServer = {};
|
||||
if (target.options) {
|
||||
handleDevServerTargetOptions(
|
||||
tree,
|
||||
target.options,
|
||||
createConfigOptions.devServer,
|
||||
project.root
|
||||
);
|
||||
}
|
||||
if (target.configurations) {
|
||||
for (const [configurationName, configuration] of Object.entries(
|
||||
target.configurations
|
||||
)) {
|
||||
configurationOptions[configurationName] ??= {};
|
||||
configurationOptions[configurationName].devServer ??= {};
|
||||
handleDevServerTargetOptions(
|
||||
tree,
|
||||
configuration,
|
||||
configurationOptions[configurationName].devServer,
|
||||
project.root
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
serveTargetNames.push(targetName);
|
||||
}
|
||||
|
||||
const customWebpackConfigInfo = customWebpackConfigPath
|
||||
? await getCustomWebpackConfig(tree, project.root, customWebpackConfigPath)
|
||||
: undefined;
|
||||
|
||||
createConfig(
|
||||
tree,
|
||||
createConfigOptions,
|
||||
configurationOptions,
|
||||
customWebpackConfigInfo?.normalizedPathToCustomWebpackConfig,
|
||||
customWebpackConfigInfo?.isWebpackConfigFunction
|
||||
);
|
||||
updateTsconfig(tree, project.root);
|
||||
|
||||
for (const targetName of [...buildTargetNames, ...serveTargetNames]) {
|
||||
delete project.targets[targetName];
|
||||
}
|
||||
|
||||
updateProjectConfiguration(tree, projectName, project);
|
||||
|
||||
const { rspackInitGenerator } = ensurePackage<typeof import('@nx/rspack')>(
|
||||
'@nx/rspack',
|
||||
nxVersion
|
||||
);
|
||||
|
||||
await rspackInitGenerator(tree, {
|
||||
addPlugin: true,
|
||||
});
|
||||
|
||||
// This is needed to prevent a circular execution of the build target
|
||||
const rootPkgJson = readJson(tree, 'package.json');
|
||||
if (rootPkgJson.scripts?.build === 'nx build') {
|
||||
delete rootPkgJson.scripts.build;
|
||||
writeJson(tree, 'package.json', rootPkgJson);
|
||||
}
|
||||
|
||||
if (!schema.skipInstall) {
|
||||
const installTask = addDependenciesToPackageJson(
|
||||
tree,
|
||||
{},
|
||||
{
|
||||
'@nx/angular-rspack': angularRspackVersion,
|
||||
}
|
||||
);
|
||||
tasks.push(installTask);
|
||||
}
|
||||
|
||||
if (!schema.skipFormat) {
|
||||
await formatFiles(tree);
|
||||
}
|
||||
|
||||
return runTasksInSerial(...tasks);
|
||||
}
|
||||
|
||||
export default convertToRspack;
|
||||
@ -0,0 +1,98 @@
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { createConfig } from './create-config';
|
||||
|
||||
describe('createConfig', () => {
|
||||
it('should create a config file', () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
const opts = {
|
||||
root: 'root',
|
||||
index: 'src/index.html',
|
||||
browser: 'src/main.ts',
|
||||
tsconfigPath: 'tsconfig.app.json',
|
||||
polyfills: ['zone.js'],
|
||||
assets: ['public'],
|
||||
styles: ['src/styles.css'],
|
||||
scripts: [],
|
||||
jit: false,
|
||||
inlineStylesExtension: 'css',
|
||||
fileReplacements: [],
|
||||
hasServer: false,
|
||||
skipTypeChecking: false,
|
||||
};
|
||||
createConfig(tree, opts);
|
||||
expect(tree.read('root/rspack.config.ts', 'utf-8')).toMatchInlineSnapshot(`
|
||||
"
|
||||
import { createConfig }from '@nx/angular-rspack';
|
||||
|
||||
|
||||
export default createConfig({
|
||||
options: {
|
||||
root: __dirname,
|
||||
|
||||
"index": "src/index.html",
|
||||
"browser": "src/main.ts",
|
||||
"tsconfigPath": "tsconfig.app.json",
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
"assets": [
|
||||
"public"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": [],
|
||||
"jit": false,
|
||||
"inlineStylesExtension": "css",
|
||||
"fileReplacements": [],
|
||||
"hasServer": false,
|
||||
"skipTypeChecking": false
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should create a config file with configurations', () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
const opts = {
|
||||
root: 'root',
|
||||
index: 'src/index.html',
|
||||
browser: 'src/main.ts',
|
||||
};
|
||||
const configurationOptions = {
|
||||
production: {
|
||||
index: 'src/index.prod.html',
|
||||
browser: 'src/main.prod.ts',
|
||||
},
|
||||
};
|
||||
createConfig(tree, opts, configurationOptions);
|
||||
expect(tree.read('root/rspack.config.ts', 'utf-8')).toMatchInlineSnapshot(`
|
||||
"
|
||||
import { createConfig }from '@nx/angular-rspack';
|
||||
|
||||
|
||||
export default createConfig({
|
||||
options: {
|
||||
root: __dirname,
|
||||
|
||||
"index": "src/index.html",
|
||||
"browser": "src/main.ts"
|
||||
|
||||
}
|
||||
}, {
|
||||
production: {
|
||||
options: {
|
||||
|
||||
"index": "src/index.prod.html",
|
||||
"browser": "src/main.prod.ts"
|
||||
|
||||
}
|
||||
}});
|
||||
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,62 @@
|
||||
import { joinPathFragments, type Tree } from '@nx/devkit';
|
||||
|
||||
export function createConfig(
|
||||
tree: Tree,
|
||||
opts: Record<string, any>,
|
||||
configurationOptions: Record<string, Record<string, any>> = {},
|
||||
existingWebpackConfigPath?: string,
|
||||
isExistingWebpackConfigFunction?: boolean
|
||||
) {
|
||||
const { root, ...createConfigOptions } = opts;
|
||||
const hasConfigurations = Object.keys(configurationOptions).length > 0;
|
||||
const expandedConfigurationOptions = hasConfigurations
|
||||
? Object.entries(configurationOptions)
|
||||
.map(([configurationName, configurationOptions]) => {
|
||||
return `
|
||||
${configurationName}: {
|
||||
options: {
|
||||
${JSON.stringify(configurationOptions, undefined, 2).slice(1, -1)}
|
||||
}
|
||||
}`;
|
||||
})
|
||||
.join(',\n')
|
||||
: '';
|
||||
const configContents = `
|
||||
import { createConfig }from '@nx/angular-rspack';
|
||||
${
|
||||
existingWebpackConfigPath
|
||||
? `import baseWebpackConfig from '${existingWebpackConfigPath}';
|
||||
${
|
||||
isExistingWebpackConfigFunction
|
||||
? ''
|
||||
: `import webpackMerge from 'webpack-merge';`
|
||||
}`
|
||||
: ''
|
||||
}
|
||||
|
||||
${
|
||||
existingWebpackConfigPath ? 'const baseConfig = ' : 'export default '
|
||||
}createConfig({
|
||||
options: {
|
||||
root: __dirname,
|
||||
${JSON.stringify(createConfigOptions, undefined, 2).slice(1, -1)}
|
||||
}
|
||||
}${hasConfigurations ? `, {${expandedConfigurationOptions}}` : ''});
|
||||
${
|
||||
existingWebpackConfigPath
|
||||
? `
|
||||
export default ${
|
||||
isExistingWebpackConfigFunction
|
||||
? `async function (env, argv) {
|
||||
const oldConfig = await baseWebpackConfig;
|
||||
const browserConfig = baseConfig[0];
|
||||
return oldConfig(browserConfig);
|
||||
}`
|
||||
: 'webpackMerge(baseConfig[0], baseWebpackConfig);'
|
||||
}
|
||||
`
|
||||
: ''
|
||||
}
|
||||
`;
|
||||
tree.write(joinPathFragments(root, 'rspack.config.ts'), configContents);
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
import { convertWebpackConfigToUseNxModuleFederationPlugin } from './get-custom-webpack-config';
|
||||
|
||||
describe('convertconvertWebpackConfigToUseNxModuleFederationPlugin', () => {
|
||||
it('should convert a basic webpack config to use Nx Module Federation Plugin', () => {
|
||||
// ARRANGE
|
||||
const webpackConfigContents = `
|
||||
import { withModuleFederation } from '@nx/module-federation/angular';
|
||||
import config from './module-federation.config';
|
||||
export default withModuleFederation(config, { dts: false });
|
||||
`;
|
||||
|
||||
// ACT
|
||||
const newWebpackConfigContents =
|
||||
convertWebpackConfigToUseNxModuleFederationPlugin(webpackConfigContents);
|
||||
|
||||
// ASSERT
|
||||
expect(newWebpackConfigContents).toMatchInlineSnapshot(`
|
||||
"
|
||||
import { NxModuleFederationPlugin } from '@nx/module-federation/rspack';
|
||||
import config from './module-federation.config';
|
||||
|
||||
|
||||
export default {
|
||||
plugins: [
|
||||
new NxModuleFederationPlugin(config, {
|
||||
dts: false,
|
||||
}),
|
||||
]
|
||||
}
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should convert a basic cjs webpack config to use Nx Module Federation Plugin', () => {
|
||||
// ARRANGE
|
||||
const webpackConfigContents = `
|
||||
const { withModuleFederation } = require('@nx/module-federation/angular');
|
||||
const config = require('./module-federation.config');
|
||||
module.exports = withModuleFederation(config, { dts: false });
|
||||
`;
|
||||
|
||||
// ACT
|
||||
const newWebpackConfigContents =
|
||||
convertWebpackConfigToUseNxModuleFederationPlugin(webpackConfigContents);
|
||||
|
||||
// ASSERT
|
||||
expect(newWebpackConfigContents).toMatchInlineSnapshot(`
|
||||
"
|
||||
const { NxModuleFederationPlugin } = require('@nx/module-federation/rspack');
|
||||
const config = require('./module-federation.config');
|
||||
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
new NxModuleFederationPlugin({ config }, {
|
||||
dts: false,
|
||||
}),
|
||||
]
|
||||
}
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,135 @@
|
||||
import { logger, Tree } from '@nx/devkit';
|
||||
import { loadConfigFile } from '@nx/devkit/src/utils/config-utils';
|
||||
import { join, relative } from 'path';
|
||||
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||
|
||||
const FILE_EXTENSION_REGEX = /\.[^.]+$/;
|
||||
|
||||
export async function getCustomWebpackConfig(
|
||||
tree: Tree,
|
||||
projectRoot: string,
|
||||
pathToCustomWebpackConfig: string
|
||||
) {
|
||||
const webpackConfigContents = tree.read(pathToCustomWebpackConfig, 'utf-8');
|
||||
if (
|
||||
webpackConfigContents.includes('@nx/module-federation/angular') &&
|
||||
webpackConfigContents.includes('withModuleFederation')
|
||||
) {
|
||||
tree.write(
|
||||
pathToCustomWebpackConfig,
|
||||
convertWebpackConfigToUseNxModuleFederationPlugin(webpackConfigContents)
|
||||
);
|
||||
return {
|
||||
isWebpackConfigFunction: false,
|
||||
normalizedPathToCustomWebpackConfig: `./${relative(
|
||||
projectRoot,
|
||||
pathToCustomWebpackConfig
|
||||
).replace(FILE_EXTENSION_REGEX, '')}`,
|
||||
};
|
||||
}
|
||||
const configFile = await loadConfigFile(
|
||||
join(tree.root, pathToCustomWebpackConfig)
|
||||
);
|
||||
const webpackConfig =
|
||||
'default' in configFile ? configFile.default : configFile;
|
||||
return {
|
||||
isWebpackConfigFunction: typeof webpackConfig === 'function',
|
||||
normalizedPathToCustomWebpackConfig: `./${relative(
|
||||
projectRoot,
|
||||
pathToCustomWebpackConfig
|
||||
).replace(FILE_EXTENSION_REGEX, '')}`,
|
||||
};
|
||||
}
|
||||
|
||||
export function convertWebpackConfigToUseNxModuleFederationPlugin(
|
||||
webpackConfigContents: string
|
||||
): string {
|
||||
let newWebpackConfigContents = webpackConfigContents;
|
||||
let ast = tsquery.ast(webpackConfigContents);
|
||||
|
||||
const withModuleFederationImportNodes = tsquery(
|
||||
ast,
|
||||
'ImportDeclaration:has(StringLiteral[value=@nx/module-federation/angular])'
|
||||
);
|
||||
if (withModuleFederationImportNodes.length > 0) {
|
||||
const withModuleFederationImportNode = withModuleFederationImportNodes[0];
|
||||
newWebpackConfigContents = `${webpackConfigContents.slice(
|
||||
0,
|
||||
withModuleFederationImportNode.getStart()
|
||||
)}import { NxModuleFederationPlugin } from '@nx/module-federation/rspack';${webpackConfigContents.slice(
|
||||
withModuleFederationImportNode.getEnd()
|
||||
)}`;
|
||||
|
||||
ast = tsquery.ast(newWebpackConfigContents);
|
||||
const exportedWithModuleFederationNodes = tsquery(
|
||||
ast,
|
||||
'ExportAssignment:has(CallExpression > Identifier[name=withModuleFederation])'
|
||||
);
|
||||
if (exportedWithModuleFederationNodes.length > 0) {
|
||||
const exportedWithModuleFederationNode =
|
||||
exportedWithModuleFederationNodes[0];
|
||||
newWebpackConfigContents = `${newWebpackConfigContents.slice(
|
||||
0,
|
||||
exportedWithModuleFederationNode.getStart()
|
||||
)}${newWebpackConfigContents.slice(
|
||||
exportedWithModuleFederationNode.getEnd()
|
||||
)}
|
||||
export default {
|
||||
plugins: [
|
||||
new NxModuleFederationPlugin(config, {
|
||||
dts: false,
|
||||
}),
|
||||
]
|
||||
}
|
||||
`;
|
||||
} else {
|
||||
logger.warn(
|
||||
"Could not find 'export default withModuleFederation' in the webpack config file. Skipping conversion."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const withModuleFederationRequireNodes = tsquery(
|
||||
ast,
|
||||
'VariableStatement:has(CallExpression > Identifier[name=withModuleFederation], StringLiteral[value=@nx/module-federation/angular])'
|
||||
);
|
||||
if (withModuleFederationRequireNodes.length > 0) {
|
||||
const withModuleFederationRequireNode = withModuleFederationRequireNodes[0];
|
||||
newWebpackConfigContents = `${webpackConfigContents.slice(
|
||||
0,
|
||||
withModuleFederationRequireNode.getStart()
|
||||
)}const { NxModuleFederationPlugin } = require('@nx/module-federation/rspack');${webpackConfigContents.slice(
|
||||
withModuleFederationRequireNode.getEnd()
|
||||
)}`;
|
||||
|
||||
ast = tsquery.ast(newWebpackConfigContents);
|
||||
const exportedWithModuleFederationNodes = tsquery(
|
||||
ast,
|
||||
'ExpressionStatement:has(BinaryExpression > PropertyAccessExpression:has(Identifier[name=module], Identifier[name=exports]), CallExpression:has(Identifier[name=withModuleFederation]))'
|
||||
);
|
||||
if (exportedWithModuleFederationNodes.length > 0) {
|
||||
const exportedWithModuleFederationNode =
|
||||
exportedWithModuleFederationNodes[0];
|
||||
newWebpackConfigContents = `${newWebpackConfigContents.slice(
|
||||
0,
|
||||
exportedWithModuleFederationNode.getStart()
|
||||
)}${newWebpackConfigContents.slice(
|
||||
exportedWithModuleFederationNode.getEnd()
|
||||
)}
|
||||
module.exports = {
|
||||
plugins: [
|
||||
new NxModuleFederationPlugin({ config }, {
|
||||
dts: false,
|
||||
}),
|
||||
]
|
||||
}
|
||||
`;
|
||||
} else {
|
||||
logger.warn(
|
||||
"Could not find 'module.exports = withModuleFederation' in the webpack config file. Skipping conversion."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return newWebpackConfigContents;
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
import { joinPathFragments, readJson, Tree, writeJson } from '@nx/devkit';
|
||||
|
||||
export function updateTsconfig(tree: Tree, projectRoot: string) {
|
||||
const tsconfigPath = joinPathFragments(projectRoot, 'tsconfig.json');
|
||||
const tsconfig = readJson(tree, tsconfigPath);
|
||||
tsconfig['ts-node'] = {
|
||||
compilerOptions: {
|
||||
module: 'CommonJS',
|
||||
},
|
||||
};
|
||||
writeJson(tree, tsconfigPath, tsconfig);
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
import { TargetConfiguration } from '@nx/devkit';
|
||||
|
||||
const SUPPORTED_BUILD_EXECUTORS = [
|
||||
'@angular-devkit/build-angular:browser',
|
||||
'@nx/angular:webpack-browser',
|
||||
];
|
||||
|
||||
export function validateSupportedBuildExecutor(targets: TargetConfiguration[]) {
|
||||
const executorsUsedByProject = targets.map((target) => target.executor);
|
||||
if (
|
||||
!executorsUsedByProject.some((executor) =>
|
||||
SUPPORTED_BUILD_EXECUTORS.includes(executor)
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
'The project does not use a supported build executor. Please use one of the following executors: ' +
|
||||
SUPPORTED_BUILD_EXECUTORS.join(', ')
|
||||
);
|
||||
}
|
||||
}
|
||||
5
packages/angular/src/generators/convert-to-rspack/schema.d.ts
vendored
Normal file
5
packages/angular/src/generators/convert-to-rspack/schema.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
export interface ConvertToRspackSchema {
|
||||
project: string;
|
||||
skipFormat?: boolean;
|
||||
skipInstall?: boolean;
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/schema",
|
||||
"$id": "GeneratorNxApp",
|
||||
"title": "Creates an Angular application.",
|
||||
"description": "Creates an Angular application.",
|
||||
"type": "object",
|
||||
"cli": "nx",
|
||||
"properties": {
|
||||
"project": {
|
||||
"type": "string",
|
||||
"aliases": ["name", "projectName"],
|
||||
"description": "Project for which to convert to rspack.",
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
},
|
||||
"x-priority": "important"
|
||||
},
|
||||
"skipFormat": {
|
||||
"description": "Skip formatting files.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"skipInstall": {
|
||||
"description": "Skip installing dependencies.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -28,6 +28,7 @@ export const backwardCompatibleVersions: VersionMap = {
|
||||
angularVersion: '~17.3.0',
|
||||
angularDevkitVersion: '~17.3.0',
|
||||
ngPackagrVersion: '~17.3.0',
|
||||
angularRspackVersion: '~20.6.1',
|
||||
ngrxVersion: '~17.0.0',
|
||||
rxjsVersion: '~7.8.0',
|
||||
zoneJsVersion: '~0.14.3',
|
||||
@ -55,6 +56,7 @@ export const backwardCompatibleVersions: VersionMap = {
|
||||
angularVersion: '~18.2.0',
|
||||
angularDevkitVersion: '~18.2.0',
|
||||
ngPackagrVersion: '~18.2.0',
|
||||
angularRspackVersion: '~20.6.1',
|
||||
ngrxVersion: '~18.0.2',
|
||||
rxjsVersion: '~7.8.0',
|
||||
zoneJsVersion: '~0.14.3',
|
||||
|
||||
@ -3,6 +3,7 @@ export const nxVersion = require('../../package.json').version;
|
||||
export const angularVersion = '~19.2.0';
|
||||
export const angularDevkitVersion = '~19.2.0';
|
||||
export const ngPackagrVersion = '~19.2.0';
|
||||
export const angularRspackVersion = '~20.6.1';
|
||||
export const ngrxVersion = '^19.0.0';
|
||||
export const rxjsVersion = '~7.8.0';
|
||||
export const zoneJsVersion = '~0.15.0';
|
||||
|
||||
35
pnpm-lock.yaml
generated
35
pnpm-lock.yaml
generated
@ -171,7 +171,7 @@ importers:
|
||||
version: 0.1902.0(chokidar@3.6.0)
|
||||
'@angular-devkit/build-angular':
|
||||
specifier: 19.2.0
|
||||
version: 19.2.0(rggnxzdz2sowtz4b5zhppsknj4)
|
||||
version: 19.2.0(35iavymh3xczxvipjxbozhxvvq)
|
||||
'@angular-devkit/core':
|
||||
specifier: 19.2.0
|
||||
version: 19.2.0(chokidar@3.6.0)
|
||||
@ -297,7 +297,7 @@ importers:
|
||||
version: 3.13.2(rollup@4.22.0)(webpack-sources@3.2.3)
|
||||
'@nx/angular':
|
||||
specifier: 20.5.0-rc.4
|
||||
version: 20.5.0-rc.4(6bzzb6mump3mw6fvztrlsbt2rq)
|
||||
version: 20.5.0-rc.4(gup3gk2idig5qjq722krlimspe)
|
||||
'@nx/conformance':
|
||||
specifier: 1.3.0-beta.7
|
||||
version: 1.3.0-beta.7(@nx/js@20.5.0-rc.4(@babel/traverse@7.26.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(nx@20.5.0-rc.4(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0)))(nx@20.5.0-rc.4(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))
|
||||
@ -1384,6 +1384,17 @@ packages:
|
||||
'@angular/platform-browser': 19.2.0
|
||||
rxjs: ^6.5.3 || ^7.4.0
|
||||
|
||||
'@angular/ssr@19.2.0':
|
||||
resolution: {integrity: sha512-2Tnunv0FDMxf5KHpxnxHhxQ5Xd+4qZ+fGBBbvH2njO5tDdFab6QxTRpKH9xWNiLjAqVIarytmTsuYtpchRmnaw==}
|
||||
peerDependencies:
|
||||
'@angular/common': ^19.0.0 || ^19.2.0-next.0
|
||||
'@angular/core': ^19.0.0 || ^19.2.0-next.0
|
||||
'@angular/platform-server': ^19.0.0 || ^19.2.0-next.0
|
||||
'@angular/router': ^19.0.0 || ^19.2.0-next.0
|
||||
peerDependenciesMeta:
|
||||
'@angular/platform-server':
|
||||
optional: true
|
||||
|
||||
'@antfu/utils@0.7.10':
|
||||
resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==}
|
||||
|
||||
@ -18457,13 +18468,13 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- chokidar
|
||||
|
||||
'@angular-devkit/build-angular@19.2.0(rggnxzdz2sowtz4b5zhppsknj4)':
|
||||
'@angular-devkit/build-angular@19.2.0(35iavymh3xczxvipjxbozhxvvq)':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@angular-devkit/architect': 0.1902.0(chokidar@3.6.0)
|
||||
'@angular-devkit/build-webpack': 0.1902.0(chokidar@3.6.0)(webpack-dev-server@5.2.0(bufferutil@4.0.7)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0))))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)))
|
||||
'@angular-devkit/core': 19.2.0(chokidar@3.6.0)
|
||||
'@angular/build': 19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.7.3))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(@types/node@20.16.10)(chokidar@3.6.0)(jiti@1.21.6)(less@4.2.2)(ng-packagr@19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.7.3))(tailwindcss@3.4.4(ts-node@10.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(typescript@5.7.3)))(tslib@2.7.0)(typescript@5.7.3))(postcss@8.5.2)(sass-embedded@1.85.1)(stylus@0.64.0)(tailwindcss@3.4.4(ts-node@10.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(typescript@5.7.3)))(terser@5.39.0)(typescript@5.7.3)(yaml@2.6.1)
|
||||
'@angular/build': 19.2.0(cxjrqumqlcnastohrhciqzxvse)
|
||||
'@angular/compiler-cli': 19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.7.3)
|
||||
'@babel/core': 7.26.9
|
||||
'@babel/generator': 7.26.9
|
||||
@ -18517,6 +18528,7 @@ snapshots:
|
||||
webpack-merge: 6.0.1
|
||||
webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.5.0(webpack@5.88.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4)))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.88.0)))
|
||||
optionalDependencies:
|
||||
'@angular/ssr': 19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(@angular/router@19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(@angular/platform-browser@18.2.5(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(rxjs@7.8.1))
|
||||
esbuild: 0.25.0
|
||||
jest: 29.7.0(@types/node@20.16.10)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(typescript@5.7.3))
|
||||
jest-environment-jsdom: 29.7.0(bufferutil@4.0.7)
|
||||
@ -18687,7 +18699,7 @@ snapshots:
|
||||
eslint: 8.57.0
|
||||
typescript: 5.7.3
|
||||
|
||||
'@angular/build@19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.7.3))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(@types/node@20.16.10)(chokidar@3.6.0)(jiti@1.21.6)(less@4.2.2)(ng-packagr@19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.7.3))(tailwindcss@3.4.4(ts-node@10.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(typescript@5.7.3)))(tslib@2.7.0)(typescript@5.7.3))(postcss@8.5.2)(sass-embedded@1.85.1)(stylus@0.64.0)(tailwindcss@3.4.4(ts-node@10.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(typescript@5.7.3)))(terser@5.39.0)(typescript@5.7.3)(yaml@2.6.1)':
|
||||
'@angular/build@19.2.0(cxjrqumqlcnastohrhciqzxvse)':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@angular-devkit/architect': 0.1902.0(chokidar@3.6.0)
|
||||
@ -18719,6 +18731,7 @@ snapshots:
|
||||
vite: 6.1.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.2.2)(sass-embedded@1.85.1)(sass@1.85.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1)
|
||||
watchpack: 2.4.2
|
||||
optionalDependencies:
|
||||
'@angular/ssr': 19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(@angular/router@19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(@angular/platform-browser@18.2.5(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(rxjs@7.8.1))
|
||||
less: 4.2.2
|
||||
lmdb: 3.2.6
|
||||
ng-packagr: 19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.7.3))(tailwindcss@3.4.4(ts-node@10.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(typescript@5.7.3)))(tslib@2.7.0)(typescript@5.7.3)
|
||||
@ -18809,6 +18822,14 @@ snapshots:
|
||||
rxjs: 7.8.1
|
||||
tslib: 2.8.1
|
||||
|
||||
'@angular/ssr@19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(@angular/router@19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(@angular/platform-browser@18.2.5(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(rxjs@7.8.1))':
|
||||
dependencies:
|
||||
'@angular/common': 19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1)
|
||||
'@angular/core': 19.2.0(rxjs@7.8.1)(zone.js@0.14.10)
|
||||
'@angular/router': 19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(@angular/platform-browser@18.2.5(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(rxjs@7.8.1)
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@antfu/utils@0.7.10': {}
|
||||
|
||||
'@apidevtools/json-schema-ref-parser@9.0.9':
|
||||
@ -23434,9 +23455,9 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
'@nx/angular@20.5.0-rc.4(6bzzb6mump3mw6fvztrlsbt2rq)':
|
||||
'@nx/angular@20.5.0-rc.4(gup3gk2idig5qjq722krlimspe)':
|
||||
dependencies:
|
||||
'@angular-devkit/build-angular': 19.2.0(rggnxzdz2sowtz4b5zhppsknj4)
|
||||
'@angular-devkit/build-angular': 19.2.0(35iavymh3xczxvipjxbozhxvvq)
|
||||
'@angular-devkit/core': 19.2.0(chokidar@3.6.0)
|
||||
'@angular-devkit/schematics': 19.2.0(chokidar@3.6.0)
|
||||
'@nx/devkit': 20.5.0-rc.4(nx@20.5.0-rc.4(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user