diff --git a/.prettierignore b/.prettierignore
index 0f3bb1a9f9..039a945266 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -51,3 +51,6 @@ CODEOWNERS
/.nx/workflows/dynamic-changesets.yaml
_files
_solution
+
+# this file uses TS import attributes which the current prettier version does not support
+tools/documentation/create-embeddings/src/main.mts
diff --git a/docs/generated/manifests/nx-api.json b/docs/generated/manifests/nx-api.json
index d9013d9d83..5c9409fedb 100644
--- a/docs/generated/manifests/nx-api.json
+++ b/docs/generated/manifests/nx-api.json
@@ -444,6 +444,66 @@
}
},
"migrations": {
+ "/nx-api/angular/migrations/update-angular-cli-version-20-0-0-rc-3": {
+ "description": "Update the @angular/cli package version to 20.0.0-rc.3.",
+ "file": "generated/packages/angular/migrations/update-angular-cli-version-20-0-0-rc-3.json",
+ "hidden": false,
+ "name": "update-angular-cli-version-20-0-0-rc-3",
+ "version": "21.2.0-beta.0",
+ "originalFilePath": "/packages/angular",
+ "path": "/nx-api/angular/migrations/update-angular-cli-version-20-0-0-rc-3",
+ "type": "migration"
+ },
+ "/nx-api/angular/migrations/migrate-provide-server-rendering-import": {
+ "description": "Migrate imports of `provideServerRendering` from `@angular/platform-server` to `@angular/ssr`.",
+ "file": "generated/packages/angular/migrations/migrate-provide-server-rendering-import.json",
+ "hidden": false,
+ "name": "migrate-provide-server-rendering-import",
+ "version": "21.2.0-beta.0",
+ "originalFilePath": "/packages/angular",
+ "path": "/nx-api/angular/migrations/migrate-provide-server-rendering-import",
+ "type": "migration"
+ },
+ "/nx-api/angular/migrations/replace-provide-server-routing": {
+ "description": "Replace `provideServerRouting` with `provideServerRendering` using `withRoutes`.",
+ "file": "generated/packages/angular/migrations/replace-provide-server-routing.json",
+ "hidden": false,
+ "name": "replace-provide-server-routing",
+ "version": "21.2.0-beta.0",
+ "originalFilePath": "/packages/angular",
+ "path": "/nx-api/angular/migrations/replace-provide-server-routing",
+ "type": "migration"
+ },
+ "/nx-api/angular/migrations/set-generator-defaults-for-previous-style-guide": {
+ "description": "Update the generator defaults to maintain the previous style guide behavior.",
+ "file": "generated/packages/angular/migrations/set-generator-defaults-for-previous-style-guide.json",
+ "hidden": false,
+ "name": "set-generator-defaults-for-previous-style-guide",
+ "version": "21.2.0-beta.0",
+ "originalFilePath": "/packages/angular",
+ "path": "/nx-api/angular/migrations/set-generator-defaults-for-previous-style-guide",
+ "type": "migration"
+ },
+ "/nx-api/angular/migrations/update-module-resolution": {
+ "description": "Update 'moduleResolution' to 'bundler' in TypeScript configurations. You can read more about this here: https://www.typescriptlang.org/tsconfig/#moduleResolution.",
+ "file": "generated/packages/angular/migrations/update-module-resolution.json",
+ "hidden": false,
+ "name": "update-module-resolution",
+ "version": "21.2.0-beta.0",
+ "originalFilePath": "/packages/angular",
+ "path": "/nx-api/angular/migrations/update-module-resolution",
+ "type": "migration"
+ },
+ "/nx-api/angular/migrations/21.2.0-package-updates": {
+ "description": "",
+ "file": "generated/packages/angular/migrations/21.2.0-package-updates.json",
+ "hidden": false,
+ "name": "21.2.0-package-updates",
+ "version": "21.2.0-beta.0",
+ "originalFilePath": "/packages/angular",
+ "path": "/nx-api/angular/migrations/21.2.0-package-updates",
+ "type": "migration"
+ },
"/nx-api/angular/migrations/21.1.0-package-updates": {
"description": "",
"file": "generated/packages/angular/migrations/21.1.0-package-updates.json",
@@ -1687,6 +1747,26 @@
}
},
"migrations": {
+ "/nx-api/eslint/migrations/21.2.0-typescript-eslint-package-updates": {
+ "description": "",
+ "file": "generated/packages/eslint/migrations/21.2.0-typescript-eslint-package-updates.json",
+ "hidden": false,
+ "name": "21.2.0-typescript-eslint-package-updates",
+ "version": "21.2.0-beta.0",
+ "originalFilePath": "/packages/eslint",
+ "path": "/nx-api/eslint/migrations/21.2.0-typescript-eslint-package-updates",
+ "type": "migration"
+ },
+ "/nx-api/eslint/migrations/21.2.0-@typescript-eslint-package-updates": {
+ "description": "",
+ "file": "generated/packages/eslint/migrations/21.2.0-@typescript-eslint-package-updates.json",
+ "hidden": false,
+ "name": "21.2.0-@typescript-eslint-package-updates",
+ "version": "21.2.0-beta.0",
+ "originalFilePath": "/packages/eslint",
+ "path": "/nx-api/eslint/migrations/21.2.0-@typescript-eslint-package-updates",
+ "type": "migration"
+ },
"/nx-api/eslint/migrations/20.7.0-package-updates": {
"description": "",
"file": "generated/packages/eslint/migrations/20.7.0-package-updates.json",
@@ -2454,6 +2534,16 @@
}
},
"migrations": {
+ "/nx-api/js/migrations/21.2.0-package-updates": {
+ "description": "",
+ "file": "generated/packages/js/migrations/21.2.0-package-updates.json",
+ "hidden": false,
+ "name": "21.2.0-package-updates",
+ "version": "21.2.0-beta.0",
+ "originalFilePath": "/packages/js",
+ "path": "/nx-api/js/migrations/21.2.0-package-updates",
+ "type": "migration"
+ },
"/nx-api/js/migrations/20.7.1-beta.0-package-updates": {
"description": "",
"file": "generated/packages/js/migrations/20.7.1-beta.0-package-updates.json",
@@ -5724,6 +5814,16 @@
}
},
"migrations": {
+ "/nx-api/workspace/migrations/21.2.0-package-updates": {
+ "description": "",
+ "file": "generated/packages/workspace/migrations/21.2.0-package-updates.json",
+ "hidden": false,
+ "name": "21.2.0-package-updates",
+ "version": "21.2.0-beta.0",
+ "originalFilePath": "/packages/workspace",
+ "path": "/nx-api/workspace/migrations/21.2.0-package-updates",
+ "type": "migration"
+ },
"/nx-api/workspace/migrations/20.4.0-package-updates": {
"description": "",
"file": "generated/packages/workspace/migrations/20.4.0-package-updates.json",
diff --git a/docs/generated/packages-metadata.json b/docs/generated/packages-metadata.json
index abe34799bd..fb1c57d08d 100644
--- a/docs/generated/packages-metadata.json
+++ b/docs/generated/packages-metadata.json
@@ -439,6 +439,66 @@
}
],
"migrations": [
+ {
+ "description": "Update the @angular/cli package version to 20.0.0-rc.3.",
+ "file": "generated/packages/angular/migrations/update-angular-cli-version-20-0-0-rc-3.json",
+ "hidden": false,
+ "name": "update-angular-cli-version-20-0-0-rc-3",
+ "version": "21.2.0-beta.0",
+ "originalFilePath": "/packages/angular",
+ "path": "angular/migrations/update-angular-cli-version-20-0-0-rc-3",
+ "type": "migration"
+ },
+ {
+ "description": "Migrate imports of `provideServerRendering` from `@angular/platform-server` to `@angular/ssr`.",
+ "file": "generated/packages/angular/migrations/migrate-provide-server-rendering-import.json",
+ "hidden": false,
+ "name": "migrate-provide-server-rendering-import",
+ "version": "21.2.0-beta.0",
+ "originalFilePath": "/packages/angular",
+ "path": "angular/migrations/migrate-provide-server-rendering-import",
+ "type": "migration"
+ },
+ {
+ "description": "Replace `provideServerRouting` with `provideServerRendering` using `withRoutes`.",
+ "file": "generated/packages/angular/migrations/replace-provide-server-routing.json",
+ "hidden": false,
+ "name": "replace-provide-server-routing",
+ "version": "21.2.0-beta.0",
+ "originalFilePath": "/packages/angular",
+ "path": "angular/migrations/replace-provide-server-routing",
+ "type": "migration"
+ },
+ {
+ "description": "Update the generator defaults to maintain the previous style guide behavior.",
+ "file": "generated/packages/angular/migrations/set-generator-defaults-for-previous-style-guide.json",
+ "hidden": false,
+ "name": "set-generator-defaults-for-previous-style-guide",
+ "version": "21.2.0-beta.0",
+ "originalFilePath": "/packages/angular",
+ "path": "angular/migrations/set-generator-defaults-for-previous-style-guide",
+ "type": "migration"
+ },
+ {
+ "description": "Update 'moduleResolution' to 'bundler' in TypeScript configurations. You can read more about this here: https://www.typescriptlang.org/tsconfig/#moduleResolution.",
+ "file": "generated/packages/angular/migrations/update-module-resolution.json",
+ "hidden": false,
+ "name": "update-module-resolution",
+ "version": "21.2.0-beta.0",
+ "originalFilePath": "/packages/angular",
+ "path": "angular/migrations/update-module-resolution",
+ "type": "migration"
+ },
+ {
+ "description": "",
+ "file": "generated/packages/angular/migrations/21.2.0-package-updates.json",
+ "hidden": false,
+ "name": "21.2.0-package-updates",
+ "version": "21.2.0-beta.0",
+ "originalFilePath": "/packages/angular",
+ "path": "angular/migrations/21.2.0-package-updates",
+ "type": "migration"
+ },
{
"description": "",
"file": "generated/packages/angular/migrations/21.1.0-package-updates.json",
@@ -1675,6 +1735,26 @@
}
],
"migrations": [
+ {
+ "description": "",
+ "file": "generated/packages/eslint/migrations/21.2.0-typescript-eslint-package-updates.json",
+ "hidden": false,
+ "name": "21.2.0-typescript-eslint-package-updates",
+ "version": "21.2.0-beta.0",
+ "originalFilePath": "/packages/eslint",
+ "path": "eslint/migrations/21.2.0-typescript-eslint-package-updates",
+ "type": "migration"
+ },
+ {
+ "description": "",
+ "file": "generated/packages/eslint/migrations/21.2.0-@typescript-eslint-package-updates.json",
+ "hidden": false,
+ "name": "21.2.0-@typescript-eslint-package-updates",
+ "version": "21.2.0-beta.0",
+ "originalFilePath": "/packages/eslint",
+ "path": "eslint/migrations/21.2.0-@typescript-eslint-package-updates",
+ "type": "migration"
+ },
{
"description": "",
"file": "generated/packages/eslint/migrations/20.7.0-package-updates.json",
@@ -2436,6 +2516,16 @@
}
],
"migrations": [
+ {
+ "description": "",
+ "file": "generated/packages/js/migrations/21.2.0-package-updates.json",
+ "hidden": false,
+ "name": "21.2.0-package-updates",
+ "version": "21.2.0-beta.0",
+ "originalFilePath": "/packages/js",
+ "path": "js/migrations/21.2.0-package-updates",
+ "type": "migration"
+ },
{
"description": "",
"file": "generated/packages/js/migrations/20.7.1-beta.0-package-updates.json",
@@ -5686,6 +5776,16 @@
}
],
"migrations": [
+ {
+ "description": "",
+ "file": "generated/packages/workspace/migrations/21.2.0-package-updates.json",
+ "hidden": false,
+ "name": "21.2.0-package-updates",
+ "version": "21.2.0-beta.0",
+ "originalFilePath": "/packages/workspace",
+ "path": "workspace/migrations/21.2.0-package-updates",
+ "type": "migration"
+ },
{
"description": "",
"file": "generated/packages/workspace/migrations/20.4.0-package-updates.json",
diff --git a/docs/generated/packages/angular/executors/application.json b/docs/generated/packages/angular/executors/application.json
index 96c3103273..7695776f32 100644
--- a/docs/generated/packages/angular/executors/application.json
+++ b/docs/generated/packages/angular/executors/application.json
@@ -54,7 +54,6 @@
"description": "The full path for the browser entry point to the application, relative to the current workspace."
},
"server": {
- "type": "string",
"description": "The full path for the server entry point to the application, relative to the current workspace.",
"oneOf": [
{
@@ -80,7 +79,7 @@
},
"deployUrl": {
"type": "string",
- "description": "Customize the base path for the URLs of resources in 'index.html' and component stylesheets. This option is only necessary for specific deployment scenarios, such as with Angular Elements or when utilizing different CDN locations. _Note: this is only supported in Angular versions >= 17.3.0_."
+ "description": "Customize the base path for the URLs of resources in 'index.html' and component stylesheets. This option is only necessary for specific deployment scenarios, such as with Angular Elements or when utilizing different CDN locations."
},
"security": {
"description": "Security features to protect against XSS and other common attacks. _Note: this is only supported in Angular versions >= 19.0.0_.",
@@ -226,7 +225,7 @@
"clearScreen": {
"type": "boolean",
"default": false,
- "description": "Automatically clear the terminal screen during rebuilds. _Note: this is only supported in Angular versions >= 17.2.0_."
+ "description": "Automatically clear the terminal screen during rebuilds."
},
"optimization": {
"description": "Enables optimization of the build output. Including minification of scripts and styles, tree-shaking, dead-code elimination, inlining of critical CSS and fonts inlining. For more information, see https://angular.dev/reference/configs/workspace-config#optimization-configuration.",
@@ -259,7 +258,7 @@
},
"removeSpecialComments": {
"type": "boolean",
- "description": "Remove comments in global CSS that contains '@license' or '@preserve' or that starts with '//!' or '/*!'. _Note: this is only supported in Angular versions >= 17.1.0_.",
+ "description": "Remove comments in global CSS that contains '@license' or '@preserve' or that starts with '//!' or '/*!'.",
"default": true
}
},
@@ -293,17 +292,22 @@
]
},
"loader": {
- "description": "Defines the type of loader to use with a specified file extension when used with a JavaScript `import`. `text` inlines the content as a string; `binary` inlines the content as a Uint8Array; `file` emits the file and provides the runtime location of the file; `empty` considers the content to be empty and not include it in bundles. _Note: this is only supported in Angular versions >= 17.1.0_.",
+ "description": "Defines the type of loader to use with a specified file extension when used with a JavaScript `import`. `text` inlines the content as a string; `binary` inlines the content as a Uint8Array; `file` emits the file and provides the runtime location of the file; `empty` considers the content to be empty and not include it in bundles.",
"type": "object",
"patternProperties": {
"^\\.\\S+$": { "enum": ["text", "binary", "file", "empty"] }
}
},
"define": {
- "description": "Defines global identifiers that will be replaced with a specified constant value when found in any JavaScript or TypeScript code including libraries. The value will be used directly. String values must be put in quotes. Identifiers within Angular metadata such as Component Decorators will not be replaced. _Note: this is only supported in Angular versions >= 17.2.0_.",
+ "description": "Defines global identifiers that will be replaced with a specified constant value when found in any JavaScript or TypeScript code including libraries. The value will be used directly. String values must be put in quotes. Identifiers within Angular metadata such as Component Decorators will not be replaced.",
"type": "object",
"additionalProperties": { "type": "string" }
},
+ "conditions": {
+ "description": "Custom package resolution conditions used to resolve conditional exports/imports. Defaults to ['module', 'development'/'production']. The following special conditions are always present if the requirements are satisfied: 'default', 'import', 'require', 'browser', 'node'. _Note: this is only supported in Angular versions >= 20.0.0_.",
+ "type": "array",
+ "items": { "type": "string" }
+ },
"fileReplacements": {
"description": "Replace compilation source files with other compilation source files in the build.",
"type": "array",
@@ -322,7 +326,7 @@
"default": []
},
"outputPath": {
- "description": "Specify the output path relative to workspace root. _Note: the object notation is only supported in Angular versions >= 17.1.0_.",
+ "description": "Specify the output path relative to workspace root.",
"oneOf": [
{
"type": "object",
@@ -387,6 +391,11 @@
"type": "boolean",
"description": "Resolve vendor packages source maps.",
"default": false
+ },
+ "sourcesContent": {
+ "type": "boolean",
+ "description": "Output original source content for files within the source map. _Note: this is only supported in Angular versions >= 20.0.0_.",
+ "default": true
}
},
"additionalProperties": false
@@ -511,7 +520,7 @@
"preloadInitial": {
"type": "boolean",
"default": true,
- "description": "Generates 'preload', 'modulepreload', and 'preconnect' link elements for initial application files and resources. _Note: this is only supported in Angular versions >= 17.1.0_."
+ "description": "Generates 'preload', 'modulepreload', and 'preconnect' link elements for initial application files and resources."
}
},
"required": ["input"]
@@ -694,12 +703,12 @@
}
},
"indexHtmlTransformer": {
- "description": "Path to a file exposing a default function to transform the `index.html` file. _Note: this is only supported in Angular versions >= 17.1.0_.",
+ "description": "Path to a file exposing a default function to transform the `index.html` file.",
"type": "string"
}
},
"additionalProperties": false,
- "required": ["outputPath", "index", "browser", "tsConfig"],
+ "required": ["outputPath", "tsConfig"],
"definitions": {
"assetPattern": {
"oneOf": [
diff --git a/docs/generated/packages/angular/executors/dev-server.json b/docs/generated/packages/angular/executors/dev-server.json
index 838ec08c7e..44ccd0ebe9 100644
--- a/docs/generated/packages/angular/executors/dev-server.json
+++ b/docs/generated/packages/angular/executors/dev-server.json
@@ -8,18 +8,12 @@
"$schema": "http://json-schema.org/draft-07/schema",
"title": "Schema for Webpack Dev Server",
"description": "Serves an Angular application using [webpack](https://webpack.js.org/) when the build target is using a webpack-based executor, or [Vite](https://vitejs.dev/) when the build target uses an [esbuild](https://esbuild.github.io/)-based executor.",
- "examplesFile": "This executor is a drop-in replacement for the `@angular-devkit/build-angular:dev-server` builder provided by the Angular CLI. In addition to the features provided by the Angular CLI builder, the `@nx/angular:dev-server` executor also supports the following:\n\n- Serving applications with Vite when using the `@nx/angular:application` or `@nx/angular:browser-esbuild` executors to build them\n- Serving applications with webpack when using the `@nx/angular:webpack-browser` executor\n- Providing HTTP request middleware functions when the build target is using an esbuild-based executor\n- Incremental builds\n\n## Examples\n\n{% tabs %}\n{% tab label=\"Using a custom webpack configuration\" %}\n\nThis executor should be used along with `@nx/angular:webpack-browser` to serve an application using a custom webpack configuration.\n\nAdd the `serve` target using the `@nx/angular:dev-server` executor, set the `build` target executor as `@nx/angular:webpack-browser` and set the `customWebpackConfig` option as shown below:\n\n```json {% fileName=\"apps/my-app/project.json\" highlightLines=[2,\"5-7\",\"10-20\"] %}\n\"build\": {\n \"executor\": \"@nx/angular:webpack-browser\",\n \"options\": {\n ...\n \"customWebpackConfig\": {\n \"path\": \"apps/my-app/webpack.config.js\"\n }\n }\n},\n\"serve\": {\n \"executor\": \"@nx/angular:dev-server\",\n \"configurations\": {\n \"production\": {\n \"buildTarget\": \"my-app:build:production\"\n },\n \"development\": {\n \"buildTarget\": \"my-app:build:development\"\n }\n },\n \"defaultConfiguration\": \"development\",\n}\n```\n\n```js {% fileName=\"apps/my-app/webpack.config.js\" %}\nmodule.exports = (config) => {\n // update the config with your custom configuration\n\n return config;\n};\n```\n\n{% /tab %}\n\n{% tab label=\"Providing HTTP request middleware function\" %}\n\n{% callout type=\"warning\" title=\"Overrides\" }\n\nAvailable for workspaces using Angular version 17.0.0 or greater and with `build` targets using an esbuild-based executor.\n\n{% /callout %}\n\nThe executor accepts an `esbuildMiddleware` option that allows you to provide HTTP require middleware functions that will be used by the Vite development server.\n\n```json {% fileName=\"apps/my-app/project.json\" highlightLines=[8] %}\n{\n ...\n \"targets\": {\n \"serve\": {\n \"executor\": \"@nx/angular:dev-server\",\n \"options\": {\n ...\n \"esbuildMiddleware\": [\"apps/my-app/hello-world.middleware.ts\"]\n }\n }\n ...\n }\n}\n```\n\n```ts {% fileName=\"apps/my-app/hello-world.middleware.ts\" %}\nimport type { IncomingMessage, ServerResponse } from 'node:http';\n\nconst helloWorldMiddleware = (\n req: IncomingMessage,\n res: ServerResponse,\n next: (err?: unknown) => void\n) => {\n if (req.url === '/hello-world') {\n res.end('
Hello World!
');\n } else {\n next();\n }\n};\n\nexport default helloWorldMiddleware;\n```\n\n{% /tab %}\n",
+ "examplesFile": "This executor is a drop-in replacement for the `@angular-devkit/build-angular:dev-server` builder provided by the Angular CLI. In addition to the features provided by the Angular CLI builder, the `@nx/angular:dev-server` executor also supports the following:\n\n- Serving applications with Vite when using the `@nx/angular:application` or `@nx/angular:browser-esbuild` executors to build them\n- Serving applications with webpack when using the `@nx/angular:webpack-browser` executor\n- Providing HTTP request middleware functions when the build target is using an esbuild-based executor\n- Incremental builds\n\n## Examples\n\n{% tabs %}\n{% tab label=\"Using a custom webpack configuration\" %}\n\nThis executor should be used along with `@nx/angular:webpack-browser` to serve an application using a custom webpack configuration.\n\nAdd the `serve` target using the `@nx/angular:dev-server` executor, set the `build` target executor as `@nx/angular:webpack-browser` and set the `customWebpackConfig` option as shown below:\n\n```json {% fileName=\"apps/my-app/project.json\" highlightLines=[2,\"5-7\",\"10-20\"] %}\n\"build\": {\n \"executor\": \"@nx/angular:webpack-browser\",\n \"options\": {\n ...\n \"customWebpackConfig\": {\n \"path\": \"apps/my-app/webpack.config.js\"\n }\n }\n},\n\"serve\": {\n \"executor\": \"@nx/angular:dev-server\",\n \"configurations\": {\n \"production\": {\n \"buildTarget\": \"my-app:build:production\"\n },\n \"development\": {\n \"buildTarget\": \"my-app:build:development\"\n }\n },\n \"defaultConfiguration\": \"development\",\n}\n```\n\n```js {% fileName=\"apps/my-app/webpack.config.js\" %}\nmodule.exports = (config) => {\n // update the config with your custom configuration\n\n return config;\n};\n```\n\n{% /tab %}\n\n{% tab label=\"Providing HTTP request middleware function\" %}\n\nThe executor accepts an `esbuildMiddleware` option that allows you to provide HTTP require middleware functions that will be used by the Vite development server.\n\n```json {% fileName=\"apps/my-app/project.json\" highlightLines=[8] %}\n{\n ...\n \"targets\": {\n \"serve\": {\n \"executor\": \"@nx/angular:dev-server\",\n \"options\": {\n ...\n \"esbuildMiddleware\": [\"apps/my-app/hello-world.middleware.ts\"]\n }\n }\n ...\n }\n}\n```\n\n```ts {% fileName=\"apps/my-app/hello-world.middleware.ts\" %}\nimport type { IncomingMessage, ServerResponse } from 'node:http';\n\nconst helloWorldMiddleware = (\n req: IncomingMessage,\n res: ServerResponse,\n next: (err?: unknown) => void\n) => {\n if (req.url === '/hello-world') {\n res.end('
Hello World!
');\n } else {\n next();\n }\n};\n\nexport default helloWorldMiddleware;\n```\n\n{% /tab %}\n",
"type": "object",
"presets": [
{ "name": "Using a Different Port", "keys": ["buildTarget", "port"] }
],
"properties": {
- "browserTarget": {
- "type": "string",
- "description": "A browser builder target to serve in the format of `project:target[:configuration]`. Ignored if `buildTarget` is set.",
- "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$",
- "x-deprecated": "Use 'buildTarget' instead. It will be removed when Angular v20 is released."
- },
"buildTarget": {
"type": "string",
"description": "A build builder target to serve in the format of `project:target[:configuration]`.",
@@ -122,7 +116,7 @@
]
},
"prebundle": {
- "description": "Enable and control the Vite-based development server's prebundling capabilities. To enable prebundling, the Angular CLI cache must also be enabled. This option has no effect when using the 'browser' or other Webpack-based builders. _Note: this is only supported in Angular versions >= 17.2.0_.",
+ "description": "Enable and control the Vite-based development server's prebundling capabilities. To enable prebundling, the Angular CLI cache must also be enabled. This option has no effect when using the 'browser' or other Webpack-based builders.",
"oneOf": [
{ "type": "boolean" },
{
@@ -141,7 +135,7 @@
},
"buildLibsFromSource": {
"type": "boolean",
- "description": "Read buildable libraries from source instead of building them separately. If not set, it will take the value specified in the `browserTarget` options, or it will default to `true` if it's also not set in the `browserTarget` options.",
+ "description": "Read buildable libraries from source instead of building them separately. If not set, it will take the value specified in the `buildTarget` options, or it will default to `true` if it's also not set in the `buildTarget` options.",
"x-priority": "important"
},
"esbuildMiddleware": {
@@ -159,10 +153,7 @@
}
},
"additionalProperties": false,
- "anyOf": [
- { "required": ["buildTarget"] },
- { "required": ["browserTarget"] }
- ]
+ "required": ["buildTarget"]
},
"description": "Serves an Angular application using [webpack](https://webpack.js.org/) when the build target is using a webpack-based executor, or [Vite](https://vitejs.dev/) when the build target uses an [esbuild](https://esbuild.github.io/)-based executor.",
"aliases": [],
diff --git a/docs/generated/packages/angular/executors/extract-i18n.json b/docs/generated/packages/angular/executors/extract-i18n.json
index 3a607bbbfd..f7b8115d8c 100644
--- a/docs/generated/packages/angular/executors/extract-i18n.json
+++ b/docs/generated/packages/angular/executors/extract-i18n.json
@@ -41,6 +41,11 @@
"outFile": {
"type": "string",
"description": "Name of the file to output."
+ },
+ "i18nDuplicateTranslation": {
+ "type": "string",
+ "description": "How to handle duplicate translations. _Note: this is only available in Angular 20.0.0 and above._",
+ "enum": ["error", "warning", "ignore"]
}
},
"additionalProperties": false,
diff --git a/docs/generated/packages/angular/executors/module-federation-dev-server.json b/docs/generated/packages/angular/executors/module-federation-dev-server.json
index d27cfdf967..c9cc557f34 100644
--- a/docs/generated/packages/angular/executors/module-federation-dev-server.json
+++ b/docs/generated/packages/angular/executors/module-federation-dev-server.json
@@ -12,12 +12,6 @@
{ "name": "Using a Different Port", "keys": ["buildTarget", "port"] }
],
"properties": {
- "browserTarget": {
- "type": "string",
- "description": "A browser builder target to serve in the format of `project:target[:configuration]`.",
- "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$",
- "x-deprecated": "Use 'buildTarget' instead. It will be removed when Angular v20 is released."
- },
"buildTarget": {
"type": "string",
"description": "A build builder target to serve in the format of `project:target[:configuration]`.",
@@ -152,15 +146,12 @@
},
"buildLibsFromSource": {
"type": "boolean",
- "description": "Read buildable libraries from source instead of building them separately. If not set, it will take the value specified in the `browserTarget` options, or it will default to `true` if it's also not set in the `browserTarget` options.",
+ "description": "Read buildable libraries from source instead of building them separately. If not set, it will take the value specified in the `buildTarget` options, or it will default to `true` if it's also not set in the `buildTarget` options.",
"x-priority": "important"
}
},
"additionalProperties": false,
- "anyOf": [
- { "required": ["buildTarget"] },
- { "required": ["browserTarget"] }
- ],
+ "required": ["buildTarget"],
"examplesFile": "## Examples\n\n{% tabs %}\n\n{% tab label=\"Basic Usage\" %}\nThe Module Federation Dev Server will serve a host application and find the remote applications associated with the host and serve them statically also. \nSee an example set up of it below:\n\n```json\n{\n \"serve\": {\n \"executor\": \"@nx/angular:module-federation-dev-server\",\n \"configurations\": {\n \"production\": {\n \"buildTarget\": \"host:build:production\"\n },\n \"development\": {\n \"buildTarget\": \"host:build:development\"\n }\n },\n \"defaultConfiguration\": \"development\",\n \"options\": {\n \"port\": 4200,\n \"publicHost\": \"http://localhost:4200\"\n }\n }\n}\n```\n\n{% /tab %}\n\n{% tab label=\"Serve host with remotes that can be live reloaded\" %}\nThe Module Federation Dev Server will serve a host application and find the remote applications associated with the host and serve a set selection with live reloading enabled also. \nSee an example set up of it below:\n\n```json\n{\n \"serve-with-hmr-remotes\": {\n \"executor\": \"@nx/angular:module-federation-dev-server\",\n \"configurations\": {\n \"production\": {\n \"buildTarget\": \"host:build:production\"\n },\n \"development\": {\n \"buildTarget\": \"host:build:development\"\n }\n },\n \"defaultConfiguration\": \"development\",\n \"options\": {\n \"port\": 4200,\n \"publicHost\": \"http://localhost:4200\",\n \"devRemotes\": [\n \"remote1\",\n {\n \"remoteName\": \"remote2\",\n \"configuration\": \"development\"\n }\n ]\n }\n }\n}\n```\n\n{% /tab %}\n\n{% /tabs %}\n"
},
"description": "Serves host [Module Federation](https://module-federation.io/) applications ([webpack](https://webpack.js.org/)-based) allowing to specify which remote applications should be served with the host.",
diff --git a/docs/generated/packages/angular/executors/ng-packagr-lite.json b/docs/generated/packages/angular/executors/ng-packagr-lite.json
index 5afa9203fc..197fc3a075 100644
--- a/docs/generated/packages/angular/executors/ng-packagr-lite.json
+++ b/docs/generated/packages/angular/executors/ng-packagr-lite.json
@@ -34,11 +34,10 @@
},
"poll": {
"type": "number",
- "description": "Enable and define the file watching poll time period in milliseconds. _Note: this is only supported in Angular versions >= 18.0.0_."
+ "description": "Enable and define the file watching poll time period in milliseconds."
}
},
- "additionalProperties": false,
- "required": ["project"]
+ "additionalProperties": false
},
"description": "Builds an Angular library with support for incremental builds.\n\nThis executor is meant to be used with buildable libraries in an incremental build scenario. It is similar to the `@nx/angular:package` executor but it only produces ESM2022 bundles.",
"aliases": [],
diff --git a/docs/generated/packages/angular/executors/package.json b/docs/generated/packages/angular/executors/package.json
index 8a7e2f7ad6..dd7ff75766 100644
--- a/docs/generated/packages/angular/executors/package.json
+++ b/docs/generated/packages/angular/executors/package.json
@@ -36,13 +36,10 @@
},
"poll": {
"type": "number",
- "description": "Enable and define the file watching poll time period in milliseconds. _Note: this is only supported in Angular versions >= 18.0.0_."
+ "description": "Enable and define the file watching poll time period in milliseconds."
}
},
- "additionalProperties": false,
- "required": [
- "project"
- ]
+ "additionalProperties": false
},
"description": "Builds and packages an Angular library producing an output following the Angular Package Format (APF) to be distributed as an NPM package.\n\nThis executor is a drop-in replacement for the `@angular-devkit/build-angular:ng-packagr` and `@angular/build:ng-packagr` builders, with additional support for incremental builds.",
"aliases": [],
diff --git a/docs/generated/packages/angular/generators/application.json b/docs/generated/packages/angular/generators/application.json
index 0d37d84906..7c46efa901 100644
--- a/docs/generated/packages/angular/generators/application.json
+++ b/docs/generated/packages/angular/generators/application.json
@@ -174,7 +174,7 @@
"default": false
},
"serverRouting": {
- "description": "Creates a server application using the Server Routing and App Engine APIs (Developer Preview). _Note: this is only supported in Angular versions >= 19.0.0_.",
+ "description": "Creates a server application using the Server Routing and App Engine APIs for application using the `application` builder (Developer Preview). _Note: this is only supported in Angular versions 19.x.x_. From Angular 20 onwards, SSR will always enable server routing when using the `application` builder.",
"type": "boolean"
}
},
diff --git a/docs/generated/packages/angular/generators/component.json b/docs/generated/packages/angular/generators/component.json
index 65a4cb9b13..8e8c982610 100644
--- a/docs/generated/packages/angular/generators/component.json
+++ b/docs/generated/packages/angular/generators/component.json
@@ -95,8 +95,7 @@
},
"type": {
"type": "string",
- "description": "Adds a developer-defined type to the filename, in the format `name.type.ts`.",
- "default": "component"
+ "description": "Append a custom type to the component's filename. It defaults to 'component' for Angular versions below v20. For Angular v20 and above, no type is appended unless specified."
},
"export": {
"type": "boolean",
@@ -109,6 +108,11 @@
"default": false,
"description": "Use default export for the component instead of a named export."
},
+ "ngHtml": {
+ "type": "boolean",
+ "default": false,
+ "description": "Generate component template files with an '.ng.html' file extension instead of '.html'."
+ },
"skipFormat": {
"description": "Skip formatting files.",
"type": "boolean",
@@ -117,7 +121,7 @@
}
},
"required": ["path"],
- "examplesFile": "## Examples\n\n{% tabs %}\n{% tab label=\"Simple Component\" %}\n\nGenerate a component named `MyComponent` at `apps/my-app/src/lib/my-component/my-component.component.ts`:\n\n```bash\nnx g @nx/angular:component apps/my-app/src/lib/my-component/my-component.ts\n```\n\n{% /tab %}\n\n{% tab label=\"Without Providing the File Extension\" %}\n\nGenerate a component named `MyComponent` at `apps/my-app/src/lib/my-component/my-component.component.ts`:\n\n```bash\nnx g @nx/angular:component apps/my-app/src/lib/my-component/my-component\n```\n\n{% /tab %}\n\n{% tab label=\"With Different Symbol Name\" %}\n\nGenerate a component named `CustomComponent` at `apps/my-app/src/lib/my-component/my-component.component.ts`:\n\n```bash\nnx g @nx/angular:component apps/my-app/src/lib/my-component/my-component --name=custom\n```\n\n{% /tab %}\n\n{% tab label=\"Single File Component\" %}\n\nCreate a component named `my-component` with inline styles and inline template:\n\n```bash\nnx g @nx/angular:component apps/my-app/src/lib/my-component/my-component --inlineStyle --inlineTemplate\n```\n\n{% /tab %}\n\n{% tab label=\"Component with OnPush Change Detection Strategy\" %}\n\nCreate a component named `my-component` with OnPush Change Detection Strategy:\n\n```bash\nnx g @nx/angular:component apps/my-app/src/lib/my-component/my-component --changeDetection=OnPush\n```\n\n{% /tab %}\n",
+ "examplesFile": "## Examples\n\n{% tabs %}\n{% tab label=\"Simple Component\" %}\n\nGenerate a component named `Card` at `apps/my-app/src/lib/card/card.ts`:\n\n```bash\nnx g @nx/angular:component apps/my-app/src/lib/card/card.ts\n```\n\n{% /tab %}\n\n{% tab label=\"Without Providing the File Extension\" %}\n\nGenerate a component named `Card` at `apps/my-app/src/lib/card/card.ts`:\n\n```bash\nnx g @nx/angular:component apps/my-app/src/lib/card/card\n```\n\n{% /tab %}\n\n{% tab label=\"With Different Symbol Name\" %}\n\nGenerate a component named `Custom` at `apps/my-app/src/lib/card/card.ts`:\n\n```bash\nnx g @nx/angular:component apps/my-app/src/lib/card/card --name=custom\n```\n\n{% /tab %}\n\n{% tab label=\"With a Component Type\" %}\n\nGenerate a component named `CardComponent` at `apps/my-app/src/lib/card/card.component.ts`:\n\n```bash\nnx g @nx/angular:component apps/my-app/src/lib/card/card --type=component\n```\n\n{% /tab %}\n\n{% tab label=\"Single File Component\" %}\n\nCreate a component named `Card` with inline styles and inline template:\n\n```bash\nnx g @nx/angular:component apps/my-app/src/lib/card/card --inlineStyle --inlineTemplate\n```\n\n{% /tab %}\n\n{% tab label=\"Component with OnPush Change Detection Strategy\" %}\n\nCreate a component named `Card` with `OnPush` Change Detection Strategy:\n\n```bash\nnx g @nx/angular:component apps/my-app/src/lib/card/card --changeDetection=OnPush\n```\n\n{% /tab %}\n",
"presets": []
},
"aliases": ["c"],
diff --git a/docs/generated/packages/angular/generators/directive.json b/docs/generated/packages/angular/generators/directive.json
index 6cab845005..ba701e61cb 100644
--- a/docs/generated/packages/angular/generators/directive.json
+++ b/docs/generated/packages/angular/generators/directive.json
@@ -15,11 +15,15 @@
"command": "nx g @nx/angular:directive mylib/src/lib/foo.directive.ts"
},
{
- "description": "Generate a directive without providing the file extension. It results in the directive `FooDirective` at `mylib/src/lib/foo.directive.ts`",
+ "description": "Generate a directive without providing the file extension. It results in the directive `Foo` at `mylib/src/lib/foo.ts`",
"command": "nx g @nx/angular:directive mylib/src/lib/foo"
},
{
- "description": "Generate a directive with the exported symbol different from the file name. It results in the directive `CustomDirective` at `mylib/src/lib/foo.directive.ts`",
+ "description": "Generate a directive with a given type/suffix. It results in the directive `FooDirective` at `mylib/src/lib/foo.directive.ts`",
+ "command": "nx g @nx/angular:directive mylib/src/lib/foo --type=directive"
+ },
+ {
+ "description": "Generate a directive with the exported symbol different from the file name. It results in the directive `Custom` at `mylib/src/lib/foo.ts`",
"command": "nx g @nx/angular:directive mylib/src/lib/foo --name=custom"
}
],
@@ -73,6 +77,10 @@
"default": false,
"description": "The declaring NgModule exports this directive."
},
+ "type": {
+ "type": "string",
+ "description": "Append a custom type to the directive's filename. It defaults to 'directive' for Angular versions below v20. For Angular v20 and above, no type is appended unless specified."
+ },
"skipFormat": {
"type": "boolean",
"default": false,
diff --git a/docs/generated/packages/angular/generators/host.json b/docs/generated/packages/angular/generators/host.json
index e19716c567..2a710a4a09 100644
--- a/docs/generated/packages/angular/generators/host.json
+++ b/docs/generated/packages/angular/generators/host.json
@@ -175,7 +175,7 @@
"x-priority": "important"
},
"serverRouting": {
- "description": "Creates a server application using the Server Routing and App Engine APIs (Developer Preview). _Note: this is only supported in Angular versions >= 19.0.0_.",
+ "description": "Creates a server application using the Server Routing and App Engine APIs for application using the `application` builder (Developer Preview). _Note: this is only supported in Angular versions 19.x.x_. From Angular 20 onwards, SSR will always enable server routing when using the `application` builder.",
"type": "boolean"
},
"typescriptConfiguration": {
diff --git a/docs/generated/packages/angular/generators/ngrx.json b/docs/generated/packages/angular/generators/ngrx.json
index 8a219a39e2..8afb903818 100644
--- a/docs/generated/packages/angular/generators/ngrx.json
+++ b/docs/generated/packages/angular/generators/ngrx.json
@@ -11,11 +11,11 @@
"type": "object",
"examples": [
{
- "command": "nx g @nx/angular:ngrx --root --parent=apps/my-app/src/app/app.module.ts --facade=false placeholder",
+ "command": "nx g @nx/angular:ngrx --root --parent=apps/my-app/src/app/app-module.ts --facade=false placeholder",
"description": "Add root ngrx configration to the `my-app` application"
},
{
- "command": "nx g @nx/angular:ngrx --parent=libs/my-lib/src/lib/my-lib.module.ts --facade=true --root=false users",
+ "command": "nx g @nx/angular:ngrx --parent=libs/my-lib/src/lib/my-lib-module.ts --facade=true --root=false users",
"description": "Add a `users` state with a facade to the `my-lib` library. It will be tracked under the default `+state` folder in the lib"
},
{
@@ -42,7 +42,7 @@
},
"parent": {
"type": "string",
- "description": "The path to the file where the state will be registered. For NgModule usage, this will be your `app.module.ts` for your root state, or your Feature Module for feature state. For Standalone API usage, this will be your `app.config.ts` file for your root state, or the Routes definition file for your feature state. The host directory will create/use the new state directory.",
+ "description": "The path to the file where the state will be registered. For NgModule usage, this will be your `app-module.ts` for your root state, or your Feature Module for feature state. For Standalone API usage, this will be your `app.config.ts` file for your root state, or the Routes definition file for your feature state. The host directory will create/use the new state directory.",
"x-prompt": "What is the path to the module or Routes definition where this NgRx state should be registered?",
"x-priority": "important"
},
diff --git a/docs/generated/packages/angular/generators/pipe.json b/docs/generated/packages/angular/generators/pipe.json
index d2bdb46f56..b6142cdbaa 100644
--- a/docs/generated/packages/angular/generators/pipe.json
+++ b/docs/generated/packages/angular/generators/pipe.json
@@ -15,11 +15,15 @@
"command": "nx g @nx/angular:pipe mylib/src/lib/foo.pipe.ts"
},
{
- "description": "Generate a pipe without providing the file extension. It results in the pipe `FooPipe` at `mylib/src/lib/foo.pipe.ts`",
+ "description": "Generate a pipe without providing the file extension. It results in the pipe `FooPipe` at `mylib/src/lib/foo-pipe.ts`",
"command": "nx g @nx/angular:pipe mylib/src/lib/foo"
},
{
- "description": "Generate a pipe with the exported symbol different from the file name. It results in the pipe `CustomPipe` at `mylib/src/lib/foo.pipe.ts`",
+ "description": "Generate a pipe with a different type separator. It results in the pipe `FooPipe` at `mylib/src/lib/foo.pipe.ts`",
+ "command": "nx g @nx/angular:pipe mylib/src/lib/foo --typeSeparator=."
+ },
+ {
+ "description": "Generate a pipe with the exported symbol different from the file name. It results in the pipe `CustomPipe` at `mylib/src/lib/foo-pipe.ts`",
"command": "nx g @nx/angular:pipe mylib/src/lib/foo --name=custom"
}
],
@@ -59,6 +63,11 @@
"default": false,
"description": "The declaring NgModule exports this pipe."
},
+ "typeSeparator": {
+ "type": "string",
+ "enum": ["-", "."],
+ "description": "The separator character to use before the type within the generated file's name. For example, if you set the option to `.`, the file will be named `example.pipe.ts`. It defaults to '-' for Angular v20+. For versions below v20, it defaults to '.'."
+ },
"skipFormat": {
"type": "boolean",
"default": false,
diff --git a/docs/generated/packages/angular/generators/remote.json b/docs/generated/packages/angular/generators/remote.json
index 18a26c6023..b7d489b05e 100644
--- a/docs/generated/packages/angular/generators/remote.json
+++ b/docs/generated/packages/angular/generators/remote.json
@@ -168,7 +168,7 @@
"default": false
},
"serverRouting": {
- "description": "Creates a server application using the Server Routing and App Engine APIs (Developer Preview). _Note: this is only supported in Angular versions >= 19.0.0_.",
+ "description": "Creates a server application using the Server Routing and App Engine APIs for application using the `application` builder (Developer Preview). _Note: this is only supported in Angular versions 19.x.x_. From Angular 20 onwards, SSR will always enable server routing when using the `application` builder.",
"type": "boolean"
},
"typescriptConfiguration": {
diff --git a/docs/generated/packages/angular/generators/scam-directive.json b/docs/generated/packages/angular/generators/scam-directive.json
index a6bd20adf5..b03bcb75db 100644
--- a/docs/generated/packages/angular/generators/scam-directive.json
+++ b/docs/generated/packages/angular/generators/scam-directive.json
@@ -13,11 +13,15 @@
"command": "nx g @nx/angular:scam-directive mylib/src/lib/foo.directive.ts"
},
{
- "description": "Generate a directive without providing the file extension. It results in the directive `FooDirective` at `mylib/src/lib/foo.directive.ts`",
+ "description": "Generate a directive without providing the file extension. It results in the directive `Foo` at `mylib/src/lib/foo.ts`",
"command": "nx g @nx/angular:scam-directive mylib/src/lib/foo"
},
{
- "description": "Generate a directive with the exported symbol different from the file name. It results in the directive `CustomDirective` at `mylib/src/lib/foo.directive.ts`",
+ "description": "Generate a directive with a given type/suffix. It results in the directive `FooDirective` at `mylib/src/lib/foo.directive.ts`",
+ "command": "nx g @nx/angular:scam-directive mylib/src/lib/foo --type=directive"
+ },
+ {
+ "description": "Generate a directive with the exported symbol different from the file name. It results in the directive `Custom` at `mylib/src/lib/foo.ts`",
"command": "nx g @nx/angular:scam-directive mylib/src/lib/foo --name=custom"
}
],
@@ -65,6 +69,10 @@
"default": true,
"x-priority": "important"
},
+ "type": {
+ "type": "string",
+ "description": "Append a custom type to the directive's filename. It defaults to 'directive' for Angular versions below v20. For Angular v20 and above, no type is appended unless specified."
+ },
"skipFormat": {
"description": "Skip formatting files.",
"type": "boolean",
diff --git a/docs/generated/packages/angular/generators/scam-pipe.json b/docs/generated/packages/angular/generators/scam-pipe.json
index 96035de5df..0f96cf92ab 100644
--- a/docs/generated/packages/angular/generators/scam-pipe.json
+++ b/docs/generated/packages/angular/generators/scam-pipe.json
@@ -51,6 +51,11 @@
"default": true,
"x-priority": "important"
},
+ "typeSeparator": {
+ "type": "string",
+ "enum": ["-", "."],
+ "description": "The separator character to use before the type within the generated file's name. For example, if you set the option to `.`, the file will be named `example.pipe.ts`. It defaults to '-' for Angular v20+. For versions below v20, it defaults to '.'."
+ },
"skipFormat": {
"description": "Skip formatting files.",
"type": "boolean",
diff --git a/docs/generated/packages/angular/generators/scam.json b/docs/generated/packages/angular/generators/scam.json
index 418d311414..d16520de48 100644
--- a/docs/generated/packages/angular/generators/scam.json
+++ b/docs/generated/packages/angular/generators/scam.json
@@ -94,8 +94,7 @@
},
"type": {
"type": "string",
- "description": "Adds a developer-defined type to the filename, in the format `name.type.ts`.",
- "default": "component"
+ "description": "Append a custom type to the component's filename. It defaults to 'component' for Angular versions below v20. For Angular v20 and above, no type is appended unless specified."
},
"prefix": {
"type": "string",
diff --git a/docs/generated/packages/angular/generators/setup-ssr.json b/docs/generated/packages/angular/generators/setup-ssr.json
index 5c03b2aab9..ea4de81a7a 100644
--- a/docs/generated/packages/angular/generators/setup-ssr.json
+++ b/docs/generated/packages/angular/generators/setup-ssr.json
@@ -53,7 +53,7 @@
"default": true
},
"serverRouting": {
- "description": "Creates a server application using the Server Routing and App Engine APIs (Developer Preview). _Note: this is only supported in Angular versions >= 19.0.0_.",
+ "description": "Creates a server application using the Server Routing and App Engine APIs for application using the `application` builder (Developer Preview). _Note: this is only supported in Angular versions 19.x.x_. From Angular 20 onwards, SSR will always enable server routing when using the `application` builder.",
"type": "boolean"
},
"skipFormat": {
diff --git a/docs/generated/packages/angular/migrations/21.2.0-package-updates.json b/docs/generated/packages/angular/migrations/21.2.0-package-updates.json
new file mode 100644
index 0000000000..148284e0a0
--- /dev/null
+++ b/docs/generated/packages/angular/migrations/21.2.0-package-updates.json
@@ -0,0 +1,68 @@
+{
+ "name": "21.2.0-package-updates",
+ "version": "21.2.0-beta.0",
+ "x-prompt": "Do you want to update the Angular version to v20?",
+ "requires": { "@angular/core": ">=19.2.0 <20.0.0-rc.2" },
+ "packages": {
+ "@angular-devkit/build-angular": {
+ "version": "20.0.0-rc.3",
+ "alwaysAddToPackageJson": false
+ },
+ "@angular-devkit/core": {
+ "version": "20.0.0-rc.3",
+ "alwaysAddToPackageJson": false
+ },
+ "@angular-devkit/schematics": {
+ "version": "20.0.0-rc.3",
+ "alwaysAddToPackageJson": false
+ },
+ "@angular/build": {
+ "version": "20.0.0-rc.3",
+ "alwaysAddToPackageJson": false
+ },
+ "@angular/pwa": {
+ "version": "20.0.0-rc.3",
+ "alwaysAddToPackageJson": false
+ },
+ "@angular/ssr": {
+ "version": "20.0.0-rc.3",
+ "alwaysAddToPackageJson": false
+ },
+ "@schematics/angular": {
+ "version": "20.0.0-rc.3",
+ "alwaysAddToPackageJson": false
+ },
+ "@angular-devkit/architect": {
+ "version": "0.2000.0-rc.3",
+ "alwaysAddToPackageJson": false
+ },
+ "@angular-devkit/build-webpack": {
+ "version": "0.2000.0-rc.3",
+ "alwaysAddToPackageJson": false
+ },
+ "@angular/core": {
+ "version": "20.0.0-rc.2",
+ "alwaysAddToPackageJson": true
+ },
+ "@angular/material": {
+ "version": "20.0.0-rc.2",
+ "alwaysAddToPackageJson": false
+ },
+ "@angular/cdk": {
+ "version": "20.0.0-rc.2",
+ "alwaysAddToPackageJson": false
+ },
+ "@angular/google-maps": {
+ "version": "20.0.0-rc.2",
+ "alwaysAddToPackageJson": false
+ },
+ "ng-packagr": { "version": "20.0.0-rc.1", "alwaysAddToPackageJson": false }
+ },
+ "aliases": [],
+ "description": "",
+ "hidden": false,
+ "implementation": "",
+ "path": "/packages/angular",
+ "schema": null,
+ "type": "migration"
+}
diff --git a/docs/generated/packages/angular/migrations/migrate-provide-server-rendering-import.json b/docs/generated/packages/angular/migrations/migrate-provide-server-rendering-import.json
new file mode 100644
index 0000000000..cfade409b5
--- /dev/null
+++ b/docs/generated/packages/angular/migrations/migrate-provide-server-rendering-import.json
@@ -0,0 +1,14 @@
+{
+ "name": "migrate-provide-server-rendering-import",
+ "version": "21.2.0-beta.0",
+ "requires": { "@angular/core": ">=20.0.0-rc.2" },
+ "description": "Migrate imports of `provideServerRendering` from `@angular/platform-server` to `@angular/ssr`.",
+ "factory": "./src/migrations/update-21-2-0/migrate-provide-server-rendering-import",
+ "implementation": "/packages/angular/src/migrations/update-21-2-0/migrate-provide-server-rendering-import.ts",
+ "aliases": [],
+ "hidden": false,
+ "path": "/packages/angular",
+ "schema": null,
+ "type": "migration",
+ "examplesFile": "#### Migrate Imports of `provideServerRendering` from `@angular/platform-server` to `@angular/ssr`\n\nMigrate the imports of `provideServerRendering` from `@angular/platform-server` to `@angular/ssr`. This migration will also add the `@angular/ssr` package to your dependencies if needed.\n\n#### Examples\n\nChange the import of `provideServerRendering` from `@angular/platform-server` to `@angular/ssr`:\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```ts {% fileName=\"app/app.config.server.ts\" highlightLines=[2] %}\nimport { ApplicationConfig } from '@angular/core';\nimport { provideServerRendering } from '@angular/platform-server';\n\nconst serverConfig: ApplicationConfig = {\n providers: [provideServerRendering()],\n};\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```ts {% fileName=\"app/app.config.server.ts\" highlightLines=[2] %}\nimport { ApplicationConfig } from '@angular/core';\nimport { provideServerRendering } from '@angular/ssr';\n\nconst serverConfig: ApplicationConfig = {\n providers: [provideServerRendering()],\n};\n```\n\n{% /tab %}\n{% /tabs %}\n\nIf you already have imports from `@angular/ssr`, the migration will add `provideServerRendering` to the existing import:\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```ts {% fileName=\"app/app.config.server.ts\" highlightLines=[2,3] %}\nimport { ApplicationConfig } from '@angular/core';\nimport { provideServerRendering } from '@angular/platform-server';\nimport { provideServerRouting } from '@angular/ssr';\nimport { serverRoutes } from './app.routes.server';\n\nconst serverConfig: ApplicationConfig = {\n providers: [provideServerRendering(), provideServerRouting(serverRoutes)],\n};\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```ts {% fileName=\"app/app.config.server.ts\" highlightLines=[2] %}\nimport { ApplicationConfig } from '@angular/core';\nimport { provideServerRouting, provideServerRendering } from '@angular/ssr';\nimport { serverRoutes } from './app.routes.server';\n\nconst serverConfig: ApplicationConfig = {\n providers: [provideServerRendering(), provideServerRouting(serverRoutes)],\n};\n```\n\n{% /tab %}\n{% /tabs %}\n"
+}
diff --git a/docs/generated/packages/angular/migrations/replace-provide-server-routing.json b/docs/generated/packages/angular/migrations/replace-provide-server-routing.json
new file mode 100644
index 0000000000..e9671537cc
--- /dev/null
+++ b/docs/generated/packages/angular/migrations/replace-provide-server-routing.json
@@ -0,0 +1,14 @@
+{
+ "name": "replace-provide-server-routing",
+ "version": "21.2.0-beta.0",
+ "requires": { "@angular/core": ">=20.0.0-rc.2" },
+ "description": "Replace `provideServerRouting` with `provideServerRendering` using `withRoutes`.",
+ "factory": "./src/migrations/update-21-2-0/replace-provide-server-routing",
+ "implementation": "/packages/angular/src/migrations/update-21-2-0/replace-provide-server-routing.ts",
+ "aliases": [],
+ "hidden": false,
+ "path": "/packages/angular",
+ "schema": null,
+ "type": "migration",
+ "examplesFile": "#### Replace `provideServerRouting` with `provideServerRendering`\n\nReplace `provideServerRouting` calls with `provideServerRendering` using `withRoutes`.\n\n#### Examples\n\nRemove `provideServerRouting` from your providers array and update the `provideServerRendering` call to use `withRoutes`:\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```ts {% fileName=\"app/app.config.server.ts\" highlightLines=[2,6] %}\nimport { ApplicationConfig } from '@angular/core';\nimport { provideServerRendering, provideServerRouting } from '@angular/ssr';\nimport { serverRoutes } from './app.routes.server';\n\nconst serverConfig: ApplicationConfig = {\n providers: [provideServerRendering(), provideServerRouting(serverRoutes)],\n};\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```ts {% fileName=\"app/app.config.server.ts\" highlightLines=[2,6] %}\nimport { ApplicationConfig } from '@angular/core';\nimport { provideServerRendering, withRoutes } from '@angular/ssr';\nimport { serverRoutes } from './app.routes.server';\n\nconst serverConfig: ApplicationConfig = {\n providers: [provideServerRendering(withRoutes(serverRoutes))],\n};\n```\n\n{% /tab %}\n{% /tabs %}\n\nIf you have `provideServerRouting` with additional arguments, the migration will preserve them:\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```ts {% fileName=\"app/app.config.server.ts\" highlightLines=[4,11,12] %}\nimport { ApplicationConfig } from '@angular/core';\nimport {\n provideServerRendering,\n provideServerRouting,\n withAppShell,\n} from '@angular/ssr';\nimport { serverRoutes } from './app.routes.server';\n\nconst serverConfig: ApplicationConfig = {\n providers: [\n provideServerRendering(),\n provideServerRouting(serverRoutes, withAppShell(AppShellComponent)),\n ],\n};\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```ts {% fileName=\"app/app.config.server.ts\" highlightLines=[2,\"7-10\"] %}\nimport { ApplicationConfig } from '@angular/core';\nimport { provideServerRendering, withAppShell, withRoutes } from '@angular/ssr';\nimport { serverRoutes } from './app.routes.server';\n\nconst serverConfig: ApplicationConfig = {\n providers: [\n provideServerRendering(\n withRoutes(serverRoutes),\n withAppShell(AppShellComponent)\n ),\n ],\n};\n```\n\n{% /tab %}\n{% /tabs %}\n"
+}
diff --git a/docs/generated/packages/angular/migrations/set-generator-defaults-for-previous-style-guide.json b/docs/generated/packages/angular/migrations/set-generator-defaults-for-previous-style-guide.json
new file mode 100644
index 0000000000..8e00489a83
--- /dev/null
+++ b/docs/generated/packages/angular/migrations/set-generator-defaults-for-previous-style-guide.json
@@ -0,0 +1,14 @@
+{
+ "name": "set-generator-defaults-for-previous-style-guide",
+ "version": "21.2.0-beta.0",
+ "requires": { "@angular/core": ">=20.0.0-rc.2" },
+ "description": "Update the generator defaults to maintain the previous style guide behavior.",
+ "factory": "./src/migrations/update-21-2-0/set-generator-defaults-for-previous-style-guide",
+ "implementation": "/packages/angular/src/migrations/update-21-2-0/set-generator-defaults-for-previous-style-guide.ts",
+ "aliases": [],
+ "hidden": false,
+ "path": "/packages/angular",
+ "schema": null,
+ "type": "migration",
+ "examplesFile": "#### Set Generator Defaults for Previous Style Guide\n\nUpdates the generator defaults in the `nx.json` file to maintain the previous Angular Style Guide behavior. This ensures that newly generated code in existing workspaces follows the same conventions as the existing codebase.\n\n#### Examples\n\nThe migration will add default configurations for the relevant Angular generators in the workspace's `nx.json` file:\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```json {% fileName=\"nx.json\" %}\n{\n \"generators\": {}\n}\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```json {% fileName=\"nx.json\" %}\n{\n \"generators\": {\n \"@nx/angular:component\": {\n \"type\": \"component\"\n },\n \"@nx/angular:directive\": {\n \"type\": \"directive\"\n },\n \"@nx/angular:service\": {\n \"type\": \"service\"\n },\n \"@nx/angular:scam\": {\n \"type\": \"component\"\n },\n \"@nx/angular:scam-directive\": {\n \"type\": \"directive\"\n },\n \"@nx/angular:guard\": {\n \"typeSeparator\": \".\"\n },\n \"@nx/angular:interceptor\": {\n \"typeSeparator\": \".\"\n },\n \"@nx/angular:module\": {\n \"typeSeparator\": \".\"\n },\n \"@nx/angular:pipe\": {\n \"typeSeparator\": \".\"\n },\n \"@nx/angular:resolver\": {\n \"typeSeparator\": \".\"\n },\n \"@schematics/angular:component\": {\n \"type\": \"component\"\n },\n \"@schematics/angular:directive\": {\n \"type\": \"directive\"\n },\n \"@schematics/angular:service\": {\n \"type\": \"service\"\n },\n \"@schematics/angular:guard\": {\n \"typeSeparator\": \".\"\n },\n \"@schematics/angular:interceptor\": {\n \"typeSeparator\": \".\"\n },\n \"@schematics/angular:module\": {\n \"typeSeparator\": \".\"\n },\n \"@schematics/angular:pipe\": {\n \"typeSeparator\": \".\"\n },\n \"@schematics/angular:resolver\": {\n \"typeSeparator\": \".\"\n }\n }\n}\n```\n\n{% /tab %}\n{% /tabs %}\n\nIf some of the generator defaults are already set, the migration will not override them:\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```json {% fileName=\"nx.json\" highlightLines=[\"3-14\"] %}\n{\n \"generators\": {\n \"@nx/angular:component\": {\n \"type\": \"cmp\"\n },\n \"@schematics/angular:component\": {\n \"type\": \"cmp\"\n },\n \"@nx/angular:interceptor\": {\n \"typeSeparator\": \"-\"\n },\n \"@schematics/angular:interceptor\": {\n \"typeSeparator\": \"-\"\n }\n }\n}\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```json {% fileName=\"nx.json\" highlightLines=[\"3-14\"] %}\n{\n \"generators\": {\n \"@nx/angular:component\": {\n \"type\": \"cmp\"\n },\n \"@schematics/angular:component\": {\n \"type\": \"cmp\"\n },\n \"@nx/angular:interceptor\": {\n \"typeSeparator\": \"-\"\n },\n \"@schematics/angular:interceptor\": {\n \"typeSeparator\": \"-\"\n },\n \"@nx/angular:directive\": {\n \"type\": \"directive\"\n },\n \"@nx/angular:service\": {\n \"type\": \"service\"\n },\n \"@nx/angular:scam\": {\n \"type\": \"component\"\n },\n \"@nx/angular:scam-directive\": {\n \"type\": \"directive\"\n },\n \"@nx/angular:guard\": {\n \"typeSeparator\": \".\"\n },\n \"@nx/angular:module\": {\n \"typeSeparator\": \".\"\n },\n \"@nx/angular:pipe\": {\n \"typeSeparator\": \".\"\n },\n \"@nx/angular:resolver\": {\n \"typeSeparator\": \".\"\n },\n \"@schematics/angular:directive\": {\n \"type\": \"directive\"\n },\n \"@schematics/angular:service\": {\n \"type\": \"service\"\n },\n \"@schematics/angular:guard\": {\n \"typeSeparator\": \".\"\n },\n \"@schematics/angular:module\": {\n \"typeSeparator\": \".\"\n },\n \"@schematics/angular:pipe\": {\n \"typeSeparator\": \".\"\n },\n \"@schematics/angular:resolver\": {\n \"typeSeparator\": \".\"\n }\n }\n}\n```\n\n{% /tab %}\n{% /tabs %}\n"
+}
diff --git a/docs/generated/packages/angular/migrations/update-angular-cli-version-20-0-0-rc-3.json b/docs/generated/packages/angular/migrations/update-angular-cli-version-20-0-0-rc-3.json
new file mode 100644
index 0000000000..7039c20489
--- /dev/null
+++ b/docs/generated/packages/angular/migrations/update-angular-cli-version-20-0-0-rc-3.json
@@ -0,0 +1,15 @@
+{
+ "name": "update-angular-cli-version-20-0-0-rc-3",
+ "cli": "nx",
+ "version": "21.2.0-beta.0",
+ "requires": { "@angular/core": ">=20.0.0-rc.2" },
+ "description": "Update the @angular/cli package version to 20.0.0-rc.3.",
+ "factory": "./src/migrations/update-21-2-0/update-angular-cli",
+ "implementation": "/packages/angular/src/migrations/update-21-2-0/update-angular-cli.ts",
+ "aliases": [],
+ "hidden": false,
+ "path": "/packages/angular",
+ "schema": null,
+ "type": "migration",
+ "examplesFile": "#### Sample Code Changes\n\nUpdate the `@angular/cli` package version in the `package.json` file at the workspace root to **~20.0.0**.\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```json {% fileName=\"package.json\" %}\n{\n \"devDependencies\": {\n \"@angular/cli\": \"~19.2.0\"\n }\n}\n```\n\n{% /tab %}\n{% tab label=\"After\" %}\n\n```json {% highlightLines=[3] fileName=\"package.json\" %}\n{\n \"devDependencies\": {\n \"@angular/cli\": \"~20.0.0\"\n }\n}\n```\n\n{% /tab %}\n\n{% /tabs %}\n"
+}
diff --git a/docs/generated/packages/angular/migrations/update-module-resolution.json b/docs/generated/packages/angular/migrations/update-module-resolution.json
new file mode 100644
index 0000000000..ea832e6ac2
--- /dev/null
+++ b/docs/generated/packages/angular/migrations/update-module-resolution.json
@@ -0,0 +1,14 @@
+{
+ "name": "update-module-resolution",
+ "version": "21.2.0-beta.0",
+ "requires": { "@angular/core": ">=20.0.0-rc.2" },
+ "description": "Update 'moduleResolution' to 'bundler' in TypeScript configurations. You can read more about this here: https://www.typescriptlang.org/tsconfig/#moduleResolution.",
+ "factory": "./src/migrations/update-21-2-0/update-module-resolution",
+ "implementation": "/packages/angular/src/migrations/update-21-2-0/update-module-resolution.ts",
+ "aliases": [],
+ "hidden": false,
+ "path": "/packages/angular",
+ "schema": null,
+ "type": "migration",
+ "examplesFile": "#### Update `moduleResolution` to `bundler` in TypeScript configurations\n\nUpdates the TypeScript `moduleResolution` option to `'bundler'` for improved compatibility with modern package resolution algorithms used by bundlers like Webpack, Vite, and esbuild.\n\n#### Examples\n\nThe migration will update TypeScript configuration files in your workspace to use the `'bundler'` module resolution strategy:\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```json {% fileName=\"apps/app1/tsconfig.app.json\" highlightLines=[\"4\"] %}\n{\n \"compilerOptions\": {\n \"module\": \"es2020\",\n \"moduleResolution\": \"node\"\n }\n}\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```json {% fileName=\"apps/app1/tsconfig.app.json\" highlightLines=[\"4\"] %}\n{\n \"compilerOptions\": {\n \"module\": \"es2020\",\n \"moduleResolution\": \"bundler\"\n }\n}\n```\n\n{% /tab %}\n{% /tabs %}\n\nIf the `moduleResolution` is already set to `'bundler'` or the `module` is set to `'preserve'`, the migration will not modify the configuration:\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```json {% fileName=\"apps/app1/tsconfig.app.json\" highlightLines=[3,4] %}\n{\n \"compilerOptions\": {\n \"module\": \"preserve\",\n \"moduleResolution\": \"node\"\n }\n}\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```json {% fileName=\"apps/app1/tsconfig.app.json\" highlightLines=[3,4] %}\n{\n \"compilerOptions\": {\n \"module\": \"preserve\",\n \"moduleResolution\": \"node\"\n }\n}\n```\n\n{% /tab %}\n{% /tabs %}\n"
+}
diff --git a/docs/generated/packages/eslint/migrations/21.2.0-@typescript-eslint-package-updates.json b/docs/generated/packages/eslint/migrations/21.2.0-@typescript-eslint-package-updates.json
new file mode 100644
index 0000000000..99cdda4c68
--- /dev/null
+++ b/docs/generated/packages/eslint/migrations/21.2.0-@typescript-eslint-package-updates.json
@@ -0,0 +1,30 @@
+{
+ "name": "21.2.0-@typescript-eslint-package-updates",
+ "version": "21.2.0-beta.0",
+ "requires": { "@typescript-eslint/eslint-plugin": ">8.0.0 <8.29.0" },
+ "packages": {
+ "typescript-eslint": { "version": "^8.29.0" },
+ "@typescript-eslint/eslint-plugin": { "version": "^8.29.0" },
+ "@typescript-eslint/parser": { "version": "^8.29.0" },
+ "@typescript-eslint/utils": { "version": "^8.29.0" },
+ "@typescript-eslint/rule-tester": {
+ "version": "^8.29.0",
+ "alwaysAddToPackageJson": false
+ },
+ "@typescript-eslint/scope-manager": {
+ "version": "^8.29.0",
+ "alwaysAddToPackageJson": false
+ },
+ "@typescript-eslint/typescript-estree": {
+ "version": "^8.29.0",
+ "alwaysAddToPackageJson": false
+ }
+ },
+ "aliases": [],
+ "description": "",
+ "hidden": false,
+ "implementation": "",
+ "path": "/packages/eslint",
+ "schema": null,
+ "type": "migration"
+}
diff --git a/docs/generated/packages/eslint/migrations/21.2.0-typescript-eslint-package-updates.json b/docs/generated/packages/eslint/migrations/21.2.0-typescript-eslint-package-updates.json
new file mode 100644
index 0000000000..5bbe3e7f0f
--- /dev/null
+++ b/docs/generated/packages/eslint/migrations/21.2.0-typescript-eslint-package-updates.json
@@ -0,0 +1,30 @@
+{
+ "name": "21.2.0-typescript-eslint-package-updates",
+ "version": "21.2.0-beta.0",
+ "requires": { "typescript-eslint": ">8.0.0 <8.29.0" },
+ "packages": {
+ "typescript-eslint": { "version": "^8.29.0" },
+ "@typescript-eslint/eslint-plugin": { "version": "^8.29.0" },
+ "@typescript-eslint/parser": { "version": "^8.29.0" },
+ "@typescript-eslint/utils": { "version": "^8.29.0" },
+ "@typescript-eslint/rule-tester": {
+ "version": "^8.29.0",
+ "alwaysAddToPackageJson": false
+ },
+ "@typescript-eslint/scope-manager": {
+ "version": "^8.29.0",
+ "alwaysAddToPackageJson": false
+ },
+ "@typescript-eslint/typescript-estree": {
+ "version": "^8.29.0",
+ "alwaysAddToPackageJson": false
+ }
+ },
+ "aliases": [],
+ "description": "",
+ "hidden": false,
+ "implementation": "",
+ "path": "/packages/eslint",
+ "schema": null,
+ "type": "migration"
+}
diff --git a/docs/generated/packages/js/migrations/21.2.0-package-updates.json b/docs/generated/packages/js/migrations/21.2.0-package-updates.json
new file mode 100644
index 0000000000..e613ca9e56
--- /dev/null
+++ b/docs/generated/packages/js/migrations/21.2.0-package-updates.json
@@ -0,0 +1,16 @@
+{
+ "name": "21.2.0-package-updates",
+ "version": "21.2.0-beta.0",
+ "x-prompt": "Do you want to update to TypeScript v5.8?",
+ "requires": { "typescript": ">=5.7.0 <5.8.0" },
+ "packages": {
+ "typescript": { "version": "~5.8.2", "alwaysAddToPackageJson": false }
+ },
+ "aliases": [],
+ "description": "",
+ "hidden": false,
+ "implementation": "",
+ "path": "/packages/js",
+ "schema": null,
+ "type": "migration"
+}
diff --git a/docs/generated/packages/workspace/generators/new.json b/docs/generated/packages/workspace/generators/new.json
index f485a8d30b..a213115c73 100644
--- a/docs/generated/packages/workspace/generators/new.json
+++ b/docs/generated/packages/workspace/generators/new.json
@@ -90,10 +90,6 @@
"type": "boolean",
"default": false
},
- "serverRouting": {
- "description": "Use the Angular Server Routing and App Engine APIs (Developer Preview).",
- "type": "boolean"
- },
"prefix": {
"description": "The prefix to use for Angular component and directive selectors.",
"type": "string"
diff --git a/docs/generated/packages/workspace/generators/preset.json b/docs/generated/packages/workspace/generators/preset.json
index bd928ff683..b7c32afaab 100644
--- a/docs/generated/packages/workspace/generators/preset.json
+++ b/docs/generated/packages/workspace/generators/preset.json
@@ -107,10 +107,6 @@
"type": "boolean",
"default": false
},
- "serverRouting": {
- "description": "Use the Angular Server Routing and App Engine APIs (Developer Preview).",
- "type": "boolean"
- },
"prefix": {
"description": "The prefix to use for Angular component and directive selectors.",
"type": "string"
diff --git a/docs/generated/packages/workspace/migrations/21.2.0-package-updates.json b/docs/generated/packages/workspace/migrations/21.2.0-package-updates.json
new file mode 100644
index 0000000000..84ce142e23
--- /dev/null
+++ b/docs/generated/packages/workspace/migrations/21.2.0-package-updates.json
@@ -0,0 +1,16 @@
+{
+ "name": "21.2.0-package-updates",
+ "version": "21.2.0-beta.0",
+ "x-prompt": "Do you want to update to TypeScript v5.8?",
+ "requires": { "typescript": ">=5.7.0 <5.8.0" },
+ "packages": {
+ "typescript": { "version": "~5.8.2", "alwaysAddToPackageJson": false }
+ },
+ "aliases": [],
+ "description": "",
+ "hidden": false,
+ "implementation": "",
+ "path": "/packages/workspace",
+ "schema": null,
+ "type": "migration"
+}
diff --git a/e2e/angular/src/config.test.ts b/e2e/angular/src/config.test.ts
index f7f0c3df96..ea730c70a7 100644
--- a/e2e/angular/src/config.test.ts
+++ b/e2e/angular/src/config.test.ts
@@ -13,7 +13,9 @@ describe('angular.json v1 config', () => {
beforeAll(() => {
newProject({ packages: ['@nx/angular'] });
- runCLI(`generate @nx/angular:app ${app1} --no-interactive`);
+ runCLI(
+ `generate @nx/angular:app ${app1} --bundler=webpack --no-interactive`
+ );
// reset workspace to use v1 config
updateFile(`angular.json`, angularV1Json(app1));
removeFile(`${app1}/project.json`);
diff --git a/e2e/angular/src/cypress-component-tests.test.ts b/e2e/angular/src/cypress-component-tests.test.ts
index b780433646..5ce393768a 100644
--- a/e2e/angular/src/cypress-component-tests.test.ts
+++ b/e2e/angular/src/cypress-component-tests.test.ts
@@ -88,7 +88,7 @@ describe('Angular Cypress Component Tests', () => {
}
);
updateFile(
- `${buildableLibName}/src/lib/input-standalone/input-standalone.component.cy.ts`,
+ `${buildableLibName}/src/lib/input-standalone/input-standalone.cy.ts`,
(content) => {
// text-green-500 should now apply
return content.replace('rgb(0, 0, 0)', 'rgb(34, 197, 94)');
@@ -149,7 +149,7 @@ function createLib(projectName: string, appName: string, libName: string) {
`generate @nx/angular:component ${libName}/src/lib/btn-standalone/btn-standalone --inlineTemplate --inlineStyle --export --standalone --no-interactive`
);
updateFile(
- `${libName}/src/lib/btn/btn.component.ts`,
+ `${libName}/src/lib/btn/btn.ts`,
`
import { Component, Input } from '@angular/core';
@@ -158,13 +158,13 @@ import { Component, Input } from '@angular/core';
template: '',
styles: []
})
-export class BtnComponent {
+export class Btn {
@Input() text = 'something';
}
`
);
updateFile(
- `${libName}/src/lib/btn-standalone/btn-standalone.component.ts`,
+ `${libName}/src/lib/btn-standalone/btn-standalone.ts`,
`
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
@@ -175,7 +175,7 @@ import { CommonModule } from '@angular/common';
template: '',
styles: [],
})
-export class BtnStandaloneComponent {
+export class BtnStandalone {
@Input() text = 'something';
}
`
@@ -187,7 +187,7 @@ function createBuildableLib(projectName: string, libName: string) {
runCLI(`generate @nx/angular:lib ${libName} --buildable --no-interactive`);
// create cmp for lib
runCLI(
- `generate @nx/angular:component ${libName}/src/lib/input/input --inlineTemplate --inlineStyle --export --no-interactive`
+ `generate @nx/angular:component ${libName}/src/lib/input/input.component --inlineTemplate --inlineStyle --export --no-interactive`
);
// create standlone cmp for lib
runCLI(
@@ -210,7 +210,7 @@ import {Component, Input} from '@angular/core';
`
);
updateFile(
- `${libName}/src/lib/input-standalone/input-standalone.component.ts`,
+ `${libName}/src/lib/input-standalone/input-standalone.ts`,
`
import {Component, Input} from '@angular/core';
import {CommonModule} from '@angular/common';
@@ -221,7 +221,7 @@ import {CommonModule} from '@angular/common';
template: \`\`,
styles : []
})
- export class InputStandaloneComponent{
+ export class InputStandalone{
@Input() readOnly = false;
}
`
@@ -230,7 +230,7 @@ import {CommonModule} from '@angular/common';
function useLibInApp(projectName: string, appName: string, libName: string) {
createFile(
- `${appName}/src/app/app.component.html`,
+ `${appName}/src/app/app.html`,
`
<${projectName}-btn>${projectName}-btn>
<${projectName}-btn-standalone>${projectName}-btn-standalone>
@@ -239,7 +239,7 @@ function useLibInApp(projectName: string, appName: string, libName: string) {
);
const btnModuleName = names(libName).className;
updateFile(
- `${appName}/src/app/app.component.scss`,
+ `${appName}/src/app/app.scss`,
`
@use 'styleguide' as *;
@@ -248,20 +248,20 @@ h1 {
}`
);
updateFile(
- `${appName}/src/app/app.module.ts`,
+ `${appName}/src/app/app-module.ts`,
`
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import {${btnModuleName}Module} from "@${projectName}/${libName}";
-import { AppComponent } from './app.component';
-import { NxWelcomeComponent } from './nx-welcome.component';
+import { App } from './app';
+import { NxWelcome } from './nx-welcome';
@NgModule({
- declarations: [AppComponent, NxWelcomeComponent],
+ declarations: [App, NxWelcome],
imports: [BrowserModule, ${btnModuleName}Module],
providers: [],
- bootstrap: [AppComponent],
+ bootstrap: [App],
})
export class AppModule {}
`
@@ -328,25 +328,25 @@ describe(InputComponent.name, () => {
);
createFile(
- `${libName}/src/lib/input-standalone/input-standalone.component.cy.ts`,
+ `${libName}/src/lib/input-standalone/input-standalone.cy.ts`,
`
import { MountConfig } from 'cypress/angular';
-import { InputStandaloneComponent } from './input-standalone.component';
+import { InputStandalone } from './input-standalone';
-describe(InputStandaloneComponent.name, () => {
- const config: MountConfig = {
+describe(InputStandalone.name, () => {
+ const config: MountConfig = {
declarations: [],
imports: [],
providers: [],
};
it('renders', () => {
- cy.mount(InputStandaloneComponent, config);
+ cy.mount(InputStandalone, config);
// make sure tailwind isn't getting applied
cy.get('label').should('have.css', 'color', 'rgb(0, 0, 0)');
});
it('should be readonly', () => {
- cy.mount(InputStandaloneComponent, {
+ cy.mount(InputStandalone, {
...config,
componentProperties: {
readOnly: true,
@@ -367,19 +367,19 @@ function useBuildableLibInLib(
const buildLibNames = names(buildableLibName);
// use the buildable lib in lib so now buildableLib has an indirect dep on app
updateFile(
- `${libName}/src/lib/btn-standalone/btn-standalone.component.ts`,
+ `${libName}/src/lib/btn-standalone/btn-standalone.ts`,
`
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
-import { InputStandaloneComponent } from '@${projectName}/${buildLibNames.fileName}';
+import { InputStandalone } from '@${projectName}/${buildLibNames.fileName}';
@Component({
selector: '${projectName}-btn-standalone',
standalone: true,
- imports: [CommonModule, InputStandaloneComponent],
+ imports: [CommonModule, InputStandalone],
template: '${projectName} <${projectName}-input-standalone>${projectName}-input-standalone>',
styles: [],
})
-export class BtnStandaloneComponent {
+export class BtnStandalone {
@Input() text = 'something';
}
`
@@ -394,7 +394,7 @@ function updateBuilableLibTestsToAssertAppStyles(
removeFile(`${buildableLibName}/src/lib/input/input.component.cy.ts`);
updateFile(
- `${buildableLibName}/src/lib/input-standalone/input-standalone.component.cy.ts`,
+ `${buildableLibName}/src/lib/input-standalone/input-standalone.cy.ts`,
(content) => {
// app styles should now apply
return content.replace('rgb(34, 197, 94)', 'rgb(255, 192, 203)');
diff --git a/e2e/angular/src/misc.test.ts b/e2e/angular/src/misc.test.ts
index 4c171ed6c3..f6fe69fe35 100644
--- a/e2e/angular/src/misc.test.ts
+++ b/e2e/angular/src/misc.test.ts
@@ -46,10 +46,8 @@ describe('Move Angular Project', () => {
expect(moveOutput).toContain(`CREATE ${newPath}/src/main.ts`);
expect(moveOutput).toContain(`CREATE ${newPath}/src/styles.css`);
expect(moveOutput).toContain(`CREATE ${newPath}/src/test-setup.ts`);
- expect(moveOutput).toContain(
- `CREATE ${newPath}/src/app/app.component.html`
- );
- expect(moveOutput).toContain(`CREATE ${newPath}/src/app/app.component.ts`);
+ expect(moveOutput).toContain(`CREATE ${newPath}/src/app/app.html`);
+ expect(moveOutput).toContain(`CREATE ${newPath}/src/app/app.ts`);
expect(moveOutput).toContain(`CREATE ${newPath}/src/app/app.config.ts`);
});
@@ -105,7 +103,7 @@ describe('Move Angular Project', () => {
runCLI(`generate @nx/angular:lib ${lib2} --no-standalone --no-interactive`);
updateFile(
- `${lib2}/src/lib/${lib2}.module.ts`,
+ `${lib2}/src/lib/${lib2}-module.ts`,
`import { ${classify(lib1)}Module } from '@${proj}/${lib1}';
export class ExtendedModule extends ${classify(lib1)}Module { }`
@@ -122,7 +120,7 @@ describe('Move Angular Project', () => {
expect(moveOutput).toContain(`CREATE ${testSetupPath}`);
checkFilesExist(testSetupPath);
- const modulePath = `${newPath}/src/lib/shared-${lib1}.module.ts`;
+ const modulePath = `${newPath}/src/lib/shared-${lib1}-module.ts`;
expect(moveOutput).toContain(`CREATE ${modulePath}`);
checkFilesExist(modulePath);
const moduleFile = readFile(modulePath);
@@ -132,12 +130,12 @@ describe('Move Angular Project', () => {
expect(moveOutput).toContain(`CREATE ${indexPath}`);
checkFilesExist(indexPath);
const index = readFile(indexPath);
- expect(index).toContain(`export * from './lib/shared-${lib1}.module'`);
+ expect(index).toContain(`export * from './lib/shared-${lib1}-module'`);
/**
* Check that the import in lib2 has been updated
*/
- const lib2FilePath = `${lib2}/src/lib/${lib2}.module.ts`;
+ const lib2FilePath = `${lib2}/src/lib/${lib2}-module.ts`;
const lib2File = readFile(lib2FilePath);
expect(lib2File).toContain(
`import { ${newModule} } from '@${proj}/shared-${lib1}';`
diff --git a/e2e/angular/src/module-federation.rspack.test.ts b/e2e/angular/src/module-federation.rspack.test.ts
index cc56b9c709..e8585e588e 100644
--- a/e2e/angular/src/module-federation.rspack.test.ts
+++ b/e2e/angular/src/module-federation.rspack.test.ts
@@ -2,14 +2,11 @@ import { names } from '@nx/devkit';
import {
checkFilesExist,
cleanupProject,
- killPorts,
killProcessAndPorts,
newProject,
readFile,
- readJson,
runCLI,
runCommandUntil,
- runE2ETests,
uniq,
updateFile,
updateJson,
@@ -63,8 +60,8 @@ describe('Angular Module Federation', () => {
// check files are generated without the layout directory ("apps/")
checkFilesExist(
- `${hostApp}/src/app/app.module.ts`,
- `${remoteApp1}/src/app/app.module.ts`
+ `${hostApp}/src/app/app-module.ts`,
+ `${remoteApp1}/src/app/app-module.ts`
);
// check default generated host is built successfully
@@ -96,26 +93,26 @@ describe('Angular Module Federation', () => {
// update host & remote files to use shared library
updateFile(
- `${hostApp}/src/app/app.module.ts`,
+ `${hostApp}/src/app/app-module.ts`,
`import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ${
names(wildcardLib).className
}Module } from '@${proj}/${wildcardLib}/${
names(secondaryEntry).fileName
- }.module';
+ }-module';
import { ${
names(sharedLib).className
}Module } from '@${proj}/${sharedLib}';
import { ${
names(secondaryEntry).className
}Module } from '@${proj}/${sharedLib}/${secondaryEntry}';
- import { AppComponent } from './app.component';
- import { NxWelcomeComponent } from './nx-welcome.component';
+ import { App } from './app';
+ import { NxWelcome } from './nx-welcome';
import { RouterModule } from '@angular/router';
@NgModule({
- declarations: [AppComponent, NxWelcomeComponent],
+ declarations: [App, NxWelcome],
imports: [
BrowserModule,
${names(sharedLib).className}Module,
@@ -134,13 +131,13 @@ describe('Angular Module Federation', () => {
),
],
providers: [],
- bootstrap: [AppComponent],
+ bootstrap: [App],
})
export class AppModule {}
`
);
updateFile(
- `${remoteApp1}/src/app/remote-entry/entry.module.ts`,
+ `${remoteApp1}/src/app/remote-entry/entry-module.ts`,
`import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
@@ -148,18 +145,18 @@ describe('Angular Module Federation', () => {
import { ${
names(secondaryEntry).className
}Module } from '@${proj}/${sharedLib}/${secondaryEntry}';
- import { RemoteEntryComponent } from './entry.component';
- import { NxWelcomeComponent } from './nx-welcome.component';
+ import { RemoteEntry } from './entry';
+ import { NxWelcome } from './nx-welcome';
@NgModule({
- declarations: [RemoteEntryComponent, NxWelcomeComponent],
+ declarations: [RemoteEntry, NxWelcome],
imports: [
CommonModule,
${names(sharedLib).className}Module,
RouterModule.forChild([
{
path: '',
- component: RemoteEntryComponent,
+ component: RemoteEntry,
},
]),
],
diff --git a/e2e/angular/src/module-federation.test.ts b/e2e/angular/src/module-federation.test.ts
index 45f95c31e1..eb78d0176a 100644
--- a/e2e/angular/src/module-federation.test.ts
+++ b/e2e/angular/src/module-federation.test.ts
@@ -49,8 +49,8 @@ describe('Angular Module Federation', () => {
// check files are generated without the layout directory ("apps/")
checkFilesExist(
- `${hostApp}/src/app/app.module.ts`,
- `${remoteApp1}/src/app/app.module.ts`
+ `${hostApp}/src/app/app-module.ts`,
+ `${remoteApp1}/src/app/app-module.ts`
);
// check default generated host is built successfully
@@ -80,26 +80,26 @@ describe('Angular Module Federation', () => {
// update host & remote files to use shared library
updateFile(
- `${hostApp}/src/app/app.module.ts`,
+ `${hostApp}/src/app/app-module.ts`,
`import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ${
names(wildcardLib).className
}Module } from '@${proj}/${wildcardLib}/${
- names(secondaryEntry).fileName
- }.module';
+ names(wildcardLib).fileName
+ }-module';
import { ${
names(sharedLib).className
}Module } from '@${proj}/${sharedLib}';
import { ${
names(secondaryEntry).className
}Module } from '@${proj}/${sharedLib}/${secondaryEntry}';
- import { AppComponent } from './app.component';
- import { NxWelcomeComponent } from './nx-welcome.component';
+ import { App } from './app';
+ import { NxWelcome } from './nx-welcome';
import { RouterModule } from '@angular/router';
@NgModule({
- declarations: [AppComponent, NxWelcomeComponent],
+ declarations: [App, NxWelcome],
imports: [
BrowserModule,
${names(sharedLib).className}Module,
@@ -118,13 +118,13 @@ describe('Angular Module Federation', () => {
),
],
providers: [],
- bootstrap: [AppComponent],
+ bootstrap: [App],
})
export class AppModule {}
`
);
updateFile(
- `${remoteApp1}/src/app/remote-entry/entry.module.ts`,
+ `${remoteApp1}/src/app/remote-entry/entry-module.ts`,
`import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
@@ -132,18 +132,18 @@ describe('Angular Module Federation', () => {
import { ${
names(secondaryEntry).className
}Module } from '@${proj}/${sharedLib}/${secondaryEntry}';
- import { RemoteEntryComponent } from './entry.component';
- import { NxWelcomeComponent } from './nx-welcome.component';
+ import { RemoteEntry } from './entry';
+ import { NxWelcome } from './nx-welcome';
@NgModule({
- declarations: [RemoteEntryComponent, NxWelcomeComponent],
+ declarations: [RemoteEntry, NxWelcome],
imports: [
CommonModule,
${names(sharedLib).className}Module,
RouterModule.forChild([
{
path: '',
- component: RemoteEntryComponent,
+ component: RemoteEntry,
},
]),
],
@@ -301,7 +301,7 @@ test('renders remotes', async ({ page }) => {
// Update Host to use the module
updateFile(
- `${host}/src/app/app.component.ts`,
+ `${host}/src/app/app.ts`,
`
import { Component } from '@angular/core';
import { isEven } from '${remote}/${module}';
@@ -311,7 +311,7 @@ test('renders remotes', async ({ page }) => {
template: \`
{{title}}
\`,
standalone: true
})
- export class AppComponent {
+ export class App {
title = \`shell is \${isEven(2) ? 'even' : 'odd'}\`;
}`
);
@@ -373,7 +373,7 @@ test('renders remotes', async ({ page }) => {
// Update Host to use the module
updateFile(
- `${remote}/src/app/remote-entry/entry.component.ts`,
+ `${remote}/src/app/remote-entry/entry.ts`,
`
import { Component } from '@angular/core';
import { isEven } from '${childRemote}/${module}';
@@ -383,7 +383,7 @@ test('renders remotes', async ({ page }) => {
template: \`
{{title}}
\`,
standalone: true
})
- export class RemoteEntryComponent {
+ export class RemoteEntry {
title = \`shell is \${isEven(2) ? 'even' : 'odd'}\`;
}`
);
@@ -398,7 +398,7 @@ test('renders remotes', async ({ page }) => {
remotes: ['${childRemote}'],
exposes: {
'./Routes': '${remote}/src/app/remote-entry/entry.routes.ts',
- './Module': '${remote}/src/app/remote-entry/entry.component.ts',
+ './Module': '${remote}/src/app/remote-entry/entry.ts',
},
};
diff --git a/e2e/angular/src/ng-add.test.ts b/e2e/angular/src/ng-add.test.ts
index 5a500ae193..0dc85b76ed 100644
--- a/e2e/angular/src/ng-add.test.ts
+++ b/e2e/angular/src/ng-add.test.ts
@@ -115,7 +115,7 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
'.prettierrc',
`apps/${project}/src/main.ts`,
`apps/${project}/src/app/app.config.ts`,
- `apps/${project}/src/app/app.component.ts`,
+ `apps/${project}/src/app/app.ts`,
`apps/${project}/src/app/app.routes.ts`
);
@@ -178,10 +178,10 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
const projectConfig = readJson(`apps/${project}/project.json`);
expect(projectConfig.sourceRoot).toEqual(`apps/${project}/src`);
expect(projectConfig.targets.build).toStrictEqual({
- executor: '@angular-devkit/build-angular:application',
+ executor: '@angular/build:application',
+ outputs: ['{options.outputPath}'],
options: {
- outputPath: `dist/apps/${project}`,
- index: `apps/${project}/src/index.html`,
+ outputPath: `dist/${project}`,
browser: `apps/${project}/src/main.ts`,
polyfills: [`zone.js`],
tsConfig: `apps/${project}/tsconfig.app.json`,
@@ -214,7 +214,7 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
defaultConfiguration: 'production',
});
expect(projectConfig.targets.serve).toEqual({
- executor: '@angular-devkit/build-angular:dev-server',
+ executor: '@angular/build:dev-server',
configurations: {
production: { buildTarget: `${project}:build:production` },
development: { buildTarget: `${project}:build:development` },
@@ -222,7 +222,7 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
defaultConfiguration: 'development',
});
expect(projectConfig.targets.test).toStrictEqual({
- executor: '@angular-devkit/build-angular:karma',
+ executor: '@angular/build:karma',
options: {
polyfills: [`zone.js`, `zone.js/testing`],
tsConfig: `apps/${project}/tsconfig.spec.json`,
@@ -249,7 +249,7 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
});
runCLI(`build ${project} --configuration production --outputHashing none`);
- checkFilesExist(`dist/apps/${project}/browser/main.js`);
+ checkFilesExist(`dist/${project}/browser/main.js`);
});
it('should handle a workspace with cypress v9', () => {
@@ -436,7 +436,7 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
expect(output).toContain(
`Successfully ran target build for project ${project}`
);
- checkFilesExist(`dist/apps/${project}/browser/main.js`);
+ checkFilesExist(`dist/${project}/browser/main.js`);
output = runCLI(`build ${project} --outputHashing none`);
expect(output).toContain(
@@ -454,7 +454,7 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
expect(output).toContain(
`Successfully ran target build for project ${app1}`
);
- checkFilesExist(`dist/apps/${app1}/browser/main.js`);
+ checkFilesExist(`dist/${app1}/browser/main.js`);
output = runCLI(`build ${app1} --outputHashing none`);
expect(output).toContain(
diff --git a/e2e/angular/src/ngrx.test.ts b/e2e/angular/src/ngrx.test.ts
index e58dab427c..7dc23c94af 100644
--- a/e2e/angular/src/ngrx.test.ts
+++ b/e2e/angular/src/ngrx.test.ts
@@ -26,7 +26,7 @@ describe('Angular Package', () => {
// Generate root ngrx state management
runCLI(
- `generate @nx/angular:ngrx users --parent=${myapp}/src/app/app.module.ts --root --minimal=false`
+ `generate @nx/angular:ngrx users --parent=${myapp}/src/app/app-module.ts --root --minimal=false`
);
const packageJson = readJson('package.json');
expect(packageJson.dependencies['@ngrx/store']).toBeDefined();
@@ -38,7 +38,7 @@ describe('Angular Package', () => {
// Generate feature library and ngrx state within that library
runCLI(`g @nx/angular:lib ${mylib} --prefix=fl --no-standalone`);
runCLI(
- `generate @nx/angular:ngrx flights --parent=${mylib}/src/lib/${mylib}.module.ts --facade`
+ `generate @nx/angular:ngrx flights --parent=${mylib}/src/lib/${mylib}-module.ts --facade`
);
expect(runCLI(`build ${myapp}`)).toMatch(/main-[a-zA-Z0-9]+\.js/);
@@ -57,7 +57,7 @@ describe('Angular Package', () => {
// Generate root ngrx state management
runCLI(
- `generate @nx/angular:ngrx users --parent=${myapp}/src/app/app.module.ts --root`
+ `generate @nx/angular:ngrx users --parent=${myapp}/src/app/app-module.ts --root`
);
const packageJson = readJson('package.json');
expect(packageJson.dependencies['@ngrx/entity']).toBeDefined();
@@ -73,7 +73,7 @@ describe('Angular Package', () => {
const flags = `--facade --barrels`;
runCLI(
- `generate @nx/angular:ngrx flights --parent=${mylib}/src/lib/${mylib}.module.ts ${flags}`
+ `generate @nx/angular:ngrx flights --parent=${mylib}/src/lib/${mylib}-module.ts ${flags}`
);
expect(runCLI(`build ${myapp}`)).toMatch(/main-[a-zA-Z0-9]+\.js/);
@@ -92,7 +92,7 @@ describe('Angular Package', () => {
// Generate root ngrx state management
runCLI(
- `generate @nx/angular:ngrx users --parent=${myapp}/src/app/app.module.ts --root`
+ `generate @nx/angular:ngrx users --parent=${myapp}/src/app/app-module.ts --root`
);
const packageJson = readJson('package.json');
expect(packageJson.dependencies['@ngrx/entity']).toBeDefined();
@@ -108,7 +108,7 @@ describe('Angular Package', () => {
const flags = `--facade --barrels`;
runCLI(
- `generate @nx/angular:ngrx flights --module=${mylib}/src/lib/${mylib}.module.ts ${flags}`
+ `generate @nx/angular:ngrx flights --module=${mylib}/src/lib/${mylib}-module.ts ${flags}`
);
expect(runCLI(`build ${myapp}`)).toMatch(/main-[a-zA-Z0-9]+\.js/);
diff --git a/e2e/angular/src/projects.test.ts b/e2e/angular/src/projects.test.ts
index 861b30b1c6..dfd92eb8a7 100644
--- a/e2e/angular/src/projects.test.ts
+++ b/e2e/angular/src/projects.test.ts
@@ -40,26 +40,21 @@ describe('Angular Projects', () => {
`generate @nx/angular:app ${esbuildApp} --bundler=esbuild --no-standalone --no-interactive`
);
runCLI(`generate @nx/angular:lib ${lib1} --no-interactive`);
- app1DefaultModule = readFile(`${app1}/src/app/app.module.ts`);
- app1DefaultComponentTemplate = readFile(
- `${app1}/src/app/app.component.html`
- );
- esbuildAppDefaultModule = readFile(`${esbuildApp}/src/app/app.module.ts`);
+ app1DefaultModule = readFile(`${app1}/src/app/app-module.ts`);
+ app1DefaultComponentTemplate = readFile(`${app1}/src/app/app.html`);
+ esbuildAppDefaultModule = readFile(`${esbuildApp}/src/app/app-module.ts`);
esbuildAppDefaultComponentTemplate = readFile(
- `${esbuildApp}/src/app/app.component.html`
+ `${esbuildApp}/src/app/app.html`
);
esbuildAppDefaultProjectConfig = readFile(`${esbuildApp}/project.json`);
});
afterEach(() => {
- updateFile(`${app1}/src/app/app.module.ts`, app1DefaultModule);
+ updateFile(`${app1}/src/app/app-module.ts`, app1DefaultModule);
+ updateFile(`${app1}/src/app/app.html`, app1DefaultComponentTemplate);
+ updateFile(`${esbuildApp}/src/app/app-module.ts`, esbuildAppDefaultModule);
updateFile(
- `${app1}/src/app/app.component.html`,
- app1DefaultComponentTemplate
- );
- updateFile(`${esbuildApp}/src/app/app.module.ts`, esbuildAppDefaultModule);
- updateFile(
- `${esbuildApp}/src/app/app.component.html`,
+ `${esbuildApp}/src/app/app.html`,
esbuildAppDefaultComponentTemplate
);
updateFile(`${esbuildApp}/project.json`, esbuildAppDefaultProjectConfig);
@@ -79,24 +74,24 @@ describe('Angular Projects', () => {
);
updateFile(
- `${app1}/src/app/app.module.ts`,
+ `${app1}/src/app/app-module.ts`,
`
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
- import { AppComponent } from './app.component';
+ import { App } from './app';
import { appRoutes } from './app.routes';
- import { NxWelcomeComponent } from './nx-welcome.component';
- import { ${names(lib1).className}Component } from '@${proj}/${lib1}';
+ import { NxWelcome } from './nx-welcome';
+ import { ${names(lib1).className} } from '@${proj}/${lib1}';
@NgModule({
imports: [
BrowserModule,
RouterModule.forRoot(appRoutes, { initialNavigation: 'enabledBlocking' }),
- ${names(lib1).className}Component
+ ${names(lib1).className}
],
- declarations: [AppComponent, NxWelcomeComponent],
- bootstrap: [AppComponent]
+ declarations: [App, NxWelcome],
+ bootstrap: [App]
})
export class AppModule {}
`
@@ -193,7 +188,7 @@ describe('Angular Projects', () => {
// External HTML template file
const templateWhichFailsBananaInBoxLintCheck = ``;
updateFile(
- `${app1}/src/app/app.component.html`,
+ `${app1}/src/app/app.html`,
templateWhichFailsBananaInBoxLintCheck
);
// Inline template within component.ts file
@@ -216,9 +211,7 @@ describe('Angular Projects', () => {
const appLintStdOut = runCLI(`lint ${app1}`, {
silenceError: true,
});
- expect(appLintStdOut).toContain(
- normalize(`${app1}/src/app/app.component.html`)
- );
+ expect(appLintStdOut).toContain(normalize(`${app1}/src/app/app.html`));
expect(appLintStdOut).toContain(`1:6`);
expect(appLintStdOut).toContain(`Invalid binding syntax`);
expect(appLintStdOut).toContain(
@@ -249,53 +242,53 @@ describe('Angular Projects', () => {
// update the app module to include a ref to the buildable lib
updateFile(
- `${app1}/src/app/app.module.ts`,
+ `${app1}/src/app/app-module.ts`,
`
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
- import { AppComponent } from './app.component';
+ import { App } from './app';
import { appRoutes } from './app.routes';
- import { NxWelcomeComponent } from './nx-welcome.component';
+ import { NxWelcome } from './nx-welcome';
import {${
names(buildableLib).className
}Module} from '@${proj}/${buildableLib}';
@NgModule({
- declarations: [AppComponent, NxWelcomeComponent],
+ declarations: [App, NxWelcome],
imports: [
BrowserModule,
RouterModule.forRoot(appRoutes, { initialNavigation: 'enabledBlocking' }),
${names(buildableLib).className}Module
],
providers: [],
- bootstrap: [AppComponent],
+ bootstrap: [App],
})
export class AppModule {}
`
);
updateFile(
- `${esbuildApp}/src/app/app.module.ts`,
+ `${esbuildApp}/src/app/app-module.ts`,
`
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
- import { AppComponent } from './app.component';
+ import { App } from './app';
import { appRoutes } from './app.routes';
- import { NxWelcomeComponent } from './nx-welcome.component';
+ import { NxWelcome } from './nx-welcome';
import {${
names(buildableLib).className
}Module} from '@${proj}/${buildableLib}';
@NgModule({
- declarations: [AppComponent, NxWelcomeComponent],
+ declarations: [App, NxWelcome],
imports: [
BrowserModule,
RouterModule.forRoot(appRoutes, { initialNavigation: 'enabledBlocking' }),
${names(buildableLib).className}Module
],
providers: [],
- bootstrap: [AppComponent],
+ bootstrap: [App],
})
export class AppModule {}
`
@@ -303,7 +296,7 @@ describe('Angular Projects', () => {
// update the buildable lib module to include a ref to the buildable child lib
updateFile(
- `${buildableLib}/src/lib/${names(buildableLib).fileName}.module.ts`,
+ `${buildableLib}/src/lib/${names(buildableLib).fileName}-module.ts`,
`
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@@ -333,6 +326,7 @@ describe('Angular Projects', () => {
config.targets.build.options = {
...config.targets.build.options,
outputPath: `dist/${esbuildApp}`,
+ index: `${esbuildApp}/src/index.html`,
main: config.targets.build.options.browser,
browser: undefined,
buildLibsFromSource: false,
@@ -397,7 +391,7 @@ describe('Angular Projects', () => {
export default replaceTextPlugin;`
);
updateFile(
- `${esbuildApp}/src/app/app.component.ts`,
+ `${esbuildApp}/src/app/app.ts`,
`import { Component } from '@angular/core';
declare const BUILD_DEFINED: string;
@@ -405,9 +399,9 @@ describe('Angular Projects', () => {
@Component({
selector: 'app-root',
standalone: false,
- templateUrl: './app.component.html',
+ templateUrl: './app.html',
})
- export class AppComponent {
+ export class App {
title = 'esbuild-app';
buildDefined = BUILD_DEFINED;
}`
@@ -437,6 +431,7 @@ describe('Angular Projects', () => {
...config.targets.build.options,
main: config.targets.build.options.browser,
browser: undefined,
+ index: `${esbuildApp}/src/index.html`,
};
return config;
});
@@ -511,7 +506,7 @@ describe('Angular Projects', () => {
})
export class ${names(lib).className}Module {}`;
- updateFile(`${lib}/src/lib/${lib}.module.ts`, moduleContent);
+ updateFile(`${lib}/src/lib/${lib}-module.ts`, moduleContent);
// ACT
const buildOutput = runCLI(`build ${lib}`, { env: { CI: 'false' } });
@@ -536,9 +531,7 @@ describe('Angular Projects', () => {
// using the project name as the directory when no directory is provided
checkFilesExist(
`${libName}/src/index.ts`,
- `${libName}/src/lib/${libName.split('/')[1]}/${
- libName.split('/')[1]
- }.component.ts`
+ `${libName}/src/lib/${libName.split('/')[1]}/${libName.split('/')[1]}.ts`
);
// check build works
expect(() => runCLI(`build ${libName}`)).not.toThrow();
diff --git a/e2e/angular/src/tailwind.test.ts b/e2e/angular/src/tailwind.test.ts
index 3de3ca8bb3..b8e107c965 100644
--- a/e2e/angular/src/tailwind.test.ts
+++ b/e2e/angular/src/tailwind.test.ts
@@ -132,7 +132,7 @@ describe('Tailwind support', () => {
buttonBgColor: string = defaultButtonBgColor
) => {
updateFile(
- `${lib}/src/lib/foo.component.ts`,
+ `${lib}/src/lib/foo.ts`,
`import { Component } from '@angular/core';
@Component({
@@ -145,20 +145,20 @@ describe('Tailwind support', () => {
}
\`]
})
- export class FooComponent {}
+ export class Foo {}
`
);
updateFile(
- `${lib}/src/lib/${lib}.module.ts`,
+ `${lib}/src/lib/${lib}-module.ts`,
`import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
- import { FooComponent } from './foo.component';
+ import { Foo } from './foo';
@NgModule({
imports: [CommonModule],
- declarations: [FooComponent],
- exports: [FooComponent],
+ declarations: [Foo],
+ exports: [Foo],
})
export class LibModule {}
`
@@ -166,8 +166,8 @@ describe('Tailwind support', () => {
updateFile(
`${lib}/src/index.ts`,
- `export * from './lib/foo.component';
- export * from './lib/${lib}.module';
+ `export * from './lib/foo';
+ export * from './lib/${lib}-module';
`
);
};
@@ -180,7 +180,7 @@ describe('Tailwind support', () => {
const builtComponentContent = readFile(
isPublishable
? `dist/${lib}/fesm2022/${project}-${lib}.mjs`
- : `dist/${lib}/esm2022/lib/foo.component.mjs`
+ : `dist/${lib}/esm2022/lib/foo.js`
);
let expectedStylesRegex = new RegExp(
`styles: \\[\\"\\.custom\\-btn(\\[_ngcontent\\-%COMP%\\])?{margin:${libSpacing.md};padding:${libSpacing.sm}}(\\\\n)?\\"\\]`
@@ -356,30 +356,30 @@ describe('Tailwind support', () => {
spacing.projectVariant1
);
updateFile(
- `${appName}/src/app/app.module.ts`,
+ `${appName}/src/app/app-module.ts`,
`import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { LibModule as LibModule1 } from '@${project}/${buildLibWithTailwind.name}';
import { LibModule as LibModule2 } from '@${project}/${pubLibWithTailwind.name}';
- import { AppComponent } from './app.component';
+ import { App } from './app';
@NgModule({
- declarations: [AppComponent],
- imports: [BrowserModule, LibModule1, LibModule2],
+ declarations: [],
+ imports: [BrowserModule, App, LibModule1, LibModule2],
providers: [],
- bootstrap: [AppComponent],
+ bootstrap: [App],
})
export class AppModule {}
`
);
updateFile(
- `${appName}/src/app/app.component.html`,
+ `${appName}/src/app/app.html`,
``
);
updateFile(
- `${appName}/src/app/app.component.css`,
+ `${appName}/src/app/app.css`,
`.custom-btn {
@apply m-md p-sm;
}`
diff --git a/e2e/nx-init/src/nx-init-angular.test.ts b/e2e/nx-init/src/nx-init-angular.test.ts
index 90b39715da..5c482724f8 100644
--- a/e2e/nx-init/src/nx-init-angular.test.ts
+++ b/e2e/nx-init/src/nx-init-angular.test.ts
@@ -85,7 +85,7 @@ describe('nx init (Angular CLI - legacy)', () => {
expect(coldBuildOutput).toContain(
`Successfully ran target build for project ${project}`
);
- checkFilesExist(`dist/apps/${project}/browser/main.js`);
+ checkFilesExist(`dist/${project}/browser/main.js`);
// run build again to check is coming from cache
const cachedBuildOutput = runCLI(`build ${project} --outputHashing none`);
diff --git a/e2e/release/src/release-publishable-libraries.test.ts b/e2e/release/src/release-publishable-libraries.test.ts
index a5372d1692..888af9994c 100644
--- a/e2e/release/src/release-publishable-libraries.test.ts
+++ b/e2e/release/src/release-publishable-libraries.test.ts
@@ -230,7 +230,6 @@ describe('release publishable libraries', () => {
XXX.XXX kb fesm2022/proj-{project-name}.mjs
XXX.XXX kb fesm2022/proj-{project-name}.mjs.map
XXB index.d.ts
- XXB lib/{project-name}/{project-name}.component.d.ts
XXXB package.json
=== Tarball Details ===
name: @proj/{project-name}
diff --git a/e2e/workspace-create/src/create-nx-workspace.test.ts b/e2e/workspace-create/src/create-nx-workspace.test.ts
index 54fedd2444..be5931ddca 100644
--- a/e2e/workspace-create/src/create-nx-workspace.test.ts
+++ b/e2e/workspace-create/src/create-nx-workspace.test.ts
@@ -38,7 +38,7 @@ describe('create-nx-workspace', () => {
checkFilesExist('package.json');
checkFilesExist('project.json');
- checkFilesExist('src/app/app.module.ts');
+ checkFilesExist('src/app/app-module.ts');
checkFilesDoNotExist('src/app/app.routes.ts');
expectCodeIsFormatted();
});
@@ -61,7 +61,7 @@ describe('create-nx-workspace', () => {
checkFilesExist('package.json');
checkFilesExist('project.json');
checkFilesExist('src/app/app.routes.ts');
- checkFilesDoNotExist('src/app/app.module.ts');
+ checkFilesDoNotExist('src/app/app-module.ts');
expectCodeIsFormatted();
});
diff --git a/package.json b/package.json
index 0360547a4b..44ebcfb856 100644
--- a/package.json
+++ b/package.json
@@ -28,19 +28,21 @@
},
"devDependencies": {
"@actions/core": "^1.10.0",
- "@angular-devkit/architect": "0.1902.0",
- "@angular-devkit/build-angular": "19.2.0",
- "@angular-devkit/core": "19.2.0",
- "@angular-devkit/schematics": "19.2.0",
+ "@angular-devkit/architect": "0.2000.0-rc.3",
+ "@angular-devkit/build-angular": "20.0.0-rc.3",
+ "@angular-devkit/core": "20.0.0-rc.3",
+ "@angular-devkit/schematics": "20.0.0-rc.3",
"@angular-eslint/eslint-plugin": "19.2.0",
"@angular-eslint/eslint-plugin-template": "19.2.0",
"@angular-eslint/template-parser": "19.2.0",
- "@angular/cli": "~19.2.0",
- "@angular/common": "19.2.0",
- "@angular/compiler": "19.2.0",
- "@angular/compiler-cli": "19.2.0",
- "@angular/core": "19.2.0",
- "@angular/router": "19.2.0",
+ "@angular/build": "20.0.0-rc.3",
+ "@angular/cli": "20.0.0-rc.3",
+ "@angular/common": "20.0.0-rc.2",
+ "@angular/compiler": "20.0.0-rc.2",
+ "@angular/compiler-cli": "20.0.0-rc.2",
+ "@angular/core": "20.0.0-rc.2",
+ "@angular/platform-browser": "20.0.0-rc.2",
+ "@angular/router": "20.0.0-rc.2",
"@astrojs/check": "^0.7.0",
"@astrojs/react": "^3.6.2",
"@babel/core": "^7.23.2",
@@ -114,7 +116,7 @@
"@rspack/dev-server": "1.1.1",
"@rspack/plugin-minify": "^0.7.5",
"@rspack/plugin-react-refresh": "^1.0.0",
- "@schematics/angular": "~19.2.0",
+ "@schematics/angular": "20.0.0-rc.3",
"@storybook/addon-essentials": "8.4.6",
"@storybook/addon-interactions": "8.4.6",
"@storybook/core-server": "8.4.6",
@@ -160,10 +162,10 @@
"@types/tmp": "^0.2.0",
"@types/yargs": "17.0.10",
"@types/yarnpkg__lockfile": "^1.1.5",
- "@typescript-eslint/eslint-plugin": "^8.19.0",
- "@typescript-eslint/rule-tester": "^8.19.0",
- "@typescript-eslint/type-utils": "^8.19.0",
- "@typescript-eslint/utils": "^8.19.0",
+ "@typescript-eslint/eslint-plugin": "^8.29.0",
+ "@typescript-eslint/rule-tester": "^8.29.0",
+ "@typescript-eslint/type-utils": "^8.29.0",
+ "@typescript-eslint/utils": "^8.29.0",
"@vitejs/plugin-react": "^4.2.0",
"@webcontainer/api": "1.5.1",
"@xstate/immer": "0.3.1",
@@ -264,7 +266,7 @@
"mini-css-extract-plugin": "~2.4.7",
"minimatch": "9.0.3",
"next-sitemap": "^3.1.10",
- "ng-packagr": "19.2.0",
+ "ng-packagr": "20.0.0-rc.1",
"npm-package-arg": "11.0.1",
"nuxt": "^3.10.0",
"nx": "21.1.0-beta.2",
@@ -276,7 +278,6 @@
"parse5": "4.0.0",
"picocolors": "^1.1.0",
"picomatch": "4.0.2",
- "piscina": "^4.4.0",
"postcss": "8.4.38",
"postcss-import": "~14.1.0",
"postcss-preset-env": "~7.5.0",
@@ -296,7 +297,7 @@
"rollup-plugin-copy": "^3.5.0",
"rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-typescript2": "^0.36.0",
- "rxjs": "^7.8.0",
+ "rxjs": "^7.8.2",
"sass": "1.55.0",
"sass-embedded": "1.85.1",
"sass-loader": "16.0.5",
@@ -319,14 +320,14 @@
"tsconfig-paths-webpack-plugin": "4.0.0",
"typedoc": "0.25.12",
"typedoc-plugin-markdown": "3.17.1",
- "typescript": "~5.7.2",
- "typescript-eslint": "^8.19.0",
+ "typescript": "~5.8.2",
+ "typescript-eslint": "^8.29.0",
"unist-builder": "^4.0.0",
"use-sync-external-store": "^1.2.0",
"verdaccio": "6.0.5",
"vite": "6.2.0",
"vitest": "3.0.5",
- "webpack": "5.98.0",
+ "webpack": "5.99.8",
"webpack-dev-server": "5.2.1",
"webpack-merge": "^5.8.0",
"webpack-node-externals": "^3.0.0",
diff --git a/packages/angular/.eslintrc.json b/packages/angular/.eslintrc.json
index fd61a7a4d0..d2cfa514e6 100644
--- a/packages/angular/.eslintrc.json
+++ b/packages/angular/.eslintrc.json
@@ -80,7 +80,6 @@
"@storybook/angular",
"@module-federation/node",
"@nguniversal/builders",
- "ng-packagr",
"injection-js",
"browserslist",
"cacache",
@@ -96,7 +95,8 @@
"stylus",
"tailwindcss",
"cypress",
- "esbuild"
+ "esbuild",
+ "piscina"
]
}
]
diff --git a/packages/angular/docs/component-examples.md b/packages/angular/docs/component-examples.md
index 69959968eb..ca083778e9 100644
--- a/packages/angular/docs/component-examples.md
+++ b/packages/angular/docs/component-examples.md
@@ -3,50 +3,60 @@
{% tabs %}
{% tab label="Simple Component" %}
-Generate a component named `MyComponent` at `apps/my-app/src/lib/my-component/my-component.component.ts`:
+Generate a component named `Card` at `apps/my-app/src/lib/card/card.ts`:
```bash
-nx g @nx/angular:component apps/my-app/src/lib/my-component/my-component.ts
+nx g @nx/angular:component apps/my-app/src/lib/card/card.ts
```
{% /tab %}
{% tab label="Without Providing the File Extension" %}
-Generate a component named `MyComponent` at `apps/my-app/src/lib/my-component/my-component.component.ts`:
+Generate a component named `Card` at `apps/my-app/src/lib/card/card.ts`:
```bash
-nx g @nx/angular:component apps/my-app/src/lib/my-component/my-component
+nx g @nx/angular:component apps/my-app/src/lib/card/card
```
{% /tab %}
{% tab label="With Different Symbol Name" %}
-Generate a component named `CustomComponent` at `apps/my-app/src/lib/my-component/my-component.component.ts`:
+Generate a component named `Custom` at `apps/my-app/src/lib/card/card.ts`:
```bash
-nx g @nx/angular:component apps/my-app/src/lib/my-component/my-component --name=custom
+nx g @nx/angular:component apps/my-app/src/lib/card/card --name=custom
+```
+
+{% /tab %}
+
+{% tab label="With a Component Type" %}
+
+Generate a component named `CardComponent` at `apps/my-app/src/lib/card/card.component.ts`:
+
+```bash
+nx g @nx/angular:component apps/my-app/src/lib/card/card --type=component
```
{% /tab %}
{% tab label="Single File Component" %}
-Create a component named `my-component` with inline styles and inline template:
+Create a component named `Card` with inline styles and inline template:
```bash
-nx g @nx/angular:component apps/my-app/src/lib/my-component/my-component --inlineStyle --inlineTemplate
+nx g @nx/angular:component apps/my-app/src/lib/card/card --inlineStyle --inlineTemplate
```
{% /tab %}
{% tab label="Component with OnPush Change Detection Strategy" %}
-Create a component named `my-component` with OnPush Change Detection Strategy:
+Create a component named `Card` with `OnPush` Change Detection Strategy:
```bash
-nx g @nx/angular:component apps/my-app/src/lib/my-component/my-component --changeDetection=OnPush
+nx g @nx/angular:component apps/my-app/src/lib/card/card --changeDetection=OnPush
```
{% /tab %}
diff --git a/packages/angular/docs/dev-server-examples.md b/packages/angular/docs/dev-server-examples.md
index be060890f0..d1ac3fdac0 100644
--- a/packages/angular/docs/dev-server-examples.md
+++ b/packages/angular/docs/dev-server-examples.md
@@ -50,12 +50,6 @@ module.exports = (config) => {
{% tab label="Providing HTTP request middleware function" %}
-{% callout type="warning" title="Overrides" }
-
-Available for workspaces using Angular version 17.0.0 or greater and with `build` targets using an esbuild-based executor.
-
-{% /callout %}
-
The executor accepts an `esbuildMiddleware` option that allows you to provide HTTP require middleware functions that will be used by the Vite development server.
```json {% fileName="apps/my-app/project.json" highlightLines=[8] %}
diff --git a/packages/angular/migrations.json b/packages/angular/migrations.json
index 9506b2fd42..e8d3ae2327 100644
--- a/packages/angular/migrations.json
+++ b/packages/angular/migrations.json
@@ -302,6 +302,47 @@
},
"description": "Change the data persistence operator imports to '@ngrx/router-store/data-persistence'.",
"factory": "./src/migrations/update-21-0-0/change-data-persistence-operators-imports-to-ngrx-router-store-data-persistence"
+ },
+ "update-angular-cli-version-20-0-0-rc-3": {
+ "cli": "nx",
+ "version": "21.2.0-beta.0",
+ "requires": {
+ "@angular/core": ">=20.0.0-rc.2"
+ },
+ "description": "Update the @angular/cli package version to 20.0.0-rc.3.",
+ "factory": "./src/migrations/update-21-2-0/update-angular-cli"
+ },
+ "migrate-provide-server-rendering-import": {
+ "version": "21.2.0-beta.0",
+ "requires": {
+ "@angular/core": ">=20.0.0-rc.2"
+ },
+ "description": "Migrate imports of `provideServerRendering` from `@angular/platform-server` to `@angular/ssr`.",
+ "factory": "./src/migrations/update-21-2-0/migrate-provide-server-rendering-import"
+ },
+ "replace-provide-server-routing": {
+ "version": "21.2.0-beta.0",
+ "requires": {
+ "@angular/core": ">=20.0.0-rc.2"
+ },
+ "description": "Replace `provideServerRouting` with `provideServerRendering` using `withRoutes`.",
+ "factory": "./src/migrations/update-21-2-0/replace-provide-server-routing"
+ },
+ "set-generator-defaults-for-previous-style-guide": {
+ "version": "21.2.0-beta.0",
+ "requires": {
+ "@angular/core": ">=20.0.0-rc.2"
+ },
+ "description": "Update the generator defaults to maintain the previous style guide behavior.",
+ "factory": "./src/migrations/update-21-2-0/set-generator-defaults-for-previous-style-guide"
+ },
+ "update-module-resolution": {
+ "version": "21.2.0-beta.0",
+ "requires": {
+ "@angular/core": ">=20.0.0-rc.2"
+ },
+ "description": "Update 'moduleResolution' to 'bundler' in TypeScript configurations. You can read more about this here: https://www.typescriptlang.org/tsconfig/#moduleResolution.",
+ "factory": "./src/migrations/update-21-2-0/update-module-resolution"
}
},
"packageJsonUpdates": {
@@ -1624,6 +1665,71 @@
"alwaysAddToPackageJson": false
}
}
+ },
+ "21.2.0": {
+ "version": "21.2.0-beta.0",
+ "x-prompt": "Do you want to update the Angular version to v20?",
+ "requires": {
+ "@angular/core": ">=19.2.0 <20.0.0-rc.2"
+ },
+ "packages": {
+ "@angular-devkit/build-angular": {
+ "version": "20.0.0-rc.3",
+ "alwaysAddToPackageJson": false
+ },
+ "@angular-devkit/core": {
+ "version": "20.0.0-rc.3",
+ "alwaysAddToPackageJson": false
+ },
+ "@angular-devkit/schematics": {
+ "version": "20.0.0-rc.3",
+ "alwaysAddToPackageJson": false
+ },
+ "@angular/build": {
+ "version": "20.0.0-rc.3",
+ "alwaysAddToPackageJson": false
+ },
+ "@angular/pwa": {
+ "version": "20.0.0-rc.3",
+ "alwaysAddToPackageJson": false
+ },
+ "@angular/ssr": {
+ "version": "20.0.0-rc.3",
+ "alwaysAddToPackageJson": false
+ },
+ "@schematics/angular": {
+ "version": "20.0.0-rc.3",
+ "alwaysAddToPackageJson": false
+ },
+ "@angular-devkit/architect": {
+ "version": "0.2000.0-rc.3",
+ "alwaysAddToPackageJson": false
+ },
+ "@angular-devkit/build-webpack": {
+ "version": "0.2000.0-rc.3",
+ "alwaysAddToPackageJson": false
+ },
+ "@angular/core": {
+ "version": "20.0.0-rc.2",
+ "alwaysAddToPackageJson": true
+ },
+ "@angular/material": {
+ "version": "20.0.0-rc.2",
+ "alwaysAddToPackageJson": false
+ },
+ "@angular/cdk": {
+ "version": "20.0.0-rc.2",
+ "alwaysAddToPackageJson": false
+ },
+ "@angular/google-maps": {
+ "version": "20.0.0-rc.2",
+ "alwaysAddToPackageJson": false
+ },
+ "ng-packagr": {
+ "version": "20.0.0-rc.1",
+ "alwaysAddToPackageJson": false
+ }
+ }
}
}
}
diff --git a/packages/angular/ng-package.json b/packages/angular/ng-package.json
index a7e877e944..d5527a2d8f 100644
--- a/packages/angular/ng-package.json
+++ b/packages/angular/ng-package.json
@@ -28,7 +28,6 @@
"magic-string",
"enquirer",
"find-cache-dir",
- "piscina",
"webpack"
],
"keepLifecycleScripts": true
diff --git a/packages/angular/package.json b/packages/angular/package.json
index a52e91479a..900b296db8 100644
--- a/packages/angular/package.json
+++ b/packages/angular/package.json
@@ -47,32 +47,44 @@
"migrations": "./migrations.json"
},
"dependencies": {
+ "@nx/devkit": "file:../devkit",
+ "@nx/eslint": "file:../eslint",
+ "@nx/js": "file:../js",
+ "@nx/module-federation": "file:../module-federation",
+ "@nx/rspack": "file:../rspack",
+ "@nx/web": "file:../web",
+ "@nx/webpack": "file:../webpack",
+ "@nx/workspace": "file:../workspace",
"@phenomnomnominal/tsquery": "~5.0.1",
"@typescript-eslint/type-utils": "^8.0.0",
"enquirer": "~2.3.6",
+ "magic-string": "~0.30.2",
"picocolors": "^1.1.0",
"picomatch": "4.0.2",
- "magic-string": "~0.30.2",
"semver": "^7.5.3",
"tslib": "^2.3.0",
- "webpack-merge": "^5.8.0",
- "@nx/devkit": "file:../devkit",
- "@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",
- "piscina": "^4.4.0"
+ "webpack-merge": "^5.8.0"
},
"peerDependencies": {
- "@angular-devkit/build-angular": ">= 17.0.0 < 20.0.0",
- "@angular-devkit/core": ">= 17.0.0 < 20.0.0",
- "@angular-devkit/schematics": ">= 17.0.0 < 20.0.0",
- "@schematics/angular": ">= 17.0.0 < 20.0.0",
+ "@angular/build": ">= 18.0.0 < 21.0.0",
+ "@angular-devkit/build-angular": ">= 18.0.0 < 21.0.0",
+ "@angular-devkit/core": ">= 18.0.0 < 21.0.0",
+ "@angular-devkit/schematics": ">= 18.0.0 < 21.0.0",
+ "@schematics/angular": ">= 18.0.0 < 21.0.0",
+ "ng-packagr": ">= 18.0.0 < 21.0.0",
"rxjs": "^6.5.3 || ^7.5.0"
},
+ "peerDependenciesMeta": {
+ "@angular/build": {
+ "optional": true
+ },
+ "@angular-devkit/build-angular": {
+ "optional": true
+ },
+ "ng-packagr": {
+ "optional": true
+ }
+ },
"publishConfig": {
"access": "public"
}
diff --git a/packages/angular/plugins/component-testing.ts b/packages/angular/plugins/component-testing.ts
index 16e72c2780..41a8cde7b6 100644
--- a/packages/angular/plugins/component-testing.ts
+++ b/packages/angular/plugins/component-testing.ts
@@ -195,7 +195,9 @@ function normalizeBuildTargetOptions(
},
buildContext
);
- const buildOptions = withSchemaDefaults(options);
+ const project =
+ buildContext.projectsConfigurations.projects[buildContext.projectName];
+ const buildOptions = withSchemaDefaults(options, project, buildContext.root);
// cypress creates a tsconfig if one isn't preset
// that contains all the support required for angular and component tests
@@ -305,9 +307,18 @@ Note: this may fail, setting the correct 'sourceRoot' for ${buildContext.project
};
}
-function withSchemaDefaults(options: any): BrowserBuilderSchema {
+function withSchemaDefaults(
+ options: any,
+ project: ProjectConfiguration,
+ workspaceRoot: string
+): BrowserBuilderSchema {
if (!options.main && !options.browser) {
- throw new Error('Missing executor options "main" and "browser"');
+ const sourceRoot =
+ project.sourceRoot ?? joinPathFragments(project.root, 'src');
+ options.browser = joinPathFragments(sourceRoot, 'main.ts');
+ if (!existsSync(join(workspaceRoot, options.browser))) {
+ throw new Error('Missing executor options "main" and "browser"');
+ }
}
if (!options.index) {
throw new Error('Missing executor options "index"');
diff --git a/packages/angular/src/builders/dev-server/dev-server.impl.ts b/packages/angular/src/builders/dev-server/dev-server.impl.ts
index 5d7d4a6d5b..eb9771d0e3 100644
--- a/packages/angular/src/builders/dev-server/dev-server.impl.ts
+++ b/packages/angular/src/builders/dev-server/dev-server.impl.ts
@@ -12,12 +12,13 @@ import { getRootTsConfigPath } from '@nx/js';
import type { DependentBuildableProjectNode } from '@nx/js/src/utils/buildable-libs-utils';
import { WebpackNxBuildCoordinationPlugin } from '@nx/webpack/src/plugins/webpack-nx-build-coordination-plugin';
import { existsSync } from 'fs';
+import { readNxJson } from 'nx/src/config/configuration';
import { isNpmProject } from 'nx/src/project-graph/operators';
import { readCachedProjectConfiguration } from 'nx/src/project-graph/project-graph';
import { relative } from 'path';
import { combineLatest, from } from 'rxjs';
import { switchMap } from 'rxjs/operators';
-import { getInstalledAngularVersionInfo } from '../../executors/utilities/angular-version-utils';
+import { assertBuilderPackageIsInstalled } from '../../executors/utilities/builder-package';
import {
loadIndexHtmlTransformer,
loadMiddleware,
@@ -31,13 +32,7 @@ import {
resolveIndexHtmlTransformer,
} from '../utilities/webpack';
import { normalizeOptions, validateOptions } from './lib';
-import type {
- NormalizedSchema,
- Schema,
- SchemaWithBrowserTarget,
-} from './schema';
-import { readNxJson } from 'nx/src/config/configuration';
-
+import type { NormalizedSchema, Schema } from './schema';
type BuildTargetOptions = {
tsConfig: string;
buildLibsFromSource?: boolean;
@@ -158,6 +153,7 @@ export function executeDevServerBuilder(
const delegateBuilderOptions = getDelegateBuilderOptions(options);
const isUsingWebpackBuilder = ![
+ '@angular/build:application',
'@angular-devkit/build-angular:application',
'@angular-devkit/build-angular:browser-esbuild',
'@nx/angular:application',
@@ -171,6 +167,7 @@ export function executeDevServerBuilder(
* handle `@nx/angular:*` executors.
*/
patchBuilderContext(context, !isUsingWebpackBuilder, parsedBuildTarget);
+ assertBuilderPackageIsInstalled('@angular-devkit/build-angular');
return combineLatest([
from(import('@angular-devkit/build-angular')),
@@ -259,14 +256,6 @@ function getDelegateBuilderOptions(
...options,
};
- const { major: angularMajorVersion } = getInstalledAngularVersionInfo();
- if (angularMajorVersion <= 17) {
- (
- delegateBuilderOptions as unknown as SchemaWithBrowserTarget
- ).browserTarget = delegateBuilderOptions.buildTarget;
- delete delegateBuilderOptions.buildTarget;
- }
-
// delete extra option not supported by the delegate builder
delete delegateBuilderOptions.buildLibsFromSource;
delete delegateBuilderOptions.watchDependencies;
diff --git a/packages/angular/src/builders/dev-server/lib/normalize-options.ts b/packages/angular/src/builders/dev-server/lib/normalize-options.ts
index 4559be275c..594172ccce 100644
--- a/packages/angular/src/builders/dev-server/lib/normalize-options.ts
+++ b/packages/angular/src/builders/dev-server/lib/normalize-options.ts
@@ -1,23 +1,11 @@
import { getInstalledAngularVersionInfo } from '../../../executors/utilities/angular-version-utils';
-import type {
- NormalizedSchema,
- Schema,
- SchemaWithBrowserTarget,
- SchemaWithBuildTarget,
-} from '../schema';
+import type { NormalizedSchema, Schema } from '../schema';
export function normalizeOptions(schema: Schema): NormalizedSchema {
- let buildTarget = (schema as SchemaWithBuildTarget).buildTarget;
- if ((schema as SchemaWithBrowserTarget).browserTarget) {
- buildTarget ??= (schema as SchemaWithBrowserTarget).browserTarget;
- delete (schema as SchemaWithBrowserTarget).browserTarget;
- }
-
const { major: angularMajorVersion } = getInstalledAngularVersionInfo();
return {
...schema,
- buildTarget,
host: schema.host ?? 'localhost',
port: schema.port ?? 4200,
liveReload: schema.liveReload ?? true,
diff --git a/packages/angular/src/builders/dev-server/lib/validate-options.ts b/packages/angular/src/builders/dev-server/lib/validate-options.ts
index f9beebfffa..669ed9d00f 100644
--- a/packages/angular/src/builders/dev-server/lib/validate-options.ts
+++ b/packages/angular/src/builders/dev-server/lib/validate-options.ts
@@ -6,11 +6,6 @@ import type { Schema } from '../schema';
export function validateOptions(options: Schema): void {
const { version: angularVersion } = getInstalledAngularVersionInfo();
- if (lt(angularVersion, '17.2.0') && options.prebundle) {
- throw new Error(stripIndents`The "prebundle" option is only supported in Angular >= 17.2.0. You are currently using "${angularVersion}".
- You can resolve this error by removing the "prebundle" option or by migrating to Angular 17.2.0.`);
- }
-
if (lt(angularVersion, '18.1.0') && options.inspect) {
throw new Error(stripIndents`The "inspect" option is only supported in Angular >= 18.1.0. You are currently using "${angularVersion}".
You can resolve this error by removing the "inspect" option or by migrating to Angular 18.1.0.`);
diff --git a/packages/angular/src/builders/dev-server/schema.d.ts b/packages/angular/src/builders/dev-server/schema.d.ts
index 7ffe17ec38..a8e6b97161 100644
--- a/packages/angular/src/builders/dev-server/schema.d.ts
+++ b/packages/angular/src/builders/dev-server/schema.d.ts
@@ -1,4 +1,5 @@
-interface BaseSchema {
+interface Schema {
+ buildTarget: string;
port?: number;
host?: string;
proxyConfig?: string;
@@ -24,20 +25,7 @@ interface BaseSchema {
watchDependencies?: boolean;
}
-export type SchemaWithBrowserTarget = BaseSchema & {
- /**
- * @deprecated Use `buildTarget` instead. It will be removed when Angular v20 is released.
- */
- browserTarget: string;
-};
-
-export type SchemaWithBuildTarget = BaseSchema & {
- buildTarget: string;
-};
-
-export type Schema = SchemaWithBrowserTarget | SchemaWithBuildTarget;
-
-export type NormalizedSchema = SchemaWithBuildTarget & {
+export type NormalizedSchema = Schema & {
liveReload: boolean;
open: boolean;
ssl: boolean;
diff --git a/packages/angular/src/builders/dev-server/schema.json b/packages/angular/src/builders/dev-server/schema.json
index 5ad0b11878..99e1145403 100644
--- a/packages/angular/src/builders/dev-server/schema.json
+++ b/packages/angular/src/builders/dev-server/schema.json
@@ -14,12 +14,6 @@
}
],
"properties": {
- "browserTarget": {
- "type": "string",
- "description": "A browser builder target to serve in the format of `project:target[:configuration]`. Ignored if `buildTarget` is set.",
- "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$",
- "x-deprecated": "Use 'buildTarget' instead. It will be removed when Angular v20 is released."
- },
"buildTarget": {
"type": "string",
"description": "A build builder target to serve in the format of `project:target[:configuration]`.",
@@ -128,7 +122,7 @@
]
},
"prebundle": {
- "description": "Enable and control the Vite-based development server's prebundling capabilities. To enable prebundling, the Angular CLI cache must also be enabled. This option has no effect when using the 'browser' or other Webpack-based builders. _Note: this is only supported in Angular versions >= 17.2.0_.",
+ "description": "Enable and control the Vite-based development server's prebundling capabilities. To enable prebundling, the Angular CLI cache must also be enabled. This option has no effect when using the 'browser' or other Webpack-based builders.",
"oneOf": [
{ "type": "boolean" },
{
@@ -147,7 +141,7 @@
},
"buildLibsFromSource": {
"type": "boolean",
- "description": "Read buildable libraries from source instead of building them separately. If not set, it will take the value specified in the `browserTarget` options, or it will default to `true` if it's also not set in the `browserTarget` options.",
+ "description": "Read buildable libraries from source instead of building them separately. If not set, it will take the value specified in the `buildTarget` options, or it will default to `true` if it's also not set in the `buildTarget` options.",
"x-priority": "important"
},
"esbuildMiddleware": {
@@ -165,5 +159,5 @@
}
},
"additionalProperties": false,
- "anyOf": [{ "required": ["buildTarget"] }, { "required": ["browserTarget"] }]
+ "required": ["buildTarget"]
}
diff --git a/packages/angular/src/builders/webpack-browser/webpack-browser.impl.ts b/packages/angular/src/builders/webpack-browser/webpack-browser.impl.ts
index c487e93c99..dc4c1796ca 100644
--- a/packages/angular/src/builders/webpack-browser/webpack-browser.impl.ts
+++ b/packages/angular/src/builders/webpack-browser/webpack-browser.impl.ts
@@ -13,6 +13,7 @@ import { getDependencyConfigs } from 'nx/src/tasks-runner/utils';
import { relative } from 'path';
import { from, Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
+import { assertBuilderPackageIsInstalled } from '../../executors/utilities/builder-package';
import { createTmpTsConfigForBuildableLibs } from '../utilities/buildable-libs';
import {
mergeCustomWebpackConfig,
@@ -98,6 +99,7 @@ export function executeWebpackBrowserBuilder(
);
}
+ assertBuilderPackageIsInstalled('@angular-devkit/build-angular');
return from(import('@angular-devkit/build-angular')).pipe(
switchMap(({ executeBrowserBuilder }) =>
executeBrowserBuilder(delegateBuilderOptions, context as any, {
diff --git a/packages/angular/src/builders/webpack-server/webpack-server.impl.ts b/packages/angular/src/builders/webpack-server/webpack-server.impl.ts
index aba3e42ef9..1d9252f8a1 100644
--- a/packages/angular/src/builders/webpack-server/webpack-server.impl.ts
+++ b/packages/angular/src/builders/webpack-server/webpack-server.impl.ts
@@ -1,3 +1,5 @@
+import type { BuilderContext } from '@angular-devkit/architect';
+import type { ServerBuilderOutput } from '@angular-devkit/build-angular';
import {
joinPathFragments,
normalizePath,
@@ -7,14 +9,17 @@ import { existsSync } from 'fs';
import { relative } from 'path';
import { Observable, from } from 'rxjs';
import { switchMap } from 'rxjs/operators';
+import { assertBuilderPackageIsInstalled } from '../../executors/utilities/builder-package';
import { createTmpTsConfigForBuildableLibs } from '../utilities/buildable-libs';
import { mergeCustomWebpackConfig } from '../utilities/webpack';
import { Schema } from './schema';
function buildServerApp(
options: Schema,
- context: import('@angular-devkit/architect').BuilderContext
-): Observable {
+ context: BuilderContext
+): Observable {
+ assertBuilderPackageIsInstalled('@angular-devkit/build-angular');
+
const { buildLibsFromSource, customWebpackConfig, ...delegateOptions } =
options;
// If there is a path to custom webpack config
@@ -47,7 +52,7 @@ function buildServerApp(
function buildServerAppWithCustomWebpackConfiguration(
options: Schema,
- context: import('@angular-devkit/architect').BuilderContext,
+ context: BuilderContext,
pathToWebpackConfig: string
) {
return from(import('@angular-devkit/build-angular')).pipe(
@@ -89,8 +94,8 @@ function buildServerAppWithCustomWebpackConfiguration(
export function executeWebpackServerBuilder(
options: Schema,
- context: import('@angular-devkit/architect').BuilderContext
-): Observable {
+ context: BuilderContext
+): Observable {
options.buildLibsFromSource ??= true;
process.env.NX_BUILD_LIBS_FROM_SOURCE = `${options.buildLibsFromSource}`;
diff --git a/packages/angular/src/executors/application/application.impl.ts b/packages/angular/src/executors/application/application.impl.ts
index b0a570ab92..275cdc5cd7 100644
--- a/packages/angular/src/executors/application/application.impl.ts
+++ b/packages/angular/src/executors/application/application.impl.ts
@@ -1,10 +1,10 @@
-import type { buildApplication as buildApplicationFn } from '@angular-devkit/build-angular';
+import type { BuilderOutput } from '@angular-devkit/architect';
import type { ExecutorContext } from '@nx/devkit';
import type { DependentBuildableProjectNode } from '@nx/js/src/utils/buildable-libs-utils';
import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter';
-import { gte } from 'semver';
import { getInstalledAngularVersionInfo } from '../utilities/angular-version-utils';
import { createTmpTsConfigForBuildableLibs } from '../utilities/buildable-libs';
+import { assertBuilderPackageIsInstalled } from '../utilities/builder-package';
import {
loadIndexHtmlTransformer,
loadPlugins,
@@ -16,7 +16,7 @@ import { validateOptions } from './utils/validate-options';
export default async function* applicationExecutor(
options: ApplicationExecutorOptions,
context: ExecutorContext
-): ReturnType {
+): AsyncIterable {
validateOptions(options);
options = normalizeOptions(options);
@@ -53,19 +53,20 @@ export default async function* applicationExecutor(
context
);
- const { version: angularVersion } = getInstalledAngularVersionInfo();
- if (gte(angularVersion, '17.1.0')) {
- const { buildApplication } = await import('@angular-devkit/build-angular');
+ const { major: angularMajorVersion } = getInstalledAngularVersionInfo();
+ if (angularMajorVersion >= 20) {
+ assertBuilderPackageIsInstalled('@angular/build');
+ const { buildApplication } = await import('@angular/build');
return yield* buildApplication(delegateExecutorOptions, builderContext, {
codePlugins: plugins,
indexHtmlTransformer,
});
}
- const { buildApplication } = require('@angular-devkit/build-angular');
- return yield* buildApplication(
- delegateExecutorOptions,
- builderContext,
- plugins
- );
+ assertBuilderPackageIsInstalled('@angular-devkit/build-angular');
+ const { buildApplication } = await import('@angular-devkit/build-angular');
+ return yield* buildApplication(delegateExecutorOptions, builderContext, {
+ codePlugins: plugins,
+ indexHtmlTransformer,
+ });
}
diff --git a/packages/angular/src/executors/application/schema.d.ts b/packages/angular/src/executors/application/schema.d.ts
index dcb0463e59..ebf821de24 100644
--- a/packages/angular/src/executors/application/schema.d.ts
+++ b/packages/angular/src/executors/application/schema.d.ts
@@ -1,4 +1,4 @@
-import type { ApplicationBuilderOptions } from '@angular-devkit/build-angular';
+import type { ApplicationBuilderOptions } from '@angular/build';
import type { PluginSpec } from '../utilities/esbuild-extensions';
export interface ApplicationExecutorOptions extends ApplicationBuilderOptions {
diff --git a/packages/angular/src/executors/application/schema.json b/packages/angular/src/executors/application/schema.json
index 8939f2d3a8..66a6a0c465 100644
--- a/packages/angular/src/executors/application/schema.json
+++ b/packages/angular/src/executors/application/schema.json
@@ -19,7 +19,6 @@
"description": "The full path for the browser entry point to the application, relative to the current workspace."
},
"server": {
- "type": "string",
"description": "The full path for the server entry point to the application, relative to the current workspace.",
"oneOf": [
{
@@ -48,7 +47,7 @@
},
"deployUrl": {
"type": "string",
- "description": "Customize the base path for the URLs of resources in 'index.html' and component stylesheets. This option is only necessary for specific deployment scenarios, such as with Angular Elements or when utilizing different CDN locations. _Note: this is only supported in Angular versions >= 17.3.0_."
+ "description": "Customize the base path for the URLs of resources in 'index.html' and component stylesheets. This option is only necessary for specific deployment scenarios, such as with Angular Elements or when utilizing different CDN locations."
},
"security": {
"description": "Security features to protect against XSS and other common attacks. _Note: this is only supported in Angular versions >= 19.0.0_.",
@@ -206,7 +205,7 @@
"clearScreen": {
"type": "boolean",
"default": false,
- "description": "Automatically clear the terminal screen during rebuilds. _Note: this is only supported in Angular versions >= 17.2.0_."
+ "description": "Automatically clear the terminal screen during rebuilds."
},
"optimization": {
"description": "Enables optimization of the build output. Including minification of scripts and styles, tree-shaking, dead-code elimination, inlining of critical CSS and fonts inlining. For more information, see https://angular.dev/reference/configs/workspace-config#optimization-configuration.",
@@ -239,7 +238,7 @@
},
"removeSpecialComments": {
"type": "boolean",
- "description": "Remove comments in global CSS that contains '@license' or '@preserve' or that starts with '//!' or '/*!'. _Note: this is only supported in Angular versions >= 17.1.0_.",
+ "description": "Remove comments in global CSS that contains '@license' or '@preserve' or that starts with '//!' or '/*!'.",
"default": true
}
},
@@ -279,19 +278,26 @@
]
},
"loader": {
- "description": "Defines the type of loader to use with a specified file extension when used with a JavaScript `import`. `text` inlines the content as a string; `binary` inlines the content as a Uint8Array; `file` emits the file and provides the runtime location of the file; `empty` considers the content to be empty and not include it in bundles. _Note: this is only supported in Angular versions >= 17.1.0_.",
+ "description": "Defines the type of loader to use with a specified file extension when used with a JavaScript `import`. `text` inlines the content as a string; `binary` inlines the content as a Uint8Array; `file` emits the file and provides the runtime location of the file; `empty` considers the content to be empty and not include it in bundles.",
"type": "object",
"patternProperties": {
"^\\.\\S+$": { "enum": ["text", "binary", "file", "empty"] }
}
},
"define": {
- "description": "Defines global identifiers that will be replaced with a specified constant value when found in any JavaScript or TypeScript code including libraries. The value will be used directly. String values must be put in quotes. Identifiers within Angular metadata such as Component Decorators will not be replaced. _Note: this is only supported in Angular versions >= 17.2.0_.",
+ "description": "Defines global identifiers that will be replaced with a specified constant value when found in any JavaScript or TypeScript code including libraries. The value will be used directly. String values must be put in quotes. Identifiers within Angular metadata such as Component Decorators will not be replaced.",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
+ "conditions": {
+ "description": "Custom package resolution conditions used to resolve conditional exports/imports. Defaults to ['module', 'development'/'production']. The following special conditions are always present if the requirements are satisfied: 'default', 'import', 'require', 'browser', 'node'. _Note: this is only supported in Angular versions >= 20.0.0_.",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
"fileReplacements": {
"description": "Replace compilation source files with other compilation source files in the build.",
"type": "array",
@@ -301,7 +307,7 @@
"default": []
},
"outputPath": {
- "description": "Specify the output path relative to workspace root. _Note: the object notation is only supported in Angular versions >= 17.1.0_.",
+ "description": "Specify the output path relative to workspace root.",
"oneOf": [
{
"type": "object",
@@ -368,6 +374,11 @@
"type": "boolean",
"description": "Resolve vendor packages source maps.",
"default": false
+ },
+ "sourcesContent": {
+ "type": "boolean",
+ "description": "Output original source content for files within the source map. _Note: this is only supported in Angular versions >= 20.0.0_.",
+ "default": true
}
},
"additionalProperties": false
@@ -500,7 +511,7 @@
"preloadInitial": {
"type": "boolean",
"default": true,
- "description": "Generates 'preload', 'modulepreload', and 'preconnect' link elements for initial application files and resources. _Note: this is only supported in Angular versions >= 17.1.0_."
+ "description": "Generates 'preload', 'modulepreload', and 'preconnect' link elements for initial application files and resources."
}
},
"required": ["input"]
@@ -636,12 +647,12 @@
}
},
"indexHtmlTransformer": {
- "description": "Path to a file exposing a default function to transform the `index.html` file. _Note: this is only supported in Angular versions >= 17.1.0_.",
+ "description": "Path to a file exposing a default function to transform the `index.html` file.",
"type": "string"
}
},
"additionalProperties": false,
- "required": ["outputPath", "index", "browser", "tsConfig"],
+ "required": ["outputPath", "tsConfig"],
"definitions": {
"assetPattern": {
"oneOf": [
diff --git a/packages/angular/src/executors/application/utils/normalize-options.ts b/packages/angular/src/executors/application/utils/normalize-options.ts
index f6b6522c8a..34ad28d1ce 100644
--- a/packages/angular/src/executors/application/utils/normalize-options.ts
+++ b/packages/angular/src/executors/application/utils/normalize-options.ts
@@ -35,5 +35,15 @@ export function normalizeOptions(
prerender ??= false;
}
- return { ...options, appShell, prerender, security };
+ let sourceMap = options.sourceMap;
+ if (
+ sourceMap &&
+ typeof sourceMap === 'object' &&
+ sourceMap.sourcesContent !== undefined &&
+ angularMajorVersion < 20
+ ) {
+ delete sourceMap.sourcesContent;
+ }
+
+ return { ...options, appShell, prerender, security, sourceMap };
}
diff --git a/packages/angular/src/executors/application/utils/validate-options.ts b/packages/angular/src/executors/application/utils/validate-options.ts
index 832399dfdc..c298c06356 100644
--- a/packages/angular/src/executors/application/utils/validate-options.ts
+++ b/packages/angular/src/executors/application/utils/validate-options.ts
@@ -5,73 +5,6 @@ import type { ApplicationExecutorOptions } from '../schema';
export function validateOptions(options: ApplicationExecutorOptions): void {
const { version: angularVersion } = getInstalledAngularVersionInfo();
- if (lt(angularVersion, '17.1.0')) {
- if (options.loader) {
- throw new Error(
- `The "loader" option requires Angular version 17.1.0 or greater. You are currently using version ${angularVersion}.`
- );
- }
-
- if (options.indexHtmlTransformer) {
- throw new Error(
- `The "indexHtmlTransformer" option requires Angular version 17.1.0 or greater. You are currently using version ${angularVersion}.`
- );
- }
-
- if (
- typeof options.index === 'object' &&
- options.index.preloadInitial !== undefined
- ) {
- throw new Error(
- `The "index.preloadInitial" option requires Angular version 17.1.0 or greater. You are currently using version ${angularVersion}.`
- );
- }
-
- if (
- options.optimization &&
- typeof options.optimization !== 'boolean' &&
- options.optimization.styles &&
- typeof options.optimization.styles !== 'boolean'
- ) {
- if (options.optimization.styles.removeSpecialComments === false) {
- throw new Error(
- `The "optimization.styles.removeSpecialComments" option requires Angular version 17.1.0 or greater. You are currently using version ${angularVersion}.`
- );
- } else if (options.optimization.styles.removeSpecialComments === true) {
- // silently remove the option, as it was the default before 17.1.0
- delete options.optimization.styles.removeSpecialComments;
- }
- }
-
- if (typeof options.outputPath === 'object') {
- throw new Error(
- `The "outputPath" option as an object requires Angular version 17.1.0 or greater. You are currently using version ${angularVersion}.`
- );
- }
- }
-
- if (lt(angularVersion, '17.2.0')) {
- if (options.define) {
- throw new Error(
- `The "define" option requires Angular version 17.2.0 or greater. You are currently using version ${angularVersion}.`
- );
- }
-
- if (options.clearScreen !== undefined) {
- throw new Error(
- `The "clearScreen" option requires Angular version 17.2.0 or greater. You are currently using version ${angularVersion}.`
- );
- }
- }
-
- if (lt(angularVersion, '17.3.0')) {
- if (options.deployUrl) {
- throw new Error(
- `The "deployUrl" option requires Angular version 17.3.0 or greater. You are currently using version ${angularVersion}.`
- );
- }
- }
-
if (lt(angularVersion, '19.0.0')) {
if (options.outputMode) {
throw new Error(
@@ -103,4 +36,22 @@ export function validateOptions(options: ApplicationExecutorOptions): void {
);
}
}
+
+ if (lt(angularVersion, '20.0.0')) {
+ if (
+ options.sourceMap &&
+ typeof options.sourceMap === 'object' &&
+ options.sourceMap.sourcesContent === false
+ ) {
+ throw new Error(
+ `The "sourceMap.sourcesContent" option requires Angular version 20.0.0 or greater. You are currently using version ${angularVersion}.`
+ );
+ }
+
+ if (options.conditions) {
+ throw new Error(
+ `The "conditions" option requires Angular version 20.0.0 or greater. You are currently using version ${angularVersion}.`
+ );
+ }
+ }
}
diff --git a/packages/angular/src/executors/extract-i18n/extract-i18n.impl.ts b/packages/angular/src/executors/extract-i18n/extract-i18n.impl.ts
index 05842fcf85..152a150fb2 100644
--- a/packages/angular/src/executors/extract-i18n/extract-i18n.impl.ts
+++ b/packages/angular/src/executors/extract-i18n/extract-i18n.impl.ts
@@ -1,8 +1,7 @@
-import type { ExtractI18nBuilderOptions } from '@angular-devkit/build-angular';
import { parseTargetString, type ExecutorContext } from '@nx/devkit';
import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter';
import { readCachedProjectConfiguration } from 'nx/src/project-graph/project-graph';
-import { getInstalledAngularVersionInfo } from '../utilities/angular-version-utils';
+import { assertBuilderPackageIsInstalled } from '../utilities/builder-package';
import { patchBuilderContext } from '../utilities/patch-builder-context';
import type { ExtractI18nExecutorOptions } from './schema';
@@ -11,14 +10,15 @@ export default async function* extractI18nExecutor(
context: ExecutorContext
) {
const parsedBuildTarget = parseTargetString(options.buildTarget, context);
- const browserTargetProjectConfiguration = readCachedProjectConfiguration(
+ const buildTargetProjectConfiguration = readCachedProjectConfiguration(
parsedBuildTarget.project
);
const buildTarget =
- browserTargetProjectConfiguration.targets[parsedBuildTarget.target];
+ buildTargetProjectConfiguration.targets[parsedBuildTarget.target];
const isUsingEsbuildBuilder = [
+ '@angular/build:application',
'@angular-devkit/build-angular:application',
'@angular-devkit/build-angular:browser-esbuild',
'@nx/angular:application',
@@ -42,29 +42,10 @@ export default async function* extractI18nExecutor(
*/
patchBuilderContext(builderContext, isUsingEsbuildBuilder, parsedBuildTarget);
+ assertBuilderPackageIsInstalled('@angular-devkit/build-angular');
const { executeExtractI18nBuilder } = await import(
'@angular-devkit/build-angular'
);
- const delegateBuilderOptions = getDelegateBuilderOptions(options);
- return await executeExtractI18nBuilder(
- delegateBuilderOptions,
- builderContext
- );
-}
-
-function getDelegateBuilderOptions(
- options: ExtractI18nExecutorOptions
-): ExtractI18nBuilderOptions {
- const delegateBuilderOptions: ExtractI18nBuilderOptions & {
- browserTarget?: string;
- } = { ...options };
-
- const { major: angularMajorVersion } = getInstalledAngularVersionInfo();
- if (angularMajorVersion <= 17) {
- delegateBuilderOptions.browserTarget = delegateBuilderOptions.buildTarget;
- delete delegateBuilderOptions.buildTarget;
- }
-
- return delegateBuilderOptions;
+ return await executeExtractI18nBuilder(options, builderContext);
}
diff --git a/packages/angular/src/executors/extract-i18n/schema.d.ts b/packages/angular/src/executors/extract-i18n/schema.d.ts
index 5b8e2d4760..9d1bc519e3 100644
--- a/packages/angular/src/executors/extract-i18n/schema.d.ts
+++ b/packages/angular/src/executors/extract-i18n/schema.d.ts
@@ -1,8 +1,3 @@
import type { ExtractI18nBuilderOptions } from '@angular-devkit/build-angular';
-export type ExtractI18nExecutorOptions = Omit<
- ExtractI18nBuilderOptions,
- 'browserTarget'
-> & {
- buildTarget: string;
-};
+export type ExtractI18nExecutorOptions = ExtractI18nBuilderOptions;
diff --git a/packages/angular/src/executors/extract-i18n/schema.json b/packages/angular/src/executors/extract-i18n/schema.json
index 79dfffdc3e..7f2425aa1a 100644
--- a/packages/angular/src/executors/extract-i18n/schema.json
+++ b/packages/angular/src/executors/extract-i18n/schema.json
@@ -38,6 +38,11 @@
"outFile": {
"type": "string",
"description": "Name of the file to output."
+ },
+ "i18nDuplicateTranslation": {
+ "type": "string",
+ "description": "How to handle duplicate translations. _Note: this is only available in Angular 20.0.0 and above._",
+ "enum": ["error", "warning", "ignore"]
}
},
"additionalProperties": false,
diff --git a/packages/angular/src/executors/extract-i18n/utils/validate-options.ts b/packages/angular/src/executors/extract-i18n/utils/validate-options.ts
new file mode 100644
index 0000000000..bad06328ba
--- /dev/null
+++ b/packages/angular/src/executors/extract-i18n/utils/validate-options.ts
@@ -0,0 +1,15 @@
+import { lt } from 'semver';
+import { getInstalledAngularVersionInfo } from '../../utilities/angular-version-utils';
+import type { ExtractI18nExecutorOptions } from '../schema';
+
+export function validateOptions(options: ExtractI18nExecutorOptions): void {
+ const { version: angularVersion } = getInstalledAngularVersionInfo();
+
+ if (lt(angularVersion, '20.0.0')) {
+ if (options.i18nDuplicateTranslation) {
+ throw new Error(
+ `The "i18nDuplicateTranslation" option requires Angular version 20.0.0 or greater. You are currently using version ${angularVersion}.`
+ );
+ }
+ }
+}
diff --git a/packages/angular/src/executors/module-federation-dev-server/lib/normalize-options.ts b/packages/angular/src/executors/module-federation-dev-server/lib/normalize-options.ts
index 41d63136db..7cb5f7f47e 100644
--- a/packages/angular/src/executors/module-federation-dev-server/lib/normalize-options.ts
+++ b/packages/angular/src/executors/module-federation-dev-server/lib/normalize-options.ts
@@ -1,25 +1,14 @@
-import type {
- NormalizedSchema,
- Schema,
- SchemaWithBrowserTarget,
- SchemaWithBuildTarget,
-} from '../schema';
+import type { NormalizedSchema, Schema } from '../schema';
import { join } from 'path';
import { workspaceRoot } from '@nx/devkit';
export function normalizeOptions(schema: Schema): NormalizedSchema {
- let buildTarget = (schema as SchemaWithBuildTarget).buildTarget;
- if ((schema as SchemaWithBrowserTarget).browserTarget) {
- buildTarget ??= (schema as SchemaWithBrowserTarget).browserTarget;
- delete (schema as SchemaWithBrowserTarget).browserTarget;
- }
schema.buildLibsFromSource ??= true;
process.env.NX_BUILD_LIBS_FROM_SOURCE = `${schema.buildLibsFromSource}`;
- process.env.NX_BUILD_TARGET = `${buildTarget}`;
+ process.env.NX_BUILD_TARGET = `${schema.buildTarget}`;
return {
...schema,
- buildTarget,
devRemotes: schema.devRemotes ?? [],
host: schema.host ?? 'localhost',
port: schema.port ?? 4200,
diff --git a/packages/angular/src/executors/module-federation-dev-server/schema.d.ts b/packages/angular/src/executors/module-federation-dev-server/schema.d.ts
index 6c9f61969b..3eeb31d007 100644
--- a/packages/angular/src/executors/module-federation-dev-server/schema.d.ts
+++ b/packages/angular/src/executors/module-federation-dev-server/schema.d.ts
@@ -1,6 +1,7 @@
import type { DevRemoteDefinition } from '../../builders/utilities/module-federation';
-interface BaseSchema {
+interface Schema {
+ buildTarget: string;
port?: number;
host?: string;
proxyConfig?: string;
@@ -28,20 +29,7 @@ interface BaseSchema {
buildLibsFromSource?: boolean;
}
-export type SchemaWithBrowserTarget = BaseSchema & {
- /**
- * @deprecated Use `buildTarget` instead. It will be removed when Angular v20 is released.
- */
- browserTarget: string;
-};
-
-export type SchemaWithBuildTarget = BaseSchema & {
- buildTarget: string;
-};
-
-export type Schema = SchemaWithBrowserTarget | SchemaWithBuildTarget;
-
-export type NormalizedSchema = SchemaWithBuildTarget & {
+export type NormalizedSchema = Schema & {
devRemotes: DevRemoteDefinition[];
liveReload: boolean;
open: boolean;
diff --git a/packages/angular/src/executors/module-federation-dev-server/schema.json b/packages/angular/src/executors/module-federation-dev-server/schema.json
index 8ab1793525..f1c10d0583 100644
--- a/packages/angular/src/executors/module-federation-dev-server/schema.json
+++ b/packages/angular/src/executors/module-federation-dev-server/schema.json
@@ -12,12 +12,6 @@
}
],
"properties": {
- "browserTarget": {
- "type": "string",
- "description": "A browser builder target to serve in the format of `project:target[:configuration]`.",
- "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$",
- "x-deprecated": "Use 'buildTarget' instead. It will be removed when Angular v20 is released."
- },
"buildTarget": {
"type": "string",
"description": "A build builder target to serve in the format of `project:target[:configuration]`.",
@@ -166,18 +160,11 @@
},
"buildLibsFromSource": {
"type": "boolean",
- "description": "Read buildable libraries from source instead of building them separately. If not set, it will take the value specified in the `browserTarget` options, or it will default to `true` if it's also not set in the `browserTarget` options.",
+ "description": "Read buildable libraries from source instead of building them separately. If not set, it will take the value specified in the `buildTarget` options, or it will default to `true` if it's also not set in the `buildTarget` options.",
"x-priority": "important"
}
},
"additionalProperties": false,
- "anyOf": [
- {
- "required": ["buildTarget"]
- },
- {
- "required": ["browserTarget"]
- }
- ],
+ "required": ["buildTarget"],
"examplesFile": "../../../docs/module-federation-dev-server-examples.md"
}
diff --git a/packages/angular/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts b/packages/angular/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts
index c53565e683..dbbd4eba08 100644
--- a/packages/angular/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts
+++ b/packages/angular/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts
@@ -1,24 +1,24 @@
-import { executeSSRDevServerBuilder } from '@angular-devkit/build-angular';
import { type ExecutorContext, logger } from '@nx/devkit';
-import { existsSync } from 'fs';
-import { readProjectsConfigurationFromProjectGraph } from 'nx/src/project-graph/project-graph';
-import { extname, join } from 'path';
-import {
- getDynamicMfManifestFile,
- validateDevRemotes,
-} from '../../builders/utilities/module-federation';
-import type { Schema } from './schema';
-import { startRemoteIterators } from '@nx/module-federation/src/executors/utils';
-import { startRemotes } from './lib/start-dev-remotes';
import {
combineAsyncIterables,
createAsyncIterable,
mapAsyncIterable,
} from '@nx/devkit/src/utils/async-iterable';
import { eachValueFrom } from '@nx/devkit/src/utils/rxjs-for-await';
-import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter';
-import { normalizeOptions } from './lib/normalize-options';
+import { startRemoteIterators } from '@nx/module-federation/src/executors/utils';
import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
+import { existsSync } from 'fs';
+import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter';
+import { readProjectsConfigurationFromProjectGraph } from 'nx/src/project-graph/project-graph';
+import { extname, join } from 'path';
+import {
+ getDynamicMfManifestFile,
+ validateDevRemotes,
+} from '../../builders/utilities/module-federation';
+import { assertBuilderPackageIsInstalled } from '../utilities/builder-package';
+import { normalizeOptions } from './lib/normalize-options';
+import { startRemotes } from './lib/start-dev-remotes';
+import type { Schema } from './schema';
export async function* moduleFederationSsrDevServerExecutor(
schema: Schema,
@@ -26,6 +26,11 @@ export async function* moduleFederationSsrDevServerExecutor(
) {
const options = normalizeOptions(schema);
+ assertBuilderPackageIsInstalled('@angular-devkit/build-angular');
+ const { executeSSRDevServerBuilder } = await import(
+ '@angular-devkit/build-angular'
+ );
+
const currIter = eachValueFrom(
executeSSRDevServerBuilder(
options,
diff --git a/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/entry-point.ts b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/entry-point.ts
index f76337a0b5..034041ec82 100644
--- a/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/entry-point.ts
+++ b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/entry-point.ts
@@ -1,34 +1,117 @@
import type {
DestinationFiles,
NgEntryPoint as NgEntryPointBase,
-} from 'ng-packagr/lib/ng-package/entry-point/entry-point';
-import type { NgPackageConfig } from 'ng-packagr/ng-package.schema';
-import { dirname } from 'node:path';
+} from 'ng-packagr/src/lib/ng-package/entry-point/entry-point';
+import type { NgPackageConfig } from 'ng-packagr/src/ng-package.schema';
+import { dirname, join, relative } from 'node:path';
import { getNgPackagrVersionInfo } from '../../../../utilities/ng-packagr/ng-packagr-version';
import { importNgPackagrPath } from '../../../../utilities/ng-packagr/package-imports';
+export type NgEntryPointType = NgEntryPointBase & {
+ primaryDestinationPath?: string;
+};
+
export function createNgEntryPoint(
packageJson: Record,
ngPackageJson: NgPackageConfig,
basePath: string,
secondaryData?: Record
-): NgEntryPointBase {
+): NgEntryPointType {
const { major: ngPackagrMajorVersion } = getNgPackagrVersionInfo();
const { NgEntryPoint: NgEntryPointBase } = importNgPackagrPath<
- typeof import('ng-packagr/lib/ng-package/entry-point/entry-point')
- >('ng-packagr/lib/ng-package/entry-point/entry-point', ngPackagrMajorVersion);
+ typeof import('ng-packagr/src/lib/ng-package/entry-point/entry-point')
+ >(
+ 'ng-packagr/src/lib/ng-package/entry-point/entry-point',
+ ngPackagrMajorVersion
+ );
+
+ if (ngPackagrMajorVersion < 20) {
+ class NgEntryPoint extends NgEntryPointBase {
+ /**
+ * Point the FESM2022 files to the ESM2022 files.
+ */
+ public override get destinationFiles(): DestinationFiles {
+ const result = super.destinationFiles;
+ result.fesm2022 = result.esm2022;
+ result.fesm2022Dir = dirname(result.esm2022);
+
+ return result;
+ }
+ }
+
+ return new NgEntryPoint(
+ packageJson,
+ ngPackageJson,
+ basePath,
+ secondaryData
+ );
+ }
+
+ const { ensureUnixPath } = importNgPackagrPath<
+ typeof import('ng-packagr/src/lib/utils/path')
+ >('ng-packagr/src/lib/utils/path', ngPackagrMajorVersion);
class NgEntryPoint extends NgEntryPointBase {
- /**
- * Point the FESM2022 files to the ESM2022 files.
- */
- public override get destinationFiles(): DestinationFiles {
- const result = super.destinationFiles;
- result.fesm2022 = result.esm2022;
- result.fesm2022Dir = dirname(result.esm2022);
+ constructor(
+ public readonly packageJson: Record,
+ public readonly ngPackageJson: NgPackageConfig,
+ public readonly basePath: string,
+ private readonly _secondaryData?: Record
+ ) {
+ super(packageJson, ngPackageJson, basePath, _secondaryData);
+ }
- return result;
+ public get primaryDestinationPath(): string {
+ return (
+ this._secondaryData?.primaryDestinationPath ?? this.destinationPath
+ );
+ }
+
+ public get destinationFiles(): DestinationFiles {
+ let primaryDestPath = this.destinationPath;
+ let secondaryDir = '';
+
+ if (this._secondaryData) {
+ primaryDestPath = this._secondaryData.primaryDestinationPath;
+ secondaryDir = relative(
+ primaryDestPath,
+ this._secondaryData.destinationPath
+ );
+ }
+
+ const flatModuleFile = this.flatModuleFile;
+ const pathJoinWithDest = (...paths: string[]) =>
+ join(primaryDestPath, ...paths);
+
+ return {
+ directory: ensureUnixPath(secondaryDir),
+ declarations: pathJoinWithDest(
+ 'tmp-typings',
+ secondaryDir,
+ `${flatModuleFile}.d.ts`
+ ),
+ // changed to use esm2022
+ declarationsBundled: pathJoinWithDest(
+ 'esm2022',
+ secondaryDir,
+ `${flatModuleFile}.d.ts`
+ ),
+ declarationsDir: pathJoinWithDest(secondaryDir),
+ esm2022: pathJoinWithDest(
+ 'tmp-esm2022',
+ secondaryDir,
+ `${flatModuleFile}.js`
+ ),
+ // changed to use esm2022
+ fesm2022: pathJoinWithDest(
+ 'esm2022',
+ secondaryDir,
+ `${flatModuleFile}.js`
+ ),
+ // changed to use esm2022
+ fesm2022Dir: pathJoinWithDest('esm2022'),
+ };
}
}
diff --git a/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/write-bundles.di.ts b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/write-bundles.di.ts
index 4adf0b9b9c..a4184bd362 100644
--- a/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/write-bundles.di.ts
+++ b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/write-bundles.di.ts
@@ -1,4 +1,4 @@
-import type { TransformProvider } from 'ng-packagr/lib/graph/transform.di';
+import type { TransformProvider } from 'ng-packagr/src/lib/graph/transform.di';
import { getNgPackagrVersionInfo } from '../../../../utilities/ng-packagr/ng-packagr-version';
import { importNgPackagrPath } from '../../../../utilities/ng-packagr/package-imports';
import { writeBundlesTransform } from './write-bundles.transform';
@@ -7,17 +7,17 @@ export function getWriteBundlesTransformProvider(): TransformProvider {
const { major: ngPackagrMajorVersion } = getNgPackagrVersionInfo();
const { provideTransform } = importNgPackagrPath<
- typeof import('ng-packagr/lib/graph/transform.di')
- >('ng-packagr/lib/graph/transform.di', ngPackagrMajorVersion);
+ typeof import('ng-packagr/src/lib/graph/transform.di')
+ >('ng-packagr/src/lib/graph/transform.di', ngPackagrMajorVersion);
const { WRITE_BUNDLES_TRANSFORM_TOKEN } = importNgPackagrPath<
- typeof import('ng-packagr/lib/ng-package/entry-point/write-bundles.di')
+ typeof import('ng-packagr/src/lib/ng-package/entry-point/write-bundles.di')
>(
- 'ng-packagr/lib/ng-package/entry-point/write-bundles.di',
+ 'ng-packagr/src/lib/ng-package/entry-point/write-bundles.di',
ngPackagrMajorVersion
);
const { OPTIONS_TOKEN } = importNgPackagrPath<
- typeof import('ng-packagr/lib/ng-package/options.di')
- >('ng-packagr/lib/ng-package/options.di', ngPackagrMajorVersion);
+ typeof import('ng-packagr/src/lib/ng-package/options.di')
+ >('ng-packagr/src/lib/ng-package/options.di', ngPackagrMajorVersion);
return provideTransform({
provide: WRITE_BUNDLES_TRANSFORM_TOKEN,
diff --git a/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/write-bundles.transform.ts b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/write-bundles.transform.ts
index 5928b543d5..de1364a7a7 100644
--- a/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/write-bundles.transform.ts
+++ b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/write-bundles.transform.ts
@@ -7,29 +7,29 @@
* - Fake the FESM2022 outputs pointing them to the ESM2022 outputs.
*/
-import type { NgEntryPoint } from 'ng-packagr/lib/ng-package/entry-point/entry-point';
-import type { NgPackagrOptions } from 'ng-packagr/lib/ng-package/options.di';
+import type { NgEntryPoint } from 'ng-packagr/src/lib/ng-package/entry-point/entry-point';
+import type { NgPackagrOptions } from 'ng-packagr/src/lib/ng-package/options.di';
import { mkdir, writeFile } from 'node:fs/promises';
-import { dirname } from 'node:path';
+import { dirname, join } from 'node:path';
import { getNgPackagrVersionInfo } from '../../../../utilities/ng-packagr/ng-packagr-version';
import { importNgPackagrPath } from '../../../../utilities/ng-packagr/package-imports';
-import { createNgEntryPoint } from './entry-point';
+import { createNgEntryPoint, type NgEntryPointType } from './entry-point';
export const writeBundlesTransform = (_options: NgPackagrOptions) => {
const { major: ngPackagrMajorVersion } = getNgPackagrVersionInfo();
const { BuildGraph } = importNgPackagrPath<
- typeof import('ng-packagr/lib/graph/build-graph')
- >('ng-packagr/lib/graph/build-graph', ngPackagrMajorVersion);
+ typeof import('ng-packagr/src/lib/graph/build-graph')
+ >('ng-packagr/src/lib/graph/build-graph', ngPackagrMajorVersion);
const { transformFromPromise } = importNgPackagrPath<
- typeof import('ng-packagr/lib/graph/transform')
- >('ng-packagr/lib/graph/transform', ngPackagrMajorVersion);
+ typeof import('ng-packagr/src/lib/graph/transform')
+ >('ng-packagr/src/lib/graph/transform', ngPackagrMajorVersion);
const { isEntryPoint, isPackage } = importNgPackagrPath<
- typeof import('ng-packagr/lib/ng-package/nodes')
- >('ng-packagr/lib/ng-package/nodes', ngPackagrMajorVersion);
+ typeof import('ng-packagr/src/lib/ng-package/nodes')
+ >('ng-packagr/src/lib/ng-package/nodes', ngPackagrMajorVersion);
const { NgPackage } = importNgPackagrPath<
- typeof import('ng-packagr/lib/ng-package/package')
- >('ng-packagr/lib/ng-package/package', ngPackagrMajorVersion);
+ typeof import('ng-packagr/src/lib/ng-package/package')
+ >('ng-packagr/src/lib/ng-package/package', ngPackagrMajorVersion);
return transformFromPromise(async (graph) => {
const updatedGraph = new BuildGraph();
@@ -41,9 +41,13 @@ export const writeBundlesTransform = (_options: NgPackagrOptions) => {
entry.data.destinationFiles = entryPoint.destinationFiles;
for (const [path, outputCache] of entry.cache.outputCache.entries()) {
+ const normalizedPath = normalizeEsm2022Path(path, entryPoint);
// write the outputs to the file system
- await mkdir(dirname(path), { recursive: true });
- await writeFile(path, outputCache.content);
+ await mkdir(dirname(normalizedPath), { recursive: true });
+ await writeFile(normalizedPath, outputCache.content);
+ }
+ if (!entry.cache.outputCache.size && entryPoint.isSecondaryEntryPoint) {
+ await mkdir(entryPoint.destinationPath, { recursive: true });
}
} else if (isPackage(entry)) {
entry.data = new NgPackage(
@@ -61,7 +65,26 @@ export const writeBundlesTransform = (_options: NgPackagrOptions) => {
});
};
-function toCustomNgEntryPoint(entryPoint: NgEntryPoint): NgEntryPoint {
+function normalizeEsm2022Path(
+ path: string,
+ entryPoint: NgEntryPointType
+): string {
+ if (!entryPoint.primaryDestinationPath) {
+ return path;
+ }
+
+ if (path.startsWith(join(entryPoint.primaryDestinationPath, 'tmp-esm2022'))) {
+ return path.replace('tmp-esm2022', 'esm2022');
+ }
+
+ if (path.startsWith(join(entryPoint.primaryDestinationPath, 'tmp-typings'))) {
+ return path.replace('tmp-typings', 'esm2022');
+ }
+
+ return path;
+}
+
+function toCustomNgEntryPoint(entryPoint: NgEntryPoint): NgEntryPointType {
return createNgEntryPoint(
entryPoint.packageJson,
entryPoint.ngPackageJson,
diff --git a/packages/angular/src/executors/ng-packagr-lite/schema.json b/packages/angular/src/executors/ng-packagr-lite/schema.json
index da37fab49f..a47e21e427 100644
--- a/packages/angular/src/executors/ng-packagr-lite/schema.json
+++ b/packages/angular/src/executors/ng-packagr-lite/schema.json
@@ -31,9 +31,8 @@
},
"poll": {
"type": "number",
- "description": "Enable and define the file watching poll time period in milliseconds. _Note: this is only supported in Angular versions >= 18.0.0_."
+ "description": "Enable and define the file watching poll time period in milliseconds."
}
},
- "additionalProperties": false,
- "required": ["project"]
+ "additionalProperties": false
}
diff --git a/packages/angular/src/executors/package/package.impl.ts b/packages/angular/src/executors/package/package.impl.ts
index f951d2ef5d..34c1980922 100644
--- a/packages/angular/src/executors/package/package.impl.ts
+++ b/packages/angular/src/executors/package/package.impl.ts
@@ -9,7 +9,6 @@ import type { NgPackagr } from 'ng-packagr';
import { join, resolve } from 'path';
import { from } from 'rxjs';
import { mapTo, switchMap } from 'rxjs/operators';
-import { getInstalledAngularVersionInfo } from '../utilities/angular-version-utils';
import { parseRemappedTsConfigAndMergeDefaults } from '../utilities/typescript';
import { getNgPackagrInstance } from './ng-packagr-adjustments/ng-packagr';
import type { BuildAngularLibraryExecutorOptions } from './schema';
@@ -56,13 +55,10 @@ export function createLibraryExecutor(
options: BuildAngularLibraryExecutorOptions,
context: ExecutorContext
) {
- const { major: angularMajorVersion, version: angularVersion } =
- getInstalledAngularVersionInfo();
- if (angularMajorVersion < 18 && options.poll !== undefined) {
- throw new Error(
- `The "poll" option requires Angular version 18.0.0 or greater. You are currently using version ${angularVersion}.`
- );
- }
+ options.project ??= join(
+ context.projectsConfigurations.projects[context.projectName].root,
+ 'ng-package.json'
+ );
const { dependencies } = calculateProjectBuildableDependencies(
context.taskGraph,
diff --git a/packages/angular/src/executors/package/schema.d.ts b/packages/angular/src/executors/package/schema.d.ts
index eb0248ee02..6e8059ddcb 100644
--- a/packages/angular/src/executors/package/schema.d.ts
+++ b/packages/angular/src/executors/package/schema.d.ts
@@ -1,4 +1,6 @@
import type { NgPackagrBuilderOptions } from '@angular-devkit/build-angular';
export interface BuildAngularLibraryExecutorOptions
- extends NgPackagrBuilderOptions {}
+ extends NgPackagrBuilderOptions {
+ project?: string;
+}
diff --git a/packages/angular/src/executors/package/schema.json b/packages/angular/src/executors/package/schema.json
index 540c0578a8..a6de8f01c4 100644
--- a/packages/angular/src/executors/package/schema.json
+++ b/packages/angular/src/executors/package/schema.json
@@ -31,9 +31,8 @@
},
"poll": {
"type": "number",
- "description": "Enable and define the file watching poll time period in milliseconds. _Note: this is only supported in Angular versions >= 18.0.0_."
+ "description": "Enable and define the file watching poll time period in milliseconds."
}
},
- "additionalProperties": false,
- "required": ["project"]
+ "additionalProperties": false
}
diff --git a/packages/angular/src/executors/utilities/builder-package.ts b/packages/angular/src/executors/utilities/builder-package.ts
new file mode 100644
index 0000000000..c075ae8ead
--- /dev/null
+++ b/packages/angular/src/executors/utilities/builder-package.ts
@@ -0,0 +1,9 @@
+export function assertBuilderPackageIsInstalled(packageName: string): void {
+ try {
+ require.resolve(packageName);
+ } catch {
+ throw new Error(
+ `This executor requires the package ${packageName} to be installed. Please make sure it is installed and try again.`
+ );
+ }
+}
diff --git a/packages/angular/src/executors/utilities/esbuild-extensions.ts b/packages/angular/src/executors/utilities/esbuild-extensions.ts
index dcee69217f..cf0055b80a 100644
--- a/packages/angular/src/executors/utilities/esbuild-extensions.ts
+++ b/packages/angular/src/executors/utilities/esbuild-extensions.ts
@@ -1,12 +1,11 @@
+import type { buildApplication } from '@angular/build';
import { registerTsProject } from '@nx/js/src/internal';
import { loadModule } from './module-loader';
// This is a workaround to make sure we use the same esbuild version as the
// Angular DevKit uses. This is only used internally to load the plugins and
// forward them to the Angular DevKit builders.
-type Plugin = Parameters<
- typeof import('@angular-devkit/build-angular').buildApplication
->[2]['codePlugins'][number];
+type Plugin = Parameters[2]['codePlugins'][number];
export type PluginSpec = {
path: string;
diff --git a/packages/angular/src/executors/utilities/ng-packagr/package-imports.ts b/packages/angular/src/executors/utilities/ng-packagr/package-imports.ts
index 49fcec6ebf..53e3a8588b 100644
--- a/packages/angular/src/executors/utilities/ng-packagr/package-imports.ts
+++ b/packages/angular/src/executors/utilities/ng-packagr/package-imports.ts
@@ -1,11 +1,13 @@
+type NgPackagrImportPath = `ng-packagr/src/${string}`;
+
export function importNgPackagrPath(
- path: string,
+ path: NgPackagrImportPath,
ngPackagrMajorVersion: number
): T {
- let finalPath = path;
+ let finalPath: string = path;
- if (ngPackagrMajorVersion >= 20 && !path.startsWith('ng-packagr/src/')) {
- finalPath = path.replace(/^ng-packagr\//, 'ng-packagr/src/');
+ if (ngPackagrMajorVersion < 20 && path.startsWith('ng-packagr/src/')) {
+ finalPath = path.replace(/^ng-packagr\/src\//, 'ng-packagr/');
}
return require(finalPath);
diff --git a/packages/angular/src/executors/utilities/ng-packagr/pre-v19/stylesheet-processor.ts b/packages/angular/src/executors/utilities/ng-packagr/pre-v19/stylesheet-processor.ts
index 99449fc61c..b8e202f2e6 100644
--- a/packages/angular/src/executors/utilities/ng-packagr/pre-v19/stylesheet-processor.ts
+++ b/packages/angular/src/executors/utilities/ng-packagr/pre-v19/stylesheet-processor.ts
@@ -2,21 +2,15 @@
* Adapted from the original ng-packagr source.
*
* Changes made:
- * - Use our own function to get the TailwindCSS config path to support a
- * config at the root of the workspace.
+ * - Resolve `piscina` from the installed `ng-packagr` package.
+ * - Additionally search for the TailwindCSS config in the workspace root.
*/
+import { workspaceRoot } from '@nx/devkit';
import browserslist from 'browserslist';
import { existsSync } from 'fs';
+import { colors } from 'ng-packagr/src/lib/utils/color';
import { dirname, join } from 'path';
-const Piscina = require('piscina');
-import { colors } from 'ng-packagr/lib/utils/color';
-// using this instead of the one from ng-packagr
-import { getTailwindConfigPath } from '../tailwindcss';
-import { workspaceRoot } from '@nx/devkit';
-import type { PostcssConfiguration } from 'ng-packagr/lib/styles/postcss-configuration';
-import { gt, gte } from 'semver';
-import { getNgPackagrVersionInfo } from '../ng-packagr-version';
const maxWorkersVariable = process.env['NG_BUILD_MAX_WORKERS'];
const maxThreads =
@@ -30,7 +24,7 @@ export enum CssUrl {
}
export class StylesheetProcessor {
- private renderWorker: typeof Piscina | undefined;
+ private renderWorker: any | undefined;
constructor(
private readonly projectBasePath: string,
@@ -91,33 +85,22 @@ export class StylesheetProcessor {
const browserslistData = browserslist(undefined, { path: this.basePath });
- const { version: ngPackagrVersion } = getNgPackagrVersionInfo();
- let tailwindConfigPath: string | undefined;
- let postcssConfiguration: PostcssConfiguration | undefined;
- if (gte(ngPackagrVersion, '18.0.0')) {
- const {
- findTailwindConfiguration,
- generateSearchDirectories,
- loadPostcssConfiguration,
- } = require('ng-packagr/lib/styles/postcss-configuration');
- let searchDirs = generateSearchDirectories([this.projectBasePath]);
- postcssConfiguration = loadPostcssConfiguration(searchDirs);
- // (nx-specific): we support loading the TailwindCSS config from the root of the workspace
- searchDirs = generateSearchDirectories([
- this.projectBasePath,
- workspaceRoot,
- ]);
- tailwindConfigPath = findTailwindConfiguration(searchDirs);
- } else if (gt(ngPackagrVersion, '17.2.0')) {
- const {
- loadPostcssConfiguration,
- } = require('ng-packagr/lib/styles/postcss-configuration');
- postcssConfiguration = loadPostcssConfiguration(this.projectBasePath);
- tailwindConfigPath = getTailwindConfigPath(
- this.projectBasePath,
- workspaceRoot
- );
- }
+ const {
+ findTailwindConfiguration,
+ generateSearchDirectories,
+ loadPostcssConfiguration,
+ } = require('ng-packagr/lib/styles/postcss-configuration');
+
+ let searchDirs = generateSearchDirectories([this.projectBasePath]);
+ const postcssConfiguration = loadPostcssConfiguration(searchDirs);
+ // (nx-specific): we support loading the TailwindCSS config from the root of the workspace
+ searchDirs = generateSearchDirectories([
+ this.projectBasePath,
+ workspaceRoot,
+ ]);
+ const tailwindConfigPath = findTailwindConfiguration(searchDirs);
+
+ const Piscina = getPiscina();
this.renderWorker = new Piscina({
filename: require.resolve(
@@ -143,112 +126,6 @@ export class StylesheetProcessor {
}
}
-/**
- * This class is used when ng-packagr version is 17.2.0. The async `loadPostcssConfiguration` function
- * introduced in ng-packagr 17.2.0 causes a memory leak due to multiple workers being created. We must
- * keep this class to support any workspace that might be using ng-packagr 17.2.0 where that function
- * need to be awaited.
- */
-export class AsyncStylesheetProcessor {
- private renderWorker: typeof Piscina | undefined;
-
- constructor(
- private readonly projectBasePath: string,
- private readonly basePath: string,
- private readonly cssUrl?: CssUrl,
- private readonly includePaths?: string[],
- private readonly cacheDirectory?: string | false
- ) {
- // By default, browserslist defaults are too inclusive
- // https://github.com/browserslist/browserslist/blob/83764ea81ffaa39111c204b02c371afa44a4ff07/index.js#L516-L522
- // We change the default query to browsers that Angular support.
- // https://angular.io/guide/browser-support
- (browserslist.defaults as string[]) = [
- 'last 2 Chrome versions',
- 'last 1 Firefox version',
- 'last 2 Edge major versions',
- 'last 2 Safari major versions',
- 'last 2 iOS major versions',
- 'Firefox ESR',
- ];
- }
-
- async process({
- filePath,
- content,
- }: {
- filePath: string;
- content: string;
- }): Promise {
- await this.createRenderWorker();
-
- return this.renderWorker.run({ content, filePath });
- }
-
- /** Destory workers in pool. */
- destroy(): void {
- void this.renderWorker?.destroy();
- }
-
- private async createRenderWorker(): Promise {
- if (this.renderWorker) {
- return;
- }
-
- const styleIncludePaths = [...this.includePaths];
- let prevDir = null;
- let currentDir = this.basePath;
-
- while (currentDir !== prevDir) {
- const p = join(currentDir, 'node_modules');
- if (existsSync(p)) {
- styleIncludePaths.push(p);
- }
-
- prevDir = currentDir;
- currentDir = dirname(prevDir);
- }
-
- const browserslistData = browserslist(undefined, { path: this.basePath });
-
- const { version: ngPackagrVersion } = getNgPackagrVersionInfo();
- let postcssConfiguration: PostcssConfiguration | undefined;
- if (ngPackagrVersion === '17.2.0') {
- const {
- loadPostcssConfiguration,
- } = require('ng-packagr/lib/styles/postcss-configuration');
- postcssConfiguration = await loadPostcssConfiguration(
- this.projectBasePath
- );
- }
-
- this.renderWorker = new Piscina({
- filename: require.resolve(
- 'ng-packagr/lib/styles/stylesheet-processor-worker'
- ),
- maxThreads,
- recordTiming: false,
- env: {
- ...process.env,
- FORCE_COLOR: '' + colors.enabled,
- },
- workerData: {
- postcssConfiguration,
- tailwindConfigPath: getTailwindConfigPath(
- this.projectBasePath,
- workspaceRoot
- ),
- projectBasePath: this.projectBasePath,
- browserslistData,
- targets: transformSupportedBrowsersToTargets(browserslistData),
- cacheDirectory: this.cacheDirectory,
- cssUrl: this.cssUrl,
- styleIncludePaths,
- },
- });
- }
-}
-
function transformSupportedBrowsersToTargets(
supportedBrowsers: string[]
): string[] {
@@ -288,3 +165,34 @@ function transformSupportedBrowsersToTargets(
return transformed.length ? transformed : undefined;
}
+
+/**
+ * Loads the `piscina` package from the installed `ng-packagr` package.
+ */
+function getPiscina() {
+ const ngPackagrPath = getInstalledNgPackagrPath();
+
+ try {
+ // Resolve the main piscina module entry point
+ const piscinaModulePath = require.resolve('piscina', {
+ paths: [ngPackagrPath],
+ });
+
+ return require(piscinaModulePath);
+ } catch (error) {
+ throw new Error(
+ `Failed to load the \`piscina\` package from \`ng-packagr\` dependencies: ${error.message}`
+ );
+ }
+}
+
+function getInstalledNgPackagrPath(): string {
+ try {
+ const ngPackagrPackageJsonPath = require.resolve('ng-packagr/package.json');
+ return dirname(ngPackagrPackageJsonPath);
+ } catch (e) {
+ throw new Error(
+ 'The `ng-packagr` package is not installed. The package is required to use this executor. Please install it in your workspace.'
+ );
+ }
+}
diff --git a/packages/angular/src/executors/utilities/ng-packagr/stylesheet-processor.di.ts b/packages/angular/src/executors/utilities/ng-packagr/stylesheet-processor.di.ts
index d498898c80..8999a51f28 100644
--- a/packages/angular/src/executors/utilities/ng-packagr/stylesheet-processor.di.ts
+++ b/packages/angular/src/executors/utilities/ng-packagr/stylesheet-processor.di.ts
@@ -3,12 +3,11 @@ import { getNgPackagrVersionInfo } from './ng-packagr-version';
import { importNgPackagrPath } from './package-imports';
export function getStylesheetProcessorFactoryProvider(): FactoryProvider {
- const { major: ngPackagrMajorVersion, version: ngPackagrVersion } =
- getNgPackagrVersionInfo();
+ const { major: ngPackagrMajorVersion } = getNgPackagrVersionInfo();
const { STYLESHEET_PROCESSOR_TOKEN } = importNgPackagrPath<
- typeof import('ng-packagr/lib/styles/stylesheet-processor.di')
- >('ng-packagr/lib/styles/stylesheet-processor.di', ngPackagrMajorVersion);
+ typeof import('ng-packagr/src/lib/styles/stylesheet-processor.di')
+ >('ng-packagr/src/lib/styles/stylesheet-processor.di', ngPackagrMajorVersion);
return {
provide: STYLESHEET_PROCESSOR_TOKEN,
@@ -20,17 +19,8 @@ export function getStylesheetProcessorFactoryProvider(): FactoryProvider {
return getStylesheetProcessor();
}
- if (ngPackagrVersion !== '17.2.0') {
- const {
- StylesheetProcessor,
- } = require('./pre-v19/stylesheet-processor');
- return StylesheetProcessor;
- }
-
- const {
- AsyncStylesheetProcessor,
- } = require('./pre-v19/stylesheet-processor');
- return AsyncStylesheetProcessor;
+ const { StylesheetProcessor } = require('./pre-v19/stylesheet-processor');
+ return StylesheetProcessor;
},
deps: [],
};
diff --git a/packages/angular/src/executors/utilities/ng-packagr/v19+/stylesheet-processor.ts b/packages/angular/src/executors/utilities/ng-packagr/v19+/stylesheet-processor.ts
index 5990c33c42..5e9501bffd 100644
--- a/packages/angular/src/executors/utilities/ng-packagr/v19+/stylesheet-processor.ts
+++ b/packages/angular/src/executors/utilities/ng-packagr/v19+/stylesheet-processor.ts
@@ -7,7 +7,7 @@
import { workspaceRoot } from '@nx/devkit';
import browserslist from 'browserslist';
-import type { NgPackageEntryConfig } from 'ng-packagr/ng-entrypoint.schema';
+import type { NgPackageEntryConfig } from 'ng-packagr/src/ng-entrypoint.schema';
import { getNgPackagrVersionInfo } from '../ng-packagr-version';
import { importNgPackagrPath } from '../package-imports';
@@ -30,15 +30,15 @@ export function getStylesheetProcessor(): new (
const { major: ngPackagrMajorVersion } = getNgPackagrVersionInfo();
const { ComponentStylesheetBundler } = importNgPackagrPath<
- typeof import('ng-packagr/lib/styles/component-stylesheets')
- >('ng-packagr/lib/styles/component-stylesheets', ngPackagrMajorVersion);
+ typeof import('ng-packagr/src/lib/styles/component-stylesheets')
+ >('ng-packagr/src/lib/styles/component-stylesheets', ngPackagrMajorVersion);
const {
generateSearchDirectories,
getTailwindConfig,
loadPostcssConfiguration,
} = importNgPackagrPath<
- typeof import('ng-packagr/lib/styles/postcss-configuration')
- >('ng-packagr/lib/styles/postcss-configuration', ngPackagrMajorVersion);
+ typeof import('ng-packagr/src/lib/styles/postcss-configuration')
+ >('ng-packagr/src/lib/styles/postcss-configuration', ngPackagrMajorVersion);
class StylesheetProcessor extends ComponentStylesheetBundler {
constructor(
@@ -53,15 +53,21 @@ export function getStylesheetProcessor(): new (
// By default, browserslist defaults are too inclusive
// https://github.com/browserslist/browserslist/blob/83764ea81ffaa39111c204b02c371afa44a4ff07/index.js#L516-L522
// We change the default query to browsers that Angular support.
- // https://angular.io/guide/browser-support
- (browserslist.defaults as string[]) = [
- 'last 2 Chrome versions',
- 'last 1 Firefox version',
- 'last 2 Edge major versions',
- 'last 2 Safari major versions',
- 'last 2 iOS major versions',
- 'Firefox ESR',
- ];
+ // https://angular.dev/reference/versions#browser-support
+ if (ngPackagrMajorVersion >= 20) {
+ (browserslist.defaults as string[]) = browserslist(undefined, {
+ path: require.resolve('ng-packagr/.browserslistrc'),
+ });
+ } else {
+ (browserslist.defaults as string[]) = [
+ 'last 2 Chrome versions',
+ 'last 1 Firefox version',
+ 'last 2 Edge major versions',
+ 'last 2 Safari major versions',
+ 'last 2 iOS major versions',
+ 'Firefox ESR',
+ ];
+ }
const browserslistData = browserslist(undefined, { path: basePath });
let searchDirs = generateSearchDirectories([projectBasePath]);
diff --git a/packages/angular/src/executors/utilities/patch-builder-context.ts b/packages/angular/src/executors/utilities/patch-builder-context.ts
index 071a74bb1f..1c93666838 100644
--- a/packages/angular/src/executors/utilities/patch-builder-context.ts
+++ b/packages/angular/src/executors/utilities/patch-builder-context.ts
@@ -1,9 +1,7 @@
import type { BuilderContext } from '@angular-devkit/architect';
-import type {
- ApplicationBuilderOptions,
- BrowserBuilderOptions,
-} from '@angular-devkit/build-angular';
+import type { BrowserBuilderOptions } from '@angular-devkit/build-angular';
import type { Schema as BrowserEsbuildBuilderOptions } from '@angular-devkit/build-angular/src/builders/browser-esbuild/schema';
+import type { ApplicationBuilderOptions } from '@angular/build';
import type { Target } from '@nx/devkit';
const executorToBuilderMap = new Map([
diff --git a/packages/angular/src/generators/add-linting/__snapshots__/add-linting.spec.ts.snap b/packages/angular/src/generators/add-linting/__snapshots__/add-linting.spec.ts.snap
index deedf6d74c..153077a36f 100644
--- a/packages/angular/src/generators/add-linting/__snapshots__/add-linting.spec.ts.snap
+++ b/packages/angular/src/generators/add-linting/__snapshots__/add-linting.spec.ts.snap
@@ -18,6 +18,7 @@ exports[`addLinting generator should correctly generate the .eslintrc.json file
"*.ts",
],
"rules": {
+ "@angular-eslint/component-class-suffix": "off",
"@angular-eslint/component-selector": [
"error",
{
@@ -26,6 +27,7 @@ exports[`addLinting generator should correctly generate the .eslintrc.json file
"type": "element",
},
],
+ "@angular-eslint/directive-class-suffix": "off",
"@angular-eslint/directive-selector": [
"error",
{
diff --git a/packages/angular/src/generators/add-linting/add-linting.spec.ts b/packages/angular/src/generators/add-linting/add-linting.spec.ts
index 26a6132adc..344259193b 100644
--- a/packages/angular/src/generators/add-linting/add-linting.spec.ts
+++ b/packages/angular/src/generators/add-linting/add-linting.spec.ts
@@ -66,7 +66,7 @@ describe('addLinting generator', () => {
const { devDependencies } = readJson(tree, 'package.json');
expect(devDependencies['@typescript-eslint/utils']).toMatchInlineSnapshot(
- `"^8.19.0"`
+ `"^8.29.0"`
);
delete process.env.ESLINT_USE_FLAT_CONFIG;
});
@@ -168,7 +168,9 @@ describe('addLinting generator', () => {
prefix: "my-org",
style: "kebab-case"
}
- ]
+ ],
+ "@angular-eslint/component-class-suffix": "off",
+ "@angular-eslint/directive-class-suffix": "off"
}
},
{
@@ -289,7 +291,9 @@ describe('addLinting generator', () => {
"prefix": "my-org",
"style": "kebab-case"
}
- ]
+ ],
+ "@angular-eslint/component-class-suffix": "off",
+ "@angular-eslint/directive-class-suffix": "off"
}
},
{
diff --git a/packages/angular/src/generators/add-linting/add-linting.ts b/packages/angular/src/generators/add-linting/add-linting.ts
index 782b5c94e2..e96765aa50 100755
--- a/packages/angular/src/generators/add-linting/add-linting.ts
+++ b/packages/angular/src/generators/add-linting/add-linting.ts
@@ -82,6 +82,10 @@ export async function addLintingGenerator(
style: 'kebab-case',
},
],
+ // Temporary disable these rules until Angular ESLint recommended
+ // rules are updated with the new Style Guide
+ '@angular-eslint/component-class-suffix': 'off',
+ '@angular-eslint/directive-class-suffix': 'off',
},
});
addOverrideToLintConfig(tree, options.projectRoot, {
@@ -121,6 +125,10 @@ export async function addLintingGenerator(
style: 'kebab-case',
},
],
+ // Temporary disable these rules until Angular ESLint recommended
+ // rules are updated with the new Style Guide
+ '@angular-eslint/component-class-suffix': 'off',
+ '@angular-eslint/directive-class-suffix': 'off',
},
},
{
diff --git a/packages/angular/src/generators/application/__snapshots__/application.spec.ts.snap b/packages/angular/src/generators/application/__snapshots__/application.spec.ts.snap
index 6792d4ef17..7430053125 100644
--- a/packages/angular/src/generators/application/__snapshots__/application.spec.ts.snap
+++ b/packages/angular/src/generators/application/__snapshots__/application.spec.ts.snap
@@ -27,7 +27,6 @@ exports[`app --minimal should generate a correct setup when --bundler=rspack and
"styles": [
"./src/styles.css"
],
- "scripts": [],
"devServer": {},
"ssr": {
"entry": "./src/server.ts"
@@ -188,7 +187,6 @@ exports[`app --minimal should generate a correct setup when --bundler=rspack inc
"styles": [
"./src/styles.css"
],
- "scripts": [],
"devServer": {}
}
@@ -228,54 +226,54 @@ exports[`app --minimal should generate a correct setup when --bundler=rspack inc
}});"
`;
-exports[`app --minimal should skip "nx-welcome.component.ts" file and references for non-standalone apps with routing 1`] = `
-"import { NgModule } from '@angular/core';
+exports[`app --minimal should skip "nx-welcome.ts" file and references for non-standalone apps with routing 1`] = `
+"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
-import { AppComponent } from './app.component';
+import { App } from './app';
import { appRoutes } from './app.routes';
@NgModule({
- declarations: [AppComponent],
+ declarations: [App],
imports: [
BrowserModule,
RouterModule.forRoot(appRoutes),
],
- providers: [],
- bootstrap: [AppComponent],
+ providers: [provideBrowserGlobalErrorListeners()],
+ bootstrap: [App],
})
export class AppModule {}
"
`;
-exports[`app --minimal should skip "nx-welcome.component.ts" file and references for non-standalone apps with routing 2`] = `
+exports[`app --minimal should skip "nx-welcome.ts" file and references for non-standalone apps with routing 2`] = `
"import { Component } from '@angular/core';
@Component({
selector: 'app-root',
standalone: false,
- templateUrl: './app.component.html',
- styleUrl: './app.component.css',
+ templateUrl: './app.html',
+ styleUrl: './app.css',
})
-export class AppComponent {}
+export class App {}
"
`;
-exports[`app --minimal should skip "nx-welcome.component.ts" file and references for non-standalone apps with routing 3`] = `
+exports[`app --minimal should skip "nx-welcome.ts" file and references for non-standalone apps with routing 3`] = `
"import { TestBed } from '@angular/core/testing';
-import { AppComponent } from './app.component';
+import { App } from './app';
import { RouterModule } from '@angular/router';
-describe('AppComponent', () => {
+describe('App', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [RouterModule.forRoot([])],
- declarations: [AppComponent]
+ declarations: [App]
}).compileComponents();
});
it('should render title', () => {
- const fixture = TestBed.createComponent(AppComponent);
+ const fixture = TestBed.createComponent(App);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain(
@@ -286,56 +284,56 @@ describe('AppComponent', () => {
"
`;
-exports[`app --minimal should skip "nx-welcome.component.ts" file and references for non-standalone apps with routing 4`] = `
+exports[`app --minimal should skip "nx-welcome.ts" file and references for non-standalone apps with routing 4`] = `
"
Welcome plain
"
`;
-exports[`app --minimal should skip "nx-welcome.component.ts" file and references for non-standalone apps without routing 1`] = `
-"import { NgModule } from '@angular/core';
+exports[`app --minimal should skip "nx-welcome.ts" file and references for non-standalone apps without routing 1`] = `
+"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
-import { AppComponent } from './app.component';
+import { App } from './app';
@NgModule({
- declarations: [AppComponent],
+ declarations: [App],
imports: [
BrowserModule,
],
- providers: [],
- bootstrap: [AppComponent],
+ providers: [provideBrowserGlobalErrorListeners()],
+ bootstrap: [App],
})
export class AppModule {}
"
`;
-exports[`app --minimal should skip "nx-welcome.component.ts" file and references for non-standalone apps without routing 2`] = `
+exports[`app --minimal should skip "nx-welcome.ts" file and references for non-standalone apps without routing 2`] = `
"import { Component } from '@angular/core';
@Component({
selector: 'app-root',
standalone: false,
- templateUrl: './app.component.html',
- styleUrl: './app.component.css',
+ templateUrl: './app.html',
+ styleUrl: './app.css',
})
-export class AppComponent {}
+export class App {}
"
`;
-exports[`app --minimal should skip "nx-welcome.component.ts" file and references for non-standalone apps without routing 3`] = `
+exports[`app --minimal should skip "nx-welcome.ts" file and references for non-standalone apps without routing 3`] = `
"import { TestBed } from '@angular/core/testing';
-import { AppComponent } from './app.component';
+import { App } from './app';
-describe('AppComponent', () => {
+describe('App', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [],
- declarations: [AppComponent]
+ declarations: [App]
}).compileComponents();
});
it('should render title', () => {
- const fixture = TestBed.createComponent(AppComponent);
+ const fixture = TestBed.createComponent(App);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain(
@@ -346,39 +344,39 @@ describe('AppComponent', () => {
"
`;
-exports[`app --minimal should skip "nx-welcome.component.ts" file and references for non-standalone apps without routing 4`] = `
+exports[`app --minimal should skip "nx-welcome.ts" file and references for non-standalone apps without routing 4`] = `
"
Welcome plain
"
`;
-exports[`app --minimal should skip "nx-welcome.component.ts" file and references for standalone apps with routing 1`] = `
+exports[`app --minimal should skip "nx-welcome.ts" file and references for standalone apps with routing 1`] = `
"import { Component } from '@angular/core';
import { RouterModule } from '@angular/router';
@Component({
imports: [RouterModule],
selector: 'app-root',
- templateUrl: './app.component.html',
- styleUrl: './app.component.css',
+ templateUrl: './app.html',
+ styleUrl: './app.css',
})
-export class AppComponent {}
+export class App {}
"
`;
-exports[`app --minimal should skip "nx-welcome.component.ts" file and references for standalone apps with routing 2`] = `
+exports[`app --minimal should skip "nx-welcome.ts" file and references for standalone apps with routing 2`] = `
"import { TestBed } from '@angular/core/testing';
-import { AppComponent } from './app.component';
+import { App } from './app';
import { RouterModule } from '@angular/router';
-describe('AppComponent', () => {
+describe('App', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
- imports: [AppComponent, RouterModule.forRoot([])],
+ imports: [App, RouterModule.forRoot([])],
}).compileComponents();
});
it('should render title', () => {
- const fixture = TestBed.createComponent(AppComponent);
+ const fixture = TestBed.createComponent(App);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain(
@@ -389,38 +387,38 @@ describe('AppComponent', () => {
"
`;
-exports[`app --minimal should skip "nx-welcome.component.ts" file and references for standalone apps with routing 3`] = `
+exports[`app --minimal should skip "nx-welcome.ts" file and references for standalone apps with routing 3`] = `
"
Welcome plain
"
`;
-exports[`app --minimal should skip "nx-welcome.component.ts" file and references for standalone apps without routing 1`] = `
+exports[`app --minimal should skip "nx-welcome.ts" file and references for standalone apps without routing 1`] = `
"import { Component } from '@angular/core';
@Component({
imports: [],
selector: 'app-root',
- templateUrl: './app.component.html',
- styleUrl: './app.component.css',
+ templateUrl: './app.html',
+ styleUrl: './app.css',
})
-export class AppComponent {}
+export class App {}
"
`;
-exports[`app --minimal should skip "nx-welcome.component.ts" file and references for standalone apps without routing 2`] = `
+exports[`app --minimal should skip "nx-welcome.ts" file and references for standalone apps without routing 2`] = `
"import { TestBed } from '@angular/core/testing';
-import { AppComponent } from './app.component';
+import { App } from './app';
-describe('AppComponent', () => {
+describe('App', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
- imports: [AppComponent],
+ imports: [App],
}).compileComponents();
});
it('should render title', () => {
- const fixture = TestBed.createComponent(AppComponent);
+ const fixture = TestBed.createComponent(App);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain(
@@ -431,7 +429,7 @@ describe('AppComponent', () => {
"
`;
-exports[`app --minimal should skip "nx-welcome.component.ts" file and references for standalone apps without routing 3`] = `
+exports[`app --minimal should skip "nx-welcome.ts" file and references for standalone apps without routing 3`] = `
"
@@ -159,7 +159,7 @@ describe('Host App Generator', () => {
it('should generate a host, integrate existing remotes and generate any remotes that dont exist', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
await generateTestRemoteApplication(tree, {
directory: 'remote1',
typescriptConfiguration: false,
@@ -187,7 +187,7 @@ describe('Host App Generator', () => {
it('should generate a host, integrate existing remotes and generate any remotes that dont exist when --typescript=true', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
await generateTestRemoteApplication(tree, {
directory: 'remote1',
typescriptConfiguration: true,
@@ -215,7 +215,7 @@ describe('Host App Generator', () => {
it('should generate a host, integrate existing remotes and generate any remotes that dont exist, in a directory', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
await generateTestRemoteApplication(tree, {
directory: 'remote1',
typescriptConfiguration: false,
@@ -243,7 +243,7 @@ describe('Host App Generator', () => {
it('should generate a host, integrate existing remotes and generate any remotes that dont exist, in a directory when --typescript=true', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
await generateTestRemoteApplication(tree, {
directory: 'remote1',
typescriptConfiguration: true,
@@ -271,7 +271,7 @@ describe('Host App Generator', () => {
it('should generate a host with remotes using standalone components', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
// ACT
await generateTestHostApplication(tree, {
@@ -281,16 +281,14 @@ describe('Host App Generator', () => {
});
// ASSERT
- expect(tree.exists(`host/src/app/app.module.ts`)).toBeFalsy();
+ expect(tree.exists(`host/src/app/app-module.ts`)).toBeFalsy();
expect(tree.read(`host/src/bootstrap.ts`, 'utf-8')).toMatchSnapshot();
- expect(
- tree.read(`host/src/app/app.component.ts`, 'utf-8')
- ).toMatchSnapshot();
+ expect(tree.read(`host/src/app/app.ts`, 'utf-8')).toMatchSnapshot();
});
it('should generate the correct app component spec file', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
// ACT
await generateTestHostApplication(tree, {
@@ -300,14 +298,12 @@ describe('Host App Generator', () => {
});
// ASSERT
- expect(
- tree.read(`host/src/app/app.component.spec.ts`, 'utf-8')
- ).toMatchSnapshot();
+ expect(tree.read(`host/src/app/app.spec.ts`, 'utf-8')).toMatchSnapshot();
});
it('should generate the correct app component spec file with a directory', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
// ACT
await generateTestHostApplication(tree, {
@@ -318,13 +314,13 @@ describe('Host App Generator', () => {
// ASSERT
expect(
- tree.read(`test/dashboard/src/app/app.component.spec.ts`, 'utf-8')
+ tree.read(`test/dashboard/src/app/app.spec.ts`, 'utf-8')
).toMatchSnapshot();
});
it('should not generate an e2e project when e2eTestRunner is none', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
// ACT
await generateTestHostApplication(tree, {
@@ -344,7 +340,7 @@ describe('Host App Generator', () => {
describe('--ssr', () => {
it('should generate the correct files', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
// ACT
await generateTestHostApplication(tree, {
@@ -357,7 +353,7 @@ describe('Host App Generator', () => {
// ASSERT
const project = readProjectConfiguration(tree, 'test');
expect(
- tree.read(`test/src/app/app.module.ts`, 'utf-8')
+ tree.read(`test/src/app/app-module.ts`, 'utf-8')
).toMatchSnapshot();
expect(tree.read(`test/src/bootstrap.ts`, 'utf-8')).toMatchSnapshot();
expect(
@@ -380,7 +376,7 @@ describe('Host App Generator', () => {
it('should generate the correct files when --typescript=true', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
// ACT
await generateTestHostApplication(tree, {
@@ -394,7 +390,7 @@ describe('Host App Generator', () => {
// ASSERT
const project = readProjectConfiguration(tree, 'test');
expect(
- tree.read(`test/src/app/app.module.ts`, 'utf-8')
+ tree.read(`test/src/app/app-module.ts`, 'utf-8')
).toMatchSnapshot();
expect(tree.read(`test/src/bootstrap.ts`, 'utf-8')).toMatchSnapshot();
expect(
@@ -417,7 +413,7 @@ describe('Host App Generator', () => {
it('should generate the correct files for standalone', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
// ACT
await generateTestHostApplication(tree, {
@@ -428,7 +424,7 @@ describe('Host App Generator', () => {
// ASSERT
const project = readProjectConfiguration(tree, 'test');
- expect(tree.exists(`test/src/app/app.module.ts`)).toBeFalsy();
+ expect(tree.exists(`test/src/app/app-module.ts`)).toBeFalsy();
expect(tree.read(`test/src/bootstrap.ts`, 'utf-8')).toMatchSnapshot();
expect(
tree.read(`test/src/bootstrap.server.ts`, 'utf-8')
@@ -456,7 +452,7 @@ describe('Host App Generator', () => {
it('should generate the correct files for standalone when --typescript=true', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
// ACT
await generateTestHostApplication(tree, {
@@ -468,7 +464,7 @@ describe('Host App Generator', () => {
// ASSERT
const project = readProjectConfiguration(tree, 'test');
- expect(tree.exists(`test/src/app/app.module.ts`)).toBeFalsy();
+ expect(tree.exists(`test/src/app/app-module.ts`)).toBeFalsy();
expect(tree.read(`test/src/bootstrap.ts`, 'utf-8')).toMatchSnapshot();
expect(
tree.read(`test/src/bootstrap.server.ts`, 'utf-8')
@@ -493,32 +489,10 @@ describe('Host App Generator', () => {
expect(project.targets.server).toMatchSnapshot();
expect(project.targets['serve-ssr']).toMatchSnapshot();
});
-
- describe('compat', () => {
- it('should generate the correct app component spec file using RouterTestingModule', async () => {
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
- updateJson(tree, 'package.json', (json) => ({
- ...json,
- dependencies: {
- '@angular/core': '17.0.0',
- },
- }));
-
- await generateTestHostApplication(tree, {
- directory: 'host',
- remotes: ['remote1'],
- skipFormat: true,
- });
-
- expect(
- tree.read(`host/src/app/app.component.spec.ts`, 'utf-8')
- ).toMatchSnapshot();
- });
- });
});
it('should not touch the package.json when run with `--skipPackageJson`', async () => {
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
let initialPackageJson;
updateJson(tree, 'package.json', (json) => {
json.dependencies = {};
@@ -554,6 +528,141 @@ describe('Host App Generator', () => {
unitTestRunner: UnitTestRunner.None,
typescriptConfiguration: false,
})
- ).rejects.toThrowError(`Invalid remote name provided: ${remote}.`);
+ ).rejects.toThrow(`Invalid remote name provided: ${remote}.`);
+ });
+
+ describe('compat', () => {
+ it('should generate components with the "component" type for versions lower than v20', async () => {
+ const tree = createTreeWithEmptyWorkspace();
+ updateJson(tree, 'package.json', (json) => {
+ json.dependencies = {
+ ...json.dependencies,
+ '@angular/core': '~19.2.0',
+ };
+ return json;
+ });
+
+ await generateTestHostApplication(tree, {
+ directory: 'host',
+ remotes: ['remote1'],
+ skipFormat: true,
+ });
+
+ expect(tree.read('host/src/app/app.component.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { Component } from '@angular/core';
+ import { RouterModule } from '@angular/router';
+ import { NxWelcomeComponent } from './nx-welcome.component';
+
+ @Component({
+ imports: [NxWelcomeComponent, RouterModule],
+ selector: 'app-root',
+ templateUrl: './app.component.html',
+ styleUrl: './app.component.css',
+ })
+ export class AppComponent {
+ title = 'host';
+ }
+ "
+ `);
+ expect(tree.read('host/src/app/app.routes.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { NxWelcomeComponent } from './nx-welcome.component';
+ import { Route } from '@angular/router';
+
+ export const appRoutes: Route[] = [
+ {
+ path: 'remote1',
+ loadChildren: () => import('remote1/Routes').then(m => m!.remoteRoutes)
+ },
+ {
+ path: '',
+ component: NxWelcomeComponent
+ },];
+ "
+ `);
+ expect(
+ tree.read('remote1/src/app/remote-entry/entry.component.ts', 'utf-8')
+ ).toMatchInlineSnapshot(`
+ "import { Component } from '@angular/core';
+ import { CommonModule } from '@angular/common';
+ import { NxWelcomeComponent } from './nx-welcome.component';
+
+ @Component({
+ imports: [CommonModule, NxWelcomeComponent],
+ selector: 'app-remote1-entry',
+ template: \`\`
+ })
+ export class RemoteEntryComponent {}
+ "
+ `);
+ expect(tree.read('remote1/src/app/remote-entry/entry.routes.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { Route } from '@angular/router';
+ import { RemoteEntryComponent } from './entry.component';
+
+ export const remoteRoutes: Route[] = [{ path: '', component: RemoteEntryComponent }];"
+ `);
+ });
+
+ it('should generate modules with the "." type separator for versions lower than v20', async () => {
+ const tree = createTreeWithEmptyWorkspace();
+ updateJson(tree, 'package.json', (json) => {
+ json.dependencies = {
+ ...json.dependencies,
+ '@angular/core': '~19.2.0',
+ };
+ return json;
+ });
+
+ await generateTestHostApplication(tree, {
+ directory: 'host',
+ remotes: ['remote1'],
+ standalone: false,
+ skipFormat: true,
+ });
+
+ expect(tree.read('host/src/app/app.module.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { NgModule } from '@angular/core';
+ import { BrowserModule } from '@angular/platform-browser';
+ import { RouterModule } from '@angular/router';
+ import { AppComponent } from './app.component';
+ import { appRoutes } from './app.routes';
+ import { NxWelcomeComponent } from './nx-welcome.component';
+
+ @NgModule({
+ declarations: [AppComponent, NxWelcomeComponent],
+ imports: [
+ BrowserModule,
+ RouterModule.forRoot(appRoutes),
+ ],
+ providers: [],
+ bootstrap: [AppComponent],
+ })
+ export class AppModule {}
+ "
+ `);
+ expect(tree.read('remote1/src/app/remote-entry/entry.module.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { NgModule } from '@angular/core';
+ import { CommonModule } from '@angular/common';
+ import { RouterModule } from '@angular/router';
+
+ import { RemoteEntryComponent } from './entry.component';
+ import { NxWelcomeComponent } from './nx-welcome.component';
+ import { remoteRoutes } from './entry.routes';
+
+ @NgModule({
+ declarations: [RemoteEntryComponent, NxWelcomeComponent],
+ imports: [
+ CommonModule,
+ RouterModule.forChild(remoteRoutes),
+ ],
+ providers: [],
+ })
+ export class RemoteEntryModule {}"
+ `);
+ });
});
});
diff --git a/packages/angular/src/generators/host/schema.json b/packages/angular/src/generators/host/schema.json
index 9148f30df5..5633cf88e2 100644
--- a/packages/angular/src/generators/host/schema.json
+++ b/packages/angular/src/generators/host/schema.json
@@ -178,7 +178,7 @@
"x-priority": "important"
},
"serverRouting": {
- "description": "Creates a server application using the Server Routing and App Engine APIs (Developer Preview). _Note: this is only supported in Angular versions >= 19.0.0_.",
+ "description": "Creates a server application using the Server Routing and App Engine APIs for application using the `application` builder (Developer Preview). _Note: this is only supported in Angular versions 19.x.x_. From Angular 20 onwards, SSR will always enable server routing when using the `application` builder.",
"type": "boolean"
},
"typescriptConfiguration": {
diff --git a/packages/angular/src/generators/init/init.ts b/packages/angular/src/generators/init/init.ts
index 4641dac634..95dfb65bd0 100755
--- a/packages/angular/src/generators/init/init.ts
+++ b/packages/angular/src/generators/init/init.ts
@@ -11,7 +11,11 @@ import {
import { addPlugin } from '@nx/devkit/src/utils/add-plugin';
import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
import { createNodesV2 } from '../../plugins/plugin';
-import { getInstalledPackageVersion, versions } from '../utils/version-utils';
+import {
+ getInstalledAngularDevkitVersion,
+ getInstalledPackageVersion,
+ versions,
+} from '../utils/version-utils';
import { Schema } from './schema';
export async function angularInitGenerator(
@@ -61,7 +65,7 @@ function installAngularDevkitCoreIfMissing(
if (!packageVersion) {
const pkgVersions = versions(tree);
const devkitVersion =
- getInstalledPackageVersion(tree, '@angular-devkit/build-angular') ??
+ getInstalledAngularDevkitVersion(tree) ??
pkgVersions.angularDevkitVersion;
try {
diff --git a/packages/angular/src/generators/library-secondary-entry-point/__snapshots__/library-secondary-entry-point.spec.ts.snap b/packages/angular/src/generators/library-secondary-entry-point/__snapshots__/library-secondary-entry-point.spec.ts.snap
index ec2e3fad9f..b407609b76 100644
--- a/packages/angular/src/generators/library-secondary-entry-point/__snapshots__/library-secondary-entry-point.spec.ts.snap
+++ b/packages/angular/src/generators/library-secondary-entry-point/__snapshots__/library-secondary-entry-point.spec.ts.snap
@@ -6,7 +6,7 @@ exports[`librarySecondaryEntryPoint generator --skipModule should not generate a
`;
exports[`librarySecondaryEntryPoint generator should format files 1`] = `
-"export * from './lib/testing.module';
+"export * from './lib/testing-module';
"
`;
@@ -22,6 +22,6 @@ export class TestingModule {}
`;
exports[`librarySecondaryEntryPoint generator should generate files for the secondary entry point 1`] = `
-"export * from './lib/testing.module';
+"export * from './lib/testing-module';
"
`;
diff --git a/packages/angular/src/generators/library-secondary-entry-point/files/src/index.ts__tmpl__ b/packages/angular/src/generators/library-secondary-entry-point/files/src/index.ts__tmpl__
index 32c51366a5..a9d8e0a84d 100644
--- a/packages/angular/src/generators/library-secondary-entry-point/files/src/index.ts__tmpl__
+++ b/packages/angular/src/generators/library-secondary-entry-point/files/src/index.ts__tmpl__
@@ -1 +1 @@
-<% if (!skipModule) { %>export * from './lib/<%= fileName %>.module';<% } else { %>export const greeting = 'Hello World!';<% } %>
+<% if (!skipModule) { %>export * from './lib/<%= fileName %><%= moduleTypeSeparator %>module';<% } else { %>export const greeting = 'Hello World!';<% } %>
diff --git a/packages/angular/src/generators/library-secondary-entry-point/files/src/lib/__fileName__.module.ts__tmpl__ b/packages/angular/src/generators/library-secondary-entry-point/files/src/lib/__fileName____moduleTypeSeparator__module.ts__tmpl__
similarity index 100%
rename from packages/angular/src/generators/library-secondary-entry-point/files/src/lib/__fileName__.module.ts__tmpl__
rename to packages/angular/src/generators/library-secondary-entry-point/files/src/lib/__fileName____moduleTypeSeparator__module.ts__tmpl__
diff --git a/packages/angular/src/generators/library-secondary-entry-point/lib/add-files.ts b/packages/angular/src/generators/library-secondary-entry-point/lib/add-files.ts
index e525539509..c954e8b910 100644
--- a/packages/angular/src/generators/library-secondary-entry-point/lib/add-files.ts
+++ b/packages/angular/src/generators/library-secondary-entry-point/lib/add-files.ts
@@ -14,6 +14,7 @@ export function addFiles(
{
...options,
...nameVariants,
+ moduleTypeSeparator: options.moduleTypeSeparator,
tmpl: '',
}
);
@@ -22,7 +23,7 @@ export function addFiles(
tree.delete(
joinPathFragments(
options.entryPointDestination,
- `src/lib/${nameVariants.fileName}.module.ts`
+ `src/lib/${nameVariants.fileName}${options.moduleTypeSeparator}module.ts`
)
);
}
diff --git a/packages/angular/src/generators/library-secondary-entry-point/lib/normalize-options.ts b/packages/angular/src/generators/library-secondary-entry-point/lib/normalize-options.ts
index 585b150a4d..5415393dba 100644
--- a/packages/angular/src/generators/library-secondary-entry-point/lib/normalize-options.ts
+++ b/packages/angular/src/generators/library-secondary-entry-point/lib/normalize-options.ts
@@ -3,9 +3,10 @@ import {
joinPathFragments,
names,
readJson,
- Tree,
+ type Tree,
} from '@nx/devkit';
-import { GeneratorOptions, NormalizedGeneratorOptions } from '../schema';
+import { getModuleTypeSeparator } from '../../utils/artifact-types';
+import type { GeneratorOptions, NormalizedGeneratorOptions } from '../schema';
export function normalizeOptions(
tree: Tree,
@@ -42,6 +43,7 @@ export function normalizeOptions(
joinPathFragments(libraryProject.root, 'package.json')
);
const secondaryEntryPoint = `${mainEntryPoint}/${options.name}`;
+ const moduleTypeSeparator = getModuleTypeSeparator(tree);
return {
...options,
@@ -51,5 +53,6 @@ export function normalizeOptions(
libraryProject,
secondaryEntryPoint,
skipModule: options.skipModule ?? false,
+ moduleTypeSeparator,
};
}
diff --git a/packages/angular/src/generators/library-secondary-entry-point/library-secondary-entry-point.spec.ts b/packages/angular/src/generators/library-secondary-entry-point/library-secondary-entry-point.spec.ts
index 1c6f10309c..e5c61c2c84 100644
--- a/packages/angular/src/generators/library-secondary-entry-point/library-secondary-entry-point.spec.ts
+++ b/packages/angular/src/generators/library-secondary-entry-point/library-secondary-entry-point.spec.ts
@@ -1,7 +1,14 @@
import 'nx/src/internal-testing-utils/mock-project-graph';
import * as devkit from '@nx/devkit';
-import { addProjectConfiguration, readJson, Tree } from '@nx/devkit';
+import {
+ addProjectConfiguration,
+ readJson,
+ readNxJson,
+ Tree,
+ updateJson,
+ updateNxJson,
+} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { generateTestLibrary } from '../utils/testing';
import { librarySecondaryEntryPointGenerator } from './library-secondary-entry-point';
@@ -10,7 +17,7 @@ describe('librarySecondaryEntryPoint generator', () => {
let tree: Tree;
beforeEach(() => {
- tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ tree = createTreeWithEmptyWorkspace();
});
it('should throw when the library does not exist in the workspace', async () => {
@@ -78,13 +85,47 @@ describe('librarySecondaryEntryPoint generator', () => {
expect(tree.exists('libs/lib1/testing/README.md')).toBeTruthy();
expect(tree.exists('libs/lib1/testing/src/index.ts')).toBeTruthy();
expect(
- tree.exists('libs/lib1/testing/src/lib/testing.module.ts')
+ tree.exists('libs/lib1/testing/src/lib/testing-module.ts')
).toBeTruthy();
expect(
tree.read('libs/lib1/testing/src/index.ts', 'utf-8')
).toMatchSnapshot();
});
+ it('should generate the module file respecting the "typeSeparator" generator default', async () => {
+ const nxJson = readNxJson(tree);
+ nxJson.generators = {
+ ...nxJson.generators,
+ '@nx/angular:module': {
+ typeSeparator: '.',
+ },
+ };
+ updateNxJson(tree, nxJson);
+ addProjectConfiguration(tree, 'lib1', {
+ root: 'libs/lib1',
+ projectType: 'library',
+ });
+ tree.write(
+ 'libs/lib1/package.json',
+ JSON.stringify({ name: '@my-org/lib1' })
+ );
+
+ await librarySecondaryEntryPointGenerator(tree, {
+ name: 'testing',
+ library: 'lib1',
+ skipFormat: true,
+ });
+
+ expect(tree.exists('libs/lib1/testing/src/lib/testing.module.ts')).toBe(
+ true
+ );
+ expect(tree.read('libs/lib1/testing/src/index.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "export * from './lib/testing.module';
+ "
+ `);
+ });
+
it('should configure the entry file', async () => {
addProjectConfiguration(tree, 'lib1', {
root: 'libs/lib1',
@@ -205,7 +246,7 @@ describe('librarySecondaryEntryPoint generator', () => {
tree.read('libs/lib1/testing/src/index.ts', 'utf-8')
).toMatchSnapshot();
expect(
- tree.read('libs/lib1/testing/src/lib/testing.module.ts', 'utf-8')
+ tree.read('libs/lib1/testing/src/lib/testing-module.ts', 'utf-8')
).toMatchSnapshot();
});
@@ -228,7 +269,7 @@ describe('librarySecondaryEntryPoint generator', () => {
});
expect(
- tree.exists('libs/lib1/testing/src/lib/testing.module.ts')
+ tree.exists('libs/lib1/testing/src/lib/testing-module.ts')
).toBeFalsy();
expect(tree.exists('libs/lib1/testing/ng-package.json')).toBeTruthy();
expect(tree.exists('libs/lib1/testing/README.md')).toBeTruthy();
@@ -238,4 +279,40 @@ describe('librarySecondaryEntryPoint generator', () => {
).toMatchSnapshot();
});
});
+
+ describe('compat', () => {
+ it('should generate the module file with the "." type separator for versions lower than v20', async () => {
+ updateJson(tree, 'package.json', (json) => {
+ json.dependencies = {
+ ...json.dependencies,
+ '@angular/core': '~19.2.0',
+ };
+ return json;
+ });
+
+ addProjectConfiguration(tree, 'lib1', {
+ root: 'libs/lib1',
+ projectType: 'library',
+ });
+ tree.write(
+ 'libs/lib1/package.json',
+ JSON.stringify({ name: '@my-org/lib1' })
+ );
+
+ await librarySecondaryEntryPointGenerator(tree, {
+ name: 'testing',
+ library: 'lib1',
+ skipFormat: true,
+ });
+
+ expect(tree.exists('libs/lib1/testing/src/lib/testing.module.ts')).toBe(
+ true
+ );
+ expect(tree.read('libs/lib1/testing/src/index.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "export * from './lib/testing.module';
+ "
+ `);
+ });
+ });
});
diff --git a/packages/angular/src/generators/library-secondary-entry-point/schema.d.ts b/packages/angular/src/generators/library-secondary-entry-point/schema.d.ts
index 0ecca58162..cc7bac6a5f 100644
--- a/packages/angular/src/generators/library-secondary-entry-point/schema.d.ts
+++ b/packages/angular/src/generators/library-secondary-entry-point/schema.d.ts
@@ -13,4 +13,5 @@ export interface NormalizedGeneratorOptions extends GeneratorOptions {
mainEntryPoint: string;
secondaryEntryPoint: string;
skipModule: boolean;
+ moduleTypeSeparator: '-' | '.';
}
diff --git a/packages/angular/src/generators/library/__snapshots__/library.spec.ts.snap b/packages/angular/src/generators/library/__snapshots__/library.spec.ts.snap
index 422ff3f9f3..a591a2a9de 100644
--- a/packages/angular/src/generators/library/__snapshots__/library.spec.ts.snap
+++ b/packages/angular/src/generators/library/__snapshots__/library.spec.ts.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`lib --standalone should generate a library with a standalone component and have it flat 1`] = `"export * from './lib/my-lib.component';"`;
+exports[`lib --standalone should generate a library with a standalone component and have it flat 1`] = `"export * from './lib/my-lib';"`;
exports[`lib --standalone should generate a library with a standalone component and have it flat 2`] = `
"import { Component } from '@angular/core';
@@ -9,27 +9,27 @@ import { CommonModule } from '@angular/common';
@Component({
selector: 'lib-my-lib',
imports: [CommonModule],
- templateUrl: './my-lib.component.html',
- styleUrl: './my-lib.component.css'
+ templateUrl: './my-lib.html',
+ styleUrl: './my-lib.css'
})
-export class MyLibComponent {}
+export class MyLib {}
"
`;
exports[`lib --standalone should generate a library with a standalone component and have it flat 3`] = `
"import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { MyLibComponent } from './my-lib.component';
+import { MyLib } from './my-lib';
-describe('MyLibComponent', () => {
- let component: MyLibComponent;
- let fixture: ComponentFixture;
+describe('MyLib', () => {
+ let component: MyLib;
+ let fixture: ComponentFixture;
beforeEach(async () => {
await TestBed.configureTestingModule({
- imports: [MyLibComponent]
+ imports: [MyLib]
}).compileComponents();
- fixture = TestBed.createComponent(MyLibComponent);
+ fixture = TestBed.createComponent(MyLib);
component = fixture.componentInstance;
fixture.detectChanges();
});
@@ -44,7 +44,7 @@ describe('MyLibComponent', () => {
exports[`lib --standalone should generate a library with a standalone component and have it flat with routing setup 1`] = `
"export * from './lib/lib.routes';
-export * from './lib/my-lib.component';"
+export * from './lib/my-lib';"
`;
exports[`lib --standalone should generate a library with a standalone component and have it flat with routing setup 2`] = `
@@ -54,27 +54,27 @@ import { CommonModule } from '@angular/common';
@Component({
selector: 'lib-my-lib',
imports: [CommonModule],
- templateUrl: './my-lib.component.html',
- styleUrl: './my-lib.component.css'
+ templateUrl: './my-lib.html',
+ styleUrl: './my-lib.css'
})
-export class MyLibComponent {}
+export class MyLib {}
"
`;
exports[`lib --standalone should generate a library with a standalone component and have it flat with routing setup 3`] = `
"import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { MyLibComponent } from './my-lib.component';
+import { MyLib } from './my-lib';
-describe('MyLibComponent', () => {
- let component: MyLibComponent;
- let fixture: ComponentFixture;
+describe('MyLib', () => {
+ let component: MyLib;
+ let fixture: ComponentFixture;
beforeEach(async () => {
await TestBed.configureTestingModule({
- imports: [MyLibComponent]
+ imports: [MyLib]
}).compileComponents();
- fixture = TestBed.createComponent(MyLibComponent);
+ fixture = TestBed.createComponent(MyLib);
component = fixture.componentInstance;
fixture.detectChanges();
});
@@ -88,15 +88,15 @@ describe('MyLibComponent', () => {
exports[`lib --standalone should generate a library with a standalone component and have it flat with routing setup 4`] = `
"import { Route } from '@angular/router';
-import { MyLibComponent } from './my-lib.component';
+import { MyLib } from './my-lib';
export const myLibRoutes: Route[] = [
- { path: '', component: MyLibComponent }
+ { path: '', component: MyLib }
];
"
`;
-exports[`lib --standalone should generate a library with a standalone component as entry point 1`] = `"export * from './lib/my-lib/my-lib.component';"`;
+exports[`lib --standalone should generate a library with a standalone component as entry point 1`] = `"export * from './lib/my-lib/my-lib';"`;
exports[`lib --standalone should generate a library with a standalone component as entry point 2`] = `
"import { Component } from '@angular/core';
@@ -105,27 +105,27 @@ import { CommonModule } from '@angular/common';
@Component({
selector: 'lib-my-lib',
imports: [CommonModule],
- templateUrl: './my-lib.component.html',
- styleUrl: './my-lib.component.css'
+ templateUrl: './my-lib.html',
+ styleUrl: './my-lib.css'
})
-export class MyLibComponent {}
+export class MyLib {}
"
`;
exports[`lib --standalone should generate a library with a standalone component as entry point 3`] = `
"import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { MyLibComponent } from './my-lib.component';
+import { MyLib } from './my-lib';
-describe('MyLibComponent', () => {
- let component: MyLibComponent;
- let fixture: ComponentFixture;
+describe('MyLib', () => {
+ let component: MyLib;
+ let fixture: ComponentFixture;
beforeEach(async () => {
await TestBed.configureTestingModule({
- imports: [MyLibComponent]
+ imports: [MyLib]
}).compileComponents();
- fixture = TestBed.createComponent(MyLibComponent);
+ fixture = TestBed.createComponent(MyLib);
component = fixture.componentInstance;
fixture.detectChanges();
});
@@ -137,7 +137,7 @@ describe('MyLibComponent', () => {
"
`;
-exports[`lib --standalone should generate a library with a standalone component as entry point and set up view encapsulation and change detection 1`] = `"export * from './lib/my-lib/my-lib.component';"`;
+exports[`lib --standalone should generate a library with a standalone component as entry point and set up view encapsulation and change detection 1`] = `"export * from './lib/my-lib/my-lib';"`;
exports[`lib --standalone should generate a library with a standalone component as entry point and set up view encapsulation and change detection 2`] = `
"import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
@@ -151,11 +151,11 @@ import { CommonModule } from '@angular/common';
encapsulation: ViewEncapsulation.ShadowDom,
changeDetection: ChangeDetectionStrategy.OnPush
})
-export class MyLibComponent {}
+export class MyLib {}
"
`;
-exports[`lib --standalone should generate a library with a standalone component as entry point and skip tests 1`] = `"export * from './lib/my-lib/my-lib.component';"`;
+exports[`lib --standalone should generate a library with a standalone component as entry point and skip tests 1`] = `"export * from './lib/my-lib/my-lib';"`;
exports[`lib --standalone should generate a library with a standalone component as entry point and skip tests 2`] = `
"import { Component } from '@angular/core';
@@ -167,11 +167,11 @@ import { CommonModule } from '@angular/common';
template: \`
MyLib works!
\`,
styles: \`\`
})
-export class MyLibComponent {}
+export class MyLib {}
"
`;
-exports[`lib --standalone should generate a library with a standalone component as entry point following SFC pattern 1`] = `"export * from './lib/my-lib/my-lib.component';"`;
+exports[`lib --standalone should generate a library with a standalone component as entry point following SFC pattern 1`] = `"export * from './lib/my-lib/my-lib';"`;
exports[`lib --standalone should generate a library with a standalone component as entry point following SFC pattern 2`] = `
"import { Component } from '@angular/core';
@@ -183,24 +183,24 @@ import { CommonModule } from '@angular/common';
template: \`
MyLib works!
\`,
styles: \`\`
})
-export class MyLibComponent {}
+export class MyLib {}
"
`;
exports[`lib --standalone should generate a library with a standalone component as entry point following SFC pattern 3`] = `
"import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { MyLibComponent } from './my-lib.component';
+import { MyLib } from './my-lib';
-describe('MyLibComponent', () => {
- let component: MyLibComponent;
- let fixture: ComponentFixture;
+describe('MyLib', () => {
+ let component: MyLib;
+ let fixture: ComponentFixture;
beforeEach(async () => {
await TestBed.configureTestingModule({
- imports: [MyLibComponent]
+ imports: [MyLib]
}).compileComponents();
- fixture = TestBed.createComponent(MyLibComponent);
+ fixture = TestBed.createComponent(MyLib);
component = fixture.componentInstance;
fixture.detectChanges();
});
@@ -215,15 +215,15 @@ describe('MyLibComponent', () => {
exports[`lib --standalone should generate a library with a standalone component as entry point with routing setup 1`] = `
"export * from './lib/lib.routes';
-export * from './lib/my-lib/my-lib.component';"
+export * from './lib/my-lib/my-lib';"
`;
exports[`lib --standalone should generate a library with a standalone component as entry point with routing setup 2`] = `
"import { Route } from '@angular/router';
-import { MyLibComponent } from './my-lib/my-lib.component';
+import { MyLib } from './my-lib/my-lib';
export const myLibRoutes: Route[] = [
- { path: '', component: MyLibComponent }
+ { path: '', component: MyLib }
];
"
`;
@@ -235,27 +235,27 @@ import { CommonModule } from '@angular/common';
@Component({
selector: 'lib-my-lib',
imports: [CommonModule],
- templateUrl: './my-lib.component.html',
- styleUrl: './my-lib.component.css'
+ templateUrl: './my-lib.html',
+ styleUrl: './my-lib.css'
})
-export class MyLibComponent {}
+export class MyLib {}
"
`;
exports[`lib --standalone should generate a library with a standalone component as entry point with routing setup 4`] = `
"import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { MyLibComponent } from './my-lib.component';
+import { MyLib } from './my-lib';
-describe('MyLibComponent', () => {
- let component: MyLibComponent;
- let fixture: ComponentFixture;
+describe('MyLib', () => {
+ let component: MyLib;
+ let fixture: ComponentFixture;
beforeEach(async () => {
await TestBed.configureTestingModule({
- imports: [MyLibComponent]
+ imports: [MyLib]
}).compileComponents();
- fixture = TestBed.createComponent(MyLibComponent);
+ fixture = TestBed.createComponent(MyLib);
component = fixture.componentInstance;
fixture.detectChanges();
});
@@ -270,15 +270,15 @@ describe('MyLibComponent', () => {
exports[`lib --standalone should generate a library with a standalone component as entry point with routing setup and attach it to parent module as a lazy child 1`] = `
"export * from './lib/lib.routes';
-export * from './lib/my-lib/my-lib.component';"
+export * from './lib/my-lib/my-lib';"
`;
exports[`lib --standalone should generate a library with a standalone component as entry point with routing setup and attach it to parent module as a lazy child 2`] = `
"import { Route } from '@angular/router';
-import { MyLibComponent } from './my-lib/my-lib.component';
+import { MyLib } from './my-lib/my-lib';
export const myLibRoutes: Route[] = [
- { path: '', component: MyLibComponent }
+ { path: '', component: MyLib }
];
"
`;
@@ -294,15 +294,15 @@ export const appRoutes: Route[] = [
exports[`lib --standalone should generate a library with a standalone component as entry point with routing setup and attach it to parent module as direct child 1`] = `
"export * from './lib/lib.routes';
-export * from './lib/my-lib/my-lib.component';"
+export * from './lib/my-lib/my-lib';"
`;
exports[`lib --standalone should generate a library with a standalone component as entry point with routing setup and attach it to parent module as direct child 2`] = `
"import { Route } from '@angular/router';
-import { MyLibComponent } from './my-lib/my-lib.component';
+import { MyLib } from './my-lib/my-lib';
export const myLibRoutes: Route[] = [
- { path: '', component: MyLibComponent }
+ { path: '', component: MyLib }
];
"
`;
@@ -318,28 +318,28 @@ export const appRoutes: Route[] = [
exports[`lib --standalone should generate a library with a standalone component as entry point with routing setup and attach it to standalone parent routes as a lazy child 1`] = `
"import { Route } from '@angular/router';
-import { MyLibComponent } from './my-lib/my-lib.component';
+import { MyLib } from './my-lib/my-lib';
export const myLibRoutes: Route[] = [
{ path: 'second', loadChildren: () => import('@proj/second').then(m => m.secondRoutes) },
- { path: '', component: MyLibComponent }
+ { path: '', component: MyLib }
];
"
`;
exports[`lib --standalone should generate a library with a standalone component as entry point with routing setup and attach it to standalone parent routes as direct child 1`] = `
"import { Route } from '@angular/router';
-import { MyLibComponent } from './my-lib/my-lib.component';
+import { MyLib } from './my-lib/my-lib';
import { secondRoutes } from '@proj/second';
export const myLibRoutes: Route[] = [
{ path: 'second', children: secondRoutes },
- { path: '', component: MyLibComponent }
+ { path: '', component: MyLib }
];
"
`;
-exports[`lib --standalone should generate a library with a standalone component in a directory 1`] = `"export * from './lib/my-lib/my-lib.component';"`;
+exports[`lib --standalone should generate a library with a standalone component in a directory 1`] = `"export * from './lib/my-lib/my-lib';"`;
exports[`lib --standalone should generate a library with a standalone component in a directory 2`] = `
"import { Component } from '@angular/core';
@@ -348,27 +348,27 @@ import { CommonModule } from '@angular/common';
@Component({
selector: 'lib-my-lib',
imports: [CommonModule],
- templateUrl: './my-lib.component.html',
- styleUrl: './my-lib.component.css'
+ templateUrl: './my-lib.html',
+ styleUrl: './my-lib.css'
})
-export class MyLibComponent {}
+export class MyLib {}
"
`;
exports[`lib --standalone should generate a library with a standalone component in a directory 3`] = `
"import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { MyLibComponent } from './my-lib.component';
+import { MyLib } from './my-lib';
-describe('MyLibComponent', () => {
- let component: MyLibComponent;
- let fixture: ComponentFixture;
+describe('MyLib', () => {
+ let component: MyLib;
+ let fixture: ComponentFixture;
beforeEach(async () => {
await TestBed.configureTestingModule({
- imports: [MyLibComponent]
+ imports: [MyLib]
}).compileComponents();
- fixture = TestBed.createComponent(MyLibComponent);
+ fixture = TestBed.createComponent(MyLib);
component = fixture.componentInstance;
fixture.detectChanges();
});
@@ -380,7 +380,7 @@ describe('MyLibComponent', () => {
"
`;
-exports[`lib --standalone should generate a library with a standalone component in a directory with a simple name 1`] = `"export * from './lib/my-lib/my-lib.component';"`;
+exports[`lib --standalone should generate a library with a standalone component in a directory with a simple name 1`] = `"export * from './lib/my-lib/my-lib';"`;
exports[`lib --standalone should generate a library with a standalone component in a directory with a simple name 2`] = `
"import { Component } from '@angular/core';
@@ -389,27 +389,27 @@ import { CommonModule } from '@angular/common';
@Component({
selector: 'lib-my-lib',
imports: [CommonModule],
- templateUrl: './my-lib.component.html',
- styleUrl: './my-lib.component.css'
+ templateUrl: './my-lib.html',
+ styleUrl: './my-lib.css'
})
-export class MyLibComponent {}
+export class MyLib {}
"
`;
exports[`lib --standalone should generate a library with a standalone component in a directory with a simple name 3`] = `
"import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { MyLibComponent } from './my-lib.component';
+import { MyLib } from './my-lib';
-describe('MyLibComponent', () => {
- let component: MyLibComponent;
- let fixture: ComponentFixture;
+describe('MyLib', () => {
+ let component: MyLib;
+ let fixture: ComponentFixture;
beforeEach(async () => {
await TestBed.configureTestingModule({
- imports: [MyLibComponent]
+ imports: [MyLib]
}).compileComponents();
- fixture = TestBed.createComponent(MyLibComponent);
+ fixture = TestBed.createComponent(MyLib);
component = fixture.componentInstance;
fixture.detectChanges();
});
@@ -421,7 +421,7 @@ describe('MyLibComponent', () => {
"
`;
-exports[`lib --standalone should generate a library with a valid selector for the standalone component when library name has a slash 1`] = `"export * from './lib/auth/common/auth/common.component';"`;
+exports[`lib --standalone should generate a library with a valid selector for the standalone component when library name has a slash 1`] = `"export * from './lib/auth/common/auth/common';"`;
exports[`lib --standalone should generate a library with a valid selector for the standalone component when library name has a slash 2`] = `
"import { Component } from '@angular/core';
@@ -430,10 +430,10 @@ import { CommonModule } from '@angular/common';
@Component({
selector: 'lib-auth-common',
imports: [CommonModule],
- templateUrl: './common.component.html',
- styleUrl: './common.component.css'
+ templateUrl: './common.html',
+ styleUrl: './common.css'
})
-export class AuthCommonComponent {}
+export class AuthCommon {}
"
`;
@@ -467,7 +467,7 @@ exports[`lib router lazy should update the parent module 1`] = `
"import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
-import { AppComponent } from './app.component';
+import { App } from './app';
@NgModule({
imports: [
BrowserModule,
@@ -478,8 +478,8 @@ import { AppComponent } from './app.component';
},
]),
],
- declarations: [AppComponent],
- bootstrap: [AppComponent],
+ declarations: [App],
+ bootstrap: [App],
})
export class AppModule {}
"
@@ -489,7 +489,7 @@ exports[`lib router lazy should update the parent module 3`] = `
"import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
-import { AppComponent } from './app.component';
+import { App } from './app';
@NgModule({
imports: [
BrowserModule,
@@ -508,8 +508,8 @@ import { AppComponent } from './app.component';
},
]),
],
- declarations: [AppComponent],
- bootstrap: [AppComponent],
+ declarations: [App],
+ bootstrap: [App],
})
export class AppModule {}
"
@@ -520,14 +520,14 @@ exports[`lib router lazy should update the parent module even if the route is de
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
- import { AppComponent } from './app.component';
+ import { App } from './app';
const routes = [{ path: 'my-lib', loadChildren: () => import('@proj/my-lib').then(m => m.MyLibModule) }];
@NgModule({
imports: [BrowserModule, RouterModule.forRoot(routes)],
- declarations: [AppComponent],
- bootstrap: [AppComponent]
+ declarations: [App],
+ bootstrap: [App]
})
export class AppModule {}
"
diff --git a/packages/angular/src/generators/library/files/base/tsconfig.json__tpl__ b/packages/angular/src/generators/library/files/base/tsconfig.json__tpl__
index 04102d98f1..cc86607c01 100644
--- a/packages/angular/src/generators/library/files/base/tsconfig.json__tpl__
+++ b/packages/angular/src/generators/library/files/base/tsconfig.json__tpl__
@@ -1,7 +1,8 @@
{
- "compilerOptions": {
- "target": "es2022"<% if (disableModernClassFieldsBehavior) { %>,
- "useDefineForClassFields": false<% } %>
+ "extends": "<%= rootTsConfig %>",
+ "compilerOptions": {},
+ "angularCompilerOptions": {
+ "enableI18nLegacyMessageIdFormat": false
},
"files": [],
"include": [],
@@ -9,6 +10,5 @@
{
"path": "./tsconfig.lib.json"
}
- ],
- "extends": "<%= rootTsConfig %>"
+ ]
}
diff --git a/packages/angular/src/generators/library/files/ng-module/src/index.ts__tpl__ b/packages/angular/src/generators/library/files/ng-module/src/index.ts__tpl__
index 8d8502946f..524e652be8 100644
--- a/packages/angular/src/generators/library/files/ng-module/src/index.ts__tpl__
+++ b/packages/angular/src/generators/library/files/ng-module/src/index.ts__tpl__
@@ -1,2 +1,2 @@
-<% if (!skipModule) { %>export * from './lib/<%= libFileName %>.module';<% } %><% if(routing) { %>
+<% if (!skipModule) { %>export * from './lib/<%= libFileName %><%= moduleTypeSeparator %>module';<% } %><% if(routing) { %>
export * from './lib/lib.routes';<% } %>
diff --git a/packages/angular/src/generators/library/files/ng-module/src/lib/__libFileName__.module.ts__tpl__ b/packages/angular/src/generators/library/files/ng-module/src/lib/__libFileName____moduleTypeSeparator__module.ts__tpl__
similarity index 100%
rename from packages/angular/src/generators/library/files/ng-module/src/lib/__libFileName__.module.ts__tpl__
rename to packages/angular/src/generators/library/files/ng-module/src/lib/__libFileName____moduleTypeSeparator__module.ts__tpl__
diff --git a/packages/angular/src/generators/library/files/standalone-components/src/lib/lib.routes.ts__tpl__ b/packages/angular/src/generators/library/files/standalone-components/src/lib/lib.routes.ts__tpl__
index 295f0ae9e9..d6db24a0dc 100644
--- a/packages/angular/src/generators/library/files/standalone-components/src/lib/lib.routes.ts__tpl__
+++ b/packages/angular/src/generators/library/files/standalone-components/src/lib/lib.routes.ts__tpl__
@@ -1,6 +1,6 @@
import { Route } from '@angular/router';
-import { <%= libClassName %>Component } from './<%= pathToComponent %>.component';
+import { <%= libClassName %><%= componentType %> } from './<%= pathToComponent %><%= componentFileSuffix %>';
export const <%= libPropertyName %>Routes: Route[] = [
- { path: '', component: <%= libClassName %>Component }
+ { path: '', component: <%= libClassName %><%= componentType %> }
];
diff --git a/packages/angular/src/generators/library/lib/add-standalone-component.ts b/packages/angular/src/generators/library/lib/add-standalone-component.ts
index dd1507f49e..7769e38c90 100644
--- a/packages/angular/src/generators/library/lib/add-standalone-component.ts
+++ b/packages/angular/src/generators/library/lib/add-standalone-component.ts
@@ -21,6 +21,7 @@ export async function addStandaloneComponent(
),
standalone: true,
export: true,
+ type: componentOptions.type,
skipFormat: true,
});
diff --git a/packages/angular/src/generators/library/lib/create-files.ts b/packages/angular/src/generators/library/lib/create-files.ts
index 33b33da182..ea88d786ec 100644
--- a/packages/angular/src/generators/library/lib/create-files.ts
+++ b/packages/angular/src/generators/library/lib/create-files.ts
@@ -6,7 +6,7 @@ import {
offsetFromRoot,
} from '@nx/devkit';
import { getRootTsConfigFileName } from '@nx/js';
-import { lt, parse } from 'semver';
+import { parse } from 'semver';
import { UnitTestRunner } from '../../../utils/test-runners';
import type { AngularProjectConfiguration } from '../../../utils/types';
import { getInstalledAngularVersion } from '../../utils/version-utils';
@@ -28,7 +28,13 @@ export function createFiles(
const version = getInstalledAngularVersion(tree);
const { major, minor } = parse(version);
- const disableModernClassFieldsBehavior = lt(version, '18.1.0-rc.0');
+
+ const componentType = options.componentOptions.type
+ ? names(options.componentOptions.type).className
+ : '';
+ const componentFileSuffix = options.componentOptions.type
+ ? `.${options.componentOptions.type}`
+ : '';
const substitutions = {
libName: options.libraryOptions.name,
@@ -44,7 +50,9 @@ export function createFiles(
importPath: options.libraryOptions.importPath,
rootOffset,
angularPeerDepVersion: `^${major}.${minor}.0`,
- disableModernClassFieldsBehavior,
+ componentType,
+ componentFileSuffix,
+ moduleTypeSeparator: options.libraryOptions.moduleTypeSeparator,
tpl: '',
};
@@ -71,12 +79,7 @@ export function createFiles(
);
if (options.libraryOptions.skipModule) {
- tree.delete(
- joinPathFragments(
- project.sourceRoot,
- `lib/${options.libraryOptions.fileName}.module.ts`
- )
- );
+ tree.delete(options.libraryOptions.modulePath);
}
}
diff --git a/packages/angular/src/generators/library/lib/enable-strict-type-checking.ts b/packages/angular/src/generators/library/lib/enable-strict-type-checking.ts
deleted file mode 100644
index 8ada56d655..0000000000
--- a/packages/angular/src/generators/library/lib/enable-strict-type-checking.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import type { Tree } from '@nx/devkit';
-import { readNxJson, updateJson, updateNxJson } from '@nx/devkit';
-import { NormalizedSchema } from './normalized-schema';
-
-/**
- * Enable Strict Mode in the library and spec TS Config
- * */
-export function enableStrictTypeChecking(
- host: Tree,
- options: NormalizedSchema['libraryOptions']
-) {
- updateTsConfig(host, options);
-}
-
-export function setLibraryStrictDefault(host: Tree, isStrict: boolean) {
- // set the default so future libraries use it
- // unless the user has previously set this value
- const nxJson = readNxJson(host);
-
- nxJson.generators = nxJson.generators || {};
-
- nxJson.generators['@nx/angular:library'] =
- nxJson.generators['@nx/angular:library'] || {};
-
- nxJson.generators['@nx/angular:library'].strict =
- nxJson.generators['@nx/angular:library'].strict ?? isStrict;
- updateNxJson(host, nxJson);
-}
-
-function updateTsConfig(
- host: Tree,
- options: NormalizedSchema['libraryOptions']
-) {
- // Update the settings in the tsconfig.app.json to enable strict type checking.
- // This matches the settings defined by the Angular CL https://angular.io/guide/strict-mode
- updateJson(host, `${options.projectRoot}/tsconfig.json`, (json) => {
- // update the TypeScript settings
- json.compilerOptions = {
- ...(json.compilerOptions ?? {}),
- forceConsistentCasingInFileNames: true,
- strict: true,
- noImplicitOverride: true,
- noPropertyAccessFromIndexSignature: true,
- noImplicitReturns: true,
- noFallthroughCasesInSwitch: true,
- };
-
- // update Angular Template Settings
- json.angularCompilerOptions = {
- ...(json.angularCompilerOptions ?? {}),
- enableI18nLegacyMessageIdFormat: false,
- strictInjectionParameters: true,
- strictInputAccessModifiers: true,
- strictTemplates: true,
- };
-
- return json;
- });
-}
diff --git a/packages/angular/src/generators/library/lib/normalize-options.ts b/packages/angular/src/generators/library/lib/normalize-options.ts
index b9da4e4782..d8623f3aca 100644
--- a/packages/angular/src/generators/library/lib/normalize-options.ts
+++ b/packages/angular/src/generators/library/lib/normalize-options.ts
@@ -1,11 +1,15 @@
-import { names, Tree } from '@nx/devkit';
+import { names, type Tree } from '@nx/devkit';
import {
determineProjectNameAndRootOptions,
ensureRootProjectName,
} from '@nx/devkit/src/generators/project-name-and-root-utils';
import { UnitTestRunner } from '../../../utils/test-runners';
-import { Schema } from '../schema';
-import { NormalizedSchema } from './normalized-schema';
+import {
+ getComponentType,
+ getModuleTypeSeparator,
+} from '../../utils/artifact-types';
+import type { Schema } from '../schema';
+import type { NormalizedSchema } from './normalized-schema';
export async function normalizeOptions(
host: Tree,
@@ -49,7 +53,8 @@ export async function normalizeOptions(
const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim())
: [];
- const modulePath = `${projectRoot}/src/lib/${fileName}.module.ts`;
+ const moduleTypeSeparator = getModuleTypeSeparator(host);
+ const modulePath = `${projectRoot}/src/lib/${fileName}${moduleTypeSeparator}module.ts`;
const ngCliSchematicLibRoot = projectName;
const allNormalizedOptions = {
@@ -70,6 +75,7 @@ export async function normalizeOptions(
standaloneComponentName: `${
names(projectNames.projectSimpleName).className
}Component`,
+ moduleTypeSeparator,
};
const {
@@ -86,6 +92,8 @@ export async function normalizeOptions(
...libraryOptions
} = allNormalizedOptions;
+ const componentType = getComponentType(host);
+
return {
libraryOptions,
componentOptions: {
@@ -101,6 +109,7 @@ export async function normalizeOptions(
selector,
skipSelector,
flat,
+ type: componentType,
},
};
}
diff --git a/packages/angular/src/generators/library/lib/normalized-schema.ts b/packages/angular/src/generators/library/lib/normalized-schema.ts
index edad7ddf5a..efe305eada 100644
--- a/packages/angular/src/generators/library/lib/normalized-schema.ts
+++ b/packages/angular/src/generators/library/lib/normalized-schema.ts
@@ -37,6 +37,7 @@ export interface NormalizedSchema {
parsedTags: string[];
ngCliSchematicLibRoot: string;
standaloneComponentName: string;
+ moduleTypeSeparator: '-' | '.';
};
componentOptions: {
name: string;
@@ -51,5 +52,6 @@ export interface NormalizedSchema {
selector?: string;
skipSelector?: boolean;
flat?: boolean;
+ type?: string;
};
}
diff --git a/packages/angular/src/generators/library/lib/set-generator-defaults.ts b/packages/angular/src/generators/library/lib/set-generator-defaults.ts
index 49833624a1..6d82af767b 100644
--- a/packages/angular/src/generators/library/lib/set-generator-defaults.ts
+++ b/packages/angular/src/generators/library/lib/set-generator-defaults.ts
@@ -11,6 +11,7 @@ export function setGeneratorDefaults(
nxJson.generators['@nx/angular:library'] = {
linter: options.libraryOptions.linter,
unitTestRunner: options.libraryOptions.unitTestRunner,
+ strict: !options.libraryOptions.strict ? false : undefined,
...(nxJson.generators['@nx/angular:library'] || {}),
};
diff --git a/packages/angular/src/generators/library/lib/update-tsconfig-files.ts b/packages/angular/src/generators/library/lib/update-tsconfig-files.ts
new file mode 100644
index 0000000000..b6af10ad39
--- /dev/null
+++ b/packages/angular/src/generators/library/lib/update-tsconfig-files.ts
@@ -0,0 +1,118 @@
+import { joinPathFragments, type Tree, updateJson } from '@nx/devkit';
+import {
+ addTsConfigPath,
+ extractTsConfigBase,
+ getRelativePathToRootTsConfig,
+ getRootTsConfigFileName,
+} from '@nx/js';
+import { getNeededCompilerOptionOverrides } from '@nx/js/src/utils/typescript/configuration';
+import { lt } from 'semver';
+import { updateProjectRootTsConfig } from '../../utils/update-project-root-tsconfig';
+import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
+import type { NormalizedSchema } from './normalized-schema';
+
+export function updateTsConfigFiles(
+ tree: Tree,
+ options: NormalizedSchema['libraryOptions']
+) {
+ extractTsConfigBase(tree);
+ updateProjectConfig(tree, options);
+ updateProjectIvyConfig(tree, options);
+ addTsConfigPath(tree, options.importPath, [
+ joinPathFragments(options.projectRoot, './src', 'index.ts'),
+ ]);
+
+ const compilerOptions: Record = {
+ skipLibCheck: true,
+ experimentalDecorators: true,
+ importHelpers: true,
+ target: 'es2022',
+ ...(options.strict
+ ? {
+ strict: true,
+ noImplicitOverride: true,
+ noPropertyAccessFromIndexSignature: true,
+ noImplicitReturns: true,
+ noFallthroughCasesInSwitch: true,
+ }
+ : {}),
+ };
+
+ const { major: angularMajorVersion, version: angularVersion } =
+ getInstalledAngularVersionInfo(tree);
+ if (lt(angularVersion, '18.1.0')) {
+ compilerOptions.useDefineForClassFields = false;
+ }
+ if (angularMajorVersion >= 20) {
+ compilerOptions.module = 'preserve';
+ } else {
+ compilerOptions.moduleResolution = 'bundler';
+ compilerOptions.module = 'es2022';
+ }
+
+ updateJson(tree, `${options.projectRoot}/tsconfig.json`, (json) => {
+ json.compilerOptions = {
+ ...json.compilerOptions,
+ ...compilerOptions,
+ };
+ json.compilerOptions = getNeededCompilerOptionOverrides(
+ tree,
+ json.compilerOptions,
+ getRootTsConfigFileName(tree)
+ );
+
+ if (options.strict) {
+ json.angularCompilerOptions = {
+ ...json.angularCompilerOptions,
+ strictInjectionParameters: true,
+ strictInputAccessModifiers: true,
+ typeCheckHostBindings: angularMajorVersion >= 20 ? true : undefined,
+ strictTemplates: true,
+ };
+ }
+
+ return json;
+ });
+}
+
+function updateProjectConfig(
+ host: Tree,
+ options: NormalizedSchema['libraryOptions']
+) {
+ updateJson(host, `${options.projectRoot}/tsconfig.lib.json`, (json) => {
+ json.include = ['src/**/*.ts'];
+ json.exclude = [
+ ...new Set([
+ ...(json.exclude || []),
+ 'jest.config.ts',
+ 'src/**/*.test.ts',
+ 'src/**/*.spec.ts',
+ ]),
+ ];
+ return json;
+ });
+
+ // tsconfig.json
+ updateProjectRootTsConfig(
+ host,
+ options.projectRoot,
+ getRelativePathToRootTsConfig(host, options.projectRoot)
+ );
+}
+
+function updateProjectIvyConfig(
+ host: Tree,
+ options: NormalizedSchema['libraryOptions']
+) {
+ if (options.buildable || options.publishable) {
+ return updateJson(
+ host,
+ `${options.projectRoot}/tsconfig.lib.prod.json`,
+ (json) => {
+ json.angularCompilerOptions['compilationMode'] =
+ options.compilationMode === 'full' ? undefined : 'partial';
+ return json;
+ }
+ );
+ }
+}
diff --git a/packages/angular/src/generators/library/lib/update-tsconfig.ts b/packages/angular/src/generators/library/lib/update-tsconfig.ts
deleted file mode 100644
index 45bf7d41e2..0000000000
--- a/packages/angular/src/generators/library/lib/update-tsconfig.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import type { Tree } from '@nx/devkit';
-import { updateJson } from '@nx/devkit';
-import { getRelativePathToRootTsConfig } from '@nx/js';
-import { NormalizedSchema } from './normalized-schema';
-import {
- extractTsConfigBase,
- updateProjectRootTsConfig,
-} from '../../utils/update-project-root-tsconfig';
-
-function updateProjectConfig(
- host: Tree,
- options: NormalizedSchema['libraryOptions']
-) {
- updateJson(host, `${options.projectRoot}/tsconfig.lib.json`, (json) => {
- json.include = ['src/**/*.ts'];
- json.exclude = [
- ...new Set([
- ...(json.exclude || []),
- 'jest.config.ts',
- 'src/**/*.test.ts',
- 'src/**/*.spec.ts',
- ]),
- ];
- return json;
- });
-
- // tsconfig.json
- updateProjectRootTsConfig(
- host,
- options.projectRoot,
- getRelativePathToRootTsConfig(host, options.projectRoot)
- );
-}
-
-function updateProjectIvyConfig(
- host: Tree,
- options: NormalizedSchema['libraryOptions']
-) {
- if (options.buildable || options.publishable) {
- return updateJson(
- host,
- `${options.projectRoot}/tsconfig.lib.prod.json`,
- (json) => {
- json.angularCompilerOptions['compilationMode'] =
- options.compilationMode === 'full' ? undefined : 'partial';
- return json;
- }
- );
- }
-}
-
-export function updateTsConfig(
- host: Tree,
- options: NormalizedSchema['libraryOptions']
-) {
- extractTsConfigBase(host);
- updateProjectConfig(host, options);
- updateProjectIvyConfig(host, options);
-}
diff --git a/packages/angular/src/generators/library/library.spec.ts b/packages/angular/src/generators/library/library.spec.ts
index d1315931af..34ce47dc49 100644
--- a/packages/angular/src/generators/library/library.spec.ts
+++ b/packages/angular/src/generators/library/library.spec.ts
@@ -6,9 +6,11 @@ import {
parseJson,
ProjectGraph,
readJson,
+ readNxJson,
readProjectConfiguration,
Tree,
updateJson,
+ updateNxJson,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { createApp } from '../../utils/nx-devkit/testing';
@@ -79,9 +81,6 @@ describe('lib', () => {
expect(dependencies['@angular/compiler']).toBe(angularVersion);
expect(dependencies['@angular/core']).toBe(angularVersion);
expect(dependencies['@angular/platform-browser']).toBe(angularVersion);
- expect(dependencies['@angular/platform-browser-dynamic']).toBe(
- angularVersion
- );
expect(dependencies['@angular/router']).toBe(angularVersion);
expect(dependencies['rxjs']).toBeDefined();
expect(dependencies['tslib']).toBeDefined();
@@ -89,14 +88,23 @@ describe('lib', () => {
expect(devDependencies['@angular/cli']).toBe(angularDevkitVersion);
expect(devDependencies['@angular/compiler-cli']).toBe(angularVersion);
expect(devDependencies['@angular/language-service']).toBe(angularVersion);
- expect(devDependencies['@angular-devkit/build-angular']).toBe(
- angularDevkitVersion
- );
+ // @angular/build should no be installed unless using vitest, as no other
+ // task requires it
+ expect(devDependencies['@angular/build']).toBeUndefined();
// codelyzer should no longer be there by default
expect(devDependencies['codelyzer']).toBeUndefined();
});
+ it('should add @angular/build when using vitest', async () => {
+ await runLibraryGeneratorWithOpts({
+ unitTestRunner: UnitTestRunner.Vitest,
+ });
+
+ const { devDependencies } = readJson(tree, 'package.json');
+ expect(devDependencies['@angular/build']).toBe(angularDevkitVersion);
+ });
+
it('should not touch the package.json when run with `--skipPackageJson`', async () => {
let initialPackageJson;
updateJson(tree, 'package.json', (json) => {
@@ -205,7 +213,7 @@ describe('lib', () => {
});
// ASSERT
- const moduleFileExists = tree.exists('my-lib/src/lib/my-lib.module.ts');
+ const moduleFileExists = tree.exists('my-lib/src/lib/my-lib-module.ts');
expect(moduleFileExists).toBeFalsy();
const indexApi = tree.read('my-lib/src/index.ts', 'utf-8');
expect(indexApi).toMatchInlineSnapshot(`
@@ -302,9 +310,13 @@ describe('lib', () => {
strictInjectionParameters: true,
strictInputAccessModifiers: true,
strictTemplates: true,
+ typeCheckHostBindings: true,
},
compilerOptions: {
- forceConsistentCasingInFileNames: true,
+ experimentalDecorators: true,
+ importHelpers: true,
+ module: 'preserve',
+ skipLibCheck: true,
noFallthroughCasesInSwitch: true,
noPropertyAccessFromIndexSignature: true,
noImplicitOverride: true,
@@ -357,7 +369,7 @@ describe('lib', () => {
await runLibraryGeneratorWithOpts();
// ASSERT
- expect(tree.read('my-lib/src/lib/my-lib.component.spec.ts')).toBeFalsy();
+ expect(tree.read('my-lib/src/lib/my-lib.spec.ts')).toBeFalsy();
expect(tree.read('my-lib/src/lib/my-lib.service.spec.ts')).toBeFalsy();
});
@@ -427,29 +439,40 @@ describe('lib', () => {
// ASSERT
expect(tree.exists(`my-lib/jest.config.ts`)).toBeTruthy();
expect(tree.exists('my-lib/src/index.ts')).toBeTruthy();
- expect(tree.exists('my-lib/src/lib/my-lib.module.ts')).toBeTruthy();
+ expect(tree.exists('my-lib/src/lib/my-lib-module.ts')).toBeTruthy();
- expect(tree.exists('my-lib/src/lib/my-lib.component.ts')).toBeFalsy();
- expect(
- tree.exists('my-lib/src/lib/my-lib.component.spec.ts')
- ).toBeFalsy();
+ expect(tree.exists('my-lib/src/lib/my-lib.ts')).toBeFalsy();
+ expect(tree.exists('my-lib/src/lib/my-lib.spec.ts')).toBeFalsy();
expect(tree.exists('my-lib/src/lib/my-lib.service.ts')).toBeFalsy();
expect(tree.exists('my-lib/src/lib/my-lib.service.spec.ts')).toBeFalsy();
expect(tree.exists(`my-lib2/jest.config.ts`)).toBeTruthy();
expect(tree.exists('my-lib2/src/index.ts')).toBeTruthy();
- expect(tree.exists('my-lib2/src/lib/my-lib2.module.ts')).toBeTruthy();
+ expect(tree.exists('my-lib2/src/lib/my-lib2-module.ts')).toBeTruthy();
- expect(tree.exists('my-lib2/src/lib/my-lib2.component.ts')).toBeFalsy();
- expect(
- tree.exists('my-lib2/src/lib/my-lib2.component.spec.ts')
- ).toBeFalsy();
+ expect(tree.exists('my-lib2/src/lib/my-lib2.ts')).toBeFalsy();
+ expect(tree.exists('my-lib2/src/lib/my-lib2.spec.ts')).toBeFalsy();
expect(tree.exists('my-lib2/src/lib/my-lib2.service.ts')).toBeFalsy();
expect(
tree.exists('my-lib2/src/lib/my-lib2.service.spec.ts')
).toBeFalsy();
});
+ it('should generate the module file respecting the "typeSeparator" generator default', async () => {
+ const nxJson = readNxJson(tree);
+ nxJson.generators = {
+ ...nxJson.generators,
+ '@nx/angular:module': {
+ typeSeparator: '.',
+ },
+ };
+ updateNxJson(tree, nxJson);
+
+ await runLibraryGeneratorWithOpts();
+
+ expect(tree.exists('my-lib/src/lib/my-lib.module.ts')).toBeTruthy();
+ });
+
it('should not install any e2e test runners', async () => {
// ACT
await runLibraryGeneratorWithOpts({
@@ -508,15 +531,11 @@ describe('lib', () => {
expect(tree.exists(`my-dir/my-lib/jest.config.ts`)).toBeTruthy();
expect(tree.exists('my-dir/my-lib/src/index.ts')).toBeTruthy();
expect(
- tree.exists('my-dir/my-lib/src/lib/my-lib.module.ts')
+ tree.exists('my-dir/my-lib/src/lib/my-lib-module.ts')
).toBeTruthy();
- expect(
- tree.exists('my-dir/my-lib/src/lib/my-lib.component.ts')
- ).toBeFalsy();
- expect(
- tree.exists('my-dir/my-lib/src/lib/my-lib.component.spec.ts')
- ).toBeFalsy();
+ expect(tree.exists('my-dir/my-lib/src/lib/my-lib.ts')).toBeFalsy();
+ expect(tree.exists('my-dir/my-lib/src/lib/my-lib.spec.ts')).toBeFalsy();
expect(
tree.exists('my-dir/my-lib/src/lib/my-lib.service.ts')
).toBeFalsy();
@@ -527,15 +546,11 @@ describe('lib', () => {
expect(tree.exists(`my-dir/my-lib2/jest.config.ts`)).toBeTruthy();
expect(tree.exists('my-dir/my-lib2/src/index.ts')).toBeTruthy();
expect(
- tree.exists('my-dir/my-lib2/src/lib/my-lib2.module.ts')
+ tree.exists('my-dir/my-lib2/src/lib/my-lib2-module.ts')
).toBeTruthy();
- expect(
- tree.exists('my-dir/my-lib2/src/lib/my-lib2.component.ts')
- ).toBeFalsy();
- expect(
- tree.exists('my-dir/my-lib2/src/lib/my-lib2.component.spec.ts')
- ).toBeFalsy();
+ expect(tree.exists('my-dir/my-lib2/src/lib/my-lib2.ts')).toBeFalsy();
+ expect(tree.exists('my-dir/my-lib2/src/lib/my-lib2.spec.ts')).toBeFalsy();
expect(
tree.exists('my-dir/my-lib2/src/lib/my-lib2.service.ts')
).toBeFalsy();
@@ -635,7 +650,7 @@ describe('lib', () => {
importPath: '@myorg/lib',
});
- const libModulePath = 'my-dir/my-lib/src/lib/my-lib.module.ts';
+ const libModulePath = 'my-dir/my-lib/src/lib/my-lib-module.ts';
expect(tree.read(libModulePath, 'utf-8')).toContain('class MyLibModule');
// Make sure these exist
@@ -645,7 +660,7 @@ describe('lib', () => {
'my-dir/my-lib/project.json',
'my-dir/my-lib/tsconfig.lib.prod.json',
'my-dir/my-lib/src/index.ts',
- 'my-dir/my-lib/src/lib/my-lib.module.ts',
+ 'my-dir/my-lib/src/lib/my-lib-module.ts',
].forEach((path) => {
expect(tree.exists(path)).toBeTruthy();
});
@@ -684,7 +699,9 @@ describe('lib', () => {
"prefix": "lib",
"style": "kebab-case"
}
- ]
+ ],
+ "@angular-eslint/component-class-suffix": "off",
+ "@angular-eslint/directive-class-suffix": "off"
}
},
{
@@ -775,17 +792,17 @@ describe('lib', () => {
// ASSERT
expect(
- tree.exists('my-dir/my-lib/src/lib/my-lib.module.ts')
+ tree.exists('my-dir/my-lib/src/lib/my-lib-module.ts')
).toBeTruthy();
expect(
- tree.read('my-dir/my-lib/src/lib/my-lib.module.ts', 'utf-8')
+ tree.read('my-dir/my-lib/src/lib/my-lib-module.ts', 'utf-8')
).toMatchSnapshot();
expect(
- tree.exists('my-dir/my-lib2/src/lib/my-lib2.module.ts')
+ tree.exists('my-dir/my-lib2/src/lib/my-lib2-module.ts')
).toBeTruthy();
expect(
- tree.read('my-dir/my-lib2/src/lib/my-lib2.module.ts', 'utf-8')
+ tree.read('my-dir/my-lib2/src/lib/my-lib2-module.ts', 'utf-8')
).toMatchSnapshot();
});
@@ -798,12 +815,12 @@ describe('lib', () => {
directory: 'my-dir/my-lib',
routing: true,
lazy: true,
- parent: 'myapp/src/app/app.module.ts',
+ parent: 'myapp/src/app/app-module.ts',
skipFormat: false,
});
const moduleContents = tree
- .read('myapp/src/app/app.module.ts')
+ .read('myapp/src/app/app-module.ts')
.toString();
const tsConfigLibJson = parseJson(
tree.read('my-dir/my-lib/tsconfig.lib.json').toString()
@@ -815,12 +832,12 @@ describe('lib', () => {
routing: true,
lazy: true,
simpleName: true,
- parent: 'myapp/src/app/app.module.ts',
+ parent: 'myapp/src/app/app-module.ts',
skipFormat: false,
});
const moduleContents2 = tree
- .read('myapp/src/app/app.module.ts')
+ .read('myapp/src/app/app-module.ts')
.toString();
const tsConfigLibJson2 = parseJson(
tree.read('my-dir/my-lib2/tsconfig.lib.json').toString()
@@ -832,12 +849,12 @@ describe('lib', () => {
routing: true,
lazy: true,
simpleName: true,
- parent: 'myapp/src/app/app.module.ts',
+ parent: 'myapp/src/app/app-module.ts',
skipFormat: false,
});
const moduleContents3 = tree
- .read('myapp/src/app/app.module.ts')
+ .read('myapp/src/app/app-module.ts')
.toString();
const tsConfigLibJson3 = parseJson(
tree.read('my-dir/my-lib3/tsconfig.lib.json').toString()
@@ -857,7 +874,7 @@ describe('lib', () => {
"import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
- import { AppComponent } from './app.component';
+ import { App } from './app';
@NgModule({
imports: [
BrowserModule,
@@ -872,8 +889,8 @@ describe('lib', () => {
},
]),
],
- declarations: [AppComponent],
- bootstrap: [AppComponent],
+ declarations: [App],
+ bootstrap: [App],
})
export class AppModule {}
"
@@ -900,19 +917,19 @@ describe('lib', () => {
// ARRANGE
createApp(tree, 'myapp');
tree.write(
- 'myapp/src/app/app.module.ts',
+ 'myapp/src/app/app-module.ts',
`
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
- import { AppComponent } from './app.component';
+ import { App } from './app';
const routes = [];
@NgModule({
imports: [BrowserModule, RouterModule.forRoot(routes)],
- declarations: [AppComponent],
- bootstrap: [AppComponent]
+ declarations: [App],
+ bootstrap: [App]
})
export class AppModule {}
`
@@ -923,12 +940,12 @@ describe('lib', () => {
directory: 'my-dir/my-lib',
routing: true,
lazy: true,
- parent: 'myapp/src/app/app.module.ts',
+ parent: 'myapp/src/app/app-module.ts',
});
// ASSERT
const moduleContents = tree
- .read('myapp/src/app/app.module.ts')
+ .read('myapp/src/app/app-module.ts')
.toString();
expect(moduleContents).toMatchSnapshot();
@@ -951,20 +968,20 @@ describe('lib', () => {
});
// ASSERT
expect(
- tree.exists('my-dir/my-lib/src/lib/my-lib.module.ts')
+ tree.exists('my-dir/my-lib/src/lib/my-lib-module.ts')
).toBeTruthy();
expect(
- tree.read('my-dir/my-lib/src/lib/my-lib.module.ts').toString()
+ tree.read('my-dir/my-lib/src/lib/my-lib-module.ts').toString()
).toContain('RouterModule');
expect(
tree.read('my-dir/my-lib/src/lib/lib.routes.ts').toString()
).toContain('const myLibRoutes: Route[] = ');
expect(
- tree.exists('my-dir/my-lib2/src/lib/my-lib2.module.ts')
+ tree.exists('my-dir/my-lib2/src/lib/my-lib2-module.ts')
).toBeTruthy();
expect(
- tree.read('my-dir/my-lib2/src/lib/my-lib2.module.ts').toString()
+ tree.read('my-dir/my-lib2/src/lib/my-lib2-module.ts').toString()
).toContain('RouterModule');
expect(
tree.read('my-dir/my-lib2/src/lib/lib.routes.ts').toString()
@@ -980,11 +997,11 @@ describe('lib', () => {
name: 'my-lib',
directory: 'my-dir/my-lib',
routing: true,
- parent: 'myapp/src/app/app.module.ts',
+ parent: 'myapp/src/app/app-module.ts',
});
const moduleContents = tree
- .read('myapp/src/app/app.module.ts')
+ .read('myapp/src/app/app-module.ts')
.toString();
await runLibraryGeneratorWithOpts({
@@ -992,23 +1009,23 @@ describe('lib', () => {
directory: 'my-dir/my-lib2',
simpleName: true,
routing: true,
- parent: 'myapp/src/app/app.module.ts',
+ parent: 'myapp/src/app/app-module.ts',
});
const moduleContents2 = tree
- .read('myapp/src/app/app.module.ts')
+ .read('myapp/src/app/app-module.ts')
.toString();
await runLibraryGeneratorWithOpts({
name: 'my-lib3',
directory: 'my-dir/my-lib3',
routing: true,
- parent: 'myapp/src/app/app.module.ts',
+ parent: 'myapp/src/app/app-module.ts',
simpleName: true,
});
const moduleContents3 = tree
- .read('myapp/src/app/app.module.ts')
+ .read('myapp/src/app/app-module.ts')
.toString();
// ASSERT
@@ -1044,19 +1061,19 @@ describe('lib', () => {
// ARRANGE
createApp(tree, 'myapp');
tree.write(
- 'myapp/src/app/app.module.ts',
+ 'myapp/src/app/app-module.ts',
`
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
- import { AppComponent } from './app.component';
+ import { App } from './app';
const routes = [];
@NgModule({
imports: [BrowserModule, RouterModule.forRoot(routes)],
- declarations: [AppComponent],
- bootstrap: [AppComponent]
+ declarations: [App],
+ bootstrap: [App]
})
export class AppModule {}
`
@@ -1067,12 +1084,12 @@ describe('lib', () => {
name: 'my-lib',
directory: 'my-dir/my-lib',
routing: true,
- parent: 'myapp/src/app/app.module.ts',
+ parent: 'myapp/src/app/app-module.ts',
});
// ASSERT
const moduleContents = tree
- .read('myapp/src/app/app.module.ts')
+ .read('myapp/src/app/app-module.ts')
.toString();
expect(moduleContents).toContain('RouterModule.forRoot(routes)');
@@ -1091,7 +1108,7 @@ describe('lib', () => {
});
// ASSERT
- expect(tree.exists('my-lib/src/lib/my-lib.module.spec.ts')).toBeFalsy();
+ expect(tree.exists('my-lib/src/lib/my-lib-module.spec.ts')).toBeFalsy();
expect(tree.exists('my-lib/src/test.ts')).toBeFalsy();
expect(tree.exists('my-lib/src/test.ts')).toBeFalsy();
expect(tree.exists('my-lib/tsconfig.spec.json')).toBeFalsy();
@@ -1133,9 +1150,7 @@ describe('lib', () => {
publishable: true,
importPath: '@myorg/lib',
})
- ).rejects.toThrowError(
- 'You already have a library using the import path'
- );
+ ).rejects.toThrow('You already have a library using the import path');
});
it('should fail if no importPath has been used', async () => {
@@ -1144,7 +1159,7 @@ describe('lib', () => {
runLibraryGeneratorWithOpts({
publishable: true,
})
- ).rejects.toThrowError(
+ ).rejects.toThrow(
'For publishable libs you have to provide a proper "--importPath"'
);
});
@@ -1167,7 +1182,6 @@ describe('lib', () => {
const { generators } = readJson(tree, 'nx.json');
// check that the TypeScript compiler options have been updated
- expect(compilerOptions.forceConsistentCasingInFileNames).toBe(true);
expect(compilerOptions.strict).toBe(true);
expect(compilerOptions.noImplicitOverride).toBe(true);
expect(compilerOptions.noPropertyAccessFromIndexSignature).toBe(true);
@@ -1235,7 +1249,9 @@ describe('lib', () => {
prefix: "lib",
style: "kebab-case"
}
- ]
+ ],
+ "@angular-eslint/component-class-suffix": "off",
+ "@angular-eslint/directive-class-suffix": "off"
}
},
{
@@ -1275,6 +1291,7 @@ describe('lib', () => {
"*.ts",
],
"rules": {
+ "@angular-eslint/component-class-suffix": "off",
"@angular-eslint/component-selector": [
"error",
{
@@ -1283,6 +1300,7 @@ describe('lib', () => {
"type": "element",
},
],
+ "@angular-eslint/directive-class-suffix": "off",
"@angular-eslint/directive-selector": [
"error",
{
@@ -1335,6 +1353,7 @@ describe('lib', () => {
"*.ts",
],
"rules": {
+ "@angular-eslint/component-class-suffix": "off",
"@angular-eslint/component-selector": [
"error",
{
@@ -1343,6 +1362,7 @@ describe('lib', () => {
"type": "element",
},
],
+ "@angular-eslint/directive-class-suffix": "off",
"@angular-eslint/directive-selector": [
"error",
{
@@ -1482,13 +1502,71 @@ describe('lib', () => {
expect(tree.read('my-lib/src/index.ts', 'utf-8')).toMatchSnapshot();
expect(
- tree.read('my-lib/src/lib/my-lib/my-lib.component.ts', 'utf-8')
+ tree.read('my-lib/src/lib/my-lib/my-lib.ts', 'utf-8')
).toMatchSnapshot();
expect(
- tree.read('my-lib/src/lib/my-lib/my-lib.component.spec.ts', 'utf-8')
+ tree.read('my-lib/src/lib/my-lib/my-lib.spec.ts', 'utf-8')
).toMatchSnapshot();
});
+ it('should generate a library with a standalone component respecting the "type" option in the component generator defaults', async () => {
+ const nxJson = readNxJson(tree);
+ nxJson.generators = {
+ ...nxJson.generators,
+ '@nx/angular:component': {
+ ...nxJson.generators?.['@nx/angular:component'],
+ type: 'component',
+ },
+ };
+ updateNxJson(tree, nxJson);
+
+ await runLibraryGeneratorWithOpts({ standalone: true, skipFormat: true });
+
+ expect(tree.read('my-lib/src/index.ts', 'utf-8')).toMatchInlineSnapshot(
+ `"export * from './lib/my-lib/my-lib.component';"`
+ );
+ expect(tree.read('my-lib/src/lib/my-lib/my-lib.component.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { Component } from '@angular/core';
+ import { CommonModule } from '@angular/common';
+
+ @Component({
+ selector: 'lib-my-lib',
+ imports: [CommonModule],
+ templateUrl: './my-lib.component.html',
+ styleUrl: './my-lib.component.css'
+ })
+ export class MyLibComponent {}
+ "
+ `);
+ expect(
+ tree.read('my-lib/src/lib/my-lib/my-lib.component.spec.ts', 'utf-8')
+ ).toMatchInlineSnapshot(`
+ "import { ComponentFixture, TestBed } from '@angular/core/testing';
+ import { MyLibComponent } from './my-lib.component';
+
+ describe('MyLibComponent', () => {
+ let component: MyLibComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [MyLibComponent]
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(MyLibComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+ });
+ "
+ `);
+ });
+
it('should generate a library with a valid selector for the standalone component when library name has a slash', async () => {
await runLibraryGeneratorWithOpts({
standalone: true,
@@ -1497,10 +1575,7 @@ describe('lib', () => {
expect(tree.read('my-lib/src/index.ts', 'utf-8')).toMatchSnapshot();
expect(
- tree.read(
- 'my-lib/src/lib/auth/common/auth/common.component.ts',
- 'utf-8'
- )
+ tree.read('my-lib/src/lib/auth/common/auth/common.ts', 'utf-8')
).toMatchSnapshot();
});
@@ -1508,11 +1583,9 @@ describe('lib', () => {
await runLibraryGeneratorWithOpts({ standalone: true, flat: true });
expect(tree.read('my-lib/src/index.ts', 'utf-8')).toMatchSnapshot();
+ expect(tree.read('my-lib/src/lib/my-lib.ts', 'utf-8')).toMatchSnapshot();
expect(
- tree.read('my-lib/src/lib/my-lib.component.ts', 'utf-8')
- ).toMatchSnapshot();
- expect(
- tree.read('my-lib/src/lib/my-lib.component.spec.ts', 'utf-8')
+ tree.read('my-lib/src/lib/my-lib.spec.ts', 'utf-8')
).toMatchSnapshot();
});
@@ -1526,13 +1599,10 @@ describe('lib', () => {
tree.read('my-dir/my-lib/src/index.ts', 'utf-8')
).toMatchSnapshot();
expect(
- tree.read('my-dir/my-lib/src/lib/my-lib/my-lib.component.ts', 'utf-8')
+ tree.read('my-dir/my-lib/src/lib/my-lib/my-lib.ts', 'utf-8')
).toMatchSnapshot();
expect(
- tree.read(
- 'my-dir/my-lib/src/lib/my-lib/my-lib.component.spec.ts',
- 'utf-8'
- )
+ tree.read('my-dir/my-lib/src/lib/my-lib/my-lib.spec.ts', 'utf-8')
).toMatchSnapshot();
});
@@ -1547,13 +1617,10 @@ describe('lib', () => {
tree.read('my-dir/my-lib/src/index.ts', 'utf-8')
).toMatchSnapshot();
expect(
- tree.read('my-dir/my-lib/src/lib/my-lib/my-lib.component.ts', 'utf-8')
+ tree.read('my-dir/my-lib/src/lib/my-lib/my-lib.ts', 'utf-8')
).toMatchSnapshot();
expect(
- tree.read(
- 'my-dir/my-lib/src/lib/my-lib/my-lib.component.spec.ts',
- 'utf-8'
- )
+ tree.read('my-dir/my-lib/src/lib/my-lib/my-lib.spec.ts', 'utf-8')
).toMatchSnapshot();
});
@@ -1565,25 +1632,23 @@ describe('lib', () => {
});
expect(tree.read('my-lib/src/index.ts', 'utf-8')).toMatchSnapshot();
+ expect(tree.read('my-lib/src/lib/my-lib.ts', 'utf-8')).toMatchSnapshot();
expect(
- tree.read('my-lib/src/lib/my-lib.component.ts', 'utf-8')
- ).toMatchSnapshot();
- expect(
- tree.read('my-lib/src/lib/my-lib.component.spec.ts', 'utf-8')
+ tree.read('my-lib/src/lib/my-lib.spec.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('my-lib/src/lib/lib.routes.ts', 'utf-8')
).toMatchSnapshot();
- expect(tree.children('my-lib/src/lib')).toMatchInlineSnapshot(`
+ expect(tree.children('my-lib/src/lib').sort()).toMatchInlineSnapshot(`
[
"lib.routes.ts",
- "my-lib.component.css",
- "my-lib.component.html",
- "my-lib.component.spec.ts",
- "my-lib.component.ts",
+ "my-lib.css",
+ "my-lib.html",
+ "my-lib.spec.ts",
+ "my-lib.ts",
]
`);
- expect(tree.children('my-lib/src')).toMatchInlineSnapshot(`
+ expect(tree.children('my-lib/src').sort()).toMatchInlineSnapshot(`
[
"index.ts",
"lib",
@@ -1600,10 +1665,10 @@ describe('lib', () => {
tree.read('my-lib/src/lib/lib.routes.ts', 'utf-8')
).toMatchSnapshot();
expect(
- tree.read('my-lib/src/lib/my-lib/my-lib.component.ts', 'utf-8')
+ tree.read('my-lib/src/lib/my-lib/my-lib.ts', 'utf-8')
).toMatchSnapshot();
expect(
- tree.read('my-lib/src/lib/my-lib/my-lib.component.spec.ts', 'utf-8')
+ tree.read('my-lib/src/lib/my-lib/my-lib.spec.ts', 'utf-8')
).toMatchSnapshot();
});
@@ -1766,10 +1831,10 @@ describe('lib', () => {
expect(tree.read('my-lib/src/index.ts', 'utf-8')).toMatchSnapshot();
expect(
- tree.read('my-lib/src/lib/my-lib/my-lib.component.ts', 'utf-8')
+ tree.read('my-lib/src/lib/my-lib/my-lib.ts', 'utf-8')
).toMatchSnapshot();
expect(
- tree.read('my-lib/src/lib/my-lib/my-lib.component.spec.ts', 'utf-8')
+ tree.read('my-lib/src/lib/my-lib/my-lib.spec.ts', 'utf-8')
).toMatchSnapshot();
});
@@ -1783,11 +1848,9 @@ describe('lib', () => {
expect(tree.read('my-lib/src/index.ts', 'utf-8')).toMatchSnapshot();
expect(
- tree.read('my-lib/src/lib/my-lib/my-lib.component.ts', 'utf-8')
+ tree.read('my-lib/src/lib/my-lib/my-lib.ts', 'utf-8')
).toMatchSnapshot();
- expect(
- tree.exists('my-lib/src/lib/my-lib/my-lib.component.spec.ts')
- ).toBeFalsy();
+ expect(tree.exists('my-lib/src/lib/my-lib/my-lib.spec.ts')).toBeFalsy();
});
it('should generate a library with a standalone component as entry point and set up view encapsulation and change detection', async () => {
@@ -1801,23 +1864,21 @@ describe('lib', () => {
expect(tree.read('my-lib/src/index.ts', 'utf-8')).toMatchSnapshot();
expect(
- tree.read('my-lib/src/lib/my-lib/my-lib.component.ts', 'utf-8')
+ tree.read('my-lib/src/lib/my-lib/my-lib.ts', 'utf-8')
).toMatchSnapshot();
});
});
describe('angular compat support', () => {
- beforeEach(() => {
+ it('should disable modern class fields behavior in versions lower than v18.1', async () => {
updateJson(tree, 'package.json', (json) => ({
...json,
dependencies: {
...json.dependencies,
- '@angular/core': '~17.2.0',
+ '@angular/core': '~18.0.0',
},
}));
- });
- it('should disable modern class fields behavior', async () => {
await runLibraryGeneratorWithOpts();
expect(
@@ -1825,5 +1886,148 @@ describe('lib', () => {
.useDefineForClassFields
).toBe(false);
});
+
+ it('should not set "typeCheckHostBindings" when strict is true if Angular version is lower than v20', async () => {
+ updateJson(tree, 'package.json', (json) => ({
+ ...json,
+ dependencies: {
+ ...json.dependencies,
+ '@angular/core': '~19.0.0',
+ },
+ }));
+
+ await runLibraryGeneratorWithOpts();
+
+ expect(readJson(tree, 'my-lib/tsconfig.json').angularCompilerOptions)
+ .toMatchInlineSnapshot(`
+ {
+ "enableI18nLegacyMessageIdFormat": false,
+ "strictInjectionParameters": true,
+ "strictInputAccessModifiers": true,
+ "strictTemplates": true,
+ }
+ `);
+ });
+
+ it('should generate components with the "component" type for versions lower than v20', async () => {
+ updateJson(tree, 'package.json', (json) => ({
+ ...json,
+ dependencies: {
+ ...json.dependencies,
+ '@angular/core': '~19.2.0',
+ },
+ }));
+ await generateTestApplication(tree, {
+ directory: 'app1',
+ routing: true,
+ standalone: true,
+ skipFormat: true,
+ });
+
+ await runLibraryGeneratorWithOpts({
+ standalone: true,
+ routing: true,
+ lazy: true,
+ parent: 'app1/src/app/app.routes.ts',
+ skipFormat: true,
+ });
+
+ expect(tree.read('my-lib/src/index.ts', 'utf-8')).toMatchInlineSnapshot(`
+ "export * from './lib/lib.routes';
+
+ export * from './lib/my-lib/my-lib.component';"
+ `);
+ expect(tree.read('my-lib/src/lib/my-lib/my-lib.component.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { Component } from '@angular/core';
+ import { CommonModule } from '@angular/common';
+
+ @Component({
+ selector: 'lib-my-lib',
+ imports: [CommonModule],
+ templateUrl: './my-lib.component.html',
+ styleUrl: './my-lib.component.css'
+ })
+ export class MyLibComponent {}
+ "
+ `);
+ expect(tree.exists('my-lib/src/lib/my-lib/my-lib.component.html')).toBe(
+ true
+ );
+ expect(tree.exists('my-lib/src/lib/my-lib/my-lib.component.css')).toBe(
+ true
+ );
+ expect(
+ tree.read('my-lib/src/lib/my-lib/my-lib.component.spec.ts', 'utf-8')
+ ).toMatchInlineSnapshot(`
+ "import { ComponentFixture, TestBed } from '@angular/core/testing';
+ import { MyLibComponent } from './my-lib.component';
+
+ describe('MyLibComponent', () => {
+ let component: MyLibComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [MyLibComponent]
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(MyLibComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+ });
+ "
+ `);
+ expect(tree.read('my-lib/src/lib/lib.routes.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { Route } from '@angular/router';
+ import { MyLibComponent } from './my-lib/my-lib.component';
+
+ export const myLibRoutes: Route[] = [
+ { path: '', component: MyLibComponent }
+ ];
+ "
+ `);
+ expect(tree.read('app1/src/app/app.routes.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { Route } from '@angular/router';
+
+ export const appRoutes: Route[] = [
+ { path: 'my-lib', loadChildren: () => import('@proj/my-lib').then(m => m.myLibRoutes) },];
+ "
+ `);
+ });
+
+ it('should generate modules with the "." type separator for versions lower than v20', async () => {
+ updateJson(tree, 'package.json', (json) => ({
+ ...json,
+ dependencies: {
+ ...json.dependencies,
+ '@angular/core': '~19.2.0',
+ },
+ }));
+
+ await runLibraryGeneratorWithOpts({
+ standalone: false,
+ skipFormat: true,
+ });
+
+ expect(tree.read('my-lib/src/lib/my-lib.module.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { NgModule } from '@angular/core';
+ import { CommonModule } from '@angular/common';
+
+ @NgModule({
+ imports: [CommonModule],
+ })
+ export class MyLibModule {}
+ "
+ `);
+ });
});
});
diff --git a/packages/angular/src/generators/library/library.ts b/packages/angular/src/generators/library/library.ts
index 37b064af18..7f74cece78 100644
--- a/packages/angular/src/generators/library/library.ts
+++ b/packages/angular/src/generators/library/library.ts
@@ -3,36 +3,32 @@ import {
formatFiles,
GeneratorCallback,
installPackagesTask,
- joinPathFragments,
+ runTasksInSerial,
Tree,
} from '@nx/devkit';
-import { addTsConfigPath, initGenerator as jsInitGenerator } from '@nx/js';
+import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
+import { initGenerator as jsInitGenerator } from '@nx/js';
+import { releaseTasks } from '@nx/js/src/generators/library/utils/add-release-config';
+import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
import init from '../../generators/init/init';
+import { UnitTestRunner } from '../../utils/test-runners';
import addLintingGenerator from '../add-linting/add-linting';
import setupTailwindGenerator from '../setup-tailwind/setup-tailwind';
-import { versions } from '../utils/version-utils';
+import { addJest } from '../utils/add-jest';
+import { addVitest } from '../utils/add-vitest';
import { addBuildableLibrariesPostCssDependencies } from '../utils/dependencies';
+import { ensureAngularDependencies } from '../utils/ensure-angular-dependencies';
+import { versions } from '../utils/version-utils';
import { addModule } from './lib/add-module';
+import { addProject } from './lib/add-project';
import { addStandaloneComponent } from './lib/add-standalone-component';
-import {
- enableStrictTypeChecking,
- setLibraryStrictDefault,
-} from './lib/enable-strict-type-checking';
+import { createFiles } from './lib/create-files';
import { normalizeOptions } from './lib/normalize-options';
import { NormalizedSchema } from './lib/normalized-schema';
-import { updateLibPackageNpmScope } from './lib/update-lib-package-npm-scope';
-import { updateTsConfig } from './lib/update-tsconfig';
-import { Schema } from './schema';
-import { createFiles } from './lib/create-files';
-import { addProject } from './lib/add-project';
-import { addJest } from '../utils/add-jest';
import { setGeneratorDefaults } from './lib/set-generator-defaults';
-import { ensureAngularDependencies } from '../utils/ensure-angular-dependencies';
-import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
-import { UnitTestRunner } from '../../utils/test-runners';
-import { addVitest } from '../utils/add-vitest';
-import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
-import { releaseTasks } from '@nx/js/src/generators/library/utils/add-release-config';
+import { updateLibPackageNpmScope } from './lib/update-lib-package-npm-scope';
+import { updateTsConfigFiles } from './lib/update-tsconfig-files';
+import { Schema } from './schema';
export async function libraryGenerator(
tree: Tree,
@@ -76,7 +72,7 @@ export async function libraryGenerator(
const project = await addProject(tree, libraryOptions);
createFiles(tree, options, project);
- updateTsConfig(tree, libraryOptions);
+ updateTsConfigFiles(tree, libraryOptions);
await addUnitTestRunner(tree, libraryOptions);
updateNpmScopeIfBuildableOrPublishable(tree, libraryOptions);
setGeneratorDefaults(tree, options);
@@ -87,7 +83,6 @@ export async function libraryGenerator(
await addStandaloneComponent(tree, options);
}
- setStrictMode(tree, libraryOptions);
await addLinting(tree, libraryOptions);
if (libraryOptions.addTailwind) {
@@ -112,23 +107,19 @@ export async function libraryGenerator(
true
);
addBuildableLibrariesPostCssDependencies(tree);
- if (libraryOptions.publishable) {
- await releaseTasks(tree);
- }
}
- addTsConfigPath(tree, libraryOptions.importPath, [
- joinPathFragments(libraryOptions.projectRoot, './src', 'index.ts'),
- ]);
-
if (!libraryOptions.skipFormat) {
await formatFiles(tree);
}
- return () => {
- installPackagesTask(tree);
- logShowProjectCommand(libraryOptions.name);
- };
+ const tasks: GeneratorCallback[] = [() => installPackagesTask(tree)];
+ if (libraryOptions.publishable) {
+ tasks.push(await releaseTasks(tree));
+ }
+ tasks.push(() => logShowProjectCommand(libraryOptions.name));
+
+ return runTasksInSerial(...tasks);
}
async function addUnitTestRunner(
@@ -164,17 +155,6 @@ function updateNpmScopeIfBuildableOrPublishable(
}
}
-function setStrictMode(
- host: Tree,
- options: NormalizedSchema['libraryOptions']
-) {
- if (options.strict) {
- enableStrictTypeChecking(host, options);
- } else {
- setLibraryStrictDefault(host, options.strict);
- }
-}
-
async function addLinting(
host: Tree,
options: NormalizedSchema['libraryOptions']
diff --git a/packages/angular/src/generators/move/lib/update-module-name.ts b/packages/angular/src/generators/move/lib/update-module-name.ts
index 89157ab1a7..f73bc14461 100644
--- a/packages/angular/src/generators/move/lib/update-module-name.ts
+++ b/packages/angular/src/generators/move/lib/update-module-name.ts
@@ -44,33 +44,41 @@ export function updateModuleName(
const findModuleName = new RegExp(`\\b${moduleName.from}`, 'g');
- const moduleFile = {
- from: `${oldProjectName}.module`,
- to: `${unscopedNewProjectName}.module`,
- };
-
- const findFileName = new RegExp(`\\b${moduleFile.from}`, 'g');
-
- const filesToRename = [
+ const moduleFiles = [
{
- from: `${project.sourceRoot}/lib/${moduleFile.from}.ts`,
- to: `${project.sourceRoot}/lib/${moduleFile.to}.ts`,
+ from: `${oldProjectName}.module`,
+ fromRegex: new RegExp(`\\b${oldProjectName}\\.module`, 'g'),
+ to: `${unscopedNewProjectName}.module`,
},
{
- from: `${project.sourceRoot}/lib/${moduleFile.from}.spec.ts`,
- to: `${project.sourceRoot}/lib/${moduleFile.to}.spec.ts`,
+ from: `${oldProjectName}-module`,
+ fromRegex: new RegExp(`\\b${oldProjectName}-module`, 'g'),
+ to: `${unscopedNewProjectName}-module`,
},
- ].filter((rename) => rename.from !== rename.to);
+ ];
+
+ const filesToRename = moduleFiles.flatMap((moduleFile) =>
+ [
+ {
+ from: `${project.sourceRoot}/lib/${moduleFile.from}.ts`,
+ to: `${project.sourceRoot}/lib/${moduleFile.to}.ts`,
+ },
+ {
+ from: `${project.sourceRoot}/lib/${moduleFile.from}.spec.ts`,
+ to: `${project.sourceRoot}/lib/${moduleFile.to}.spec.ts`,
+ },
+ ].filter((rename) => rename.from !== rename.to)
+ );
if (filesToRename.length === 0) {
return;
}
const replacements = [
- {
- regex: findFileName,
+ ...moduleFiles.map((moduleFile) => ({
+ regex: moduleFile.fromRegex,
replaceWith: moduleFile.to,
- },
+ })),
{
regex: findModuleName,
replaceWith: moduleName.to,
diff --git a/packages/angular/src/generators/move/lib/update-secondary-entry-points.ts b/packages/angular/src/generators/move/lib/update-secondary-entry-points.ts
index 8406f6c534..db9b746d41 100644
--- a/packages/angular/src/generators/move/lib/update-secondary-entry-points.ts
+++ b/packages/angular/src/generators/move/lib/update-secondary-entry-points.ts
@@ -13,9 +13,6 @@ const libraryExecutors = [
'@angular/build:ng-packagr',
'@nx/angular:ng-packagr-lite',
'@nx/angular:package',
- // TODO(v17): remove when @nrwl/* scope is removed
- '@nrwl/angular:ng-packagr-lite',
- '@nrwl/angular:package',
];
export function updateSecondaryEntryPoints(
diff --git a/packages/angular/src/generators/move/move.spec.ts b/packages/angular/src/generators/move/move.spec.ts
index 62f5f32b42..27fc45c37d 100644
--- a/packages/angular/src/generators/move/move.spec.ts
+++ b/packages/angular/src/generators/move/move.spec.ts
@@ -1,7 +1,7 @@
import 'nx/src/internal-testing-utils/mock-project-graph';
import * as devkit from '@nx/devkit';
-import { ProjectGraph, readJson, Tree } from '@nx/devkit';
+import { type ProjectGraph, readJson, type Tree, updateJson } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { UnitTestRunner } from '../../utils/test-runners';
import { librarySecondaryEntryPointGenerator } from '../library-secondary-entry-point/library-secondary-entry-point';
@@ -30,7 +30,7 @@ describe('@nx/angular:move', () => {
}
beforeEach(async () => {
- tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ tree = createTreeWithEmptyWorkspace();
await generateTestLibrary(tree, {
directory: 'my-lib',
@@ -59,7 +59,7 @@ describe('@nx/angular:move', () => {
skipFormat: true,
});
- expect(tree.exists('mynewlib/src/lib/mynewlib.module.ts')).toEqual(true);
+ expect(tree.exists('mynewlib/src/lib/mynewlib-module.ts')).toEqual(true);
});
it('should update ng-package.json dest property', async () => {
@@ -121,8 +121,8 @@ describe('@nx/angular:move', () => {
skipFormat: true,
});
- expect(tree.exists('my/lib/src/lib/my-lib.module.ts')).toBe(true);
- const moduleFile = tree.read('my/lib/src/lib/my-lib.module.ts', 'utf-8');
+ expect(tree.exists('my/lib/src/lib/my-lib-module.ts')).toBe(true);
+ const moduleFile = tree.read('my/lib/src/lib/my-lib-module.ts', 'utf-8');
expect(moduleFile).toContain(`export class MyLibModule {}`);
});
@@ -138,7 +138,7 @@ describe('@nx/angular:move', () => {
unitTestRunner: UnitTestRunner.Jest,
});
tree.write(
- 'my-lib/src/lib/my-lib.module.ts',
+ 'my-lib/src/lib/my-lib-module.ts',
`import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@@ -149,7 +149,7 @@ describe('@nx/angular:move', () => {
);
tree.write(
- 'my-lib/src/lib/my-lib.module.spec.ts',
+ 'my-lib/src/lib/my-lib-module.spec.ts',
`import { async, TestBed } from '@angular/core/testing';
import { MyLibModule } from './my-lib.module';
@@ -166,7 +166,7 @@ describe('@nx/angular:move', () => {
});`
);
tree.write(
- 'my-lib2/src/lib/my-lib2.module.ts',
+ 'my-lib2/src/lib/my-lib2-module.ts',
`import { MyLibModule } from '@proj/my-lib';
export class MyLib2Module extends MyLibModule {}
@@ -185,21 +185,21 @@ describe('@nx/angular:move', () => {
skipFormat: true,
});
- expect(tree.exists('shared/my-lib/src/lib/shared-my-lib.module.ts')).toBe(
+ expect(tree.exists('shared/my-lib/src/lib/shared-my-lib-module.ts')).toBe(
true
);
expect(
- tree.exists('shared/my-lib/src/lib/shared-my-lib.module.spec.ts')
+ tree.exists('shared/my-lib/src/lib/shared-my-lib-module.spec.ts')
).toBe(true);
const moduleFile = tree.read(
- 'shared/my-lib/src/lib/shared-my-lib.module.ts',
+ 'shared/my-lib/src/lib/shared-my-lib-module.ts',
'utf-8'
);
expect(moduleFile).toContain(`export class SharedMyLibModule {}`);
const moduleSpecFile = tree.read(
- 'shared/my-lib/src/lib/shared-my-lib.module.spec.ts',
+ 'shared/my-lib/src/lib/shared-my-lib-module.spec.ts',
'utf-8'
);
expect(moduleSpecFile).toContain(
@@ -224,7 +224,7 @@ describe('@nx/angular:move', () => {
});
const importerFile = tree.read(
- 'my-lib2/src/lib/my-lib2.module.ts',
+ 'my-lib2/src/lib/my-lib2-module.ts',
'utf-8'
);
expect(importerFile).toContain(
@@ -248,7 +248,7 @@ describe('@nx/angular:move', () => {
const indexFile = tree.read('shared/my-lib/src/index.ts', 'utf-8');
expect(indexFile).toContain(
- `export * from './lib/shared-my-lib.module';`
+ `export * from './lib/shared-my-lib-module';`
);
});
});
@@ -285,11 +285,11 @@ describe('@nx/angular:move', () => {
});
expect(
- tree.exists('my-destination/src/lib/my-destination.module.ts')
+ tree.exists('my-destination/src/lib/my-destination-module.ts')
).toBe(true);
const moduleFile = tree.read(
- 'my-destination/src/lib/my-destination.module.ts',
+ 'my-destination/src/lib/my-destination-module.ts',
'utf-8'
);
expect(moduleFile).toContain(`export class MyDestinationModule {}`);
@@ -331,7 +331,7 @@ describe('@nx/angular:move', () => {
const indexFile = tree.read('my-destination/src/index.ts', 'utf-8');
expect(indexFile).toContain(
- `export * from './lib/my-destination.module';`
+ `export * from './lib/my-destination-module';`
);
});
@@ -359,10 +359,118 @@ describe('@nx/angular:move', () => {
});
const moduleFile = tree.read(
- 'my-lib-demo/src/lib/my-lib-demo.module.ts',
+ 'my-lib-demo/src/lib/my-lib-demo-module.ts',
'utf-8'
);
expect(moduleFile).toContain(`export class MyLibDemoModule {}`);
});
});
+
+ describe('legacy "." module type separator', () => {
+ beforeEach(async () => {
+ tree = createTreeWithEmptyWorkspace();
+ });
+
+ it('should move project that uses "." module type separator', async () => {
+ updateJson(tree, 'nx.json', (json) => {
+ json.generators = {
+ ...json.generators,
+ '@nx/angular:module': {
+ typeSeparator: '.',
+ },
+ };
+ return json;
+ });
+ addProjectToGraph('my-lib');
+ await generateTestLibrary(tree, {
+ directory: 'my-lib',
+ buildable: true,
+ standalone: false,
+ skipFormat: true,
+ });
+
+ await angularMoveGenerator(tree, {
+ projectName: 'my-lib',
+ destination: 'my-new-lib',
+ newProjectName: 'my-new-lib',
+ updateImportPath: true,
+ skipFormat: true,
+ });
+
+ expect(tree.exists('my-lib/src/lib/my-lib.module.ts')).toBe(false);
+ expect(tree.read('my-new-lib/src/lib/my-new-lib.module.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { NgModule } from '@angular/core';
+ import { CommonModule } from '@angular/common';
+
+ @NgModule({
+ imports: [CommonModule],
+ })
+ export class MyNewLibModule {}
+ "
+ `);
+ expect(tree.exists('my-lib/ng-package.json')).toBe(false);
+ expect(tree.read('my-new-lib/ng-package.json', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "{
+ "$schema": "../node_modules/ng-packagr/ng-package.schema.json",
+ "dest": "../dist/my-new-lib",
+ "lib": {
+ "entryFile": "src/index.ts"
+ }
+ }
+ "
+ `);
+ });
+
+ it('should move project when angular version is lower than v20', async () => {
+ updateJson(tree, 'package.json', (json) => {
+ json.dependencies = {
+ ...json.dependencies,
+ '@angular/core': '~19.2.0',
+ };
+ return json;
+ });
+ addProjectToGraph('my-lib');
+ await generateTestLibrary(tree, {
+ directory: 'my-lib',
+ buildable: true,
+ standalone: false,
+ skipFormat: true,
+ });
+
+ await angularMoveGenerator(tree, {
+ projectName: 'my-lib',
+ destination: 'my-new-lib',
+ newProjectName: 'my-new-lib',
+ updateImportPath: true,
+ skipFormat: true,
+ });
+
+ expect(tree.exists('my-lib/src/lib/my-lib.module.ts')).toBe(false);
+ expect(tree.read('my-new-lib/src/lib/my-new-lib.module.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { NgModule } from '@angular/core';
+ import { CommonModule } from '@angular/common';
+
+ @NgModule({
+ imports: [CommonModule],
+ })
+ export class MyNewLibModule {}
+ "
+ `);
+ expect(tree.exists('my-lib/ng-package.json')).toBe(false);
+ expect(tree.read('my-new-lib/ng-package.json', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "{
+ "$schema": "../node_modules/ng-packagr/ng-package.schema.json",
+ "dest": "../dist/my-new-lib",
+ "lib": {
+ "entryFile": "src/index.ts"
+ }
+ }
+ "
+ `);
+ });
+ });
});
diff --git a/packages/angular/src/generators/ng-add/migrators/builders/angular-devkit-karma.migrator.ts b/packages/angular/src/generators/ng-add/migrators/builders/angular-devkit-karma.migrator.ts
index cbd00ad6bf..0d5e5b180a 100644
--- a/packages/angular/src/generators/ng-add/migrators/builders/angular-devkit-karma.migrator.ts
+++ b/packages/angular/src/generators/ng-add/migrators/builders/angular-devkit-karma.migrator.ts
@@ -22,7 +22,7 @@ export class AngularDevkitKarmaMigrator extends BuilderMigrator {
) {
super(
tree,
- ['@angular-devkit/build-angular:karma'],
+ ['@angular/build:karma', '@angular-devkit/build-angular:karma'],
'karma',
project,
projectConfig,
diff --git a/packages/angular/src/generators/ng-add/migrators/projects/app.migrator.spec.ts b/packages/angular/src/generators/ng-add/migrators/projects/app.migrator.spec.ts
index 074795923e..8f10deffa6 100644
--- a/packages/angular/src/generators/ng-add/migrators/projects/app.migrator.spec.ts
+++ b/packages/angular/src/generators/ng-add/migrators/projects/app.migrator.spec.ts
@@ -139,7 +139,7 @@ describe('app migrator', () => {
`The "build" target is using a builder "@not/supported:builder" that's not currently supported by the automated migration. The target will be skipped.`,
]);
expect(result[0].hint).toMatchInlineSnapshot(
- `"Make sure to manually migrate the target configuration and any possible associated files. Alternatively, you could revert the migration, change the builder to one of the builders supported by the automated migration ("@angular-devkit/build-angular:application", "@angular-devkit/build-angular:browser", "@angular-devkit/build-angular:browser-esbuild", "@angular-devkit/build-angular:protractor", "@cypress/schematic:cypress", "@angular-devkit/build-angular:extract-i18n", "@nguniversal/builders:prerender", "@angular-devkit/build-angular:prerender", "@angular-devkit/build-angular:dev-server", "@angular-devkit/build-angular:server", "@nguniversal/builders:ssr-dev-server", "@angular-devkit/build-angular:ssr-dev-server", "@angular-devkit/build-angular:karma" and "@angular-eslint/builder:lint"), and run the migration again."`
+ `"Make sure to manually migrate the target configuration and any possible associated files. Alternatively, you could revert the migration, change the builder to one of the builders supported by the automated migration ("@angular/build:application", "@angular-devkit/build-angular:application", "@angular-devkit/build-angular:browser", "@angular-devkit/build-angular:browser-esbuild", "@angular-devkit/build-angular:protractor", "@cypress/schematic:cypress", "@angular/build:extract-i18n", "@angular-devkit/build-angular:extract-i18n", "@nguniversal/builders:prerender", "@angular-devkit/build-angular:prerender", "@angular/build:dev-server", "@angular-devkit/build-angular:dev-server", "@angular-devkit/build-angular:server", "@nguniversal/builders:ssr-dev-server", "@angular-devkit/build-angular:ssr-dev-server", "@angular/build:karma", "@angular-devkit/build-angular:karma" and "@angular-eslint/builder:lint"), and run the migration again."`
);
});
@@ -162,7 +162,7 @@ describe('app migrator', () => {
`The "test" target is using a builder "@other/not-supported:builder" that's not currently supported by the automated migration. The target will be skipped.`,
]);
expect(result[0].hint).toMatchInlineSnapshot(
- `"Make sure to manually migrate the target configuration and any possible associated files. Alternatively, you could revert the migration, change the builder to one of the builders supported by the automated migration ("@angular-devkit/build-angular:application", "@angular-devkit/build-angular:browser", "@angular-devkit/build-angular:browser-esbuild", "@angular-devkit/build-angular:protractor", "@cypress/schematic:cypress", "@angular-devkit/build-angular:extract-i18n", "@nguniversal/builders:prerender", "@angular-devkit/build-angular:prerender", "@angular-devkit/build-angular:dev-server", "@angular-devkit/build-angular:server", "@nguniversal/builders:ssr-dev-server", "@angular-devkit/build-angular:ssr-dev-server", "@angular-devkit/build-angular:karma" and "@angular-eslint/builder:lint"), and run the migration again."`
+ `"Make sure to manually migrate the target configuration and any possible associated files. Alternatively, you could revert the migration, change the builder to one of the builders supported by the automated migration ("@angular/build:application", "@angular-devkit/build-angular:application", "@angular-devkit/build-angular:browser", "@angular-devkit/build-angular:browser-esbuild", "@angular-devkit/build-angular:protractor", "@cypress/schematic:cypress", "@angular/build:extract-i18n", "@angular-devkit/build-angular:extract-i18n", "@nguniversal/builders:prerender", "@angular-devkit/build-angular:prerender", "@angular/build:dev-server", "@angular-devkit/build-angular:dev-server", "@angular-devkit/build-angular:server", "@nguniversal/builders:ssr-dev-server", "@angular-devkit/build-angular:ssr-dev-server", "@angular/build:karma", "@angular-devkit/build-angular:karma" and "@angular-eslint/builder:lint"), and run the migration again."`
);
});
@@ -181,7 +181,7 @@ describe('app migrator', () => {
`The "my-build" target is using a builder "@not/supported:builder" that's not currently supported by the automated migration. The target will be skipped.`,
]);
expect(result[0].hint).toMatchInlineSnapshot(
- `"Make sure to manually migrate the target configuration and any possible associated files. Alternatively, you could revert the migration, change the builder to one of the builders supported by the automated migration ("@angular-devkit/build-angular:application", "@angular-devkit/build-angular:browser", "@angular-devkit/build-angular:browser-esbuild", "@angular-devkit/build-angular:protractor", "@cypress/schematic:cypress", "@angular-devkit/build-angular:extract-i18n", "@nguniversal/builders:prerender", "@angular-devkit/build-angular:prerender", "@angular-devkit/build-angular:dev-server", "@angular-devkit/build-angular:server", "@nguniversal/builders:ssr-dev-server", "@angular-devkit/build-angular:ssr-dev-server", "@angular-devkit/build-angular:karma" and "@angular-eslint/builder:lint"), and run the migration again."`
+ `"Make sure to manually migrate the target configuration and any possible associated files. Alternatively, you could revert the migration, change the builder to one of the builders supported by the automated migration ("@angular/build:application", "@angular-devkit/build-angular:application", "@angular-devkit/build-angular:browser", "@angular-devkit/build-angular:browser-esbuild", "@angular-devkit/build-angular:protractor", "@cypress/schematic:cypress", "@angular/build:extract-i18n", "@angular-devkit/build-angular:extract-i18n", "@nguniversal/builders:prerender", "@angular-devkit/build-angular:prerender", "@angular/build:dev-server", "@angular-devkit/build-angular:dev-server", "@angular-devkit/build-angular:server", "@nguniversal/builders:ssr-dev-server", "@angular-devkit/build-angular:ssr-dev-server", "@angular/build:karma", "@angular-devkit/build-angular:karma" and "@angular-eslint/builder:lint"), and run the migration again."`
);
});
@@ -710,6 +710,7 @@ describe('app migrator', () => {
const { targets } = readProjectConfiguration(tree, 'app1');
expect(targets.build).toStrictEqual({
executor: '@angular-devkit/build-angular:browser',
+ outputs: ['{options.outputPath}'],
options: {
outputPath: 'dist/apps/app1',
index: 'apps/app1/src/index.html',
@@ -785,6 +786,7 @@ describe('app migrator', () => {
const { targets } = readProjectConfiguration(tree, 'app1');
expect(targets.build).toStrictEqual({
executor: '@angular-devkit/build-angular:application',
+ outputs: ['{options.outputPath}'],
options: {
outputPath: 'dist/apps/app1',
index: 'apps/app1/src/index.html',
@@ -853,6 +855,7 @@ describe('app migrator', () => {
const { targets } = readProjectConfiguration(tree, 'app1');
expect(targets.build).toStrictEqual({
executor: '@angular-devkit/build-angular:browser',
+ outputs: ['{options.outputPath}'],
options: {
outputPath: 'dist/apps/app1',
index: 'apps/app1/src/index.html',
@@ -910,6 +913,7 @@ describe('app migrator', () => {
const { targets } = readProjectConfiguration(tree, 'app1');
expect(targets.myCustomBuildTarget).toStrictEqual({
executor: '@angular-devkit/build-angular:browser',
+ outputs: ['{options.outputPath}'],
options: {
outputPath: 'dist/apps/app1',
index: 'apps/app1/src/index.html',
@@ -954,6 +958,7 @@ describe('app migrator', () => {
const { targets } = readProjectConfiguration(tree, 'app1');
expect(targets.build).toStrictEqual({
executor: '@angular-devkit/build-angular:browser',
+ outputs: ['{options.outputPath}'],
options: { outputPath: 'dist/apps/app1/browser' },
});
});
diff --git a/packages/angular/src/generators/ng-add/migrators/projects/app.migrator.ts b/packages/angular/src/generators/ng-add/migrators/projects/app.migrator.ts
index 20328c4086..da100678b7 100644
--- a/packages/angular/src/generators/ng-add/migrators/projects/app.migrator.ts
+++ b/packages/angular/src/generators/ng-add/migrators/projects/app.migrator.ts
@@ -1,4 +1,4 @@
-import type { Tree } from '@nx/devkit';
+import type { TargetConfiguration, Tree } from '@nx/devkit';
import {
joinPathFragments,
offsetFromRoot,
@@ -34,6 +34,7 @@ type SupportedTargets =
const supportedTargets: Record = {
build: {
builders: [
+ '@angular/build:application',
'@angular-devkit/build-angular:application',
'@angular-devkit/build-angular:browser',
'@angular-devkit/build-angular:browser-esbuild',
@@ -46,14 +47,24 @@ const supportedTargets: Record = {
'@cypress/schematic:cypress',
],
},
- i18n: { builders: ['@angular-devkit/build-angular:extract-i18n'] },
+ i18n: {
+ builders: [
+ '@angular/build:extract-i18n',
+ '@angular-devkit/build-angular:extract-i18n',
+ ],
+ },
prerender: {
builders: [
'@nguniversal/builders:prerender',
'@angular-devkit/build-angular:prerender',
],
},
- serve: { builders: ['@angular-devkit/build-angular:dev-server'] },
+ serve: {
+ builders: [
+ '@angular/build:dev-server',
+ '@angular-devkit/build-angular:dev-server',
+ ],
+ },
server: { builders: ['@angular-devkit/build-angular:server'] },
serveSsr: {
builders: [
@@ -231,14 +242,48 @@ export class AppMigrator extends ProjectMigrator {
this.updateTsConfigFileUsedByServerTarget(projectOffsetFromRoot);
}
- private convertBuildOptions(buildOptions: any): void {
- buildOptions.outputPath =
- buildOptions.outputPath &&
- joinPathFragments(
- 'dist',
- this.project.newRoot,
- this.targetNames.server ? 'browser' : ''
- );
+ private convertBuildOptions(
+ buildOptions: any,
+ target: TargetConfiguration,
+ updateOutputs: boolean = true
+ ): void {
+ const { executor } = target;
+ const isApplicationBuilder =
+ executor === '@angular/build:application' ||
+ executor === '@angular-devkit/build-angular:application';
+
+ if (updateOutputs) {
+ if (buildOptions.outputPath) {
+ if (isApplicationBuilder) {
+ if (typeof buildOptions.outputPath === 'string') {
+ buildOptions.outputPath = joinPathFragments(
+ 'dist',
+ this.project.newRoot
+ );
+ } else if (buildOptions.outputPath.base) {
+ buildOptions.outputPath.base = joinPathFragments(
+ 'dist',
+ this.project.newRoot
+ );
+ }
+ } else {
+ buildOptions.outputPath = joinPathFragments(
+ 'dist',
+ this.project.newRoot,
+ this.targetNames.server ? 'browser' : ''
+ );
+ }
+ } else if (isApplicationBuilder) {
+ buildOptions.outputPath = joinPathFragments('dist', this.projectName);
+ }
+
+ if (typeof buildOptions.outputPath === 'string') {
+ target.outputs = ['{options.outputPath}'];
+ } else if (buildOptions.outputPath?.base) {
+ target.outputs = ['{options.outputPath.base}'];
+ }
+ }
+
if (buildOptions.index) {
if (typeof buildOptions.index === 'string') {
buildOptions.index = this.convertAsset(buildOptions.index);
@@ -355,9 +400,9 @@ export class AppMigrator extends ProjectMigrator {
}
}
- this.convertBuildOptions(buildTarget.options ?? {});
+ this.convertBuildOptions(buildTarget.options ?? {}, buildTarget);
Object.values(buildTarget.configurations ?? {}).forEach((config) =>
- this.convertBuildOptions(config)
+ this.convertBuildOptions(config, buildTarget, false)
);
}
diff --git a/packages/angular/src/generators/ng-add/migrators/projects/lib.migrator.spec.ts b/packages/angular/src/generators/ng-add/migrators/projects/lib.migrator.spec.ts
index 1da9a98458..bfaefebf3c 100644
--- a/packages/angular/src/generators/ng-add/migrators/projects/lib.migrator.spec.ts
+++ b/packages/angular/src/generators/ng-add/migrators/projects/lib.migrator.spec.ts
@@ -140,7 +140,7 @@ describe('lib migrator', () => {
`The "build" target is using a builder "@not/supported:builder" that's not currently supported by the automated migration. The target will be skipped.`,
]);
expect(result[0].hint).toMatchInlineSnapshot(
- `"Make sure to manually migrate the target configuration and any possible associated files. Alternatively, you could revert the migration, change the builder to one of the builders supported by the automated migration ("@angular-devkit/build-angular:ng-packagr", "@angular/build:ng-packagr", "@angular-devkit/build-angular:karma" and "@angular-eslint/builder:lint"), and run the migration again."`
+ `"Make sure to manually migrate the target configuration and any possible associated files. Alternatively, you could revert the migration, change the builder to one of the builders supported by the automated migration ("@angular-devkit/build-angular:ng-packagr", "@angular/build:ng-packagr", "@angular/build:karma", "@angular-devkit/build-angular:karma" and "@angular-eslint/builder:lint"), and run the migration again."`
);
});
@@ -163,7 +163,7 @@ describe('lib migrator', () => {
`The "test" target is using a builder "@other/not-supported:builder" that's not currently supported by the automated migration. The target will be skipped.`,
]);
expect(result[0].hint).toMatchInlineSnapshot(
- `"Make sure to manually migrate the target configuration and any possible associated files. Alternatively, you could revert the migration, change the builder to one of the builders supported by the automated migration ("@angular-devkit/build-angular:ng-packagr", "@angular/build:ng-packagr", "@angular-devkit/build-angular:karma" and "@angular-eslint/builder:lint"), and run the migration again."`
+ `"Make sure to manually migrate the target configuration and any possible associated files. Alternatively, you could revert the migration, change the builder to one of the builders supported by the automated migration ("@angular-devkit/build-angular:ng-packagr", "@angular/build:ng-packagr", "@angular/build:karma", "@angular-devkit/build-angular:karma" and "@angular-eslint/builder:lint"), and run the migration again."`
);
});
@@ -182,7 +182,7 @@ describe('lib migrator', () => {
`The "my-build" target is using a builder "@not/supported:builder" that's not currently supported by the automated migration. The target will be skipped.`,
]);
expect(result[0].hint).toMatchInlineSnapshot(
- `"Make sure to manually migrate the target configuration and any possible associated files. Alternatively, you could revert the migration, change the builder to one of the builders supported by the automated migration ("@angular-devkit/build-angular:ng-packagr", "@angular/build:ng-packagr", "@angular-devkit/build-angular:karma" and "@angular-eslint/builder:lint"), and run the migration again."`
+ `"Make sure to manually migrate the target configuration and any possible associated files. Alternatively, you could revert the migration, change the builder to one of the builders supported by the automated migration ("@angular-devkit/build-angular:ng-packagr", "@angular/build:ng-packagr", "@angular/build:karma", "@angular-devkit/build-angular:karma" and "@angular-eslint/builder:lint"), and run the migration again."`
);
});
diff --git a/packages/angular/src/generators/ngrx-feature-store/__snapshots__/ngrx-feature-store.spec.ts.snap b/packages/angular/src/generators/ngrx-feature-store/__snapshots__/ngrx-feature-store.spec.ts.snap
index 29a35dc18c..1266514837 100644
--- a/packages/angular/src/generators/ngrx-feature-store/__snapshots__/ngrx-feature-store.spec.ts.snap
+++ b/packages/angular/src/generators/ngrx-feature-store/__snapshots__/ngrx-feature-store.spec.ts.snap
@@ -6,7 +6,7 @@ export * from './lib/+state/users/users.models';
export * from './lib/+state/users/users.selectors';
export * from './lib/+state/users/users.reducer';
export * from './lib/+state/users/users.actions';
-export * from './lib/feature-module.module';
+export * from './lib/feature-module-module';
"
`;
@@ -646,7 +646,7 @@ export * from './lib/+state/users.models';
export * from './lib/+state/users.selectors';
export * from './lib/+state/users.reducer';
export * from './lib/+state/users.actions';
-export * from './lib/feature-module.module';
+export * from './lib/feature-module-module';
"
`;
@@ -662,7 +662,7 @@ export * from './lib/+state/users.facade';
export * from './lib/+state/users.models';
export { UsersActions, UsersFeature, UsersSelectors };
-export * from './lib/feature-module.module';
+export * from './lib/feature-module-module';
"
`;
@@ -1088,7 +1088,7 @@ describe('Users Selectors', () => {
exports[`ngrx-feature-store Standalone APIs should generate the files with the correct content 11`] = `
"import { Route } from '@angular/router';
-import { FeatureComponent } from './feature/feature.component';
+import { Feature } from './feature/feature';
import { provideStore, provideState } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';
import * as fromUsers from './+state/users.reducer';
@@ -1096,7 +1096,7 @@ import { UsersEffects } from './+state/users.effects';
import { UsersFacade } from './+state/users.facade';
export const featureRoutes: Route[] = [
- { path: '', component: FeatureComponent , providers: [UsersFacade, provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer), provideEffects(UsersEffects)]}
+ { path: '', component: Feature , providers: [UsersFacade, provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer), provideEffects(UsersEffects)]}
];
"
`;
@@ -1109,7 +1109,7 @@ export * from './lib/+state/users.reducer';
export * from './lib/+state/users.actions';
export * from './lib/lib.routes';
-export * from './lib/feature/feature.component';"
+export * from './lib/feature/feature';"
`;
exports[`ngrx-feature-store Standalone APIs should have the correct entry point when --barrels=true 1`] = `
@@ -1126,5 +1126,5 @@ export * from './lib/+state/users.models';
export { UsersActions, UsersFeature, UsersSelectors };
export * from './lib/lib.routes';
-export * from './lib/feature/feature.component';"
+export * from './lib/feature/feature';"
`;
diff --git a/packages/angular/src/generators/ngrx-feature-store/ngrx-feature-store.spec.ts b/packages/angular/src/generators/ngrx-feature-store/ngrx-feature-store.spec.ts
index 55334679a1..a8ba6ac853 100644
--- a/packages/angular/src/generators/ngrx-feature-store/ngrx-feature-store.spec.ts
+++ b/packages/angular/src/generators/ngrx-feature-store/ngrx-feature-store.spec.ts
@@ -9,7 +9,7 @@ import { ngrxVersion } from '../../utils/versions';
describe('ngrx-feature-store', () => {
describe('NgModule', () => {
- const parent = 'feature-module/src/lib/feature-module.module.ts';
+ const parent = 'feature-module/src/lib/feature-module-module.ts';
it('should error when parent cannot be found', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace();
@@ -23,8 +23,8 @@ describe('ngrx-feature-store', () => {
parent,
skipFormat: true,
})
- ).rejects.toThrowError(
- `Parent does not exist: feature-module/src/lib/feature-module.module.ts.`
+ ).rejects.toThrow(
+ `Parent does not exist: feature-module/src/lib/feature-module-module.ts.`
);
});
@@ -333,9 +333,7 @@ describe('ngrx-feature-store', () => {
parent,
skipFormat: true,
})
- ).rejects.toThrowError(
- `Parent does not exist: feature/src/lib/lib.routes.ts`
- );
+ ).rejects.toThrow(`Parent does not exist: feature/src/lib/lib.routes.ts`);
});
it('should update package.json', async () => {
diff --git a/packages/angular/src/generators/ngrx-root-store/__snapshots__/ngrx-root-store.spec.ts.snap b/packages/angular/src/generators/ngrx-root-store/__snapshots__/ngrx-root-store.spec.ts.snap
index 56a631eec9..c6b4722ccf 100644
--- a/packages/angular/src/generators/ngrx-root-store/__snapshots__/ngrx-root-store.spec.ts.snap
+++ b/packages/angular/src/generators/ngrx-root-store/__snapshots__/ngrx-root-store.spec.ts.snap
@@ -1,12 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`NgRxRootStoreGenerator NgModule should add a facade when --facade=true 1`] = `
-"import { NgModule } from '@angular/core';
+"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
-import { AppComponent } from './app.component';
+import { App } from './app';
import { appRoutes } from './app.routes';
-import { NxWelcomeComponent } from './nx-welcome.component';
+import { NxWelcome } from './nx-welcome';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { StoreRouterConnectingModule } from '@ngrx/router-store';
@@ -15,7 +15,7 @@ import { UsersEffects } from './+state/users.effects';
import { UsersFacade } from './+state/users.facade';
@NgModule({
- declarations: [AppComponent, NxWelcomeComponent],
+ declarations: [App, NxWelcome],
imports: [
BrowserModule,
RouterModule.forRoot(appRoutes),
@@ -34,8 +34,8 @@ import { UsersFacade } from './+state/users.facade';
StoreModule.forFeature(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),
EffectsModule.forFeature([UsersEffects]),
],
- providers: [UsersFacade],
- bootstrap: [AppComponent],
+ providers: [provideBrowserGlobalErrorListeners(), UsersFacade],
+ bootstrap: [App],
})
export class AppModule {}
"
@@ -73,12 +73,12 @@ export class UsersFacade {
`;
exports[`NgRxRootStoreGenerator NgModule should add a root module and root state when --minimal=false 1`] = `
-"import { NgModule } from '@angular/core';
+"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
-import { AppComponent } from './app.component';
+import { App } from './app';
import { appRoutes } from './app.routes';
-import { NxWelcomeComponent } from './nx-welcome.component';
+import { NxWelcome } from './nx-welcome';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { StoreRouterConnectingModule } from '@ngrx/router-store';
@@ -86,7 +86,7 @@ import * as fromUsers from './+state/users.reducer';
import { UsersEffects } from './+state/users.effects';
@NgModule({
- declarations: [AppComponent, NxWelcomeComponent],
+ declarations: [App, NxWelcome],
imports: [
BrowserModule,
RouterModule.forRoot(appRoutes),
@@ -105,8 +105,8 @@ import { UsersEffects } from './+state/users.effects';
StoreModule.forFeature(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),
EffectsModule.forFeature([UsersEffects]),
],
- providers: [],
- bootstrap: [AppComponent],
+ providers: [provideBrowserGlobalErrorListeners()],
+ bootstrap: [App],
})
export class AppModule {}
"
@@ -358,18 +358,18 @@ describe('Users Selectors', () => {
`;
exports[`NgRxRootStoreGenerator NgModule should add an empty root module when --minimal=true 1`] = `
-"import { NgModule } from '@angular/core';
+"import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
-import { AppComponent } from './app.component';
+import { App } from './app';
import { appRoutes } from './app.routes';
-import { NxWelcomeComponent } from './nx-welcome.component';
+import { NxWelcome } from './nx-welcome';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { StoreRouterConnectingModule } from '@ngrx/router-store';
@NgModule({
- declarations: [AppComponent, NxWelcomeComponent],
+ declarations: [App, NxWelcome],
imports: [
BrowserModule,
RouterModule.forRoot(appRoutes),
@@ -383,27 +383,27 @@ import { StoreRouterConnectingModule } from '@ngrx/router-store';
}),
StoreRouterConnectingModule.forRoot(),
],
- providers: [],
- bootstrap: [AppComponent],
+ providers: [provideBrowserGlobalErrorListeners()],
+ bootstrap: [App],
})
export class AppModule {}
"
`;
exports[`NgRxRootStoreGenerator NgModule should instrument the store devtools when "addDevTools: true" 1`] = `
-"import { NgModule, isDevMode } from '@angular/core';
+"import { NgModule, provideBrowserGlobalErrorListeners, isDevMode } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
-import { AppComponent } from './app.component';
+import { App } from './app';
import { appRoutes } from './app.routes';
-import { NxWelcomeComponent } from './nx-welcome.component';
+import { NxWelcome } from './nx-welcome';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { StoreRouterConnectingModule } from '@ngrx/router-store';
@NgModule({
- declarations: [AppComponent, NxWelcomeComponent],
+ declarations: [App, NxWelcome],
imports: [
BrowserModule,
RouterModule.forRoot(appRoutes),
@@ -418,15 +418,15 @@ import { StoreRouterConnectingModule } from '@ngrx/router-store';
}),
StoreRouterConnectingModule.forRoot(),
],
- providers: [],
- bootstrap: [AppComponent],
+ providers: [provideBrowserGlobalErrorListeners()],
+ bootstrap: [App],
})
export class AppModule {}
"
`;
exports[`NgRxRootStoreGenerator Standalone APIs should add a facade when --facade=true 1`] = `
-"import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
+"import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes';
import { provideStore, provideState } from '@ngrx/store';
@@ -436,7 +436,11 @@ import { UsersEffects } from './+state/users.effects';
import { UsersFacade } from './+state/users.facade';
export const appConfig: ApplicationConfig = {
- providers: [provideEffects(UsersEffects),provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),UsersFacade,provideStore(),provideEffects(),provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(appRoutes) ]
+ providers: [provideEffects(UsersEffects),provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),UsersFacade,provideStore(),provideEffects(),
+ provideBrowserGlobalErrorListeners(),
+ provideZoneChangeDetection({ eventCoalescing: true }),
+ provideRouter(appRoutes)
+ ]
};
"
`;
@@ -473,7 +477,7 @@ export class UsersFacade {
`;
exports[`NgRxRootStoreGenerator Standalone APIs should add a root module and root state when --minimal=false 1`] = `
-"import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
+"import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes';
import { provideStore, provideState } from '@ngrx/store';
@@ -482,7 +486,11 @@ import * as fromUsers from './+state/users.reducer';
import { UsersEffects } from './+state/users.effects';
export const appConfig: ApplicationConfig = {
- providers: [provideEffects(UsersEffects),provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),provideStore(),provideEffects(),provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(appRoutes) ]
+ providers: [provideEffects(UsersEffects),provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),provideStore(),provideEffects(),
+ provideBrowserGlobalErrorListeners(),
+ provideZoneChangeDetection({ eventCoalescing: true }),
+ provideRouter(appRoutes)
+ ]
};
"
`;
@@ -722,20 +730,24 @@ describe('Users Selectors', () => {
`;
exports[`NgRxRootStoreGenerator Standalone APIs should add an empty root module when --minimal=true 1`] = `
-"import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
+"import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes';
import { provideStore } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';
export const appConfig: ApplicationConfig = {
- providers: [provideStore(),provideEffects(),provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(appRoutes) ]
+ providers: [provideStore(),provideEffects(),
+ provideBrowserGlobalErrorListeners(),
+ provideZoneChangeDetection({ eventCoalescing: true }),
+ provideRouter(appRoutes)
+ ]
};
"
`;
exports[`NgRxRootStoreGenerator Standalone APIs should instrument the store devtools when "addDevTools: true" 1`] = `
-"import { ApplicationConfig, provideZoneChangeDetection, isDevMode } from '@angular/core';
+"import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection, isDevMode } from '@angular/core';
import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes';
import { provideStore } from '@ngrx/store';
@@ -743,7 +755,11 @@ import { provideEffects } from '@ngrx/effects';
import { provideStoreDevtools } from '@ngrx/store-devtools';
export const appConfig: ApplicationConfig = {
- providers: [provideStore(),provideStoreDevtools({ logOnly: !isDevMode() }),provideEffects(),provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(appRoutes) ]
+ providers: [provideStore(),provideStoreDevtools({ logOnly: !isDevMode() }),provideEffects(),
+ provideBrowserGlobalErrorListeners(),
+ provideZoneChangeDetection({ eventCoalescing: true }),
+ provideRouter(appRoutes)
+ ]
};
"
`;
diff --git a/packages/angular/src/generators/ngrx-root-store/lib/normalize-options.ts b/packages/angular/src/generators/ngrx-root-store/lib/normalize-options.ts
index 42df1dac64..13785601e7 100644
--- a/packages/angular/src/generators/ngrx-root-store/lib/normalize-options.ts
+++ b/packages/angular/src/generators/ngrx-root-store/lib/normalize-options.ts
@@ -35,8 +35,13 @@ export function normalizeOptions(
project.sourceRoot,
'app/app.config.ts'
);
- const appMainPath =
+ let appMainPath =
project.targets.build.options.main ?? project.targets.build.options.browser;
+ if (!appMainPath) {
+ const sourceRoot =
+ project.sourceRoot ?? joinPathFragments(project.root, 'src');
+ appMainPath = joinPathFragments(sourceRoot, 'main.ts');
+ }
/** If NgModule App
* -> Use App Module
@@ -45,10 +50,10 @@ export function normalizeOptions(
* --> If so, use that
* --> If not, use main.ts
*/
- const ngModulePath = joinPathFragments(
- project.sourceRoot,
- 'app/app.module.ts'
- );
+ let ngModulePath = joinPathFragments(project.sourceRoot, 'app/app.module.ts');
+ if (!tree.exists(ngModulePath)) {
+ ngModulePath = joinPathFragments(project.sourceRoot, 'app/app-module.ts');
+ }
const parent =
!isStandalone && tree.exists(ngModulePath)
? ngModulePath
diff --git a/packages/angular/src/generators/ngrx-root-store/ngrx-root-store.spec.ts b/packages/angular/src/generators/ngrx-root-store/ngrx-root-store.spec.ts
index f2efe3c98d..5a2a28e4d4 100644
--- a/packages/angular/src/generators/ngrx-root-store/ngrx-root-store.spec.ts
+++ b/packages/angular/src/generators/ngrx-root-store/ngrx-root-store.spec.ts
@@ -19,7 +19,7 @@ describe('NgRxRootStoreGenerator', () => {
name: '',
skipFormat: true,
})
- ).rejects.toThrowError();
+ ).rejects.toThrow();
});
it('should error when minimal false, but name is undefined or falsy', async () => {
@@ -35,7 +35,7 @@ describe('NgRxRootStoreGenerator', () => {
name: undefined,
skipFormat: true,
})
- ).rejects.toThrowError();
+ ).rejects.toThrow();
});
it('should add an empty root module when --minimal=true', async () => {
@@ -52,7 +52,7 @@ describe('NgRxRootStoreGenerator', () => {
// ASSERT
expect(
- tree.read('my-app/src/app/app.module.ts', 'utf-8')
+ tree.read('my-app/src/app/app-module.ts', 'utf-8')
).toMatchSnapshot();
expect(tree.exists('/my-app/src/app/+state/users.actions.ts')).toBe(
false
@@ -88,7 +88,7 @@ describe('NgRxRootStoreGenerator', () => {
// ASSERT
expect(
- tree.read('my-app/src/app/app.module.ts', 'utf-8')
+ tree.read('my-app/src/app/app-module.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('/my-app/src/app/+state/users.actions.ts', 'utf-8')
@@ -125,7 +125,7 @@ describe('NgRxRootStoreGenerator', () => {
// ASSERT
expect(
- tree.read('my-app/src/app/app.module.ts', 'utf-8')
+ tree.read('my-app/src/app/app-module.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('/my-app/src/app/+state/users.facade.ts', 'utf-8')
@@ -147,7 +147,7 @@ describe('NgRxRootStoreGenerator', () => {
// ASSERT
expect(
- tree.read('my-app/src/app/app.module.ts', 'utf-8')
+ tree.read('my-app/src/app/app-module.ts', 'utf-8')
).toMatchSnapshot();
});
@@ -242,7 +242,7 @@ describe('NgRxRootStoreGenerator', () => {
name: '',
skipFormat: true,
})
- ).rejects.toThrowError();
+ ).rejects.toThrow();
});
it('should error when minimal false, but name is undefined or falsy', async () => {
@@ -258,7 +258,7 @@ describe('NgRxRootStoreGenerator', () => {
name: undefined,
skipFormat: true,
})
- ).rejects.toThrowError();
+ ).rejects.toThrow();
});
it('should add an empty root module when --minimal=true', async () => {
diff --git a/packages/angular/src/generators/ngrx/__snapshots__/ngrx.spec.ts.snap b/packages/angular/src/generators/ngrx/__snapshots__/ngrx.spec.ts.snap
index 98d812a7df..a2621799fe 100644
--- a/packages/angular/src/generators/ngrx/__snapshots__/ngrx.spec.ts.snap
+++ b/packages/angular/src/generators/ngrx/__snapshots__/ngrx.spec.ts.snap
@@ -251,7 +251,7 @@ exports[`ngrx NgModule Syntax should add a root module with feature module when
"import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
-import { AppComponent } from './app.component';
+import { App } from './app';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import * as fromUsers from './+state/users.reducer';
@@ -265,8 +265,8 @@ import { StoreRouterConnectingModule } from '@ngrx/router-store';
strictStateImmutability: true
}
}), EffectsModule.forRoot([UsersEffects]), StoreRouterConnectingModule.forRoot(), StoreModule.forFeature(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer)],
- declarations: [AppComponent],
- bootstrap: [AppComponent]
+ declarations: [App],
+ bootstrap: [App]
})
export class AppModule {}
"
@@ -276,7 +276,7 @@ exports[`ngrx NgModule Syntax should add a root module with feature module when
"import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
-import { AppComponent } from './app.component';
+import { App } from './app';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import * as fromUsers from './+state/users.reducer';
@@ -290,8 +290,8 @@ import { StoreRouterConnectingModule } from '@ngrx/router-store';
strictStateImmutability: true
}
}), EffectsModule.forRoot([UsersEffects]), StoreRouterConnectingModule.forRoot(), StoreModule.forFeature(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer)],
- declarations: [AppComponent],
- bootstrap: [AppComponent]
+ declarations: [App],
+ bootstrap: [App]
})
export class AppModule {}
"
@@ -301,7 +301,7 @@ exports[`ngrx NgModule Syntax should add an empty root module when minimal and r
"import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
-import { AppComponent } from './app.component';
+import { App } from './app';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { StoreRouterConnectingModule } from '@ngrx/router-store';
@@ -313,8 +313,8 @@ import { StoreRouterConnectingModule } from '@ngrx/router-store';
strictStateImmutability: true
}
}), EffectsModule.forRoot([]), StoreRouterConnectingModule.forRoot()],
- declarations: [AppComponent],
- bootstrap: [AppComponent]
+ declarations: [App],
+ bootstrap: [App]
})
export class AppModule {}
"
@@ -324,7 +324,7 @@ exports[`ngrx NgModule Syntax should format files 1`] = `
"import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
-import { AppComponent } from './app.component';
+import { App } from './app';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import * as fromUsers from './+state/users.reducer';
@@ -336,8 +336,8 @@ import { UsersEffects } from './+state/users.effects';
StoreModule.forFeature(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),
EffectsModule.forFeature([UsersEffects]),
],
- declarations: [AppComponent],
- bootstrap: [AppComponent],
+ declarations: [App],
+ bootstrap: [App],
})
export class AppModule {}
"
@@ -663,11 +663,11 @@ exports[`ngrx NgModule Syntax should not generate imports when skipImport is tru
"import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
-import { AppComponent } from './app.component';
+import { App } from './app';
@NgModule({
imports: [BrowserModule, RouterModule.forRoot([])],
- declarations: [AppComponent],
- bootstrap: [AppComponent]
+ declarations: [App],
+ bootstrap: [App]
})
export class AppModule {}
"
@@ -685,7 +685,7 @@ export * from './lib/+state/super-users.facade';
export * from './lib/+state/super-users.models';
export { SuperUsersActions, SuperUsersFeature, SuperUsersSelectors };
-export * from './lib/flights.module';
+export * from './lib/flights-module';
"
`;
@@ -694,7 +694,7 @@ exports[`ngrx NgModule Syntax should update the entry point file with no facade
export * from './lib/+state/super-users.selectors';
export * from './lib/+state/super-users.reducer';
export * from './lib/+state/super-users.actions';
-export * from './lib/flights.module';
+export * from './lib/flights-module';
"
`;
@@ -704,59 +704,59 @@ export * from './lib/+state/super-users.models';
export * from './lib/+state/super-users.selectors';
export * from './lib/+state/super-users.reducer';
export * from './lib/+state/super-users.actions';
-export * from './lib/flights.module';
+export * from './lib/flights-module';
"
`;
exports[`ngrx Standalone APIs should add a feature module when route is non-empty 1`] = `
"import { Routes } from '@angular/router';
-import { NxWelcomeComponent } from './nx-welcome.component';
+import { NxWelcome } from './nx-welcome';
import { provideStore, provideState } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';
import * as fromUsers from './+state/users.reducer';
import { UsersEffects } from './+state/users.effects';
-export const appRoutes: Routes = [{ path: 'home', component: NxWelcomeComponent , providers: [provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer), provideEffects(UsersEffects)]}];
+export const appRoutes: Routes = [{ path: 'home', component: NxWelcome , providers: [provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer), provideEffects(UsersEffects)]}];
"
`;
exports[`ngrx Standalone APIs should add a feature module when route is set to default 1`] = `
"import { Routes } from '@angular/router';
-import { NxWelcomeComponent } from './nx-welcome.component';
+import { NxWelcome } from './nx-welcome';
import { provideStore, provideState } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';
import * as fromUsers from './+state/users.reducer';
import { UsersEffects } from './+state/users.effects';
-export const appRoutes: Routes = [{ path: '', component: NxWelcomeComponent , providers: [provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer), provideEffects(UsersEffects)]}];
+export const appRoutes: Routes = [{ path: '', component: NxWelcome , providers: [provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer), provideEffects(UsersEffects)]}];
"
`;
exports[`ngrx Standalone APIs should add a feature module when route is undefined 1`] = `
"import { Routes } from '@angular/router';
-import { NxWelcomeComponent } from './nx-welcome.component';
+import { NxWelcome } from './nx-welcome';
import { provideStore, provideState } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';
import * as fromUsers from './+state/users.reducer';
import { UsersEffects } from './+state/users.effects';
-export const appRoutes: Routes = [{ path: '', component: NxWelcomeComponent , providers: [provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer), provideEffects(UsersEffects)]}];
+export const appRoutes: Routes = [{ path: '', component: NxWelcome , providers: [provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer), provideEffects(UsersEffects)]}];
"
`;
exports[`ngrx Standalone APIs should add a root module with feature module when minimal is set to false 1`] = `
"import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
-import { AppComponent } from './app/app.component';
+import { App } from './app/app';
-bootstrapApplication(AppComponent, appConfig).catch((err) =>
+bootstrapApplication(App, appConfig).catch((err) =>
console.error(err)
);
"
`;
exports[`ngrx Standalone APIs should add a root module with feature module when minimal is set to false 2`] = `
-"import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
+"import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes';
import { provideStore, provideState } from '@ngrx/store';
@@ -765,7 +765,11 @@ import * as fromUsers from './+state/users.reducer';
import { UsersEffects } from './+state/users.effects';
export const appConfig: ApplicationConfig = {
- providers: [provideEffects(UsersEffects),provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),provideEffects(),provideStore(),provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(appRoutes) ]
+ providers: [provideEffects(UsersEffects),provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),provideEffects(),provideStore(),
+ provideBrowserGlobalErrorListeners(),
+ provideZoneChangeDetection({ eventCoalescing: true }),
+ provideRouter(appRoutes)
+ ]
};
"
`;
@@ -773,23 +777,27 @@ export const appConfig: ApplicationConfig = {
exports[`ngrx Standalone APIs should add an empty provideStore when minimal and root are set to true 1`] = `
"import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
-import { AppComponent } from './app/app.component';
+import { App } from './app/app';
-bootstrapApplication(AppComponent, appConfig).catch((err) =>
+bootstrapApplication(App, appConfig).catch((err) =>
console.error(err)
);
"
`;
exports[`ngrx Standalone APIs should add an empty provideStore when minimal and root are set to true 2`] = `
-"import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
+"import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes';
import { provideStore, provideState } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';
export const appConfig: ApplicationConfig = {
- providers: [provideEffects(),provideStore(),provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(appRoutes) ]
+ providers: [provideEffects(),provideStore(),
+ provideBrowserGlobalErrorListeners(),
+ provideZoneChangeDetection({ eventCoalescing: true }),
+ provideRouter(appRoutes)
+ ]
};
"
`;
@@ -797,16 +805,16 @@ export const appConfig: ApplicationConfig = {
exports[`ngrx Standalone APIs should add facade provider when facade is true 1`] = `
"import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
-import { AppComponent } from './app/app.component';
+import { App } from './app/app';
-bootstrapApplication(AppComponent, appConfig).catch((err) =>
+bootstrapApplication(App, appConfig).catch((err) =>
console.error(err)
);
"
`;
exports[`ngrx Standalone APIs should add facade provider when facade is true 2`] = `
-"import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
+"import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes';
import { provideStore, provideState } from '@ngrx/store';
@@ -816,21 +824,25 @@ import { UsersEffects } from './+state/users.effects';
import { UsersFacade } from './+state/users.facade';
export const appConfig: ApplicationConfig = {
- providers: [provideEffects(UsersEffects),provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),provideEffects(),provideStore(),UsersFacade,provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(appRoutes) ]
+ providers: [provideEffects(UsersEffects),provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),provideEffects(),provideStore(),UsersFacade,
+ provideBrowserGlobalErrorListeners(),
+ provideZoneChangeDetection({ eventCoalescing: true }),
+ provideRouter(appRoutes)
+ ]
};
"
`;
exports[`ngrx Standalone APIs should add facade provider when facade is true and --root is false 1`] = `
"import { Routes } from '@angular/router';
-import { NxWelcomeComponent } from './nx-welcome.component';
+import { NxWelcome } from './nx-welcome';
import { provideStore, provideState } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';
import * as fromUsers from './+state/users.reducer';
import { UsersEffects } from './+state/users.effects';
import { UsersFacade } from './+state/users.facade';
-export const appRoutes: Routes = [{ path: '', component: NxWelcomeComponent , providers: [UsersFacade, provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer), provideEffects(UsersEffects)]}];
+export const appRoutes: Routes = [{ path: '', component: NxWelcome , providers: [UsersFacade, provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer), provideEffects(UsersEffects)]}];
"
`;
diff --git a/packages/angular/src/generators/ngrx/ngrx.spec.ts b/packages/angular/src/generators/ngrx/ngrx.spec.ts
index 7f0cb55799..742088cafa 100644
--- a/packages/angular/src/generators/ngrx/ngrx.spec.ts
+++ b/packages/angular/src/generators/ngrx/ngrx.spec.ts
@@ -25,7 +25,7 @@ describe('ngrx', () => {
const defaultOptions: NgRxGeneratorOptions = {
directory: '+state',
minimal: true,
- parent: 'myapp/src/app/app.module.ts',
+ parent: 'myapp/src/app/app-module.ts',
name: 'users',
skipFormat: true,
};
@@ -41,7 +41,7 @@ describe('ngrx', () => {
const defaultModuleOptions: NgRxGeneratorOptions = {
directory: '+state',
minimal: true,
- module: 'myapp/src/app/app.module.ts',
+ module: 'myapp/src/app/app-module.ts',
name: 'users',
skipFormat: true,
};
@@ -54,14 +54,14 @@ describe('ngrx', () => {
describe('NgModule Syntax', () => {
beforeEach(() => {
jest.clearAllMocks();
- tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ tree = createTreeWithEmptyWorkspace();
createApp(tree, 'myapp');
appConfig = getAppConfig();
statePath = `${dirname(appConfig.appModule)}/+state`;
});
it('should error when the module could not be found', async () => {
- const modulePath = 'not-existing.module.ts';
+ const modulePath = 'not-existing-module.ts';
await expect(
ngrxGenerator(tree, {
@@ -72,7 +72,7 @@ describe('ngrx', () => {
});
it('should error when the module could not be found using --module', async () => {
- const modulePath = 'not-existing.module.ts';
+ const modulePath = 'not-existing-module.ts';
await expect(
ngrxGenerator(tree, {
@@ -90,7 +90,7 @@ describe('ngrx', () => {
});
expect(
- tree.read('myapp/src/app/app.module.ts', 'utf-8')
+ tree.read('myapp/src/app/app-module.ts', 'utf-8')
).toMatchSnapshot();
});
@@ -123,7 +123,7 @@ describe('ngrx', () => {
});
expect(
- tree.read('myapp/src/app/app.module.ts', 'utf-8')
+ tree.read('myapp/src/app/app-module.ts', 'utf-8')
).toMatchSnapshot();
});
@@ -135,7 +135,7 @@ describe('ngrx', () => {
});
expect(
- tree.read('myapp/src/app/app.module.ts', 'utf-8')
+ tree.read('myapp/src/app/app-module.ts', 'utf-8')
).toMatchSnapshot();
});
@@ -144,12 +144,12 @@ describe('ngrx', () => {
await ngrxGenerator(tree, {
...defaultOptions,
- module: 'no-router-app/src/app/app.module.ts',
+ module: 'no-router-app/src/app/app-module.ts',
root: true,
});
const appModule = tree.read(
- 'no-router-app/src/app/app.module.ts',
+ 'no-router-app/src/app/app-module.ts',
'utf-8'
);
expect(appModule).not.toContain('StoreRouterConnectingModule.forRoot()');
@@ -163,7 +163,7 @@ describe('ngrx', () => {
facade: true,
});
- expect(tree.read('myapp/src/app/app.module.ts', 'utf-8')).toContain(
+ expect(tree.read('myapp/src/app/app-module.ts', 'utf-8')).toContain(
'providers: [UsersFacade]'
);
});
@@ -176,7 +176,7 @@ describe('ngrx', () => {
facade: false,
});
- expect(tree.read('myapp/src/app/app.module.ts', 'utf-8')).not.toContain(
+ expect(tree.read('myapp/src/app/app-module.ts', 'utf-8')).not.toContain(
'providers: [UsersFacade]'
);
});
@@ -189,7 +189,7 @@ describe('ngrx', () => {
facade: true,
});
- expect(tree.read('myapp/src/app/app.module.ts', 'utf-8')).not.toContain(
+ expect(tree.read('myapp/src/app/app-module.ts', 'utf-8')).not.toContain(
'providers: [UsersFacade]'
);
});
@@ -208,7 +208,7 @@ describe('ngrx', () => {
expectFileToExist('myapp/src/app/+state/users.selectors.ts');
expectFileToExist('myapp/src/app/+state/users.selectors.spec.ts');
expect(
- tree.read('myapp/src/app/app.module.ts', 'utf-8')
+ tree.read('myapp/src/app/app-module.ts', 'utf-8')
).toMatchSnapshot();
});
@@ -426,7 +426,7 @@ describe('ngrx', () => {
expect(devkit.formatFiles).toHaveBeenCalled();
expect(
- tree.read('myapp/src/app/app.module.ts', 'utf-8')
+ tree.read('myapp/src/app/app-module.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('myapp/src/app/+state/users.actions.ts', 'utf-8')
@@ -512,23 +512,20 @@ describe('ngrx', () => {
describe('Standalone APIs', () => {
beforeEach(async () => {
jest.clearAllMocks();
- tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ tree = createTreeWithEmptyWorkspace();
await generateTestApplication(tree, {
directory: 'my-app',
standalone: true,
routing: true,
skipFormat: true,
});
- tree.write(
- 'my-app/src/app/app.component.html',
- ''
- );
+ tree.write('my-app/src/app/app.html', '');
tree.write(
'my-app/src/app/app.routes.ts',
`import { Routes } from '@angular/router';
-import { NxWelcomeComponent } from './nx-welcome.component';
+import { NxWelcome } from './nx-welcome';
-export const appRoutes: Routes = [{ path: '', component: NxWelcomeComponent }];
+export const appRoutes: Routes = [{ path: '', component: NxWelcome }];
`
);
});
@@ -601,9 +598,9 @@ export const appRoutes: Routes = [{ path: '', component: NxWelcomeComponent }];
tree.write(
'my-app/src/app/app.routes.ts',
`import { Routes } from '@angular/router';
-import { NxWelcomeComponent } from './nx-welcome.component';
+import { NxWelcome } from './nx-welcome';
-export const appRoutes: Routes = [{ path: 'home', component: NxWelcomeComponent }];
+export const appRoutes: Routes = [{ path: 'home', component: NxWelcome }];
`
);
@@ -664,7 +661,7 @@ export const appRoutes: Routes = [{ path: 'home', component: NxWelcomeComponent
describe('angular compat support', () => {
beforeEach(async () => {
jest.clearAllMocks();
- tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ tree = createTreeWithEmptyWorkspace();
await generateTestApplication(tree, {
directory: 'myapp',
standalone: false,
@@ -674,35 +671,35 @@ export const appRoutes: Routes = [{ path: 'home', component: NxWelcomeComponent
...json,
dependencies: {
...json.dependencies,
- '@angular/core': '17.0.0',
+ '@angular/core': '18.0.0',
},
}));
});
- it('should install the ngrx 17 packages', async () => {
+ it('should install the ngrx 18 packages', async () => {
await ngrxGenerator(tree, defaultOptions);
const packageJson = devkit.readJson(tree, 'package.json');
expect(packageJson.dependencies['@ngrx/store']).toEqual(
- backwardCompatibleVersions.angularV17.ngrxVersion
+ backwardCompatibleVersions.angularV18.ngrxVersion
);
expect(packageJson.dependencies['@ngrx/effects']).toEqual(
- backwardCompatibleVersions.angularV17.ngrxVersion
+ backwardCompatibleVersions.angularV18.ngrxVersion
);
expect(packageJson.dependencies['@ngrx/entity']).toEqual(
- backwardCompatibleVersions.angularV17.ngrxVersion
+ backwardCompatibleVersions.angularV18.ngrxVersion
);
expect(packageJson.dependencies['@ngrx/router-store']).toEqual(
- backwardCompatibleVersions.angularV17.ngrxVersion
+ backwardCompatibleVersions.angularV18.ngrxVersion
);
expect(packageJson.dependencies['@ngrx/component-store']).toEqual(
- backwardCompatibleVersions.angularV17.ngrxVersion
+ backwardCompatibleVersions.angularV18.ngrxVersion
);
expect(packageJson.devDependencies['@ngrx/schematics']).toEqual(
- backwardCompatibleVersions.angularV17.ngrxVersion
+ backwardCompatibleVersions.angularV18.ngrxVersion
);
expect(packageJson.devDependencies['@ngrx/store-devtools']).toEqual(
- backwardCompatibleVersions.angularV17.ngrxVersion
+ backwardCompatibleVersions.angularV18.ngrxVersion
);
expect(packageJson.devDependencies['jasmine-marbles']).toBeDefined();
});
@@ -710,7 +707,7 @@ export const appRoutes: Routes = [{ path: 'home', component: NxWelcomeComponent
describe('rxjs v6 support', () => {
beforeEach(async () => {
- tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ tree = createTreeWithEmptyWorkspace();
await generateTestApplication(tree, {
directory: 'myapp',
standalone: false,
diff --git a/packages/angular/src/generators/ngrx/schema.json b/packages/angular/src/generators/ngrx/schema.json
index def96bfc43..2033536f18 100644
--- a/packages/angular/src/generators/ngrx/schema.json
+++ b/packages/angular/src/generators/ngrx/schema.json
@@ -8,11 +8,11 @@
"type": "object",
"examples": [
{
- "command": "nx g @nx/angular:ngrx --root --parent=apps/my-app/src/app/app.module.ts --facade=false placeholder",
+ "command": "nx g @nx/angular:ngrx --root --parent=apps/my-app/src/app/app-module.ts --facade=false placeholder",
"description": "Add root ngrx configration to the `my-app` application"
},
{
- "command": "nx g @nx/angular:ngrx --parent=libs/my-lib/src/lib/my-lib.module.ts --facade=true --root=false users",
+ "command": "nx g @nx/angular:ngrx --parent=libs/my-lib/src/lib/my-lib-module.ts --facade=true --root=false users",
"description": "Add a `users` state with a facade to the `my-lib` library. It will be tracked under the default `+state` folder in the lib"
},
{
@@ -42,7 +42,7 @@
},
"parent": {
"type": "string",
- "description": "The path to the file where the state will be registered. For NgModule usage, this will be your `app.module.ts` for your root state, or your Feature Module for feature state. For Standalone API usage, this will be your `app.config.ts` file for your root state, or the Routes definition file for your feature state. The host directory will create/use the new state directory.",
+ "description": "The path to the file where the state will be registered. For NgModule usage, this will be your `app-module.ts` for your root state, or your Feature Module for feature state. For Standalone API usage, this will be your `app.config.ts` file for your root state, or the Routes definition file for your feature state. The host directory will create/use the new state directory.",
"x-prompt": "What is the path to the module or Routes definition where this NgRx state should be registered?",
"x-priority": "important"
},
diff --git a/packages/angular/src/generators/pipe/__snapshots__/pipe.spec.ts.snap b/packages/angular/src/generators/pipe/__snapshots__/pipe.spec.ts.snap
index 8fe79ad3e9..cd9e9c1c03 100644
--- a/packages/angular/src/generators/pipe/__snapshots__/pipe.spec.ts.snap
+++ b/packages/angular/src/generators/pipe/__snapshots__/pipe.spec.ts.snap
@@ -2,7 +2,7 @@
exports[`pipe generator --no-standalone should export the pipe correctly when directory is nested deeper 1`] = `
"import { NgModule } from '@angular/core';
-import { TestPipe } from './my-pipes/test/test.pipe';
+import { TestPipe } from './my-pipes/test/test-pipe';
@NgModule({
imports: [],
declarations: [TestPipe],
@@ -28,7 +28,7 @@ export class TestPipe implements PipeTransform {
`;
exports[`pipe generator --no-standalone should generate a pipe with test files and attach to the NgModule automatically 2`] = `
-"import { TestPipe } from './test.pipe';
+"import { TestPipe } from './test-pipe';
describe('TestPipe', () => {
it('create an instance', () => {
@@ -41,7 +41,7 @@ describe('TestPipe', () => {
exports[`pipe generator --no-standalone should generate a pipe with test files and attach to the NgModule automatically 3`] = `
"import { NgModule } from '@angular/core';
-import { TestPipe } from './test.pipe';
+import { TestPipe } from './test-pipe';
@NgModule({
imports: [],
declarations: [TestPipe],
@@ -67,7 +67,7 @@ export class TestPipe implements PipeTransform {
`;
exports[`pipe generator --no-standalone should import the pipe correctly when files are flat 2`] = `
-"import { TestPipe } from './test.pipe';
+"import { TestPipe } from './test-pipe';
describe('TestPipe', () => {
it('create an instance', () => {
@@ -80,7 +80,7 @@ describe('TestPipe', () => {
exports[`pipe generator --no-standalone should import the pipe correctly when files are flat 3`] = `
"import { NgModule } from '@angular/core';
-import { TestPipe } from './test/test.pipe';
+import { TestPipe } from './test/test-pipe';
@NgModule({
imports: [],
declarations: [TestPipe],
@@ -106,7 +106,7 @@ export class TestPipe implements PipeTransform {
`;
exports[`pipe generator --no-standalone should import the pipe correctly when files are flat but deeply nested 2`] = `
-"import { TestPipe } from './test.pipe';
+"import { TestPipe } from './test-pipe';
describe('TestPipe', () => {
it('create an instance', () => {
@@ -119,7 +119,7 @@ describe('TestPipe', () => {
exports[`pipe generator --no-standalone should import the pipe correctly when files are flat but deeply nested 3`] = `
"import { NgModule } from '@angular/core';
-import { TestPipe } from './my-pipes/test/test.pipe';
+import { TestPipe } from './my-pipes/test/test-pipe';
@NgModule({
imports: [],
declarations: [TestPipe],
@@ -144,7 +144,7 @@ export class TestPipe implements PipeTransform {
`;
exports[`pipe generator should generate correctly 2`] = `
-"import { TestPipe } from './test.pipe';
+"import { TestPipe } from './test-pipe';
describe('TestPipe', () => {
it('create an instance', () => {
diff --git a/packages/angular/src/generators/pipe/lib/normalize-options.ts b/packages/angular/src/generators/pipe/lib/normalize-options.ts
index 9ee2ca660d..ea353088c7 100644
--- a/packages/angular/src/generators/pipe/lib/normalize-options.ts
+++ b/packages/angular/src/generators/pipe/lib/normalize-options.ts
@@ -2,12 +2,16 @@ import type { Tree } from '@nx/devkit';
import { names } from '@nx/devkit';
import { determineArtifactNameAndDirectoryOptions } from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
import { validateClassName } from '../../utils/validations';
+import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
import type { NormalizedSchema, Schema } from '../schema';
export async function normalizeOptions(
tree: Tree,
options: Schema
): Promise {
+ const { major: angularMajorVersion } = getInstalledAngularVersionInfo(tree);
+ options.typeSeparator ??= angularMajorVersion < 20 ? '.' : '-';
+
const {
artifactName: name,
directory,
@@ -18,6 +22,7 @@ export async function normalizeOptions(
name: options.name,
path: options.path,
suffix: 'pipe',
+ suffixSeparator: options.typeSeparator,
allowedFileExtensions: ['ts'],
fileExtension: 'ts',
});
diff --git a/packages/angular/src/generators/pipe/pipe.spec.ts b/packages/angular/src/generators/pipe/pipe.spec.ts
index a06524b165..2c6049713d 100644
--- a/packages/angular/src/generators/pipe/pipe.spec.ts
+++ b/packages/angular/src/generators/pipe/pipe.spec.ts
@@ -1,4 +1,4 @@
-import { addProjectConfiguration, Tree } from '@nx/devkit';
+import { addProjectConfiguration, type Tree, updateJson } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { pipeGenerator } from './pipe';
import type { Schema } from './schema';
@@ -21,12 +21,46 @@ describe('pipe generator', () => {
await generatePipeWithDefaultOptions(tree, { skipFormat: false });
// ASSERT
- expect(tree.read('test/src/app/test.pipe.ts', 'utf-8')).toMatchSnapshot();
+ expect(tree.read('test/src/app/test-pipe.ts', 'utf-8')).toMatchSnapshot();
expect(
- tree.read('test/src/app/test.pipe.spec.ts', 'utf-8')
+ tree.read('test/src/app/test-pipe.spec.ts', 'utf-8')
).toMatchSnapshot();
});
+ it('should generate files with the provided type separator', async () => {
+ await generatePipeWithDefaultOptions(tree, {
+ typeSeparator: '.',
+ skipFormat: false,
+ });
+
+ expect(tree.read('test/src/app/test.pipe.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { Pipe, PipeTransform } from '@angular/core';
+
+ @Pipe({
+ name: 'test',
+ })
+ export class TestPipe implements PipeTransform {
+ transform(value: unknown, ...args: unknown[]): unknown {
+ return null;
+ }
+ }
+ "
+ `);
+ expect(tree.read('test/src/app/test.pipe.spec.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { TestPipe } from './test.pipe';
+
+ describe('TestPipe', () => {
+ it('create an instance', () => {
+ const pipe = new TestPipe();
+ expect(pipe).toBeTruthy();
+ });
+ });
+ "
+ `);
+ });
+
it('should handle path with file extension', async () => {
await generatePipeWithDefaultOptions(tree, {
path: 'test/src/app/test.pipe.ts',
@@ -47,7 +81,7 @@ describe('pipe generator', () => {
await generatePipeWithDefaultOptions(tree, { standalone: true });
// ASSERT
- expect(tree.read('test/src/app/test.module.ts', 'utf-8')).not.toContain(
+ expect(tree.read('test/src/app/test-module.ts', 'utf-8')).not.toContain(
'TestPipe'
);
});
@@ -61,7 +95,7 @@ describe('pipe generator', () => {
// ASSERT
expect(
- tree.exists('test/src/app/my-pipes/test/test.pipe.spec.ts')
+ tree.exists('test/src/app/my-pipes/test/test-pipe.spec.ts')
).toBeFalsy();
});
@@ -86,12 +120,12 @@ describe('pipe generator', () => {
});
// ASSERT
- expect(tree.read('test/src/app/test.pipe.ts', 'utf-8')).toMatchSnapshot();
+ expect(tree.read('test/src/app/test-pipe.ts', 'utf-8')).toMatchSnapshot();
expect(
- tree.read('test/src/app/test.pipe.spec.ts', 'utf-8')
+ tree.read('test/src/app/test-pipe.spec.ts', 'utf-8')
).toMatchSnapshot();
expect(
- tree.read('test/src/app/test.module.ts', 'utf-8')
+ tree.read('test/src/app/test-module.ts', 'utf-8')
).toMatchSnapshot();
});
@@ -106,13 +140,13 @@ describe('pipe generator', () => {
// ASSERT
expect(
- tree.read('test/src/app/test/test.pipe.ts', 'utf-8')
+ tree.read('test/src/app/test/test-pipe.ts', 'utf-8')
).toMatchSnapshot();
expect(
- tree.read('test/src/app/test/test.pipe.spec.ts', 'utf-8')
+ tree.read('test/src/app/test/test-pipe.spec.ts', 'utf-8')
).toMatchSnapshot();
expect(
- tree.read('test/src/app/test.module.ts', 'utf-8')
+ tree.read('test/src/app/test-module.ts', 'utf-8')
).toMatchSnapshot();
});
@@ -127,13 +161,13 @@ describe('pipe generator', () => {
// ASSERT
expect(
- tree.read('test/src/app/my-pipes/test/test.pipe.ts', 'utf-8')
+ tree.read('test/src/app/my-pipes/test/test-pipe.ts', 'utf-8')
).toMatchSnapshot();
expect(
- tree.read('test/src/app/my-pipes/test/test.pipe.spec.ts', 'utf-8')
+ tree.read('test/src/app/my-pipes/test/test-pipe.spec.ts', 'utf-8')
).toMatchSnapshot();
expect(
- tree.read('test/src/app/test.module.ts', 'utf-8')
+ tree.read('test/src/app/test-module.ts', 'utf-8')
).toMatchSnapshot();
});
@@ -149,7 +183,7 @@ describe('pipe generator', () => {
// ASSERT
expect(
- tree.read('test/src/app/test.module.ts', 'utf-8')
+ tree.read('test/src/app/test-module.ts', 'utf-8')
).toMatchSnapshot();
});
@@ -164,16 +198,57 @@ describe('pipe generator', () => {
});
// ASSERT
- expect(tree.read('test/src/app/test.module.ts', 'utf-8')).not.toContain(
+ expect(tree.read('test/src/app/test-module.ts', 'utf-8')).not.toContain(
'TestPipe'
);
});
});
+
+ describe('compat', () => {
+ it('should generate the files with the "." type separator for versions below v20', async () => {
+ updateJson(tree, 'package.json', (json) => {
+ json.dependencies = {
+ ...json.dependencies,
+ '@angular/core': '~19.2.0',
+ };
+ return json;
+ });
+
+ await generatePipeWithDefaultOptions(tree, { skipFormat: false });
+
+ expect(tree.read('test/src/app/test.pipe.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { Pipe, PipeTransform } from '@angular/core';
+
+ @Pipe({
+ name: 'test',
+ })
+ export class TestPipe implements PipeTransform {
+ transform(value: unknown, ...args: unknown[]): unknown {
+ return null;
+ }
+ }
+ "
+ `);
+ expect(tree.read('test/src/app/test.pipe.spec.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { TestPipe } from './test.pipe';
+
+ describe('TestPipe', () => {
+ it('create an instance', () => {
+ const pipe = new TestPipe();
+ expect(pipe).toBeTruthy();
+ });
+ });
+ "
+ `);
+ });
+ });
});
function addModule(tree: Tree) {
tree.write(
- 'test/src/app/test.module.ts',
+ 'test/src/app/test-module.ts',
`import { NgModule } from '@angular/core';
@NgModule({
imports: [],
diff --git a/packages/angular/src/generators/pipe/pipe.ts b/packages/angular/src/generators/pipe/pipe.ts
index 8b8ca40eb7..93fcaa7964 100644
--- a/packages/angular/src/generators/pipe/pipe.ts
+++ b/packages/angular/src/generators/pipe/pipe.ts
@@ -25,7 +25,7 @@ export async function pipeGenerator(tree: Tree, rawOptions: Schema) {
fileName: options.fileName,
selector: pipeNames.propertyName,
standalone: options.standalone,
- // Angular v19 or higher defaults to true, while v18 or lower defaults to false
+ // Angular v19 or higher defaults to true, while lower versions default to false
setStandalone:
(angularMajorVersion >= 19 && !options.standalone) ||
(angularMajorVersion < 19 && options.standalone),
diff --git a/packages/angular/src/generators/pipe/schema.d.ts b/packages/angular/src/generators/pipe/schema.d.ts
index 15d061553d..8e7c61c16b 100644
--- a/packages/angular/src/generators/pipe/schema.d.ts
+++ b/packages/angular/src/generators/pipe/schema.d.ts
@@ -6,6 +6,7 @@ export interface Schema {
standalone?: boolean;
module?: string;
export?: boolean;
+ typeSeparator?: string;
skipFormat?: boolean;
}
diff --git a/packages/angular/src/generators/pipe/schema.json b/packages/angular/src/generators/pipe/schema.json
index a3769b07c7..07ac833265 100644
--- a/packages/angular/src/generators/pipe/schema.json
+++ b/packages/angular/src/generators/pipe/schema.json
@@ -12,11 +12,15 @@
"command": "nx g @nx/angular:pipe mylib/src/lib/foo.pipe.ts"
},
{
- "description": "Generate a pipe without providing the file extension. It results in the pipe `FooPipe` at `mylib/src/lib/foo.pipe.ts`",
+ "description": "Generate a pipe without providing the file extension. It results in the pipe `FooPipe` at `mylib/src/lib/foo-pipe.ts`",
"command": "nx g @nx/angular:pipe mylib/src/lib/foo"
},
{
- "description": "Generate a pipe with the exported symbol different from the file name. It results in the pipe `CustomPipe` at `mylib/src/lib/foo.pipe.ts`",
+ "description": "Generate a pipe with a different type separator. It results in the pipe `FooPipe` at `mylib/src/lib/foo.pipe.ts`",
+ "command": "nx g @nx/angular:pipe mylib/src/lib/foo --typeSeparator=."
+ },
+ {
+ "description": "Generate a pipe with the exported symbol different from the file name. It results in the pipe `CustomPipe` at `mylib/src/lib/foo-pipe.ts`",
"command": "nx g @nx/angular:pipe mylib/src/lib/foo --name=custom"
}
],
@@ -59,6 +63,11 @@
"default": false,
"description": "The declaring NgModule exports this pipe."
},
+ "typeSeparator": {
+ "type": "string",
+ "enum": ["-", "."],
+ "description": "The separator character to use before the type within the generated file's name. For example, if you set the option to `.`, the file will be named `example.pipe.ts`. It defaults to '-' for Angular v20+. For versions below v20, it defaults to '.'."
+ },
"skipFormat": {
"type": "boolean",
"default": false,
diff --git a/packages/angular/src/generators/remote/__snapshots__/remote.spec.ts.snap b/packages/angular/src/generators/remote/__snapshots__/remote.spec.ts.snap
index 206398909b..38c7f6a23f 100644
--- a/packages/angular/src/generators/remote/__snapshots__/remote.spec.ts.snap
+++ b/packages/angular/src/generators/remote/__snapshots__/remote.spec.ts.snap
@@ -4,10 +4,10 @@ exports[`MF Remote App Generator --ssr should generate the correct files 1`] = `
"import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
-import { AppComponent } from './app.component';
+import { App } from './app';
@NgModule({
- declarations: [AppComponent],
+ declarations: [App],
imports: [
BrowserModule,
RouterModule.forRoot(
@@ -15,7 +15,7 @@ import { AppComponent } from './app.component';
{
path: '',
loadChildren: () =>
- import('./remote-entry/entry.module').then(
+ import('./remote-entry/entry-module').then(
(m) => m.RemoteEntryModule
),
},
@@ -24,17 +24,17 @@ import { AppComponent } from './app.component';
),
],
providers: [],
- bootstrap: [AppComponent],
+ bootstrap: [App],
})
export class AppModule {}
"
`;
exports[`MF Remote App Generator --ssr should generate the correct files 2`] = `
-"import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
-import { AppModule } from './app/app.module';
+"import { platformBrowser } from '@angular/platform-browser';
+import { AppModule } from './app/app-module';
-platformBrowserDynamic()
+platformBrowser()
.bootstrapModule(AppModule, {
ngZoneEventCoalescing: true,
})
@@ -136,7 +136,7 @@ exports[`MF Remote App Generator --ssr should generate the correct files 6`] = `
module.exports = {
name: 'test',
exposes: {
- './Module': 'test/src/app/remote-entry/entry.module.ts',
+ './Module': 'test/src/app/remote-entry/entry-module.ts',
},
};
"
@@ -163,7 +163,7 @@ exports[`MF Remote App Generator --ssr should generate the correct files 8`] = `
standalone: false,
template: \`\`,
})
-export class RemoteEntryComponent {}
+export class RemoteEntry {}
"
`;
@@ -174,7 +174,7 @@ export const appRoutes: Route[] = [
{
path: '',
loadChildren: () =>
- import('./remote-entry/entry.module').then((m) => m.RemoteEntryModule),
+ import('./remote-entry/entry-module').then((m) => m.RemoteEntryModule),
},
];
"
@@ -182,11 +182,9 @@ export const appRoutes: Route[] = [
exports[`MF Remote App Generator --ssr should generate the correct files 10`] = `
"import { Route } from '@angular/router';
-import { RemoteEntryComponent } from './entry.component';
+import { RemoteEntry } from './entry';
-export const remoteRoutes: Route[] = [
- { path: '', component: RemoteEntryComponent },
-];
+export const remoteRoutes: Route[] = [{ path: '', component: RemoteEntry }];
"
`;
@@ -221,11 +219,9 @@ exports[`MF Remote App Generator --ssr should generate the correct files 11`] =
exports[`MF Remote App Generator --ssr should generate the correct files 12`] = `
"import { Route } from '@angular/router';
-import { RemoteEntryComponent } from './entry.component';
+import { RemoteEntry } from './entry';
-export const remoteRoutes: Route[] = [
- { path: '', component: RemoteEntryComponent },
-];
+export const remoteRoutes: Route[] = [{ path: '', component: RemoteEntry }];
"
`;
@@ -246,28 +242,28 @@ exports[`MF Remote App Generator --ssr should generate the correct files when --
"import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
-import { AppComponent } from './app.component';
+import { App } from './app';
@NgModule({
- declarations: [AppComponent],
+ declarations: [App],
imports: [
BrowserModule,
RouterModule.forRoot([{
path: '',
- loadChildren: () => import('./remote-entry/entry.module').then(m => m.RemoteEntryModule)
+ loadChildren: () => import('./remote-entry/entry-module').then(m => m.RemoteEntryModule)
}], { initialNavigation: 'enabledBlocking' }),
],
providers: [],
- bootstrap: [AppComponent],
+ bootstrap: [App],
})
export class AppModule {}"
`;
exports[`MF Remote App Generator --ssr should generate the correct files when --typescriptConfiguration=true 2`] = `
-"import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
-import { AppModule } from './app/app.module';
+"import { platformBrowser } from '@angular/platform-browser';
+import { AppModule } from './app/app-module';
-platformBrowserDynamic()
+platformBrowser()
.bootstrapModule(AppModule, {
ngZoneEventCoalescing: true,
})
@@ -365,7 +361,7 @@ exports[`MF Remote App Generator --ssr should generate the correct files when --
const config: ModuleFederationConfig = {
name: 'test',
exposes: {
- './Module': 'test/src/app/remote-entry/entry.module.ts',
+ './Module': 'test/src/app/remote-entry/entry-module.ts',
},
};
@@ -397,7 +393,7 @@ exports[`MF Remote App Generator --ssr should generate the correct files when --
standalone: false,
template: \`\`
})
-export class RemoteEntryComponent {}
+export class RemoteEntry {}
"
`;
@@ -405,15 +401,15 @@ exports[`MF Remote App Generator --ssr should generate the correct files when --
"import { Route } from '@angular/router';
export const appRoutes: Route[] = [
- { path: '', loadChildren: () => import('./remote-entry/entry.module').then(m => m.RemoteEntryModule) },];
+ { path: '', loadChildren: () => import('./remote-entry/entry-module').then(m => m.RemoteEntryModule) },];
"
`;
exports[`MF Remote App Generator --ssr should generate the correct files when --typescriptConfiguration=true 10`] = `
"import { Route } from '@angular/router';
-import { RemoteEntryComponent } from './entry.component';
+import { RemoteEntry } from './entry';
-export const remoteRoutes: Route[] = [{ path: '', component: RemoteEntryComponent }];"
+export const remoteRoutes: Route[] = [{ path: '', component: RemoteEntry }];"
`;
exports[`MF Remote App Generator --ssr should generate the correct files when --typescriptConfiguration=true 11`] = `
@@ -447,9 +443,9 @@ exports[`MF Remote App Generator --ssr should generate the correct files when --
exports[`MF Remote App Generator --ssr should generate the correct files when --typescriptConfiguration=true 12`] = `
"import { Route } from '@angular/router';
-import { RemoteEntryComponent } from './entry.component';
+import { RemoteEntry } from './entry';
-export const remoteRoutes: Route[] = [{ path: '', component: RemoteEntryComponent }];"
+export const remoteRoutes: Route[] = [{ path: '', component: RemoteEntry }];"
`;
exports[`MF Remote App Generator --ssr should generate the correct files when --typescriptConfiguration=true 13`] = `
@@ -546,11 +542,9 @@ export default withModuleFederation(config, { dts: false });
exports[`MF Remote App Generator should generate the a remote setup for standalone components 1`] = `
"import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
-import { RemoteEntryComponent } from './app/remote-entry/entry.component';
+import { RemoteEntry } from './app/remote-entry/entry';
-bootstrapApplication(RemoteEntryComponent, appConfig).catch((err) =>
- console.error(err)
-);
+bootstrapApplication(RemoteEntry, appConfig).catch((err) => console.error(err));
"
`;
@@ -570,14 +564,14 @@ module.exports = {
exports[`MF Remote App Generator should generate the a remote setup for standalone components 3`] = `
"import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
-import { NxWelcomeComponent } from './nx-welcome.component';
+import { NxWelcome } from './nx-welcome';
@Component({
- imports: [CommonModule, NxWelcomeComponent],
+ imports: [CommonModule, NxWelcome],
selector: 'app-test-entry',
template: \`\`,
})
-export class RemoteEntryComponent {}
+export class RemoteEntry {}
"
`;
@@ -596,20 +590,18 @@ export const appRoutes: Route[] = [
exports[`MF Remote App Generator should generate the a remote setup for standalone components 5`] = `
"import { Route } from '@angular/router';
-import { RemoteEntryComponent } from './entry.component';
+import { RemoteEntry } from './entry';
-export const remoteRoutes: Route[] = [
- { path: '', component: RemoteEntryComponent },
-];
+export const remoteRoutes: Route[] = [{ path: '', component: RemoteEntry }];
"
`;
exports[`MF Remote App Generator should generate the a remote setup for standalone components when --typescriptConfiguration=true 1`] = `
"import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
-import { RemoteEntryComponent } from './app/remote-entry/entry.component';
+import { RemoteEntry } from './app/remote-entry/entry';
-bootstrapApplication(RemoteEntryComponent, appConfig).catch((err) =>
+bootstrapApplication(RemoteEntry, appConfig).catch((err) =>
console.error(err)
);
"
@@ -635,14 +627,14 @@ export default config;
exports[`MF Remote App Generator should generate the a remote setup for standalone components when --typescriptConfiguration=true 3`] = `
"import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
-import { NxWelcomeComponent } from './nx-welcome.component';
+import { NxWelcome } from './nx-welcome';
@Component({
- imports: [CommonModule, NxWelcomeComponent],
+ imports: [CommonModule, NxWelcome],
selector: 'app-test-entry',
template: \`\`
})
-export class RemoteEntryComponent {}
+export class RemoteEntry {}
"
`;
@@ -656,7 +648,7 @@ export const appRoutes: Route[] = [
exports[`MF Remote App Generator should generate the a remote setup for standalone components when --typescriptConfiguration=true 5`] = `
"import { Route } from '@angular/router';
-import { RemoteEntryComponent } from './entry.component';
+import { RemoteEntry } from './entry';
-export const remoteRoutes: Route[] = [{ path: '', component: RemoteEntryComponent }];"
+export const remoteRoutes: Route[] = [{ path: '', component: RemoteEntry }];"
`;
diff --git a/packages/angular/src/generators/remote/files/standalone/src/bootstrap.server.ts__tmpl__ b/packages/angular/src/generators/remote/files/standalone/src/bootstrap.server.ts__tmpl__
index 93b6fc501d..8d29dce84f 100644
--- a/packages/angular/src/generators/remote/files/standalone/src/bootstrap.server.ts__tmpl__
+++ b/packages/angular/src/generators/remote/files/standalone/src/bootstrap.server.ts__tmpl__
@@ -1,7 +1,7 @@
-import {bootstrapApplication} from '@angular/platform-browser';
-import {RemoteEntryComponent} from './app/remote-entry/entry.component';
-import {config} from './app/app.config.server';
+import { bootstrapApplication } from '@angular/platform-browser';
+import { RemoteEntry<%= componentType %> } from './app/remote-entry/entry<%= componentFileSuffix %>';
+import { config } from './app/app.config.server';
-const bootstrap = () => bootstrapApplication(RemoteEntryComponent, config);
+const bootstrap = () => bootstrapApplication(RemoteEntry<%= componentType %>, config);
export default bootstrap;
diff --git a/packages/angular/src/generators/remote/lib/update-ssr-setup.ts b/packages/angular/src/generators/remote/lib/update-ssr-setup.ts
index e8c1459338..8fd6fc3eb8 100644
--- a/packages/angular/src/generators/remote/lib/update-ssr-setup.ts
+++ b/packages/angular/src/generators/remote/lib/update-ssr-setup.ts
@@ -3,6 +3,7 @@ import {
addDependenciesToPackageJson,
generateFiles,
joinPathFragments,
+ names,
readProjectConfiguration,
updateProjectConfiguration,
} from '@nx/devkit';
@@ -12,6 +13,7 @@ import {
moduleFederationNodeVersion,
typesCorsVersion,
} from '../../../utils/versions';
+import { getComponentType } from '../../utils/artifact-types';
import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
export async function updateSsrSetup(
@@ -77,6 +79,9 @@ export async function updateSsrSetup(
);
if (standalone) {
+ const componentType = getComponentType(tree);
+ const componentFileSuffix = componentType ? `.${componentType}` : '';
+
generateFiles(
tree,
joinPathFragments(__dirname, '../files/standalone'),
@@ -84,6 +89,8 @@ export async function updateSsrSetup(
{
appName,
standalone,
+ componentType: componentType ? names(componentType).className : '',
+ componentFileSuffix,
tmpl: '',
}
);
diff --git a/packages/angular/src/generators/remote/remote.spec.ts b/packages/angular/src/generators/remote/remote.spec.ts
index 5647dc6ceb..ccc91e3b0f 100644
--- a/packages/angular/src/generators/remote/remote.spec.ts
+++ b/packages/angular/src/generators/remote/remote.spec.ts
@@ -1,24 +1,25 @@
import 'nx/src/internal-testing-utils/mock-project-graph';
-import { E2eTestRunner } from '../../utils/test-runners';
import {
getProjects,
readJson,
readNxJson,
readProjectConfiguration,
updateJson,
+ updateNxJson,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
+import { getRootTsConfigPathInTree } from '@nx/js';
+import { E2eTestRunner } from '../../utils/test-runners';
import {
generateTestHostApplication,
generateTestRemoteApplication,
} from '../utils/testing';
-import { getRootTsConfigPathInTree } from '@nx/js';
describe('MF Remote App Generator', () => {
it('should generate a remote mf app with no host', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
// ACT
await generateTestRemoteApplication(tree, {
@@ -32,6 +33,65 @@ describe('MF Remote App Generator', () => {
// ASSERT
expect(tree.read('test/webpack.config.js', 'utf-8')).toMatchSnapshot();
const tsconfigJson = readJson(tree, getRootTsConfigPathInTree(tree));
+ expect(tsconfigJson.compilerOptions.paths['test/Module']).toEqual([
+ 'test/src/app/remote-entry/entry-module.ts',
+ ]);
+ });
+
+ it('should generate the module file with the "typeSeparator" generator default', async () => {
+ const tree = createTreeWithEmptyWorkspace();
+ const nxJson = readNxJson(tree);
+ nxJson.generators = {
+ ...nxJson.generators,
+ '@nx/angular:module': {
+ typeSeparator: '.',
+ },
+ };
+ updateNxJson(tree, nxJson);
+
+ await generateTestRemoteApplication(tree, {
+ directory: 'test',
+ port: 4201,
+ typescriptConfiguration: false,
+ standalone: false,
+ skipFormat: true,
+ });
+
+ expect(tree.read('test/module-federation.config.js', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "/**
+ * Nx requires a default export of the config to allow correct resolution of the module federation graph.
+ **/
+ module.exports = {
+ name: 'test',
+ exposes: {
+ './Module': 'test/src/app/remote-entry/entry.module.ts',
+ },
+ };
+ "
+ `);
+ expect(tree.read(`test/src/app/app.module.ts`, 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { NgModule } from '@angular/core';
+ import { BrowserModule } from '@angular/platform-browser';
+ import { RouterModule } from '@angular/router';
+ import { App } from './app';
+
+ @NgModule({
+ declarations: [App],
+ imports: [
+ BrowserModule,
+ RouterModule.forRoot([{
+ path: '',
+ loadChildren: () => import('./remote-entry/entry.module').then(m => m.RemoteEntryModule)
+ }], { initialNavigation: 'enabledBlocking' }),
+ ],
+ providers: [],
+ bootstrap: [App],
+ })
+ export class AppModule {}"
+ `);
+ const tsconfigJson = readJson(tree, getRootTsConfigPathInTree(tree));
expect(tsconfigJson.compilerOptions.paths['test/Module']).toEqual([
'test/src/app/remote-entry/entry.module.ts',
]);
@@ -39,7 +99,7 @@ describe('MF Remote App Generator', () => {
it('should generate a remote mf app with no host when --typescriptConfiguration=true', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
// ACT
await generateTestRemoteApplication(tree, {
@@ -56,7 +116,7 @@ describe('MF Remote App Generator', () => {
it('should generate a remote mf app with a host', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
await generateTestHostApplication(tree, {
directory: 'host',
@@ -81,7 +141,7 @@ describe('MF Remote App Generator', () => {
it('should generate a remote mf app with a host when --typescriptConfiguration=true', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
await generateTestHostApplication(tree, {
directory: 'host',
@@ -106,7 +166,7 @@ describe('MF Remote App Generator', () => {
it('should error when a remote app is attempted to be generated with an incorrect host', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
// ACT
try {
@@ -126,7 +186,7 @@ describe('MF Remote App Generator', () => {
it('should generate a remote mf app and automatically find the next port available', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
await generateTestRemoteApplication(tree, {
directory: 'existing',
port: 4201,
@@ -148,7 +208,7 @@ describe('MF Remote App Generator', () => {
it('should generate a remote mf app and automatically find the next port available even when there are no other targets', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
// ACT
await generateTestRemoteApplication(tree, {
@@ -164,7 +224,7 @@ describe('MF Remote App Generator', () => {
it('should not set the remote as the default project', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
// ACT
await generateTestRemoteApplication(tree, {
@@ -181,7 +241,7 @@ describe('MF Remote App Generator', () => {
it('should generate the a remote setup for standalone components', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
// ACT
await generateTestRemoteApplication(tree, {
@@ -190,17 +250,17 @@ describe('MF Remote App Generator', () => {
});
// ASSERT
- expect(tree.exists(`test/src/app/app.module.ts`)).toBeFalsy();
- expect(tree.exists(`test/src/app/app.component.ts`)).toBeFalsy();
+ expect(tree.exists(`test/src/app/app-module.ts`)).toBeFalsy();
+ expect(tree.exists(`test/src/app/app.ts`)).toBeFalsy();
expect(
- tree.exists(`test/src/app/remote-entry/entry.module.ts`)
+ tree.exists(`test/src/app/remote-entry/entry-module.ts`)
).toBeFalsy();
expect(tree.read(`test/src/bootstrap.ts`, 'utf-8')).toMatchSnapshot();
expect(
tree.read(`test/module-federation.config.js`, 'utf-8')
).toMatchSnapshot();
expect(
- tree.read(`test/src/app/remote-entry/entry.component.ts`, 'utf-8')
+ tree.read(`test/src/app/remote-entry/entry.ts`, 'utf-8')
).toMatchSnapshot();
expect(tree.read(`test/src/app/app.routes.ts`, 'utf-8')).toMatchSnapshot();
expect(
@@ -214,7 +274,7 @@ describe('MF Remote App Generator', () => {
it('should generate the a remote setup for standalone components when --typescriptConfiguration=true', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
// ACT
await generateTestRemoteApplication(tree, {
@@ -224,17 +284,17 @@ describe('MF Remote App Generator', () => {
});
// ASSERT
- expect(tree.exists(`test/src/app/app.module.ts`)).toBeFalsy();
- expect(tree.exists(`test/src/app/app.component.ts`)).toBeFalsy();
+ expect(tree.exists(`test/src/app/app-module.ts`)).toBeFalsy();
+ expect(tree.exists(`test/src/app/app.ts`)).toBeFalsy();
expect(
- tree.exists(`test/src/app/remote-entry/entry.module.ts`)
+ tree.exists(`test/src/app/remote-entry/entry-module.ts`)
).toBeFalsy();
expect(tree.read(`test/src/bootstrap.ts`, 'utf-8')).toMatchSnapshot();
expect(
tree.read(`test/module-federation.config.ts`, 'utf-8')
).toMatchSnapshot();
expect(
- tree.read(`test/src/app/remote-entry/entry.component.ts`, 'utf-8')
+ tree.read(`test/src/app/remote-entry/entry.ts`, 'utf-8')
).toMatchSnapshot();
expect(tree.read(`test/src/app/app.routes.ts`, 'utf-8')).toMatchSnapshot();
expect(
@@ -244,7 +304,7 @@ describe('MF Remote App Generator', () => {
it('should not generate an e2e project when e2eTestRunner is none', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
// ACT
await generateTestRemoteApplication(tree, {
@@ -261,7 +321,7 @@ describe('MF Remote App Generator', () => {
it('should generate a correct app component when inline template is used', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
// ACT
await generateTestRemoteApplication(tree, {
@@ -272,8 +332,7 @@ describe('MF Remote App Generator', () => {
});
// ASSERT
- expect(tree.read('test/src/app/app.component.ts', 'utf-8'))
- .toMatchInlineSnapshot(`
+ expect(tree.read('test/src/app/app.ts', 'utf-8')).toMatchInlineSnapshot(`
"import { Component } from '@angular/core';
@Component({
@@ -282,13 +341,13 @@ describe('MF Remote App Generator', () => {
template: ''
})
- export class AppComponent {}"
+ export class App {}"
`);
});
it('should update the index.html to use the remote entry component selector for root when standalone', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
// ACT
await generateTestRemoteApplication(tree, {
@@ -306,7 +365,7 @@ describe('MF Remote App Generator', () => {
describe('--ssr', () => {
it('should generate the correct files', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
// ACT
await generateTestRemoteApplication(tree, {
@@ -319,10 +378,10 @@ describe('MF Remote App Generator', () => {
// ASSERT
const project = readProjectConfiguration(tree, 'test');
expect(
- tree.exists(`test/src/app/remote-entry/entry.module.ts`)
+ tree.exists(`test/src/app/remote-entry/entry-module.ts`)
).toBeTruthy();
expect(
- tree.read(`test/src/app/app.module.ts`, 'utf-8')
+ tree.read(`test/src/app/app-module.ts`, 'utf-8')
).toMatchSnapshot();
expect(tree.read(`test/src/bootstrap.ts`, 'utf-8')).toMatchSnapshot();
expect(
@@ -337,7 +396,7 @@ describe('MF Remote App Generator', () => {
tree.read(`test/webpack.server.config.js`, 'utf-8')
).toMatchSnapshot();
expect(
- tree.read(`test/src/app/remote-entry/entry.component.ts`, 'utf-8')
+ tree.read(`test/src/app/remote-entry/entry.ts`, 'utf-8')
).toMatchSnapshot();
expect(
tree.read(`test/src/app/app.routes.ts`, 'utf-8')
@@ -354,7 +413,7 @@ describe('MF Remote App Generator', () => {
it('should generate the correct files when --typescriptConfiguration=true', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
// ACT
await generateTestRemoteApplication(tree, {
@@ -368,10 +427,10 @@ describe('MF Remote App Generator', () => {
// ASSERT
const project = readProjectConfiguration(tree, 'test');
expect(
- tree.exists(`test/src/app/remote-entry/entry.module.ts`)
+ tree.exists(`test/src/app/remote-entry/entry-module.ts`)
).toBeTruthy();
expect(
- tree.read(`test/src/app/app.module.ts`, 'utf-8')
+ tree.read(`test/src/app/app-module.ts`, 'utf-8')
).toMatchSnapshot();
expect(tree.read(`test/src/bootstrap.ts`, 'utf-8')).toMatchSnapshot();
expect(
@@ -386,7 +445,7 @@ describe('MF Remote App Generator', () => {
tree.read(`test/webpack.server.config.ts`, 'utf-8')
).toMatchSnapshot();
expect(
- tree.read(`test/src/app/remote-entry/entry.component.ts`, 'utf-8')
+ tree.read(`test/src/app/remote-entry/entry.ts`, 'utf-8')
).toMatchSnapshot();
expect(
tree.read(`test/src/app/app.routes.ts`, 'utf-8')
@@ -403,7 +462,7 @@ describe('MF Remote App Generator', () => {
});
it('should not touch the package.json when run with `--skipPackageJson`', async () => {
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
let initialPackageJson;
updateJson(tree, 'package.json', (json) => {
json.dependencies = {};
@@ -426,7 +485,7 @@ describe('MF Remote App Generator', () => {
});
it('should error when an invalid remote name is passed to the remote generator', async () => {
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
await expect(
generateTestRemoteApplication(tree, {
@@ -439,4 +498,128 @@ describe('MF Remote App Generator', () => {
The regular expression used is ^[a-zA-Z_$][a-zA-Z_$0-9]*$.]
`);
});
+
+ describe('compat', () => {
+ it('should generate components with the "component" type for versions lower than v20', async () => {
+ const tree = createTreeWithEmptyWorkspace();
+ updateJson(tree, 'package.json', (json) => {
+ json.dependencies = {
+ ...json.dependencies,
+ '@angular/core': '~19.2.0',
+ };
+ return json;
+ });
+ await generateTestHostApplication(tree, {
+ directory: 'host',
+ typescriptConfiguration: false,
+ skipFormat: true,
+ });
+
+ await generateTestRemoteApplication(tree, {
+ directory: 'test',
+ host: 'host',
+ typescriptConfiguration: false,
+ skipFormat: true,
+ });
+
+ expect(tree.read('host/src/app/app.component.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { Component } from '@angular/core';
+ import { RouterModule } from '@angular/router';
+ import { NxWelcomeComponent } from './nx-welcome.component';
+
+ @Component({
+ imports: [NxWelcomeComponent, RouterModule],
+ selector: 'app-root',
+ templateUrl: './app.component.html',
+ styleUrl: './app.component.css',
+ })
+ export class AppComponent {
+ title = 'host';
+ }
+ "
+ `);
+ expect(tree.read('test/src/app/remote-entry/entry.component.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { Component } from '@angular/core';
+ import { CommonModule } from '@angular/common';
+ import { NxWelcomeComponent } from './nx-welcome.component';
+
+ @Component({
+ imports: [CommonModule, NxWelcomeComponent],
+ selector: 'app-test-entry',
+ template: \`\`
+ })
+ export class RemoteEntryComponent {}
+ "
+ `);
+ expect(tree.read('test/src/app/remote-entry/entry.routes.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { Route } from '@angular/router';
+ import { RemoteEntryComponent } from './entry.component';
+
+ export const remoteRoutes: Route[] = [{ path: '', component: RemoteEntryComponent }];"
+ `);
+ });
+
+ it('should generate modules with the "." type separator for versions lower than v20', async () => {
+ const tree = createTreeWithEmptyWorkspace();
+ updateJson(tree, 'package.json', (json) => {
+ json.dependencies = {
+ ...json.dependencies,
+ '@angular/core': '~19.2.0',
+ };
+ return json;
+ });
+
+ await generateTestRemoteApplication(tree, {
+ directory: 'test',
+ standalone: false,
+ typescriptConfiguration: false,
+ skipFormat: true,
+ });
+
+ expect(tree.read('test/src/app/app.module.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { NgModule } from '@angular/core';
+ import { BrowserModule } from '@angular/platform-browser';
+ import { RouterModule } from '@angular/router';
+ import { AppComponent } from './app.component';
+
+ @NgModule({
+ declarations: [AppComponent],
+ imports: [
+ BrowserModule,
+ RouterModule.forRoot([{
+ path: '',
+ loadChildren: () => import('./remote-entry/entry.module').then(m => m.RemoteEntryModule)
+ }], { initialNavigation: 'enabledBlocking' }),
+ ],
+ providers: [],
+ bootstrap: [AppComponent],
+ })
+ export class AppModule {}"
+ `);
+ expect(tree.read('test/src/app/remote-entry/entry.module.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { NgModule } from '@angular/core';
+ import { CommonModule } from '@angular/common';
+ import { RouterModule } from '@angular/router';
+
+ import { RemoteEntryComponent } from './entry.component';
+ import { NxWelcomeComponent } from './nx-welcome.component';
+ import { remoteRoutes } from './entry.routes';
+
+ @NgModule({
+ declarations: [RemoteEntryComponent, NxWelcomeComponent],
+ imports: [
+ CommonModule,
+ RouterModule.forChild(remoteRoutes),
+ ],
+ providers: [],
+ })
+ export class RemoteEntryModule {}"
+ `);
+ });
+ });
});
diff --git a/packages/angular/src/generators/remote/schema.json b/packages/angular/src/generators/remote/schema.json
index 252147d9de..d98914e253 100644
--- a/packages/angular/src/generators/remote/schema.json
+++ b/packages/angular/src/generators/remote/schema.json
@@ -171,7 +171,7 @@
"default": false
},
"serverRouting": {
- "description": "Creates a server application using the Server Routing and App Engine APIs (Developer Preview). _Note: this is only supported in Angular versions >= 19.0.0_.",
+ "description": "Creates a server application using the Server Routing and App Engine APIs for application using the `application` builder (Developer Preview). _Note: this is only supported in Angular versions 19.x.x_. From Angular 20 onwards, SSR will always enable server routing when using the `application` builder.",
"type": "boolean"
},
"typescriptConfiguration": {
diff --git a/packages/angular/src/generators/scam-directive/lib/convert-directive-to-scam.spec.ts b/packages/angular/src/generators/scam-directive/lib/convert-directive-to-scam.spec.ts
index fe015a7d1b..65787b41c1 100644
--- a/packages/angular/src/generators/scam-directive/lib/convert-directive-to-scam.spec.ts
+++ b/packages/angular/src/generators/scam-directive/lib/convert-directive-to-scam.spec.ts
@@ -6,7 +6,7 @@ import { convertDirectiveToScam } from './convert-directive-to-scam';
describe('convertDirectiveToScam', () => {
it('should create the scam directive inline correctly', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -26,18 +26,19 @@ describe('convertDirectiveToScam', () => {
convertDirectiveToScam(tree, {
path: 'apps/app1/src/app/example/example',
directory: 'apps/app1/src/app/example',
- fileName: 'example.directive',
- filePath: 'apps/app1/src/app/example/example.directive.ts',
+ fileName: 'example',
+ filePath: 'apps/app1/src/app/example/example.ts',
name: 'example',
projectName: 'app1',
+ modulePath: 'apps/app1/src/app/example/example-module.ts',
export: false,
inlineScam: true,
- symbolName: 'ExampleDirective',
+ symbolName: 'Example',
});
// ASSERT
const directiveSource = tree.read(
- 'apps/app1/src/app/example/example.directive.ts',
+ 'apps/app1/src/app/example/example.ts',
'utf-8'
);
expect(directiveSource).toMatchInlineSnapshot(`
@@ -48,23 +49,23 @@ describe('convertDirectiveToScam', () => {
selector: '[example]',
standalone: false
})
- export class ExampleDirective {
+ export class Example {
constructor() {}
}
@NgModule({
imports: [CommonModule],
- declarations: [ExampleDirective],
- exports: [ExampleDirective],
+ declarations: [Example],
+ exports: [Example],
})
- export class ExampleDirectiveModule {}
+ export class ExampleModule {}
"
`);
});
it('should create the scam directive separately correctly', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -84,31 +85,32 @@ describe('convertDirectiveToScam', () => {
convertDirectiveToScam(tree, {
path: 'apps/app1/src/app/example/example',
directory: 'apps/app1/src/app/example',
- fileName: 'example.directive',
- filePath: 'apps/app1/src/app/example/example.directive.ts',
+ fileName: 'example',
+ filePath: 'apps/app1/src/app/example/example.ts',
name: 'example',
projectName: 'app1',
+ modulePath: 'apps/app1/src/app/example/example-module.ts',
export: false,
inlineScam: false,
- symbolName: 'ExampleDirective',
+ symbolName: 'Example',
});
// ASSERT
const directiveModuleSource = tree.read(
- 'apps/app1/src/app/example/example.module.ts',
+ 'apps/app1/src/app/example/example-module.ts',
'utf-8'
);
expect(directiveModuleSource).toMatchInlineSnapshot(`
"import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
- import { ExampleDirective } from './example.directive';
+ import { Example } from './example';
@NgModule({
imports: [CommonModule],
- declarations: [ExampleDirective],
- exports: [ExampleDirective],
+ declarations: [Example],
+ exports: [Example],
})
- export class ExampleDirectiveModule {}
+ export class ExampleModule {}
"
`);
});
diff --git a/packages/angular/src/generators/scam-directive/lib/convert-directive-to-scam.ts b/packages/angular/src/generators/scam-directive/lib/convert-directive-to-scam.ts
index 648c16e55e..699d7e3c3f 100644
--- a/packages/angular/src/generators/scam-directive/lib/convert-directive-to-scam.ts
+++ b/packages/angular/src/generators/scam-directive/lib/convert-directive-to-scam.ts
@@ -1,5 +1,4 @@
import type { Tree } from '@nx/devkit';
-import { joinPathFragments, names } from '@nx/devkit';
import { insertImport } from '@nx/js';
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
import type { NormalizedSchema } from '../schema';
@@ -52,13 +51,8 @@ export function convertDirectiveToScam(
return;
}
- const scamFilePath = joinPathFragments(
- options.directory,
- `${options.name}.module.ts`
- );
-
tree.write(
- scamFilePath,
+ options.modulePath,
getModuleFileContent(options.symbolName, options.fileName)
);
}
diff --git a/packages/angular/src/generators/scam-directive/lib/normalize-options.ts b/packages/angular/src/generators/scam-directive/lib/normalize-options.ts
index f8392cddd3..c81245b977 100644
--- a/packages/angular/src/generators/scam-directive/lib/normalize-options.ts
+++ b/packages/angular/src/generators/scam-directive/lib/normalize-options.ts
@@ -1,13 +1,20 @@
import type { Tree } from '@nx/devkit';
-import { names } from '@nx/devkit';
+import { joinPathFragments, names } from '@nx/devkit';
import { determineArtifactNameAndDirectoryOptions } from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
+import { getModuleTypeSeparator } from '../../utils/artifact-types';
import { validateClassName } from '../../utils/validations';
+import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
import type { NormalizedSchema, Schema } from '../schema';
export async function normalizeOptions(
tree: Tree,
options: Schema
): Promise {
+ const { major: angularMajorVersion } = getInstalledAngularVersionInfo(tree);
+ if (angularMajorVersion < 20) {
+ options.type ??= 'directive';
+ }
+
const {
artifactName: name,
directory,
@@ -17,16 +24,22 @@ export async function normalizeOptions(
} = await determineArtifactNameAndDirectoryOptions(tree, {
name: options.name,
path: options.path,
- suffix: 'directive',
+ suffix: options.type,
allowedFileExtensions: ['ts'],
fileExtension: 'ts',
});
const { className } = names(name);
- const { className: suffixClassName } = names('directive');
+ const suffixClassName = options.type ? names(options.type).className : '';
const symbolName = `${className}${suffixClassName}`;
validateClassName(symbolName);
+ const moduleTypeSeparator = getModuleTypeSeparator(tree);
+ const modulePath = joinPathFragments(
+ directory,
+ `${name}${moduleTypeSeparator}module.ts`
+ );
+
return {
...options,
export: options.export ?? true,
@@ -37,5 +50,6 @@ export async function normalizeOptions(
name,
symbolName,
projectName,
+ modulePath,
};
}
diff --git a/packages/angular/src/generators/scam-directive/scam-directive.spec.ts b/packages/angular/src/generators/scam-directive/scam-directive.spec.ts
index b2bcea3694..378c63b17b 100644
--- a/packages/angular/src/generators/scam-directive/scam-directive.spec.ts
+++ b/packages/angular/src/generators/scam-directive/scam-directive.spec.ts
@@ -1,11 +1,17 @@
-import { addProjectConfiguration, writeJson } from '@nx/devkit';
+import {
+ addProjectConfiguration,
+ readNxJson,
+ updateJson,
+ updateNxJson,
+ writeJson,
+} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { scamDirectiveGenerator } from './scam-directive';
describe('SCAM Directive Generator', () => {
it('should create the inline scam directive correctly', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -21,14 +27,50 @@ describe('SCAM Directive Generator', () => {
});
// ASSERT
- const directiveSource = tree.read(
- 'apps/app1/src/app/example.directive.ts',
- 'utf-8'
- );
+ const directiveSource = tree.read('apps/app1/src/app/example.ts', 'utf-8');
expect(directiveSource).toMatchInlineSnapshot(`
"import { Directive, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
+ @Directive({
+ selector: '[example]',
+ standalone: false
+ })
+ export class Example {
+ constructor() {}
+ }
+
+ @NgModule({
+ imports: [CommonModule],
+ declarations: [Example],
+ exports: [Example],
+ })
+ export class ExampleModule {}
+ "
+ `);
+ });
+
+ it('should create the inline scam directive with the provided "directive" type', async () => {
+ const tree = createTreeWithEmptyWorkspace();
+ addProjectConfiguration(tree, 'app1', {
+ projectType: 'application',
+ sourceRoot: 'apps/app1/src',
+ root: 'apps/app1',
+ });
+
+ await scamDirectiveGenerator(tree, {
+ name: 'example',
+ path: 'apps/app1/src/app/example',
+ inlineScam: true,
+ type: 'directive',
+ skipFormat: true,
+ });
+
+ expect(tree.read('apps/app1/src/app/example.directive.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { Directive, NgModule } from '@angular/core';
+ import { CommonModule } from '@angular/common';
+
@Directive({
selector: '[example]',
standalone: false
@@ -49,7 +91,7 @@ describe('SCAM Directive Generator', () => {
it('should create the separate scam directive correctly', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -66,26 +108,63 @@ describe('SCAM Directive Generator', () => {
// ASSERT
const directiveModuleSource = tree.read(
- 'apps/app1/src/app/example.module.ts',
+ 'apps/app1/src/app/example-module.ts',
'utf-8'
);
expect(directiveModuleSource).toMatchInlineSnapshot(`
"import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
- import { ExampleDirective } from './example.directive';
+ import { Example } from './example';
@NgModule({
imports: [CommonModule],
- declarations: [ExampleDirective],
- exports: [ExampleDirective],
+ declarations: [Example],
+ exports: [Example],
})
- export class ExampleDirectiveModule {}
+ export class ExampleModule {}
+ "
+ `);
+ });
+
+ it('should create the module respecting the "typeSeparator" generator default', async () => {
+ const tree = createTreeWithEmptyWorkspace();
+ const nxJson = readNxJson(tree);
+ nxJson.generators = {
+ ...nxJson.generators,
+ '@nx/angular:module': { typeSeparator: '.' },
+ };
+ updateNxJson(tree, nxJson);
+ addProjectConfiguration(tree, 'app1', {
+ projectType: 'application',
+ sourceRoot: 'apps/app1/src',
+ root: 'apps/app1',
+ });
+
+ await scamDirectiveGenerator(tree, {
+ name: 'example',
+ path: 'apps/app1/src/app/example',
+ inlineScam: false,
+ skipFormat: true,
+ });
+
+ expect(tree.read('apps/app1/src/app/example.module.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { NgModule } from '@angular/core';
+ import { CommonModule } from '@angular/common';
+ import { Example } from './example';
+
+ @NgModule({
+ imports: [CommonModule],
+ declarations: [Example],
+ exports: [Example],
+ })
+ export class ExampleModule {}
"
`);
});
it('should handle path with file extension', async () => {
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -111,23 +190,23 @@ describe('SCAM Directive Generator', () => {
selector: '[example]',
standalone: false
})
- export class ExampleDirective {
+ export class Example {
constructor() {}
}
@NgModule({
imports: [CommonModule],
- declarations: [ExampleDirective],
- exports: [ExampleDirective],
+ declarations: [Example],
+ exports: [Example],
})
- export class ExampleDirectiveModule {}
+ export class ExampleModule {}
"
`);
});
it('should create the scam directive correctly and export it for a secondary entrypoint', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'lib1', {
projectType: 'library',
sourceRoot: 'libs/lib1/src',
@@ -149,20 +228,20 @@ describe('SCAM Directive Generator', () => {
// ASSERT
const directiveModuleSource = tree.read(
- 'libs/lib1/feature/src/lib/example/example.module.ts',
+ 'libs/lib1/feature/src/lib/example/example-module.ts',
'utf-8'
);
expect(directiveModuleSource).toMatchInlineSnapshot(`
"import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
- import { ExampleDirective } from './example.directive';
+ import { Example } from './example';
@NgModule({
imports: [CommonModule],
- declarations: [ExampleDirective],
- exports: [ExampleDirective],
+ declarations: [Example],
+ exports: [Example],
})
- export class ExampleDirectiveModule {}
+ export class ExampleModule {}
"
`);
const secondaryEntryPointSource = tree.read(
@@ -170,8 +249,8 @@ describe('SCAM Directive Generator', () => {
'utf-8'
);
expect(secondaryEntryPointSource).toMatchInlineSnapshot(`
- "export * from './lib/example/example.directive';
- export * from './lib/example/example.module';"
+ "export * from './lib/example/example';
+ export * from './lib/example/example-module';"
`);
});
@@ -188,13 +267,13 @@ describe('SCAM Directive Generator', () => {
name: '404',
path: 'apps/app1/src/app/example',
})
- ).rejects.toThrow('Class name "404Directive" is invalid.');
+ ).rejects.toThrow('Class name "404" is invalid.');
});
describe('--path', () => {
- it('should not throw when the path does not exist under project', async () => {
+ it('should not throw when the path exists under project', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -211,7 +290,7 @@ describe('SCAM Directive Generator', () => {
// ASSERT
const directiveSource = tree.read(
- 'apps/app1/src/app/random/example/example.directive.ts',
+ 'apps/app1/src/app/random/example/example.ts',
'utf-8'
);
expect(directiveSource).toMatchInlineSnapshot(`
@@ -222,23 +301,23 @@ describe('SCAM Directive Generator', () => {
selector: '[example]',
standalone: false
})
- export class ExampleDirective {
+ export class Example {
constructor() {}
}
@NgModule({
imports: [CommonModule],
- declarations: [ExampleDirective],
- exports: [ExampleDirective],
+ declarations: [Example],
+ exports: [Example],
})
- export class ExampleDirectiveModule {}
+ export class ExampleModule {}
"
`);
});
it('should not matter if the path starts with a slash', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -255,7 +334,7 @@ describe('SCAM Directive Generator', () => {
// ASSERT
const directiveSource = tree.read(
- 'apps/app1/src/app/random/example/example.directive.ts',
+ 'apps/app1/src/app/random/example/example.ts',
'utf-8'
);
expect(directiveSource).toMatchInlineSnapshot(`
@@ -266,23 +345,23 @@ describe('SCAM Directive Generator', () => {
selector: '[example]',
standalone: false
})
- export class ExampleDirective {
+ export class Example {
constructor() {}
}
@NgModule({
imports: [CommonModule],
- declarations: [ExampleDirective],
- exports: [ExampleDirective],
+ declarations: [Example],
+ exports: [Example],
})
- export class ExampleDirectiveModule {}
+ export class ExampleModule {}
"
`);
});
it('should throw when the path does not exist under project', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -302,4 +381,89 @@ describe('SCAM Directive Generator', () => {
);
});
});
+
+ describe('compat', () => {
+ it('should generate the scam directive with the "directive" type for versions lower than v20', async () => {
+ const tree = createTreeWithEmptyWorkspace();
+ updateJson(tree, 'package.json', (json) => {
+ json.dependencies = {
+ ...json.dependencies,
+ '@angular/core': '~19.2.0',
+ };
+ return json;
+ });
+ addProjectConfiguration(tree, 'app1', {
+ projectType: 'application',
+ sourceRoot: 'apps/app1/src',
+ root: 'apps/app1',
+ });
+
+ await scamDirectiveGenerator(tree, {
+ name: 'example',
+ path: 'apps/app1/src/app/example',
+ inlineScam: true,
+ skipFormat: true,
+ });
+
+ expect(tree.read('apps/app1/src/app/example.directive.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { Directive, NgModule } from '@angular/core';
+ import { CommonModule } from '@angular/common';
+
+ @Directive({
+ selector: '[example]',
+ standalone: false
+ })
+ export class ExampleDirective {
+ constructor() {}
+ }
+
+ @NgModule({
+ imports: [CommonModule],
+ declarations: [ExampleDirective],
+ exports: [ExampleDirective],
+ })
+ export class ExampleDirectiveModule {}
+ "
+ `);
+ });
+
+ it('should generate the module with the "." type separator for versions lower than v20', async () => {
+ const tree = createTreeWithEmptyWorkspace();
+ updateJson(tree, 'package.json', (json) => {
+ json.dependencies = {
+ ...json.dependencies,
+ '@angular/core': '~19.2.0',
+ };
+ return json;
+ });
+ addProjectConfiguration(tree, 'app1', {
+ projectType: 'application',
+ sourceRoot: 'apps/app1/src',
+ root: 'apps/app1',
+ });
+
+ await scamDirectiveGenerator(tree, {
+ name: 'example',
+ path: 'apps/app1/src/app/example',
+ inlineScam: false,
+ skipFormat: true,
+ });
+
+ expect(tree.read('apps/app1/src/app/example.module.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { NgModule } from '@angular/core';
+ import { CommonModule } from '@angular/common';
+ import { ExampleDirective } from './example.directive';
+
+ @NgModule({
+ imports: [CommonModule],
+ declarations: [ExampleDirective],
+ exports: [ExampleDirective],
+ })
+ export class ExampleDirectiveModule {}
+ "
+ `);
+ });
+ });
});
diff --git a/packages/angular/src/generators/scam-directive/schema.d.ts b/packages/angular/src/generators/scam-directive/schema.d.ts
index ed335ab8df..dbd9f90e32 100644
--- a/packages/angular/src/generators/scam-directive/schema.d.ts
+++ b/packages/angular/src/generators/scam-directive/schema.d.ts
@@ -6,6 +6,7 @@ export interface Schema {
prefix?: string;
selector?: string;
export?: boolean;
+ type?: string;
skipFormat?: boolean;
}
@@ -18,4 +19,5 @@ export interface NormalizedSchema extends Schema {
inlineScam: boolean;
symbolName: string;
projectName: string;
+ modulePath: string;
}
diff --git a/packages/angular/src/generators/scam-directive/schema.json b/packages/angular/src/generators/scam-directive/schema.json
index a4de0a5cab..3ab64b789e 100644
--- a/packages/angular/src/generators/scam-directive/schema.json
+++ b/packages/angular/src/generators/scam-directive/schema.json
@@ -10,11 +10,15 @@
"command": "nx g @nx/angular:scam-directive mylib/src/lib/foo.directive.ts"
},
{
- "description": "Generate a directive without providing the file extension. It results in the directive `FooDirective` at `mylib/src/lib/foo.directive.ts`",
+ "description": "Generate a directive without providing the file extension. It results in the directive `Foo` at `mylib/src/lib/foo.ts`",
"command": "nx g @nx/angular:scam-directive mylib/src/lib/foo"
},
{
- "description": "Generate a directive with the exported symbol different from the file name. It results in the directive `CustomDirective` at `mylib/src/lib/foo.directive.ts`",
+ "description": "Generate a directive with a given type/suffix. It results in the directive `FooDirective` at `mylib/src/lib/foo.directive.ts`",
+ "command": "nx g @nx/angular:scam-directive mylib/src/lib/foo --type=directive"
+ },
+ {
+ "description": "Generate a directive with the exported symbol different from the file name. It results in the directive `Custom` at `mylib/src/lib/foo.ts`",
"command": "nx g @nx/angular:scam-directive mylib/src/lib/foo --name=custom"
}
],
@@ -70,6 +74,10 @@
"default": true,
"x-priority": "important"
},
+ "type": {
+ "type": "string",
+ "description": "Append a custom type to the directive's filename. It defaults to 'directive' for Angular versions below v20. For Angular v20 and above, no type is appended unless specified."
+ },
"skipFormat": {
"description": "Skip formatting files.",
"type": "boolean",
diff --git a/packages/angular/src/generators/scam-pipe/lib/convert-pipe-to-scam.spec.ts b/packages/angular/src/generators/scam-pipe/lib/convert-pipe-to-scam.spec.ts
index da73325d1b..80a381e7d6 100644
--- a/packages/angular/src/generators/scam-pipe/lib/convert-pipe-to-scam.spec.ts
+++ b/packages/angular/src/generators/scam-pipe/lib/convert-pipe-to-scam.spec.ts
@@ -6,7 +6,7 @@ import { convertPipeToScam } from './convert-pipe-to-scam';
describe('convertPipeToScam', () => {
it('should create the scam pipe inline correctly', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -26,10 +26,11 @@ describe('convertPipeToScam', () => {
convertPipeToScam(tree, {
path: 'apps/app1/src/app/example/example',
directory: 'apps/app1/src/app/example',
- fileName: 'example.pipe',
- filePath: 'apps/app1/src/app/example/example.pipe.ts',
+ fileName: 'example-pipe',
+ filePath: 'apps/app1/src/app/example/example-pipe.ts',
name: 'example',
projectName: 'app1',
+ modulePath: 'apps/app1/src/app/example/example-module.ts',
export: false,
inlineScam: true,
symbolName: 'ExamplePipe',
@@ -37,7 +38,7 @@ describe('convertPipeToScam', () => {
// ASSERT
const pipeSource = tree.read(
- 'apps/app1/src/app/example/example.pipe.ts',
+ 'apps/app1/src/app/example/example-pipe.ts',
'utf-8'
);
expect(pipeSource).toMatchInlineSnapshot(`
@@ -66,7 +67,7 @@ describe('convertPipeToScam', () => {
it('should create the scam pipe separately correctly', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -85,10 +86,11 @@ describe('convertPipeToScam', () => {
convertPipeToScam(tree, {
path: 'apps/app1/src/app/example/example',
directory: 'apps/app1/src/app/example',
- fileName: 'example.pipe',
- filePath: 'apps/app1/src/app/example/example.pipe.ts',
+ fileName: 'example-pipe',
+ filePath: 'apps/app1/src/app/example/example-pipe.ts',
name: 'example',
projectName: 'app1',
+ modulePath: 'apps/app1/src/app/example/example-module.ts',
export: false,
inlineScam: false,
symbolName: 'ExamplePipe',
@@ -96,13 +98,13 @@ describe('convertPipeToScam', () => {
// ASSERT
const pipeModuleSource = tree.read(
- 'apps/app1/src/app/example/example.module.ts',
+ 'apps/app1/src/app/example/example-module.ts',
'utf-8'
);
expect(pipeModuleSource).toMatchInlineSnapshot(`
"import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
- import { ExamplePipe } from './example.pipe';
+ import { ExamplePipe } from './example-pipe';
@NgModule({
imports: [CommonModule],
diff --git a/packages/angular/src/generators/scam-pipe/lib/convert-pipe-to-scam.ts b/packages/angular/src/generators/scam-pipe/lib/convert-pipe-to-scam.ts
index 55b6a14174..cc5c056162 100644
--- a/packages/angular/src/generators/scam-pipe/lib/convert-pipe-to-scam.ts
+++ b/packages/angular/src/generators/scam-pipe/lib/convert-pipe-to-scam.ts
@@ -1,5 +1,4 @@
import type { Tree } from '@nx/devkit';
-import { joinPathFragments, names } from '@nx/devkit';
import { insertImport } from '@nx/js';
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
import type { NormalizedSchema } from '../schema';
@@ -49,13 +48,8 @@ export function convertPipeToScam(tree: Tree, options: NormalizedSchema) {
return;
}
- const scamFilePath = joinPathFragments(
- options.directory,
- `${options.name}.module.ts`
- );
-
tree.write(
- scamFilePath,
+ options.modulePath,
getModuleFileContent(options.symbolName, options.fileName)
);
}
diff --git a/packages/angular/src/generators/scam-pipe/lib/normalize-options.ts b/packages/angular/src/generators/scam-pipe/lib/normalize-options.ts
index b1aaf04842..d764fc65c0 100644
--- a/packages/angular/src/generators/scam-pipe/lib/normalize-options.ts
+++ b/packages/angular/src/generators/scam-pipe/lib/normalize-options.ts
@@ -1,13 +1,18 @@
import type { Tree } from '@nx/devkit';
-import { names } from '@nx/devkit';
+import { joinPathFragments, names } from '@nx/devkit';
import { determineArtifactNameAndDirectoryOptions } from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
+import { getModuleTypeSeparator } from '../../utils/artifact-types';
import { validateClassName } from '../../utils/validations';
+import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
import type { NormalizedSchema, Schema } from '../schema';
export async function normalizeOptions(
tree: Tree,
options: Schema
): Promise {
+ const { major: angularMajorVersion } = getInstalledAngularVersionInfo(tree);
+ options.typeSeparator ??= angularMajorVersion < 20 ? '.' : '-';
+
const {
artifactName: name,
directory,
@@ -18,6 +23,7 @@ export async function normalizeOptions(
name: options.name,
path: options.path,
suffix: 'pipe',
+ suffixSeparator: options.typeSeparator,
allowedFileExtensions: ['ts'],
fileExtension: 'ts',
});
@@ -27,6 +33,12 @@ export async function normalizeOptions(
const symbolName = `${className}${suffixClassName}`;
validateClassName(symbolName);
+ const moduleTypeSeparator = getModuleTypeSeparator(tree);
+ const modulePath = joinPathFragments(
+ directory,
+ `${name}${moduleTypeSeparator}module.ts`
+ );
+
return {
...options,
export: options.export ?? true,
@@ -37,5 +49,6 @@ export async function normalizeOptions(
name,
symbolName,
projectName,
+ modulePath,
};
}
diff --git a/packages/angular/src/generators/scam-pipe/scam-pipe.spec.ts b/packages/angular/src/generators/scam-pipe/scam-pipe.spec.ts
index dcd5ae25d5..e3ed96720c 100644
--- a/packages/angular/src/generators/scam-pipe/scam-pipe.spec.ts
+++ b/packages/angular/src/generators/scam-pipe/scam-pipe.spec.ts
@@ -1,11 +1,17 @@
-import { addProjectConfiguration, writeJson } from '@nx/devkit';
+import {
+ addProjectConfiguration,
+ readNxJson,
+ updateJson,
+ updateNxJson,
+ writeJson,
+} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { scamPipeGenerator } from './scam-pipe';
describe('SCAM Pipe Generator', () => {
it('should create the inline scam pipe correctly', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -21,6 +27,50 @@ describe('SCAM Pipe Generator', () => {
});
// ASSERT
+ const pipeSource = tree.read(
+ 'apps/app1/src/app/example/example-pipe.ts',
+ 'utf-8'
+ );
+ expect(pipeSource).toMatchInlineSnapshot(`
+ "import { Pipe, PipeTransform, NgModule } from '@angular/core';
+ import { CommonModule } from '@angular/common';
+
+ @Pipe({
+ name: 'example',
+ standalone: false
+ })
+ export class ExamplePipe implements PipeTransform {
+ transform(value: unknown, ...args: unknown[]): unknown {
+ return null;
+ }
+ }
+
+ @NgModule({
+ imports: [CommonModule],
+ declarations: [ExamplePipe],
+ exports: [ExamplePipe],
+ })
+ export class ExamplePipeModule {}
+ "
+ `);
+ });
+
+ it('should create the inline scam pipe file with the provided type separator', async () => {
+ const tree = createTreeWithEmptyWorkspace();
+ addProjectConfiguration(tree, 'app1', {
+ projectType: 'application',
+ sourceRoot: 'apps/app1/src',
+ root: 'apps/app1',
+ });
+
+ await scamPipeGenerator(tree, {
+ name: 'example',
+ path: 'apps/app1/src/app/example/example',
+ inlineScam: true,
+ typeSeparator: '.',
+ skipFormat: true,
+ });
+
const pipeSource = tree.read(
'apps/app1/src/app/example/example.pipe.ts',
'utf-8'
@@ -51,7 +101,7 @@ describe('SCAM Pipe Generator', () => {
it('should create the separate scam pipe correctly', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -68,13 +118,50 @@ describe('SCAM Pipe Generator', () => {
// ASSERT
const pipeModuleSource = tree.read(
- 'apps/app1/src/app/example/example.module.ts',
+ 'apps/app1/src/app/example/example-module.ts',
'utf-8'
);
expect(pipeModuleSource).toMatchInlineSnapshot(`
"import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
- import { ExamplePipe } from './example.pipe';
+ import { ExamplePipe } from './example-pipe';
+
+ @NgModule({
+ imports: [CommonModule],
+ declarations: [ExamplePipe],
+ exports: [ExamplePipe],
+ })
+ export class ExamplePipeModule {}
+ "
+ `);
+ });
+
+ it('should create the module respecting the "typeSeparator" generator default', async () => {
+ const tree = createTreeWithEmptyWorkspace();
+ const nxJson = readNxJson(tree);
+ nxJson.generators = {
+ ...nxJson.generators,
+ '@nx/angular:module': { typeSeparator: '.' },
+ };
+ updateNxJson(tree, nxJson);
+ addProjectConfiguration(tree, 'app1', {
+ projectType: 'application',
+ sourceRoot: 'apps/app1/src',
+ root: 'apps/app1',
+ });
+
+ await scamPipeGenerator(tree, {
+ name: 'example',
+ path: 'apps/app1/src/app/example/example',
+ inlineScam: false,
+ skipFormat: true,
+ });
+
+ expect(tree.read('apps/app1/src/app/example/example.module.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { NgModule } from '@angular/core';
+ import { CommonModule } from '@angular/common';
+ import { ExamplePipe } from './example-pipe';
@NgModule({
imports: [CommonModule],
@@ -87,7 +174,7 @@ describe('SCAM Pipe Generator', () => {
});
it('should handle path with file extension', async () => {
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -131,7 +218,7 @@ describe('SCAM Pipe Generator', () => {
it('should create the scam pipe correctly and export it for a secondary entrypoint', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'lib1', {
projectType: 'library',
sourceRoot: 'libs/lib1/src',
@@ -153,13 +240,13 @@ describe('SCAM Pipe Generator', () => {
// ASSERT
const pipeModuleSource = tree.read(
- 'libs/lib1/feature/src/lib/example/example.module.ts',
+ 'libs/lib1/feature/src/lib/example/example-module.ts',
'utf-8'
);
expect(pipeModuleSource).toMatchInlineSnapshot(`
"import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
- import { ExamplePipe } from './example.pipe';
+ import { ExamplePipe } from './example-pipe';
@NgModule({
imports: [CommonModule],
@@ -174,8 +261,8 @@ describe('SCAM Pipe Generator', () => {
'utf-8'
);
expect(secondaryEntryPointSource).toMatchInlineSnapshot(`
- "export * from './lib/example/example.pipe';
- export * from './lib/example/example.module';"
+ "export * from './lib/example/example-pipe';
+ export * from './lib/example/example-module';"
`);
});
@@ -196,9 +283,9 @@ describe('SCAM Pipe Generator', () => {
});
describe('--path', () => {
- it('should not throw when the path does not exist under project', async () => {
+ it('should not throw when the path exists under project', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -215,7 +302,7 @@ describe('SCAM Pipe Generator', () => {
// ASSERT
const pipeSource = tree.read(
- 'apps/app1/src/app/random/example/example.pipe.ts',
+ 'apps/app1/src/app/random/example/example-pipe.ts',
'utf-8'
);
expect(pipeSource).toMatchInlineSnapshot(`
@@ -244,7 +331,7 @@ describe('SCAM Pipe Generator', () => {
it('should not matter if the path starts with a slash', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -261,7 +348,7 @@ describe('SCAM Pipe Generator', () => {
// ASSERT
const pipeSource = tree.read(
- 'apps/app1/src/app/random/example/example.pipe.ts',
+ 'apps/app1/src/app/random/example/example-pipe.ts',
'utf-8'
);
expect(pipeSource).toMatchInlineSnapshot(`
@@ -290,7 +377,7 @@ describe('SCAM Pipe Generator', () => {
it('should throw when the path does not exist under project', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -310,4 +397,91 @@ describe('SCAM Pipe Generator', () => {
);
});
});
+
+ describe('compat', () => {
+ it('should generate scam pipe file with the "." type separator for versions lower than v20', async () => {
+ const tree = createTreeWithEmptyWorkspace();
+ updateJson(tree, 'package.json', (json) => {
+ json.dependencies = {
+ ...json.dependencies,
+ '@angular/core': '~19.2.0',
+ };
+ return json;
+ });
+ addProjectConfiguration(tree, 'app1', {
+ projectType: 'application',
+ sourceRoot: 'apps/app1/src',
+ root: 'apps/app1',
+ });
+
+ await scamPipeGenerator(tree, {
+ name: 'example',
+ path: 'apps/app1/src/app/example/example',
+ inlineScam: true,
+ skipFormat: true,
+ });
+
+ expect(tree.read('apps/app1/src/app/example/example.pipe.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { Pipe, PipeTransform, NgModule } from '@angular/core';
+ import { CommonModule } from '@angular/common';
+
+ @Pipe({
+ name: 'example',
+ standalone: false
+ })
+ export class ExamplePipe implements PipeTransform {
+ transform(value: unknown, ...args: unknown[]): unknown {
+ return null;
+ }
+ }
+
+ @NgModule({
+ imports: [CommonModule],
+ declarations: [ExamplePipe],
+ exports: [ExamplePipe],
+ })
+ export class ExamplePipeModule {}
+ "
+ `);
+ });
+
+ it('should generate the module file with the "." type separator for versions lower than v20', async () => {
+ const tree = createTreeWithEmptyWorkspace();
+ updateJson(tree, 'package.json', (json) => {
+ json.dependencies = {
+ ...json.dependencies,
+ '@angular/core': '~19.2.0',
+ };
+ return json;
+ });
+ addProjectConfiguration(tree, 'app1', {
+ projectType: 'application',
+ sourceRoot: 'apps/app1/src',
+ root: 'apps/app1',
+ });
+
+ await scamPipeGenerator(tree, {
+ name: 'example',
+ path: 'apps/app1/src/app/example/example',
+ inlineScam: false,
+ skipFormat: true,
+ });
+
+ expect(tree.read('apps/app1/src/app/example/example.module.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { NgModule } from '@angular/core';
+ import { CommonModule } from '@angular/common';
+ import { ExamplePipe } from './example.pipe';
+
+ @NgModule({
+ imports: [CommonModule],
+ declarations: [ExamplePipe],
+ exports: [ExamplePipe],
+ })
+ export class ExamplePipeModule {}
+ "
+ `);
+ });
+ });
});
diff --git a/packages/angular/src/generators/scam-pipe/schema.d.ts b/packages/angular/src/generators/scam-pipe/schema.d.ts
index 1d8c717ba8..968d0d4f24 100644
--- a/packages/angular/src/generators/scam-pipe/schema.d.ts
+++ b/packages/angular/src/generators/scam-pipe/schema.d.ts
@@ -4,6 +4,7 @@ export interface Schema {
skipTests?: boolean;
inlineScam?: boolean;
export?: boolean;
+ typeSeparator?: string;
skipFormat?: boolean;
}
@@ -16,4 +17,5 @@ export interface NormalizedSchema extends Schema {
inlineScam: boolean;
projectName: string;
symbolName: string;
+ modulePath: string;
}
diff --git a/packages/angular/src/generators/scam-pipe/schema.json b/packages/angular/src/generators/scam-pipe/schema.json
index 7f3ce2b740..43f219e703 100644
--- a/packages/angular/src/generators/scam-pipe/schema.json
+++ b/packages/angular/src/generators/scam-pipe/schema.json
@@ -51,6 +51,11 @@
"default": true,
"x-priority": "important"
},
+ "typeSeparator": {
+ "type": "string",
+ "enum": ["-", "."],
+ "description": "The separator character to use before the type within the generated file's name. For example, if you set the option to `.`, the file will be named `example.pipe.ts`. It defaults to '-' for Angular v20+. For versions below v20, it defaults to '.'."
+ },
"skipFormat": {
"description": "Skip formatting files.",
"type": "boolean",
diff --git a/packages/angular/src/generators/scam-to-standalone/scam-to-standalone.spec.ts b/packages/angular/src/generators/scam-to-standalone/scam-to-standalone.spec.ts
index 6a0c364a65..e9a02a0980 100644
--- a/packages/angular/src/generators/scam-to-standalone/scam-to-standalone.spec.ts
+++ b/packages/angular/src/generators/scam-to-standalone/scam-to-standalone.spec.ts
@@ -17,22 +17,21 @@ describe('scam-to-standalone', () => {
tree.write(
'foo/src/app/mymodule.module.ts',
- `import { BarComponentModule } from './bar/bar.component';
+ `import { BarModule } from './bar/bar';
import { ExtraBarComponentModule } from './bar/extra-bar.component';
@NgModule({
- imports: [BarComponentModule, ExtraBarComponentModule]
+ imports: [BarModule, ExtraBarComponentModule]
})
export class MyModule {}`
);
await scamToStandalone(tree, {
- component: 'src/app/bar/bar.component.ts',
+ component: 'src/app/bar/bar.ts',
project: 'foo',
});
- expect(tree.read('foo/src/app/bar/bar.component.ts', 'utf-8'))
- .toMatchInlineSnapshot(`
+ expect(tree.read('foo/src/app/bar/bar.ts', 'utf-8')).toMatchInlineSnapshot(`
"import { Component, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@@ -40,40 +39,40 @@ describe('scam-to-standalone', () => {
imports: [CommonModule],
selector: 'app-bar',
standalone: false,
- templateUrl: './bar.component.html',
- styleUrl: './bar.component.css',
+ templateUrl: './bar.html',
+ styleUrl: './bar.css',
})
- export class BarComponent {}
+ export class Bar {}
"
`);
expect(tree.read('foo/src/app/mymodule.module.ts', 'utf-8'))
.toMatchInlineSnapshot(`
- "import { BarComponent } from './bar/bar.component';
+ "import { Bar } from './bar/bar';
import { ExtraBarComponentModule } from './bar/extra-bar.component';
@NgModule({
- imports: [BarComponent, ExtraBarComponentModule],
+ imports: [Bar, ExtraBarComponentModule],
})
export class MyModule {}
"
`);
- expect(tree.read('foo/src/app/bar/bar.component.spec.ts', 'utf-8'))
+ expect(tree.read('foo/src/app/bar/bar.spec.ts', 'utf-8'))
.toMatchInlineSnapshot(`
"import { ComponentFixture, TestBed } from '@angular/core/testing';
- import { BarComponent } from './bar.component';
+ import { Bar } from './bar';
- describe('BarComponent', () => {
- let component: BarComponent;
- let fixture: ComponentFixture;
+ describe('Bar', () => {
+ let component: Bar;
+ let fixture: ComponentFixture;
beforeEach(async () => {
await TestBed.configureTestingModule({
- imports: [BarComponent],
+ imports: [Bar],
}).compileComponents();
- fixture = TestBed.createComponent(BarComponent);
+ fixture = TestBed.createComponent(Bar);
component = fixture.componentInstance;
fixture.detectChanges();
});
diff --git a/packages/angular/src/generators/scam/lib/convert-component-to-scam.spec.ts b/packages/angular/src/generators/scam/lib/convert-component-to-scam.spec.ts
index 42a175bda2..e0db361432 100644
--- a/packages/angular/src/generators/scam/lib/convert-component-to-scam.spec.ts
+++ b/packages/angular/src/generators/scam/lib/convert-component-to-scam.spec.ts
@@ -6,7 +6,7 @@ import { convertComponentToScam } from './convert-component-to-scam';
describe('convertComponentToScam', () => {
it('should create the scam inline correctly', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -26,18 +26,19 @@ describe('convertComponentToScam', () => {
convertComponentToScam(tree, {
path: 'apps/app1/src/app/example/example',
directory: 'apps/app1/src/app/example',
- fileName: 'example.component',
- filePath: 'apps/app1/src/app/example/example.component.ts',
+ fileName: 'example',
+ filePath: 'apps/app1/src/app/example/example.ts',
name: 'example',
projectName: 'app1',
+ modulePath: 'apps/app1/src/app/example/example-module.ts',
export: false,
inlineScam: true,
- symbolName: 'ExampleComponent',
+ symbolName: 'Example',
});
// ASSERT
const componentSource = tree.read(
- 'apps/app1/src/app/example/example.component.ts',
+ 'apps/app1/src/app/example/example.ts',
'utf-8'
);
expect(componentSource).toMatchInlineSnapshot(`
@@ -47,24 +48,24 @@ describe('convertComponentToScam', () => {
@Component({
selector: 'example',
standalone: false,
- templateUrl: './example.component.html',
- styleUrl: './example.component.css'
+ templateUrl: './example.html',
+ styleUrl: './example.css'
})
- export class ExampleComponent {}
+ export class Example {}
@NgModule({
imports: [CommonModule],
- declarations: [ExampleComponent],
- exports: [ExampleComponent],
+ declarations: [Example],
+ exports: [Example],
})
- export class ExampleComponentModule {}
+ export class ExampleModule {}
"
`);
});
it('should create the scam separately correctly', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -84,38 +85,39 @@ describe('convertComponentToScam', () => {
convertComponentToScam(tree, {
path: 'apps/app1/src/app/example/example',
directory: 'apps/app1/src/app/example',
- fileName: 'example.component',
- filePath: 'apps/app1/src/app/example/example.component.ts',
+ fileName: 'example',
+ filePath: 'apps/app1/src/app/example/example.ts',
name: 'example',
projectName: 'app1',
+ modulePath: 'apps/app1/src/app/example/example-module.ts',
export: false,
inlineScam: false,
- symbolName: 'ExampleComponent',
+ symbolName: 'Example',
});
// ASSERT
const componentModuleSource = tree.read(
- 'apps/app1/src/app/example/example.module.ts',
+ 'apps/app1/src/app/example/example-module.ts',
'utf-8'
);
expect(componentModuleSource).toMatchInlineSnapshot(`
"import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
- import { ExampleComponent } from './example.component';
+ import { Example } from './example';
@NgModule({
imports: [CommonModule],
- declarations: [ExampleComponent],
- exports: [ExampleComponent],
+ declarations: [Example],
+ exports: [Example],
})
- export class ExampleComponentModule {}
+ export class ExampleModule {}
"
`);
});
it('should create the scam inline correctly when --type', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -140,6 +142,7 @@ describe('convertComponentToScam', () => {
filePath: 'apps/app1/src/app/example.random.ts',
name: 'example',
projectName: 'app1',
+ modulePath: 'apps/app1/src/app/example-module.ts',
export: false,
inlineScam: true,
type: 'random',
@@ -175,7 +178,7 @@ describe('convertComponentToScam', () => {
it('should create the scam separately correctly when --type', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -200,6 +203,7 @@ describe('convertComponentToScam', () => {
filePath: 'apps/app1/src/app/example.random.ts',
name: 'example',
projectName: 'app1',
+ modulePath: 'apps/app1/src/app/example-module.ts',
export: false,
inlineScam: false,
type: 'random',
@@ -208,7 +212,7 @@ describe('convertComponentToScam', () => {
// ASSERT
const componentModuleSource = tree.read(
- 'apps/app1/src/app/example.module.ts',
+ 'apps/app1/src/app/example-module.ts',
'utf-8'
);
expect(componentModuleSource).toMatchInlineSnapshot(`
diff --git a/packages/angular/src/generators/scam/lib/convert-component-to-scam.ts b/packages/angular/src/generators/scam/lib/convert-component-to-scam.ts
index 09394c1cff..e2fb051f41 100644
--- a/packages/angular/src/generators/scam/lib/convert-component-to-scam.ts
+++ b/packages/angular/src/generators/scam/lib/convert-component-to-scam.ts
@@ -1,5 +1,4 @@
import type { Tree } from '@nx/devkit';
-import { joinPathFragments, names } from '@nx/devkit';
import { insertImport } from '@nx/js';
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
import type { NormalizedSchema } from '../schema';
@@ -50,13 +49,8 @@ export function convertComponentToScam(tree: Tree, options: NormalizedSchema) {
return;
}
- const moduleFilePath = joinPathFragments(
- options.directory,
- `${options.name}.module.ts`
- );
-
tree.write(
- moduleFilePath,
+ options.modulePath,
getModuleFileContent(options.symbolName, options.fileName)
);
}
diff --git a/packages/angular/src/generators/scam/lib/normalize-options.ts b/packages/angular/src/generators/scam/lib/normalize-options.ts
index 8970112677..b0cad4be24 100644
--- a/packages/angular/src/generators/scam/lib/normalize-options.ts
+++ b/packages/angular/src/generators/scam/lib/normalize-options.ts
@@ -1,14 +1,20 @@
import type { Tree } from '@nx/devkit';
-import { names } from '@nx/devkit';
+import { joinPathFragments, names } from '@nx/devkit';
import { determineArtifactNameAndDirectoryOptions } from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
+import { getModuleTypeSeparator } from '../../utils/artifact-types';
import { validateClassName } from '../../utils/validations';
+import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
import type { NormalizedSchema, Schema } from '../schema';
export async function normalizeOptions(
tree: Tree,
options: Schema
): Promise {
- options.type ??= 'component';
+ const { major: angularMajorVersion } = getInstalledAngularVersionInfo(tree);
+ if (angularMajorVersion < 20) {
+ options.type ??= 'component';
+ }
+
const {
artifactName: name,
directory,
@@ -18,16 +24,22 @@ export async function normalizeOptions(
} = await determineArtifactNameAndDirectoryOptions(tree, {
name: options.name,
path: options.path,
- suffix: options.type ?? 'component',
+ suffix: options.type,
allowedFileExtensions: ['ts'],
fileExtension: 'ts',
});
const { className } = names(name);
- const { className: suffixClassName } = names(options.type);
+ const suffixClassName = options.type ? names(options.type).className : '';
const symbolName = `${className}${suffixClassName}`;
validateClassName(symbolName);
+ const moduleTypeSeparator = getModuleTypeSeparator(tree);
+ const modulePath = joinPathFragments(
+ directory,
+ `${name}${moduleTypeSeparator}module.ts`
+ );
+
return {
...options,
export: options.export ?? true,
@@ -38,5 +50,6 @@ export async function normalizeOptions(
name,
symbolName,
projectName,
+ modulePath,
};
}
diff --git a/packages/angular/src/generators/scam/scam.spec.ts b/packages/angular/src/generators/scam/scam.spec.ts
index 4f0fe2205f..b1438603c4 100644
--- a/packages/angular/src/generators/scam/scam.spec.ts
+++ b/packages/angular/src/generators/scam/scam.spec.ts
@@ -1,11 +1,17 @@
-import { addProjectConfiguration, writeJson } from '@nx/devkit';
+import {
+ addProjectConfiguration,
+ readNxJson,
+ updateJson,
+ updateNxJson,
+ writeJson,
+} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { scamGenerator } from './scam';
describe('SCAM Generator', () => {
it('should create the inline scam correctly', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -22,7 +28,7 @@ describe('SCAM Generator', () => {
// ASSERT
const componentSource = tree.read(
- 'apps/app1/src/app/example/example.component.ts',
+ 'apps/app1/src/app/example/example.ts',
'utf-8'
);
expect(componentSource).toMatchInlineSnapshot(`
@@ -32,24 +38,24 @@ describe('SCAM Generator', () => {
@Component({
selector: 'example',
standalone: false,
- templateUrl: './example.component.html',
- styleUrl: './example.component.css'
+ templateUrl: './example.html',
+ styleUrl: './example.css'
})
- export class ExampleComponent {}
+ export class Example {}
@NgModule({
imports: [CommonModule],
- declarations: [ExampleComponent],
- exports: [ExampleComponent],
+ declarations: [Example],
+ exports: [Example],
})
- export class ExampleComponentModule {}
+ export class ExampleModule {}
"
`);
});
it('should create the separate scam correctly', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -66,26 +72,63 @@ describe('SCAM Generator', () => {
// ASSERT
const componentModuleSource = tree.read(
- 'apps/app1/src/app/example/example.module.ts',
+ 'apps/app1/src/app/example/example-module.ts',
'utf-8'
);
expect(componentModuleSource).toMatchInlineSnapshot(`
"import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
- import { ExampleComponent } from './example.component';
+ import { Example } from './example';
@NgModule({
imports: [CommonModule],
- declarations: [ExampleComponent],
- exports: [ExampleComponent],
+ declarations: [Example],
+ exports: [Example],
})
- export class ExampleComponentModule {}
+ export class ExampleModule {}
+ "
+ `);
+ });
+
+ it('should create the module respecting the "typeSeparator" generator default', async () => {
+ const tree = createTreeWithEmptyWorkspace();
+ const nxJson = readNxJson(tree);
+ nxJson.generators = {
+ ...nxJson.generators,
+ '@nx/angular:module': { typeSeparator: '.' },
+ };
+ updateNxJson(tree, nxJson);
+ addProjectConfiguration(tree, 'app1', {
+ projectType: 'application',
+ sourceRoot: 'apps/app1/src',
+ root: 'apps/app1',
+ });
+
+ await scamGenerator(tree, {
+ name: 'example',
+ path: 'apps/app1/src/app/example/example',
+ inlineScam: false,
+ skipFormat: true,
+ });
+
+ expect(tree.read('apps/app1/src/app/example/example.module.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { NgModule } from '@angular/core';
+ import { CommonModule } from '@angular/common';
+ import { Example } from './example';
+
+ @NgModule({
+ imports: [CommonModule],
+ declarations: [Example],
+ exports: [Example],
+ })
+ export class ExampleModule {}
"
`);
});
it('should handle path with file extension', async () => {
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -113,21 +156,21 @@ describe('SCAM Generator', () => {
templateUrl: './example.component.html',
styleUrl: './example.component.css'
})
- export class ExampleComponent {}
+ export class Example {}
@NgModule({
imports: [CommonModule],
- declarations: [ExampleComponent],
- exports: [ExampleComponent],
+ declarations: [Example],
+ exports: [Example],
})
- export class ExampleComponentModule {}
+ export class ExampleModule {}
"
`);
});
it('should create the scam correctly and export it for a secondary entrypoint', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'lib1', {
projectType: 'library',
sourceRoot: 'libs/lib1/src',
@@ -149,20 +192,20 @@ describe('SCAM Generator', () => {
// ASSERT
const componentModuleSource = tree.read(
- 'libs/lib1/feature/src/lib/example/example.module.ts',
+ 'libs/lib1/feature/src/lib/example/example-module.ts',
'utf-8'
);
expect(componentModuleSource).toMatchInlineSnapshot(`
"import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
- import { ExampleComponent } from './example.component';
+ import { Example } from './example';
@NgModule({
imports: [CommonModule],
- declarations: [ExampleComponent],
- exports: [ExampleComponent],
+ declarations: [Example],
+ exports: [Example],
})
- export class ExampleComponentModule {}
+ export class ExampleModule {}
"
`);
const secondaryEntryPointSource = tree.read(
@@ -170,8 +213,8 @@ describe('SCAM Generator', () => {
'utf-8'
);
expect(secondaryEntryPointSource).toMatchInlineSnapshot(`
- "export * from './lib/example/example.component';
- export * from './lib/example/example.module';"
+ "export * from './lib/example/example';
+ export * from './lib/example/example-module';"
`);
});
@@ -188,13 +231,13 @@ describe('SCAM Generator', () => {
name: '404',
path: 'apps/app1/src/app/example/example',
})
- ).rejects.toThrow('Class name "404Component" is invalid.');
+ ).rejects.toThrow('Class name "404" is invalid.');
});
describe('--path', () => {
- it('should not throw when the directory does not exist under project', async () => {
+ it('should not throw when the directory exists under project', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -211,7 +254,7 @@ describe('SCAM Generator', () => {
// ASSERT
const componentSource = tree.read(
- 'apps/app1/src/app/random/example/example.component.ts',
+ 'apps/app1/src/app/random/example/example.ts',
'utf-8'
);
expect(componentSource).toMatchInlineSnapshot(`
@@ -221,24 +264,24 @@ describe('SCAM Generator', () => {
@Component({
selector: 'example',
standalone: false,
- templateUrl: './example.component.html',
- styleUrl: './example.component.css'
+ templateUrl: './example.html',
+ styleUrl: './example.css'
})
- export class ExampleComponent {}
+ export class Example {}
@NgModule({
imports: [CommonModule],
- declarations: [ExampleComponent],
- exports: [ExampleComponent],
+ declarations: [Example],
+ exports: [Example],
})
- export class ExampleComponentModule {}
+ export class ExampleModule {}
"
`);
});
it('should not matter if the directory starts with a slash', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -255,7 +298,7 @@ describe('SCAM Generator', () => {
// ASSERT
const componentSource = tree.read(
- 'apps/app1/src/app/random/example/example.component.ts',
+ 'apps/app1/src/app/random/example/example.ts',
'utf-8'
);
expect(componentSource).toMatchInlineSnapshot(`
@@ -265,24 +308,24 @@ describe('SCAM Generator', () => {
@Component({
selector: 'example',
standalone: false,
- templateUrl: './example.component.html',
- styleUrl: './example.component.css'
+ templateUrl: './example.html',
+ styleUrl: './example.css'
})
- export class ExampleComponent {}
+ export class Example {}
@NgModule({
imports: [CommonModule],
- declarations: [ExampleComponent],
- exports: [ExampleComponent],
+ declarations: [Example],
+ exports: [Example],
})
- export class ExampleComponentModule {}
+ export class ExampleModule {}
"
`);
});
it('should throw when the directory does not exist under project', async () => {
// ARRANGE
- const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
+ const tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'app1', {
projectType: 'application',
sourceRoot: 'apps/app1/src',
@@ -302,4 +345,86 @@ describe('SCAM Generator', () => {
);
});
});
+
+ describe('compat', () => {
+ it('should generate the component with the "component" type for versions lower than v20', async () => {
+ const tree = createTreeWithEmptyWorkspace();
+ updateJson(tree, 'package.json', (json) => {
+ json.dependencies['@angular/core'] = '~19.2.0';
+ return json;
+ });
+ addProjectConfiguration(tree, 'app1', {
+ projectType: 'application',
+ sourceRoot: 'apps/app1/src',
+ root: 'apps/app1',
+ });
+
+ await scamGenerator(tree, {
+ name: 'example',
+ path: 'apps/app1/src/app/example/example',
+ inlineScam: true,
+ skipFormat: true,
+ });
+
+ const componentSource = tree.read(
+ 'apps/app1/src/app/example/example.component.ts',
+ 'utf-8'
+ );
+ expect(componentSource).toMatchInlineSnapshot(`
+ "import { Component, NgModule } from '@angular/core';
+ import { CommonModule } from '@angular/common';
+
+ @Component({
+ selector: 'example',
+ standalone: false,
+ templateUrl: './example.component.html',
+ styleUrl: './example.component.css'
+ })
+ export class ExampleComponent {}
+
+ @NgModule({
+ imports: [CommonModule],
+ declarations: [ExampleComponent],
+ exports: [ExampleComponent],
+ })
+ export class ExampleComponentModule {}
+ "
+ `);
+ });
+
+ it('should generate the module file with the "." type separator for versions lower than v20', async () => {
+ const tree = createTreeWithEmptyWorkspace();
+ updateJson(tree, 'package.json', (json) => {
+ json.dependencies['@angular/core'] = '~19.2.0';
+ return json;
+ });
+ addProjectConfiguration(tree, 'app1', {
+ projectType: 'application',
+ sourceRoot: 'apps/app1/src',
+ root: 'apps/app1',
+ });
+
+ await scamGenerator(tree, {
+ name: 'example',
+ path: 'apps/app1/src/app/example/example',
+ inlineScam: false,
+ skipFormat: true,
+ });
+
+ expect(tree.read('apps/app1/src/app/example/example.module.ts', 'utf-8'))
+ .toMatchInlineSnapshot(`
+ "import { NgModule } from '@angular/core';
+ import { CommonModule } from '@angular/common';
+ import { ExampleComponent } from './example.component';
+
+ @NgModule({
+ imports: [CommonModule],
+ declarations: [ExampleComponent],
+ exports: [ExampleComponent],
+ })
+ export class ExampleComponentModule {}
+ "
+ `);
+ });
+ });
});
diff --git a/packages/angular/src/generators/scam/schema.d.ts b/packages/angular/src/generators/scam/schema.d.ts
index 621e09425c..6489f9e0bd 100644
--- a/packages/angular/src/generators/scam/schema.d.ts
+++ b/packages/angular/src/generators/scam/schema.d.ts
@@ -26,4 +26,5 @@ export interface NormalizedSchema extends Schema {
symbolName: string;
export: boolean;
inlineScam: boolean;
+ modulePath: string;
}
diff --git a/packages/angular/src/generators/scam/schema.json b/packages/angular/src/generators/scam/schema.json
index 22c1e96213..dea7e9c9e1 100644
--- a/packages/angular/src/generators/scam/schema.json
+++ b/packages/angular/src/generators/scam/schema.json
@@ -94,8 +94,7 @@
},
"type": {
"type": "string",
- "description": "Adds a developer-defined type to the filename, in the format `name.type.ts`.",
- "default": "component"
+ "description": "Append a custom type to the component's filename. It defaults to 'component' for Angular versions below v20. For Angular v20 and above, no type is appended unless specified."
},
"prefix": {
"type": "string",
diff --git a/packages/angular/src/generators/setup-mf/__snapshots__/setup-mf.spec.ts.snap b/packages/angular/src/generators/setup-mf/__snapshots__/setup-mf.spec.ts.snap
index c3b35d31fb..8d96173439 100644
--- a/packages/angular/src/generators/setup-mf/__snapshots__/setup-mf.spec.ts.snap
+++ b/packages/angular/src/generators/setup-mf/__snapshots__/setup-mf.spec.ts.snap
@@ -21,7 +21,7 @@ fetch('/module-federation.manifest.json')
`;
exports[`Init MF --federationType=dynamic should wire up existing remote to dynamic host correctly 1`] = `
-"import { NxWelcomeComponent } from './nx-welcome.component';
+"import { NxWelcome } from './nx-welcome';
import { Route } from '@angular/router';
import { loadRemote } from '@module-federation/enhanced/runtime';
@@ -32,13 +32,13 @@ export const appRoutes: Route[] = [
},
{
path: '',
- component: NxWelcomeComponent
+ component: NxWelcome
},];
"
`;
exports[`Init MF --federationType=dynamic should wire up existing remote to dynamic host correctly when --typescriptConfiguration=true 1`] = `
-"import { NxWelcomeComponent } from './nx-welcome.component';
+"import { NxWelcome } from './nx-welcome';
import { Route } from '@angular/router';
import { loadRemote } from '@module-federation/enhanced/runtime';
@@ -49,13 +49,13 @@ export const appRoutes: Route[] = [
},
{
path: '',
- component: NxWelcomeComponent
+ component: NxWelcome
},];
"
`;
exports[`Init MF should add a remote application and add it to a specified host applications router config 1`] = `
-"import { NxWelcomeComponent } from './nx-welcome.component';
+"import { NxWelcome } from './nx-welcome';
import { Route } from '@angular/router';
export const appRoutes: Route[] = [
@@ -69,7 +69,7 @@ export const appRoutes: Route[] = [
},
{
path: '',
- component: NxWelcomeComponent
+ component: NxWelcome
},];
"
`;
@@ -175,7 +175,7 @@ export default config;
`;
exports[`Init MF should add a remote to dynamic host correctly 1`] = `
-"import { NxWelcomeComponent } from './nx-welcome.component';
+"import { NxWelcome } from './nx-welcome';
import { Route } from '@angular/router';
import { loadRemote } from '@module-federation/enhanced/runtime';
@@ -186,13 +186,13 @@ export const appRoutes: Route[] = [
},
{
path: '',
- component: NxWelcomeComponent
+ component: NxWelcome
},];
"
`;
exports[`Init MF should add a remote to dynamic host correctly when --typescriptConfiguration=true 1`] = `
-"import { NxWelcomeComponent } from './nx-welcome.component';
+"import { NxWelcome } from './nx-welcome';
import { Route } from '@angular/router';
import { loadRemote } from '@module-federation/enhanced/runtime';
@@ -203,7 +203,7 @@ export const appRoutes: Route[] = [
},
{
path: '',
- component: NxWelcomeComponent
+ component: NxWelcome
},];
"
`;
@@ -264,7 +264,7 @@ exports[`Init MF should create webpack and mf configs correctly 4`] = `
module.exports = {
name: 'remote1',
exposes: {
- './Module': 'remote1/src/app/remote-entry/entry.module.ts',
+ './Module': 'remote1/src/app/remote-entry/entry-module.ts',
},
};
"
@@ -329,7 +329,7 @@ exports[`Init MF should create webpack and mf configs correctly when --typescrip
const config: ModuleFederationConfig = {
name: 'remote1',
exposes: {
- './Module': 'remote1/src/app/remote-entry/entry.module.ts',
+ './Module': 'remote1/src/app/remote-entry/entry-module.ts',
},
};
@@ -348,7 +348,7 @@ exports[`Init MF should generate the remote entry component correctly when prefi
standalone: false,
template: \`\`
})
-export class RemoteEntryComponent {}
+export class RemoteEntry {}
"
`;
@@ -360,7 +360,7 @@ exports[`Init MF should generate the remote entry module and component correctly
standalone: false,
template: \`\`,
})
-export class RemoteEntryComponent {}
+export class RemoteEntry {}
"
`;
@@ -368,14 +368,14 @@ exports[`Init MF should generate the remote entry module and component correctly
"import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
-import { RemoteEntryComponent } from './entry.component';
-import { NxWelcomeComponent } from './nx-welcome.component';
+import { RemoteEntry } from './entry';
+import { NxWelcome } from './nx-welcome';
@NgModule({
- declarations: [RemoteEntryComponent, NxWelcomeComponent],
+ declarations: [RemoteEntry, NxWelcome],
imports: [CommonModule],
providers: [],
- exports: [RemoteEntryComponent],
+ exports: [RemoteEntry],
})
export class RemoteEntryModule {}
"
diff --git a/packages/angular/src/generators/setup-mf/files/entry-module-files/entry.module.ts__tmpl__ b/packages/angular/src/generators/setup-mf/files/entry-module-files/__entryModuleFileName__.ts__tmpl__
similarity index 56%
rename from packages/angular/src/generators/setup-mf/files/entry-module-files/entry.module.ts__tmpl__
rename to packages/angular/src/generators/setup-mf/files/entry-module-files/__entryModuleFileName__.ts__tmpl__
index 82fc12618d..cfa6df9d99 100644
--- a/packages/angular/src/generators/setup-mf/files/entry-module-files/entry.module.ts__tmpl__
+++ b/packages/angular/src/generators/setup-mf/files/entry-module-files/__entryModuleFileName__.ts__tmpl__
@@ -2,17 +2,17 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';<% if(routing) { %>
import { RouterModule } from '@angular/router';<% } %>
-import { RemoteEntryComponent } from './entry.component';
-import { NxWelcomeComponent } from './nx-welcome.component';<% if(routing) { %>
+import { RemoteEntry<%= componentType %> } from './entry<%= componentFileSuffix %>';
+import { <%= nxWelcomeSymbolName %> } from './<%= nxWelcomeFileName %>';<% if(routing) { %>
import { remoteRoutes } from './entry.routes';<% } %>
@NgModule({
- declarations: [RemoteEntryComponent, NxWelcomeComponent],
+ declarations: [RemoteEntry<%= componentType %>, <%= nxWelcomeSymbolName %>],
imports: [
CommonModule,<% if(routing) { %>
RouterModule.forChild(remoteRoutes),<% } %>
],
providers: [],<% if(!routing) { %>
- exports: [RemoteEntryComponent],<% } %>
+ exports: [RemoteEntry<%= componentType %>],<% } %>
})
export class RemoteEntryModule {}
\ No newline at end of file
diff --git a/packages/angular/src/generators/setup-mf/files/entry-module-files/entry.routes.ts__tmpl__ b/packages/angular/src/generators/setup-mf/files/entry-module-files/entry.routes.ts__tmpl__
index 76cdef4997..a997287e1a 100644
--- a/packages/angular/src/generators/setup-mf/files/entry-module-files/entry.routes.ts__tmpl__
+++ b/packages/angular/src/generators/setup-mf/files/entry-module-files/entry.routes.ts__tmpl__
@@ -1,4 +1,4 @@
import { Route } from '@angular/router';
-import { RemoteEntryComponent } from './entry.component';
+import { RemoteEntry<%= componentType %> } from './entry<%= componentFileSuffix %>';
-export const remoteRoutes: Route[] = [{ path: '', component: RemoteEntryComponent }];
\ No newline at end of file
+export const remoteRoutes: Route[] = [{ path: '', component: RemoteEntry<%= componentType %> }];
\ No newline at end of file
diff --git a/packages/angular/src/generators/setup-mf/files/entry-module-files/entry.component.ts__tmpl__ b/packages/angular/src/generators/setup-mf/files/entry-module-files/entry__componentFileSuffix__.ts__tmpl__
similarity index 88%
rename from packages/angular/src/generators/setup-mf/files/entry-module-files/entry.component.ts__tmpl__
rename to packages/angular/src/generators/setup-mf/files/entry-module-files/entry__componentFileSuffix__.ts__tmpl__
index 04d0b9e1a2..440aea2585 100644
--- a/packages/angular/src/generators/setup-mf/files/entry-module-files/entry.component.ts__tmpl__
+++ b/packages/angular/src/generators/setup-mf/files/entry-module-files/entry__componentFileSuffix__.ts__tmpl__
@@ -7,4 +7,4 @@ import { Component } from '@angular/core';
selector: '<%= appName %>-entry',
template: ``<% } %>
})
-export class RemoteEntryComponent {}
+export class RemoteEntry<%= componentType %> {}
diff --git a/packages/angular/src/generators/setup-mf/files/host-files/__appFileName__.spec.ts__tmpl__ b/packages/angular/src/generators/setup-mf/files/host-files/__appFileName__.spec.ts__tmpl__
new file mode 100644
index 0000000000..c528bc61b9
--- /dev/null
+++ b/packages/angular/src/generators/setup-mf/files/host-files/__appFileName__.spec.ts__tmpl__
@@ -0,0 +1,43 @@
+import { fakeAsync, TestBed, tick } from '@angular/core/testing';
+import { <%= appSymbolName %> } from './<%= appFileName %>';
+import { <%= nxWelcomeSymbolName %> } from './<%= nxWelcomeFileName %>';
+import { Router, RouterModule } from '@angular/router';
+
+describe('<%= appSymbolName %>', () => {
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [
+ RouterModule.forRoot([
+ { path: '', component: <%= nxWelcomeSymbolName %> },
+ ]),<% if (standalone) { %>
+ <%= appSymbolName %>,
+ <%= nxWelcomeSymbolName %>,<% } %>
+ ],<% if (!standalone) { %>
+ declarations: [<%= appSymbolName %>, <%= nxWelcomeSymbolName %>],<% } %>
+ }).compileComponents();
+ });
+
+ it('should create the app', () => {
+ const fixture = TestBed.createComponent(<%= appSymbolName %>);
+ const app = fixture.componentInstance;
+ expect(app).toBeTruthy();
+ });
+
+ it(`should have as title '<%= appName %>'`, () => {
+ const fixture = TestBed.createComponent(<%= appSymbolName %>);
+ const app = fixture.componentInstance;
+ expect(app.title).toEqual('<%= appName %>');
+ });
+
+ it('should render title', fakeAsync(() => {
+ const fixture = TestBed.createComponent(<%= appSymbolName %>);
+ const router = TestBed.inject(Router);
+ fixture.ngZone?.run(() => router.navigate(['']));
+ tick();
+ fixture.detectChanges();
+ const compiled = fixture.nativeElement as HTMLElement;
+ expect(compiled.querySelector('h1')?.textContent).toContain(
+ 'Welcome <%= appName %>'
+ );
+ }));
+});
diff --git a/packages/angular/src/generators/setup-mf/files/host-files/app.component.spec.ts__tmpl__ b/packages/angular/src/generators/setup-mf/files/host-files/app.component.spec.ts__tmpl__
deleted file mode 100644
index be04c96682..0000000000
--- a/packages/angular/src/generators/setup-mf/files/host-files/app.component.spec.ts__tmpl__
+++ /dev/null
@@ -1,47 +0,0 @@
-import { fakeAsync, TestBed, tick } from '@angular/core/testing';
-import { AppComponent } from './app.component';
-import { NxWelcomeComponent } from './nx-welcome.component';<% if(useRouterTestingModule) { %>
-import { RouterTestingModule } from '@angular/router/testing';<% } %>
-import { Router<% if(!useRouterTestingModule) { %>, RouterModule<% } %> } from '@angular/router';
-
-describe('AppComponent', () => {
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [<% if(useRouterTestingModule) { %>
- RouterTestingModule.withRoutes([
- { path: '', component: NxWelcomeComponent },
- ]),<% } %><% if(!useRouterTestingModule) { %>
- RouterModule.forRoot([
- { path: '', component: NxWelcomeComponent },
- ]),<% } %><% if (standalone) { %>
- AppComponent,
- NxWelcomeComponent,<% } %>
- ],<% if (!standalone) { %>
- declarations: [AppComponent, NxWelcomeComponent],<% } %>
- }).compileComponents();
- });
-
- it('should create the app', () => {
- const fixture = TestBed.createComponent(AppComponent);
- const app = fixture.componentInstance;
- expect(app).toBeTruthy();
- });
-
- it(`should have as title '<%= appName %>'`, () => {
- const fixture = TestBed.createComponent(AppComponent);
- const app = fixture.componentInstance;
- expect(app.title).toEqual('<%= appName %>');
- });
-
- it('should render title', fakeAsync(() => {
- const fixture = TestBed.createComponent(AppComponent);
- const router = TestBed.inject(Router);
- fixture.ngZone?.run(() => router.navigate(['']));
- tick();
- fixture.detectChanges();
- const compiled = fixture.nativeElement as HTMLElement;
- expect(compiled.querySelector('h1')?.textContent).toContain(
- 'Welcome <%= appName %>'
- );
- }));
-});
diff --git a/packages/angular/src/generators/setup-mf/files/standalone-entry-component-files/entry.routes.ts__tmpl__ b/packages/angular/src/generators/setup-mf/files/standalone-entry-component-files/entry.routes.ts__tmpl__
index 76cdef4997..a997287e1a 100644
--- a/packages/angular/src/generators/setup-mf/files/standalone-entry-component-files/entry.routes.ts__tmpl__
+++ b/packages/angular/src/generators/setup-mf/files/standalone-entry-component-files/entry.routes.ts__tmpl__
@@ -1,4 +1,4 @@
import { Route } from '@angular/router';
-import { RemoteEntryComponent } from './entry.component';
+import { RemoteEntry<%= componentType %> } from './entry<%= componentFileSuffix %>';
-export const remoteRoutes: Route[] = [{ path: '', component: RemoteEntryComponent }];
\ No newline at end of file
+export const remoteRoutes: Route[] = [{ path: '', component: RemoteEntry<%= componentType %> }];
\ No newline at end of file
diff --git a/packages/angular/src/generators/setup-mf/files/standalone-entry-component-files/entry.component.ts__tmpl__ b/packages/angular/src/generators/setup-mf/files/standalone-entry-component-files/entry__componentFileSuffix__.ts__tmpl__
similarity index 66%
rename from packages/angular/src/generators/setup-mf/files/standalone-entry-component-files/entry.component.ts__tmpl__
rename to packages/angular/src/generators/setup-mf/files/standalone-entry-component-files/entry__componentFileSuffix__.ts__tmpl__
index 41e0b27433..8af5e868a7 100644
--- a/packages/angular/src/generators/setup-mf/files/standalone-entry-component-files/entry.component.ts__tmpl__
+++ b/packages/angular/src/generators/setup-mf/files/standalone-entry-component-files/entry__componentFileSuffix__.ts__tmpl__
@@ -1,13 +1,13 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
-import { NxWelcomeComponent } from './nx-welcome.component';
+import { <%= nxWelcomeSymbolName %> } from './<%= nxWelcomeFileName %>';
@Component({<% if (setStandaloneTrue) { %>
standalone: true,<% } %>
- imports: [CommonModule, NxWelcomeComponent],<% if (prefix) { %>
+ imports: [CommonModule, <%= nxWelcomeSymbolName %>],<% if (prefix) { %>
selector: '<%= prefix %>-<%= appName %>-entry',
template: `<<%= prefix %>-nx-welcome><%= prefix %>-nx-welcome>`<% } else { %>
selector: '<%= appName %>-entry',
template: ``<% } %>
})
-export class RemoteEntryComponent {}
+export class RemoteEntry<%= componentType %> {}
diff --git a/packages/angular/src/generators/setup-mf/files/ts-webpack/module-federation.config.ts__tmpl__ b/packages/angular/src/generators/setup-mf/files/ts-webpack/module-federation.config.ts__tmpl__
index af68795490..9688abab6a 100644
--- a/packages/angular/src/generators/setup-mf/files/ts-webpack/module-federation.config.ts__tmpl__
+++ b/packages/angular/src/generators/setup-mf/files/ts-webpack/module-federation.config.ts__tmpl__
@@ -17,7 +17,7 @@ const config: ModuleFederationConfig = {
remotes: [<% if (federationType === 'static') { remotes.forEach(function(remote) { %>'<%= remote.remoteName %>',<% }); } %>]<% } %><% if(type === 'remote') { %>
exposes: {<% if(standalone) { %>
'./Routes': '<%= projectRoot %>/src/app/remote-entry/entry.routes.ts',<% } else { %>
- './Module': '<%= projectRoot %>/src/app/remote-entry/entry.module.ts',<% } %>
+ './Module': '<%= projectRoot %>/src/app/remote-entry/<%= entryModuleFileName %>.ts',<% } %>
},<% } %>
};
diff --git a/packages/angular/src/generators/setup-mf/files/ts-webpack/tsconfig.lint.json__tmpl__ b/packages/angular/src/generators/setup-mf/files/ts-webpack/tsconfig.lint.json__tmpl__
index 1df0c1a0f2..ff3efdacd4 100644
--- a/packages/angular/src/generators/setup-mf/files/ts-webpack/tsconfig.lint.json__tmpl__
+++ b/packages/angular/src/generators/setup-mf/files/ts-webpack/tsconfig.lint.json__tmpl__
@@ -5,7 +5,7 @@
},
"include": [
"src/main.ts",
- <% if(type === "remote") { %> "src/remote-entry/<% if(standalone) { %>entry.routes.ts", <% } else { %> entry.module.ts", <% } } %>
+ <% if(type === "remote") { %> "src/remote-entry/<% if(standalone) { %>entry.routes.ts",<% } else { %><%= entryModuleFileName %>.ts",<% } } %>
"webpack.config.ts",
"webpack.prod.config.ts"
]
diff --git a/packages/angular/src/generators/setup-mf/files/webpack/module-federation.config.js__tmpl__ b/packages/angular/src/generators/setup-mf/files/webpack/module-federation.config.js__tmpl__
index 0fc24d6c7c..d23dd483e1 100644
--- a/packages/angular/src/generators/setup-mf/files/webpack/module-federation.config.js__tmpl__
+++ b/packages/angular/src/generators/setup-mf/files/webpack/module-federation.config.js__tmpl__
@@ -18,6 +18,6 @@ module.exports = {
remotes: [<% if (federationType === 'static') { remotes.forEach(function(remote) { %>'<%= remote.remoteName %>',<% }); } %>]<% } %><% if(type === 'remote') { %>
exposes: {<% if(standalone) { %>
'./Routes': '<%= projectRoot %>/src/app/remote-entry/entry.routes.ts',<% } else { %>
- './Module': '<%= projectRoot %>/src/app/remote-entry/entry.module.ts',<% } %>
+ './Module': '<%= projectRoot %>/src/app/remote-entry/<%= entryModuleFileName %>.ts',<% } %>
},<% } %>
};
diff --git a/packages/angular/src/generators/setup-mf/lib/add-remote-entry.ts b/packages/angular/src/generators/setup-mf/lib/add-remote-entry.ts
index 8790a67aca..07570d7a77 100644
--- a/packages/angular/src/generators/setup-mf/lib/add-remote-entry.ts
+++ b/packages/angular/src/generators/setup-mf/lib/add-remote-entry.ts
@@ -1,15 +1,25 @@
import type { Tree } from '@nx/devkit';
import { generateFiles, joinPathFragments } from '@nx/devkit';
import { addRoute } from '../../../utils/nx-devkit/route-utils';
-import type { Schema } from '../schema';
import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
+import type { NormalizedOptions } from '../schema';
export function addRemoteEntry(
tree: Tree,
- { appName, routing, prefix, standalone }: Schema,
+ options: NormalizedOptions,
appRoot: string
) {
const { major: angularMajorVersion } = getInstalledAngularVersionInfo(tree);
+ const {
+ appName,
+ routing,
+ prefix,
+ standalone,
+ componentType,
+ componentFileSuffix,
+ nxWelcomeComponentInfo,
+ entryModuleFileName,
+ } = options;
generateFiles(
tree,
@@ -25,9 +35,14 @@ export function addRemoteEntry(
appName,
routing,
prefix,
- // Angular v19 or higher defaults to true, while v18 or lower defaults to false
+ // Angular v19 or higher defaults to true, while lower versions default to false
setStandaloneFalse: angularMajorVersion >= 19,
setStandaloneTrue: angularMajorVersion < 19,
+ componentType,
+ componentFileSuffix,
+ entryModuleFileName,
+ nxWelcomeFileName: nxWelcomeComponentInfo.extensionlessFileName,
+ nxWelcomeSymbolName: nxWelcomeComponentInfo.symbolName,
}
);
@@ -41,7 +56,7 @@ export function addRemoteEntry(
addRoute(
tree,
joinPathFragments(appRoot, 'src/app/app.routes.ts'),
- `{ path: '', loadChildren: () => import('./remote-entry/entry.module').then(m => m.RemoteEntryModule) }`
+ `{ path: '', loadChildren: () => import('./remote-entry/${entryModuleFileName}').then(m => m.RemoteEntryModule) }`
);
}
}
diff --git a/packages/angular/src/generators/setup-mf/lib/add-remote-to-host.ts b/packages/angular/src/generators/setup-mf/lib/add-remote-to-host.ts
index c8cb6e2fa7..7c8cd2417d 100644
--- a/packages/angular/src/generators/setup-mf/lib/add-remote-to-host.ts
+++ b/packages/angular/src/generators/setup-mf/lib/add-remote-to-host.ts
@@ -207,10 +207,20 @@ function addLazyLoadedRouteToHostAppModule(
}`
);
- const pathToAppComponentTemplate = joinPathFragments(
+ let pathToAppComponentTemplate = joinPathFragments(
hostAppConfig.sourceRoot,
'app/app.component.html'
);
+ const candidatePaths = [
+ pathToAppComponentTemplate,
+ joinPathFragments(hostAppConfig.sourceRoot, 'app/app.html'),
+ ];
+ for (const path of candidatePaths) {
+ if (tree.exists(path)) {
+ pathToAppComponentTemplate = path;
+ break;
+ }
+ }
const appComponent = tree.read(pathToAppComponentTemplate, 'utf-8');
if (
appComponent.includes(`