feat(angular): support angular v17 (#19689)

This commit is contained in:
Leosvel Pérez Espinosa 2023-11-07 21:45:42 +01:00 committed by GitHub
parent 0197444df5
commit 25d6ec3a92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
255 changed files with 8794 additions and 5673 deletions

View File

@ -145,6 +145,8 @@ Customizes the initial content of your workspace. Default presets include: ["app
Type: `boolean`
Default: `true`
Add a routing setup for an Angular app
### skipGit
@ -155,10 +157,18 @@ Default: `false`
Skip initializing a git repository
### ssr
Type: `boolean`
Enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering) for the Angular application
### standaloneApi
Type: `boolean`
Default: `true`
Use Standalone Components if generating an Angular app
### style

View File

@ -245,7 +245,7 @@
},
"outputPath": {
"type": "string",
"description": "The full path for the new output directory, relative to the current workspace.\nBy default, writes output to a folder named dist/ in the current project."
"description": "The full path for the new output directory, relative to the current workspace."
},
"resourcesOutputPath": {
"type": "string",
@ -292,12 +292,12 @@
},
"vendorChunk": {
"type": "boolean",
"description": "Generate a seperate bundle containing only vendor libraries. This option should only be used for development to reduce the incremental compilation time.",
"description": "Generate a separate bundle containing only vendor libraries. This option should only be used for development to reduce the incremental compilation time.",
"default": false
},
"commonChunk": {
"type": "boolean",
"description": "Generate a seperate bundle containing code used across multiple bundles.",
"description": "Generate a separate bundle containing code used across multiple bundles.",
"default": true
},
"baseHref": {
@ -426,6 +426,7 @@
},
{
"const": false,
"type": "boolean",
"description": "Does not generate an `index.html` file."
}
]
@ -503,7 +504,7 @@
"enum": ["none", "anonymous", "use-credentials"]
},
"allowedCommonJsDependencies": {
"description": "A list of CommonJS packages that are allowed to be used without a build time warning.",
"description": "A list of CommonJS or AMD packages that are allowed to be used without a build time warning. Use `'*'` to allow all.",
"type": "array",
"items": { "type": "string" },
"default": []

View File

@ -9,12 +9,18 @@
"description": "The module-federation-dev-server executor is reserved exclusively for use with host Module Federation applications. It allows the user to specify which remote applications should be served with the host.",
"type": "object",
"presets": [
{ "name": "Using a Different Port", "keys": ["browserTarget", "port"] }
{ "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]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
"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 in Nx v18."
},
"buildTarget": {
"type": "string",
"description": "A build builder target to serve in the format of `project:target[:configuration]`.",
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$"
},
"port": {
@ -129,8 +135,11 @@
}
},
"additionalProperties": false,
"required": ["browserTarget"],
"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 \"browserTarget\": \"host:build:production\"\n },\n \"development\": {\n \"browserTarget\": \"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 \"browserTarget\": \"host:build:production\"\n },\n \"development\": {\n \"browserTarget\": \"host:build:development\"\n }\n },\n \"defaultConfiguration\": \"development\",\n \"options\": {\n \"port\": 4200,\n \"publicHost\": \"http://localhost:4200\",\n \"devRemotes\": [\"remote1\", \"remote2\"]\n }\n }\n}\n```\n\n{% /tab %}\n\n{% /tabs %}\n"
"anyOf": [
{ "required": ["buildTarget"] },
{ "required": ["browserTarget"] }
],
"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\": [\"remote1\", \"remote2\"]\n }\n }\n}\n```\n\n{% /tab %}\n\n{% /tabs %}\n"
},
"description": "The module-federation-dev-server executor is reserved exclusively for use with host Module Federation applications. It allows the user to specify which remote applications should be served with the host.",
"aliases": [],

View File

@ -75,7 +75,7 @@
"oneOf": [
{
"type": "array",
"description": "A list of polyfills to include in the build. Can be a full path for a file, relative to the current workspace or module specifier. Example: 'zone.js'. _Note: supported in Angular versions >= 15.0.0_.",
"description": "A list of polyfills to include in the build. Can be a full path for a file, relative to the current workspace or module specifier. Example: 'zone.js'.",
"items": { "type": "string", "uniqueItems": true },
"default": []
},
@ -137,7 +137,7 @@
"input": {
"type": "string",
"description": "The file to include.",
"pattern": "\\.(?:css|scss|sass|less|styl)$"
"pattern": "\\.(?:css|scss|sass|less)$"
},
"bundleName": {
"type": "string",
@ -156,7 +156,7 @@
{
"type": "string",
"description": "The file to include.",
"pattern": "\\.(?:css|scss|sass|less|styl)$"
"pattern": "\\.(?:css|scss|sass|less)$"
}
]
},
@ -282,7 +282,7 @@
},
"outputPath": {
"type": "string",
"description": "The full path for the new output directory, relative to the current workspace.\n\nBy default, writes output to a folder named dist/ in the current project."
"description": "The full path for the new output directory, relative to the current workspace."
},
"resourcesOutputPath": {
"type": "string",
@ -330,12 +330,12 @@
},
"vendorChunk": {
"type": "boolean",
"description": "Generate a seperate bundle containing only vendor libraries. This option should only be used for development to reduce the incremental compilation time.",
"description": "Generate a separate bundle containing only vendor libraries. This option should only be used for development to reduce the incremental compilation time.",
"default": false
},
"commonChunk": {
"type": "boolean",
"description": "Generate a seperate bundle containing code used across multiple bundles.",
"description": "Generate a separate bundle containing code used across multiple bundles.",
"default": true
},
"baseHref": {
@ -537,7 +537,7 @@
"enum": ["none", "anonymous", "use-credentials"]
},
"allowedCommonJsDependencies": {
"description": "A list of CommonJS packages that are allowed to be used without a build time warning.",
"description": "A list of CommonJS or AMD packages that are allowed to be used without a build time warning. Use `'*'` to allow all.",
"type": "array",
"items": { "type": "string" },
"default": []

View File

@ -7,15 +7,21 @@
"$schema": "http://json-schema.org/draft-07/schema",
"title": "Schema for Webpack Dev Server",
"description": "The webpack-dev-server executor is very similar to the standard dev server builder provided by the Angular Devkit. It is usually used in tandem with `@nx/angular:webpack-browser` when your Angular application uses a custom webpack configuration.",
"examplesFile": "##### Seving an application with 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\nYour `project.json` file should contain a `build` and `serve` target that matches the following:\n\n```json\n\"build\": {\n \"executor\": \"@nx/angular:webpack-browser\",\n \"options\": {\n ...\n \"customWebpackConfig\": {\n \"path\": \"apps/appName/webpack.config.js\"\n }\n }\n},\n\"serve\": {\n \"executor\": \"@nx/angular:webpack-dev-server\",\n \"configurations\": {\n \"production\": {\n \"browserTarget\": \"appName:build:production\"\n },\n \"development\": {\n \"browserTarget\": \"appName:build:development\"\n }\n },\n \"defaultConfiguration\": \"development\",\n}\n```\n",
"examplesFile": "##### Seving an application with 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\nYour `project.json` file should contain a `build` and `serve` target that matches the following:\n\n```json\n\"build\": {\n \"executor\": \"@nx/angular:webpack-browser\",\n \"options\": {\n ...\n \"customWebpackConfig\": {\n \"path\": \"apps/appName/webpack.config.js\"\n }\n }\n},\n\"serve\": {\n \"executor\": \"@nx/angular:webpack-dev-server\",\n \"configurations\": {\n \"production\": {\n \"buildTarget\": \"appName:build:production\"\n },\n \"development\": {\n \"buildTarget\": \"appName:build:development\"\n }\n },\n \"defaultConfiguration\": \"development\",\n}\n```\n",
"type": "object",
"presets": [
{ "name": "Using a Different Port", "keys": ["browserTarget", "port"] }
{ "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]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
"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 in Nx v18."
},
"buildTarget": {
"type": "string",
"description": "A build builder target to serve in the format of `project:target[:configuration]`.",
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$"
},
"port": {
@ -106,7 +112,10 @@
}
},
"additionalProperties": false,
"required": ["browserTarget"]
"anyOf": [
{ "required": ["buildTarget"] },
{ "required": ["browserTarget"] }
]
},
"description": "The `webpack-dev-server` executor is very similar to the standard `dev-server` builder provided by the Angular Devkit. It is usually used in tandem with `@nrwl/angular:webpack-browser` when your Angular application uses a custom webpack configuration.",
"aliases": [],

View File

@ -187,7 +187,7 @@
},
"vendorChunk": {
"type": "boolean",
"description": "Generate a seperate bundle containing only vendor libraries. This option should only be used for development to reduce the incremental compilation time. _Note: supported in Angular versions >= 15.1.0_",
"description": "Generate a separate bundle containing only vendor libraries. This option should only be used for development to reduce the incremental compilation time. _Note: supported in Angular versions >= 15.1.0_",
"default": false
},
"verbose": {
@ -263,13 +263,6 @@
"items": { "type": "string" },
"default": []
},
"bundleDependencies": {
"description": "Which external dependencies to bundle into the bundle. By default, all of node_modules will be bundled. _Note: This is only supported in Angular versions >= 14.0.0, < 15.0.0. It was removed in Angular 15._",
"oneOf": [
{ "type": "boolean" },
{ "type": "string", "enum": ["none", "all"] }
]
},
"statsJson": {
"type": "boolean",
"description": "Generates a 'stats.json' file which can be analyzed using tools such as 'webpack-bundle-analyzer'.",

View File

@ -53,9 +53,8 @@
},
"routing": {
"type": "boolean",
"description": "Generate a routing module.",
"default": false,
"x-prompt": "Would you like to configure routing for this application?",
"description": "Enable routing for the application.",
"default": true,
"x-priority": "important"
},
"inlineStyle": {
@ -152,8 +151,9 @@
"default": false
},
"standalone": {
"description": "Generate an application that is setup to use standalone components. _Note: This is only supported in Angular versions >= 14.1.0_",
"description": "Generate an application that is setup to use standalone components.",
"type": "boolean",
"default": true,
"x-priority": "important"
},
"rootProject": {
@ -169,10 +169,17 @@
"default": false
},
"bundler": {
"description": "Bundler to use to build the application.",
"description": "Bundler to use to build the application. It defaults to `esbuild` for Angular versions >= 17.0.0. Otherwise, it defaults to `webpack`. _Note: The `esbuild` bundler is only considered stable from Angular v17._",
"type": "string",
"enum": ["webpack", "esbuild"],
"default": "webpack"
"x-prompt": "Which bundler do you want to use to build the application?",
"x-priority": "important"
},
"ssr": {
"description": "Creates an application with Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering) enabled.",
"type": "boolean",
"x-prompt": "Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)?",
"default": false
}
},
"additionalProperties": false,

View File

@ -58,9 +58,9 @@
"alias": "t"
},
"standalone": {
"description": "Whether the generated component is standalone. _Note: This is only supported in Angular versions >= 14.1.0_.",
"description": "Whether the generated component is standalone.",
"type": "boolean",
"default": false,
"default": true,
"x-priority": "important"
},
"viewEncapsulation": {

View File

@ -59,9 +59,9 @@
"description": "The HTML selector to use for this directive."
},
"standalone": {
"description": "Whether the generated directive is standalone. _Note: This is only supported in Angular versions >= 14.1.0_.",
"description": "Whether the generated directive is standalone.",
"type": "boolean",
"default": false
"default": true
},
"flat": {
"type": "boolean",

View File

@ -67,9 +67,9 @@
"default": "cypress"
},
"standalone": {
"description": "Whether to generate the remote application with standalone components if it needs to be created. _Note: This is only supported in Angular versions >= 14.1.0_",
"description": "Whether to generate the remote application with standalone components if it needs to be created.",
"type": "boolean",
"default": false
"default": true
},
"host": {
"type": "string",

View File

@ -162,8 +162,8 @@
},
"standalone": {
"type": "boolean",
"description": "Whether to generate a host application that uses standalone components. _Note: This is only supported in Angular versions >= 14.1.0_",
"default": false
"description": "Whether to generate a host application that uses standalone components.",
"default": true
},
"ssr": {
"description": "Whether to configure SSR for the host application",

View File

@ -141,65 +141,65 @@
},
"standalone": {
"type": "boolean",
"description": "Generate a library that uses a standalone component instead of a module as the entry point. _Note: This is only supported in Angular versions >= 14.1.0_",
"default": false
"description": "Generate a library that uses a standalone component instead of a module as the entry point.",
"default": true
},
"displayBlock": {
"description": "Specifies if the component generated style will contain `:host { display: block; }`. Disclaimer: This option is only valid when `--standalone` is set to `true`. _Note: This is only supported in Angular versions >= 14.1.0_",
"description": "Specifies if the component generated style will contain `:host { display: block; }`. Disclaimer: This option is only valid when `--standalone` is set to `true`.",
"type": "boolean",
"default": false,
"alias": "b"
},
"inlineStyle": {
"description": "Include styles inline in the component.ts file. Only CSS styles can be included inline. By default, an external styles file is created and referenced in the component.ts file. Disclaimer: This option is only valid when `--standalone` is set to `true`. _Note: This is only supported in Angular versions >= 14.1.0_",
"description": "Include styles inline in the component.ts file. Only CSS styles can be included inline. By default, an external styles file is created and referenced in the component.ts file. Disclaimer: This option is only valid when `--standalone` is set to `true`.",
"type": "boolean",
"default": false,
"alias": "s"
},
"inlineTemplate": {
"description": "Include template inline in the component.ts file. By default, an external template file is created and referenced in the component.ts file. Disclaimer: This option is only valid when `--standalone` is set to `true`. _Note: This is only supported in Angular versions >= 14.1.0_",
"description": "Include template inline in the component.ts file. By default, an external template file is created and referenced in the component.ts file. Disclaimer: This option is only valid when `--standalone` is set to `true`.",
"type": "boolean",
"default": false,
"alias": "t"
},
"viewEncapsulation": {
"description": "The view encapsulation strategy to use in the new component. Disclaimer: This option is only valid when `--standalone` is set to `true`. _Note: This is only supported in Angular versions >= 14.1.0_",
"description": "The view encapsulation strategy to use in the new component. Disclaimer: This option is only valid when `--standalone` is set to `true`.",
"enum": ["Emulated", "None", "ShadowDom"],
"type": "string",
"alias": "v"
},
"changeDetection": {
"description": "The change detection strategy to use in the new component. Disclaimer: This option is only valid when `--standalone` is set to `true`. _Note: This is only supported in Angular versions >= 14.1.0_",
"description": "The change detection strategy to use in the new component. Disclaimer: This option is only valid when `--standalone` is set to `true`.",
"enum": ["Default", "OnPush"],
"type": "string",
"default": "Default",
"alias": "c"
},
"style": {
"description": "The file extension or preprocessor to use for style files, or `none` to skip generating the style file. Disclaimer: This option is only valid when `--standalone` is set to `true`. _Note: This is only supported in Angular versions >= 14.1.0_",
"description": "The file extension or preprocessor to use for style files, or `none` to skip generating the style file. Disclaimer: This option is only valid when `--standalone` is set to `true`.",
"type": "string",
"default": "css",
"enum": ["css", "scss", "sass", "less", "none"]
},
"skipTests": {
"type": "boolean",
"description": "Do not create `spec.ts` test files for the new component. Disclaimer: This option is only valid when `--standalone` is set to `true`. _Note: This is only supported in Angular versions >= 14.1.0_",
"description": "Do not create `spec.ts` test files for the new component. Disclaimer: This option is only valid when `--standalone` is set to `true`.",
"default": false
},
"selector": {
"type": "string",
"format": "html-selector",
"description": "The HTML selector to use for this component. Disclaimer: This option is only valid when `--standalone` is set to `true`. _Note: This is only supported in Angular versions >= 14.1.0_"
"description": "The HTML selector to use for this component. Disclaimer: This option is only valid when `--standalone` is set to `true`."
},
"skipSelector": {
"type": "boolean",
"default": false,
"description": "Specifies if the component should have a selector or not. Disclaimer: This option is only valid when `--standalone` is set to `true`. _Note: This is only supported in Angular versions >= 14.1.0_"
"description": "Specifies if the component should have a selector or not. Disclaimer: This option is only valid when `--standalone` is set to `true`."
},
"flat": {
"type": "boolean",
"default": false,
"description": "Ensure the generated standalone component is not placed in a subdirectory. Disclaimer: This option is only valid when `--standalone` is set to `true`. _Note: This is only supported in Angular versions >= 14.1.0_"
"description": "Ensure the generated standalone component is not placed in a subdirectory. Disclaimer: This option is only valid when `--standalone` is set to `true`."
}
},
"additionalProperties": false,

View File

@ -18,13 +18,13 @@
},
"parent": {
"type": "string",
"description": "The path to the file where the state will be registered. For NgModule usage, this will be your Feature Module. For Standalone API usage, this will be your Routes definition file for your feature state. The host directory will create/use the new state directory. _Note: The Standalone API usage is only supported in Angular versions >= 14.1.0_.",
"description": "The path to the file where the state will be registered. For NgModule usage, this will be your Feature Module. For Standalone API usage, this will be your 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"
},
"route": {
"type": "string",
"description": "The route that the Standalone NgRx Providers should be added to. _Note: This is only supported in Angular versions >= 14.1.0_.",
"description": "The route that the Standalone NgRx Providers should be added to.",
"default": "''"
},
"minimal": {

View File

@ -29,7 +29,7 @@
},
"route": {
"type": "string",
"description": "The route that the Standalone NgRx Providers should be added to. _Note: This is only supported in Angular versions >= 14.1.0_.",
"description": "The route that the Standalone NgRx Providers should be added to.",
"default": "''"
},
"directory": {

View File

@ -42,13 +42,13 @@
},
"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. _Note: The Standalone API usage is only supported in Angular versions >= 14.1.0_.",
"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"
},
"route": {
"type": "string",
"description": "The route that the Standalone NgRx Providers should be added to. _Note: This is only supported in Angular versions >= 14.1.0_.",
"description": "The route that the Standalone NgRx Providers should be added to.",
"default": "''"
},
"directory": {

View File

@ -51,9 +51,9 @@
"description": "Do not import this pipe into the owning NgModule."
},
"standalone": {
"description": "Whether the generated pipe is standalone. _Note: This is only supported in Angular versions >= 14.1.0_.",
"description": "Whether the generated pipe is standalone.",
"type": "boolean",
"default": false
"default": true
},
"module": {
"type": "string",

View File

@ -155,9 +155,9 @@
"x-priority": "internal"
},
"standalone": {
"description": "Whether to generate a remote application with standalone components. _Note: This is only supported in Angular versions >= 14.1.0_",
"description": "Whether to generate a remote application with standalone components.",
"type": "boolean",
"default": false
"default": true
},
"ssr": {
"description": "Whether to configure SSR for the remote application to be consumed by a host application using SSR.",

View File

@ -6,7 +6,7 @@
"$id": "GeneratorAngularScamToStandalone",
"cli": "nx",
"title": "Convert an Inline SCAM to Standalone Component",
"description": "Convert an Inline SCAM to a Standalone Component. _Note: This generator is only supported with Angular versions >= 14.1.0_.",
"description": "Convert an Inline SCAM to a Standalone Component.",
"type": "object",
"properties": {
"component": {

View File

@ -71,8 +71,8 @@
},
"standalone": {
"type": "boolean",
"description": "Whether the application is a standalone application. _Note: This is only supported in Angular versions >= 14.1.0_",
"default": false
"description": "Whether the application is a standalone application.",
"default": true
},
"typescriptConfiguration": {
"type": "boolean",

View File

@ -19,7 +19,7 @@
"appId": {
"type": "string",
"format": "html-selector",
"description": "The `appId` to use with `withServerTransition`. _Note: This is only used in Angular versions <16.0.0. It's deprecated since Angular 16._",
"description": "The `appId` to use with `withServerTransition`. _Note: This is only used in Angular versions <16.0.0. It's deprecated since Angular 16 and not supported since Angular 17._",
"default": "serverApp"
},
"main": {
@ -51,11 +51,11 @@
},
"standalone": {
"type": "boolean",
"description": "Use Standalone Components to bootstrap SSR. _Note: This is only supported in Angular versions >= 14.1.0_."
"description": "Use Standalone Components to bootstrap SSR."
},
"hydration": {
"type": "boolean",
"description": "Set up Hydration for the SSR application. _Note: This is only supported in Angular versions >= 16.0.0_."
"description": "Set up Hydration for the SSR application. It defaults to `true` for Angular versions >= 17.0.0. Otherwise, it defaults to `false`. _Note: This is only supported in Angular versions >= 16.0.0_."
},
"skipFormat": {
"type": "boolean",

View File

@ -145,6 +145,8 @@ Customizes the initial content of your workspace. Default presets include: ["app
Type: `boolean`
Default: `true`
Add a routing setup for an Angular app
### skipGit
@ -155,10 +157,18 @@ Default: `false`
Skip initializing a git repository
### ssr
Type: `boolean`
Enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering) for the Angular application
### standaloneApi
Type: `boolean`
Default: `true`
Use Standalone Components if generating an Angular app
### style

View File

@ -69,6 +69,11 @@
"description": "The tool to use for running e2e tests.",
"type": "string",
"enum": ["cypress", "playwright", "jest", "detox", "none"]
},
"ssr": {
"description": "Enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering) for the Angular application.",
"type": "boolean",
"default": false
}
},
"additionalProperties": true,

View File

@ -86,6 +86,11 @@
"description": "The tool to use for running e2e tests.",
"type": "string",
"enum": ["cypress", "playwright", "jest", "detox", "none"]
},
"ssr": {
"description": "Enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering) for the Angular application.",
"type": "boolean",
"default": false
}
},
"required": ["preset", "name"],

View File

@ -85,13 +85,17 @@ The automated migration supports Angular CLI workspaces with a standard structur
Currently, the automated migration supports workspaces using the following executors (builders):
- `@angular-devkit/build-angular:application`
- `@angular-devkit/build-angular:browser`
- `@angular-devkit/build-angular:browser-esbuild`
- `@angular-devkit/build-angular:dev-server`
- `@angular-devkit/build-angular:extract-i18n`
- `@angular-devkit/build-angular:karma`
- `@angular-devkit/build-angular:ng-packagr`
- `@angular-devkit/build-angular:prerender`
- `@angular-devkit/build-angular:protractor`
- `@angular-devkit/build-angular:server`
- `@angular-devkit/build-angular:ssr-dev-server`
- `@angular-eslint/builder:lint`
- `@cypress/schematic:cypress`
- `@nguniversal/builders:prerender`

View File

@ -37,11 +37,11 @@ describe('Angular Module Federation', () => {
// generate host app
runCLI(
`generate @nx/angular:host ${hostApp} --style=css --project-name-and-root-format=as-provided --no-interactive`
`generate @nx/angular:host ${hostApp} --style=css --no-standalone --project-name-and-root-format=as-provided --no-interactive`
);
// generate remote app
runCLI(
`generate @nx/angular:remote ${remoteApp1} --host=${hostApp} --port=${remotePort} --style=css --project-name-and-root-format=as-provided --no-interactive`
`generate @nx/angular:remote ${remoteApp1} --host=${hostApp} --port=${remotePort} --style=css --no-standalone --project-name-and-root-format=as-provided --no-interactive`
);
// check files are generated without the layout directory ("apps/")
@ -56,7 +56,7 @@ describe('Angular Module Federation', () => {
// generate a shared lib with a seconary entry point
runCLI(
`generate @nx/angular:library ${sharedLib} --buildable --project-name-and-root-format=as-provided --no-interactive`
`generate @nx/angular:library ${sharedLib} --buildable --no-standalone --project-name-and-root-format=as-provided --no-interactive`
);
runCLI(
`generate @nx/angular:library-secondary-entry-point --library=${sharedLib} --name=${secondaryEntry} --no-interactive`
@ -157,10 +157,10 @@ describe('Angular Module Federation', () => {
// generate apps
runCLI(
`generate @nx/angular:application ${app1} --routing --project-name-and-root-format=as-provided --no-interactive`
`generate @nx/angular:application ${app1} --routing --bundler=webpack --project-name-and-root-format=as-provided --no-interactive`
);
runCLI(
`generate @nx/angular:application ${app2} --project-name-and-root-format=as-provided --no-interactive`
`generate @nx/angular:application ${app2} --bundler=webpack --project-name-and-root-format=as-provided --no-interactive`
);
// convert apps
@ -260,11 +260,11 @@ describe('Angular Module Federation', () => {
// generate host app
runCLI(
`generate @nx/angular:host ${hostApp} --project-name-and-root-format=derived --no-interactive`
`generate @nx/angular:host ${hostApp} --no-standalone --project-name-and-root-format=derived --no-interactive`
);
// generate remote app
runCLI(
`generate @nx/angular:remote ${remoteApp} --host=${hostApp} --port=${remotePort} --project-name-and-root-format=derived --no-interactive`
`generate @nx/angular:remote ${remoteApp} --host=${hostApp} --port=${remotePort} --no-standalone --project-name-and-root-format=derived --no-interactive`
);
// check files are generated with the layout directory ("apps/")
@ -344,7 +344,8 @@ describe('Angular Module Federation', () => {
@Component({
selector: 'proj-root',
template: \`<div class="host">{{title}}</div>\`
template: \`<div class="host">{{title}}</div>\`,
standalone: true
})
export class AppComponent {
title = \`shell is \${isEven(2) ? 'even' : 'odd'}\`;
@ -421,7 +422,8 @@ describe('Angular Module Federation', () => {
@Component({
selector: 'proj-${remote}-entry',
template: \`<div class="childremote">{{title}}</div>\`
template: \`<div class="childremote">{{title}}</div>\`,
standalone: true
})
export class RemoteEntryComponent {
title = \`shell is \${isEven(2) ? 'even' : 'odd'}\`;
@ -437,7 +439,8 @@ describe('Angular Module Federation', () => {
name: '${remote}',
remotes: ['${childRemote}'],
exposes: {
'./Module': '${remote}/src/app/remote-entry/entry.module.ts',
'./Routes': '${remote}/src/app/remote-entry/entry.routes.ts',
'./Module': '${remote}/src/app/remote-entry/entry.component.ts',
},
};

View File

@ -114,7 +114,9 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
'.vscode/extensions.json',
'.prettierrc',
`apps/${project}/src/main.ts`,
`apps/${project}/src/app/app.module.ts`
`apps/${project}/src/app/app.config.ts`,
`apps/${project}/src/app/app.component.ts`,
`apps/${project}/src/app/app.routes.ts`
);
// check the right VSCode extensions are recommended
@ -178,11 +180,11 @@ 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:browser',
executor: '@angular-devkit/build-angular:application',
options: {
outputPath: `dist/apps/${project}`,
index: `apps/${project}/src/index.html`,
main: `apps/${project}/src/main.ts`,
browser: `apps/${project}/src/main.ts`,
polyfills: [`zone.js`],
tsConfig: `apps/${project}/tsconfig.app.json`,
assets: [
@ -209,12 +211,9 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
outputHashing: 'all',
},
development: {
buildOptimizer: false,
optimization: false,
vendorChunk: true,
extractLicenses: false,
sourceMap: true,
namedChunks: true,
},
},
defaultConfiguration: 'production',
@ -222,8 +221,8 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
expect(projectConfig.targets.serve).toEqual({
executor: '@angular-devkit/build-angular:dev-server',
configurations: {
production: { browserTarget: `${project}:build:production` },
development: { browserTarget: `${project}:build:development` },
production: { buildTarget: `${project}:build:production` },
development: { buildTarget: `${project}:build:development` },
},
defaultConfiguration: 'development',
});
@ -258,7 +257,7 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
});
runCLI(`build ${project} --configuration production --outputHashing none`);
checkFilesExist(`dist/apps/${project}/main.js`);
checkFilesExist(`dist/apps/${project}/browser/main.js`);
});
it('should handle a workspace with cypress v9', () => {
@ -451,7 +450,7 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
expect(output).toContain(
`Successfully ran target build for project ${project}`
);
checkFilesExist(`dist/apps/${project}/main.js`);
checkFilesExist(`dist/apps/${project}/browser/main.js`);
output = runCLI(`build ${project} --outputHashing none`);
expect(output).toContain(
@ -469,7 +468,7 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
expect(output).toContain(
`Successfully ran target build for project ${app1}`
);
checkFilesExist(`dist/apps/${app1}/main.js`);
checkFilesExist(`dist/apps/${app1}/browser/main.js`);
output = runCLI(`build ${app1} --outputHashing none`);
expect(output).toContain(

View File

@ -28,10 +28,10 @@ describe('Angular Projects', () => {
beforeAll(() => {
proj = newProject();
runCLI(
`generate @nx/angular:app ${app1} --project-name-and-root-format=as-provided --no-interactive`
`generate @nx/angular:app ${app1} --no-standalone --bundler=webpack --project-name-and-root-format=as-provided --no-interactive`
);
runCLI(
`generate @nx/angular:lib ${lib1} --add-module-spec --project-name-and-root-format=as-provided --no-interactive`
`generate @nx/angular:lib ${lib1} --no-standalone --add-module-spec --project-name-and-root-format=as-provided --no-interactive`
);
app1DefaultModule = readFile(`${app1}/src/app/app.module.ts`);
app1DefaultComponentTemplate = readFile(
@ -52,7 +52,7 @@ describe('Angular Projects', () => {
it('should successfully generate apps and libs and work correctly', async () => {
const standaloneApp = uniq('standalone-app');
runCLI(
`generate @nx/angular:app ${standaloneApp} --directory=my-dir/${standaloneApp} --standalone=true --project-name-and-root-format=as-provided --no-interactive`
`generate @nx/angular:app ${standaloneApp} --directory=my-dir/${standaloneApp} --bundler=webpack --project-name-and-root-format=as-provided --no-interactive`
);
const esbuildApp = uniq('esbuild-app');
@ -65,12 +65,18 @@ describe('Angular Projects', () => {
`
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ${names(lib1).className}Module } from '@${proj}/${lib1}';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { appRoutes } from './app.routes';
import { NxWelcomeComponent } from './nx-welcome.component';
import { ${names(lib1).className}Module } from '@${proj}/${lib1}';
@NgModule({
imports: [BrowserModule, ${names(lib1).className}Module],
imports: [
BrowserModule,
RouterModule.forRoot(appRoutes, { initialNavigation: 'enabledBlocking' }),
${names(lib1).className}Module
],
declarations: [AppComponent, NxWelcomeComponent],
bootstrap: [AppComponent]
})
@ -84,14 +90,14 @@ describe('Angular Projects', () => {
);
checkFilesExist(`dist/${app1}/main.js`);
checkFilesExist(`dist/my-dir/${standaloneApp}/main.js`);
checkFilesExist(`dist/my-dir/${esbuildApp}/main.js`);
checkFilesExist(`dist/my-dir/${esbuildApp}/browser/main.js`);
// This is a loose requirement because there are a lot of
// influences external from this project that affect this.
const es2015BundleSize = getSize(tmpProjPath(`dist/${app1}/main.js`));
console.log(
`The current es2015 bundle size is ${es2015BundleSize / 1000} KB`
);
expect(es2015BundleSize).toBeLessThanOrEqual(160000);
expect(es2015BundleSize).toBeLessThanOrEqual(220000);
// check unit tests
runCLI(
@ -195,35 +201,40 @@ describe('Angular Projects', () => {
// ARRANGE
const esbuildApp = uniq('esbuild-app');
runCLI(
`generate @nx/angular:app ${esbuildApp} --bundler=esbuild --project-name-and-root-format=as-provided --no-interactive`
`generate @nx/angular:app ${esbuildApp} --bundler=esbuild --no-standalone --project-name-and-root-format=as-provided --no-interactive`
);
const buildableLib = uniq('buildlib1');
const buildableChildLib = uniq('buildlib2');
runCLI(
`generate @nx/angular:library ${buildableLib} --buildable=true --project-name-and-root-format=as-provided --no-interactive`
`generate @nx/angular:library ${buildableLib} --buildable=true --no-standalone --project-name-and-root-format=as-provided --no-interactive`
);
runCLI(
`generate @nx/angular:library ${buildableChildLib} --buildable=true --project-name-and-root-format=as-provided --no-interactive`
`generate @nx/angular:library ${buildableChildLib} --buildable=true --no-standalone --project-name-and-root-format=as-provided --no-interactive`
);
// update the app module to include a ref to the buildable lib
updateFile(
`${app1}/src/app/app.module.ts`,
`
import { BrowserModule } from '@angular/platform-browser';
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';
import {${
names(buildableLib).className
}Module} from '@${proj}/${buildableLib}';
import { AppComponent } from './app.component';
import { NxWelcomeComponent } from './nx-welcome.component';
@NgModule({
declarations: [AppComponent, NxWelcomeComponent],
imports: [BrowserModule, ${names(buildableLib).className}Module],
imports: [
BrowserModule,
RouterModule.forRoot(appRoutes, { initialNavigation: 'enabledBlocking' }),
${names(buildableLib).className}Module
],
providers: [],
bootstrap: [AppComponent],
})
@ -233,18 +244,23 @@ describe('Angular Projects', () => {
updateFile(
`${esbuildApp}/src/app/app.module.ts`,
`
import { BrowserModule } from '@angular/platform-browser';
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';
import {${
names(buildableLib).className
}Module} from '@${proj}/${buildableLib}';
import { AppComponent } from './app.component';
import { NxWelcomeComponent } from './nx-welcome.component';
@NgModule({
declarations: [AppComponent, NxWelcomeComponent],
imports: [BrowserModule, ${names(buildableLib).className}Module],
imports: [
BrowserModule,
RouterModule.forRoot(appRoutes, { initialNavigation: 'enabledBlocking' }),
${names(buildableLib).className}Module
],
providers: [],
bootstrap: [AppComponent],
})
@ -283,6 +299,9 @@ describe('Angular Projects', () => {
config.targets.build.executor = '@nx/angular:browser-esbuild';
config.targets.build.options = {
...config.targets.build.options,
outputPath: `dist/${esbuildApp}`,
main: config.targets.build.options.browser,
browser: undefined,
buildLibsFromSource: false,
};
return config;
@ -316,14 +335,14 @@ describe('Angular Projects', () => {
const entryPoint = uniq('entrypoint');
runCLI(
`generate @nx/angular:lib ${lib} --publishable --importPath=@${proj}/${lib} --project-name-and-root-format=as-provided --no-interactive`
`generate @nx/angular:lib ${lib} --publishable --importPath=@${proj}/${lib} --no-standalone --project-name-and-root-format=as-provided --no-interactive`
);
runCLI(
`generate @nx/angular:secondary-entry-point --name=${entryPoint} --library=${lib} --no-interactive`
);
runCLI(
`generate @nx/angular:library ${childLib} --publishable=true --importPath=@${proj}/${childLib} --project-name-and-root-format=as-provided --no-interactive`
`generate @nx/angular:library ${childLib} --publishable=true --importPath=@${proj}/${childLib} --no-standalone --project-name-and-root-format=as-provided --no-interactive`
);
runCLI(
`generate @nx/angular:secondary-entry-point --name=sub --library=${childLib} --no-interactive`
@ -359,7 +378,7 @@ describe('Angular Projects', () => {
const libName = uniq('lib1');
runCLI(
`generate @nx/angular:app ${appName} --project-name-and-root-format=derived --no-interactive`
`generate @nx/angular:app ${appName} --no-standalone --project-name-and-root-format=derived --no-interactive`
);
// check files are generated with the layout directory ("apps/")
@ -375,7 +394,7 @@ describe('Angular Projects', () => {
);
runCLI(
`generate @nx/angular:lib ${libName} --buildable --project-name-and-root-format=derived`
`generate @nx/angular:lib ${libName} --no-standalone --buildable --project-name-and-root-format=derived`
);
// check files are generated with the layout directory ("libs/")
@ -400,12 +419,12 @@ describe('Angular Projects', () => {
// assert scoped project names are not supported when --project-name-and-root-format=derived
expect(() =>
runCLI(
`generate @nx/angular:lib ${libName} --buildable --project-name-and-root-format=derived`
`generate @nx/angular:lib ${libName} --buildable --no-standalone --project-name-and-root-format=derived`
)
).toThrow();
runCLI(
`generate @nx/angular:lib ${libName} --buildable --project-name-and-root-format=as-provided`
`generate @nx/angular:lib ${libName} --buildable --no-standalone --project-name-and-root-format=as-provided`
);
// check files are generated without the layout directory ("libs/") and

View File

@ -130,7 +130,7 @@ describe('Angular Cypress Component Tests', () => {
function createApp(appName: string) {
runCLI(
`generate @nx/angular:app ${appName} --project-name-and-root-format=as-provided --no-interactive`
`generate @nx/angular:app ${appName} --bundler=webpack --project-name-and-root-format=as-provided --no-interactive`
);
runCLI(
`generate @nx/angular:component fancy-component --project=${appName} --no-interactive`

View File

@ -50,7 +50,8 @@ describe('Move Angular Project', () => {
expect(moveOutput).toContain(
`CREATE ${newPath}/src/app/app.component.html`
);
expect(moveOutput).toContain(`CREATE ${newPath}/src/app/app.module.ts`);
expect(moveOutput).toContain(`CREATE ${newPath}/src/app/app.component.ts`);
expect(moveOutput).toContain(`CREATE ${newPath}/src/app/app.config.ts`);
expect(moveOutput).toContain(`CREATE ${newPath}/src/assets/.gitkeep`);
});
@ -98,7 +99,7 @@ describe('Move Angular Project', () => {
const lib1 = uniq('mylib');
const lib2 = uniq('mylib');
runCLI(
`generate @nx/angular:lib ${lib1} --project-name-and-root-format=as-provided --no-interactive`
`generate @nx/angular:lib ${lib1} --no-standalone --project-name-and-root-format=as-provided --no-interactive`
);
/**
@ -106,7 +107,7 @@ describe('Move Angular Project', () => {
*/
runCLI(
`generate @nx/angular:lib ${lib2} --project-name-and-root-format=as-provided --no-interactive`
`generate @nx/angular:lib ${lib2} --no-standalone --project-name-and-root-format=as-provided --no-interactive`
);
updateFile(
@ -154,7 +155,7 @@ describe('Move Angular Project', () => {
const lib1 = uniq('mylib');
const lib2 = uniq('mylib');
runCLI(
`generate @nx/angular:lib ${lib1} --project-name-and-root-format=derived --no-interactive`
`generate @nx/angular:lib ${lib1} --no-standalone --project-name-and-root-format=derived --no-interactive`
);
/**
@ -162,7 +163,7 @@ describe('Move Angular Project', () => {
*/
runCLI(
`generate @nx/angular:lib ${lib2} --project-name-and-root-format=derived --no-interactive`
`generate @nx/angular:lib ${lib2} --no-standalone --project-name-and-root-format=derived --no-interactive`
);
updateFile(

View File

@ -21,7 +21,7 @@ describe('Angular Package', () => {
it('should work', async () => {
const myapp = uniq('myapp');
runCLI(
`generate @nx/angular:app ${myapp} --project-name-and-root-format=as-provided --no-interactive`
`generate @nx/angular:app ${myapp} --no-standalone --project-name-and-root-format=as-provided --no-interactive`
);
// Generate root ngrx state management
@ -37,13 +37,13 @@ describe('Angular Package', () => {
const mylib = uniq('mylib');
// Generate feature library and ngrx state within that library
runCLI(
`g @nx/angular:lib ${mylib} --prefix=fl --project-name-and-root-format=as-provided`
`g @nx/angular:lib ${mylib} --prefix=fl --no-standalone --project-name-and-root-format=as-provided`
);
runCLI(
`generate @nx/angular:ngrx flights --parent=${mylib}/src/lib/${mylib}.module.ts --facade`
);
expect(runCLI(`build ${myapp}`)).toMatch(/main\.[a-z0-9]+\.js/);
expect(runCLI(`build ${myapp}`)).toMatch(/main-[a-zA-Z0-9]+\.js/);
expectTestsPass(await runCLIAsync(`test ${myapp} --no-watch`));
// TODO: remove this condition
if (getSelectedPackageManager() !== 'pnpm') {
@ -54,7 +54,7 @@ describe('Angular Package', () => {
it('should work with creators', async () => {
const myapp = uniq('myapp');
runCLI(
`generate @nx/angular:app ${myapp} --routing --project-name-and-root-format=as-provided --no-interactive`
`generate @nx/angular:app ${myapp} --routing --no-standalone --project-name-and-root-format=as-provided --no-interactive`
);
// Generate root ngrx state management
@ -72,7 +72,7 @@ describe('Angular Package', () => {
const mylib = uniq('mylib');
// Generate feature library and ngrx state within that library
runCLI(
`g @nx/angular:lib ${mylib} --prefix=fl --project-name-and-root-format=as-provided`
`g @nx/angular:lib ${mylib} --prefix=fl --no-standalone --project-name-and-root-format=as-provided`
);
const flags = `--facade --barrels`;
@ -80,7 +80,7 @@ describe('Angular Package', () => {
`generate @nx/angular:ngrx flights --parent=${mylib}/src/lib/${mylib}.module.ts ${flags}`
);
expect(runCLI(`build ${myapp}`)).toMatch(/main\.[a-z0-9]+\.js/);
expect(runCLI(`build ${myapp}`)).toMatch(/main-[a-zA-Z0-9]+\.js/);
expectTestsPass(await runCLIAsync(`test ${myapp} --no-watch`));
// TODO: remove this condition
if (getSelectedPackageManager() !== 'pnpm') {
@ -91,7 +91,7 @@ describe('Angular Package', () => {
it('should work with creators using --module', async () => {
const myapp = uniq('myapp');
runCLI(
`generate @nx/angular:app ${myapp} --routing --project-name-and-root-format=as-provided --no-interactive`
`generate @nx/angular:app ${myapp} --routing --no-standalone --project-name-and-root-format=as-provided --no-interactive`
);
// Generate root ngrx state management
@ -109,7 +109,7 @@ describe('Angular Package', () => {
const mylib = uniq('mylib');
// Generate feature library and ngrx state within that library
runCLI(
`g @nx/angular:lib ${mylib} --prefix=fl --project-name-and-root-format=as-provided`
`g @nx/angular:lib ${mylib} --prefix=fl --no-standalone --project-name-and-root-format=as-provided`
);
const flags = `--facade --barrels`;
@ -117,7 +117,7 @@ describe('Angular Package', () => {
`generate @nx/angular:ngrx flights --module=${mylib}/src/lib/${mylib}.module.ts ${flags}`
);
expect(runCLI(`build ${myapp}`)).toMatch(/main\.[a-z0-9]+\.js/);
expect(runCLI(`build ${myapp}`)).toMatch(/main-[a-zA-Z0-9]+\.js/);
expectTestsPass(await runCLIAsync(`test ${myapp} --no-watch`));
// TODO: remove this condition
if (getSelectedPackageManager() !== 'pnpm') {

View File

@ -316,37 +316,23 @@ describe('Tailwind support', () => {
});
describe('Applications', () => {
const updateAppComponent = (app: string) => {
updateFile(
`${app}/src/app/app.component.html`,
`<button class="custom-btn text-white">Click me!</button>`
const readAppStylesBundle = (outputPath: string) => {
const stylesBundlePath = listFiles(outputPath).find((file) =>
/^styles[\.-]/.test(file)
);
updateFile(
`${app}/src/app/app.component.css`,
`.custom-btn {
@apply m-md p-sm;
}`
);
};
const readAppStylesBundle = (app: string) => {
const stylesBundlePath = listFiles(`dist/${app}`).find((file) =>
file.startsWith('styles.')
);
const stylesBundle = readFile(`dist/${app}/${stylesBundlePath}`);
const stylesBundle = readFile(`${outputPath}/${stylesBundlePath}`);
return stylesBundle;
};
const assertAppComponentStyles = (
app: string,
outputPath: string,
appSpacing: typeof spacing['root']
) => {
const mainBundlePath = listFiles(`dist/${app}`).find((file) =>
file.startsWith('main.')
const mainBundlePath = listFiles(outputPath).find((file) =>
/^main[\.-]/.test(file)
);
const mainBundle = readFile(`dist/${app}/${mainBundlePath}`);
const mainBundle = readFile(`${outputPath}/${mainBundlePath}`);
let expectedStylesRegex = new RegExp(
`styles:\\[\\"\\.custom\\-btn\\[_ngcontent\\-%COMP%\\]{margin:${appSpacing.md};padding:${appSpacing.sm}}\\"\\]`
);
@ -354,25 +340,13 @@ describe('Tailwind support', () => {
expect(mainBundle).toMatch(expectedStylesRegex);
};
it('should build correctly and only output the tailwind utilities used', async () => {
const appWithTailwind = uniq('app-with-tailwind');
runCLI(
`generate @nx/angular:app ${appWithTailwind} --add-tailwind --project-name-and-root-format=as-provided --no-interactive`
);
updateJson(join(appWithTailwind, 'project.json'), (config) => {
config.targets.build.executor = '@nx/angular:webpack-browser';
config.targets.build.options = {
...config.targets.build.options,
buildLibsFromSource: false,
};
return config;
});
const setupTailwindAndProjectDependencies = (appName: string) => {
updateTailwindConfig(
`${appWithTailwind}/tailwind.config.js`,
`${appName}/tailwind.config.js`,
spacing.projectVariant1
);
updateFile(
`${appWithTailwind}/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}';
@ -389,12 +363,58 @@ describe('Tailwind support', () => {
export class AppModule {}
`
);
updateAppComponent(appWithTailwind);
updateFile(
`${appName}/src/app/app.component.html`,
`<button class="custom-btn text-white">Click me!</button>`
);
updateFile(
`${appName}/src/app/app.component.css`,
`.custom-btn {
@apply m-md p-sm;
}`
);
};
it('should build correctly and only output the tailwind utilities used', async () => {
const appWithTailwind = uniq('app-with-tailwind');
runCLI(
`generate @nx/angular:app ${appWithTailwind} --add-tailwind --project-name-and-root-format=as-provided --no-interactive`
);
setupTailwindAndProjectDependencies(appWithTailwind);
runCLI(`build ${appWithTailwind}`);
assertAppComponentStyles(appWithTailwind, spacing.projectVariant1);
let stylesBundle = readAppStylesBundle(appWithTailwind);
const outputPath = `dist/${appWithTailwind}/browser`;
assertAppComponentStyles(outputPath, spacing.projectVariant1);
let stylesBundle = readAppStylesBundle(outputPath);
expect(stylesBundle).toContain('.text-white');
expect(stylesBundle).not.toContain('.text-black');
expect(stylesBundle).toContain(`.${buildLibWithTailwind.buttonBgColor}`);
expect(stylesBundle).toContain(`.${pubLibWithTailwind.buttonBgColor}`);
expect(stylesBundle).not.toContain(`.${defaultButtonBgColor}`);
});
it('should build correctly and only output the tailwind utilities used when using webpack and incremental builds', async () => {
const appWithTailwind = uniq('app-with-tailwind');
runCLI(
`generate @nx/angular:app ${appWithTailwind} --add-tailwind --bundler=webpack --project-name-and-root-format=as-provided --no-interactive`
);
setupTailwindAndProjectDependencies(appWithTailwind);
updateJson(join(appWithTailwind, 'project.json'), (config) => {
config.targets.build.executor = '@nx/angular:webpack-browser';
config.targets.build.options = {
...config.targets.build.options,
buildLibsFromSource: false,
};
return config;
});
runCLI(`build ${appWithTailwind}`);
const outputPath = `dist/${appWithTailwind}`;
assertAppComponentStyles(outputPath, spacing.projectVariant1);
let stylesBundle = readAppStylesBundle(outputPath);
expect(stylesBundle).toContain('.text-white');
expect(stylesBundle).not.toContain('.text-black');
expect(stylesBundle).toContain(`.${buildLibWithTailwind.buttonBgColor}`);

View File

@ -187,7 +187,9 @@ async function testCtAndE2eInProject(
) {
let appName = uniq(`${projectType}-cy-app`);
runCLI(
`generate @nx/${projectType}:app ${appName} --e2eTestRunner=none --no-interactive`
`generate @nx/${projectType}:app ${appName} --e2eTestRunner=none --no-interactive ${
projectType === 'angular' ? '--bundler=webpack' : ''
}`
);
runCLI(
`generate @nx/${projectType}:component btn --project=${appName} --no-interactive`

View File

@ -528,7 +528,7 @@ ${jslib}();
runCLI(`generate @nx/nest:app ${nestapp} --linter=eslint`);
setMaxWorkers(join('apps', nestapp, 'project.json'));
packageInstall('@nestjs/swagger', undefined, '^6.0.0');
packageInstall('@nestjs/swagger', undefined, '^7.0.0');
updateJson(join('apps', nestapp, 'project.json'), (config) => {
config.targets.build.options.tsPlugins = ['@nestjs/swagger/plugin'];
@ -641,7 +641,7 @@ describe('nest libraries', function () {
const nestlib = uniq('nestlib');
runCLI(`generate @nx/nest:lib ${nestlib} --buildable`);
packageInstall('@nestjs/swagger', undefined, '~6.3.0');
packageInstall('@nestjs/swagger', undefined, '^7.0.0');
updateJson(join('libs', nestlib, 'project.json'), (config) => {
config.targets.build.options.transformers = [

View File

@ -49,7 +49,7 @@ describe('nx init (Angular CLI)', () => {
expect(coldBuildOutput).toContain(
`Successfully ran target build for project ${project}`
);
checkFilesExist(`dist/${project}/main.js`);
checkFilesExist(`dist/${project}/browser/main.js`);
// run build again to check is coming from cache
const cachedBuildOutput = runCLI(`build ${project} --outputHashing none`);
@ -83,7 +83,7 @@ describe('nx init (Angular CLI)', () => {
expect(coldBuildOutput).toContain(
`Successfully ran target build for project ${project}`
);
checkFilesExist(`dist/apps/${project}/main.js`);
checkFilesExist(`dist/apps/${project}/browser/main.js`);
// run build again to check is coming from cache
const cachedBuildOutput = runCLI(`build ${project} --outputHashing none`);

View File

@ -151,6 +151,7 @@ export function runCreateWorkspace(
docker,
nextAppDir,
e2eTestRunner,
ssr,
}: {
preset: string;
appName?: string;
@ -167,6 +168,7 @@ export function runCreateWorkspace(
docker?: boolean;
nextAppDir?: boolean;
e2eTestRunner?: 'cypress' | 'playwright' | 'jest' | 'detox' | 'none';
ssr?: boolean;
}
) {
projName = name;
@ -224,6 +226,10 @@ export function runCreateWorkspace(
command += ` --verbose`;
}
if (ssr !== undefined) {
command += ` --ssr=${ssr}`;
}
try {
const create = execSync(`${command}${isVerbose() ? ' --verbose' : ''}`, {
cwd,

View File

@ -32,6 +32,8 @@ describe('create-nx-workspace', () => {
standaloneApi: false,
routing: false,
e2eTestRunner: 'none',
bundler: 'webpack',
ssr: false,
});
checkFilesExist('package.json');
@ -52,6 +54,8 @@ describe('create-nx-workspace', () => {
standaloneApi: true,
routing: true,
e2eTestRunner: 'none',
bundler: 'webpack',
ssr: false,
});
checkFilesExist('package.json');
@ -144,6 +148,8 @@ describe('create-nx-workspace', () => {
standaloneApi: false,
routing: true,
e2eTestRunner: 'none',
bundler: 'webpack',
ssr: false,
});
expectCodeIsFormatted();
});
@ -163,6 +169,8 @@ describe('create-nx-workspace', () => {
standaloneApi: false,
routing: false,
e2eTestRunner: 'none',
bundler: 'webpack',
ssr: false,
})
).toThrow();
});

View File

@ -26,19 +26,19 @@
},
"devDependencies": {
"@actions/core": "^1.10.0",
"@angular-devkit/architect": "~0.1602.0",
"@angular-devkit/build-angular": "~16.2.0",
"@angular-devkit/core": "~16.2.0",
"@angular-devkit/schematics": "~16.2.0",
"@angular-eslint/eslint-plugin": "~16.0.0",
"@angular-eslint/eslint-plugin-template": "~16.0.0",
"@angular-eslint/template-parser": "~16.0.0",
"@angular/cli": "~16.2.0",
"@angular/common": "~16.2.0",
"@angular/compiler": "~16.2.0",
"@angular/compiler-cli": "~16.2.0",
"@angular/core": "~16.2.0",
"@angular/router": "~16.2.0",
"@angular-devkit/architect": "~0.1700.0-rc.4",
"@angular-devkit/build-angular": "~17.0.0-rc.4",
"@angular-devkit/core": "~17.0.0-rc.4",
"@angular-devkit/schematics": "~17.0.0-rc.4",
"@angular-eslint/eslint-plugin": "~17.0.0-alpha.0",
"@angular-eslint/eslint-plugin-template": "~17.0.0-alpha.0",
"@angular-eslint/template-parser": "~17.0.0-alpha.0",
"@angular/cli": "~17.0.0-rc.4",
"@angular/common": "~17.0.0-rc.3",
"@angular/compiler": "~17.0.0-rc.3",
"@angular/compiler-cli": "~17.0.0-rc.3",
"@angular/core": "~17.0.0-rc.3",
"@angular/router": "~17.0.0-rc.3",
"@babel/core": "^7.22.9",
"@babel/helper-create-regexp-features-plugin": "^7.22.9",
"@babel/plugin-transform-runtime": "^7.22.9",
@ -63,7 +63,6 @@
"@ngrx/effects": "~16.0.0",
"@ngrx/router-store": "~16.0.0",
"@ngrx/store": "~16.0.0",
"@nguniversal/builders": "~16.2.0",
"@nx/angular": "17.0.0-rc.2",
"@nx/cypress": "17.0.0-rc.2",
"@nx/devkit": "17.0.0-rc.2",
@ -89,7 +88,7 @@
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.0.4",
"@rollup/plugin-url": "^7.0.0",
"@schematics/angular": "~16.2.0",
"@schematics/angular": "~17.0.0-rc.4",
"@side/jest-runtime": "^1.1.0",
"@storybook/addon-essentials": "7.2.2",
"@storybook/core-server": "7.2.2",
@ -224,7 +223,7 @@
"mini-css-extract-plugin": "~2.4.7",
"minimatch": "3.0.5",
"next-sitemap": "^3.1.10",
"ng-packagr": "~16.2.0",
"ng-packagr": "~17.0.0-rc.1",
"node-fetch": "^2.6.7",
"npm-package-arg": "11.0.1",
"nuxi": "npm:nuxi-nightly@3.9.2-1699007958.251cab5",
@ -278,7 +277,7 @@
"tsconfig-paths-webpack-plugin": "4.0.0",
"typedoc": "0.25.1",
"typedoc-plugin-markdown": "3.16.0",
"typescript": "~5.1.3",
"typescript": "~5.2.2",
"unist-builder": "^4.0.0",
"unzipper": "^0.10.11",
"url-loader": "^4.1.1",
@ -369,4 +368,3 @@
]
}
}

View File

@ -71,6 +71,7 @@
"@ngrx/store",
"@storybook/angular",
"@module-federation/node",
"@nguniversal/builders",
// installed dynamically by the library generator
"ng-packagr",
// ng-packagr deps, some are handled if not installed

View File

@ -12,10 +12,10 @@ See an example set up of it below:
"executor": "@nx/angular:module-federation-dev-server",
"configurations": {
"production": {
"browserTarget": "host:build:production"
"buildTarget": "host:build:production"
},
"development": {
"browserTarget": "host:build:development"
"buildTarget": "host:build:development"
}
},
"defaultConfiguration": "development",
@ -39,10 +39,10 @@ See an example set up of it below:
"executor": "@nx/angular:module-federation-dev-server",
"configurations": {
"production": {
"browserTarget": "host:build:production"
"buildTarget": "host:build:production"
},
"development": {
"browserTarget": "host:build:development"
"buildTarget": "host:build:development"
}
},
"defaultConfiguration": "development",

View File

@ -18,10 +18,10 @@ Your `project.json` file should contain a `build` and `serve` target that matche
"executor": "@nx/angular:webpack-dev-server",
"configurations": {
"production": {
"browserTarget": "appName:build:production"
"buildTarget": "appName:build:production"
},
"development": {
"browserTarget": "appName:build:development"
"buildTarget": "appName:build:development"
}
},
"defaultConfiguration": "development",

View File

@ -278,6 +278,51 @@
},
"description": "Update the @angular/cli package version to ~16.2.0.",
"factory": "./src/migrations/update-16-7-0/update-angular-cli"
},
"update-angular-cli-version-17-0-0-rc-4": {
"cli": "nx",
"version": "17.1.0-beta.3",
"requires": {
"@angular/core": ">=17.0.0-rc.3"
},
"description": "Update the @angular/cli package version to ~17.0.0-rc.4.",
"factory": "./src/migrations/update-17-1-0/update-angular-cli"
},
"rename-browser-target-to-build-target": {
"cli": "nx",
"version": "17.1.0-beta.3",
"requires": {
"@angular/core": ">=17.0.0-rc.3"
},
"description": "Rename 'browserTarget' to 'buildTarget'.",
"factory": "./src/migrations/update-17-1-0/browser-target-to-build-target"
},
"stub-performance-mark-in-jest-test-setup": {
"cli": "nx",
"version": "17.1.0-beta.3",
"requires": {
"@angular/core": ">=17.0.0-rc.3"
},
"description": "Stubs out 'performance.mark' in the Jest test setup file.",
"factory": "./src/migrations/update-17-1-0/stub-performance-mark-in-jest-test-setup"
},
"replace-nguniversal-builders": {
"cli": "nx",
"version": "17.1.0-beta.3",
"requires": {
"@angular/core": ">=17.0.0-rc.3"
},
"description": "Replace usages of '@nguniversal/builders' with '@angular-devkit/build-angular'.",
"factory": "./src/migrations/update-17-1-0/replace-nguniversal-builders"
},
"replace-nguniversal-engines": {
"cli": "nx",
"version": "17.1.0-beta.3",
"requires": {
"@angular/core": ">=17.0.0-rc.3"
},
"description": "Replace usages of '@nguniversal/' packages with '@angular/ssr'.",
"factory": "./src/migrations/update-17-1-0/replace-nguniversal-engines"
}
},
"packageJsonUpdates": {
@ -1354,6 +1399,96 @@
"alwaysAddToPackageJson": false
}
}
},
"17.1.0": {
"version": "17.1.0-beta.3",
"x-prompt": "Do you want to update the Angular version to v17?",
"requires": {
"@angular/core": ">=16.2.0 <17.0.0-rc.3"
},
"packages": {
"@angular-devkit/architect": {
"version": "~0.1700.0-rc.4",
"alwaysAddToPackageJson": false
},
"@angular-devkit/build-angular": {
"version": "~17.0.0-rc.4",
"alwaysAddToPackageJson": false
},
"@angular-devkit/build-webpack": {
"version": "~0.1700.0-rc.4",
"alwaysAddToPackageJson": false
},
"@angular-devkit/core": {
"version": "~17.0.0-rc.4",
"alwaysAddToPackageJson": false
},
"@angular-devkit/schematics": {
"version": "~17.0.0-rc.4",
"alwaysAddToPackageJson": false
},
"@angular/core": {
"version": "~17.0.0-rc.3",
"alwaysAddToPackageJson": true
},
"@angular/material": {
"version": "~17.0.0-rc.3",
"alwaysAddToPackageJson": false
},
"@angular/cdk": {
"version": "~17.0.0-rc.3",
"alwaysAddToPackageJson": false
},
"@schematics/angular": {
"version": "~17.0.0-rc.4",
"alwaysAddToPackageJson": false
},
"ng-packagr": {
"version": "~17.0.0-rc.1",
"alwaysAddToPackageJson": false
},
"zone.js": {
"version": "~0.14.0",
"alwaysAddToPackageJson": false
}
}
},
"17.1.0-jest": {
"version": "17.1.0-beta.3",
"requires": {
"@angular-devkit/build-angular": ">=13.0.0 <18.0.0",
"@angular/compiler-cli": ">=13.0.0 <18.0.0",
"@angular/core": ">=13.0.0 <18.0.0",
"@angular/platform-browser-dynamic": ">=13.0.0 <18.0.0",
"jest": "^29.0.0"
},
"packages": {
"jest-preset-angular": {
"version": "~13.1.3",
"alwaysAddToPackageJson": false
}
}
},
"17.1.0-angular-eslint": {
"version": "17.1.0-beta.3",
"requires": {
"eslint": "^7.20.0 || ^8.0.0",
"@angular/core": ">= 17.0.0-rc.3 < 18.0.0"
},
"packages": {
"@angular-eslint/eslint-plugin": {
"version": "~17.0.0-alpha.0",
"alwaysAddToPackageJson": false
},
"@angular-eslint/eslint-plugin-template": {
"version": "~17.0.0-alpha.0",
"alwaysAddToPackageJson": false
},
"@angular-eslint/template-parser": {
"version": "~17.0.0-alpha.0",
"alwaysAddToPackageJson": false
}
}
}
}
}

View File

@ -9,7 +9,6 @@
"@nx/",
"@angular-devkit",
"@angular-eslint/",
"@nguniversal/builders",
"@schematics",
"@phenomnomnominal/tsquery",
"@typescript-eslint/",

View File

@ -57,7 +57,6 @@
"tslib": "^2.3.0",
"webpack": "^5.80.0",
"webpack-merge": "^5.8.0",
"enquirer": "^2.3.6",
"@nx/devkit": "file:../devkit",
"@nx/cypress": "file:../cypress",
"@nx/jest": "file:../jest",
@ -68,18 +67,14 @@
"@nx/workspace": "file:../workspace"
},
"peerDependencies": {
"@angular-devkit/build-angular": ">= 14.0.0 < 17.0.0",
"@angular-devkit/schematics": ">= 14.0.0 < 17.0.0",
"@schematics/angular": ">= 14.0.0 < 17.0.0",
"@angular-devkit/core": ">= 14.0.0 < 17.0.0",
"@nguniversal/builders": ">= 14.0.0 < 17.0.0",
"@angular-devkit/build-angular": "^17.0.0-rc.4",
"@angular-devkit/schematics": "^17.0.0-rc.4",
"@schematics/angular": "^17.0.0-rc.4",
"@angular-devkit/core": "^17.0.0-rc.4",
"rxjs": "^6.5.3 || ^7.5.0",
"esbuild": "^0.19.2"
},
"peerDependenciesMeta": {
"@nguniversal/builders": {
"optional": true
},
"esbuild": {
"optional": true
}

View File

@ -1,27 +1,28 @@
import type { Schema } from './schema';
import {
logger,
readCachedProjectGraph,
readNxJson,
workspaceRoot,
} from '@nx/devkit';
import { scheduleTarget } from 'nx/src/adapter/ngcli-adapter';
import { executeWebpackDevServerBuilder } from '../webpack-dev-server/webpack-dev-server.impl';
import { readProjectsConfigurationFromProjectGraph } from 'nx/src/project-graph/project-graph';
import { getExecutorInformation } from 'nx/src/command-line/run/executor-utils';
import {
getDynamicRemotes,
getStaticRemotes,
validateDevRemotes,
} from '../utilities/module-federation';
import { existsSync } from 'fs';
import { extname, join } from 'path';
import {
getModuleFederationConfig,
getRemotes,
} from '@nx/webpack/src/utils/module-federation';
import { fork } from 'child_process';
import { existsSync } from 'fs';
import { scheduleTarget } from 'nx/src/adapter/ngcli-adapter';
import { getExecutorInformation } from 'nx/src/command-line/run/executor-utils';
import { readProjectsConfigurationFromProjectGraph } from 'nx/src/project-graph/project-graph';
import { extname, join } from 'path';
import { combineLatest, concatMap, from, switchMap } from 'rxjs';
import { validateDevRemotes } from '../utilities/module-federation';
import { executeWebpackDevServerBuilder } from '../webpack-dev-server/webpack-dev-server.impl';
import type {
NormalizedSchema,
Schema,
SchemaWithBrowserTarget,
SchemaWithBuildTarget,
} from './schema';
export function executeModuleFederationDevServerBuilder(
schema: Schema,
@ -29,7 +30,7 @@ export function executeModuleFederationDevServerBuilder(
): ReturnType<typeof executeWebpackDevServerBuilder | any> {
// Force Node to resolve to look for the nx binary that is inside node_modules
const nxBin = require.resolve('nx/bin/nx');
const { ...options } = schema;
const options = normalizeOptions(schema);
const projectGraph = readCachedProjectGraph();
const { projects: workspaceProjects } =
readProjectsConfigurationFromProjectGraph(projectGraph);
@ -44,7 +45,7 @@ export function executeModuleFederationDevServerBuilder(
port: options.port,
host: options.host,
ssl: options.ssl,
buildTarget: options.browserTarget,
buildTarget: options.buildTarget,
parallel: false,
spa: false,
withDeps: false,
@ -272,3 +273,21 @@ export function executeModuleFederationDevServerBuilder(
export default require('@angular-devkit/architect').createBuilder(
executeModuleFederationDevServerBuilder
);
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;
}
return {
...schema,
buildTarget,
host: schema.host ?? 'localhost',
port: schema.port ?? 4200,
liveReload: schema.liveReload ?? true,
open: schema.open ?? false,
ssl: schema.ssl ?? false,
};
}

View File

@ -1,15 +1,14 @@
export interface Schema {
browserTarget: string;
port: number;
host: string;
interface BaseSchema {
port?: number;
host?: string;
proxyConfig?: string;
ssl: boolean;
ssl?: boolean;
sslKey?: string;
sslCert?: string;
headers?: Record<string, string>;
open: boolean;
open?: boolean;
verbose?: boolean;
liveReload: boolean;
liveReload?: boolean;
publicHost?: string;
allowedHosts?: string[];
servePath?: string;
@ -24,3 +23,19 @@ export interface Schema {
isInitialHost?: boolean;
parallel?: number;
}
export type SchemaWithBrowserTarget = BaseSchema & {
browserTarget: string;
};
export type SchemaWithBuildTarget = BaseSchema & {
buildTarget: string;
};
export type Schema = SchemaWithBrowserTarget | SchemaWithBuildTarget;
export type NormalizedSchema = SchemaWithBuildTarget & {
liveReload: boolean;
open: boolean;
ssl: boolean;
};

View File

@ -8,13 +8,19 @@
"presets": [
{
"name": "Using a Different Port",
"keys": ["browserTarget", "port"]
"keys": ["buildTarget", "port"]
}
],
"properties": {
"browserTarget": {
"type": "string",
"description": "A browser builder target to serve in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
"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 in Nx v18."
},
"buildTarget": {
"type": "string",
"description": "A build builder target to serve in the format of `project:target[:configuration]`.",
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$"
},
"port": {
@ -139,6 +145,6 @@
}
},
"additionalProperties": false,
"required": ["browserTarget"],
"anyOf": [{ "required": ["buildTarget"] }, { "required": ["browserTarget"] }],
"examplesFile": "../../../docs/module-federation-dev-server-examples.md"
}

View File

@ -1,22 +1,23 @@
import type { Schema } from './schema';
import { readProjectsConfigurationFromProjectGraph } from 'nx/src/project-graph/project-graph';
import { getExecutorInformation } from 'nx/src/command-line/run/executor-utils';
import {
getPackageManagerCommand,
readCachedProjectGraph,
workspaceRoot,
} from '@nx/devkit';
import { execSync, fork } from 'child_process';
import { existsSync } from 'fs';
import { scheduleTarget } from 'nx/src/adapter/ngcli-adapter';
import { getExecutorInformation } from 'nx/src/command-line/run/executor-utils';
import { readProjectsConfigurationFromProjectGraph } from 'nx/src/project-graph/project-graph';
import { extname, join } from 'path';
import { from } from 'rxjs';
import { switchMap, tap } from 'rxjs/operators';
import { getInstalledAngularVersionInfo } from '../../executors/utilities/angular-version-utils';
import {
getDynamicRemotes,
getStaticRemotes,
validateDevRemotes,
} from '../utilities/module-federation';
import { switchMap, tap } from 'rxjs/operators';
import { from } from 'rxjs';
import { extname, join } from 'path';
import { execSync, fork } from 'child_process';
import { scheduleTarget } from 'nx/src/adapter/ngcli-adapter';
import { existsSync } from 'fs';
import type { Schema } from './schema';
export function executeModuleFederationDevSSRBuilder(
schema: Schema,
@ -157,11 +158,14 @@ export function executeModuleFederationDevSSRBuilder(
remoteProcessPromises.push(remotePromise);
}
const { major: angularMajorVersion } = getInstalledAngularVersionInfo();
const { executeSSRDevServerBuilder } =
angularMajorVersion >= 17
? require('@angular-devkit/build-angular')
: require('@nguniversal/builders');
return from(Promise.all(remoteProcessPromises)).pipe(
switchMap(() => from(import('@nguniversal/builders'))),
switchMap(({ executeSSRDevServerBuilder }) =>
executeSSRDevServerBuilder(options, context)
)
switchMap(() => executeSSRDevServerBuilder(options, context))
);
}

View File

@ -41,7 +41,7 @@
"oneOf": [
{
"type": "array",
"description": "A list of polyfills to include in the build. Can be a full path for a file, relative to the current workspace or module specifier. Example: 'zone.js'. _Note: supported in Angular versions >= 15.0.0_.",
"description": "A list of polyfills to include in the build. Can be a full path for a file, relative to the current workspace or module specifier. Example: 'zone.js'.",
"items": {
"type": "string",
"uniqueItems": true
@ -106,7 +106,7 @@
"input": {
"type": "string",
"description": "The file to include.",
"pattern": "\\.(?:css|scss|sass|less|styl)$"
"pattern": "\\.(?:css|scss|sass|less)$"
},
"bundleName": {
"type": "string",
@ -125,7 +125,7 @@
{
"type": "string",
"description": "The file to include.",
"pattern": "\\.(?:css|scss|sass|less|styl)$"
"pattern": "\\.(?:css|scss|sass|less)$"
}
]
},
@ -228,7 +228,7 @@
},
"outputPath": {
"type": "string",
"description": "The full path for the new output directory, relative to the current workspace.\n\nBy default, writes output to a folder named dist/ in the current project."
"description": "The full path for the new output directory, relative to the current workspace."
},
"resourcesOutputPath": {
"type": "string",
@ -278,12 +278,12 @@
},
"vendorChunk": {
"type": "boolean",
"description": "Generate a seperate bundle containing only vendor libraries. This option should only be used for development to reduce the incremental compilation time.",
"description": "Generate a separate bundle containing only vendor libraries. This option should only be used for development to reduce the incremental compilation time.",
"default": false
},
"commonChunk": {
"type": "boolean",
"description": "Generate a seperate bundle containing code used across multiple bundles.",
"description": "Generate a separate bundle containing code used across multiple bundles.",
"default": true
},
"baseHref": {
@ -439,7 +439,7 @@
"enum": ["none", "anonymous", "use-credentials"]
},
"allowedCommonJsDependencies": {
"description": "A list of CommonJS packages that are allowed to be used without a build time warning.",
"description": "A list of CommonJS or AMD packages that are allowed to be used without a build time warning. Use `'*'` to allow all.",
"type": "array",
"items": {
"type": "string"

View File

@ -1,50 +0,0 @@
import { stripIndents } from '@nx/devkit';
import { extname } from 'path';
import type { VersionInfo } from '../../executors/utilities/angular-version-utils';
import { getInstalledAngularVersionInfo } from '../../executors/utilities/angular-version-utils';
import type { BrowserBuilderSchema } from './schema';
export function validateOptions(options: BrowserBuilderSchema): void {
const angularVersionInfo = getInstalledAngularVersionInfo();
validatePolyfills(options, angularVersionInfo);
validateStyles(options, angularVersionInfo);
}
function validatePolyfills(
options: BrowserBuilderSchema,
{ major, version }: VersionInfo
): void {
if (major < 15 && Array.isArray(options.polyfills)) {
throw new Error(stripIndents`The array syntax for the "polyfills" option is supported from Angular >= 15.0.0. You are currently using "${version}".
You can resolve this error by removing the "polyfills" option, setting it to a string value or migrating to Angular 15.0.0.`);
}
}
function validateStyles(
options: BrowserBuilderSchema,
{ major, version }: VersionInfo
): void {
if (!options.styles || !options.styles.length) {
return;
}
if (major < 15) {
return;
}
const stylusFiles = [];
options.styles.forEach((style) => {
const styleFile = typeof style === 'string' ? style : style.input;
if (extname(styleFile) === '.styl') {
stylusFiles.push(styleFile);
}
});
if (stylusFiles.length) {
throw new Error(stripIndents`Stylus is not supported since Angular v15. You're currently using "${version}".
You have the "styles" option with the following file(s) using the ".styl" extension: ${stylusFiles
.map((x) => `"${x}"`)
.join(', ')}.
Make sure to convert them to a supported extension (".css", ".scss", ".sass", ".less").`);
}
}

View File

@ -18,7 +18,6 @@ import {
resolveIndexHtmlTransformer,
} from '../utilities/webpack';
import type { BrowserBuilderSchema } from './schema';
import { validateOptions } from './validate-options';
function shouldSkipInitialTargetRun(
projectGraph: ProjectGraph,
@ -48,7 +47,6 @@ export function executeWebpackBrowserBuilder(
options: BrowserBuilderSchema,
context: import('@angular-devkit/architect').BuilderContext
): Observable<import('@angular-devkit/architect').BuilderOutput> {
validateOptions(options);
options.buildLibsFromSource ??= true;
const {

View File

@ -1,12 +1,24 @@
import type { Schema } from '../schema';
import type {
NormalizedSchema,
Schema,
SchemaWithBrowserTarget,
SchemaWithBuildTarget,
} 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;
}
export function normalizeOptions(schema: Schema): Schema {
return {
host: 'localhost',
port: 4200,
liveReload: true,
open: false,
ssl: false,
...schema,
buildTarget,
host: schema.host ?? 'localhost',
port: schema.port ?? 4200,
liveReload: schema.liveReload ?? true,
open: schema.open ?? false,
ssl: schema.ssl ?? false,
};
}

View File

@ -1,15 +1,14 @@
export interface Schema {
browserTarget: string;
port: number;
host: string;
interface BaseSchema {
port?: number;
host?: string;
proxyConfig?: string;
ssl: boolean;
ssl?: boolean;
sslKey?: string;
sslCert?: string;
headers?: Record<string, string>;
open: boolean;
open?: boolean;
verbose?: boolean;
liveReload: boolean;
liveReload?: boolean;
publicHost?: string;
allowedHosts?: string[];
servePath?: string;
@ -19,3 +18,19 @@ export interface Schema {
poll?: number;
buildLibsFromSource?: boolean;
}
export type SchemaWithBrowserTarget = BaseSchema & {
browserTarget: string;
};
export type SchemaWithBuildTarget = BaseSchema & {
buildTarget: string;
};
export type Schema = SchemaWithBrowserTarget | SchemaWithBuildTarget;
export type NormalizedSchema = SchemaWithBuildTarget & {
liveReload: boolean;
open: boolean;
ssl: boolean;
};

View File

@ -9,13 +9,19 @@
"presets": [
{
"name": "Using a Different Port",
"keys": ["browserTarget", "port"]
"keys": ["buildTarget", "port"]
}
],
"properties": {
"browserTarget": {
"type": "string",
"description": "A browser builder target to serve in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
"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 in Nx v18."
},
"buildTarget": {
"type": "string",
"description": "A build builder target to serve in the format of `project:target[:configuration]`.",
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$"
},
"port": {
@ -112,5 +118,5 @@
}
},
"additionalProperties": false,
"required": ["browserTarget"]
"anyOf": [{ "required": ["buildTarget"] }, { "required": ["browserTarget"] }]
}

View File

@ -2,24 +2,27 @@ import {
joinPathFragments,
parseTargetString,
readCachedProjectGraph,
readNxJson,
} from '@nx/devkit';
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 { DependentBuildableProjectNode } from '@nx/js/src/utils/buildable-libs-utils';
import { readCachedProjectConfiguration } from 'nx/src/project-graph/project-graph';
import { existsSync } from 'fs';
import { isNpmProject } from 'nx/src/project-graph/operators';
import { readCachedProjectConfiguration } from 'nx/src/project-graph/project-graph';
import { from } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { getInstalledAngularVersionInfo } from '../../executors/utilities/angular-version-utils';
import { createTmpTsConfigForBuildableLibs } from '../utilities/buildable-libs';
import {
mergeCustomWebpackConfig,
resolveIndexHtmlTransformer,
} from '../utilities/webpack';
import { normalizeOptions } from './lib';
import type { Schema } from './schema';
import { createTmpTsConfigForBuildableLibs } from '../utilities/buildable-libs';
import { from } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { getRootTsConfigPath } from '@nx/js';
import { join } from 'path';
import type {
NormalizedSchema,
Schema,
SchemaWithBrowserTarget,
} from './schema';
type BuildTargetOptions = {
tsConfig: string;
@ -36,7 +39,7 @@ export function executeWebpackDevServerBuilder(
const options = normalizeOptions(rawOptions);
const parsedBrowserTarget = parseTargetString(options.browserTarget, {
const parsedBuildTarget = parseTargetString(options.buildTarget, {
cwd: context.currentDirectory,
projectGraph: readCachedProjectGraph(),
projectName: context.target.project,
@ -44,16 +47,16 @@ export function executeWebpackDevServerBuilder(
isVerbose: false,
});
const browserTargetProjectConfiguration = readCachedProjectConfiguration(
parsedBrowserTarget.project
parsedBuildTarget.project
);
const buildTarget =
browserTargetProjectConfiguration.targets[parsedBrowserTarget.target];
browserTargetProjectConfiguration.targets[parsedBuildTarget.target];
const buildTargetOptions: BuildTargetOptions = {
...buildTarget.options,
...(parsedBrowserTarget.configuration
? buildTarget.configurations[parsedBrowserTarget.configuration]
...(parsedBuildTarget.configuration
? buildTarget.configurations[parsedBuildTarget.configuration]
: buildTarget.defaultConfiguration
? buildTarget.configurations[buildTarget.defaultConfiguration]
: {}),
@ -96,7 +99,7 @@ export function executeWebpackDevServerBuilder(
if (!buildLibsFromSource) {
const { tsConfigPath, dependencies: foundDependencies } =
createTmpTsConfigForBuildableLibs(buildTargetOptions.tsConfig, context, {
target: parsedBrowserTarget.target,
target: parsedBuildTarget.target,
});
dependencies = foundDependencies;
@ -120,9 +123,11 @@ export function executeWebpackDevServerBuilder(
buildTargetOptions.tsConfig = tsConfigPath;
}
const delegateBuilderOptions = getDelegateBuilderOptions(options);
return from(import('@angular-devkit/build-angular')).pipe(
switchMap(({ executeDevServerBuilder }) =>
executeDevServerBuilder(options, context, {
executeDevServerBuilder(delegateBuilderOptions, context, {
webpackConfiguration: async (baseWebpackConfig) => {
if (!buildLibsFromSource) {
const workspaceDependencies = dependencies
@ -137,7 +142,7 @@ export function executeWebpackDevServerBuilder(
// @ts-expect-error - difference between angular and webpack plugin definitions bc of webpack versions
new WebpackNxBuildCoordinationPlugin(
`nx run-many --target=${
parsedBrowserTarget.target
parsedBuildTarget.target
} --projects=${workspaceDependencies.join(',')}`
)
);
@ -173,3 +178,16 @@ export function executeWebpackDevServerBuilder(
export default require('@angular-devkit/architect').createBuilder(
executeWebpackDevServerBuilder
) as any;
function getDelegateBuilderOptions(options: NormalizedSchema) {
const delegatedBuilderOptions = { ...options };
const { major } = getInstalledAngularVersionInfo();
if (major <= 17) {
(
delegatedBuilderOptions as unknown as SchemaWithBrowserTarget
).browserTarget = delegatedBuilderOptions.buildTarget;
delete delegatedBuilderOptions.buildTarget;
}
return delegatedBuilderOptions;
}

View File

@ -5,5 +5,4 @@ export interface Schema extends ServerBuilderOptions {
path: string;
};
buildLibsFromSource?: boolean;
bundleDependencies?: boolean | 'none' | 'all';
}

View File

@ -128,7 +128,7 @@
},
"vendorChunk": {
"type": "boolean",
"description": "Generate a seperate bundle containing only vendor libraries. This option should only be used for development to reduce the incremental compilation time. _Note: supported in Angular versions >= 15.1.0_",
"description": "Generate a separate bundle containing only vendor libraries. This option should only be used for development to reduce the incremental compilation time. _Note: supported in Angular versions >= 15.1.0_",
"default": false
},
"verbose": {
@ -209,18 +209,6 @@
},
"default": []
},
"bundleDependencies": {
"description": "Which external dependencies to bundle into the bundle. By default, all of node_modules will be bundled. _Note: This is only supported in Angular versions >= 14.0.0, < 15.0.0. It was removed in Angular 15._",
"oneOf": [
{
"type": "boolean"
},
{
"type": "string",
"enum": ["none", "all"]
}
]
},
"statsJson": {
"type": "boolean",
"description": "Generates a 'stats.json' file which can be analyzed using tools such as 'webpack-bundle-analyzer'.",

View File

@ -8,7 +8,6 @@ export function validateOptions(options: Schema): void {
const angularVersionInfo = getInstalledAngularVersionInfo();
validateAssets(options, angularVersionInfo);
validateBuildOptimizer(options, angularVersionInfo);
validateBundleDependencies(options, angularVersionInfo);
validateVendorChunk(options, angularVersionInfo);
}
@ -33,16 +32,6 @@ function validateBuildOptimizer(
}
}
function validateBundleDependencies(
options: Schema,
{ major, version }: VersionInfo
): void {
if (major >= 15 && options.bundleDependencies) {
throw new Error(stripIndents`The "bundleDependencies" option was removed in Angular version 15. You are currently using "${version}".
You can resolve this error by removing the "bundleDependencies" option.`);
}
}
function validateVendorChunk(options: Schema, { version }: VersionInfo): void {
if (lt(version, '15.1.0') && options.vendorChunk) {
throw new Error(stripIndents`The "vendorChunk" option is supported from Angular >= 15.1.0. You are currently using "${version}".

View File

@ -2,8 +2,6 @@ import { joinPathFragments } from '@nx/devkit';
import { existsSync } from 'fs';
import { Observable, from } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { lt } from 'semver';
import { getInstalledAngularVersionInfo } from '../../executors/utilities/angular-version-utils';
import { createTmpTsConfigForBuildableLibs } from '../utilities/buildable-libs';
import { mergeCustomWebpackConfig } from '../utilities/webpack';
import { Schema } from './schema';
@ -95,15 +93,6 @@ export function executeWebpackServerBuilder(
): Observable<import('@angular-devkit/build-angular').ServerBuilderOutput> {
validateOptions(options);
const installedAngularVersionInfo = getInstalledAngularVersionInfo();
// default bundleDependencies to true if supported by Angular version
if (
lt(installedAngularVersionInfo.version, '15.0.0') &&
options.bundleDependencies === undefined
) {
options.bundleDependencies = true;
}
options.buildLibsFromSource ??= true;
if (!options.buildLibsFromSource) {

View File

@ -1,5 +1,4 @@
import { type EsBuildSchema } from './schema';
import { validateOptions } from './lib/validate-options';
import { type DependentBuildableProjectNode } from '@nx/js/src/utils/buildable-libs-utils';
import { type ExecutorContext, readCachedProjectGraph } from '@nx/devkit';
import { createTmpTsConfigForBuildableLibs } from './lib/buildable-libs';
@ -11,7 +10,6 @@ export default async function* esbuildExecutor(
options: EsBuildSchema,
context: ExecutorContext
) {
validateOptions(options);
options.buildLibsFromSource ??= true;
const { buildLibsFromSource, ...delegateExecutorOptions } = options;

View File

@ -1,52 +0,0 @@
import {
getInstalledAngularVersionInfo,
VersionInfo,
} from '../../utilities/angular-version-utils';
import { stripIndents } from '@nx/devkit';
import { extname } from 'path';
import { type EsBuildSchema } from '../schema';
export function validateOptions(options: EsBuildSchema): void {
const angularVersionInfo = getInstalledAngularVersionInfo();
validatePolyfills(options, angularVersionInfo);
validateStyles(options, angularVersionInfo);
}
function validatePolyfills(
options: EsBuildSchema,
{ major, version }: VersionInfo
): void {
if (major < 15 && Array.isArray(options.polyfills)) {
throw new Error(stripIndents`The array syntax for the "polyfills" option is supported from Angular >= 15.0.0. You are currently using "${version}".
You can resolve this error by removing the "polyfills" option, setting it to a string value or migrating to Angular 15.0.0.`);
}
}
function validateStyles(
options: EsBuildSchema,
{ major, version }: VersionInfo
): void {
if (!options.styles || !options.styles.length) {
return;
}
if (major < 15) {
return;
}
const stylusFiles = [];
options.styles.forEach((style) => {
const styleFile = typeof style === 'string' ? style : style.input;
if (extname(styleFile) === '.styl') {
stylusFiles.push(styleFile);
}
});
if (stylusFiles.length) {
throw new Error(stripIndents`Stylus is not supported since Angular v15. You're currently using "${version}".
You have the "styles" option with the following file(s) using the ".styl" extension: ${stylusFiles
.map((x) => `"${x}"`)
.join(', ')}.
Make sure to convert them to a supported extension (".css", ".scss", ".sass", ".less").`);
}
}

View File

@ -215,7 +215,7 @@
},
"outputPath": {
"type": "string",
"description": "The full path for the new output directory, relative to the current workspace.\nBy default, writes output to a folder named dist/ in the current project."
"description": "The full path for the new output directory, relative to the current workspace."
},
"resourcesOutputPath": {
"type": "string",
@ -264,12 +264,12 @@
},
"vendorChunk": {
"type": "boolean",
"description": "Generate a seperate bundle containing only vendor libraries. This option should only be used for development to reduce the incremental compilation time.",
"description": "Generate a separate bundle containing only vendor libraries. This option should only be used for development to reduce the incremental compilation time.",
"default": false
},
"commonChunk": {
"type": "boolean",
"description": "Generate a seperate bundle containing code used across multiple bundles.",
"description": "Generate a separate bundle containing code used across multiple bundles.",
"default": true
},
"baseHref": {
@ -401,6 +401,7 @@
},
{
"const": false,
"type": "boolean",
"description": "Does not generate an `index.html` file."
}
]
@ -429,7 +430,7 @@
"enum": ["none", "anonymous", "use-credentials"]
},
"allowedCommonJsDependencies": {
"description": "A list of CommonJS packages that are allowed to be used without a build time warning.",
"description": "A list of CommonJS or AMD packages that are allowed to be used without a build time warning. Use `'*'` to allow all.",
"type": "array",
"items": {
"type": "string"

View File

@ -59,12 +59,19 @@ export const nxWritePackageTransform = (options: NgPackagrOptions) =>
}
// 6. WRITE PACKAGE.JSON
// As of APF 14 only the primary entrypoint has a package.json
const relativeUnixFromDestPath = (filePath: string) =>
ensureUnixPath(path.relative(ngEntryPoint.destinationPath, filePath));
if (!ngEntryPoint.isSecondaryEntryPoint) {
try {
logger.info('Writing package manifest');
const relativeUnixFromDestPath = (filePath: string) =>
ensureUnixPath(path.relative(ngEntryPoint.destinationPath, filePath));
if (!options.watch) {
const primary = ngPackageNode.data.primary;
await writeFile(
path.join(primary.destinationPath, '.npmignore'),
`# Nested package.json's are only needed for development.\n**/package.json`
);
}
await writePackageJson(
ngEntryPoint,
@ -100,8 +107,9 @@ export const nxWritePackageTransform = (options: NgPackagrOptions) =>
} catch (error) {
throw error;
}
} else if (options.watch) {
// update the watch version of the primary entry point `package.json` file.
} else if (ngEntryPoint.isSecondaryEntryPoint) {
if (options.watch) {
// Update the watch version of the primary entry point `package.json` file.
// this is needed because of Webpack's 5 `cachemanagedpaths`
// https://github.com/ng-packagr/ng-packagr/issues/2069
const primary = ngPackageNode.data.primary;
@ -122,6 +130,18 @@ export const nxWritePackageTransform = (options: NgPackagrOptions) =>
}
}
// Write a package.json in each secondary entry-point
// This is need for esbuild to secondary entry-points in dist correctly.
await writeFile(
path.join(ngEntryPoint.destinationPath, 'package.json'),
JSON.stringify(
{ module: relativeUnixFromDestPath(destinationFiles.fesm2022) },
undefined,
2
)
);
}
logger.info(`Built ${ngEntryPoint.moduleId}`);
return graph;

View File

@ -20,10 +20,10 @@ import {
} from 'ng-packagr/lib/ng-package/nodes';
import { augmentProgramWithVersioning } from 'ng-packagr/lib/ts/cache-compiler-host';
import * as log from 'ng-packagr/lib/utils/log';
import { ngCompilerCli } from 'ng-packagr/lib/utils/ng-compiler-cli';
import { join } from 'node:path';
import * as ts from 'typescript';
import { getInstalledAngularVersionInfo } from '../../../utilities/angular-version-utils';
import { ngCompilerCli } from '../../../utilities/ng-compiler-cli';
import { NgPackagrOptions } from '../ng-package/options.di';
import { StylesheetProcessor } from '../styles/stylesheet-processor';
import { cacheCompilerHost } from '../ts/cache-compiler-host';
@ -231,6 +231,26 @@ export async function compileSourceFiles(
const transformers = angularCompiler.prepareEmit().transformers;
if ('getSemanticDiagnosticsOfNextAffectedFile' in builder) {
while (
builder.emitNextAffectedFile(
(fileName, data, writeByteOrderMark, onError, sourceFiles) => {
if (fileName.endsWith('.tsbuildinfo')) {
tsCompilerHost.writeFile(
fileName,
data,
writeByteOrderMark,
onError,
sourceFiles
);
}
}
)
) {
// empty
}
}
const angularVersion = getInstalledAngularVersionInfo();
const incrementalCompilation: typeof angularCompiler.incrementalCompilation =
angularVersion.major < 16

View File

@ -21,7 +21,6 @@ import { dirname, extname, join } from 'path';
import * as postcssPresetEnv from 'postcss-preset-env';
import * as postcssUrl from 'postcss-url';
import { pathToFileURL } from 'node:url';
import { getInstalledAngularVersionInfo } from '../../../utilities/angular-version-utils';
import {
getTailwindPostCssPlugins,
getTailwindSetup,
@ -251,19 +250,6 @@ export class StylesheetProcessor {
switch (ext) {
case '.sass':
case '.scss': {
const angularVersion = getInstalledAngularVersionInfo();
if (angularVersion && angularVersion.major < 15) {
return (await import('sass'))
.renderSync({
file: filePath,
data: css,
indentedSyntax: '.sass' === ext,
importer: customSassImporter,
includePaths: this.styleIncludePaths,
})
.css.toString();
}
return (await import('sass'))
.compileString(css, {
url: pathToFileURL(filePath),

View File

@ -19,10 +19,10 @@ import {
isPackage,
} from 'ng-packagr/lib/ng-package/nodes';
import * as log from 'ng-packagr/lib/utils/log';
import { ngCompilerCli } from 'ng-packagr/lib/utils/ng-compiler-cli';
import { join } from 'node:path';
import * as ts from 'typescript';
import { getInstalledAngularVersionInfo } from '../../../utilities/angular-version-utils';
import { ngCompilerCli } from '../../../utilities/ng-compiler-cli';
import { NgPackagrOptions } from '../ng-package/options.di';
import { StylesheetProcessor } from '../styles/stylesheet-processor';
import {
@ -244,6 +244,26 @@ export async function compileSourceFiles(
const transformers = angularCompiler.prepareEmit().transformers;
if ('getSemanticDiagnosticsOfNextAffectedFile' in builder) {
while (
builder.emitNextAffectedFile(
(fileName, data, writeByteOrderMark, onError, sourceFiles) => {
if (fileName.endsWith('.tsbuildinfo')) {
tsCompilerHost.writeFile(
fileName,
data,
writeByteOrderMark,
onError,
sourceFiles
);
}
}
)
) {
// empty
}
}
const angularVersion = getInstalledAngularVersionInfo();
const incrementalCompilation: typeof angularCompiler.incrementalCompilation =
angularVersion.major < 16

View File

@ -21,7 +21,6 @@ import { dirname, extname, join } from 'path';
import * as postcssPresetEnv from 'postcss-preset-env';
import * as postcssUrl from 'postcss-url';
import { pathToFileURL } from 'node:url';
import { getInstalledAngularVersionInfo } from '../../../utilities/angular-version-utils';
import {
getTailwindPostCssPlugins,
getTailwindSetup,
@ -244,19 +243,6 @@ export class StylesheetProcessor {
switch (ext) {
case '.sass':
case '.scss': {
const angularVersion = getInstalledAngularVersionInfo();
if (angularVersion && angularVersion.major < 15) {
return (await import('sass'))
.renderSync({
file: filePath,
data: css,
indentedSyntax: '.sass' === ext,
importer: customSassImporter,
includePaths: this.styleIncludePaths,
})
.css.toString();
}
return (await import('sass'))
.compileString(css, {
url: pathToFileURL(filePath),

View File

@ -0,0 +1,31 @@
export function ngCompilerCli(): Promise<
typeof import('@angular/compiler-cli')
> {
return loadEsmModule('@angular/compiler-cli');
}
/**
* Lazily compiled dynamic import loader function.
*/
let load: (<T>(modulePath: string | URL) => Promise<T>) | undefined;
/**
* This uses a dynamic import to load a module which may be ESM.
* CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript
* will currently, unconditionally downlevel dynamic import into a require call.
* require calls cannot load ESM code and will result in a runtime error. To workaround
* this, a Function constructor is used to prevent TypeScript from changing the dynamic import.
* Once TypeScript provides support for keeping the dynamic import this workaround can
* be dropped.
*
* @param modulePath The path of the module to load.
* @returns A Promise that resolves to the dynamically imported module.
*/
export function loadEsmModule<T>(modulePath: string | URL): Promise<T> {
load ??= new Function('modulePath', `return import(modulePath);`) as Exclude<
typeof load,
undefined
>;
return load(modulePath);
}

View File

@ -48,52 +48,3 @@ exports[`addLinting generator should correctly generate the .eslintrc.json file
],
}
`;
exports[`addLinting generator support angular v14 should correctly generate the .eslintrc.json file 1`] = `
{
"extends": [
"../../.eslintrc.json",
],
"ignorePatterns": [
"!**/*",
],
"overrides": [
{
"extends": [
"plugin:@nx/angular",
"plugin:@angular-eslint/template/process-inline-templates",
],
"files": [
"*.ts",
],
"rules": {
"@angular-eslint/component-selector": [
"error",
{
"prefix": "my-org",
"style": "kebab-case",
"type": "element",
},
],
"@angular-eslint/directive-selector": [
"error",
{
"prefix": "myOrg",
"style": "camelCase",
"type": "attribute",
},
],
},
},
{
"extends": [
"plugin:@nx/angular-template",
],
"files": [
"*.html",
],
"rules": {},
},
],
}
`;

View File

@ -3,7 +3,6 @@ import {
addProjectConfiguration,
readJson,
readProjectConfiguration,
updateJson,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import * as linter from '@nx/eslint';
@ -82,83 +81,4 @@ describe('addLinting generator', () => {
outputs: ['{options.outputFile}'],
});
});
describe('support angular v14', () => {
beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
updateJson(tree, 'package.json', (json) => ({
...json,
dependencies: {
...json.dependencies,
'@angular/core': '14.1.0',
},
}));
addProjectConfiguration(tree, appProjectName, {
root: appProjectRoot,
prefix: 'myOrg',
projectType: 'application',
targets: {},
} as ProjectConfiguration);
});
it('should invoke the lintProjectGenerator', async () => {
jest.spyOn(linter, 'lintProjectGenerator');
await addLintingGenerator(tree, {
prefix: 'myOrg',
projectName: appProjectName,
projectRoot: appProjectRoot,
});
expect(linter.lintProjectGenerator).toHaveBeenCalled();
});
it('should add the Angular specific EsLint devDependencies', async () => {
await addLintingGenerator(tree, {
prefix: 'myOrg',
projectName: appProjectName,
projectRoot: appProjectRoot,
});
const { dependencies, devDependencies } = readJson(tree, 'package.json');
expect(dependencies['@angular/core']).toEqual('14.1.0');
expect(devDependencies['@angular-eslint/eslint-plugin']).toBeDefined();
expect(
devDependencies['@angular-eslint/eslint-plugin-template']
).toBeDefined();
expect(devDependencies['@angular-eslint/template-parser']).toBeDefined();
});
it('should correctly generate the .eslintrc.json file', async () => {
await addLintingGenerator(tree, {
prefix: 'myOrg',
projectName: appProjectName,
projectRoot: appProjectRoot,
});
const eslintConfig = readJson(tree, `${appProjectRoot}/.eslintrc.json`);
expect(eslintConfig).toMatchSnapshot();
});
it('should update the project with the right lint target configuration', async () => {
await addLintingGenerator(tree, {
prefix: 'myOrg',
projectName: appProjectName,
projectRoot: appProjectRoot,
});
const project = readProjectConfiguration(tree, appProjectName);
expect(project.targets.lint).toEqual({
executor: '@nx/eslint:lint',
options: {
lintFilePatterns: [
`${appProjectRoot}/**/*.ts`,
`${appProjectRoot}/**/*.html`,
],
},
outputs: ['{options.outputFile}'],
});
});
});
});

View File

@ -9,10 +9,7 @@ import { appRoutes } from './app.routes';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
RouterModule.forRoot(appRoutes, { initialNavigation: 'enabledBlocking' }),
],
imports: [BrowserModule, RouterModule.forRoot(appRoutes)],
providers: [],
bootstrap: [AppComponent],
})
@ -219,12 +216,9 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
"build": {
"configurations": {
"development": {
"buildOptimizer": false,
"extractLicenses": false,
"namedChunks": true,
"optimization": false,
"sourceMap": true,
"vendorChunk": true,
},
"production": {
"budgets": [
@ -243,14 +237,14 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
},
},
"defaultConfiguration": "production",
"executor": "@angular-devkit/build-angular:browser",
"executor": "@angular-devkit/build-angular:application",
"options": {
"assets": [
"apps/my-dir/my-app/src/favicon.ico",
"apps/my-dir/my-app/src/assets",
],
"browser": "apps/my-dir/my-app/src/main.ts",
"index": "apps/my-dir/my-app/src/index.html",
"main": "apps/my-dir/my-app/src/main.ts",
"outputPath": "dist/apps/my-dir/my-app",
"polyfills": [
"zone.js",
@ -268,7 +262,7 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
"extract-i18n": {
"executor": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "my-dir-my-app:build",
"buildTarget": "my-dir-my-app:build",
},
},
"lint": {
@ -286,10 +280,10 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
"serve": {
"configurations": {
"development": {
"browserTarget": "my-dir-my-app:build:development",
"buildTarget": "my-dir-my-app:build:development",
},
"production": {
"browserTarget": "my-dir-my-app:build:production",
"buildTarget": "my-dir-my-app:build:production",
},
},
"defaultConfiguration": "development",
@ -299,6 +293,7 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
"executor": "@nx/web:file-server",
"options": {
"buildTarget": "my-dir-my-app:build",
"staticFilePath": "dist/apps/my-dir/my-app/browser",
},
},
"test": {
@ -417,6 +412,7 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
"strictTemplates": true,
},
"compilerOptions": {
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
@ -456,12 +452,9 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
"build": {
"configurations": {
"development": {
"buildOptimizer": false,
"extractLicenses": false,
"namedChunks": true,
"optimization": false,
"sourceMap": true,
"vendorChunk": true,
},
"production": {
"budgets": [
@ -480,14 +473,14 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
},
},
"defaultConfiguration": "production",
"executor": "@angular-devkit/build-angular:browser",
"executor": "@angular-devkit/build-angular:application",
"options": {
"assets": [
"apps/my-app/src/favicon.ico",
"apps/my-app/src/assets",
],
"browser": "apps/my-app/src/main.ts",
"index": "apps/my-app/src/index.html",
"main": "apps/my-app/src/main.ts",
"outputPath": "dist/apps/my-app",
"polyfills": [
"zone.js",
@ -505,7 +498,7 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
"extract-i18n": {
"executor": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "my-app:build",
"buildTarget": "my-app:build",
},
},
"lint": {
@ -523,10 +516,10 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
"serve": {
"configurations": {
"development": {
"browserTarget": "my-app:build:development",
"buildTarget": "my-app:build:development",
},
"production": {
"browserTarget": "my-app:build:production",
"buildTarget": "my-app:build:production",
},
},
"defaultConfiguration": "development",
@ -536,6 +529,7 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
"executor": "@nx/web:file-server",
"options": {
"buildTarget": "my-app:build",
"staticFilePath": "dist/apps/my-app/browser",
},
},
"test": {
@ -654,6 +648,7 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
"strictTemplates": true,
},
"compilerOptions": {
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
@ -693,14 +688,11 @@ bootstrapApplication(AppComponent, appConfig).catch((err) =>
exports[`app --standalone should generate a standalone app correctly with routing 2`] = `
"import { ApplicationConfig } from '@angular/core';
import {
provideRouter,
withEnabledBlockingInitialNavigation,
} from '@angular/router';
import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(appRoutes, withEnabledBlockingInitialNavigation())],
providers: [provideRouter(appRoutes)],
};
"
`;
@ -837,6 +829,7 @@ exports[`app --strict should enable strict type checking: app tsconfig.json 1`]
"strictTemplates": true,
},
"compilerOptions": {
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
@ -893,6 +886,17 @@ exports[`app --strict should enable strict type checking: e2e tsconfig.json 1`]
}
`;
exports[`app angular v15 support should import "ApplicationConfig" from "@angular/platform-browser" 1`] = `
"import { ApplicationConfig } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(appRoutes)],
};
"
`;
exports[`app at the root should accept numbers in the path 1`] = `"src/9-websites/my-app"`;
exports[`app nested should create project configs 1`] = `
@ -908,12 +912,9 @@ exports[`app nested should create project configs 1`] = `
"build": {
"configurations": {
"development": {
"buildOptimizer": false,
"extractLicenses": false,
"namedChunks": true,
"optimization": false,
"sourceMap": true,
"vendorChunk": true,
},
"production": {
"budgets": [
@ -932,14 +933,14 @@ exports[`app nested should create project configs 1`] = `
},
},
"defaultConfiguration": "production",
"executor": "@angular-devkit/build-angular:browser",
"executor": "@angular-devkit/build-angular:application",
"options": {
"assets": [
"my-dir/my-app/src/favicon.ico",
"my-dir/my-app/src/assets",
],
"browser": "my-dir/my-app/src/main.ts",
"index": "my-dir/my-app/src/index.html",
"main": "my-dir/my-app/src/main.ts",
"outputPath": "dist/my-dir/my-app",
"polyfills": [
"zone.js",
@ -957,7 +958,7 @@ exports[`app nested should create project configs 1`] = `
"extract-i18n": {
"executor": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "my-app:build",
"buildTarget": "my-app:build",
},
},
"lint": {
@ -975,10 +976,10 @@ exports[`app nested should create project configs 1`] = `
"serve": {
"configurations": {
"development": {
"browserTarget": "my-app:build:development",
"buildTarget": "my-app:build:development",
},
"production": {
"browserTarget": "my-app:build:production",
"buildTarget": "my-app:build:production",
},
},
"defaultConfiguration": "development",
@ -988,6 +989,7 @@ exports[`app nested should create project configs 1`] = `
"executor": "@nx/web:file-server",
"options": {
"buildTarget": "my-app:build",
"staticFilePath": "dist/my-dir/my-app/browser",
},
},
"test": {
@ -1059,12 +1061,9 @@ exports[`app not nested should create project configs 1`] = `
"build": {
"configurations": {
"development": {
"buildOptimizer": false,
"extractLicenses": false,
"namedChunks": true,
"optimization": false,
"sourceMap": true,
"vendorChunk": true,
},
"production": {
"budgets": [
@ -1083,14 +1082,14 @@ exports[`app not nested should create project configs 1`] = `
},
},
"defaultConfiguration": "production",
"executor": "@angular-devkit/build-angular:browser",
"executor": "@angular-devkit/build-angular:application",
"options": {
"assets": [
"my-app/src/favicon.ico",
"my-app/src/assets",
],
"browser": "my-app/src/main.ts",
"index": "my-app/src/index.html",
"main": "my-app/src/main.ts",
"outputPath": "dist/my-app",
"polyfills": [
"zone.js",
@ -1108,7 +1107,7 @@ exports[`app not nested should create project configs 1`] = `
"extract-i18n": {
"executor": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "my-app:build",
"buildTarget": "my-app:build",
},
},
"lint": {
@ -1126,10 +1125,10 @@ exports[`app not nested should create project configs 1`] = `
"serve": {
"configurations": {
"development": {
"browserTarget": "my-app:build:development",
"buildTarget": "my-app:build:development",
},
"production": {
"browserTarget": "my-app:build:production",
"buildTarget": "my-app:build:production",
},
},
"defaultConfiguration": "development",
@ -1139,6 +1138,7 @@ exports[`app not nested should create project configs 1`] = `
"executor": "@nx/web:file-server",
"options": {
"buildTarget": "my-app:build",
"staticFilePath": "dist/my-app/browser",
},
},
"test": {
@ -1257,6 +1257,7 @@ exports[`app not nested should generate files: tsconfig.json 1`] = `
"strictTemplates": true,
},
"compilerOptions": {
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,

View File

@ -7,7 +7,6 @@ import {
readJson,
readNxJson,
readProjectConfiguration,
stripIndents,
updateJson,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
@ -189,6 +188,24 @@ describe('app', () => {
const { defaultProject } = readNxJson(appTree);
expect(defaultProject).toBe('some-awesome-project');
});
it('should set esModuleInterop when using the application builder', async () => {
await generateApp(appTree, 'my-app');
expect(
readJson(appTree, 'my-app/tsconfig.json').compilerOptions
.esModuleInterop
).toBe(true);
});
it('should not set esModuleInterop when using the browser builder', async () => {
await generateApp(appTree, 'my-app', { bundler: 'webpack' });
expect(
readJson(appTree, 'my-app/tsconfig.json').compilerOptions
.esModuleInterop
).toBeUndefined();
});
});
describe('nested', () => {
@ -354,13 +371,31 @@ describe('app', () => {
},
].forEach(hasJsonValue);
});
it('should set esModuleInterop when using the application builder', async () => {
await generateApp(appTree, 'my-app', { rootProject: true });
expect(
readJson(appTree, 'tsconfig.json').compilerOptions.esModuleInterop
).toBe(true);
});
it('should not set esModuleInterop when using the browser builder', async () => {
await generateApp(appTree, 'my-app', {
rootProject: true,
bundler: 'webpack',
});
expect(
readJson(appTree, 'tsconfig.json').compilerOptions.esModuleInterop
).toBeUndefined();
});
});
describe('routing', () => {
it('should include RouterTestingModule', async () => {
await generateApp(appTree, 'my-app', {
await generateApp(appTree, 'myApp', {
directory: 'my-dir/my-app',
routing: true,
});
expect(
appTree.read('my-dir/my-app/src/app/app.module.ts', 'utf-8')
@ -371,9 +406,8 @@ describe('app', () => {
});
it('should not modify tests when --skip-tests is set', async () => {
await generateApp(appTree, 'my-app', {
await generateApp(appTree, 'myApp', {
directory: 'my-dir/my-app',
routing: true,
skipTests: true,
});
expect(
@ -748,7 +782,6 @@ describe('app', () => {
// ACT
await generateApp(appTree, 'standalone', {
standalone: true,
routing: true,
});
// ASSERT
@ -794,46 +827,6 @@ describe('app', () => {
appTree.read('standalone/src/app/nx-welcome.component.ts', 'utf-8')
).toContain('standalone: true');
});
it('should prompt for standalone components and not use them when the user selects false', async () => {
// ARRANGE
process.env.NX_INTERACTIVE = 'true';
// @ts-ignore
enquirer.prompt = jest
.fn()
.mockReturnValue(Promise.resolve({ 'standalone-components': false }));
// ACT
await generateApp(appTree, 'nostandalone');
// ASSERT
expect(appTree.exists('nostandalone/src/app/app.module.ts')).toBeTruthy();
expect(enquirer.prompt).toHaveBeenCalled();
// CLEANUP
process.env.NX_INTERACTIVE = undefined;
});
it('should prompt for standalone components and use them when the user selects true', async () => {
// ARRANGE
process.env.NX_INTERACTIVE = 'true';
// @ts-ignore
enquirer.prompt = jest
.fn()
.mockReturnValue(Promise.resolve({ 'standalone-components': true }));
// ACT
await generateApp(appTree, 'nostandalone');
// ASSERT
expect(
appTree.exists('nostandalone/src/app/app.module.ts')
).not.toBeTruthy();
expect(enquirer.prompt).toHaveBeenCalled();
// CLEANUP
process.env.NX_INTERACTIVE = undefined;
});
});
it('should generate correct main.ts', async () => {
@ -880,29 +873,9 @@ describe('app', () => {
});
});
it('should error correctly when Angular version does not support standalone', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
updateJson(tree, 'package.json', (json) => ({
...json,
dependencies: {
'@angular/core': '14.0.0',
},
}));
// ACT & ASSERT
await expect(
generateApp(tree, 'my-app', {
standalone: true,
})
).rejects
.toThrow(stripIndents`The "standalone" option is only supported in Angular >= 14.1.0. You are currently using 14.0.0.
You can resolve this error by removing the "standalone" option or by migrating to Angular 14.1.0.`);
});
describe('--minimal', () => {
it('should skip "nx-welcome.component.ts" file and references for non-standalone apps without routing', async () => {
await generateApp(appTree, 'plain', { minimal: true });
await generateApp(appTree, 'plain', { minimal: true, routing: false });
expect(
appTree.exists('plain/src/app/nx-welcome.component.ts')
@ -922,7 +895,7 @@ describe('app', () => {
});
it('should skip "nx-welcome.component.ts" file and references for non-standalone apps with routing', async () => {
await generateApp(appTree, 'plain', { minimal: true, routing: true });
await generateApp(appTree, 'plain', { minimal: true });
expect(
appTree.exists('plain/src/app/nx-welcome.component.ts')
@ -942,7 +915,11 @@ describe('app', () => {
});
it('should skip "nx-welcome.component.ts" file and references for standalone apps without routing', async () => {
await generateApp(appTree, 'plain', { minimal: true, standalone: true });
await generateApp(appTree, 'plain', {
minimal: true,
standalone: true,
routing: false,
});
expect(
appTree.exists('plain/src/app/nx-welcome.component.ts')
@ -962,7 +939,6 @@ describe('app', () => {
await generateApp(appTree, 'plain', {
minimal: true,
standalone: true,
routing: true,
});
expect(
@ -981,23 +957,91 @@ describe('app', () => {
it('should generate a correct build target for --bundler=esbuild', async () => {
await generateApp(appTree, 'ngesbuild', {
routing: true,
bundler: 'esbuild',
});
const project = readProjectConfiguration(appTree, 'ngesbuild');
expect(project.targets.build.executor).toEqual(
'@angular-devkit/build-angular:browser-esbuild'
'@angular-devkit/build-angular:application'
);
expect(
project.targets.build.configurations.development.buildOptimizer
).toBeUndefined();
expect(
project.targets.build.configurations.development.namedChunks
).toBeUndefined();
expect(
project.targets.build.configurations.development.vendorChunks
).toBeUndefined();
expect(project.targets.build.configurations.production.budgets)
.toMatchInlineSnapshot(`
[
{
"maximumError": "1mb",
"maximumWarning": "500kb",
"type": "initial",
},
{
"maximumError": "4kb",
"maximumWarning": "2kb",
"type": "anyComponentStyle",
},
]
`);
});
it('should generate a correct build target for --bundler=webpack', async () => {
await generateApp(appTree, 'app1', {
bundler: 'webpack',
});
const project = readProjectConfiguration(appTree, 'app1');
expect(project.targets.build.executor).toEqual(
'@angular-devkit/build-angular:browser'
);
expect(
project.targets.build.configurations.production.budgets
).toBeUndefined();
project.targets.build.configurations.development.buildOptimizer
).toBe(false);
expect(project.targets.build.configurations.development.namedChunks).toBe(
true
);
expect(project.targets.build.configurations.development.vendorChunk).toBe(
true
);
expect(project.targets.build.configurations.production.budgets)
.toMatchInlineSnapshot(`
[
{
"maximumError": "1mb",
"maximumWarning": "500kb",
"type": "initial",
},
{
"maximumError": "4kb",
"maximumWarning": "2kb",
"type": "anyComponentStyle",
},
]
`);
});
it('should generate target options "browser" and "buildTarget"', async () => {
await generateApp(appTree, 'my-app', { standalone: true });
const project = readProjectConfiguration(appTree, 'my-app');
expect(project.targets.build.options.browser).toBeDefined();
expect(
project.targets.serve.configurations.development.buildTarget
).toBeDefined();
});
});
describe('--ssr', () => {
it('should generate with ssr set up', async () => {
await generateApp(appTree, 'app1', { ssr: true });
expect(appTree.exists('app1/src/main.server.ts')).toBe(true);
expect(appTree.exists('app1/server.ts')).toBe(true);
});
});
@ -1079,6 +1123,67 @@ describe('app', () => {
expect(tsconfigE2E).toMatchSnapshot('e2e tsconfig.json');
});
});
describe('angular v15 support', () => {
beforeEach(() => {
appTree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
updateJson(appTree, 'package.json', (json) => ({
...json,
dependencies: {
...json.dependencies,
'@angular/core': '~15.2.0',
},
}));
});
it('should import "ApplicationConfig" from "@angular/platform-browser"', async () => {
await generateApp(appTree, 'my-app', { standalone: true });
expect(
appTree.read('my-app/src/app/app.config.ts', 'utf-8')
).toMatchSnapshot();
});
it('should use "@angular-devkit/build-angular:browser-esbuild" for --bundler=esbuild', async () => {
await generateApp(appTree, 'my-app', {
standalone: true,
bundler: 'esbuild',
});
const project = readProjectConfiguration(appTree, 'my-app');
expect(project.targets.build.executor).toEqual(
'@angular-devkit/build-angular:browser-esbuild'
);
});
it('should generate target options "main" and "browserTarget"', async () => {
await generateApp(appTree, 'my-app', { standalone: true });
const project = readProjectConfiguration(appTree, 'my-app');
expect(project.targets.build.options.main).toBeDefined();
expect(
project.targets.serve.configurations.development.browserTarget
).toBeDefined();
});
it('should not set esModuleInterop when using the browser-esbuild builder', async () => {
await generateApp(appTree, 'my-app', { bundler: 'esbuild' });
expect(
readJson(appTree, 'my-app/tsconfig.json').compilerOptions
.esModuleInterop
).toBeUndefined();
});
it('should not set esModuleInterop when using the browser builder', async () => {
await generateApp(appTree, 'my-app', { bundler: 'webpack' });
expect(
readJson(appTree, 'my-app/tsconfig.json').compilerOptions
.esModuleInterop
).toBeUndefined();
});
});
});
async function generateApp(
@ -1092,6 +1197,7 @@ async function generateApp(
e2eTestRunner: E2eTestRunner.Cypress,
unitTestRunner: UnitTestRunner.Jest,
linter: Linter.EsLint,
standalone: false,
...options,
});
}

View File

@ -4,13 +4,12 @@ import {
installPackagesTask,
offsetFromRoot,
readNxJson,
stripIndents,
Tree,
updateNxJson,
} from '@nx/devkit';
import { angularInitGenerator } from '../init/init';
import { setupSsr } from '../setup-ssr/setup-ssr';
import { setupTailwindGenerator } from '../setup-tailwind/setup-tailwind';
import { getInstalledAngularVersionInfo } from '../utils/version-utils';
import {
addE2e,
addLinting,
@ -24,8 +23,6 @@ import {
updateEditorTsConfig,
} from './lib';
import type { Schema } from './schema';
import { gte, lt } from 'semver';
import { prompt } from 'enquirer';
export async function applicationGenerator(
tree: Tree,
@ -41,25 +38,6 @@ export async function applicationGeneratorInternal(
tree: Tree,
schema: Partial<Schema>
): Promise<GeneratorCallback> {
const installedAngularVersionInfo = getInstalledAngularVersionInfo(tree);
if (lt(installedAngularVersionInfo.version, '14.1.0') && schema.standalone) {
throw new Error(stripIndents`The "standalone" option is only supported in Angular >= 14.1.0. You are currently using ${installedAngularVersionInfo.version}.
You can resolve this error by removing the "standalone" option or by migrating to Angular 14.1.0.`);
}
if (
gte(installedAngularVersionInfo.version, '14.1.0') &&
schema.standalone === undefined &&
process.env.NX_INTERACTIVE === 'true'
) {
schema.standalone = await prompt({
name: 'standalone-components',
message: 'Would you like to use Standalone Components?',
type: 'confirm',
}).then((a) => a['standalone-components']);
}
const options = await normalizeOptions(tree, schema);
const rootOffset = offsetFromRoot(options.appProjectRoot);
@ -101,6 +79,13 @@ export async function applicationGeneratorInternal(
setApplicationStrictDefault(tree, false);
}
if (options.ssr) {
await setupSsr(tree, {
project: options.name,
standalone: options.standalone,
});
}
if (!options.skipFormat) {
await formatFiles(tree);
}

View File

@ -4,7 +4,7 @@
"outDir": "<%= rootOffset %>dist/out-tsc",
"types": []
},
"files": ["src/main.ts"<% if(installedAngularInfo.major === 14) { %>, "src/polyfills.ts"<% } %>],
"files": ["src/main.ts"],
"include": ["src/**/*.d.ts"],
"exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"]
}

View File

@ -1,7 +1,8 @@
{
"compilerOptions": {
"target": <% if(installedAngularInfo.major === 14) { %>"es2020"<% } else { %>"es2022"<% } %><% if(installedAngularInfo.major === 15) { %>,
"useDefineForClassFields": false<% } %>
"target": "es2022",
"useDefineForClassFields": false<% if (isUsingApplicationBuilder) { %>,
"esModuleInterop": true<% } %>
},
"files": [],
"include": [],

View File

@ -9,7 +9,7 @@ import { NxWelcomeComponent } from './nx-welcome.component';<% } %>
declarations: [AppComponent<% if(!minimal) { %>, NxWelcomeComponent<% } %>],
imports: [
BrowserModule,<% if(routing) { %>
RouterModule.forRoot(appRoutes, { initialNavigation: 'enabledBlocking' }),<% } %>
RouterModule.forRoot(appRoutes),<% } %>
],
providers: [],
bootstrap: [AppComponent],

View File

@ -742,9 +742,9 @@ import { Component, ViewEncapsulation } from '@angular/core';
Add UI library
</summary>
<pre><span># Generate UI lib</span>
nx g @nx/angular:lib ui
nx g &#64;nx/angular:lib ui
<span># Add a component</span>
nx g @nx/angular:component button --project ui</pre>
nx g &#64;nx/angular:component button --project ui</pre>
</details>
<details>
<summary>

View File

@ -1,11 +1,5 @@
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';<% if(installedAngularInfo.major === 14) { %>
import { enableProdMode } from '@angular/core';<% } %>
import { AppModule } from './app/app.module';<% if(installedAngularInfo.major === 14) { %>
import { environment } from './environments/environment';
if(environment.production) {
enableProdMode();
}<% } %>
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic()
.bootstrapModule(AppModule)

View File

@ -1,7 +1,7 @@
import { ApplicationConfig } from <% if (installedAngularInfo.major >= 16) { %>'@angular/core';<% } else { %>'@angular/platform-browser';<% } %><% if (routing) { %>
import { provideRouter, withEnabledBlockingInitialNavigation } from '@angular/router';
import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes';<% } %>
export const appConfig: ApplicationConfig = {
providers: [<% if (routing) { %>provideRouter(appRoutes, withEnabledBlockingInitialNavigation()) <% } %>]
providers: [<% if (routing) { %>provideRouter(appRoutes) <% } %>]
};

View File

@ -745,9 +745,9 @@ import { CommonModule } from '@angular/common';
Add UI library
</summary>
<pre><span># Generate UI lib</span>
nx g @nx/angular:lib ui
nx g &#64;nx/angular:lib ui
<span># Add a component</span>
nx g @nx/angular:component button --project ui</pre>
nx g &#64;nx/angular:component button --project ui</pre>
</details>
<details>
<summary>

View File

@ -1,5 +0,0 @@
{
"extends": "./tsconfig.json",
"include": ["src/**/*.ts"],
"compilerOptions": {}
}

View File

@ -1,16 +0,0 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR

View File

@ -1,3 +0,0 @@
export const environment = {
production: true
};

View File

@ -1,16 +0,0 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/plugins/zone-error';

View File

@ -1,53 +0,0 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes recent versions of Safari, Chrome (including
* Opera), Edge on the desktop, and iOS and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/

View File

@ -1,3 +1,4 @@
import { configurationGenerator } from '@nx/cypress';
import type { Tree } from '@nx/devkit';
import {
addDependenciesToPackageJson,
@ -9,8 +10,8 @@ import {
updateProjectConfiguration,
} from '@nx/devkit';
import { nxVersion } from '../../../utils/versions';
import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
import type { NormalizedSchema } from './normalized-schema';
import { configurationGenerator } from '@nx/cypress';
export async function addE2e(tree: Tree, options: NormalizedSchema) {
if (options.e2eTestRunner === 'cypress') {
@ -70,12 +71,19 @@ function addFileServerTarget(
) {
addDependenciesToPackageJson(tree, {}, { '@nx/web': nxVersion });
const { major: angularMajorVersion } = getInstalledAngularVersionInfo(tree);
const isUsingApplicationBuilder =
angularMajorVersion >= 17 && options.bundler === 'esbuild';
const projectConfig = readProjectConfiguration(tree, options.name);
projectConfig.targets[targetName] = {
executor: '@nx/web:file-server',
options: {
buildTarget: `${options.name}:build`,
port: options.port,
staticFilePath: isUsingApplicationBuilder
? joinPathFragments(options.outputPath, 'browser')
: undefined,
},
};
updateProjectConfiguration(tree, options.name, projectConfig);

View File

@ -1,9 +1,8 @@
import { Tree, joinPathFragments } from '@nx/devkit';
import type { NormalizedSchema } from './normalized-schema';
import { configurationGenerator } from '@nx/jest';
import { UnitTestRunner } from '../../../utils/test-runners';
import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
import type { NormalizedSchema } from './normalized-schema';
export async function addUnitTestRunner(host: Tree, options: NormalizedSchema) {
if (options.unitTestRunner === UnitTestRunner.Jest) {
@ -20,6 +19,7 @@ export async function addUnitTestRunner(host: Tree, options: NormalizedSchema) {
'src',
'test-setup.ts'
);
const { major: angularMajorVersion } = getInstalledAngularVersionInfo(host);
if (options.strict && host.exists(setupFile)) {
const contents = host.read(setupFile, 'utf-8');
host.write(
@ -31,7 +31,17 @@ globalThis.ngJest = {
errorOnUnknownProperties: true,
},
};
${contents}`
${contents}${
angularMajorVersion >= 17
? `
/**
* Angular uses performance.mark() which is not supported by jsdom. Stub it out
* to avoid errors.
*/
global.performance.mark = jest.fn();
`
: ''
}`
);
}
}

View File

@ -12,6 +12,9 @@ export async function createFiles(
rootOffset: string
) {
const installedAngularInfo = getInstalledAngularVersionInfo(tree);
const isUsingApplicationBuilder =
installedAngularInfo.major >= 17 && options.bundler === 'esbuild';
const substitutions = {
rootSelector: `${options.prefix}-root`,
appName: options.name,
@ -26,6 +29,7 @@ export async function createFiles(
rootTsConfig: joinPathFragments(rootOffset, getRootTsConfigFileName(tree)),
installedAngularInfo,
rootOffset,
isUsingApplicationBuilder,
tpl: '',
};
@ -36,15 +40,6 @@ export async function createFiles(
substitutions
);
if (installedAngularInfo.major === 14) {
generateFiles(
tree,
joinPathFragments(__dirname, '../files/v14'),
options.appProjectRoot,
substitutions
);
}
if (options.standalone) {
generateFiles(
tree,
@ -53,7 +48,7 @@ export async function createFiles(
substitutions
);
} else {
await generateFiles(
generateFiles(
tree,
joinPathFragments(__dirname, '../files/ng-module'),
options.appProjectRoot,
@ -65,7 +60,13 @@ export async function createFiles(
tree,
options.appProjectRoot,
'app',
options,
{
bundler: options.bundler,
rootProject: options.rootProject,
strict: options.strict,
style: options.style,
esModuleInterop: isUsingApplicationBuilder,
},
getRelativePathToRootTsConfig(tree, options.appProjectRoot)
);

View File

@ -5,70 +5,79 @@ import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
import type { NormalizedSchema } from './normalized-schema';
export function createProject(tree: Tree, options: NormalizedSchema) {
const installedAngularInfo = getInstalledAngularVersionInfo(tree);
const { major: angularMajorVersion } = getInstalledAngularVersionInfo(tree);
const buildExecutor =
options.bundler === 'webpack'
? '@angular-devkit/build-angular:browser'
: angularMajorVersion >= 17
? '@angular-devkit/build-angular:application'
: '@angular-devkit/build-angular:browser-esbuild';
const buildTargetOptionName =
angularMajorVersion >= 17 ? 'buildTarget' : 'browserTarget';
const buildMainOptionName =
angularMajorVersion >= 17 && options.bundler === 'esbuild'
? 'browser'
: 'main';
let budgets = undefined;
if (options.bundler === 'webpack' || angularMajorVersion >= 17) {
if (options.strict) {
budgets = [
{ type: 'initial', maximumWarning: '500kb', maximumError: '1mb' },
{
type: 'anyComponentStyle',
maximumWarning: '2kb',
maximumError: '4kb',
},
];
} else {
budgets = [
{ type: 'initial', maximumWarning: '2mb', maximumError: '5mb' },
{
type: 'anyComponentStyle',
maximumWarning: '6kb',
maximumError: '10kb',
},
];
}
}
const inlineStyleLanguage =
options?.style !== 'css' ? options.style : undefined;
const project: AngularProjectConfiguration = {
name: options.name,
projectType: 'application',
prefix: options.prefix,
root: options.appProjectRoot,
sourceRoot: `${options.appProjectRoot}/src`,
sourceRoot: options.appProjectSourceRoot,
tags: options.parsedTags,
targets: {
build: {
executor: buildExecutor,
outputs: ['{options.outputPath}'],
options: {
outputPath: `dist/${
!options.rootProject ? options.appProjectRoot : options.name
}`,
index: `${options.appProjectRoot}/src/index.html`,
main: `${options.appProjectRoot}/src/main.ts`,
polyfills:
installedAngularInfo.major === 14
? `${options.appProjectRoot}/src/polyfills.ts`
: ['zone.js'],
outputPath: options.outputPath,
index: `${options.appProjectSourceRoot}/index.html`,
[buildMainOptionName]: `${options.appProjectSourceRoot}/main.ts`,
polyfills: ['zone.js'],
tsConfig: `${options.appProjectRoot}/tsconfig.app.json`,
inlineStyleLanguage,
assets: [
`${options.appProjectRoot}/src/favicon.ico`,
`${options.appProjectRoot}/src/assets`,
`${options.appProjectSourceRoot}/favicon.ico`,
`${options.appProjectSourceRoot}/assets`,
],
styles: [`${options.appProjectRoot}/src/styles.${options.style}`],
styles: [`${options.appProjectSourceRoot}/styles.${options.style}`],
scripts: [],
},
configurations: {
production: {
budgets:
options.bundler === 'webpack'
? [
{
type: 'initial',
maximumWarning: '500kb',
maximumError: '1mb',
},
{
type: 'anyComponentStyle',
maximumWarning: '2kb',
maximumError: '4kb',
},
]
: undefined,
fileReplacements:
installedAngularInfo.major === 14
? [
{
replace: `${options.appProjectRoot}/src/environments/environment.ts`,
with: `${options.appProjectRoot}/src/environments/environment.prod.ts`,
},
]
: undefined,
budgets,
outputHashing: 'all',
},
development: {
buildOptimizer: false,
buildOptimizer: options.bundler === 'webpack' ? false : undefined,
optimization: false,
vendorChunk: options.bundler === 'webpack' ? true : undefined,
extractLicenses: false,
@ -87,10 +96,10 @@ export function createProject(tree: Tree, options: NormalizedSchema) {
: undefined,
configurations: {
production: {
browserTarget: `${options.name}:build:production`,
[buildTargetOptionName]: `${options.name}:build:production`,
},
development: {
browserTarget: `${options.name}:build:development`,
[buildTargetOptionName]: `${options.name}:build:development`,
},
},
defaultConfiguration: 'development',
@ -98,7 +107,7 @@ export function createProject(tree: Tree, options: NormalizedSchema) {
'extract-i18n': {
executor: '@angular-devkit/build-angular:extract-i18n',
options: {
browserTarget: `${options.name}:build`,
[buildTargetOptionName]: `${options.name}:build`,
},
},
},

View File

@ -1,11 +1,12 @@
import { Tree } from '@nx/devkit';
import { joinPathFragments, type Tree } from '@nx/devkit';
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
import { getNpmScope } from '@nx/js/src/utils/package-json/get-npm-scope';
import { Linter } from '@nx/eslint';
import { getNpmScope } from '@nx/js/src/utils/package-json/get-npm-scope';
import { E2eTestRunner, UnitTestRunner } from '../../../utils/test-runners';
import { normalizeNewProjectPrefix } from '../../utils/project';
import type { Schema } from '../schema';
import type { NormalizedSchema } from './normalized-schema';
import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
export async function normalizeOptions(
host: Tree,
@ -39,10 +40,16 @@ export async function normalizeOptions(
'app'
);
let bundler = options.bundler;
if (!bundler) {
const { major: angularMajorVersion } = getInstalledAngularVersionInfo(host);
bundler = angularMajorVersion >= 17 ? 'esbuild' : 'webpack';
}
// Set defaults and then overwrite with user options
return {
style: 'css',
routing: false,
routing: true,
inlineStyle: false,
inlineTemplate: false,
skipTests: options.unitTestRunner === UnitTestRunner.None,
@ -51,13 +58,20 @@ export async function normalizeOptions(
e2eTestRunner: E2eTestRunner.Cypress,
linter: Linter.EsLint,
strict: true,
bundler: options.bundler ?? 'webpack',
standalone: true,
...options,
prefix,
name: appProjectName,
appProjectRoot,
appProjectSourceRoot: `${appProjectRoot}/src`,
e2eProjectRoot,
e2eProjectName,
parsedTags,
bundler,
outputPath: joinPathFragments(
'dist',
!options.rootProject ? appProjectRoot : appProjectName
),
ssr: options.ssr ?? false,
};
}

View File

@ -8,7 +8,9 @@ export interface NormalizedSchema extends Schema {
e2eTestRunner: E2eTestRunner;
prefix: string;
appProjectRoot: string;
appProjectSourceRoot: string;
e2eProjectName: string;
e2eProjectRoot: string;
parsedTags: string[];
outputPath: string;
}

View File

@ -30,4 +30,5 @@ export interface Schema {
rootProject?: boolean;
minimal?: boolean;
bundler?: 'webpack' | 'esbuild';
ssr?: boolean;
}

View File

@ -56,9 +56,8 @@
},
"routing": {
"type": "boolean",
"description": "Generate a routing module.",
"default": false,
"x-prompt": "Would you like to configure routing for this application?",
"description": "Enable routing for the application.",
"default": true,
"x-priority": "important"
},
"inlineStyle": {
@ -155,8 +154,9 @@
"default": false
},
"standalone": {
"description": "Generate an application that is setup to use standalone components. _Note: This is only supported in Angular versions >= 14.1.0_",
"description": "Generate an application that is setup to use standalone components.",
"type": "boolean",
"default": true,
"x-priority": "important"
},
"rootProject": {
@ -172,10 +172,17 @@
"default": false
},
"bundler": {
"description": "Bundler to use to build the application.",
"description": "Bundler to use to build the application. It defaults to `esbuild` for Angular versions >= 17.0.0. Otherwise, it defaults to `webpack`. _Note: The `esbuild` bundler is only considered stable from Angular v17._",
"type": "string",
"enum": ["webpack", "esbuild"],
"default": "webpack"
"x-prompt": "Which bundler do you want to use to build the application?",
"x-priority": "important"
},
"ssr": {
"description": "Creates an application with Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering) enabled.",
"type": "boolean",
"x-prompt": "Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)?",
"default": false
}
},
"additionalProperties": false,

View File

@ -1,9 +1,4 @@
import {
addProjectConfiguration,
stripIndents,
updateJson,
writeJson,
} from '@nx/devkit';
import { addProjectConfiguration, writeJson } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { componentGenerator } from './component';
@ -33,6 +28,7 @@ describe('component Generator', () => {
await componentGenerator(tree, {
name: 'example',
project: 'lib1',
standalone: false,
});
// ASSERT
@ -79,6 +75,7 @@ describe('component Generator', () => {
name: 'example',
project: 'lib1',
skipTests: true,
standalone: false,
});
// ASSERT
@ -113,6 +110,7 @@ describe('component Generator', () => {
name: 'example',
project: 'lib1',
inlineTemplate: true,
standalone: false,
});
// ASSERT
@ -150,6 +148,7 @@ describe('component Generator', () => {
name: 'example',
project: 'lib1',
inlineStyle: true,
standalone: false,
});
// ASSERT
@ -187,6 +186,7 @@ describe('component Generator', () => {
name: 'example',
project: 'lib1',
style: 'none',
standalone: false,
});
// ASSERT
@ -232,6 +232,7 @@ describe('component Generator', () => {
name: 'example',
project: 'lib1',
export: true,
standalone: false,
});
// ASSERT
@ -270,7 +271,6 @@ describe('component Generator', () => {
await componentGenerator(tree, {
name: 'example',
project: 'lib1',
standalone: true,
export: true,
});
@ -314,6 +314,7 @@ describe('component Generator', () => {
name: 'example',
project: 'lib1',
export: false,
standalone: false,
});
// ASSERT
@ -354,7 +355,6 @@ describe('component Generator', () => {
await componentGenerator(tree, {
name: 'example',
project: 'lib1',
standalone: true,
export: false,
});
@ -397,6 +397,7 @@ describe('component Generator', () => {
name: 'example',
project: 'lib1',
skipImport: true,
standalone: false,
});
// ASSERT
@ -437,6 +438,7 @@ describe('component Generator', () => {
name: 'example',
project: 'lib1',
export: true,
standalone: false,
});
// ASSERT
@ -476,6 +478,7 @@ describe('component Generator', () => {
name: 'example',
project: 'lib1',
export: true,
standalone: false,
});
// ASSERT
@ -511,6 +514,7 @@ describe('component Generator', () => {
project: 'lib1',
flat: true,
export: true,
standalone: false,
});
// ASSERT
@ -551,6 +555,7 @@ describe('component Generator', () => {
project: 'lib1',
flat: true,
export: false,
standalone: false,
});
// ASSERT
@ -595,6 +600,7 @@ describe('component Generator', () => {
project: 'lib1',
path: 'libs/lib1/src/lib/mycomp',
export: true,
standalone: false,
});
// ASSERT
@ -638,6 +644,7 @@ describe('component Generator', () => {
project: 'lib1',
path: 'apps/app1/src/mycomp',
export: false,
standalone: false,
})
).rejects.toThrow();
});
@ -683,6 +690,7 @@ describe('component Generator', () => {
project: 'lib1',
module,
export: true,
standalone: false,
});
// ASSERT
@ -723,6 +731,7 @@ describe('component Generator', () => {
project: 'shared-ui',
export: true,
flat: false,
standalone: false,
});
// ASSERT
@ -771,6 +780,7 @@ describe('component Generator', () => {
project: 'lib1',
module: 'not-exported',
export: true,
standalone: false,
});
// ASSERT
@ -808,6 +818,7 @@ describe('component Generator', () => {
project: 'lib1',
path: 'libs/lib1/src/lib',
module: 'not-found',
standalone: false,
})
).rejects.toThrow();
});
@ -849,6 +860,7 @@ describe('component Generator', () => {
name: 'example',
project: 'lib1',
path: 'libs/lib1/src/lib',
standalone: false,
})
).rejects.toThrow();
});
@ -902,6 +914,7 @@ describe('component Generator', () => {
project: 'lib1',
path: 'libs/lib1/secondary/src/lib',
export: true,
standalone: false,
});
// ASSERT
@ -961,6 +974,7 @@ describe('component Generator', () => {
name: 'example',
project: 'lib1',
export: true,
standalone: false,
});
// ASSERT
@ -971,32 +985,4 @@ describe('component Generator', () => {
expect(indexSource).toBe('');
});
});
it('should error correctly when Angular version does not support standalone', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
updateJson(tree, 'package.json', (json) => ({
...json,
dependencies: {
'@angular/core': '14.0.0',
},
}));
addProjectConfiguration(tree, 'lib1', {
projectType: 'library',
sourceRoot: 'libs/lib1/src',
root: 'libs/lib1',
});
// ACT & ASSERT
await expect(
componentGenerator(tree, {
name: 'example',
project: 'lib1',
standalone: true,
})
).rejects
.toThrow(stripIndents`The "standalone" option is only supported in Angular >= 14.1.0. You are currently using "14.0.0".
You can resolve this error by removing the "standalone" option or by migrating to Angular 14.1.0.`);
});
});

View File

@ -10,7 +10,6 @@ import {
exportComponentInEntryPoint,
findModuleFromOptions,
normalizeOptions,
validateOptions,
} from './lib';
import type { Schema } from './schema';
@ -25,7 +24,6 @@ export async function componentGeneratorInternal(
tree: Tree,
rawOptions: Schema
) {
validateOptions(tree, rawOptions);
const options = await normalizeOptions(tree, rawOptions);
generateFiles(

View File

@ -1,4 +1,3 @@
export * from './component';
export * from './module';
export * from './normalize-options';
export * from './validate-options';

View File

@ -46,6 +46,7 @@ export async function normalizeOptions(
projectName,
changeDetection: options.changeDetection ?? 'Default',
style: options.style ?? 'css',
standalone: options.standalone ?? true,
directory,
fileName,
filePath,

View File

@ -1,7 +0,0 @@
import type { Tree } from '@nx/devkit';
import { validateStandaloneOption } from '../../utils/validations';
import type { Schema } from '../schema';
export function validateOptions(tree: Tree, options: Schema): void {
validateStandaloneOption(tree, options.standalone);
}

View File

@ -60,9 +60,9 @@
"alias": "t"
},
"standalone": {
"description": "Whether the generated component is standalone. _Note: This is only supported in Angular versions >= 14.1.0_.",
"description": "Whether the generated component is standalone.",
"type": "boolean",
"default": false,
"default": true,
"x-priority": "important"
},
"viewEncapsulation": {

View File

@ -221,6 +221,7 @@ describe('Cypress Component Testing Configuration', () => {
it('should use own project config', async () => {
await generateTestApplication(tree, {
name: 'fancy-app',
bundler: 'webpack',
});
await componentGenerator(tree, {
name: 'fancy-cmp',
@ -259,6 +260,7 @@ describe('Cypress Component Testing Configuration', () => {
it('should use the project graph to find the correct project config', async () => {
await generateTestApplication(tree, {
name: 'fancy-app',
bundler: 'webpack',
});
await generateTestLibrary(tree, {
name: 'fancy-lib',

View File

@ -1,30 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`directive generator should export the directive correctly when flat=false and path is nested deeper 1`] = `
"import { Directive } from '@angular/core';
@Directive({
selector: '[projTest]',
})
export class TestDirective {
constructor() {}
}
"
`;
exports[`directive generator should export the directive correctly when flat=false and path is nested deeper 2`] = `
"import { TestDirective } from './test.directive';
describe('TestDirective', () => {
it('should create an instance', () => {
const directive = new TestDirective();
expect(directive).toBeTruthy();
});
});
"
`;
exports[`directive generator should export the directive correctly when flat=false and path is nested deeper 3`] = `
exports[`directive generator --no-standalone should export the directive correctly when flat=false and path is nested deeper 1`] = `
"import { NgModule } from '@angular/core';
import { TestDirective } from './my-directives/test/test.directive';
@NgModule({
@ -36,7 +12,7 @@ export class TestModule {}
"
`;
exports[`directive generator should generate a directive with test files and attach to the NgModule automatically 1`] = `
exports[`directive generator --no-standalone should generate a directive with test files and attach to the NgModule automatically 1`] = `
"import { Directive } from '@angular/core';
@Directive({
@ -48,7 +24,7 @@ export class TestDirective {
"
`;
exports[`directive generator should generate a directive with test files and attach to the NgModule automatically 2`] = `
exports[`directive generator --no-standalone should generate a directive with test files and attach to the NgModule automatically 2`] = `
"import { TestDirective } from './test.directive';
describe('TestDirective', () => {
@ -60,7 +36,7 @@ describe('TestDirective', () => {
"
`;
exports[`directive generator should generate a directive with test files and attach to the NgModule automatically 3`] = `
exports[`directive generator --no-standalone should generate a directive with test files and attach to the NgModule automatically 3`] = `
"import { NgModule } from '@angular/core';
import { TestDirective } from './test.directive';
@NgModule({
@ -72,7 +48,7 @@ export class TestModule {}
"
`;
exports[`directive generator should import the directive correctly when flat=false 1`] = `
exports[`directive generator --no-standalone should import the directive correctly when flat=false 1`] = `
"import { Directive } from '@angular/core';
@Directive({
@ -84,7 +60,7 @@ export class TestDirective {
"
`;
exports[`directive generator should import the directive correctly when flat=false 2`] = `
exports[`directive generator --no-standalone should import the directive correctly when flat=false 2`] = `
"import { TestDirective } from './test.directive';
describe('TestDirective', () => {
@ -96,7 +72,7 @@ describe('TestDirective', () => {
"
`;
exports[`directive generator should import the directive correctly when flat=false 3`] = `
exports[`directive generator --no-standalone should import the directive correctly when flat=false 3`] = `
"import { NgModule } from '@angular/core';
import { TestDirective } from './test/test.directive';
@NgModule({
@ -108,7 +84,7 @@ export class TestModule {}
"
`;
exports[`directive generator should import the directive correctly when flat=false and path is nested deeper 1`] = `
exports[`directive generator --no-standalone should import the directive correctly when flat=false and path is nested deeper 1`] = `
"import { Directive } from '@angular/core';
@Directive({
@ -120,7 +96,7 @@ export class TestDirective {
"
`;
exports[`directive generator should import the directive correctly when flat=false and path is nested deeper 2`] = `
exports[`directive generator --no-standalone should import the directive correctly when flat=false and path is nested deeper 2`] = `
"import { TestDirective } from './test.directive';
describe('TestDirective', () => {
@ -132,7 +108,7 @@ describe('TestDirective', () => {
"
`;
exports[`directive generator should import the directive correctly when flat=false and path is nested deeper 3`] = `
exports[`directive generator --no-standalone should import the directive correctly when flat=false and path is nested deeper 3`] = `
"import { NgModule } from '@angular/core';
import { TestDirective } from './my-directives/test/test.directive';
@NgModule({
@ -144,66 +120,7 @@ export class TestModule {}
"
`;
exports[`directive generator should not generate test file when skipTests=true 1`] = `
"import { Directive } from '@angular/core';
@Directive({
selector: '[projTest]',
})
export class TestDirective {
constructor() {}
}
"
`;
exports[`directive generator should not generate test file when skipTests=true 2`] = `
"import { NgModule } from '@angular/core';
import { TestDirective } from './my-directives/test/test.directive';
@NgModule({
imports: [],
declarations: [TestDirective],
exports: [],
})
export class TestModule {}
"
`;
exports[`directive generator should not import the directive when skipImport=true 1`] = `
"import { Directive } from '@angular/core';
@Directive({
selector: '[projTest]',
})
export class TestDirective {
constructor() {}
}
"
`;
exports[`directive generator should not import the directive when skipImport=true 2`] = `
"import { TestDirective } from './test.directive';
describe('TestDirective', () => {
it('should create an instance', () => {
const directive = new TestDirective();
expect(directive).toBeTruthy();
});
});
"
`;
exports[`directive generator should not import the directive when skipImport=true 3`] = `
"import { NgModule } from '@angular/core';
@NgModule({
imports: [],
declarations: [],
exports: [],
})
export class TestModule {}
"
`;
exports[`directive generator should not import the directive when standalone=true 1`] = `
exports[`directive generator should generate correctly 1`] = `
"import { Directive } from '@angular/core';
@Directive({
@ -216,7 +133,7 @@ export class TestDirective {
"
`;
exports[`directive generator should not import the directive when standalone=true 2`] = `
exports[`directive generator should generate correctly 2`] = `
"import { TestDirective } from './test.directive';
describe('TestDirective', () => {
@ -227,14 +144,3 @@ describe('TestDirective', () => {
});
"
`;
exports[`directive generator should not import the directive when standalone=true 3`] = `
"import { NgModule } from '@angular/core';
@NgModule({
imports: [],
declarations: [],
exports: [],
})
export class TestModule {}
"
`;

Some files were not shown because too many files have changed in this diff Show More