feat(angular): add support for angular v19 (#28847)
## Third-party deps support for Angular v19
- [x] `jest-preset-angular`
- [x] PRs:
- [x] https://github.com/thymikee/jest-preset-angular/pull/2835
- [x] Released:
- [x] RC:
https://github.com/thymikee/jest-preset-angular/releases/tag/v14.4.0-rc.0
- [x] Stable:
https://github.com/thymikee/jest-preset-angular/releases/tag/v14.4.0
- [x] Angular ESLint
- [x] PRs:
- [x] https://github.com/angular-eslint/angular-eslint/pull/2109
- [x] Released:
- [x]
https://github.com/angular-eslint/angular-eslint/releases/tag/v19.0.0
- [x] Storybook
- [x] PRs:
- [x] https://github.com/storybookjs/storybook/pull/29659
- [x] https://github.com/storybookjs/storybook/pull/29677
- [x] Released:
- [x] https://github.com/storybookjs/storybook/pull/29679
- [ ] NgRx
- [x] PRs:
- [x] https://github.com/ngrx/platform/pull/4602
- [ ] Released:
- [x] Beta:
https://github.com/ngrx/platform/blob/main/CHANGELOG.md#1900-beta0-2024-11-20
- [ ] Stable:
- [ ] Analog
- [x] PRs:
- [x] https://github.com/analogjs/analog/pull/1447
- [x] https://github.com/analogjs/analog/pull/1451
- [ ] Released:
- [x] Beta:
https://github.com/analogjs/analog/releases/tag/v1.10.0-beta.6
- [ ] Stable:
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->
<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->
<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->
## Current Behavior
<!-- This is the behavior we have today -->
Angular v19 is not supported.
## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Angular v19 should be supported.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #29028
This commit is contained in:
parent
06d549b043
commit
3ec539065d
@ -108,7 +108,7 @@
|
||||
"type": "executor"
|
||||
},
|
||||
"/nx-api/angular/executors/application": {
|
||||
"description": "Builds an Angular application using [esbuild](https://esbuild.github.io/) with integrated SSR and prerendering capabilities. _Note: this is only supported in Angular versions >= 17.0.0_.",
|
||||
"description": "Builds an Angular application using [esbuild](https://esbuild.github.io/) with integrated SSR and prerendering capabilities.",
|
||||
"file": "generated/packages/angular/executors/application.json",
|
||||
"hidden": false,
|
||||
"name": "application",
|
||||
@ -200,7 +200,7 @@
|
||||
"type": "generator"
|
||||
},
|
||||
"/nx-api/angular/generators/convert-to-application-executor": {
|
||||
"description": "Converts projects to use the `@nx/angular:application` executor or the `@angular-devkit/build-angular:application` builder. _Note: this is only supported in Angular versions >= 17.0.0_.",
|
||||
"description": "Converts projects to use the `@nx/angular:application` executor or the `@angular-devkit/build-angular:application` builder.",
|
||||
"file": "generated/packages/angular/generators/convert-to-application-executor.json",
|
||||
"hidden": false,
|
||||
"name": "convert-to-application-executor",
|
||||
|
||||
@ -103,7 +103,7 @@
|
||||
"type": "executor"
|
||||
},
|
||||
{
|
||||
"description": "Builds an Angular application using [esbuild](https://esbuild.github.io/) with integrated SSR and prerendering capabilities. _Note: this is only supported in Angular versions >= 17.0.0_.",
|
||||
"description": "Builds an Angular application using [esbuild](https://esbuild.github.io/) with integrated SSR and prerendering capabilities.",
|
||||
"file": "generated/packages/angular/executors/application.json",
|
||||
"hidden": false,
|
||||
"name": "application",
|
||||
@ -195,7 +195,7 @@
|
||||
"type": "generator"
|
||||
},
|
||||
{
|
||||
"description": "Converts projects to use the `@nx/angular:application` executor or the `@angular-devkit/build-angular:application` builder. _Note: this is only supported in Angular versions >= 17.0.0_.",
|
||||
"description": "Converts projects to use the `@nx/angular:application` executor or the `@angular-devkit/build-angular:application` builder.",
|
||||
"file": "generated/packages/angular/generators/convert-to-application-executor.json",
|
||||
"hidden": false,
|
||||
"name": "convert-to-application-executor",
|
||||
|
||||
@ -14,6 +14,7 @@ We provide a recommended version, and it is usually the latest minor version of
|
||||
|
||||
| Angular Version | **Nx Version _(recommended)_** | Nx Version _(range)_ |
|
||||
| --------------- | ------------------------------ | ---------------------------------------- |
|
||||
| ~19.0.0 | **latest** | >=20.2.0 <=latest |
|
||||
| ~18.2.0 | **latest** | >=19.6.0 <=latest |
|
||||
| ~18.1.0 | **latest** | >=19.5.0 <=latest |
|
||||
| ~18.0.0 | **latest** | >=19.1.0 <=latest |
|
||||
@ -21,9 +22,9 @@ We provide a recommended version, and it is usually the latest minor version of
|
||||
| ~17.2.0 | **latest** | >=18.1.1 <=latest |
|
||||
| ~17.1.0 | **latest** | >=17.3.0 <=latest |
|
||||
| ~17.0.0 | **latest** | >=17.1.0 <=latest |
|
||||
| ~16.2.0 | **latest** | >=16.7.0 <=latest |
|
||||
| ~16.1.0 | **latest** | >=16.4.0 <=latest |
|
||||
| ~16.0.0 | **latest** | >=16.1.0 <=latest |
|
||||
| ~16.2.0 | **~20.1.0** | >=16.7.0 <20.2.0 |
|
||||
| ~16.1.0 | **~20.1.0** | >=16.4.0 <20.2.0 |
|
||||
| ~16.0.0 | **~20.1.0** | >=16.1.0 <20.2.0 |
|
||||
| ~15.2.0 | **~19.0.0** | >=15.8.0 <19.1.0 |
|
||||
| ~15.1.0 | **~19.0.0** | >=15.5.0 <19.1.0 |
|
||||
| ~15.0.0 | **~19.0.0** | >=15.2.0 <=15.4.8 \|\| >=15.7.0 <19.1.0 |
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"schema": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"title": "Schema for Nx Application Executor",
|
||||
"description": "Builds an Angular application using [esbuild](https://esbuild.github.io/) with integrated SSR and prerendering capabilities. _Note: this is only supported in Angular versions >= 17.0.0_.",
|
||||
"description": "Builds an Angular application using [esbuild](https://esbuild.github.io/) with integrated SSR and prerendering capabilities.",
|
||||
"examplesFile": "This executor is a drop-in replacement for the `@angular-devkit/build-angular:application` builder provided by the Angular CLI. It builds an Angular application using [esbuild](https://esbuild.github.io/) with integrated SSR and prerendering capabilities.\n\nIn addition to the features provided by the Angular CLI builder, the `@nx/angular:application` executor also supports the following:\n\n- Providing esbuild plugins\n- Providing a function to transform the application's `index.html` file\n- Incremental builds\n\n{% callout type=\"check\" title=\"Dev Server\" %}\nThe [`@nx/angular:dev-server` executor](/nx-api/angular/executors/dev-server) is required to serve your application when using the `@nx/angular:application` to build it. It is a drop-in replacement for the Angular CLI's `@angular-devkit/build-angular:dev-server` builder and ensures the application is correctly served with Vite when using the `@nx/angular:application` executor.\n{% /callout %}\n\n## Examples\n\n{% tabs %}\n{% tab label=\"Providing esbuild plugins\" %}\n\nThe executor accepts a `plugins` option that allows you to provide esbuild plugins that will be used when building your application. It allows providing a path to a plugin file or an object with a `path` and `options` property to provide options to the plugin.\n\n```json {% fileName=\"apps/my-app/project.json\" highlightLines=[\"8-16\"] %}\n{\n ...\n \"targets\": {\n \"build\": {\n \"executor\": \"@nx/angular:application\",\n \"options\": {\n ...\n \"plugins\": [\n \"apps/my-app/plugins/plugin1.js\",\n {\n \"path\": \"apps/my-app/plugins/plugin2.js\",\n \"options\": {\n \"someOption\": \"some value\"\n }\n }\n ]\n }\n }\n ...\n }\n}\n```\n\n```ts {% fileName=\"apps/my-app/plugins/plugin1.js\" %}\nconst plugin1 = {\n name: 'plugin1',\n setup(build) {\n const options = build.initialOptions;\n options.define.PLUGIN1_TEXT = '\"Value was provided at build time\"';\n },\n};\n\nmodule.exports = plugin1;\n```\n\n```ts {% fileName=\"apps/my-app/plugins/plugin2.js\" %}\nfunction plugin2({ someOption }) {\n return {\n name: 'plugin2',\n setup(build) {\n const options = build.initialOptions;\n options.define.PLUGIN2_TEXT = JSON.stringify(someOption);\n },\n };\n}\n\nmodule.exports = plugin2;\n```\n\nAdditionally, we need to inform TypeScript of the defined variables to prevent type-checking errors during the build. We can achieve this by creating or updating a type definition file included in the TypeScript build process (e.g. `src/types.d.ts`) with the following content:\n\n```ts {% fileName=\"apps/my-app/src/types.d.ts\" %}\ndeclare const PLUGIN1_TEXT: number;\ndeclare const PLUGIN2_TEXT: string;\n```\n\n{% /tab %}\n\n{% tab label=\"Transforming the 'index.html' file\" %}\n\nThe executor accepts an `indexHtmlTransformer` option to provide a path to a file with a default export for a function that receives the application's `index.html` file contents and outputs the updated contents.\n\n```json {% fileName=\"apps/my-app/project.json\" highlightLines=[8] %}\n{\n ...\n \"targets\": {\n \"build\": {\n \"executor\": \"@nx/angular:application\",\n \"options\": {\n ...\n \"indexHtmlTransformer\": \"apps/my-app/index-html.transformer.ts\"\n }\n }\n ...\n }\n}\n```\n\n```ts {% fileName=\"apps/my-app/index-html.transformer.ts\" %}\nexport default function (indexContent: string) {\n return indexContent.replace(\n '<title>my-app</title>',\n '<title>my-app (transformed)</title>'\n );\n}\n```\n\n{% /tab %}\n{% /tabs %}\n",
|
||||
"outputCapture": "direct-nodejs",
|
||||
"type": "object",
|
||||
@ -54,9 +54,20 @@
|
||||
"description": "The full path for the browser entry point to the application, relative to the current workspace."
|
||||
},
|
||||
"server": {
|
||||
"type": "string",
|
||||
"description": "The full path for the server entry point to the application, relative to the current workspace.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "The full path for the server entry point to the application, relative to the current workspace."
|
||||
},
|
||||
{
|
||||
"const": false,
|
||||
"type": "boolean",
|
||||
"description": "Indicates that a server entry point is not provided. _Note: this is only supported in Angular versions >= 19.0.0_."
|
||||
}
|
||||
]
|
||||
},
|
||||
"polyfills": {
|
||||
"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'.",
|
||||
"type": "array",
|
||||
@ -71,6 +82,29 @@
|
||||
"type": "string",
|
||||
"description": "Customize the base path for the URLs of resources in 'index.html' and component stylesheets. This option is only necessary for specific deployment scenarios, such as with Angular Elements or when utilizing different CDN locations. _Note: this is only supported in Angular versions >= 17.3.0_."
|
||||
},
|
||||
"security": {
|
||||
"description": "Security features to protect against XSS and other common attacks. _Note: this is only supported in Angular versions >= 19.0.0_.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"autoCsp": {
|
||||
"description": "Enables automatic generation of a hash-based Strict Content Security Policy (https://web.dev/articles/strict-csp#choose-hash) based on scripts in index.html. Will default to true once we are out of experimental/preview phases. It defaults to `false`.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"unsafeEval": {
|
||||
"type": "boolean",
|
||||
"description": "Include the `unsafe-eval` directive (https://web.dev/articles/strict-csp#remove-eval) in the auto-CSP. Please only enable this if you are absolutely sure that you need to, as allowing calls to eval will weaken the XSS defenses provided by the auto-CSP. It default to `false`."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{ "type": "boolean" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"description": "Global scripts to be included in the build.",
|
||||
"type": "array",
|
||||
@ -157,6 +191,28 @@
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"default": []
|
||||
},
|
||||
"sass": {
|
||||
"description": "Options to pass to the sass preprocessor. _Note: this is only supported in Angular versions >= 19.0.0_.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fatalDeprecations": {
|
||||
"description": "A set of deprecations to treat as fatal. If a deprecation warning of any provided type is encountered during compilation, the compiler will error instead. If a Version is provided, then all deprecations that were active in that compiler version will be treated as fatal.",
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"silenceDeprecations": {
|
||||
"description": " A set of active deprecations to ignore. If a deprecation warning of any provided type is encountered during compilation, the compiler will ignore it instead.",
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"futureDeprecations": {
|
||||
"description": "A set of future deprecations to opt into early. Future deprecations passed here will be treated as active by the compiler, emitting warnings as necessary.",
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@ -546,8 +602,7 @@
|
||||
"default": []
|
||||
},
|
||||
"prerender": {
|
||||
"description": "Prerender (SSG) pages of your application during build time.",
|
||||
"default": false,
|
||||
"description": "Prerender (SSG) pages of your application during build time. It defaults to `false` in Angular versions < 19.0.0. Otherwise, the value will be `undefined`.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "boolean",
|
||||
@ -584,6 +639,11 @@
|
||||
"entry": {
|
||||
"type": "string",
|
||||
"description": "The server entry-point that when executed will spawn the web server."
|
||||
},
|
||||
"experimentalPlatform": {
|
||||
"description": "Specifies the platform for which the server bundle is generated. This affects the APIs and modules available in the server-side code. \n\n- `node`: (Default) Generates a bundle optimized for Node.js environments. \n- `neutral`: Generates a platform-neutral bundle suitable for environments like edge workers, and other serverless platforms. This option avoids using Node.js-specific APIs, making the bundle more portable. \n\nPlease note that this feature does not provide polyfills for Node.js modules. Additionally, it is experimental, and the feature may undergo changes in future versions. _Note: this is only supported in Angular versions >= 19.0.0_.",
|
||||
"default": "node",
|
||||
"enum": ["node", "neutral"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@ -592,8 +652,12 @@
|
||||
},
|
||||
"appShell": {
|
||||
"type": "boolean",
|
||||
"description": "Generates an application shell during build time.",
|
||||
"default": false
|
||||
"description": "Generates an application shell during build time. It defaults to `false` in Angular versions < 19.0.0. Otherwise, the value will be `undefined`."
|
||||
},
|
||||
"outputMode": {
|
||||
"type": "string",
|
||||
"description": "Defines the build output target. 'static': Generates a static site for deployment on any static hosting service. 'server': Produces an application designed for deployment on a server that supports server-side rendering (SSR). _Note: this is only supported in Angular versions >= 19.0.0_.",
|
||||
"enum": ["static", "server"]
|
||||
},
|
||||
"buildLibsFromSource": {
|
||||
"type": "boolean",
|
||||
@ -739,7 +803,7 @@
|
||||
},
|
||||
"presets": []
|
||||
},
|
||||
"description": "Builds an Angular application using [esbuild](https://esbuild.github.io/) with integrated SSR and prerendering capabilities. _Note: this is only supported in Angular versions >= 17.0.0_.",
|
||||
"description": "Builds an Angular application using [esbuild](https://esbuild.github.io/) with integrated SSR and prerendering capabilities.",
|
||||
"aliases": [],
|
||||
"hidden": false,
|
||||
"path": "/packages/angular/src/executors/application/schema.json",
|
||||
|
||||
@ -514,7 +514,7 @@
|
||||
"default": true
|
||||
},
|
||||
"plugins": {
|
||||
"description": "A list of ESBuild plugins. _Note: this is only supported in Angular versions >= 17.0.0_.",
|
||||
"description": "A list of ESBuild plugins.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
|
||||
@ -93,8 +93,7 @@
|
||||
},
|
||||
"hmr": {
|
||||
"type": "boolean",
|
||||
"description": "Enable hot module replacement.",
|
||||
"default": false
|
||||
"description": "Enable hot module replacement. It defaults to `false` in Angular versions < 19.0.0. Otherwise, the value will be `undefined`."
|
||||
},
|
||||
"watch": {
|
||||
"type": "boolean",
|
||||
@ -107,7 +106,7 @@
|
||||
},
|
||||
"forceEsbuild": {
|
||||
"type": "boolean",
|
||||
"description": "Force the development server to use the 'browser-esbuild' builder when building. This is a developer preview option for the esbuild-based build system. _Note: this is only supported in Angular versions >= 16.1.0_.",
|
||||
"description": "Force the development server to use the 'browser-esbuild' builder when building. This is a developer preview option for the esbuild-based build system.",
|
||||
"default": false
|
||||
},
|
||||
"inspect": {
|
||||
@ -145,7 +144,7 @@
|
||||
"x-priority": "important"
|
||||
},
|
||||
"esbuildMiddleware": {
|
||||
"description": "A list of HTTP request middleware functions. _Note: this is only supported in Angular versions >= 17.0.0_.",
|
||||
"description": "A list of HTTP request middleware functions.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
|
||||
@ -10,10 +10,6 @@
|
||||
"cli": "nx",
|
||||
"type": "object",
|
||||
"presets": [
|
||||
{
|
||||
"name": "Buildable Library with Tailwind",
|
||||
"keys": ["project", "tailwindConfig"]
|
||||
},
|
||||
{
|
||||
"name": "Updating Project Dependencies for Buildable Library",
|
||||
"keys": ["project"]
|
||||
@ -39,10 +35,6 @@
|
||||
"poll": {
|
||||
"type": "number",
|
||||
"description": "Enable and define the file watching poll time period in milliseconds. _Note: this is only supported in Angular versions >= 18.0.0_."
|
||||
},
|
||||
"tailwindConfig": {
|
||||
"type": "string",
|
||||
"description": "The full path for the Tailwind configuration file, relative to the workspace root. If not provided and a `tailwind.config.js` file exists in the project or workspace root, it will be used. Otherwise, Tailwind will not be configured."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
@ -10,13 +10,6 @@
|
||||
"cli": "nx",
|
||||
"type": "object",
|
||||
"presets": [
|
||||
{
|
||||
"name": "Publishable Library with Tailwind",
|
||||
"keys": [
|
||||
"project",
|
||||
"tailwindConfig"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Updating Project Dependencies for Publishable Library",
|
||||
"keys": [
|
||||
@ -44,12 +37,6 @@
|
||||
"poll": {
|
||||
"type": "number",
|
||||
"description": "Enable and define the file watching poll time period in milliseconds. _Note: this is only supported in Angular versions >= 18.0.0_."
|
||||
},
|
||||
"tailwindConfig": {
|
||||
"type": "string",
|
||||
"description": "The full path for the Tailwind configuration file, relative to the workspace root. If not provided and a `tailwind.config.js` file exists in the project or workspace root, it will be used. Otherwise, Tailwind will not be configured. _Note: starting with Angular v17, this option is no longer used and the configuration will be picked up if exists at the project or workspace root_.",
|
||||
"x-completion-type": "file",
|
||||
"x-completion-glob": "tailwind.config@(.js|.cjs|.mjs|.ts)"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
@ -166,9 +166,10 @@
|
||||
"default": false
|
||||
},
|
||||
"bundler": {
|
||||
"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._",
|
||||
"description": "Bundler to use to build the application.",
|
||||
"type": "string",
|
||||
"enum": ["webpack", "esbuild"],
|
||||
"enum": ["esbuild", "webpack"],
|
||||
"default": "esbuild",
|
||||
"x-prompt": "Which bundler do you want to use to build the application?",
|
||||
"x-priority": "important"
|
||||
},
|
||||
@ -177,6 +178,10 @@
|
||||
"type": "boolean",
|
||||
"x-prompt": "Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)?",
|
||||
"default": false
|
||||
},
|
||||
"serverRouting": {
|
||||
"description": "Creates a server application using the Server Routing and App Engine APIs (Developer Preview). _Note: this is only supported in Angular versions >= 19.0.0_.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
@ -104,6 +104,11 @@
|
||||
"default": false,
|
||||
"x-priority": "important"
|
||||
},
|
||||
"exportDefault": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Use default export for the component instead of a named export."
|
||||
},
|
||||
"skipFormat": {
|
||||
"description": "Skip formatting files.",
|
||||
"type": "boolean",
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"$id": "NxAngularConvertToApplicationExecutorGenerator",
|
||||
"cli": "nx",
|
||||
"title": "Converts projects to use the `@nx/angular:application` executor or the `@angular-devkit/build-angular:application` builder. _Note: this is only supported in Angular versions >= 17.0.0_.",
|
||||
"title": "Converts projects to use the `@nx/angular:application` executor or the `@angular-devkit/build-angular:application` builder.",
|
||||
"description": "Converts a project or all projects using one of the `@angular-devkit/build-angular:browser`, `@angular-devkit/build-angular:browser-esbuild`, `@nx/angular:browser` and `@nx/angular:browser-esbuild` executors to use the `@nx/angular:application` executor or the `@angular-devkit/build-angular:application` builder. If the converted target is using one of the `@nx/angular` executors, the `@nx/angular:application` executor will be used. Otherwise, the `@angular-devkit/build-angular:application` builder will be used.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -25,7 +25,7 @@
|
||||
"additionalProperties": false,
|
||||
"presets": []
|
||||
},
|
||||
"description": "Converts projects to use the `@nx/angular:application` executor or the `@angular-devkit/build-angular:application` builder. _Note: this is only supported in Angular versions >= 17.0.0_.",
|
||||
"description": "Converts projects to use the `@nx/angular:application` executor or the `@angular-devkit/build-angular:application` builder.",
|
||||
"implementation": "/packages/angular/src/generators/convert-to-application-executor/convert-to-application-executor.ts",
|
||||
"aliases": [],
|
||||
"hidden": false,
|
||||
|
||||
@ -169,6 +169,10 @@
|
||||
"default": false,
|
||||
"x-priority": "important"
|
||||
},
|
||||
"serverRouting": {
|
||||
"description": "Creates a server application using the Server Routing and App Engine APIs (Developer Preview). _Note: this is only supported in Angular versions >= 19.0.0_.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"typescriptConfiguration": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the module federation configuration and webpack configuration files should use TS.",
|
||||
|
||||
@ -162,6 +162,10 @@
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"serverRouting": {
|
||||
"description": "Creates a server application using the Server Routing and App Engine APIs (Developer Preview). _Note: this is only supported in Angular versions >= 19.0.0_.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"typescriptConfiguration": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the module federation configuration and webpack configuration files should use TS.",
|
||||
|
||||
@ -16,13 +16,6 @@
|
||||
"x-prompt": "What app would you like to generate an Angular Universal configuration for?",
|
||||
"x-dropdown": "projects"
|
||||
},
|
||||
"appId": {
|
||||
"type": "string",
|
||||
"format": "html-selector",
|
||||
"description": "The `appId` to use with `withServerTransition`.",
|
||||
"default": "serverApp",
|
||||
"x-deprecated": "This is deprecated and ignored since Angular 16 and not supported since Angular 17."
|
||||
},
|
||||
"main": {
|
||||
"type": "string",
|
||||
"format": "path",
|
||||
@ -56,7 +49,12 @@
|
||||
},
|
||||
"hydration": {
|
||||
"type": "boolean",
|
||||
"description": "Set up Hydration for the SSR application. It defaults to `true` for Angular versions >= 17.0.0. Otherwise, it defaults to `false`."
|
||||
"description": "Set up Hydration for the SSR application.",
|
||||
"default": true
|
||||
},
|
||||
"serverRouting": {
|
||||
"description": "Creates a server application using the Server Routing and App Engine APIs (Developer Preview). _Note: this is only supported in Angular versions >= 19.0.0_.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"skipFormat": {
|
||||
"type": "boolean",
|
||||
|
||||
@ -80,6 +80,10 @@
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"serverRouting": {
|
||||
"description": "Use the Angular Server Routing and App Engine APIs (Developer Preview).",
|
||||
"type": "boolean"
|
||||
},
|
||||
"prefix": {
|
||||
"description": "The prefix to use for Angular component and directive selectors.",
|
||||
"type": "string"
|
||||
|
||||
@ -97,6 +97,10 @@
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"serverRouting": {
|
||||
"description": "Use the Angular Server Routing and App Engine APIs (Developer Preview).",
|
||||
"type": "boolean"
|
||||
},
|
||||
"prefix": {
|
||||
"description": "The prefix to use for Angular component and directive selectors.",
|
||||
"type": "string"
|
||||
|
||||
@ -423,8 +423,7 @@ to have the following targets:
|
||||
"executor": "@nx/angular:package",
|
||||
"outputs": ["{workspaceRoot}/dist/libs/lib1"],
|
||||
"options": {
|
||||
"project": "libs/lib1/ng-package.json",
|
||||
"tailwindConfig": "libs/lib1/tailwind.config.js"
|
||||
"project": "libs/lib1/ng-package.json"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
|
||||
@ -14,6 +14,7 @@ We provide a recommended version, and it is usually the latest minor version of
|
||||
|
||||
| Angular Version | **Nx Version _(recommended)_** | Nx Version _(range)_ |
|
||||
| --------------- | ------------------------------ | ---------------------------------------- |
|
||||
| ~19.0.0 | **latest** | >=20.2.0 <=latest |
|
||||
| ~18.2.0 | **latest** | >=19.6.0 <=latest |
|
||||
| ~18.1.0 | **latest** | >=19.5.0 <=latest |
|
||||
| ~18.0.0 | **latest** | >=19.1.0 <=latest |
|
||||
@ -21,9 +22,9 @@ We provide a recommended version, and it is usually the latest minor version of
|
||||
| ~17.2.0 | **latest** | >=18.1.1 <=latest |
|
||||
| ~17.1.0 | **latest** | >=17.3.0 <=latest |
|
||||
| ~17.0.0 | **latest** | >=17.1.0 <=latest |
|
||||
| ~16.2.0 | **latest** | >=16.7.0 <=latest |
|
||||
| ~16.1.0 | **latest** | >=16.4.0 <=latest |
|
||||
| ~16.0.0 | **latest** | >=16.1.0 <=latest |
|
||||
| ~16.2.0 | **~20.1.0** | >=16.7.0 <20.2.0 |
|
||||
| ~16.1.0 | **~20.1.0** | >=16.4.0 <20.2.0 |
|
||||
| ~16.0.0 | **~20.1.0** | >=16.1.0 <20.2.0 |
|
||||
| ~15.2.0 | **~19.0.0** | >=15.8.0 <19.1.0 |
|
||||
| ~15.1.0 | **~19.0.0** | >=15.5.0 <19.1.0 |
|
||||
| ~15.0.0 | **~19.0.0** | >=15.2.0 <=15.4.8 \|\| >=15.7.0 <19.1.0 |
|
||||
|
||||
@ -80,8 +80,8 @@ const angularV1Json = (appName: string) => `{
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "4kb"
|
||||
"maximumWarning": "4kb",
|
||||
"maximumError": "8kb"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
|
||||
@ -199,8 +199,8 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
|
||||
},
|
||||
{
|
||||
type: 'anyComponentStyle',
|
||||
maximumWarning: '2kB',
|
||||
maximumError: '4kB',
|
||||
maximumWarning: '4kB',
|
||||
maximumError: '8kB',
|
||||
},
|
||||
],
|
||||
outputHashing: 'all',
|
||||
|
||||
@ -163,6 +163,15 @@ describe('Angular Projects', () => {
|
||||
}, 1000000);
|
||||
|
||||
it('should lint correctly with eslint and handle external HTML files and inline templates', async () => {
|
||||
// disable the prefer-standalone rule for app1 which is not standalone
|
||||
let app1EslintConfig = readFile(`${app1}/eslint.config.js`);
|
||||
app1EslintConfig = app1EslintConfig.replace(
|
||||
`'@angular-eslint/directive-selector': [`,
|
||||
`'@angular-eslint/prefer-standalone': 'off',
|
||||
'@angular-eslint/directive-selector': [`
|
||||
);
|
||||
updateFile(`${app1}/eslint.config.js`, app1EslintConfig);
|
||||
|
||||
// check apps and lib pass linting for initial generated code
|
||||
runCLI(`run-many --target lint --projects=${app1},${lib1} --parallel`);
|
||||
|
||||
@ -380,6 +389,7 @@ describe('Angular Projects', () => {
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: false,
|
||||
templateUrl: './app.component.html',
|
||||
})
|
||||
export class AppComponent {
|
||||
|
||||
@ -137,6 +137,7 @@ describe('Tailwind support', () => {
|
||||
|
||||
@Component({
|
||||
selector: '${project}-foo',
|
||||
standalone: false,
|
||||
template: '<button class="custom-btn text-white ${buttonBgColor}">Click me!</button>',
|
||||
styles: [\`
|
||||
.custom-btn {
|
||||
@ -176,7 +177,7 @@ describe('Tailwind support', () => {
|
||||
libSpacing: (typeof spacing)['root']
|
||||
) => {
|
||||
const builtComponentContent = readFile(
|
||||
`dist/${lib}/esm2022/lib/foo.component.mjs`
|
||||
`dist/${lib}/fesm2022/${project}-${lib}.mjs`
|
||||
);
|
||||
let expectedStylesRegex = new RegExp(
|
||||
`styles: \\[\\"\\.custom\\-btn(\\[_ngcontent\\-%COMP%\\])?{margin:${libSpacing.md};padding:${libSpacing.sm}}(\\\\n)?\\"\\]`
|
||||
|
||||
@ -701,6 +701,7 @@ describe('Linter', () => {
|
||||
runCLI(
|
||||
`generate @nx/react:app --name=${myapp} --unitTestRunner=jest --linter eslint --directory="."`
|
||||
);
|
||||
runCLI('reset', { env: { CI: 'false' } });
|
||||
verifySuccessfulStandaloneSetup(myapp);
|
||||
|
||||
let appEslint = readJson('.eslintrc.json');
|
||||
@ -716,6 +717,7 @@ describe('Linter', () => {
|
||||
runCLI(
|
||||
`generate @nx/js:lib libs/${mylib} --unitTestRunner=jest --linter eslint`
|
||||
);
|
||||
runCLI('reset', { env: { CI: 'false' } });
|
||||
verifySuccessfulMigratedSetup(myapp, mylib);
|
||||
|
||||
appEslint = readJson(`.eslintrc.json`);
|
||||
@ -737,6 +739,7 @@ describe('Linter', () => {
|
||||
runCLI(
|
||||
`generate @nx/angular:app --name=${myapp} --directory="." --linter eslint --no-interactive`
|
||||
);
|
||||
runCLI('reset', { env: { CI: 'false' } });
|
||||
verifySuccessfulStandaloneSetup(myapp);
|
||||
|
||||
let appEslint = readJson('.eslintrc.json');
|
||||
@ -751,6 +754,7 @@ describe('Linter', () => {
|
||||
runCLI(
|
||||
`generate @nx/js:lib libs/${mylib} --linter eslint --no-interactive`
|
||||
);
|
||||
runCLI('reset', { env: { CI: 'false' } });
|
||||
verifySuccessfulMigratedSetup(myapp, mylib);
|
||||
|
||||
appEslint = readJson(`.eslintrc.json`);
|
||||
@ -770,6 +774,7 @@ describe('Linter', () => {
|
||||
runCLI(
|
||||
`generate @nx/node:app --name=${myapp} --linter eslint --directory="." --no-interactive`
|
||||
);
|
||||
runCLI('reset', { env: { CI: 'false' } });
|
||||
verifySuccessfulStandaloneSetup(myapp);
|
||||
|
||||
let appEslint = readJson('.eslintrc.json');
|
||||
@ -786,6 +791,7 @@ describe('Linter', () => {
|
||||
runCLI(
|
||||
`generate @nx/js:lib libs/${mylib} --linter eslint --no-interactive`
|
||||
);
|
||||
runCLI('reset', { env: { CI: 'false' } });
|
||||
verifySuccessfulMigratedSetup(myapp, mylib);
|
||||
|
||||
appEslint = readJson(`.eslintrc.json`);
|
||||
|
||||
@ -115,7 +115,7 @@
|
||||
"description": "Copy generated docs to build output"
|
||||
},
|
||||
{
|
||||
"command": "npx ts-node scripts/documentation/plugin-quality-indicators.ts",
|
||||
"command": "npx ts-node -P scripts/tsconfig.scripts.json scripts/documentation/plugin-quality-indicators.ts",
|
||||
"description": "Fetch plugin data"
|
||||
},
|
||||
{
|
||||
|
||||
58
package.json
58
package.json
@ -27,19 +27,19 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@angular-devkit/architect": "~0.1802.0",
|
||||
"@angular-devkit/build-angular": "~18.2.0",
|
||||
"@angular-devkit/core": "~18.2.0",
|
||||
"@angular-devkit/schematics": "~18.2.0",
|
||||
"@angular-eslint/eslint-plugin": "^18.3.0",
|
||||
"@angular-eslint/eslint-plugin-template": "^18.3.0",
|
||||
"@angular-eslint/template-parser": "^18.3.0",
|
||||
"@angular/cli": "~18.2.0",
|
||||
"@angular/common": "~18.2.0",
|
||||
"@angular/compiler": "~18.2.0",
|
||||
"@angular/compiler-cli": "~18.2.0",
|
||||
"@angular/core": "~18.2.0",
|
||||
"@angular/router": "~18.2.0",
|
||||
"@angular-devkit/architect": "~0.1900.0",
|
||||
"@angular-devkit/build-angular": "~19.0.0",
|
||||
"@angular-devkit/core": "~19.0.0",
|
||||
"@angular-devkit/schematics": "~19.0.0",
|
||||
"@angular-eslint/eslint-plugin": "^19.0.0",
|
||||
"@angular-eslint/eslint-plugin-template": "^19.0.0",
|
||||
"@angular-eslint/template-parser": "^19.0.0",
|
||||
"@angular/cli": "~19.0.0",
|
||||
"@angular/common": "~19.0.0",
|
||||
"@angular/compiler": "~19.0.0",
|
||||
"@angular/compiler-cli": "~19.0.0",
|
||||
"@angular/core": "~19.0.0",
|
||||
"@angular/router": "~19.0.0",
|
||||
"@babel/core": "^7.23.2",
|
||||
"@babel/helper-create-regexp-features-plugin": "^7.22.9",
|
||||
"@babel/plugin-transform-runtime": "^7.23.2",
|
||||
@ -105,14 +105,14 @@
|
||||
"@rspack/dev-server": "1.0.9",
|
||||
"@rspack/plugin-minify": "^0.7.5",
|
||||
"@rspack/plugin-react-refresh": "^1.0.0",
|
||||
"@schematics/angular": "~18.2.0",
|
||||
"@storybook/addon-essentials": "^8.2.8",
|
||||
"@storybook/addon-interactions": "^8.2.8",
|
||||
"@storybook/core-server": "^8.2.8",
|
||||
"@storybook/react": "^8.2.8",
|
||||
"@storybook/react-vite": "^8.2.8",
|
||||
"@storybook/react-webpack5": "^8.2.8",
|
||||
"@storybook/types": "^8.2.8",
|
||||
"@schematics/angular": "~19.0.0",
|
||||
"@storybook/addon-essentials": "^8.4.6",
|
||||
"@storybook/addon-interactions": "^8.4.6",
|
||||
"@storybook/core-server": "^8.4.6",
|
||||
"@storybook/react": "^8.4.6",
|
||||
"@storybook/react-vite": "^8.4.6",
|
||||
"@storybook/react-webpack5": "^8.4.6",
|
||||
"@storybook/types": "^8.4.6",
|
||||
"@supabase/supabase-js": "^2.26.0",
|
||||
"@svgr/rollup": "^8.1.0",
|
||||
"@svgr/webpack": "^8.0.1",
|
||||
@ -145,16 +145,16 @@
|
||||
"@types/tmp": "^0.2.0",
|
||||
"@types/yargs": "17.0.10",
|
||||
"@types/yarnpkg__lockfile": "^1.1.5",
|
||||
"@typescript-eslint/rule-tester": "^8.0.0",
|
||||
"@typescript-eslint/type-utils": "^8.0.0",
|
||||
"@typescript-eslint/utils": "^8.0.0",
|
||||
"@typescript-eslint/rule-tester": "^8.13.0",
|
||||
"@typescript-eslint/type-utils": "^8.13.0",
|
||||
"@typescript-eslint/utils": "^8.13.0",
|
||||
"@xstate/immer": "0.3.1",
|
||||
"@xstate/inspect": "0.7.0",
|
||||
"@xstate/react": "3.0.1",
|
||||
"@zkochan/js-yaml": "0.0.7",
|
||||
"ai": "^2.2.10",
|
||||
"ajv": "^8.12.0",
|
||||
"angular-eslint": "^18.3.0",
|
||||
"angular-eslint": "^19.0.0",
|
||||
"autoprefixer": "10.4.13",
|
||||
"babel-jest": "29.7.0",
|
||||
"babel-loader": "^9.1.2",
|
||||
@ -244,7 +244,7 @@
|
||||
"mini-css-extract-plugin": "~2.4.7",
|
||||
"minimatch": "9.0.3",
|
||||
"next-sitemap": "^3.1.10",
|
||||
"ng-packagr": "~18.2.0",
|
||||
"ng-packagr": "~19.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"npm-package-arg": "11.0.1",
|
||||
"nuxt": "^3.10.0",
|
||||
@ -283,7 +283,7 @@
|
||||
"source-map": "0.7.3",
|
||||
"source-map-loader": "^5.0.0",
|
||||
"source-map-support": "0.5.19",
|
||||
"storybook": "^8.2.8",
|
||||
"storybook": "^8.4.6",
|
||||
"storybook-dark-mode": "^4.0.2",
|
||||
"style-loader": "^3.3.0",
|
||||
"tar-stream": "~2.2.0",
|
||||
@ -298,8 +298,8 @@
|
||||
"tsconfig-paths-webpack-plugin": "4.0.0",
|
||||
"typedoc": "0.25.12",
|
||||
"typedoc-plugin-markdown": "3.17.1",
|
||||
"typescript": "~5.5.2",
|
||||
"typescript-eslint": "^8.0.0",
|
||||
"typescript": "~5.6.2",
|
||||
"typescript-eslint": "^8.13.0",
|
||||
"unist-builder": "^4.0.0",
|
||||
"unzipper": "^0.10.11",
|
||||
"url-loader": "^4.1.1",
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
"application": {
|
||||
"implementation": "./src/executors/application/application.impl",
|
||||
"schema": "./src/executors/application/schema.json",
|
||||
"description": "Builds an Angular application using [esbuild](https://esbuild.github.io/) with integrated SSR and prerendering capabilities. _Note: this is only supported in Angular versions >= 17.0.0_."
|
||||
"description": "Builds an Angular application using [esbuild](https://esbuild.github.io/) with integrated SSR and prerendering capabilities."
|
||||
},
|
||||
"extract-i18n": {
|
||||
"implementation": "./src/executors/extract-i18n/extract-i18n.impl",
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
"convert-to-application-executor": {
|
||||
"factory": "./src/generators/convert-to-application-executor/convert-to-application-executor",
|
||||
"schema": "./src/generators/convert-to-application-executor/schema.json",
|
||||
"description": "Converts projects to use the `@nx/angular:application` executor or the `@angular-devkit/build-angular:application` builder. _Note: this is only supported in Angular versions >= 17.0.0_."
|
||||
"description": "Converts projects to use the `@nx/angular:application` executor or the `@angular-devkit/build-angular:application` builder."
|
||||
},
|
||||
"directive": {
|
||||
"factory": "./src/generators/directive/directive",
|
||||
|
||||
@ -278,6 +278,33 @@
|
||||
"version": "20.2.0-beta.2",
|
||||
"description": "Update the withModuleFederation import use @nx/module-federation/angular.",
|
||||
"factory": "./src/migrations/update-20-2-0/migrate-with-mf-import-to-new-package"
|
||||
},
|
||||
"update-angular-cli-version-19-0-0": {
|
||||
"cli": "nx",
|
||||
"version": "20.2.0-beta.5",
|
||||
"requires": {
|
||||
"@angular/core": ">=19.0.0"
|
||||
},
|
||||
"description": "Update the @angular/cli package version to ~19.0.0.",
|
||||
"factory": "./src/migrations/update-20-2-0/update-angular-cli"
|
||||
},
|
||||
"add-localize-polyfill-to-targets": {
|
||||
"cli": "nx",
|
||||
"version": "20.2.0-beta.5",
|
||||
"requires": {
|
||||
"@angular/core": ">=19.0.0-rc.1"
|
||||
},
|
||||
"description": "Add the '@angular/localize/init' polyfill to the 'polyfills' option of targets using esbuild-based executors.",
|
||||
"factory": "./src/migrations/update-20-2-0/add-localize-polyfill-to-targets"
|
||||
},
|
||||
"update-angular-ssr-imports-to-use-node-entry-point": {
|
||||
"cli": "nx",
|
||||
"version": "20.2.0-beta.5",
|
||||
"requires": {
|
||||
"@angular/core": ">=19.0.0-rc.1"
|
||||
},
|
||||
"description": "Update '@angular/ssr' import paths to use the new '/node' entry point when 'CommonEngine' is detected.",
|
||||
"factory": "./src/migrations/update-20-2-0/update-angular-ssr-imports-to-use-node-entry-point"
|
||||
}
|
||||
},
|
||||
"packageJsonUpdates": {
|
||||
@ -1171,7 +1198,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"20.2.0": {
|
||||
"20.2.0-module-federation": {
|
||||
"version": "20.2.0-beta.3",
|
||||
"packages": {
|
||||
"@module-federation/enhanced": {
|
||||
@ -1191,6 +1218,129 @@
|
||||
"alwaysAddToPackageJson": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"20.2.0": {
|
||||
"version": "20.2.0-beta.5",
|
||||
"x-prompt": "Do you want to update the Angular version to v19?",
|
||||
"requires": {
|
||||
"@angular/core": ">=18.2.0 <19.0.0"
|
||||
},
|
||||
"packages": {
|
||||
"@angular-devkit/build-angular": {
|
||||
"version": "~19.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@angular-devkit/core": {
|
||||
"version": "~19.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@angular-devkit/schematics": {
|
||||
"version": "~19.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@angular/build": {
|
||||
"version": "~19.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@angular/pwa": {
|
||||
"version": "~19.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@angular/ssr": {
|
||||
"version": "~19.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@schematics/angular": {
|
||||
"version": "~19.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@angular-devkit/architect": {
|
||||
"version": "~0.1900.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@angular-devkit/build-webpack": {
|
||||
"version": "~0.1900.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@angular/core": {
|
||||
"version": "~19.0.0",
|
||||
"alwaysAddToPackageJson": true
|
||||
},
|
||||
"@angular/material": {
|
||||
"version": "~19.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@angular/cdk": {
|
||||
"version": "~19.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"ng-packagr": {
|
||||
"version": "~19.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"zone.js": {
|
||||
"version": "~0.15.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"20.2.0-analog": {
|
||||
"version": "20.2.0-beta.5",
|
||||
"packages": {
|
||||
"@analogjs/vitest-angular": {
|
||||
"version": "~1.10.0-beta.6",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@analogjs/vite-plugin-angular": {
|
||||
"version": "~1.10.0-beta.6",
|
||||
"alwaysAddToPackageJson": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"20.2.0-jest": {
|
||||
"version": "20.2.0-beta.5",
|
||||
"requires": {
|
||||
"@angular-devkit/build-angular": ">=15.0.0 <20.0.0",
|
||||
"@angular/compiler-cli": ">=15.0.0 <20.0.0",
|
||||
"@angular/core": ">=15.0.0 <20.0.0",
|
||||
"@angular/platform-browser-dynamic": ">=15.0.0 <20.0.0",
|
||||
"jest": "^29.0.0"
|
||||
},
|
||||
"packages": {
|
||||
"jest-preset-angular": {
|
||||
"version": "~14.4.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"20.2.0-angular-eslint": {
|
||||
"version": "20.2.0-beta.5",
|
||||
"requires": {
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"@angular/core": ">= 19.0.0 < 20.0.0"
|
||||
},
|
||||
"packages": {
|
||||
"angular-eslint": {
|
||||
"version": "^19.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@angular-eslint/eslint-plugin": {
|
||||
"version": "^19.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@angular-eslint/eslint-plugin-template": {
|
||||
"version": "^19.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@angular-eslint/template-parser": {
|
||||
"version": "^19.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
},
|
||||
"@angular-eslint/utils": {
|
||||
"version": "^19.0.0",
|
||||
"alwaysAddToPackageJson": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,7 +51,6 @@
|
||||
"@phenomnomnominal/tsquery": "~5.0.1",
|
||||
"@typescript-eslint/type-utils": "^8.0.0",
|
||||
"chalk": "^4.1.0",
|
||||
"find-cache-dir": "^3.3.2",
|
||||
"magic-string": "~0.30.2",
|
||||
"minimatch": "9.0.3",
|
||||
"semver": "^7.5.3",
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { getInstalledAngularVersionInfo } from '../../../executors/utilities/angular-version-utils';
|
||||
import type {
|
||||
NormalizedSchema,
|
||||
Schema,
|
||||
@ -12,12 +13,15 @@ export function normalizeOptions(schema: Schema): NormalizedSchema {
|
||||
delete (schema as SchemaWithBrowserTarget).browserTarget;
|
||||
}
|
||||
|
||||
const { major: angularMajorVersion } = getInstalledAngularVersionInfo();
|
||||
|
||||
return {
|
||||
...schema,
|
||||
buildTarget,
|
||||
host: schema.host ?? 'localhost',
|
||||
port: schema.port ?? 4200,
|
||||
liveReload: schema.liveReload ?? true,
|
||||
hmr: angularMajorVersion < 19 ? schema.hmr ?? false : undefined,
|
||||
open: schema.open ?? false,
|
||||
ssl: schema.ssl ?? false,
|
||||
};
|
||||
|
||||
@ -4,18 +4,7 @@ import { getInstalledAngularVersionInfo } from '../../../executors/utilities/ang
|
||||
import type { Schema } from '../schema';
|
||||
|
||||
export function validateOptions(options: Schema): void {
|
||||
const { major: angularMajorVersion, version: angularVersion } =
|
||||
getInstalledAngularVersionInfo();
|
||||
|
||||
if (lt(angularVersion, '16.1.0') && options.forceEsbuild) {
|
||||
throw new Error(stripIndents`The "forceEsbuild" option is only supported in Angular >= 16.1.0. You are currently using "${angularVersion}".
|
||||
You can resolve this error by removing the "forceEsbuild" option or by migrating to Angular 16.1.0.`);
|
||||
}
|
||||
|
||||
if (angularMajorVersion < 17 && options.esbuildMiddleware?.length > 0) {
|
||||
throw new Error(stripIndents`The "esbuildMiddleware" option is only supported in Angular >= 17.0.0. You are currently using "${angularVersion}".
|
||||
You can resolve this error by removing the "esbuildMiddleware" option or by migrating to Angular 17.0.0.`);
|
||||
}
|
||||
const { version: angularVersion } = getInstalledAngularVersionInfo();
|
||||
|
||||
if (lt(angularVersion, '17.2.0') && options.prebundle) {
|
||||
throw new Error(stripIndents`The "prebundle" option is only supported in Angular >= 17.2.0. You are currently using "${angularVersion}".
|
||||
|
||||
@ -99,8 +99,7 @@
|
||||
},
|
||||
"hmr": {
|
||||
"type": "boolean",
|
||||
"description": "Enable hot module replacement.",
|
||||
"default": false
|
||||
"description": "Enable hot module replacement. It defaults to `false` in Angular versions < 19.0.0. Otherwise, the value will be `undefined`."
|
||||
},
|
||||
"watch": {
|
||||
"type": "boolean",
|
||||
@ -113,7 +112,7 @@
|
||||
},
|
||||
"forceEsbuild": {
|
||||
"type": "boolean",
|
||||
"description": "Force the development server to use the 'browser-esbuild' builder when building. This is a developer preview option for the esbuild-based build system. _Note: this is only supported in Angular versions >= 16.1.0_.",
|
||||
"description": "Force the development server to use the 'browser-esbuild' builder when building. This is a developer preview option for the esbuild-based build system.",
|
||||
"default": false
|
||||
},
|
||||
"inspect": {
|
||||
@ -151,7 +150,7 @@
|
||||
"x-priority": "important"
|
||||
},
|
||||
"esbuildMiddleware": {
|
||||
"description": "A list of HTTP request middleware functions. _Note: this is only supported in Angular versions >= 17.0.0_.",
|
||||
"description": "A list of HTTP request middleware functions.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { buildApplication as buildApplicationFn } from '@angular-devkit/build-angular';
|
||||
import type { ExecutorContext } from '@nx/devkit';
|
||||
import type { DependentBuildableProjectNode } from '@nx/js/src/utils/buildable-libs-utils';
|
||||
import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter';
|
||||
@ -9,14 +10,15 @@ import {
|
||||
loadPlugins,
|
||||
} from '../utilities/esbuild-extensions';
|
||||
import type { ApplicationExecutorOptions } from './schema';
|
||||
import { normalizeOptions } from './utils/normalize-options';
|
||||
import { validateOptions } from './utils/validate-options';
|
||||
import type { buildApplication as buildApplicationFn } from '@angular-devkit/build-angular';
|
||||
|
||||
export default async function* applicationExecutor(
|
||||
options: ApplicationExecutorOptions,
|
||||
context: ExecutorContext
|
||||
): ReturnType<typeof buildApplicationFn> {
|
||||
validateOptions(options);
|
||||
options = normalizeOptions(options);
|
||||
|
||||
const {
|
||||
buildLibsFromSource = true,
|
||||
@ -42,7 +44,6 @@ export default async function* applicationExecutor(
|
||||
? await loadIndexHtmlTransformer(indexHtmlTransformerPath, options.tsConfig)
|
||||
: undefined;
|
||||
|
||||
const { buildApplication } = await import('@angular-devkit/build-angular');
|
||||
const builderContext = await createBuilderContext(
|
||||
{
|
||||
builderName: 'application',
|
||||
@ -54,12 +55,14 @@ export default async function* applicationExecutor(
|
||||
|
||||
const { version: angularVersion } = getInstalledAngularVersionInfo();
|
||||
if (gte(angularVersion, '17.1.0')) {
|
||||
const { buildApplication } = await import('@angular-devkit/build-angular');
|
||||
return yield* buildApplication(delegateExecutorOptions, builderContext, {
|
||||
codePlugins: plugins,
|
||||
indexHtmlTransformer,
|
||||
});
|
||||
}
|
||||
|
||||
const { buildApplication } = require('@angular-devkit/build-angular');
|
||||
return yield* buildApplication(
|
||||
delegateExecutorOptions,
|
||||
builderContext,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"title": "Schema for Nx Application Executor",
|
||||
"description": "Builds an Angular application using [esbuild](https://esbuild.github.io/) with integrated SSR and prerendering capabilities. _Note: this is only supported in Angular versions >= 17.0.0_.",
|
||||
"description": "Builds an Angular application using [esbuild](https://esbuild.github.io/) with integrated SSR and prerendering capabilities.",
|
||||
"examplesFile": "../../../docs/application-executor-examples.md",
|
||||
"outputCapture": "direct-nodejs",
|
||||
"type": "object",
|
||||
@ -19,9 +19,20 @@
|
||||
"description": "The full path for the browser entry point to the application, relative to the current workspace."
|
||||
},
|
||||
"server": {
|
||||
"type": "string",
|
||||
"description": "The full path for the server entry point to the application, relative to the current workspace.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "The full path for the server entry point to the application, relative to the current workspace."
|
||||
},
|
||||
{
|
||||
"const": false,
|
||||
"type": "boolean",
|
||||
"description": "Indicates that a server entry point is not provided. _Note: this is only supported in Angular versions >= 19.0.0_."
|
||||
}
|
||||
]
|
||||
},
|
||||
"polyfills": {
|
||||
"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'.",
|
||||
"type": "array",
|
||||
@ -39,6 +50,31 @@
|
||||
"type": "string",
|
||||
"description": "Customize the base path for the URLs of resources in 'index.html' and component stylesheets. This option is only necessary for specific deployment scenarios, such as with Angular Elements or when utilizing different CDN locations. _Note: this is only supported in Angular versions >= 17.3.0_."
|
||||
},
|
||||
"security": {
|
||||
"description": "Security features to protect against XSS and other common attacks. _Note: this is only supported in Angular versions >= 19.0.0_.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"autoCsp": {
|
||||
"description": "Enables automatic generation of a hash-based Strict Content Security Policy (https://web.dev/articles/strict-csp#choose-hash) based on scripts in index.html. Will default to true once we are out of experimental/preview phases. It defaults to `false`.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"unsafeEval": {
|
||||
"type": "boolean",
|
||||
"description": "Include the `unsafe-eval` directive (https://web.dev/articles/strict-csp#remove-eval) in the auto-CSP. Please only enable this if you are absolutely sure that you need to, as allowing calls to eval will weaken the XSS defenses provided by the auto-CSP. It default to `false`."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"description": "Global scripts to be included in the build.",
|
||||
"type": "array",
|
||||
@ -127,6 +163,34 @@
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"sass": {
|
||||
"description": "Options to pass to the sass preprocessor. _Note: this is only supported in Angular versions >= 19.0.0_.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fatalDeprecations": {
|
||||
"description": "A set of deprecations to treat as fatal. If a deprecation warning of any provided type is encountered during compilation, the compiler will error instead. If a Version is provided, then all deprecations that were active in that compiler version will be treated as fatal.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"silenceDeprecations": {
|
||||
"description": " A set of active deprecations to ignore. If a deprecation warning of any provided type is encountered during compilation, the compiler will ignore it instead.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"futureDeprecations": {
|
||||
"description": "A set of future deprecations to opt into early. Future deprecations passed here will be treated as active by the compiler, emitting warnings as necessary.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@ -480,8 +544,7 @@
|
||||
"default": []
|
||||
},
|
||||
"prerender": {
|
||||
"description": "Prerender (SSG) pages of your application during build time.",
|
||||
"default": false,
|
||||
"description": "Prerender (SSG) pages of your application during build time. It defaults to `false` in Angular versions < 19.0.0. Otherwise, the value will be `undefined`.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "boolean",
|
||||
@ -518,6 +581,11 @@
|
||||
"entry": {
|
||||
"type": "string",
|
||||
"description": "The server entry-point that when executed will spawn the web server."
|
||||
},
|
||||
"experimentalPlatform": {
|
||||
"description": "Specifies the platform for which the server bundle is generated. This affects the APIs and modules available in the server-side code. \n\n- `node`: (Default) Generates a bundle optimized for Node.js environments. \n- `neutral`: Generates a platform-neutral bundle suitable for environments like edge workers, and other serverless platforms. This option avoids using Node.js-specific APIs, making the bundle more portable. \n\nPlease note that this feature does not provide polyfills for Node.js modules. Additionally, it is experimental, and the feature may undergo changes in future versions. _Note: this is only supported in Angular versions >= 19.0.0_.",
|
||||
"default": "node",
|
||||
"enum": ["node", "neutral"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@ -526,8 +594,12 @@
|
||||
},
|
||||
"appShell": {
|
||||
"type": "boolean",
|
||||
"description": "Generates an application shell during build time.",
|
||||
"default": false
|
||||
"description": "Generates an application shell during build time. It defaults to `false` in Angular versions < 19.0.0. Otherwise, the value will be `undefined`."
|
||||
},
|
||||
"outputMode": {
|
||||
"type": "string",
|
||||
"description": "Defines the build output target. 'static': Generates a static site for deployment on any static hosting service. 'server': Produces an application designed for deployment on a server that supports server-side rendering (SSR). _Note: this is only supported in Angular versions >= 19.0.0_.",
|
||||
"enum": ["static", "server"]
|
||||
},
|
||||
"buildLibsFromSource": {
|
||||
"type": "boolean",
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
import { getInstalledAngularVersionInfo } from '../../utilities/angular-version-utils';
|
||||
import type { ApplicationExecutorOptions } from '../schema';
|
||||
|
||||
export function normalizeOptions(
|
||||
options: ApplicationExecutorOptions
|
||||
): ApplicationExecutorOptions {
|
||||
const { major: angularMajorVersion } = getInstalledAngularVersionInfo();
|
||||
|
||||
/**
|
||||
* We can't set the default values for `security.autoCsp` and
|
||||
* `security.autoCsp.unsafeEval` in the schema because our current schema
|
||||
* parsing would (incorrectly?) default `security` to an object with the
|
||||
* `autoCsp` property set to `false`. This would be problematic because the
|
||||
* option is not supported in Angular versions < 19. So, we don't set those
|
||||
* defaults in the schema and we normalize them here correctly.
|
||||
*/
|
||||
let security: ApplicationExecutorOptions['security'] = options.security;
|
||||
if (angularMajorVersion >= 19) {
|
||||
if (typeof security === 'object') {
|
||||
if (security.autoCsp === undefined) {
|
||||
security.autoCsp = false;
|
||||
} else if (
|
||||
typeof security.autoCsp === 'object' &&
|
||||
security.autoCsp.unsafeEval === undefined
|
||||
) {
|
||||
security.autoCsp.unsafeEval = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...options,
|
||||
appShell: angularMajorVersion < 19 ? options.appShell ?? false : undefined,
|
||||
prerender:
|
||||
angularMajorVersion < 19 ? options.prerender ?? false : undefined,
|
||||
security,
|
||||
};
|
||||
}
|
||||
@ -3,14 +3,7 @@ import { getInstalledAngularVersionInfo } from '../../utilities/angular-version-
|
||||
import type { ApplicationExecutorOptions } from '../schema';
|
||||
|
||||
export function validateOptions(options: ApplicationExecutorOptions): void {
|
||||
const { major: angularMajorVersion, version: angularVersion } =
|
||||
getInstalledAngularVersionInfo();
|
||||
|
||||
if (angularMajorVersion < 17) {
|
||||
throw new Error(
|
||||
`The "application" executor requires Angular version 17 or greater. You are currently using version ${angularVersion}.`
|
||||
);
|
||||
}
|
||||
const { version: angularVersion } = getInstalledAngularVersionInfo();
|
||||
|
||||
if (lt(angularVersion, '17.1.0')) {
|
||||
if (options.loader) {
|
||||
@ -78,4 +71,36 @@ export function validateOptions(options: ApplicationExecutorOptions): void {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (lt(angularVersion, '19.0.0')) {
|
||||
if (options.outputMode) {
|
||||
throw new Error(
|
||||
`The "outputMode" option requires Angular version 19.0.0 or greater. You are currently using version ${angularVersion}.`
|
||||
);
|
||||
}
|
||||
|
||||
if (options.stylePreprocessorOptions?.sass) {
|
||||
throw new Error(
|
||||
`The "stylePreprocessorOptions.sass" option requires Angular version 19.0.0 or greater. You are currently using version ${angularVersion}.`
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof options.ssr === 'object' && options.ssr?.experimentalPlatform) {
|
||||
throw new Error(
|
||||
`The "ssr.experimentalPlatform" option requires Angular version 19.0.0 or greater. You are currently using version ${angularVersion}.`
|
||||
);
|
||||
}
|
||||
|
||||
if (options.security !== undefined) {
|
||||
throw new Error(
|
||||
`The "security" option requires Angular version 19.0.0 or greater. You are currently using version ${angularVersion}.`
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof options.server === 'boolean' && options.server === false) {
|
||||
throw new Error(
|
||||
`The "false" value for the "server" option requires Angular version 19.0.0 or greater. You are currently using version ${angularVersion}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import type { buildEsbuildBrowser as buildEsbuildBrowserFn } from '@angular-devkit/build-angular/src/builders/browser-esbuild';
|
||||
import { stripIndents, type ExecutorContext } from '@nx/devkit';
|
||||
import type { ExecutorContext } from '@nx/devkit';
|
||||
import type { DependentBuildableProjectNode } from '@nx/js/src/utils/buildable-libs-utils';
|
||||
import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter';
|
||||
import { getInstalledAngularVersionInfo } from '../utilities/angular-version-utils';
|
||||
import { createTmpTsConfigForBuildableLibs } from '../utilities/buildable-libs';
|
||||
import { loadPlugins } from '../utilities/esbuild-extensions';
|
||||
import type { EsBuildSchema } from './schema';
|
||||
@ -11,15 +10,6 @@ export default async function* esbuildExecutor(
|
||||
options: EsBuildSchema,
|
||||
context: ExecutorContext
|
||||
): ReturnType<typeof buildEsbuildBrowserFn> {
|
||||
if (options.plugins) {
|
||||
const { major: angularMajorVersion, version: angularVersion } =
|
||||
getInstalledAngularVersionInfo();
|
||||
if (angularMajorVersion < 17) {
|
||||
throw new Error(stripIndents`The "plugins" option is only supported in Angular >= 17.0.0. You are currently using "${angularVersion}".
|
||||
You can resolve this error by removing the "plugins" option or by migrating to Angular 17.0.0.`);
|
||||
}
|
||||
}
|
||||
|
||||
options.buildLibsFromSource ??= true;
|
||||
|
||||
const {
|
||||
|
||||
@ -441,7 +441,7 @@
|
||||
"default": true
|
||||
},
|
||||
"plugins": {
|
||||
"description": "A list of ESBuild plugins. _Note: this is only supported in Angular versions >= 17.0.0_.",
|
||||
"description": "A list of ESBuild plugins.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
|
||||
@ -56,7 +56,9 @@ export default async function* extractI18nExecutor(
|
||||
function getDelegateBuilderOptions(
|
||||
options: ExtractI18nExecutorOptions
|
||||
): ExtractI18nBuilderOptions {
|
||||
const delegateBuilderOptions: ExtractI18nBuilderOptions = { ...options };
|
||||
const delegateBuilderOptions: ExtractI18nBuilderOptions & {
|
||||
browserTarget?: string;
|
||||
} = { ...options };
|
||||
|
||||
const { major: angularMajorVersion } = getInstalledAngularVersionInfo();
|
||||
if (angularMajorVersion <= 17) {
|
||||
|
||||
@ -1,31 +1,31 @@
|
||||
import { executeSSRDevServerBuilder } from '@angular-devkit/build-angular';
|
||||
import { type ExecutorContext, logger } from '@nx/devkit';
|
||||
import { existsSync } from 'fs';
|
||||
import { readProjectsConfigurationFromProjectGraph } from 'nx/src/project-graph/project-graph';
|
||||
import { extname, join } from 'path';
|
||||
import {
|
||||
getDynamicMfManifestFile,
|
||||
validateDevRemotes,
|
||||
} from '../../builders/utilities/module-federation';
|
||||
import type { Schema } from './schema';
|
||||
import {
|
||||
getModuleFederationConfig,
|
||||
getRemotes,
|
||||
parseStaticSsrRemotesConfig,
|
||||
startSsrRemoteProxies,
|
||||
} from '@nx/module-federation/src/utils';
|
||||
import { buildStaticRemotes } from './lib/build-static-remotes';
|
||||
import { startRemotes } from './lib/start-dev-remotes';
|
||||
import { startStaticRemotes } from './lib/start-static-remotes';
|
||||
import {
|
||||
combineAsyncIterables,
|
||||
createAsyncIterable,
|
||||
mapAsyncIterable,
|
||||
} from '@nx/devkit/src/utils/async-iterable';
|
||||
import { eachValueFrom } from '@nx/devkit/src/utils/rxjs-for-await';
|
||||
import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter';
|
||||
import { normalizeOptions } from './lib/normalize-options';
|
||||
import {
|
||||
getModuleFederationConfig,
|
||||
getRemotes,
|
||||
parseStaticSsrRemotesConfig,
|
||||
startSsrRemoteProxies,
|
||||
} from '@nx/module-federation/src/utils';
|
||||
import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
|
||||
import { getInstalledAngularVersionInfo } from '../utilities/angular-version-utils';
|
||||
import { existsSync } from 'fs';
|
||||
import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter';
|
||||
import { readProjectsConfigurationFromProjectGraph } from 'nx/src/project-graph/project-graph';
|
||||
import { extname, join } from 'path';
|
||||
import {
|
||||
getDynamicMfManifestFile,
|
||||
validateDevRemotes,
|
||||
} from '../../builders/utilities/module-federation';
|
||||
import { buildStaticRemotes } from './lib/build-static-remotes';
|
||||
import { normalizeOptions } from './lib/normalize-options';
|
||||
import { startRemotes } from './lib/start-dev-remotes';
|
||||
import { startStaticRemotes } from './lib/start-static-remotes';
|
||||
import type { Schema } from './schema';
|
||||
|
||||
export async function* moduleFederationSsrDevServerExecutor(
|
||||
schema: Schema,
|
||||
@ -34,12 +34,6 @@ export async function* moduleFederationSsrDevServerExecutor(
|
||||
const nxBin = require.resolve('nx/bin/nx');
|
||||
const options = normalizeOptions(schema);
|
||||
|
||||
const { major: angularMajorVersion } = getInstalledAngularVersionInfo();
|
||||
const { executeSSRDevServerBuilder } =
|
||||
angularMajorVersion >= 17
|
||||
? require('@angular-devkit/build-angular')
|
||||
: require('@nguniversal/builders');
|
||||
|
||||
const currIter = eachValueFrom(
|
||||
executeSSRDevServerBuilder(
|
||||
options,
|
||||
|
||||
@ -1,23 +1,11 @@
|
||||
import { type DevRemoteDefinition } from '../../builders/utilities/module-federation';
|
||||
import type { SSRDevServerBuilderOptions } from '@angular-devkit/build-angular';
|
||||
|
||||
export interface Schema {
|
||||
browserTarget: string;
|
||||
serverTarget: string;
|
||||
host?: string;
|
||||
port?: number;
|
||||
progress: boolean;
|
||||
open?: boolean;
|
||||
publicHost?: string;
|
||||
ssl?: boolean;
|
||||
sslKey?: string;
|
||||
sslCert?: string;
|
||||
proxyConfig?: string;
|
||||
export interface Schema extends SSRDevServerBuilderOptions {
|
||||
devRemotes?: DevRemoteDefinition[];
|
||||
skipRemotes?: string[];
|
||||
verbose: boolean;
|
||||
pathToManifestFile?: string;
|
||||
parallel?: number;
|
||||
staticRemotesPort?: number;
|
||||
parallel?: number;
|
||||
isInitialHost?: boolean;
|
||||
}
|
||||
|
||||
@ -1,17 +1,24 @@
|
||||
import { NgPackagr, ngPackagr } from 'ng-packagr';
|
||||
import type { BuildAngularLibraryExecutorOptions } from '../../package/schema';
|
||||
import { getInstalledAngularVersionInfo } from '../../utilities/angular-version-utils';
|
||||
|
||||
export async function getNgPackagrInstance(
|
||||
options: BuildAngularLibraryExecutorOptions
|
||||
): Promise<NgPackagr> {
|
||||
export async function getNgPackagrInstance(): Promise<NgPackagr> {
|
||||
const { major: angularMajorVersion } = getInstalledAngularVersionInfo();
|
||||
if (angularMajorVersion >= 17) {
|
||||
if (angularMajorVersion >= 19) {
|
||||
const { STYLESHEET_PROCESSOR } = await import(
|
||||
'../../utilities/ng-packagr/stylesheet-processor.di.js'
|
||||
);
|
||||
|
||||
const packagr = ngPackagr();
|
||||
packagr.withProviders([STYLESHEET_PROCESSOR]);
|
||||
|
||||
return packagr;
|
||||
}
|
||||
|
||||
const { WRITE_BUNDLES_TRANSFORM } = await import(
|
||||
'./v17+/ng-package/entry-point/write-bundles.di.js'
|
||||
'./pre-v19/ng-package/entry-point/write-bundles.di.js'
|
||||
);
|
||||
const { WRITE_PACKAGE_TRANSFORM } = await import(
|
||||
'./v17+/ng-package/entry-point/write-package.di.js'
|
||||
'./pre-v19/ng-package/entry-point/write-package.di.js'
|
||||
);
|
||||
const { STYLESHEET_PROCESSOR } = await import(
|
||||
'../../utilities/ng-packagr/stylesheet-processor.di.js'
|
||||
@ -26,26 +33,3 @@ export async function getNgPackagrInstance(
|
||||
|
||||
return packagr;
|
||||
}
|
||||
|
||||
const { NX_ENTRY_POINT_PROVIDERS } = await import(
|
||||
'./pre-v17/ng-package/entry-point/entry-point.di.js'
|
||||
);
|
||||
const { nxProvideOptions } = await import(
|
||||
'./pre-v17/ng-package/options.di.js'
|
||||
);
|
||||
const { NX_PACKAGE_PROVIDERS, NX_PACKAGE_TRANSFORM } = await import(
|
||||
'./pre-v17/ng-package/package.di.js'
|
||||
);
|
||||
|
||||
const packagr = new NgPackagr([
|
||||
...NX_PACKAGE_PROVIDERS,
|
||||
...NX_ENTRY_POINT_PROVIDERS,
|
||||
nxProvideOptions({
|
||||
tailwindConfig: options.tailwindConfig,
|
||||
watch: options.watch,
|
||||
}),
|
||||
]);
|
||||
packagr.withBuildTransform(NX_PACKAGE_TRANSFORM.provide);
|
||||
|
||||
return packagr;
|
||||
}
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Adapted from the original ng-packagr source.
|
||||
*
|
||||
* Changes made:
|
||||
* - Use our own compileNgcTransformFactory instead of the one provided by ng-packagr.
|
||||
* - Use NX_STYLESHEET_PROCESSOR instead of STYLESHEET_PROCESSOR.
|
||||
* - Use NX_STYLESHEET_PROCESSOR_TOKEN instead of STYLESHEET_PROCESSOR_TOKEN.
|
||||
* - USE NX_OPTIONS_TOKEN instead of OPTIONS_TOKEN.
|
||||
*/
|
||||
|
||||
import { InjectionToken, Provider } from 'injection-js';
|
||||
import type { Transform } from 'ng-packagr/lib/graph/transform';
|
||||
import { provideTransform } from 'ng-packagr/lib/graph/transform.di';
|
||||
import {
|
||||
NX_STYLESHEET_PROCESSOR,
|
||||
NX_STYLESHEET_PROCESSOR_TOKEN,
|
||||
} from '../../styles/stylesheet-processor.di';
|
||||
import { NX_OPTIONS_TOKEN } from '../options.di';
|
||||
import { nxCompileNgcTransformFactory } from './compile-ngc.transform';
|
||||
|
||||
export const NX_COMPILE_NGC_TOKEN = new InjectionToken<Transform>(
|
||||
`nx.v1.compileNgc`
|
||||
);
|
||||
export const NX_COMPILE_NGC_TRANSFORM = provideTransform({
|
||||
provide: NX_COMPILE_NGC_TOKEN,
|
||||
useFactory: nxCompileNgcTransformFactory,
|
||||
deps: [NX_STYLESHEET_PROCESSOR_TOKEN, NX_OPTIONS_TOKEN],
|
||||
});
|
||||
export const NX_COMPILE_NGC_PROVIDERS: Provider[] = [
|
||||
NX_STYLESHEET_PROCESSOR,
|
||||
NX_COMPILE_NGC_TRANSFORM,
|
||||
];
|
||||
@ -1,87 +0,0 @@
|
||||
/**
|
||||
* Adapted from the original ngPackagr source.
|
||||
*
|
||||
* Changes made:
|
||||
* - Use our own StylesheetProcessor files instead of the ones provide by ng-packagr.
|
||||
* - Excludes the ngcc compilation for faster builds (angular < v16)
|
||||
* - Support ESM2020 for Angular < 16.
|
||||
*/
|
||||
|
||||
import type { Transform } from 'ng-packagr/lib/graph/transform';
|
||||
import { transformFromPromise } from 'ng-packagr/lib/graph/transform';
|
||||
import {
|
||||
EntryPointNode,
|
||||
isEntryPoint,
|
||||
isEntryPointInProgress,
|
||||
isPackage,
|
||||
PackageNode,
|
||||
} from 'ng-packagr/lib/ng-package/nodes';
|
||||
import { setDependenciesTsConfigPaths } from 'ng-packagr/lib/ts/tsconfig';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
import { getInstalledAngularVersionInfo } from '../../../../../utilities/angular-version-utils';
|
||||
import { compileSourceFiles } from '../../ngc/compile-source-files';
|
||||
import { StylesheetProcessor as StylesheetProcessorClass } from '../../styles/stylesheet-processor';
|
||||
import { NgPackagrOptions } from '../options.di';
|
||||
|
||||
export const nxCompileNgcTransformFactory = (
|
||||
StylesheetProcessor: typeof StylesheetProcessorClass,
|
||||
options: NgPackagrOptions
|
||||
): Transform => {
|
||||
return transformFromPromise(async (graph) => {
|
||||
const entryPoints: EntryPointNode[] = graph.filter(isEntryPoint);
|
||||
const entryPoint: EntryPointNode = graph.find(isEntryPointInProgress());
|
||||
const ngPackageNode: PackageNode = graph.find(isPackage);
|
||||
const projectBasePath = ngPackageNode.data.primary.basePath;
|
||||
|
||||
try {
|
||||
// Add paths mappings for dependencies
|
||||
const tsConfig = setDependenciesTsConfigPaths(
|
||||
entryPoint.data.tsConfig,
|
||||
entryPoints
|
||||
);
|
||||
|
||||
const angularVersion = getInstalledAngularVersionInfo();
|
||||
|
||||
// Compile TypeScript sources
|
||||
const { declarations } = entryPoint.data.destinationFiles;
|
||||
const esmModulePath =
|
||||
angularVersion.major < 16
|
||||
? (entryPoint.data.destinationFiles as any).esm2020
|
||||
: entryPoint.data.destinationFiles.esm2022;
|
||||
const { basePath, cssUrl, styleIncludePaths } =
|
||||
entryPoint.data.entryPoint;
|
||||
const { moduleResolutionCache } = entryPoint.cache;
|
||||
|
||||
entryPoint.cache.stylesheetProcessor ??= new StylesheetProcessor(
|
||||
projectBasePath,
|
||||
basePath,
|
||||
cssUrl,
|
||||
styleIncludePaths,
|
||||
options.cacheEnabled && options.cacheDirectory,
|
||||
options.tailwindConfig
|
||||
) as any;
|
||||
|
||||
await compileSourceFiles(
|
||||
graph,
|
||||
tsConfig,
|
||||
moduleResolutionCache,
|
||||
options,
|
||||
{
|
||||
outDir: path.dirname(esmModulePath),
|
||||
declarationDir: path.dirname(declarations),
|
||||
declaration: true,
|
||||
target:
|
||||
angularVersion.major >= 16
|
||||
? ts.ScriptTarget.ES2022
|
||||
: ts.ScriptTarget.ES2020,
|
||||
},
|
||||
entryPoint.cache.stylesheetProcessor as any
|
||||
);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return graph;
|
||||
});
|
||||
};
|
||||
@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Adapted from the original ng-packagr source.
|
||||
*
|
||||
* Changes made:
|
||||
* - Provide our own entryPointTransformFactory function.
|
||||
* - Use NX_COMPILE_NGC_TOKEN instead of COMPILE_NGC_TOKEN.
|
||||
* - Use NX_COMPILE_NGC_PROVIDERS instead of COMPILE_NGC_PROVIDERS.
|
||||
* - Removed usage of WRITE_BUNDLES_TRANSFORM_TOKEN and WRITE_BUNDLES_TRANSFORM.
|
||||
*/
|
||||
|
||||
import type { Provider } from 'injection-js';
|
||||
import { InjectionToken } from 'injection-js';
|
||||
import type { Transform } from 'ng-packagr/lib/graph/transform';
|
||||
import { provideTransform } from 'ng-packagr/lib/graph/transform.di';
|
||||
import {
|
||||
NX_COMPILE_NGC_PROVIDERS,
|
||||
NX_COMPILE_NGC_TOKEN,
|
||||
} from './compile-ngc.di';
|
||||
import { nxEntryPointTransformFactory } from './entry-point.transform';
|
||||
import {
|
||||
NX_WRITE_PACKAGE_TRANSFORM,
|
||||
NX_WRITE_PACKAGE_TRANSFORM_TOKEN,
|
||||
} from './write-package.di';
|
||||
|
||||
export const NX_ENTRY_POINT_TRANSFORM_TOKEN = new InjectionToken<Transform>(
|
||||
`nx.v1.entryPointTransform`
|
||||
);
|
||||
|
||||
export const NX_ENTRY_POINT_TRANSFORM = provideTransform({
|
||||
provide: NX_ENTRY_POINT_TRANSFORM_TOKEN,
|
||||
useFactory: nxEntryPointTransformFactory,
|
||||
deps: [NX_COMPILE_NGC_TOKEN, NX_WRITE_PACKAGE_TRANSFORM_TOKEN],
|
||||
});
|
||||
|
||||
export const NX_ENTRY_POINT_PROVIDERS: Provider[] = [
|
||||
NX_ENTRY_POINT_TRANSFORM,
|
||||
...NX_COMPILE_NGC_PROVIDERS,
|
||||
NX_WRITE_PACKAGE_TRANSFORM,
|
||||
];
|
||||
@ -1,64 +0,0 @@
|
||||
/**
|
||||
* Adapted from the original ng-packagr source.
|
||||
*
|
||||
* Changes made:
|
||||
* - Removed writing bundles as we don't generate them for incremental builds.
|
||||
*/
|
||||
|
||||
import { logger } from '@nx/devkit';
|
||||
import { STATE_DONE } from 'ng-packagr/lib/graph/node';
|
||||
import { isInProgress } from 'ng-packagr/lib/graph/select';
|
||||
import type { Transform } from 'ng-packagr/lib/graph/transform';
|
||||
import { transformFromPromise } from 'ng-packagr/lib/graph/transform';
|
||||
import { byEntryPoint } from 'ng-packagr/lib/ng-package/nodes';
|
||||
import { pipe } from 'rxjs';
|
||||
|
||||
/**
|
||||
* A re-write of the `transformSources()` script that transforms an entry point from sources to distributable format.
|
||||
*
|
||||
* Sources are TypeScript source files accompanied by HTML templates and xCSS stylesheets.
|
||||
* See the Angular Package Format for a detailed description of what the distributables include.
|
||||
*
|
||||
* The current transformation pipeline can be thought of as:
|
||||
*
|
||||
* - clean
|
||||
* - compileTs
|
||||
* - downlevelTs
|
||||
* - relocateSourceMaps
|
||||
* - writePackage
|
||||
* - copyStagedFiles (esm, dts, sourcemaps)
|
||||
* - writePackageJson
|
||||
*
|
||||
* The transformation pipeline is pluggable through the dependency injection system.
|
||||
* Sub-transformations are passed to this factory function as arguments.
|
||||
*
|
||||
* @param compileTs Transformation compiling typescript sources to ES2022 modules.
|
||||
* @param writeBundles Transformation flattening ES2022 modules to ESM2022, UMD, and minified UMD.
|
||||
*/
|
||||
export const nxEntryPointTransformFactory = (
|
||||
compileTs: Transform,
|
||||
writePackage: Transform
|
||||
): Transform =>
|
||||
pipe(
|
||||
transformFromPromise(async (graph) => {
|
||||
// Peek the first entry point from the graph
|
||||
const entryPoint = graph.find(byEntryPoint().and(isInProgress));
|
||||
logger.info(
|
||||
'\n------------------------------------------------------------------------------'
|
||||
);
|
||||
logger.info(
|
||||
`Building entry point '${entryPoint.data.entryPoint.moduleId}'`
|
||||
);
|
||||
logger.info(
|
||||
'------------------------------------------------------------------------------'
|
||||
);
|
||||
}),
|
||||
// TypeScript sources compilation
|
||||
compileTs,
|
||||
// After TypeScript: write package
|
||||
writePackage,
|
||||
transformFromPromise(async (graph) => {
|
||||
const entryPoint = graph.find(byEntryPoint().and(isInProgress));
|
||||
entryPoint.state = STATE_DONE;
|
||||
})
|
||||
);
|
||||
@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Adapted from the original ng-packagr source.
|
||||
*
|
||||
* Changes made:
|
||||
* - Provide our own writePackageTransform function.
|
||||
* - USE NX_OPTIONS_TOKEN instead of OPTIONS_TOKEN.
|
||||
*/
|
||||
|
||||
import { InjectionToken } from 'injection-js';
|
||||
import type { Transform } from 'ng-packagr/lib/graph/transform';
|
||||
import {
|
||||
provideTransform,
|
||||
TransformProvider,
|
||||
} from 'ng-packagr/lib/graph/transform.di';
|
||||
import { NX_OPTIONS_TOKEN } from '../options.di';
|
||||
import { nxWritePackageTransform } from './write-package.transform';
|
||||
|
||||
export const NX_WRITE_PACKAGE_TRANSFORM_TOKEN = new InjectionToken<Transform>(
|
||||
`nx.v1.writePackageTransform`
|
||||
);
|
||||
export const NX_WRITE_PACKAGE_TRANSFORM: TransformProvider = provideTransform({
|
||||
provide: NX_WRITE_PACKAGE_TRANSFORM_TOKEN,
|
||||
useFactory: nxWritePackageTransform,
|
||||
deps: [NX_OPTIONS_TOKEN],
|
||||
});
|
||||
@ -1,489 +0,0 @@
|
||||
/**
|
||||
* Adapted from the original ng-packagr.
|
||||
*
|
||||
* Changes made:
|
||||
* - Change the package.json metadata to only use the ESM2022 output.
|
||||
* - Change the package.json metadata to only use the ESM2020 output (Angular < 16).
|
||||
*/
|
||||
|
||||
import { logger } from '@nx/devkit';
|
||||
import { BuildGraph } from 'ng-packagr/lib/graph/build-graph';
|
||||
import { Node } from 'ng-packagr/lib/graph/node';
|
||||
import { transformFromPromise } from 'ng-packagr/lib/graph/transform';
|
||||
import { NgEntryPoint } from 'ng-packagr/lib/ng-package/entry-point/entry-point';
|
||||
import {
|
||||
EntryPointNode,
|
||||
fileUrl,
|
||||
isEntryPointInProgress,
|
||||
isEntryPoint,
|
||||
isPackage,
|
||||
PackageNode,
|
||||
} from 'ng-packagr/lib/ng-package/nodes';
|
||||
import { NgPackagrOptions } from 'ng-packagr/lib/ng-package/options.di';
|
||||
import { NgPackage } from 'ng-packagr/lib/ng-package/package';
|
||||
import {
|
||||
copyFile,
|
||||
exists,
|
||||
readFile,
|
||||
rmdir,
|
||||
stat,
|
||||
writeFile,
|
||||
} from 'ng-packagr/lib/utils/fs';
|
||||
import { globFiles } from 'ng-packagr/lib/utils/glob';
|
||||
import { ensureUnixPath } from 'ng-packagr/lib/utils/path';
|
||||
import { AssetPattern } from 'ng-packagr/ng-package.schema';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
getInstalledAngularVersionInfo,
|
||||
VersionInfo,
|
||||
} from '../../../../../utilities/angular-version-utils';
|
||||
|
||||
export const nxWritePackageTransform = (options: NgPackagrOptions) =>
|
||||
transformFromPromise(async (graph) => {
|
||||
const entryPoint: EntryPointNode = graph.find(isEntryPointInProgress());
|
||||
const ngEntryPoint: NgEntryPoint = entryPoint.data.entryPoint;
|
||||
const ngPackageNode: PackageNode = graph.find(isPackage);
|
||||
const ngPackage = ngPackageNode.data;
|
||||
const { destinationFiles } = entryPoint.data;
|
||||
|
||||
const angularVersion = getInstalledAngularVersionInfo();
|
||||
|
||||
if (!ngEntryPoint.isSecondaryEntryPoint) {
|
||||
logger.log('Copying assets');
|
||||
|
||||
try {
|
||||
await copyAssets(graph, entryPoint, ngPackageNode, angularVersion);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 6. WRITE PACKAGE.JSON
|
||||
const relativeUnixFromDestPath = (filePath: string) =>
|
||||
ensureUnixPath(path.relative(ngEntryPoint.destinationPath, filePath));
|
||||
|
||||
if (!ngEntryPoint.isSecondaryEntryPoint) {
|
||||
try {
|
||||
logger.info('Writing package manifest');
|
||||
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,
|
||||
ngPackage,
|
||||
{
|
||||
// backward compat for Angular < 16
|
||||
...(angularVersion.major < 16
|
||||
? {
|
||||
module: relativeUnixFromDestPath(
|
||||
(destinationFiles as any).esm2020
|
||||
),
|
||||
es2020: relativeUnixFromDestPath(
|
||||
(destinationFiles as any).esm2020
|
||||
),
|
||||
esm2020: relativeUnixFromDestPath(
|
||||
(destinationFiles as any).esm2020
|
||||
),
|
||||
}
|
||||
: {
|
||||
module: relativeUnixFromDestPath(destinationFiles.esm2022),
|
||||
}),
|
||||
typings: relativeUnixFromDestPath(destinationFiles.declarations),
|
||||
exports: generatePackageExports(
|
||||
ngEntryPoint,
|
||||
graph,
|
||||
angularVersion
|
||||
),
|
||||
// webpack v4+ specific flag to enable advanced optimizations and code splitting
|
||||
sideEffects: ngEntryPoint.packageJson.sideEffects ?? false,
|
||||
},
|
||||
!!options.watch
|
||||
);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
} 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;
|
||||
const packageJsonPath = path.join(
|
||||
primary.destinationPath,
|
||||
'package.json'
|
||||
);
|
||||
|
||||
if (await exists(packageJsonPath)) {
|
||||
const packageJson = JSON.parse(
|
||||
await readFile(packageJsonPath, { encoding: 'utf8' })
|
||||
);
|
||||
packageJson.version = generateWatchVersion();
|
||||
await writeFile(
|
||||
path.join(primary.destinationPath, 'package.json'),
|
||||
JSON.stringify(packageJson, undefined, 2)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 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(
|
||||
angularVersion.major < 16
|
||||
? (destinationFiles as any).esm2020
|
||||
: destinationFiles.esm2022
|
||||
),
|
||||
},
|
||||
undefined,
|
||||
2
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`Built ${ngEntryPoint.moduleId}`);
|
||||
|
||||
return graph;
|
||||
});
|
||||
|
||||
type AssetEntry = Exclude<AssetPattern, string>;
|
||||
|
||||
async function copyAssets(
|
||||
graph: BuildGraph,
|
||||
entryPointNode: EntryPointNode,
|
||||
ngPackageNode: PackageNode,
|
||||
angularVersion: VersionInfo
|
||||
): Promise<void> {
|
||||
const ngPackage = ngPackageNode.data;
|
||||
|
||||
const globsForceIgnored: string[] = [
|
||||
'.gitkeep',
|
||||
'**/.DS_Store',
|
||||
'**/Thumbs.db',
|
||||
`${ngPackage.dest}/**`,
|
||||
];
|
||||
|
||||
const assets: AssetEntry[] = [];
|
||||
|
||||
for (const assetPath of ngPackage.assets) {
|
||||
let asset: AssetEntry;
|
||||
if (typeof assetPath === 'object') {
|
||||
asset = { ...assetPath };
|
||||
} else {
|
||||
const [isDir, isFile] = await stat(path.join(ngPackage.src, assetPath))
|
||||
.then((stats) => [stats.isDirectory(), stats.isFile()])
|
||||
.catch(() => [false, false]);
|
||||
if (isDir) {
|
||||
asset = { glob: '**/*', input: assetPath, output: assetPath };
|
||||
} else if (isFile) {
|
||||
// filenames are their own glob
|
||||
asset = {
|
||||
glob: path.basename(assetPath),
|
||||
input: path.dirname(assetPath),
|
||||
output: path.dirname(assetPath),
|
||||
};
|
||||
} else {
|
||||
asset = { glob: assetPath, input: '/', output: '/' };
|
||||
}
|
||||
}
|
||||
|
||||
asset.input = path.join(ngPackage.src, asset.input);
|
||||
asset.output = path.join(ngPackage.dest, asset.output);
|
||||
|
||||
const isAncestorPath = (target: string, datum: string) =>
|
||||
path.relative(datum, target).startsWith('..');
|
||||
if (isAncestorPath(asset.input, ngPackage.src)) {
|
||||
throw new Error(
|
||||
'Cannot read assets from a location outside of the project root.'
|
||||
);
|
||||
}
|
||||
if (isAncestorPath(asset.output, ngPackage.dest)) {
|
||||
throw new Error(
|
||||
'Cannot write assets to a location outside of the output path.'
|
||||
);
|
||||
}
|
||||
|
||||
assets.push(asset);
|
||||
}
|
||||
|
||||
for (const asset of assets) {
|
||||
const globOptions: Parameters<typeof globFiles>[1] = {
|
||||
cwd: asset.input,
|
||||
ignore: [...(asset.ignore ?? []), ...globsForceIgnored],
|
||||
dot: true,
|
||||
};
|
||||
|
||||
if (angularVersion.major < 16) {
|
||||
// versions lower than v16 support these properties
|
||||
(globOptions as any).cache = (ngPackageNode.cache as any).globCache;
|
||||
(globOptions as any).nodir = true;
|
||||
(globOptions as any).follow = asset.followSymlinks;
|
||||
} else {
|
||||
// starting in v16 these properties are supported
|
||||
globOptions.onlyFiles = true;
|
||||
globOptions.followSymbolicLinks = asset.followSymlinks;
|
||||
}
|
||||
|
||||
const filePaths = await globFiles(asset.glob, globOptions);
|
||||
for (const filePath of filePaths) {
|
||||
const fileSrcFullPath = path.join(asset.input, filePath);
|
||||
const fileDestFullPath = path.join(asset.output, filePath);
|
||||
const nodeUri = fileUrl(ensureUnixPath(fileSrcFullPath));
|
||||
let node = graph.get(nodeUri);
|
||||
if (!node) {
|
||||
node = new Node(nodeUri);
|
||||
graph.put(node);
|
||||
}
|
||||
entryPointNode.dependsOn(node);
|
||||
await copyFile(fileSrcFullPath, fileDestFullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and writes a `package.json` file of the entry point used by the `node_module`
|
||||
* resolution strategies.
|
||||
*
|
||||
* #### Example
|
||||
*
|
||||
* A consumer of the entry point depends on it by `import {..} from '@my/module/id';`.
|
||||
* The module id `@my/module/id` will be resolved to the `package.json` file that is written by
|
||||
* this build step.
|
||||
* The properties `main`, `module`, `typings` (and so on) in the `package.json` point to the
|
||||
* flattened JavaScript bundles, type definitions, (...).
|
||||
*
|
||||
* @param entryPoint An entry point of an Angular package / library
|
||||
* @param additionalProperties Additional properties, e.g. binary artefacts (bundle files), to merge into `package.json`
|
||||
*/
|
||||
async function writePackageJson(
|
||||
entryPoint: NgEntryPoint,
|
||||
pkg: NgPackage,
|
||||
additionalProperties: {
|
||||
[key: string]: string | boolean | string[] | ConditionalExport;
|
||||
},
|
||||
isWatchMode: boolean
|
||||
): Promise<void> {
|
||||
// set additional properties
|
||||
const packageJson = { ...entryPoint.packageJson, ...additionalProperties };
|
||||
|
||||
// read tslib version from `@angular/compiler` so that our tslib
|
||||
// version at least matches that of angular if we use require('tslib').version
|
||||
// it will get what installed and not the minimum version nor if it is a `~` or `^`
|
||||
// this is only required for primary
|
||||
if (isWatchMode) {
|
||||
// Needed because of Webpack's 5 `cachemanagedpaths`
|
||||
// https://github.com/angular/angular-cli/issues/20962
|
||||
packageJson.version = generateWatchVersion();
|
||||
}
|
||||
|
||||
if (
|
||||
!packageJson.peerDependencies?.tslib &&
|
||||
!packageJson.dependencies?.tslib
|
||||
) {
|
||||
const {
|
||||
peerDependencies: angularPeerDependencies = {},
|
||||
dependencies: angularDependencies = {},
|
||||
} = require('@angular/compiler/package.json');
|
||||
const tsLibVersion =
|
||||
angularPeerDependencies.tslib || angularDependencies.tslib;
|
||||
|
||||
if (tsLibVersion) {
|
||||
packageJson.dependencies = {
|
||||
...packageJson.dependencies,
|
||||
tslib: tsLibVersion,
|
||||
};
|
||||
}
|
||||
} else if (packageJson.peerDependencies?.tslib) {
|
||||
logger.warn(
|
||||
`'tslib' is no longer recommended to be used as a 'peerDependencies'. Moving it to 'dependencies'.`
|
||||
);
|
||||
packageJson.dependencies = {
|
||||
...(packageJson.dependencies || {}),
|
||||
tslib: packageJson.peerDependencies.tslib,
|
||||
};
|
||||
|
||||
delete packageJson.peerDependencies.tslib;
|
||||
}
|
||||
|
||||
// Verify non-peerDependencies as they can easily lead to duplicate installs or version conflicts
|
||||
// in the node_modules folder of an application
|
||||
const allowedList = pkg.allowedNonPeerDependencies.map(
|
||||
(value) => new RegExp(value)
|
||||
);
|
||||
try {
|
||||
checkNonPeerDependencies(packageJson, 'dependencies', allowedList);
|
||||
} catch (e) {
|
||||
await rmdir(entryPoint.destinationPath, { recursive: true });
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Removes scripts from package.json after build
|
||||
if (packageJson.scripts) {
|
||||
if (pkg.keepLifecycleScripts !== true) {
|
||||
logger.info(
|
||||
`Removing scripts section in package.json as it's considered a potential security vulnerability.`
|
||||
);
|
||||
delete packageJson.scripts;
|
||||
} else {
|
||||
logger.warn(
|
||||
`You enabled keepLifecycleScripts explicitly. The scripts section in package.json will be published to npm.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// keep the dist package.json clean
|
||||
// this will not throw if ngPackage field does not exist
|
||||
delete packageJson.ngPackage;
|
||||
|
||||
const packageJsonPropertiesToDelete = [
|
||||
'stylelint',
|
||||
'prettier',
|
||||
'browserslist',
|
||||
'devDependencies',
|
||||
'jest',
|
||||
'workspaces',
|
||||
'husky',
|
||||
];
|
||||
|
||||
for (const prop of packageJsonPropertiesToDelete) {
|
||||
if (prop in packageJson) {
|
||||
delete packageJson[prop];
|
||||
logger.info(`Removing ${prop} section in package.json.`);
|
||||
}
|
||||
}
|
||||
|
||||
packageJson.name = entryPoint.moduleId;
|
||||
await writeFile(
|
||||
path.join(entryPoint.destinationPath, 'package.json'),
|
||||
JSON.stringify(packageJson, undefined, 2)
|
||||
);
|
||||
}
|
||||
|
||||
function checkNonPeerDependencies(
|
||||
packageJson: Record<string, unknown>,
|
||||
property: string,
|
||||
allowed: RegExp[]
|
||||
) {
|
||||
if (!packageJson[property]) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const dep of Object.keys(packageJson[property])) {
|
||||
if (!allowed.some((regex) => regex.test(dep))) {
|
||||
logger.warn(
|
||||
`Distributing npm packages with '${property}' is not recommended. Please consider adding ${dep} to 'peerDependencies' or remove it from '${property}'.`
|
||||
);
|
||||
throw new Error(
|
||||
`Dependency ${dep} must be explicitly allowed using the "allowedNonPeerDependencies" option.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type PackageExports = Record<string, ConditionalExport>;
|
||||
|
||||
/**
|
||||
* Type describing the conditional exports descriptor for an entry-point.
|
||||
* https://nodejs.org/api/packages.html#packages_conditional_exports
|
||||
*/
|
||||
type ConditionalExport = {
|
||||
types?: string;
|
||||
esm2022?: string;
|
||||
esm?: string;
|
||||
default?: string;
|
||||
|
||||
// backward compat for Angular < 16
|
||||
node?: string;
|
||||
esm2020?: string;
|
||||
es2020?: string;
|
||||
es2015?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates the `package.json` package exports following APF v13.
|
||||
* This is supposed to match with: https://github.com/angular/angular/blob/e0667efa6eada64d1fb8b143840689090fc82e52/packages/bazel/src/ng_package/packager.ts#L415.
|
||||
*/
|
||||
function generatePackageExports(
|
||||
{ destinationPath, packageJson }: NgEntryPoint,
|
||||
graph: BuildGraph,
|
||||
angularVersion: VersionInfo
|
||||
): PackageExports {
|
||||
const exports: PackageExports = packageJson.exports
|
||||
? JSON.parse(JSON.stringify(packageJson.exports))
|
||||
: {};
|
||||
|
||||
const insertMappingOrError = (
|
||||
subpath: string,
|
||||
mapping: ConditionalExport
|
||||
) => {
|
||||
exports[subpath] ??= {};
|
||||
const subpathExport = exports[subpath];
|
||||
|
||||
// Go through all conditions that should be inserted. If the condition is already
|
||||
// manually set of the subpath export, we throw an error. In general, we allow for
|
||||
// additional conditions to be set. These will always precede the generated ones.
|
||||
for (const conditionName of Object.keys(mapping)) {
|
||||
if (subpathExport[conditionName] !== undefined) {
|
||||
logger.warn(
|
||||
`Found a conflicting export condition for "${subpath}". The "${conditionName}" ` +
|
||||
`condition would be overridden by ng-packagr. Please unset it.`
|
||||
);
|
||||
}
|
||||
|
||||
// **Note**: The order of the conditions is preserved even though we are setting
|
||||
// the conditions once at a time (the latest assignment will be at the end).
|
||||
subpathExport[conditionName] = mapping[conditionName];
|
||||
}
|
||||
};
|
||||
|
||||
const relativeUnixFromDestPath = (filePath: string) =>
|
||||
'./' + ensureUnixPath(path.relative(destinationPath, filePath));
|
||||
|
||||
insertMappingOrError('./package.json', { default: './package.json' });
|
||||
|
||||
const entryPoints = graph.filter(isEntryPoint);
|
||||
for (const entryPoint of entryPoints) {
|
||||
const { destinationFiles, isSecondaryEntryPoint } =
|
||||
entryPoint.data.entryPoint;
|
||||
const subpath = isSecondaryEntryPoint
|
||||
? `./${destinationFiles.directory}`
|
||||
: '.';
|
||||
|
||||
// backward compat for Angular < 16
|
||||
const mapping =
|
||||
angularVersion.major < 16
|
||||
? {
|
||||
types: relativeUnixFromDestPath(destinationFiles.declarations),
|
||||
es2020: relativeUnixFromDestPath((destinationFiles as any).esm2020),
|
||||
esm2020: relativeUnixFromDestPath(
|
||||
(destinationFiles as any).esm2020
|
||||
),
|
||||
default: relativeUnixFromDestPath(
|
||||
(destinationFiles as any).esm2020
|
||||
),
|
||||
}
|
||||
: {
|
||||
esm2022: relativeUnixFromDestPath(destinationFiles.esm2022),
|
||||
esm: relativeUnixFromDestPath(destinationFiles.esm2022),
|
||||
default: relativeUnixFromDestPath(destinationFiles.esm2022),
|
||||
};
|
||||
|
||||
insertMappingOrError(subpath, mapping);
|
||||
}
|
||||
|
||||
return exports;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new version for the package `package.json` when runing in watch mode.
|
||||
*/
|
||||
function generateWatchVersion() {
|
||||
return `0.0.0-watch+${Date.now()}`;
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
/**
|
||||
* Adapted from the original ng-packagr.
|
||||
*
|
||||
* Changes made:
|
||||
* - Use our own options interface to add support for tailwindConfig.
|
||||
*/
|
||||
|
||||
import findCacheDirectory from 'find-cache-dir';
|
||||
import { InjectionToken, Provider, ValueProvider } from 'injection-js';
|
||||
import { NgPackagrOptions as NgPackagrOptionsBase } from 'ng-packagr/lib/ng-package/options.di';
|
||||
import { tmpdir } from 'os';
|
||||
import { resolve } from 'path';
|
||||
|
||||
export interface NgPackagrOptions extends NgPackagrOptionsBase {
|
||||
tailwindConfig?: string;
|
||||
}
|
||||
|
||||
export const NX_OPTIONS_TOKEN = new InjectionToken<NgPackagrOptions>(
|
||||
`nx.v1.options`
|
||||
);
|
||||
|
||||
export const nxProvideOptions = (
|
||||
options: NgPackagrOptions = {}
|
||||
): ValueProvider => ({
|
||||
provide: NX_OPTIONS_TOKEN,
|
||||
useValue: normalizeOptions(options),
|
||||
});
|
||||
|
||||
export const NX_DEFAULT_OPTIONS_PROVIDER: Provider = nxProvideOptions();
|
||||
|
||||
function normalizeOptions(options: NgPackagrOptions = {}) {
|
||||
const ciEnv = process.env['CI'];
|
||||
const isCI = ciEnv?.toLowerCase() === 'true' || ciEnv === '1';
|
||||
const { cacheEnabled = !isCI, cacheDirectory = findCachePath() } = options;
|
||||
|
||||
return {
|
||||
...options,
|
||||
cacheEnabled,
|
||||
cacheDirectory,
|
||||
};
|
||||
}
|
||||
|
||||
function findCachePath(): string {
|
||||
const name = 'ng-packagr';
|
||||
|
||||
return findCacheDirectory({ name }) || resolve(tmpdir(), name);
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
/**
|
||||
* Adapted from the original ng-packagr.
|
||||
*
|
||||
* Changes made:
|
||||
* - Use NX_ENTRY_POINT_TRANSFORM_TOKEN instead of ENTRY_POINT_TRANSFORM_TOKEN.
|
||||
* - USE NX_OPTIONS_TOKEN instead of OPTIONS_TOKEN.
|
||||
* - USE NX_DEFAULT_OPTIONS_PROVIDER instead of DEFAULT_OPTIONS_PROVIDER.
|
||||
*/
|
||||
|
||||
import type { Provider } from 'injection-js';
|
||||
import { InjectionToken } from 'injection-js';
|
||||
import type { Transform } from 'ng-packagr/lib/graph/transform';
|
||||
import { provideTransform } from 'ng-packagr/lib/graph/transform.di';
|
||||
import {
|
||||
ANALYSE_SOURCES_TOKEN,
|
||||
ANALYSE_SOURCES_TRANSFORM,
|
||||
} from 'ng-packagr/lib/ng-package/entry-point/analyse-sources.di';
|
||||
import {
|
||||
INIT_TS_CONFIG_TOKEN,
|
||||
INIT_TS_CONFIG_TRANSFORM,
|
||||
} from 'ng-packagr/lib/ng-package/entry-point/init-tsconfig.di';
|
||||
import { packageTransformFactory } from 'ng-packagr/lib/ng-package/package.transform';
|
||||
import { PROJECT_TOKEN } from 'ng-packagr/lib/project.di';
|
||||
import { NX_ENTRY_POINT_TRANSFORM_TOKEN } from './entry-point/entry-point.di';
|
||||
import { NX_DEFAULT_OPTIONS_PROVIDER, NX_OPTIONS_TOKEN } from './options.di';
|
||||
|
||||
export const NX_PACKAGE_TRANSFORM_TOKEN = new InjectionToken<Transform>(
|
||||
`nx.v1.packageTransform`
|
||||
);
|
||||
|
||||
export const NX_PACKAGE_TRANSFORM = provideTransform({
|
||||
provide: NX_PACKAGE_TRANSFORM_TOKEN,
|
||||
useFactory: packageTransformFactory,
|
||||
deps: [
|
||||
PROJECT_TOKEN,
|
||||
NX_OPTIONS_TOKEN,
|
||||
INIT_TS_CONFIG_TOKEN,
|
||||
ANALYSE_SOURCES_TOKEN,
|
||||
NX_ENTRY_POINT_TRANSFORM_TOKEN,
|
||||
],
|
||||
});
|
||||
|
||||
export const NX_PACKAGE_PROVIDERS: Provider[] = [
|
||||
NX_PACKAGE_TRANSFORM,
|
||||
NX_DEFAULT_OPTIONS_PROVIDER,
|
||||
INIT_TS_CONFIG_TRANSFORM,
|
||||
ANALYSE_SOURCES_TRANSFORM,
|
||||
];
|
||||
@ -1,272 +0,0 @@
|
||||
/**
|
||||
* Adapted from the original ng-packagr source.
|
||||
*
|
||||
* Changes made:
|
||||
* - Use custom StylesheetProcessor instead of the one provided by ng-packagr.
|
||||
* - Support Angular Compiler `incrementalDriver` for Angular < 16.
|
||||
*/
|
||||
|
||||
import { BuildGraph } from 'ng-packagr/lib/graph/build-graph';
|
||||
import {
|
||||
EntryPointNode,
|
||||
PackageNode,
|
||||
isEntryPointInProgress,
|
||||
isPackage,
|
||||
} from 'ng-packagr/lib/ng-package/nodes';
|
||||
import {
|
||||
augmentProgramWithVersioning,
|
||||
cacheCompilerHost,
|
||||
} from 'ng-packagr/lib/ts/cache-compiler-host';
|
||||
import * as log from 'ng-packagr/lib/utils/log';
|
||||
import { join } from 'node:path';
|
||||
import * as ts from 'typescript';
|
||||
import { getInstalledAngularVersionInfo } from '../../../../utilities/angular-version-utils';
|
||||
import { loadEsmModule } from '../../../../utilities/module-loader';
|
||||
import { NgPackagrOptions } from '../ng-package/options.di';
|
||||
import { StylesheetProcessor } from '../styles/stylesheet-processor';
|
||||
|
||||
export async function compileSourceFiles(
|
||||
graph: BuildGraph,
|
||||
tsConfig: any,
|
||||
moduleResolutionCache: ts.ModuleResolutionCache,
|
||||
options: NgPackagrOptions,
|
||||
extraOptions?: Partial<ts.CompilerOptions>,
|
||||
stylesheetProcessor?: StylesheetProcessor
|
||||
) {
|
||||
const { NgtscProgram, formatDiagnostics } = await loadEsmModule(
|
||||
'@angular/compiler-cli'
|
||||
);
|
||||
const { cacheDirectory, watch, cacheEnabled } = options;
|
||||
const tsConfigOptions: ts.CompilerOptions = {
|
||||
...tsConfig.options,
|
||||
...extraOptions,
|
||||
};
|
||||
const entryPoint: EntryPointNode = graph.find(isEntryPointInProgress());
|
||||
const ngPackageNode: PackageNode = graph.find(isPackage);
|
||||
const inlineStyleLanguage = ngPackageNode.data.inlineStyleLanguage;
|
||||
|
||||
const cacheDir = cacheEnabled && cacheDirectory;
|
||||
if (cacheDir) {
|
||||
tsConfigOptions.incremental ??= true;
|
||||
tsConfigOptions.tsBuildInfoFile ??= join(
|
||||
cacheDir,
|
||||
`tsbuildinfo/${entryPoint.data.entryPoint.flatModuleFile}.tsbuildinfo`
|
||||
);
|
||||
}
|
||||
|
||||
const tsCompilerHost = cacheCompilerHost(
|
||||
graph,
|
||||
entryPoint,
|
||||
tsConfigOptions,
|
||||
moduleResolutionCache,
|
||||
stylesheetProcessor as any,
|
||||
inlineStyleLanguage
|
||||
);
|
||||
|
||||
const cache = entryPoint.cache;
|
||||
const sourceFileCache = cache.sourcesFileCache;
|
||||
let usingBuildInfo = false;
|
||||
|
||||
let oldBuilder = cache.oldBuilder;
|
||||
if (!oldBuilder && cacheDir) {
|
||||
oldBuilder = ts.readBuilderProgram(tsConfigOptions, tsCompilerHost);
|
||||
usingBuildInfo = true;
|
||||
}
|
||||
|
||||
// Create the Angular specific program that contains the Angular compiler
|
||||
const angularProgram = new NgtscProgram(
|
||||
tsConfig.rootNames,
|
||||
tsConfigOptions,
|
||||
tsCompilerHost,
|
||||
cache.oldNgtscProgram
|
||||
);
|
||||
|
||||
const angularCompiler = angularProgram.compiler;
|
||||
const { ignoreForDiagnostics, ignoreForEmit } = angularCompiler;
|
||||
|
||||
// SourceFile versions are required for builder programs.
|
||||
// The wrapped host inside NgtscProgram adds additional files that will not have versions.
|
||||
const typeScriptProgram = angularProgram.getTsProgram();
|
||||
augmentProgramWithVersioning(typeScriptProgram);
|
||||
|
||||
let builder: ts.BuilderProgram | ts.EmitAndSemanticDiagnosticsBuilderProgram;
|
||||
if (watch || cacheDir) {
|
||||
builder = cache.oldBuilder =
|
||||
ts.createEmitAndSemanticDiagnosticsBuilderProgram(
|
||||
typeScriptProgram,
|
||||
tsCompilerHost,
|
||||
oldBuilder
|
||||
);
|
||||
cache.oldNgtscProgram = angularProgram;
|
||||
} else {
|
||||
builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(
|
||||
typeScriptProgram,
|
||||
tsCompilerHost
|
||||
);
|
||||
}
|
||||
|
||||
// Update semantic diagnostics cache
|
||||
const affectedFiles = new Set<ts.SourceFile>();
|
||||
|
||||
// Analyze affected files when in watch mode for incremental type checking
|
||||
if ('getSemanticDiagnosticsOfNextAffectedFile' in builder) {
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const result = builder.getSemanticDiagnosticsOfNextAffectedFile(
|
||||
undefined,
|
||||
(sourceFile) => {
|
||||
// If the affected file is a TTC shim, add the shim's original source file.
|
||||
// This ensures that changes that affect TTC are typechecked even when the changes
|
||||
// are otherwise unrelated from a TS perspective and do not result in Ivy codegen changes.
|
||||
// For example, changing @Input property types of a directive used in another component's
|
||||
// template.
|
||||
if (
|
||||
ignoreForDiagnostics.has(sourceFile) &&
|
||||
sourceFile.fileName.endsWith('.ngtypecheck.ts')
|
||||
) {
|
||||
// This file name conversion relies on internal compiler logic and should be converted
|
||||
// to an official method when available. 15 is length of `.ngtypecheck.ts`
|
||||
const originalFilename = sourceFile.fileName.slice(0, -15) + '.ts';
|
||||
const originalSourceFile = builder.getSourceFile(originalFilename);
|
||||
if (originalSourceFile) {
|
||||
affectedFiles.add(originalSourceFile);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
break;
|
||||
}
|
||||
|
||||
affectedFiles.add(result.affected as ts.SourceFile);
|
||||
}
|
||||
|
||||
// Add all files with associated template type checking files.
|
||||
// Stored TS build info does not have knowledge of the AOT compiler or the typechecking state of the templates.
|
||||
// To ensure that errors are reported correctly, all AOT component diagnostics need to be analyzed even if build
|
||||
// info is present.
|
||||
if (usingBuildInfo) {
|
||||
for (const sourceFile of builder.getSourceFiles()) {
|
||||
if (
|
||||
ignoreForDiagnostics.has(sourceFile) &&
|
||||
sourceFile.fileName.endsWith('.ngtypecheck.ts')
|
||||
) {
|
||||
// This file name conversion relies on internal compiler logic and should be converted
|
||||
// to an official method when available. 15 is length of `.ngtypecheck.ts`
|
||||
const originalFilename = sourceFile.fileName.slice(0, -15) + '.ts';
|
||||
const originalSourceFile = builder.getSourceFile(originalFilename);
|
||||
if (originalSourceFile) {
|
||||
affectedFiles.add(originalSourceFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect program level diagnostics
|
||||
const allDiagnostics: ts.Diagnostic[] = [
|
||||
...angularCompiler.getOptionDiagnostics(),
|
||||
...builder.getOptionsDiagnostics(),
|
||||
...builder.getGlobalDiagnostics(),
|
||||
];
|
||||
|
||||
// Required to support asynchronous resource loading
|
||||
// Must be done before creating transformers or getting template diagnostics
|
||||
await angularCompiler.analyzeAsync();
|
||||
|
||||
// Collect source file specific diagnostics
|
||||
for (const sourceFile of builder.getSourceFiles()) {
|
||||
if (ignoreForDiagnostics.has(sourceFile)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
allDiagnostics.push(
|
||||
...builder.getDeclarationDiagnostics(sourceFile),
|
||||
...builder.getSyntacticDiagnostics(sourceFile),
|
||||
...builder.getSemanticDiagnostics(sourceFile)
|
||||
);
|
||||
|
||||
// Declaration files cannot have template diagnostics
|
||||
if (sourceFile.isDeclarationFile) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only request Angular template diagnostics for affected files to avoid
|
||||
// overhead of template diagnostics for unchanged files.
|
||||
if (affectedFiles.has(sourceFile)) {
|
||||
const angularDiagnostics = angularCompiler.getDiagnosticsForFile(
|
||||
sourceFile,
|
||||
affectedFiles.size === 1
|
||||
? /** OptimizeFor.SingleFile **/ 0
|
||||
: /** OptimizeFor.WholeProgram */ 1
|
||||
);
|
||||
|
||||
allDiagnostics.push(...angularDiagnostics);
|
||||
sourceFileCache.updateAngularDiagnostics(sourceFile, angularDiagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
const otherDiagnostics = [];
|
||||
const errorDiagnostics = [];
|
||||
for (const diagnostic of allDiagnostics) {
|
||||
if (diagnostic.category === ts.DiagnosticCategory.Error) {
|
||||
errorDiagnostics.push(diagnostic);
|
||||
} else {
|
||||
otherDiagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
if (otherDiagnostics.length) {
|
||||
log.msg(formatDiagnostics(errorDiagnostics));
|
||||
}
|
||||
|
||||
if (errorDiagnostics.length) {
|
||||
throw new Error(formatDiagnostics(errorDiagnostics));
|
||||
}
|
||||
|
||||
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
|
||||
? (angularCompiler as any).incrementalDriver
|
||||
: angularCompiler.incrementalCompilation;
|
||||
|
||||
for (const sourceFile of builder.getSourceFiles()) {
|
||||
if (ignoreForEmit.has(sourceFile)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (incrementalCompilation.safeToSkipEmit(sourceFile)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.emit(sourceFile, undefined, undefined, undefined, transformers);
|
||||
incrementalCompilation.recordSuccessfulEmit(sourceFile);
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
/**
|
||||
* Adapted from the original ng-packagr source.
|
||||
*
|
||||
* Changes made:
|
||||
* - Use our own StylesheetProcessor instead of the one provided by ng-packagr.
|
||||
*/
|
||||
|
||||
import { FactoryProvider, InjectionToken } from 'injection-js';
|
||||
import { StylesheetProcessor } from './stylesheet-processor';
|
||||
|
||||
export const NX_STYLESHEET_PROCESSOR_TOKEN =
|
||||
new InjectionToken<StylesheetProcessor>(`nx.v1.stylesheetProcessor`);
|
||||
|
||||
export const NX_STYLESHEET_PROCESSOR: FactoryProvider = {
|
||||
provide: NX_STYLESHEET_PROCESSOR_TOKEN,
|
||||
useFactory: () => StylesheetProcessor,
|
||||
deps: [],
|
||||
};
|
||||
@ -1,279 +0,0 @@
|
||||
/**
|
||||
* Adapted from the original ng-packagr source.
|
||||
*
|
||||
* Changes made:
|
||||
* - Refactored caching to take into account TailwindCSS processing.
|
||||
* - Added PostCSS plugin needed to support TailwindCSS.
|
||||
*/
|
||||
|
||||
import browserslist from 'browserslist';
|
||||
import { existsSync } from 'fs';
|
||||
import {
|
||||
generateKey,
|
||||
readCacheEntry,
|
||||
saveCacheEntry,
|
||||
} from 'ng-packagr/lib/utils/cache';
|
||||
import * as log from 'ng-packagr/lib/utils/log';
|
||||
import { dirname, extname, join } from 'path';
|
||||
import autoprefixer from 'autoprefixer';
|
||||
import postcssUrl from 'postcss-url';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import {
|
||||
getTailwindPostCssPlugin,
|
||||
getTailwindSetup,
|
||||
TailwindSetup,
|
||||
} from '../../../../utilities/ng-packagr/tailwindcss';
|
||||
|
||||
const postcss = require('postcss');
|
||||
|
||||
export enum CssUrl {
|
||||
inline = 'inline',
|
||||
none = 'none',
|
||||
}
|
||||
|
||||
export enum InlineStyleLanguage {
|
||||
sass = 'sass',
|
||||
scss = 'scss',
|
||||
css = 'css',
|
||||
less = 'less',
|
||||
}
|
||||
|
||||
export interface Result {
|
||||
css: string;
|
||||
warnings: string[];
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export class StylesheetProcessor {
|
||||
private browserslistData: string[];
|
||||
private targets: string[];
|
||||
private postCssProcessor: ReturnType<typeof postcss>;
|
||||
private esbuild =
|
||||
new (require('ng-packagr/lib/esbuild/esbuild-executor').EsbuildExecutor)();
|
||||
private styleIncludePaths: string[];
|
||||
|
||||
constructor(
|
||||
private readonly projectBasePath: string,
|
||||
private readonly basePath: string,
|
||||
private readonly cssUrl?: CssUrl,
|
||||
private readonly includePaths?: string[],
|
||||
private cacheDirectory?: string | false,
|
||||
private readonly tailwindConfig?: string
|
||||
) {
|
||||
// By default, browserslist defaults are too inclusive
|
||||
// https://github.com/browserslist/browserslist/blob/83764ea81ffaa39111c204b02c371afa44a4ff07/index.js#L516-L522
|
||||
// We change the default query to browsers that Angular support.
|
||||
// https://angular.io/guide/browser-support
|
||||
(browserslist.defaults as string[]) = [
|
||||
'last 2 Chrome versions',
|
||||
'last 1 Firefox version',
|
||||
'last 2 Edge major versions',
|
||||
'last 2 Safari major versions',
|
||||
'last 2 iOS major versions',
|
||||
'Firefox ESR',
|
||||
];
|
||||
|
||||
this.styleIncludePaths = [...this.includePaths];
|
||||
let prevDir = null;
|
||||
let currentDir = this.basePath;
|
||||
|
||||
while (currentDir !== prevDir) {
|
||||
const p = join(currentDir, 'node_modules');
|
||||
if (existsSync(p)) {
|
||||
this.styleIncludePaths.push(p);
|
||||
}
|
||||
|
||||
prevDir = currentDir;
|
||||
currentDir = dirname(prevDir);
|
||||
}
|
||||
|
||||
this.browserslistData = browserslist(undefined, { path: this.basePath });
|
||||
this.targets = transformSupportedBrowsersToTargets(this.browserslistData);
|
||||
const tailwindSetup = getTailwindSetup(
|
||||
this.projectBasePath,
|
||||
this.tailwindConfig
|
||||
);
|
||||
if (tailwindSetup) {
|
||||
this.cacheDirectory = undefined;
|
||||
}
|
||||
this.postCssProcessor = this.createPostCssProcessor(tailwindSetup);
|
||||
}
|
||||
|
||||
async process({
|
||||
filePath,
|
||||
content,
|
||||
}: {
|
||||
filePath: string;
|
||||
content: string;
|
||||
}): Promise<string> {
|
||||
const CACHE_KEY_VALUES = [
|
||||
...this.browserslistData,
|
||||
...this.styleIncludePaths,
|
||||
this.cssUrl,
|
||||
].join(':');
|
||||
|
||||
let key: string | undefined;
|
||||
if (
|
||||
this.cacheDirectory &&
|
||||
!content.includes('@import') &&
|
||||
!content.includes('@use')
|
||||
) {
|
||||
// No transitive deps and no Tailwind directives, we can cache more aggressively.
|
||||
key = await generateKey(content, CACHE_KEY_VALUES);
|
||||
const result = await readCacheEntry(this.cacheDirectory, key);
|
||||
if (result) {
|
||||
result.warnings.forEach((msg) => log.warn(msg));
|
||||
|
||||
return result.css;
|
||||
}
|
||||
}
|
||||
|
||||
// Render pre-processor language (sass, styl, less)
|
||||
const renderedCss = await this.renderCss(filePath, content);
|
||||
|
||||
// We cannot cache CSS re-rendering phase, because a transitive dependency via (@import) can case different CSS output.
|
||||
// Example a change in a mixin or SCSS variable.
|
||||
if (!key) {
|
||||
key = await generateKey(renderedCss, CACHE_KEY_VALUES);
|
||||
}
|
||||
|
||||
if (this.cacheDirectory) {
|
||||
const cachedResult = await readCacheEntry(this.cacheDirectory, key);
|
||||
if (cachedResult) {
|
||||
cachedResult.warnings.forEach((msg) => log.warn(msg));
|
||||
|
||||
return cachedResult.css;
|
||||
}
|
||||
}
|
||||
|
||||
// Render postcss (autoprefixing and friends)
|
||||
const result = await this.postCssProcessor.process(renderedCss, {
|
||||
from: filePath,
|
||||
to: filePath.replace(extname(filePath), '.css'),
|
||||
});
|
||||
|
||||
const warnings = result.warnings().map((w) => w.toString());
|
||||
const { code, warnings: esBuildWarnings } = await this.esbuild.transform(
|
||||
result.css,
|
||||
{
|
||||
loader: 'css',
|
||||
minify: true,
|
||||
target: this.targets,
|
||||
sourcefile: filePath,
|
||||
}
|
||||
);
|
||||
|
||||
if (esBuildWarnings.length > 0) {
|
||||
warnings.push(
|
||||
...(await this.esbuild.formatMessages(esBuildWarnings, {
|
||||
kind: 'warning',
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
if (this.cacheDirectory) {
|
||||
await saveCacheEntry(
|
||||
this.cacheDirectory,
|
||||
key,
|
||||
JSON.stringify({
|
||||
css: code,
|
||||
warnings,
|
||||
})
|
||||
);
|
||||
}
|
||||
warnings.forEach((msg) => log.warn(msg));
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
private createPostCssProcessor(
|
||||
tailwindSetup: TailwindSetup
|
||||
): ReturnType<typeof postcss> {
|
||||
const postCssPlugins = [];
|
||||
if (tailwindSetup) {
|
||||
postCssPlugins.push(getTailwindPostCssPlugin(tailwindSetup));
|
||||
}
|
||||
|
||||
if (this.cssUrl !== CssUrl.none) {
|
||||
postCssPlugins.push(postcssUrl({ url: this.cssUrl }));
|
||||
}
|
||||
|
||||
postCssPlugins.push(
|
||||
autoprefixer({
|
||||
ignoreUnknownVersions: true,
|
||||
overrideBrowserslist: this.browserslistData,
|
||||
})
|
||||
);
|
||||
|
||||
return postcss(postCssPlugins);
|
||||
}
|
||||
|
||||
private async renderCss(filePath: string, css: string): Promise<string> {
|
||||
const ext = extname(filePath);
|
||||
|
||||
switch (ext) {
|
||||
case '.sass':
|
||||
case '.scss': {
|
||||
return (await import('sass')).compileString(css, {
|
||||
url: pathToFileURL(filePath),
|
||||
syntax: '.sass' === ext ? 'indented' : 'scss',
|
||||
loadPaths: this.styleIncludePaths,
|
||||
}).css;
|
||||
}
|
||||
case '.less': {
|
||||
const { css: content } = await (
|
||||
await import('less')
|
||||
).render(css, {
|
||||
filename: filePath,
|
||||
javascriptEnabled: true,
|
||||
paths: this.styleIncludePaths,
|
||||
});
|
||||
|
||||
return content;
|
||||
}
|
||||
case '.css':
|
||||
default:
|
||||
return css;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function transformSupportedBrowsersToTargets(
|
||||
supportedBrowsers: string[]
|
||||
): string[] {
|
||||
const transformed: string[] = [];
|
||||
|
||||
// https://esbuild.github.io/api/#target
|
||||
const esBuildSupportedBrowsers = new Set([
|
||||
'safari',
|
||||
'firefox',
|
||||
'edge',
|
||||
'chrome',
|
||||
'ios',
|
||||
]);
|
||||
|
||||
for (const browser of supportedBrowsers) {
|
||||
let [browserName, version] = browser.split(' ');
|
||||
|
||||
// browserslist uses the name `ios_saf` for iOS Safari whereas esbuild uses `ios`
|
||||
if (browserName === 'ios_saf') {
|
||||
browserName = 'ios';
|
||||
}
|
||||
|
||||
// browserslist uses ranges `15.2-15.3` versions but only the lowest is required
|
||||
// to perform minimum supported feature checks. esbuild also expects a single version.
|
||||
[version] = version.split('-');
|
||||
|
||||
if (esBuildSupportedBrowsers.has(browserName)) {
|
||||
if (browserName === 'safari' && version === 'tp') {
|
||||
// esbuild only supports numeric versions so `TP` is converted to a high number (999) since
|
||||
// a Technology Preview (TP) of Safari is assumed to support all currently known features.
|
||||
version = '999';
|
||||
}
|
||||
|
||||
transformed.push(browserName + version);
|
||||
}
|
||||
}
|
||||
|
||||
return transformed.length ? transformed : undefined;
|
||||
}
|
||||
@ -15,7 +15,7 @@ async function initializeNgPackgrLite(
|
||||
context: ExecutorContext,
|
||||
projectDependencies: DependentBuildableProjectNode[]
|
||||
): Promise<NgPackagr> {
|
||||
const ngPackagr = await getNgPackagrInstance(options);
|
||||
const ngPackagr = await getNgPackagrInstance();
|
||||
ngPackagr.forProject(resolve(context.root, options.project));
|
||||
|
||||
if (options.tsConfig) {
|
||||
|
||||
@ -7,10 +7,6 @@
|
||||
"cli": "nx",
|
||||
"type": "object",
|
||||
"presets": [
|
||||
{
|
||||
"name": "Buildable Library with Tailwind",
|
||||
"keys": ["project", "tailwindConfig"]
|
||||
},
|
||||
{
|
||||
"name": "Updating Project Dependencies for Buildable Library",
|
||||
"keys": ["project"]
|
||||
@ -36,10 +32,6 @@
|
||||
"poll": {
|
||||
"type": "number",
|
||||
"description": "Enable and define the file watching poll time period in milliseconds. _Note: this is only supported in Angular versions >= 18.0.0_."
|
||||
},
|
||||
"tailwindConfig": {
|
||||
"type": "string",
|
||||
"description": "The full path for the Tailwind configuration file, relative to the workspace root. If not provided and a `tailwind.config.js` file exists in the project or workspace root, it will be used. Otherwise, Tailwind will not be configured."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
/**
|
||||
* Adapted from the original ng-packagr source.
|
||||
*
|
||||
* Changes made:
|
||||
* - Use our own compileNgcTransformFactory instead of the one provided by ng-packagr.
|
||||
* - Use NX_STYLESHEET_PROCESSOR instead of STYLESHEET_PROCESSOR.
|
||||
* - Use NX_STYLESHEET_PROCESSOR_TOKEN instead of STYLESHEET_PROCESSOR_TOKEN.
|
||||
*/
|
||||
|
||||
import { InjectionToken, Provider } from 'injection-js';
|
||||
import { Transform } from 'ng-packagr/lib/graph/transform';
|
||||
import {
|
||||
provideTransform,
|
||||
TransformProvider,
|
||||
} from 'ng-packagr/lib/graph/transform.di';
|
||||
import {
|
||||
NX_STYLESHEET_PROCESSOR,
|
||||
NX_STYLESHEET_PROCESSOR_TOKEN,
|
||||
} from '../../styles/stylesheet-processor.di';
|
||||
import { NX_OPTIONS_TOKEN } from '../options.di';
|
||||
import { compileNgcTransformFactory } from './compile-ngc.transform';
|
||||
|
||||
export const NX_COMPILE_NGC_TOKEN = new InjectionToken<Transform>(
|
||||
`nx.v1.compileNgcTransform`
|
||||
);
|
||||
|
||||
export const NX_COMPILE_NGC_TRANSFORM: TransformProvider = provideTransform({
|
||||
provide: NX_COMPILE_NGC_TOKEN,
|
||||
useFactory: compileNgcTransformFactory,
|
||||
deps: [NX_STYLESHEET_PROCESSOR_TOKEN, NX_OPTIONS_TOKEN],
|
||||
});
|
||||
|
||||
export const NX_COMPILE_NGC_PROVIDERS: Provider[] = [
|
||||
NX_STYLESHEET_PROCESSOR,
|
||||
NX_COMPILE_NGC_TRANSFORM,
|
||||
];
|
||||
@ -1,120 +0,0 @@
|
||||
/**
|
||||
* Adapted from the original ng-packagr source.
|
||||
*
|
||||
* Changes made:
|
||||
* - Use our own StylesheetProcessor files instead of the ones provide by ng-packagr.
|
||||
* - Support ngcc for Angular < 16.
|
||||
* - Support ESM2020 for Angular < 16.
|
||||
*/
|
||||
|
||||
import {
|
||||
Transform,
|
||||
transformFromPromise,
|
||||
} from 'ng-packagr/lib/graph/transform';
|
||||
import {
|
||||
EntryPointNode,
|
||||
isEntryPoint,
|
||||
isEntryPointInProgress,
|
||||
isPackage,
|
||||
PackageNode,
|
||||
} from 'ng-packagr/lib/ng-package/nodes';
|
||||
import { setDependenciesTsConfigPaths } from 'ng-packagr/lib/ts/tsconfig';
|
||||
import ora from 'ora';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
import { getInstalledAngularVersionInfo } from '../../../../utilities/angular-version-utils';
|
||||
import { compileSourceFiles } from '../../ngc/compile-source-files';
|
||||
import { StylesheetProcessor as StylesheetProcessorClass } from '../../styles/stylesheet-processor';
|
||||
import { ngccCompilerCli } from '../../utils/ng-compiler-cli';
|
||||
import { NgPackagrOptions } from '../options.di';
|
||||
|
||||
export const compileNgcTransformFactory = (
|
||||
StylesheetProcessor: typeof StylesheetProcessorClass,
|
||||
options: NgPackagrOptions
|
||||
): Transform => {
|
||||
return transformFromPromise(async (graph) => {
|
||||
const spinner = ora({
|
||||
hideCursor: false,
|
||||
discardStdin: false,
|
||||
});
|
||||
|
||||
const entryPoints: EntryPointNode[] = graph.filter(isEntryPoint);
|
||||
const entryPoint: EntryPointNode = graph.find(isEntryPointInProgress());
|
||||
const ngPackageNode: PackageNode = graph.find(isPackage);
|
||||
const projectBasePath = ngPackageNode.data.primary.basePath;
|
||||
|
||||
try {
|
||||
// Add paths mappings for dependencies
|
||||
const tsConfig = setDependenciesTsConfigPaths(
|
||||
entryPoint.data.tsConfig,
|
||||
entryPoints
|
||||
);
|
||||
|
||||
const angularVersion = getInstalledAngularVersionInfo();
|
||||
|
||||
// Compile TypeScript sources
|
||||
const { declarations } = entryPoint.data.destinationFiles;
|
||||
const esmModulePath =
|
||||
angularVersion.major < 16
|
||||
? (entryPoint.data.destinationFiles as any).esm2020
|
||||
: entryPoint.data.destinationFiles.esm2022;
|
||||
const { basePath, cssUrl, styleIncludePaths } =
|
||||
entryPoint.data.entryPoint;
|
||||
const { moduleResolutionCache } = entryPoint.cache;
|
||||
|
||||
spinner.start(
|
||||
`Compiling with Angular sources in Ivy ${
|
||||
tsConfig.options.compilationMode || 'full'
|
||||
} compilation mode.`
|
||||
);
|
||||
let ngccProcessor: any;
|
||||
if (angularVersion && angularVersion.major < 16) {
|
||||
ngccProcessor =
|
||||
new (require('ng-packagr/lib/ngc/ngcc-processor').NgccProcessor)(
|
||||
await ngccCompilerCli(),
|
||||
(entryPoint.cache as any).ngccProcessingCache,
|
||||
tsConfig.project,
|
||||
tsConfig.options,
|
||||
entryPoints
|
||||
);
|
||||
if (!entryPoint.data.entryPoint.isSecondaryEntryPoint) {
|
||||
// Only run the async version of NGCC during the primary entrypoint processing.
|
||||
await ngccProcessor.process();
|
||||
}
|
||||
}
|
||||
|
||||
entryPoint.cache.stylesheetProcessor ??= new StylesheetProcessor(
|
||||
projectBasePath,
|
||||
basePath,
|
||||
cssUrl,
|
||||
styleIncludePaths,
|
||||
options.cacheEnabled && options.cacheDirectory,
|
||||
options.tailwindConfig
|
||||
) as any;
|
||||
|
||||
await compileSourceFiles(
|
||||
graph,
|
||||
tsConfig,
|
||||
moduleResolutionCache,
|
||||
options,
|
||||
{
|
||||
outDir: path.dirname(esmModulePath),
|
||||
declarationDir: path.dirname(declarations),
|
||||
declaration: true,
|
||||
target:
|
||||
angularVersion.major >= 16
|
||||
? ts.ScriptTarget.ES2022
|
||||
: ts.ScriptTarget.ES2020,
|
||||
},
|
||||
entryPoint.cache.stylesheetProcessor as any,
|
||||
ngccProcessor
|
||||
);
|
||||
} catch (error) {
|
||||
spinner.fail();
|
||||
throw error;
|
||||
}
|
||||
|
||||
spinner.succeed();
|
||||
return graph;
|
||||
});
|
||||
};
|
||||
@ -1,48 +0,0 @@
|
||||
/**
|
||||
* Adapted from the original ng-packagr source.
|
||||
*
|
||||
* Changes made:
|
||||
* - Use NX_COMPILE_NGC_TOKEN instead of COMPILE_NGC_TOKEN.
|
||||
* - Use NX_COMPILE_NGC_PROVIDERS instead of COMPILE_NGC_PROVIDERS.
|
||||
*/
|
||||
|
||||
import { InjectionToken, Provider } from 'injection-js';
|
||||
import { Transform } from 'ng-packagr/lib/graph/transform';
|
||||
import {
|
||||
provideTransform,
|
||||
TransformProvider,
|
||||
} from 'ng-packagr/lib/graph/transform.di';
|
||||
import { entryPointTransformFactory } from 'ng-packagr/lib/ng-package/entry-point/entry-point.transform';
|
||||
import {
|
||||
WRITE_BUNDLES_TRANSFORM,
|
||||
WRITE_BUNDLES_TRANSFORM_TOKEN,
|
||||
} from 'ng-packagr/lib/ng-package/entry-point/write-bundles.di';
|
||||
import {
|
||||
WRITE_PACKAGE_TRANSFORM,
|
||||
WRITE_PACKAGE_TRANSFORM_TOKEN,
|
||||
} from 'ng-packagr/lib/ng-package/entry-point/write-package.di';
|
||||
import {
|
||||
NX_COMPILE_NGC_PROVIDERS,
|
||||
NX_COMPILE_NGC_TOKEN,
|
||||
} from './compile-ngc.di';
|
||||
|
||||
export const NX_ENTRY_POINT_TRANSFORM_TOKEN = new InjectionToken<Transform>(
|
||||
`nx.v1.entryPointTransform`
|
||||
);
|
||||
|
||||
export const NX_ENTRY_POINT_TRANSFORM: TransformProvider = provideTransform({
|
||||
provide: NX_ENTRY_POINT_TRANSFORM_TOKEN,
|
||||
useFactory: entryPointTransformFactory,
|
||||
deps: [
|
||||
NX_COMPILE_NGC_TOKEN,
|
||||
WRITE_BUNDLES_TRANSFORM_TOKEN,
|
||||
WRITE_PACKAGE_TRANSFORM_TOKEN,
|
||||
],
|
||||
});
|
||||
|
||||
export const NX_ENTRY_POINT_PROVIDERS: Provider[] = [
|
||||
NX_ENTRY_POINT_TRANSFORM,
|
||||
...NX_COMPILE_NGC_PROVIDERS,
|
||||
WRITE_BUNDLES_TRANSFORM,
|
||||
WRITE_PACKAGE_TRANSFORM,
|
||||
];
|
||||
@ -1,47 +0,0 @@
|
||||
/**
|
||||
* Adapted from the original ng-packagr.
|
||||
*
|
||||
* Changes made:
|
||||
* - Use our own options interface to add support for tailwindConfig.
|
||||
*/
|
||||
|
||||
import findCacheDirectory from 'find-cache-dir';
|
||||
import { InjectionToken, Provider, ValueProvider } from 'injection-js';
|
||||
import { NgPackagrOptions as NgPackagrOptionsBase } from 'ng-packagr/lib/ng-package/options.di';
|
||||
import { tmpdir } from 'os';
|
||||
import { resolve } from 'path';
|
||||
|
||||
export interface NgPackagrOptions extends NgPackagrOptionsBase {
|
||||
tailwindConfig?: string;
|
||||
}
|
||||
|
||||
export const NX_OPTIONS_TOKEN = new InjectionToken<NgPackagrOptions>(
|
||||
`nx.v1.options`
|
||||
);
|
||||
|
||||
export const nxProvideOptions = (
|
||||
options: NgPackagrOptions = {}
|
||||
): ValueProvider => ({
|
||||
provide: NX_OPTIONS_TOKEN,
|
||||
useValue: normalizeOptions(options),
|
||||
});
|
||||
|
||||
export const NX_DEFAULT_OPTIONS_PROVIDER: Provider = nxProvideOptions();
|
||||
|
||||
function normalizeOptions(options: NgPackagrOptions = {}) {
|
||||
const ciEnv = process.env['CI'];
|
||||
const isCI = ciEnv?.toLowerCase() === 'true' || ciEnv === '1';
|
||||
const { cacheEnabled = !isCI, cacheDirectory = findCachePath() } = options;
|
||||
|
||||
return {
|
||||
...options,
|
||||
cacheEnabled,
|
||||
cacheDirectory,
|
||||
};
|
||||
}
|
||||
|
||||
function findCachePath(): string {
|
||||
const name = 'ng-packagr';
|
||||
|
||||
return findCacheDirectory({ name }) || resolve(tmpdir(), name);
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
/**
|
||||
* Adapted from the original ng-packagr source.
|
||||
*
|
||||
* Changes made:
|
||||
* - Use NX_ENTRY_POINT_TRANSFORM_TOKEN instead of ENTRY_POINT_TRANSFORM_TOKEN.
|
||||
* - USE NX_OPTIONS_TOKEN instead of OPTIONS_TOKEN.
|
||||
* - USE NX_DEFAULT_OPTIONS_PROVIDER instead of DEFAULT_OPTIONS_PROVIDER.
|
||||
*/
|
||||
|
||||
import { InjectionToken, Provider } from 'injection-js';
|
||||
import { Transform } from 'ng-packagr/lib/graph/transform';
|
||||
import {
|
||||
provideTransform,
|
||||
TransformProvider,
|
||||
} from 'ng-packagr/lib/graph/transform.di';
|
||||
import {
|
||||
ANALYSE_SOURCES_TOKEN,
|
||||
ANALYSE_SOURCES_TRANSFORM,
|
||||
} from 'ng-packagr/lib/ng-package/entry-point/analyse-sources.di';
|
||||
import {
|
||||
INIT_TS_CONFIG_TOKEN,
|
||||
INIT_TS_CONFIG_TRANSFORM,
|
||||
} from 'ng-packagr/lib/ng-package/entry-point/init-tsconfig.di';
|
||||
import { packageTransformFactory } from 'ng-packagr/lib/ng-package/package.transform';
|
||||
import { PROJECT_TOKEN } from 'ng-packagr/lib/project.di';
|
||||
import { NX_ENTRY_POINT_TRANSFORM_TOKEN } from './entry-point/entry-point.di';
|
||||
import { NX_DEFAULT_OPTIONS_PROVIDER, NX_OPTIONS_TOKEN } from './options.di';
|
||||
|
||||
export const NX_PACKAGE_TRANSFORM_TOKEN = new InjectionToken<Transform>(
|
||||
`nx.v1.packageTransform`
|
||||
);
|
||||
|
||||
export const NX_PACKAGE_TRANSFORM: TransformProvider = provideTransform({
|
||||
provide: NX_PACKAGE_TRANSFORM_TOKEN,
|
||||
useFactory: packageTransformFactory,
|
||||
deps: [
|
||||
PROJECT_TOKEN,
|
||||
NX_OPTIONS_TOKEN,
|
||||
INIT_TS_CONFIG_TOKEN,
|
||||
ANALYSE_SOURCES_TOKEN,
|
||||
NX_ENTRY_POINT_TRANSFORM_TOKEN,
|
||||
],
|
||||
});
|
||||
|
||||
export const NX_PACKAGE_PROVIDERS: Provider[] = [
|
||||
NX_PACKAGE_TRANSFORM,
|
||||
NX_DEFAULT_OPTIONS_PROVIDER,
|
||||
INIT_TS_CONFIG_TRANSFORM,
|
||||
ANALYSE_SOURCES_TRANSFORM,
|
||||
];
|
||||
@ -1,12 +1,6 @@
|
||||
import { NgPackagr, ngPackagr } from 'ng-packagr';
|
||||
import type { BuildAngularLibraryExecutorOptions } from '../../package/schema';
|
||||
import { getInstalledAngularVersionInfo } from '../../utilities/angular-version-utils';
|
||||
|
||||
export async function getNgPackagrInstance(
|
||||
options: BuildAngularLibraryExecutorOptions
|
||||
): Promise<NgPackagr> {
|
||||
const { major: angularMajorVersion } = getInstalledAngularVersionInfo();
|
||||
if (angularMajorVersion >= 17) {
|
||||
export async function getNgPackagrInstance(): Promise<NgPackagr> {
|
||||
const { STYLESHEET_PROCESSOR } = await import(
|
||||
'../../utilities/ng-packagr/stylesheet-processor.di.js'
|
||||
);
|
||||
@ -15,24 +9,3 @@ export async function getNgPackagrInstance(
|
||||
packagr.withProviders([STYLESHEET_PROCESSOR]);
|
||||
return packagr;
|
||||
}
|
||||
|
||||
const { NX_ENTRY_POINT_PROVIDERS } = await import(
|
||||
'./ng-package/entry-point/entry-point.di.js'
|
||||
);
|
||||
const { nxProvideOptions } = await import('./ng-package/options.di.js');
|
||||
const { NX_PACKAGE_PROVIDERS, NX_PACKAGE_TRANSFORM } = await import(
|
||||
'./ng-package/package.di.js'
|
||||
);
|
||||
|
||||
const packagr = new NgPackagr([
|
||||
...NX_PACKAGE_PROVIDERS,
|
||||
...NX_ENTRY_POINT_PROVIDERS,
|
||||
nxProvideOptions({
|
||||
tailwindConfig: options.tailwindConfig,
|
||||
watch: options.watch,
|
||||
}),
|
||||
]);
|
||||
packagr.withBuildTransform(NX_PACKAGE_TRANSFORM.provide);
|
||||
|
||||
return packagr;
|
||||
}
|
||||
|
||||
@ -1,284 +0,0 @@
|
||||
/**
|
||||
* Adapted from the original ng-packagr source.
|
||||
*
|
||||
* Changes made:
|
||||
* - Use custom StylesheetProcessor instead of the one provided by ng-packagr.
|
||||
* - Support ngcc for Angular < 16.
|
||||
* - Support Angular Compiler `incrementalDriver` for Angular < 16.
|
||||
*/
|
||||
|
||||
import { BuildGraph } from 'ng-packagr/lib/graph/build-graph';
|
||||
import {
|
||||
EntryPointNode,
|
||||
PackageNode,
|
||||
isEntryPointInProgress,
|
||||
isPackage,
|
||||
} from 'ng-packagr/lib/ng-package/nodes';
|
||||
import * as log from 'ng-packagr/lib/utils/log';
|
||||
import {
|
||||
augmentProgramWithVersioning,
|
||||
cacheCompilerHost,
|
||||
} from 'ng-packagr/lib/ts/cache-compiler-host';
|
||||
import { join } from 'node:path';
|
||||
import * as ts from 'typescript';
|
||||
import { getInstalledAngularVersionInfo } from '../../../utilities/angular-version-utils';
|
||||
import { loadEsmModule } from '../../../utilities/module-loader';
|
||||
import { NgPackagrOptions } from '../ng-package/options.di';
|
||||
import { StylesheetProcessor } from '../styles/stylesheet-processor';
|
||||
|
||||
export async function compileSourceFiles(
|
||||
graph: BuildGraph,
|
||||
tsConfig: any,
|
||||
moduleResolutionCache: ts.ModuleResolutionCache,
|
||||
options: NgPackagrOptions,
|
||||
extraOptions?: Partial<ts.CompilerOptions>,
|
||||
stylesheetProcessor?: StylesheetProcessor,
|
||||
ngccProcessor?: any
|
||||
) {
|
||||
const { NgtscProgram, formatDiagnostics } = await loadEsmModule(
|
||||
'@angular/compiler-cli'
|
||||
);
|
||||
const { cacheDirectory, watch, cacheEnabled } = options;
|
||||
const tsConfigOptions: ts.CompilerOptions = {
|
||||
...tsConfig.options,
|
||||
...extraOptions,
|
||||
};
|
||||
const entryPoint: EntryPointNode = graph.find(isEntryPointInProgress());
|
||||
const ngPackageNode: PackageNode = graph.find(isPackage);
|
||||
const inlineStyleLanguage = ngPackageNode.data.inlineStyleLanguage;
|
||||
|
||||
const cacheDir = cacheEnabled && cacheDirectory;
|
||||
if (cacheDir) {
|
||||
tsConfigOptions.incremental ??= true;
|
||||
tsConfigOptions.tsBuildInfoFile ??= join(
|
||||
cacheDir,
|
||||
`tsbuildinfo/${entryPoint.data.entryPoint.flatModuleFile}.tsbuildinfo`
|
||||
);
|
||||
}
|
||||
|
||||
let tsCompilerHost = cacheCompilerHost(
|
||||
graph,
|
||||
entryPoint,
|
||||
tsConfigOptions,
|
||||
moduleResolutionCache,
|
||||
stylesheetProcessor as any,
|
||||
inlineStyleLanguage
|
||||
);
|
||||
|
||||
if (ngccProcessor) {
|
||||
tsCompilerHost =
|
||||
require('ng-packagr/lib/ts/ngcc-transform-compiler-host').ngccTransformCompilerHost(
|
||||
tsCompilerHost,
|
||||
tsConfigOptions,
|
||||
ngccProcessor,
|
||||
moduleResolutionCache
|
||||
);
|
||||
}
|
||||
|
||||
const cache = entryPoint.cache;
|
||||
const sourceFileCache = cache.sourcesFileCache;
|
||||
let usingBuildInfo = false;
|
||||
|
||||
let oldBuilder = cache.oldBuilder;
|
||||
if (!oldBuilder && cacheDir) {
|
||||
oldBuilder = ts.readBuilderProgram(tsConfigOptions, tsCompilerHost);
|
||||
usingBuildInfo = true;
|
||||
}
|
||||
|
||||
// Create the Angular specific program that contains the Angular compiler
|
||||
const angularProgram = new NgtscProgram(
|
||||
tsConfig.rootNames,
|
||||
tsConfigOptions,
|
||||
tsCompilerHost,
|
||||
cache.oldNgtscProgram
|
||||
);
|
||||
|
||||
const angularCompiler = angularProgram.compiler;
|
||||
const { ignoreForDiagnostics, ignoreForEmit } = angularCompiler;
|
||||
|
||||
// SourceFile versions are required for builder programs.
|
||||
// The wrapped host inside NgtscProgram adds additional files that will not have versions.
|
||||
const typeScriptProgram = angularProgram.getTsProgram();
|
||||
augmentProgramWithVersioning(typeScriptProgram);
|
||||
|
||||
let builder: ts.BuilderProgram | ts.EmitAndSemanticDiagnosticsBuilderProgram;
|
||||
if (watch || cacheDir) {
|
||||
builder = cache.oldBuilder =
|
||||
ts.createEmitAndSemanticDiagnosticsBuilderProgram(
|
||||
typeScriptProgram,
|
||||
tsCompilerHost,
|
||||
oldBuilder
|
||||
);
|
||||
cache.oldNgtscProgram = angularProgram;
|
||||
} else {
|
||||
builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(
|
||||
typeScriptProgram,
|
||||
tsCompilerHost
|
||||
);
|
||||
}
|
||||
|
||||
// Update semantic diagnostics cache
|
||||
const affectedFiles = new Set<ts.SourceFile>();
|
||||
|
||||
// Analyze affected files when in watch mode for incremental type checking
|
||||
if ('getSemanticDiagnosticsOfNextAffectedFile' in builder) {
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const result = builder.getSemanticDiagnosticsOfNextAffectedFile(
|
||||
undefined,
|
||||
(sourceFile) => {
|
||||
// If the affected file is a TTC shim, add the shim's original source file.
|
||||
// This ensures that changes that affect TTC are typechecked even when the changes
|
||||
// are otherwise unrelated from a TS perspective and do not result in Ivy codegen changes.
|
||||
// For example, changing @Input property types of a directive used in another component's
|
||||
// template.
|
||||
if (
|
||||
ignoreForDiagnostics.has(sourceFile) &&
|
||||
sourceFile.fileName.endsWith('.ngtypecheck.ts')
|
||||
) {
|
||||
// This file name conversion relies on internal compiler logic and should be converted
|
||||
// to an official method when available. 15 is length of `.ngtypecheck.ts`
|
||||
const originalFilename = sourceFile.fileName.slice(0, -15) + '.ts';
|
||||
const originalSourceFile = builder.getSourceFile(originalFilename);
|
||||
if (originalSourceFile) {
|
||||
affectedFiles.add(originalSourceFile);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
break;
|
||||
}
|
||||
|
||||
affectedFiles.add(result.affected as ts.SourceFile);
|
||||
}
|
||||
|
||||
// Add all files with associated template type checking files.
|
||||
// Stored TS build info does not have knowledge of the AOT compiler or the typechecking state of the templates.
|
||||
// To ensure that errors are reported correctly, all AOT component diagnostics need to be analyzed even if build
|
||||
// info is present.
|
||||
if (usingBuildInfo) {
|
||||
for (const sourceFile of builder.getSourceFiles()) {
|
||||
if (
|
||||
ignoreForDiagnostics.has(sourceFile) &&
|
||||
sourceFile.fileName.endsWith('.ngtypecheck.ts')
|
||||
) {
|
||||
// This file name conversion relies on internal compiler logic and should be converted
|
||||
// to an official method when available. 15 is length of `.ngtypecheck.ts`
|
||||
const originalFilename = sourceFile.fileName.slice(0, -15) + '.ts';
|
||||
const originalSourceFile = builder.getSourceFile(originalFilename);
|
||||
if (originalSourceFile) {
|
||||
affectedFiles.add(originalSourceFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect program level diagnostics
|
||||
const allDiagnostics: ts.Diagnostic[] = [
|
||||
...angularCompiler.getOptionDiagnostics(),
|
||||
...builder.getOptionsDiagnostics(),
|
||||
...builder.getGlobalDiagnostics(),
|
||||
];
|
||||
|
||||
// Required to support asynchronous resource loading
|
||||
// Must be done before creating transformers or getting template diagnostics
|
||||
await angularCompiler.analyzeAsync();
|
||||
|
||||
// Collect source file specific diagnostics
|
||||
for (const sourceFile of builder.getSourceFiles()) {
|
||||
if (ignoreForDiagnostics.has(sourceFile)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
allDiagnostics.push(
|
||||
...builder.getDeclarationDiagnostics(sourceFile),
|
||||
...builder.getSyntacticDiagnostics(sourceFile),
|
||||
...builder.getSemanticDiagnostics(sourceFile)
|
||||
);
|
||||
|
||||
// Declaration files cannot have template diagnostics
|
||||
if (sourceFile.isDeclarationFile) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only request Angular template diagnostics for affected files to avoid
|
||||
// overhead of template diagnostics for unchanged files.
|
||||
if (affectedFiles.has(sourceFile)) {
|
||||
const angularDiagnostics = angularCompiler.getDiagnosticsForFile(
|
||||
sourceFile,
|
||||
affectedFiles.size === 1
|
||||
? /** OptimizeFor.SingleFile **/ 0
|
||||
: /** OptimizeFor.WholeProgram */ 1
|
||||
);
|
||||
|
||||
allDiagnostics.push(...angularDiagnostics);
|
||||
sourceFileCache.updateAngularDiagnostics(sourceFile, angularDiagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
const otherDiagnostics = [];
|
||||
const errorDiagnostics = [];
|
||||
for (const diagnostic of allDiagnostics) {
|
||||
if (diagnostic.category === ts.DiagnosticCategory.Error) {
|
||||
errorDiagnostics.push(diagnostic);
|
||||
} else {
|
||||
otherDiagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
if (otherDiagnostics.length) {
|
||||
log.msg(formatDiagnostics(errorDiagnostics));
|
||||
}
|
||||
|
||||
if (errorDiagnostics.length) {
|
||||
throw new Error(formatDiagnostics(errorDiagnostics));
|
||||
}
|
||||
|
||||
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
|
||||
? (angularCompiler as any).incrementalDriver
|
||||
: angularCompiler.incrementalCompilation;
|
||||
|
||||
for (const sourceFile of builder.getSourceFiles()) {
|
||||
if (ignoreForEmit.has(sourceFile)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (incrementalCompilation.safeToSkipEmit(sourceFile)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.emit(sourceFile, undefined, undefined, undefined, transformers);
|
||||
incrementalCompilation.recordSuccessfulEmit(sourceFile);
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
/**
|
||||
* Adapted from the original ng-packagr source.
|
||||
*
|
||||
* Changes made:
|
||||
* - Use our own StylesheetProcessor instead of the one provided by ng-packagr.
|
||||
*/
|
||||
|
||||
import { FactoryProvider, InjectionToken } from 'injection-js';
|
||||
import { StylesheetProcessor } from './stylesheet-processor';
|
||||
|
||||
export const NX_STYLESHEET_PROCESSOR_TOKEN =
|
||||
new InjectionToken<StylesheetProcessor>(`nx.v1.stylesheetProcessor`);
|
||||
|
||||
export const NX_STYLESHEET_PROCESSOR: FactoryProvider = {
|
||||
provide: NX_STYLESHEET_PROCESSOR_TOKEN,
|
||||
useFactory: () => StylesheetProcessor,
|
||||
deps: [],
|
||||
};
|
||||
@ -1,272 +0,0 @@
|
||||
/**
|
||||
* Adapted from the original ng-packagr source.
|
||||
*
|
||||
* Changes made:
|
||||
* - Refactored caching to take into account TailwindCSS processing.
|
||||
* - Added PostCSS plugin needed to support TailwindCSS.
|
||||
*/
|
||||
|
||||
import browserslist from 'browserslist';
|
||||
import { existsSync } from 'fs';
|
||||
import {
|
||||
generateKey,
|
||||
readCacheEntry,
|
||||
saveCacheEntry,
|
||||
} from 'ng-packagr/lib/utils/cache';
|
||||
import * as log from 'ng-packagr/lib/utils/log';
|
||||
import { dirname, extname, join } from 'path';
|
||||
import autoprefixer from 'autoprefixer';
|
||||
import postcssUrl from 'postcss-url';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import {
|
||||
getTailwindPostCssPlugin,
|
||||
getTailwindSetup,
|
||||
TailwindSetup,
|
||||
} from '../../../utilities/ng-packagr/tailwindcss';
|
||||
|
||||
const postcss = require('postcss');
|
||||
|
||||
export enum CssUrl {
|
||||
inline = 'inline',
|
||||
none = 'none',
|
||||
}
|
||||
|
||||
export interface Result {
|
||||
css: string;
|
||||
warnings: string[];
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export class StylesheetProcessor {
|
||||
private browserslistData: string[];
|
||||
private targets: string[];
|
||||
private postCssProcessor: ReturnType<typeof postcss>;
|
||||
private esbuild =
|
||||
new (require('ng-packagr/lib/esbuild/esbuild-executor').EsbuildExecutor)();
|
||||
private styleIncludePaths: string[];
|
||||
|
||||
constructor(
|
||||
private readonly projectBasePath: string,
|
||||
private readonly basePath: string,
|
||||
private readonly cssUrl?: CssUrl,
|
||||
private readonly includePaths?: string[],
|
||||
private cacheDirectory?: string | false,
|
||||
private readonly tailwindConfig?: string
|
||||
) {
|
||||
// By default, browserslist defaults are too inclusive
|
||||
// https://github.com/browserslist/browserslist/blob/83764ea81ffaa39111c204b02c371afa44a4ff07/index.js#L516-L522
|
||||
// We change the default query to browsers that Angular support.
|
||||
// https://angular.io/guide/browser-support
|
||||
(browserslist.defaults as string[]) = [
|
||||
'last 2 Chrome versions',
|
||||
'last 1 Firefox version',
|
||||
'last 2 Edge major versions',
|
||||
'last 2 Safari major versions',
|
||||
'last 2 iOS major versions',
|
||||
'Firefox ESR',
|
||||
];
|
||||
|
||||
this.styleIncludePaths = [...this.includePaths];
|
||||
let prevDir = null;
|
||||
let currentDir = this.basePath;
|
||||
|
||||
while (currentDir !== prevDir) {
|
||||
const p = join(currentDir, 'node_modules');
|
||||
if (existsSync(p)) {
|
||||
this.styleIncludePaths.push(p);
|
||||
}
|
||||
|
||||
prevDir = currentDir;
|
||||
currentDir = dirname(prevDir);
|
||||
}
|
||||
|
||||
this.browserslistData = browserslist(undefined, { path: this.basePath });
|
||||
this.targets = transformSupportedBrowsersToTargets(this.browserslistData);
|
||||
const tailwindSetup = getTailwindSetup(
|
||||
this.projectBasePath,
|
||||
this.tailwindConfig
|
||||
);
|
||||
if (tailwindSetup) {
|
||||
this.cacheDirectory = undefined;
|
||||
}
|
||||
this.postCssProcessor = this.createPostCssProcessor(tailwindSetup);
|
||||
}
|
||||
|
||||
async process({
|
||||
filePath,
|
||||
content,
|
||||
}: {
|
||||
filePath: string;
|
||||
content: string;
|
||||
}): Promise<string> {
|
||||
const CACHE_KEY_VALUES = [
|
||||
...this.browserslistData,
|
||||
...this.styleIncludePaths,
|
||||
this.cssUrl,
|
||||
].join(':');
|
||||
|
||||
let key: string | undefined;
|
||||
if (
|
||||
this.cacheDirectory &&
|
||||
!content.includes('@import') &&
|
||||
!content.includes('@use')
|
||||
) {
|
||||
// No transitive deps and no Tailwind directives, we can cache more aggressively.
|
||||
key = await generateKey(content, CACHE_KEY_VALUES);
|
||||
const result = await readCacheEntry(this.cacheDirectory, key);
|
||||
if (result) {
|
||||
result.warnings.forEach((msg) => log.warn(msg));
|
||||
|
||||
return result.css;
|
||||
}
|
||||
}
|
||||
|
||||
// Render pre-processor language (sass, styl, less)
|
||||
const renderedCss = await this.renderCss(filePath, content);
|
||||
|
||||
// We cannot cache CSS re-rendering phase, because a transitive dependency via (@import) can case different CSS output.
|
||||
// Example a change in a mixin or SCSS variable.
|
||||
if (!key) {
|
||||
key = await generateKey(renderedCss, CACHE_KEY_VALUES);
|
||||
}
|
||||
|
||||
if (this.cacheDirectory) {
|
||||
const cachedResult = await readCacheEntry(this.cacheDirectory, key);
|
||||
if (cachedResult) {
|
||||
cachedResult.warnings.forEach((msg) => log.warn(msg));
|
||||
|
||||
return cachedResult.css;
|
||||
}
|
||||
}
|
||||
|
||||
// Render postcss (autoprefixing and friends)
|
||||
const result = await this.postCssProcessor.process(renderedCss, {
|
||||
from: filePath,
|
||||
to: filePath.replace(extname(filePath), '.css'),
|
||||
});
|
||||
|
||||
const warnings = result.warnings().map((w) => w.toString());
|
||||
const { code, warnings: esBuildWarnings } = await this.esbuild.transform(
|
||||
result.css,
|
||||
{
|
||||
loader: 'css',
|
||||
minify: true,
|
||||
target: this.targets,
|
||||
sourcefile: filePath,
|
||||
}
|
||||
);
|
||||
|
||||
if (esBuildWarnings.length > 0) {
|
||||
warnings.push(
|
||||
...(await this.esbuild.formatMessages(esBuildWarnings, {
|
||||
kind: 'warning',
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
if (this.cacheDirectory) {
|
||||
await saveCacheEntry(
|
||||
this.cacheDirectory,
|
||||
key,
|
||||
JSON.stringify({
|
||||
css: code,
|
||||
warnings,
|
||||
})
|
||||
);
|
||||
}
|
||||
warnings.forEach((msg) => log.warn(msg));
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
private createPostCssProcessor(
|
||||
tailwindSetup: TailwindSetup
|
||||
): ReturnType<typeof postcss> {
|
||||
const postCssPlugins = [];
|
||||
if (tailwindSetup) {
|
||||
postCssPlugins.push(getTailwindPostCssPlugin(tailwindSetup));
|
||||
}
|
||||
|
||||
if (this.cssUrl !== CssUrl.none) {
|
||||
postCssPlugins.push(postcssUrl({ url: this.cssUrl }));
|
||||
}
|
||||
|
||||
postCssPlugins.push(
|
||||
autoprefixer({
|
||||
ignoreUnknownVersions: true,
|
||||
overrideBrowserslist: this.browserslistData,
|
||||
})
|
||||
);
|
||||
|
||||
return postcss(postCssPlugins);
|
||||
}
|
||||
|
||||
private async renderCss(filePath: string, css: string): Promise<string> {
|
||||
const ext = extname(filePath);
|
||||
|
||||
switch (ext) {
|
||||
case '.sass':
|
||||
case '.scss': {
|
||||
return (await import('sass')).compileString(css, {
|
||||
url: pathToFileURL(filePath),
|
||||
syntax: '.sass' === ext ? 'indented' : 'scss',
|
||||
loadPaths: this.styleIncludePaths,
|
||||
}).css;
|
||||
}
|
||||
case '.less': {
|
||||
const { css: content } = await (
|
||||
await import('less')
|
||||
).render(css, {
|
||||
filename: filePath,
|
||||
javascriptEnabled: true,
|
||||
paths: this.styleIncludePaths,
|
||||
});
|
||||
|
||||
return content;
|
||||
}
|
||||
case '.css':
|
||||
default:
|
||||
return css;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function transformSupportedBrowsersToTargets(
|
||||
supportedBrowsers: string[]
|
||||
): string[] {
|
||||
const transformed: string[] = [];
|
||||
|
||||
// https://esbuild.github.io/api/#target
|
||||
const esBuildSupportedBrowsers = new Set([
|
||||
'safari',
|
||||
'firefox',
|
||||
'edge',
|
||||
'chrome',
|
||||
'ios',
|
||||
]);
|
||||
|
||||
for (const browser of supportedBrowsers) {
|
||||
let [browserName, version] = browser.split(' ');
|
||||
|
||||
// browserslist uses the name `ios_saf` for iOS Safari whereas esbuild uses `ios`
|
||||
if (browserName === 'ios_saf') {
|
||||
browserName = 'ios';
|
||||
}
|
||||
|
||||
// browserslist uses ranges `15.2-15.3` versions but only the lowest is required
|
||||
// to perform minimum supported feature checks. esbuild also expects a single version.
|
||||
[version] = version.split('-');
|
||||
|
||||
if (esBuildSupportedBrowsers.has(browserName)) {
|
||||
if (browserName === 'safari' && version === 'tp') {
|
||||
// esbuild only supports numeric versions so `TP` is converted to a high number (999) since
|
||||
// a Technology Preview (TP) of Safari is assumed to support all currently known features.
|
||||
version = '999';
|
||||
}
|
||||
|
||||
transformed.push(browserName + version);
|
||||
}
|
||||
}
|
||||
|
||||
return transformed.length ? transformed : undefined;
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
export async function ngccCompilerCli(): Promise<any> {
|
||||
const compilerCliModule = await new Function(
|
||||
`return import('@angular/compiler-cli/ngcc');`
|
||||
)();
|
||||
|
||||
return compilerCliModule.process
|
||||
? compilerCliModule
|
||||
: compilerCliModule.default;
|
||||
}
|
||||
@ -20,7 +20,7 @@ async function initializeNgPackagr(
|
||||
context: ExecutorContext,
|
||||
projectDependencies: DependentBuildableProjectNode[]
|
||||
): Promise<NgPackagr> {
|
||||
const ngPackagr = await getNgPackagrInstance(options);
|
||||
const ngPackagr = await getNgPackagrInstance();
|
||||
ngPackagr.forProject(resolve(context.root, options.project));
|
||||
|
||||
if (options.tsConfig) {
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import type { NgPackagrBuilderOptions } from '@angular-devkit/build-angular';
|
||||
|
||||
export interface BuildAngularLibraryExecutorOptions
|
||||
extends NgPackagrBuilderOptions {
|
||||
tailwindConfig?: string;
|
||||
}
|
||||
extends NgPackagrBuilderOptions {}
|
||||
|
||||
@ -7,10 +7,6 @@
|
||||
"cli": "nx",
|
||||
"type": "object",
|
||||
"presets": [
|
||||
{
|
||||
"name": "Publishable Library with Tailwind",
|
||||
"keys": ["project", "tailwindConfig"]
|
||||
},
|
||||
{
|
||||
"name": "Updating Project Dependencies for Publishable Library",
|
||||
"keys": ["project"]
|
||||
@ -36,12 +32,6 @@
|
||||
"poll": {
|
||||
"type": "number",
|
||||
"description": "Enable and define the file watching poll time period in milliseconds. _Note: this is only supported in Angular versions >= 18.0.0_."
|
||||
},
|
||||
"tailwindConfig": {
|
||||
"type": "string",
|
||||
"description": "The full path for the Tailwind configuration file, relative to the workspace root. If not provided and a `tailwind.config.js` file exists in the project or workspace root, it will be used. Otherwise, Tailwind will not be configured. _Note: starting with Angular v17, this option is no longer used and the configuration will be picked up if exists at the project or workspace root_.",
|
||||
"x-completion-type": "file",
|
||||
"x-completion-glob": "tailwind.config@(.js|.cjs|.mjs|.ts)"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
@ -12,11 +12,11 @@ import { dirname, join } from 'path';
|
||||
const Piscina = require('piscina');
|
||||
import { colors } from 'ng-packagr/lib/utils/color';
|
||||
// using this instead of the one from ng-packagr
|
||||
import { getTailwindConfigPath } from './tailwindcss';
|
||||
import { getTailwindConfigPath } from '../tailwindcss';
|
||||
import { workspaceRoot } from '@nx/devkit';
|
||||
import type { PostcssConfiguration } from 'ng-packagr/lib/styles/postcss-configuration';
|
||||
import { gt, gte } from 'semver';
|
||||
import { getInstalledPackageVersionInfo } from '../angular-version-utils';
|
||||
import { getInstalledPackageVersionInfo } from '../../angular-version-utils';
|
||||
|
||||
const maxWorkersVariable = process.env['NG_BUILD_MAX_WORKERS'];
|
||||
const maxThreads =
|
||||
@ -1,20 +1,27 @@
|
||||
import type { FactoryProvider } from 'injection-js';
|
||||
import { STYLESHEET_PROCESSOR_TOKEN } from 'ng-packagr/lib/styles/stylesheet-processor.di';
|
||||
import { getInstalledPackageVersionInfo } from '../angular-version-utils';
|
||||
import {
|
||||
AsyncStylesheetProcessor,
|
||||
StylesheetProcessor,
|
||||
} from './stylesheet-processor';
|
||||
|
||||
export const STYLESHEET_PROCESSOR: FactoryProvider = {
|
||||
provide: STYLESHEET_PROCESSOR_TOKEN,
|
||||
useFactory: () => {
|
||||
const { version: ngPackagrVersion } =
|
||||
const { major: ngPackagrMajorVersion, version: ngPackagrVersion } =
|
||||
getInstalledPackageVersionInfo('ng-packagr');
|
||||
|
||||
return ngPackagrVersion !== '17.2.0'
|
||||
? StylesheetProcessor
|
||||
: AsyncStylesheetProcessor;
|
||||
if (ngPackagrMajorVersion >= 19) {
|
||||
const { StylesheetProcessor } = require('./v19+/stylesheet-processor');
|
||||
return StylesheetProcessor;
|
||||
}
|
||||
|
||||
if (ngPackagrVersion !== '17.2.0') {
|
||||
const { StylesheetProcessor } = require('./pre-v19/stylesheet-processor');
|
||||
return StylesheetProcessor;
|
||||
}
|
||||
|
||||
const {
|
||||
AsyncStylesheetProcessor,
|
||||
} = require('./pre-v19/stylesheet-processor');
|
||||
return AsyncStylesheetProcessor;
|
||||
},
|
||||
deps: [],
|
||||
};
|
||||
|
||||
@ -1,51 +1,5 @@
|
||||
import { logger } from '@nx/devkit';
|
||||
import { workspaceRoot } from '@nx/devkit';
|
||||
import { existsSync } from 'fs';
|
||||
import { join, relative } from 'path';
|
||||
|
||||
export interface TailwindSetup {
|
||||
tailwindConfigPath: string;
|
||||
tailwindPackagePath: string;
|
||||
}
|
||||
|
||||
export function getTailwindSetup(
|
||||
basePath: string,
|
||||
tailwindConfig?: string
|
||||
): TailwindSetup | undefined {
|
||||
let tailwindConfigPath = tailwindConfig;
|
||||
|
||||
if (!tailwindConfigPath) {
|
||||
tailwindConfigPath = getTailwindConfigPath(basePath, workspaceRoot);
|
||||
}
|
||||
|
||||
// Only load Tailwind CSS plugin if configuration file was found.
|
||||
if (!tailwindConfigPath) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let tailwindPackagePath: string | undefined;
|
||||
try {
|
||||
tailwindPackagePath = require.resolve('tailwindcss');
|
||||
} catch {
|
||||
const relativeTailwindConfigPath = relative(
|
||||
workspaceRoot,
|
||||
tailwindConfigPath
|
||||
);
|
||||
logger.warn(
|
||||
`Tailwind CSS configuration file found (${relativeTailwindConfigPath})` +
|
||||
` but the 'tailwindcss' package is not installed.` +
|
||||
` To enable Tailwind CSS, please install the 'tailwindcss' package.`
|
||||
);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!tailwindPackagePath) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return { tailwindConfigPath, tailwindPackagePath };
|
||||
}
|
||||
import { join } from 'path';
|
||||
|
||||
export function getTailwindConfigPath(
|
||||
projectRoot: string,
|
||||
@ -70,10 +24,3 @@ export function getTailwindConfigPath(
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getTailwindPostCssPlugin({
|
||||
tailwindConfigPath,
|
||||
tailwindPackagePath,
|
||||
}: TailwindSetup) {
|
||||
return require(tailwindPackagePath)({ config: tailwindConfigPath });
|
||||
}
|
||||
|
||||
@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Adapted from the original ng-packagr source.
|
||||
*
|
||||
* Changes made:
|
||||
* - Add the project root to the search directories.
|
||||
*/
|
||||
|
||||
import browserslist from 'browserslist';
|
||||
import { NgPackageEntryConfig } from 'ng-packagr/ng-entrypoint.schema';
|
||||
import { ComponentStylesheetBundler } from 'ng-packagr/lib/styles/component-stylesheets';
|
||||
import {
|
||||
generateSearchDirectories,
|
||||
getTailwindConfig,
|
||||
loadPostcssConfiguration,
|
||||
} from 'ng-packagr/lib/styles/postcss-configuration';
|
||||
import { workspaceRoot } from '@nx/devkit';
|
||||
|
||||
export enum CssUrl {
|
||||
inline = 'inline',
|
||||
none = 'none',
|
||||
}
|
||||
|
||||
export class StylesheetProcessor extends ComponentStylesheetBundler {
|
||||
constructor(
|
||||
protected readonly projectBasePath: string,
|
||||
protected readonly basePath: string,
|
||||
protected readonly cssUrl?: CssUrl,
|
||||
protected readonly includePaths?: string[],
|
||||
protected readonly sass?: NgPackageEntryConfig['lib']['sass'],
|
||||
protected readonly cacheDirectory?: string | false,
|
||||
protected readonly watch?: boolean
|
||||
) {
|
||||
// By default, browserslist defaults are too inclusive
|
||||
// https://github.com/browserslist/browserslist/blob/83764ea81ffaa39111c204b02c371afa44a4ff07/index.js#L516-L522
|
||||
// We change the default query to browsers that Angular support.
|
||||
// https://angular.io/guide/browser-support
|
||||
(browserslist.defaults as string[]) = [
|
||||
'last 2 Chrome versions',
|
||||
'last 1 Firefox version',
|
||||
'last 2 Edge major versions',
|
||||
'last 2 Safari major versions',
|
||||
'last 2 iOS major versions',
|
||||
'Firefox ESR',
|
||||
];
|
||||
|
||||
const browserslistData = browserslist(undefined, { path: basePath });
|
||||
let searchDirs = generateSearchDirectories([projectBasePath]);
|
||||
const postcssConfiguration = loadPostcssConfiguration(searchDirs);
|
||||
// (nx-specific): we support loading the TailwindCSS config from the root of the workspace
|
||||
searchDirs = generateSearchDirectories([projectBasePath, workspaceRoot]);
|
||||
|
||||
super(
|
||||
{
|
||||
cacheDirectory: cacheDirectory,
|
||||
postcssConfiguration: postcssConfiguration,
|
||||
tailwindConfiguration: postcssConfiguration
|
||||
? undefined
|
||||
: getTailwindConfig(searchDirs, projectBasePath),
|
||||
sass: sass as any,
|
||||
workspaceRoot: projectBasePath,
|
||||
cssUrl: cssUrl,
|
||||
target: transformSupportedBrowsersToTargets(browserslistData),
|
||||
includePaths: includePaths,
|
||||
},
|
||||
'css',
|
||||
watch
|
||||
);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
void super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
function transformSupportedBrowsersToTargets(
|
||||
supportedBrowsers: string[]
|
||||
): string[] {
|
||||
const transformed: string[] = [];
|
||||
|
||||
// https://esbuild.github.io/api/#target
|
||||
const esBuildSupportedBrowsers = new Set([
|
||||
'safari',
|
||||
'firefox',
|
||||
'edge',
|
||||
'chrome',
|
||||
'ios',
|
||||
]);
|
||||
|
||||
for (const browser of supportedBrowsers) {
|
||||
let [browserName, version] = browser.split(' ');
|
||||
|
||||
// browserslist uses the name `ios_saf` for iOS Safari whereas esbuild uses `ios`
|
||||
if (browserName === 'ios_saf') {
|
||||
browserName = 'ios';
|
||||
}
|
||||
|
||||
// browserslist uses ranges `15.2-15.3` versions but only the lowest is required
|
||||
// to perform minimum supported feature checks. esbuild also expects a single version.
|
||||
[version] = version.split('-');
|
||||
|
||||
if (esBuildSupportedBrowsers.has(browserName)) {
|
||||
if (browserName === 'safari' && version === 'tp') {
|
||||
// esbuild only supports numeric versions so `TP` is converted to a high number (999) since
|
||||
// a Technology Preview (TP) of Safari is assumed to support all currently known features.
|
||||
version = '999';
|
||||
}
|
||||
|
||||
transformed.push(browserName + version);
|
||||
}
|
||||
}
|
||||
|
||||
return transformed.length ? transformed : undefined;
|
||||
}
|
||||
@ -66,7 +66,7 @@ describe('addLinting generator', () => {
|
||||
|
||||
const { devDependencies } = readJson(tree, 'package.json');
|
||||
expect(devDependencies['@typescript-eslint/utils']).toMatchInlineSnapshot(
|
||||
`"^8.0.0"`
|
||||
`"^8.13.0"`
|
||||
);
|
||||
delete process.env.ESLINT_USE_FLAT_CONFIG;
|
||||
});
|
||||
|
||||
@ -25,6 +25,7 @@ exports[`app --minimal should skip "nx-welcome.component.ts" file and references
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: false,
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.css',
|
||||
})
|
||||
@ -85,6 +86,7 @@ exports[`app --minimal should skip "nx-welcome.component.ts" file and references
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: false,
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.css',
|
||||
})
|
||||
@ -126,7 +128,6 @@ exports[`app --minimal should skip "nx-welcome.component.ts" file and references
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [RouterModule],
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
@ -170,7 +171,6 @@ exports[`app --minimal should skip "nx-welcome.component.ts" file and references
|
||||
"import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [],
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
@ -243,7 +243,6 @@ import { RouterModule } from '@angular/router';
|
||||
import { NxWelcomeComponent } from './nx-welcome.component';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [NxWelcomeComponent, RouterModule],
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
@ -311,7 +310,6 @@ exports[`app --standalone should generate a standalone app correctly without rou
|
||||
import { NxWelcomeComponent } from './nx-welcome.component';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [NxWelcomeComponent, ],
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
@ -545,6 +543,7 @@ exports[`app format files should format files 2`] = `
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: false,
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.css',
|
||||
})
|
||||
@ -618,8 +617,8 @@ exports[`app nested should create project configs 1`] = `
|
||||
"type": "initial",
|
||||
},
|
||||
{
|
||||
"maximumError": "4kb",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "8kb",
|
||||
"maximumWarning": "4kb",
|
||||
"type": "anyComponentStyle",
|
||||
},
|
||||
],
|
||||
@ -734,8 +733,8 @@ exports[`app not nested should create project configs 1`] = `
|
||||
"type": "initial",
|
||||
},
|
||||
{
|
||||
"maximumError": "4kb",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "8kb",
|
||||
"maximumWarning": "4kb",
|
||||
"type": "anyComponentStyle",
|
||||
},
|
||||
],
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
|
||||
import { Tree, writeJson } from '@nx/devkit';
|
||||
import { Tree, updateProjectConfiguration, writeJson } from '@nx/devkit';
|
||||
import * as devkit from '@nx/devkit';
|
||||
import {
|
||||
NxJsonConfiguration,
|
||||
@ -253,6 +253,19 @@ describe('app', () => {
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should not set esModuleInterop when using the browser-esbuild builder', async () => {
|
||||
await generateApp(appTree, 'my-app', { bundler: 'webpack' });
|
||||
const project = readProjectConfiguration(appTree, 'my-app');
|
||||
project.targets.build.executor =
|
||||
'@angular-devkit/build-angular:browser-esbuild';
|
||||
updateProjectConfiguration(appTree, 'my-app', project);
|
||||
|
||||
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' });
|
||||
|
||||
@ -487,9 +500,14 @@ describe('app', () => {
|
||||
|
||||
it('should create Nx specific `nx-welcome.component.ts` file', async () => {
|
||||
await generateApp(appTree, 'my-dir/my-app');
|
||||
expect(
|
||||
appTree.read('my-dir/my-app/src/app/nx-welcome.component.ts', 'utf-8')
|
||||
).toContain('Hello there');
|
||||
|
||||
const nxWelcomeComponentText = appTree.read(
|
||||
'my-dir/my-app/src/app/nx-welcome.component.ts',
|
||||
'utf-8'
|
||||
);
|
||||
expect(nxWelcomeComponentText).not.toContain('standalone: true');
|
||||
expect(nxWelcomeComponentText).toContain('standalone: false');
|
||||
expect(nxWelcomeComponentText).toContain('Hello there');
|
||||
});
|
||||
|
||||
it('should update the AppComponent spec to target Nx content', async () => {
|
||||
@ -963,9 +981,12 @@ describe('app', () => {
|
||||
appTree.read('standalone/src/app/app.component.spec.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
expect(appTree.exists('standalone/src/app/app.module.ts')).toBeFalsy();
|
||||
expect(
|
||||
appTree.read('standalone/src/app/nx-welcome.component.ts', 'utf-8')
|
||||
).toContain('standalone: true');
|
||||
const nxWelcomeComponentText = appTree.read(
|
||||
'standalone/src/app/nx-welcome.component.ts',
|
||||
'utf-8'
|
||||
);
|
||||
expect(nxWelcomeComponentText).not.toContain('standalone: true');
|
||||
expect(nxWelcomeComponentText).not.toContain('standalone: false');
|
||||
});
|
||||
|
||||
it('should generate a standalone app correctly without routing', async () => {
|
||||
@ -987,9 +1008,12 @@ describe('app', () => {
|
||||
appTree.read('standalone/src/app/app.component.spec.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
expect(appTree.exists('standalone/src/app/app.module.ts')).toBeFalsy();
|
||||
expect(
|
||||
appTree.read('standalone/src/app/nx-welcome.component.ts', 'utf-8')
|
||||
).toContain('standalone: true');
|
||||
const nxWelcomeComponentText = appTree.read(
|
||||
'standalone/src/app/nx-welcome.component.ts',
|
||||
'utf-8'
|
||||
);
|
||||
expect(nxWelcomeComponentText).not.toContain('standalone: true');
|
||||
expect(nxWelcomeComponentText).not.toContain('standalone: false');
|
||||
});
|
||||
|
||||
it('should should not use event coalescing in versions lower than v18', async () => {
|
||||
@ -1177,8 +1201,8 @@ describe('app', () => {
|
||||
"type": "initial",
|
||||
},
|
||||
{
|
||||
"maximumError": "4kb",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "8kb",
|
||||
"maximumWarning": "4kb",
|
||||
"type": "anyComponentStyle",
|
||||
},
|
||||
]
|
||||
@ -1212,8 +1236,8 @@ describe('app', () => {
|
||||
"type": "initial",
|
||||
},
|
||||
{
|
||||
"maximumError": "4kb",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "8kb",
|
||||
"maximumWarning": "4kb",
|
||||
"type": "anyComponentStyle",
|
||||
},
|
||||
]
|
||||
@ -1236,7 +1260,7 @@ describe('app', () => {
|
||||
await generateApp(appTree, 'app1', { ssr: true });
|
||||
|
||||
expect(appTree.exists('app1/src/main.server.ts')).toBe(true);
|
||||
expect(appTree.exists('app1/server.ts')).toBe(true);
|
||||
expect(appTree.exists('app1/src/server.ts')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1247,7 +1271,7 @@ describe('app', () => {
|
||||
...json,
|
||||
dependencies: {
|
||||
...json.dependencies,
|
||||
'@angular/core': '~16.2.0',
|
||||
'@angular/core': '~17.2.0',
|
||||
},
|
||||
}));
|
||||
});
|
||||
@ -1257,13 +1281,13 @@ describe('app', () => {
|
||||
|
||||
const { devDependencies } = readJson(appTree, 'package.json');
|
||||
expect(devDependencies['@angular-devkit/build-angular']).toEqual(
|
||||
backwardCompatibleVersions.angularV16.angularDevkitVersion
|
||||
backwardCompatibleVersions.angularV17.angularDevkitVersion
|
||||
);
|
||||
expect(devDependencies['@angular-devkit/schematics']).toEqual(
|
||||
backwardCompatibleVersions.angularV16.angularDevkitVersion
|
||||
backwardCompatibleVersions.angularV17.angularDevkitVersion
|
||||
);
|
||||
expect(devDependencies['@schematics/angular']).toEqual(
|
||||
backwardCompatibleVersions.angularV16.angularDevkitVersion
|
||||
backwardCompatibleVersions.angularV17.angularDevkitVersion
|
||||
);
|
||||
});
|
||||
|
||||
@ -1311,46 +1335,6 @@ describe('app', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
it('should disable modern class fields behavior', async () => {
|
||||
await generateApp(appTree, 'my-app');
|
||||
|
||||
|
||||
@ -98,6 +98,7 @@ export async function applicationGenerator(
|
||||
project: options.name,
|
||||
standalone: options.standalone,
|
||||
skipPackageJson: options.skipPackageJson,
|
||||
serverRouting: options.serverRouting,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import { Component<% if(viewEncapsulation) { %>, ViewEncapsulation<% } %> } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: '<%= rootSelector %>',<% if(!inlineTemplate) { %>
|
||||
selector: '<%= rootSelector %>',<% if (setStandaloneFalse) { %>
|
||||
standalone: false,<% } if(!inlineTemplate) { %>
|
||||
templateUrl: './app.component.html',<% } else { %>
|
||||
template: `<% if(minimal) { %><h1>Welcome <%= appName %></h1><% } else { %><<%= nxWelcomeSelector %>></<%= nxWelcomeSelector %>><% } %> <% if(routing) { %><router-outlet></router-outlet><% } %>`,<% } %><% if (angularMajorVersion < 17) { if(!inlineStyle) { %>
|
||||
styleUrls: ['./app.component.<%= style %>'],<% } else { %>
|
||||
styles: [``],<% } %><% } else { if(!inlineStyle) { %>
|
||||
template: `<% if(minimal) { %><h1>Welcome <%= appName %></h1><% } else { %><<%= nxWelcomeSelector %>></<%= nxWelcomeSelector %>><% } %> <% if(routing) { %><router-outlet></router-outlet><% } %>`,<% } %><% if(!inlineStyle) { %>
|
||||
styleUrl: './app.component.<%= style %>',<% } else { %>
|
||||
styles: ``,<% } %><% } %><% if(viewEncapsulation) { %>
|
||||
styles: ``,<% } %><% if(viewEncapsulation) { %>
|
||||
encapsulation: ViewEncapsulation.<%= viewEncapsulation %>,<% } %>
|
||||
})
|
||||
export class AppComponent {<% if(!minimal) { %>
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { Component, ViewEncapsulation } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: '<%= nxWelcomeSelector %>',
|
||||
selector: '<%= nxWelcomeSelector %>',<% if (setStandaloneFalse) { %>
|
||||
standalone: false,<% } %>
|
||||
template: `
|
||||
<!--
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
|
||||
@ -2,8 +2,8 @@ import { Component, ViewEncapsulation } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: '<%= nxWelcomeSelector %>',
|
||||
standalone: true,
|
||||
selector: '<%= nxWelcomeSelector %>',<% if (setStandaloneTrue) { %>
|
||||
standalone: true,<% } %>
|
||||
imports: [CommonModule],
|
||||
template: `
|
||||
<!--
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { Component, ViewEncapsulation } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: '<%= nxWelcomeSelector %>',
|
||||
selector: '<%= nxWelcomeSelector %>',<% if (setStandaloneFalse) { %>
|
||||
standalone: false,<% } %>
|
||||
template: `
|
||||
<!--
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
|
||||
@ -2,8 +2,8 @@ import { Component, ViewEncapsulation } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: '<%= nxWelcomeSelector %>',
|
||||
standalone: true,
|
||||
selector: '<%= nxWelcomeSelector %>',<% if (setStandaloneTrue) { %>
|
||||
standalone: true,<% } %>
|
||||
imports: [CommonModule],
|
||||
template: `
|
||||
<!--
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { Component, ViewEncapsulation } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: '<%= nxWelcomeSelector %>',
|
||||
selector: '<%= nxWelcomeSelector %>',<% if (setStandaloneFalse) { %>
|
||||
standalone: false,<% } %>
|
||||
template: `
|
||||
<!--
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
|
||||
@ -2,8 +2,8 @@ import { Component, ViewEncapsulation } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: '<%= nxWelcomeSelector %>',
|
||||
standalone: true,
|
||||
selector: '<%= nxWelcomeSelector %>',<% if (setStandaloneTrue) { %>
|
||||
standalone: true,<% } %>
|
||||
imports: [CommonModule],
|
||||
template: `
|
||||
<!--
|
||||
|
||||
@ -2,16 +2,14 @@ import { Component<% if(viewEncapsulation) { %>, ViewEncapsulation<% } %> } from
|
||||
import { RouterModule } from '@angular/router';<% } %><% if(!minimal) { %>
|
||||
import { NxWelcomeComponent } from './nx-welcome.component';<% } %>
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@Component({<% if (setStandaloneTrue) { %>
|
||||
standalone: true,<% } %>
|
||||
imports: [<% if(!minimal) { %>NxWelcomeComponent, <% } %><% if(routing) { %>RouterModule<% } %>],
|
||||
selector: '<%= rootSelector %>',<% if(!inlineTemplate) { %>
|
||||
templateUrl: './app.component.html',<% } else { %>
|
||||
template: `<% if(minimal) { %><h1>Welcome <%= appName %></h1><% } else { %><<%= nxWelcomeSelector %>></<%= nxWelcomeSelector %>><% } %> <% if(routing) { %><router-outlet></router-outlet><% } %>`,<% } %><% if (angularMajorVersion < 17) { if(!inlineStyle) { %>
|
||||
styleUrls: ['./app.component.<%= style %>'],<% } else { %>
|
||||
styles: [``],<% } %><% } else { if(!inlineStyle) { %>
|
||||
template: `<% if(minimal) { %><h1>Welcome <%= appName %></h1><% } else { %><<%= nxWelcomeSelector %>></<%= nxWelcomeSelector %>><% } %> <% if(routing) { %><router-outlet></router-outlet><% } %>`,<% } %><% if(!inlineStyle) { %>
|
||||
styleUrl: './app.component.<%= style %>',<% } else { %>
|
||||
styles: ``,<% } %><% } %><% if(viewEncapsulation) { %>
|
||||
styles: ``,<% } %><% if(viewEncapsulation) { %>
|
||||
encapsulation: ViewEncapsulation.<%= viewEncapsulation %>,<% } %>
|
||||
})
|
||||
export class AppComponent {<% if(!minimal) { %>
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import { Tree } from '@nx/devkit';
|
||||
import type { NormalizedSchema } from './normalized-schema';
|
||||
import {
|
||||
addDependenciesToPackageJson,
|
||||
joinPathFragments,
|
||||
readProjectConfiguration,
|
||||
updateProjectConfiguration,
|
||||
type Tree,
|
||||
} from '@nx/devkit';
|
||||
import { nxVersion } from '../../../utils/versions';
|
||||
import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
|
||||
import type { NormalizedSchema } from './normalized-schema';
|
||||
|
||||
export function addServeStaticTarget(
|
||||
tree: Tree,
|
||||
@ -27,9 +26,7 @@ function addFileServerTarget(
|
||||
addDependenciesToPackageJson(tree, {}, { '@nx/web': nxVersion });
|
||||
}
|
||||
|
||||
const { major: angularMajorVersion } = getInstalledAngularVersionInfo(tree);
|
||||
const isUsingApplicationBuilder =
|
||||
angularMajorVersion >= 17 && options.bundler === 'esbuild';
|
||||
const isUsingApplicationBuilder = options.bundler === 'esbuild';
|
||||
|
||||
const projectConfig = readProjectConfiguration(tree, options.name);
|
||||
projectConfig.targets[targetName] = {
|
||||
|
||||
@ -19,8 +19,7 @@ export async function createFiles(
|
||||
) {
|
||||
const { major: angularMajorVersion, version: angularVersion } =
|
||||
getInstalledAngularVersionInfo(tree);
|
||||
const isUsingApplicationBuilder =
|
||||
angularMajorVersion >= 17 && options.bundler === 'esbuild';
|
||||
const isUsingApplicationBuilder = options.bundler === 'esbuild';
|
||||
const disableModernClassFieldsBehavior = lt(angularVersion, '18.1.0-rc.0');
|
||||
|
||||
const rootSelector = `${options.prefix}-root`;
|
||||
@ -55,6 +54,9 @@ export async function createFiles(
|
||||
disableModernClassFieldsBehavior,
|
||||
useEventCoalescing: angularMajorVersion >= 18,
|
||||
useRouterTestingModule: angularMajorVersion < 18,
|
||||
// Angular v19 or higher defaults to true, while v18 or lower defaults to false
|
||||
setStandaloneFalse: angularMajorVersion >= 19,
|
||||
setStandaloneTrue: angularMajorVersion < 19,
|
||||
connectCloudUrl,
|
||||
tutorialUrl: options.standalone
|
||||
? 'https://nx.dev/getting-started/tutorials/angular-standalone-tutorial?utm_source=nx-project'
|
||||
|
||||
@ -10,27 +10,20 @@ export function createProject(tree: Tree, options: NormalizedSchema) {
|
||||
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';
|
||||
: '@angular-devkit/build-angular:application';
|
||||
const buildMainOptionName =
|
||||
angularMajorVersion >= 17 && options.bundler === 'esbuild'
|
||||
? 'browser'
|
||||
: 'main';
|
||||
options.bundler === 'esbuild' ? 'browser' : 'main';
|
||||
|
||||
addBuildTargetDefaults(tree, buildExecutor);
|
||||
|
||||
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',
|
||||
maximumWarning: '4kb',
|
||||
maximumError: '8kb',
|
||||
},
|
||||
];
|
||||
} else {
|
||||
@ -43,7 +36,6 @@ export function createProject(tree: Tree, options: NormalizedSchema) {
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
const inlineStyleLanguage =
|
||||
options?.style !== 'css' ? options.style : undefined;
|
||||
@ -109,10 +101,10 @@ export function createProject(tree: Tree, options: NormalizedSchema) {
|
||||
: undefined,
|
||||
configurations: {
|
||||
production: {
|
||||
[buildTargetOptionName]: `${options.name}:build:production`,
|
||||
buildTarget: `${options.name}:build:production`,
|
||||
},
|
||||
development: {
|
||||
[buildTargetOptionName]: `${options.name}:build:development`,
|
||||
buildTarget: `${options.name}:build:development`,
|
||||
},
|
||||
},
|
||||
defaultConfiguration: 'development',
|
||||
@ -120,7 +112,7 @@ export function createProject(tree: Tree, options: NormalizedSchema) {
|
||||
'extract-i18n': {
|
||||
executor: '@angular-devkit/build-angular:extract-i18n',
|
||||
options: {
|
||||
[buildTargetOptionName]: `${options.name}:build`,
|
||||
buildTarget: `${options.name}:build`,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { joinPathFragments, readNxJson, type Tree } from '@nx/devkit';
|
||||
import { joinPathFragments, type Tree } from '@nx/devkit';
|
||||
import {
|
||||
determineProjectNameAndRootOptions,
|
||||
ensureProjectName,
|
||||
@ -7,7 +7,6 @@ import { Linter } from '@nx/eslint';
|
||||
import { E2eTestRunner, UnitTestRunner } from '../../../utils/test-runners';
|
||||
import type { Schema } from '../schema';
|
||||
import type { NormalizedSchema } from './normalized-schema';
|
||||
import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
|
||||
|
||||
export async function normalizeOptions(
|
||||
host: Tree,
|
||||
@ -30,11 +29,7 @@ export async function normalizeOptions(
|
||||
? options.tags.split(',').map((s) => s.trim())
|
||||
: [];
|
||||
|
||||
let bundler = options.bundler;
|
||||
if (!bundler) {
|
||||
const { major: angularMajorVersion } = getInstalledAngularVersionInfo(host);
|
||||
bundler = angularMajorVersion >= 17 ? 'esbuild' : 'webpack';
|
||||
}
|
||||
const bundler = options.bundler ?? 'esbuild';
|
||||
|
||||
// Set defaults and then overwrite with user options
|
||||
return {
|
||||
|
||||
@ -29,5 +29,6 @@ export interface Schema {
|
||||
minimal?: boolean;
|
||||
bundler?: 'webpack' | 'esbuild';
|
||||
ssr?: boolean;
|
||||
serverRouting?: boolean;
|
||||
nxCloudToken?: string;
|
||||
}
|
||||
|
||||
@ -169,9 +169,10 @@
|
||||
"default": false
|
||||
},
|
||||
"bundler": {
|
||||
"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._",
|
||||
"description": "Bundler to use to build the application.",
|
||||
"type": "string",
|
||||
"enum": ["webpack", "esbuild"],
|
||||
"enum": ["esbuild", "webpack"],
|
||||
"default": "esbuild",
|
||||
"x-prompt": "Which bundler do you want to use to build the application?",
|
||||
"x-priority": "important"
|
||||
},
|
||||
@ -180,6 +181,10 @@
|
||||
"type": "boolean",
|
||||
"x-prompt": "Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)?",
|
||||
"default": false
|
||||
},
|
||||
"serverRouting": {
|
||||
"description": "Creates a server application using the Server Routing and App Engine APIs (Developer Preview). _Note: this is only supported in Angular versions >= 19.0.0_.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
@ -18,6 +18,7 @@ exports[`component Generator compat should inline styles when --inline-style=tru
|
||||
|
||||
@Component({
|
||||
selector: 'example',
|
||||
standalone: false,
|
||||
templateUrl: './example.component.html',
|
||||
styles: \`\`
|
||||
})
|
||||
@ -30,6 +31,7 @@ exports[`component Generator secondary entry points should create the component
|
||||
|
||||
@Component({
|
||||
selector: 'example',
|
||||
standalone: false,
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css'
|
||||
})
|
||||
@ -47,6 +49,7 @@ exports[`component Generator should create component files correctly: component
|
||||
|
||||
@Component({
|
||||
selector: 'example',
|
||||
standalone: false,
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css',
|
||||
})
|
||||
@ -96,6 +99,7 @@ exports[`component Generator should create the component correctly and export it
|
||||
|
||||
@Component({
|
||||
selector: 'example',
|
||||
standalone: false,
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css'
|
||||
})
|
||||
@ -114,7 +118,6 @@ import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'example',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css'
|
||||
@ -128,6 +131,7 @@ exports[`component Generator should create the component correctly and not expor
|
||||
|
||||
@Component({
|
||||
selector: 'example',
|
||||
standalone: false,
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css'
|
||||
})
|
||||
@ -141,7 +145,6 @@ import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'example',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css'
|
||||
@ -155,6 +158,7 @@ exports[`component Generator should create the component correctly and not expor
|
||||
|
||||
@Component({
|
||||
selector: 'example',
|
||||
standalone: false,
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css'
|
||||
})
|
||||
@ -167,6 +171,7 @@ exports[`component Generator should create the component correctly but not expor
|
||||
|
||||
@Component({
|
||||
selector: 'example',
|
||||
standalone: false,
|
||||
templateUrl: './example.component.html',
|
||||
styleUrl: './example.component.css'
|
||||
})
|
||||
@ -179,6 +184,7 @@ exports[`component Generator should inline styles when --inline-style=true 1`] =
|
||||
|
||||
@Component({
|
||||
selector: 'example',
|
||||
standalone: false,
|
||||
templateUrl: './example.component.html',
|
||||
styles: \`\`
|
||||
})
|
||||
@ -191,6 +197,7 @@ exports[`component Generator should inline template when --inline-template=true
|
||||
|
||||
@Component({
|
||||
selector: 'example',
|
||||
standalone: false,
|
||||
template: \`<p>example works!</p>\`,
|
||||
styleUrl: './example.component.css'
|
||||
})
|
||||
|
||||
@ -55,6 +55,28 @@ describe('component Generator', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should export the component as default when exportDefault is true', async () => {
|
||||
const tree = createTreeWithEmptyWorkspace({});
|
||||
addProjectConfiguration(tree, 'lib1', {
|
||||
projectType: 'library',
|
||||
sourceRoot: 'libs/lib1/src',
|
||||
root: 'libs/lib1',
|
||||
});
|
||||
tree.write('libs/lib1/src/index.ts', '');
|
||||
|
||||
await componentGenerator(tree, {
|
||||
path: 'libs/lib1/src/lib/example/example',
|
||||
exportDefault: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read('libs/lib1/src/lib/example/example.component.ts', 'utf-8')
|
||||
).toContain('export default class ExampleComponent {}');
|
||||
expect(
|
||||
tree.read('libs/lib1/src/lib/example/example.component.spec.ts', 'utf-8')
|
||||
).toContain(`import ExampleComponent from './example.component';`);
|
||||
});
|
||||
|
||||
it('should not generate test file when --skip-tests=true', async () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||
@ -205,6 +227,7 @@ describe('component Generator', () => {
|
||||
|
||||
@Component({
|
||||
selector: 'example',
|
||||
standalone: false,
|
||||
templateUrl: './example.component.html'
|
||||
})
|
||||
export class ExampleComponent {}
|
||||
@ -491,6 +514,22 @@ describe('component Generator', () => {
|
||||
expect(indexSource).toBe('');
|
||||
});
|
||||
|
||||
it('should error when the class name is invalid', async () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
addProjectConfiguration(tree, 'lib1', {
|
||||
projectType: 'library',
|
||||
sourceRoot: 'libs/lib1/src',
|
||||
root: 'libs/lib1',
|
||||
});
|
||||
|
||||
await expect(
|
||||
componentGenerator(tree, {
|
||||
path: 'libs/lib1/src/lib/example/example',
|
||||
name: '404',
|
||||
})
|
||||
).rejects.toThrow('Class name "404Component" is invalid.');
|
||||
});
|
||||
|
||||
describe('--module', () => {
|
||||
it.each([
|
||||
'./lib.module.ts',
|
||||
@ -731,10 +770,10 @@ export class LibModule {}
|
||||
it('should error when name starts with a digit', async () => {
|
||||
await expect(
|
||||
componentGenerator(tree, {
|
||||
path: 'lib1/src/lib/1-one/1-one',
|
||||
prefix: 'foo',
|
||||
path: 'lib1/src/lib/one/one',
|
||||
prefix: '1',
|
||||
})
|
||||
).rejects.toThrow('The selector "foo-1-one" is invalid.');
|
||||
).rejects.toThrow('The selector "1-one" is invalid.');
|
||||
});
|
||||
|
||||
it('should allow dash in selector before a number', async () => {
|
||||
|
||||
@ -22,6 +22,7 @@ export async function componentGenerator(tree: Tree, rawOptions: Schema) {
|
||||
name: options.name,
|
||||
fileName: options.fileName,
|
||||
symbolName: options.symbolName,
|
||||
exportDefault: options.exportDefault,
|
||||
style: options.style,
|
||||
inlineStyle: options.inlineStyle,
|
||||
inlineTemplate: options.inlineTemplate,
|
||||
@ -31,6 +32,10 @@ export async function componentGenerator(tree: Tree, rawOptions: Schema) {
|
||||
viewEncapsulation: options.viewEncapsulation,
|
||||
displayBlock: options.displayBlock,
|
||||
selector: options.selector,
|
||||
// Angular v19 or higher defaults to true, while v18 or lower defaults to false
|
||||
setStandalone:
|
||||
(angularMajorVersion >= 19 && !options.standalone) ||
|
||||
(angularMajorVersion < 19 && options.standalone),
|
||||
angularMajorVersion,
|
||||
tpl: '',
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { <%= symbolName %> } from './<%= fileName %>';
|
||||
import <% if (exportDefault) { %><%= symbolName %><% } else { %>{ <%= symbolName %> }<% } %> from './<%= fileName %>';
|
||||
|
||||
describe('<%= symbolName %>', () => {
|
||||
let component: <%= symbolName %>;
|
||||
|
||||
@ -2,26 +2,18 @@ import { <% if(changeDetection !== 'Default') { %>ChangeDetectionStrategy, <% }%
|
||||
import { CommonModule } from '@angular/common';<% } %>
|
||||
|
||||
@Component({<% if(!skipSelector) {%>
|
||||
selector: '<%= selector %>',<%}%><% if(standalone) {%>
|
||||
standalone: true,
|
||||
selector: '<%= selector %>',<%}%><% if (setStandalone) { %>
|
||||
standalone: <%= standalone %>,<% } %><% if(standalone) { %>
|
||||
imports: [CommonModule],<%}%><% if(inlineTemplate) { %>
|
||||
template: `<p><%= name %> works!</p>`<% } else { %>
|
||||
templateUrl: './<%= fileName %>.html'<% } if (angularMajorVersion < 17) { if(inlineStyle) { %>,
|
||||
styles: [<% if(displayBlock){ %>
|
||||
`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
`<% } %>
|
||||
]<% } else if (style !== 'none') { %>,
|
||||
styleUrls: ['./<%= fileName %>.<%= style %>']<% } %><% } else { if(inlineStyle) { %>,
|
||||
templateUrl: './<%= fileName %>.html'<% } if(inlineStyle) { %>,
|
||||
styles: `<% if(displayBlock){ %>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
<% } %>`<% } else if (style !== 'none') { %>,
|
||||
styleUrl: './<%= fileName %>.<%= style %>'<% } %><% } %><% if(!!viewEncapsulation) { %>,
|
||||
styleUrl: './<%= fileName %>.<%= style %>'<% } %><% if(!!viewEncapsulation) { %>,
|
||||
encapsulation: ViewEncapsulation.<%= viewEncapsulation %><% } if (changeDetection !== 'Default') { %>,
|
||||
changeDetection: ChangeDetectionStrategy.<%= changeDetection %><% } %>
|
||||
})
|
||||
export class <%= symbolName %> {}
|
||||
export <% if (exportDefault) {%>default <%}%>class <%= symbolName %> {}
|
||||
|
||||
@ -3,6 +3,7 @@ import { names, readProjectConfiguration } from '@nx/devkit';
|
||||
import { determineArtifactNameAndDirectoryOptions } from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
|
||||
import type { AngularProjectConfiguration } from '../../../utils/types';
|
||||
import { buildSelector, validateHtmlSelector } from '../../utils/selector';
|
||||
import { validateClassName } from '../../utils/validations';
|
||||
import type { NormalizedSchema, Schema } from '../schema';
|
||||
|
||||
export async function normalizeOptions(
|
||||
@ -25,6 +26,7 @@ export async function normalizeOptions(
|
||||
const { className } = names(name);
|
||||
const { className: suffixClassName } = names(options.type);
|
||||
const symbolName = `${className}${suffixClassName}`;
|
||||
validateClassName(symbolName);
|
||||
|
||||
const { prefix, root, sourceRoot } = readProjectConfiguration(
|
||||
tree,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user