diff --git a/docs/generated/manifests/menus.json b/docs/generated/manifests/menus.json index b114e01b36..d76e3d16ff 100644 --- a/docs/generated/manifests/menus.json +++ b/docs/generated/manifests/menus.json @@ -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", diff --git a/docs/generated/manifests/nx-api.json b/docs/generated/manifests/nx-api.json index 69487e9209..c7246050cf 100644 --- a/docs/generated/manifests/nx-api.json +++ b/docs/generated/manifests/nx-api.json @@ -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", diff --git a/docs/generated/packages-metadata.json b/docs/generated/packages-metadata.json index c547025b94..a9001979b5 100644 --- a/docs/generated/packages-metadata.json +++ b/docs/generated/packages-metadata.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", diff --git a/docs/generated/packages/angular/generators/convert-to-rspack.json b/docs/generated/packages/angular/generators/convert-to-rspack.json new file mode 100644 index 0000000000..a89cb80cfa --- /dev/null +++ b/docs/generated/packages/angular/generators/convert-to-rspack.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" +} diff --git a/docs/shared/reference/sitemap.md b/docs/shared/reference/sitemap.md index 4875974103..8e92061260 100644 --- a/docs/shared/reference/sitemap.md +++ b/docs/shared/reference/sitemap.md @@ -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) diff --git a/e2e/angular/src/misc.test.ts b/e2e/angular/src/misc.test.ts index 312ee03570..9f069a0347 100644 --- a/e2e/angular/src/misc.test.ts +++ b/e2e/angular/src/misc.test.ts @@ -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'); + }); +}); diff --git a/packages/angular/generators.json b/packages/angular/generators.json index edbde17e92..93e0838618 100644 --- a/packages/angular/generators.json +++ b/packages/angular/generators.json @@ -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", diff --git a/packages/angular/package.json b/packages/angular/package.json index e91d7c6571..69bfedddc6 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.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", diff --git a/packages/angular/src/generators/convert-to-rspack/convert-to-rspack.spec.ts b/packages/angular/src/generators/convert-to-rspack/convert-to-rspack.spec.ts new file mode 100644 index 0000000000..5bed973fbc --- /dev/null +++ b/packages/angular/src/generators/convert-to-rspack/convert-to-rspack.spec.ts @@ -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); + " + `); + }); +}); diff --git a/packages/angular/src/generators/convert-to-rspack/convert-to-rspack.ts b/packages/angular/src/generators/convert-to-rspack/convert-to-rspack.ts new file mode 100644 index 0000000000..b238afba49 --- /dev/null +++ b/packages/angular/src/generators/convert-to-rspack/convert-to-rspack.ts @@ -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, + 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, + newConfigurationOptions: Record, + 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, + newConfigurationOptions: Record, + 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(); + 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 = { + root: project.root, + }; + const configurationOptions: Record> = {}; + 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( + '@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; diff --git a/packages/angular/src/generators/convert-to-rspack/lib/create-config.spec.ts b/packages/angular/src/generators/convert-to-rspack/lib/create-config.spec.ts new file mode 100644 index 0000000000..2a710d4c49 --- /dev/null +++ b/packages/angular/src/generators/convert-to-rspack/lib/create-config.spec.ts @@ -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" + + } + }}); + + " + `); + }); +}); diff --git a/packages/angular/src/generators/convert-to-rspack/lib/create-config.ts b/packages/angular/src/generators/convert-to-rspack/lib/create-config.ts new file mode 100644 index 0000000000..eb5eedd123 --- /dev/null +++ b/packages/angular/src/generators/convert-to-rspack/lib/create-config.ts @@ -0,0 +1,62 @@ +import { joinPathFragments, type Tree } from '@nx/devkit'; + +export function createConfig( + tree: Tree, + opts: Record, + configurationOptions: Record> = {}, + 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); +} diff --git a/packages/angular/src/generators/convert-to-rspack/lib/get-custom-webpack-config.spec.ts b/packages/angular/src/generators/convert-to-rspack/lib/get-custom-webpack-config.spec.ts new file mode 100644 index 0000000000..e0ecb5a01e --- /dev/null +++ b/packages/angular/src/generators/convert-to-rspack/lib/get-custom-webpack-config.spec.ts @@ -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, + }), + ] + } + " + `); + }); +}); diff --git a/packages/angular/src/generators/convert-to-rspack/lib/get-custom-webpack-config.ts b/packages/angular/src/generators/convert-to-rspack/lib/get-custom-webpack-config.ts new file mode 100644 index 0000000000..08ecbc561b --- /dev/null +++ b/packages/angular/src/generators/convert-to-rspack/lib/get-custom-webpack-config.ts @@ -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; +} diff --git a/packages/angular/src/generators/convert-to-rspack/lib/update-tsconfig.ts b/packages/angular/src/generators/convert-to-rspack/lib/update-tsconfig.ts new file mode 100644 index 0000000000..3f55a325d9 --- /dev/null +++ b/packages/angular/src/generators/convert-to-rspack/lib/update-tsconfig.ts @@ -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); +} diff --git a/packages/angular/src/generators/convert-to-rspack/lib/validate-supported-executor.ts b/packages/angular/src/generators/convert-to-rspack/lib/validate-supported-executor.ts new file mode 100644 index 0000000000..f985d84688 --- /dev/null +++ b/packages/angular/src/generators/convert-to-rspack/lib/validate-supported-executor.ts @@ -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(', ') + ); + } +} diff --git a/packages/angular/src/generators/convert-to-rspack/schema.d.ts b/packages/angular/src/generators/convert-to-rspack/schema.d.ts new file mode 100644 index 0000000000..5d32eac2b3 --- /dev/null +++ b/packages/angular/src/generators/convert-to-rspack/schema.d.ts @@ -0,0 +1,5 @@ +export interface ConvertToRspackSchema { + project: string; + skipFormat?: boolean; + skipInstall?: boolean; +} diff --git a/packages/angular/src/generators/convert-to-rspack/schema.json b/packages/angular/src/generators/convert-to-rspack/schema.json new file mode 100644 index 0000000000..11a0ec333e --- /dev/null +++ b/packages/angular/src/generators/convert-to-rspack/schema.json @@ -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 + } + } +} diff --git a/packages/angular/src/utils/backward-compatible-versions.ts b/packages/angular/src/utils/backward-compatible-versions.ts index 33c32f43fa..16bb4510a0 100644 --- a/packages/angular/src/utils/backward-compatible-versions.ts +++ b/packages/angular/src/utils/backward-compatible-versions.ts @@ -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', diff --git a/packages/angular/src/utils/versions.ts b/packages/angular/src/utils/versions.ts index 4f290096ce..725b924eb9 100644 --- a/packages/angular/src/utils/versions.ts +++ b/packages/angular/src/utils/versions.ts @@ -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'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c700bedbe..ff7c610a86 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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)))