feat(angular): support angular 18.0.0 (#22509)

<!-- 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` -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #25284
This commit is contained in:
Leosvel Pérez Espinosa 2024-05-23 17:50:04 +02:00 committed by GitHub
parent 881adfb185
commit 37f02f7e6b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
96 changed files with 3744 additions and 1222 deletions

View File

@ -63,7 +63,7 @@
"type": "executor" "type": "executor"
}, },
"/nx-api/angular/executors/ng-packagr-lite": { "/nx-api/angular/executors/ng-packagr-lite": {
"description": "Builds an Angular library with support for incremental builds.\n\nThis executor is meant to be used with buildable libraries in an incremental build scenario. It is similar to the `@nx/angular:package` executor but with some key differences:\n- For supported Angular versions lower than v16, it doesn't run `ngcc` automatically (`ngcc` is no longer available from Angular v16 onwards, for lower versions, it needs to be run separately beforehand if needed, this can be done in a `postinstall` hook on `package.json`).\n- It only produces ESM2022 bundles (ESM2020 for supported Angular versions lower than v16).", "description": "Builds an Angular library with support for incremental builds.\n\nThis executor is meant to be used with buildable libraries in an incremental build scenario. It is similar to the `@nx/angular:package` executor but it only produces ESM2022 bundles.",
"file": "generated/packages/angular/executors/ng-packagr-lite.json", "file": "generated/packages/angular/executors/ng-packagr-lite.json",
"hidden": false, "hidden": false,
"name": "ng-packagr-lite", "name": "ng-packagr-lite",

View File

@ -58,7 +58,7 @@
"type": "executor" "type": "executor"
}, },
{ {
"description": "Builds an Angular library with support for incremental builds.\n\nThis executor is meant to be used with buildable libraries in an incremental build scenario. It is similar to the `@nx/angular:package` executor but with some key differences:\n- For supported Angular versions lower than v16, it doesn't run `ngcc` automatically (`ngcc` is no longer available from Angular v16 onwards, for lower versions, it needs to be run separately beforehand if needed, this can be done in a `postinstall` hook on `package.json`).\n- It only produces ESM2022 bundles (ESM2020 for supported Angular versions lower than v16).", "description": "Builds an Angular library with support for incremental builds.\n\nThis executor is meant to be used with buildable libraries in an incremental build scenario. It is similar to the `@nx/angular:package` executor but it only produces ESM2022 bundles.",
"file": "generated/packages/angular/executors/ng-packagr-lite.json", "file": "generated/packages/angular/executors/ng-packagr-lite.json",
"hidden": false, "hidden": false,
"name": "ng-packagr-lite", "name": "ng-packagr-lite",

View File

@ -38,11 +38,12 @@
}, },
"output": { "output": {
"type": "string", "type": "string",
"default": "",
"description": "Absolute path within the output." "description": "Absolute path within the output."
} }
}, },
"additionalProperties": false, "additionalProperties": false,
"required": ["glob", "input", "output"] "required": ["glob", "input"]
}, },
{ "type": "string" } { "type": "string" }
] ]
@ -663,11 +664,12 @@
}, },
"output": { "output": {
"type": "string", "type": "string",
"default": "",
"description": "Absolute path within the output." "description": "Absolute path within the output."
} }
}, },
"additionalProperties": false, "additionalProperties": false,
"required": ["glob", "input", "output"] "required": ["glob", "input"]
}, },
{ "type": "string" } { "type": "string" }
] ]

View File

@ -38,11 +38,12 @@
}, },
"output": { "output": {
"type": "string", "type": "string",
"default": "",
"description": "Absolute path within the output." "description": "Absolute path within the output."
} }
}, },
"additionalProperties": false, "additionalProperties": false,
"required": ["glob", "input", "output"] "required": ["glob", "input"]
}, },
{ "type": "string" } { "type": "string" }
] ]
@ -572,11 +573,12 @@
}, },
"output": { "output": {
"type": "string", "type": "string",
"default": "",
"description": "Absolute path within the output." "description": "Absolute path within the output."
} }
}, },
"additionalProperties": false, "additionalProperties": false,
"required": ["glob", "input", "output"] "required": ["glob", "input"]
}, },
{ "type": "string" } { "type": "string" }
] ]

View File

@ -6,7 +6,7 @@
"outputCapture": "direct-nodejs", "outputCapture": "direct-nodejs",
"$schema": "https://json-schema.org/schema", "$schema": "https://json-schema.org/schema",
"title": "ng-packagr Target", "title": "ng-packagr Target",
"description": "Builds an Angular library with support for incremental builds.\n\nThis executor is meant to be used with buildable libraries in an incremental build scenario. It is similar to the `@nx/angular:package` executor but with some key differences:\n- For supported Angular versions lower than v16, it doesn't run `ngcc` automatically (`ngcc` is no longer available from Angular v16 onwards, for lower versions, it needs to be run separately beforehand if needed, this can be done in a `postinstall` hook on `package.json`).\n- It only produces ESM2022 bundles (ESM2020 for supported Angular versions lower than v16).", "description": "Builds an Angular library with support for incremental builds.\n\nThis executor is meant to be used with buildable libraries in an incremental build scenario. It is similar to the `@nx/angular:package` executor but it only produces ESM2022 bundles.",
"cli": "nx", "cli": "nx",
"type": "object", "type": "object",
"presets": [ "presets": [
@ -44,7 +44,7 @@
"additionalProperties": false, "additionalProperties": false,
"required": ["project"] "required": ["project"]
}, },
"description": "Builds an Angular library with support for incremental builds.\n\nThis executor is meant to be used with buildable libraries in an incremental build scenario. It is similar to the `@nx/angular:package` executor but with some key differences:\n- For supported Angular versions lower than v16, it doesn't run `ngcc` automatically (`ngcc` is no longer available from Angular v16 onwards, for lower versions, it needs to be run separately beforehand if needed, this can be done in a `postinstall` hook on `package.json`).\n- It only produces ESM2022 bundles (ESM2020 for supported Angular versions lower than v16).", "description": "Builds an Angular library with support for incremental builds.\n\nThis executor is meant to be used with buildable libraries in an incremental build scenario. It is similar to the `@nx/angular:package` executor but it only produces ESM2022 bundles.",
"aliases": [], "aliases": [],
"hidden": false, "hidden": false,
"path": "/packages/angular/src/executors/ng-packagr-lite/schema.json", "path": "/packages/angular/src/executors/ng-packagr-lite/schema.json",

View File

@ -56,11 +56,12 @@
}, },
"output": { "output": {
"type": "string", "type": "string",
"default": "",
"description": "Absolute path within the output." "description": "Absolute path within the output."
} }
}, },
"additionalProperties": false, "additionalProperties": false,
"required": ["glob", "input", "output"] "required": ["glob", "input"]
}, },
{ "type": "string" } { "type": "string" }
] ]
@ -592,11 +593,12 @@
}, },
"output": { "output": {
"type": "string", "type": "string",
"default": "",
"description": "Absolute path within the output." "description": "Absolute path within the output."
} }
}, },
"additionalProperties": false, "additionalProperties": false,
"required": ["glob", "input", "output"] "required": ["glob", "input"]
}, },
{ "type": "string" } { "type": "string" }
] ]

View File

@ -11,7 +11,7 @@
"properties": { "properties": {
"assets": { "assets": {
"type": "array", "type": "array",
"description": "List of static application assets. _Note: only supported in Angular versions >= 15.1.0_", "description": "List of static application assets.",
"default": [], "default": [],
"items": { "items": {
"oneOf": [ "oneOf": [
@ -38,11 +38,12 @@
}, },
"output": { "output": {
"type": "string", "type": "string",
"default": "",
"description": "Absolute path within the output." "description": "Absolute path within the output."
} }
}, },
"additionalProperties": false, "additionalProperties": false,
"required": ["glob", "input", "output"] "required": ["glob", "input"]
}, },
{ "type": "string" } { "type": "string" }
] ]
@ -186,7 +187,7 @@
}, },
"vendorChunk": { "vendorChunk": {
"type": "boolean", "type": "boolean",
"description": "Generate a separate bundle containing only vendor libraries. This option should only be used for development to reduce the incremental compilation time. _Note: supported in Angular versions >= 15.1.0_", "description": "Generate a separate bundle containing only vendor libraries. This option should only be used for development to reduce the incremental compilation time.",
"default": false "default": false
}, },
"verbose": { "verbose": {
@ -248,7 +249,7 @@
}, },
"buildOptimizer": { "buildOptimizer": {
"type": "boolean", "type": "boolean",
"description": "Enables advanced build optimizations. _Note: only supported in Angular versions >= 16.0.0_.", "description": "Enables advanced build optimizations.",
"default": true "default": true
}, },
"namedChunks": { "namedChunks": {
@ -323,11 +324,12 @@
}, },
"output": { "output": {
"type": "string", "type": "string",
"default": "",
"description": "Absolute path within the output." "description": "Absolute path within the output."
} }
}, },
"additionalProperties": false, "additionalProperties": false,
"required": ["glob", "input", "output"] "required": ["glob", "input"]
}, },
{ "type": "string" } { "type": "string" }
] ]

View File

@ -19,8 +19,9 @@
"appId": { "appId": {
"type": "string", "type": "string",
"format": "html-selector", "format": "html-selector",
"description": "The `appId` to use with `withServerTransition`. _Note: This is only used in Angular versions <16.0.0. It's deprecated since Angular 16 and not supported since Angular 17._", "description": "The `appId` to use with `withServerTransition`.",
"default": "serverApp" "default": "serverApp",
"x-deprecated": "This is deprecated and ignored since Angular 16 and not supported since Angular 17."
}, },
"main": { "main": {
"type": "string", "type": "string",
@ -55,7 +56,7 @@
}, },
"hydration": { "hydration": {
"type": "boolean", "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`. _Note: This is only supported in Angular versions >= 16.0.0_." "description": "Set up Hydration for the SSR application. It defaults to `true` for Angular versions >= 17.0.0. Otherwise, it defaults to `false`."
}, },
"skipFormat": { "skipFormat": {
"type": "boolean", "type": "boolean",

View File

@ -42,7 +42,7 @@ describe('Move Angular Project', () => {
expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.json`); expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.json`);
expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.spec.json`); expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.spec.json`);
expect(moveOutput).toContain(`CREATE ${newPath}/.eslintrc.json`); expect(moveOutput).toContain(`CREATE ${newPath}/.eslintrc.json`);
expect(moveOutput).toContain(`CREATE ${newPath}/src/favicon.ico`); expect(moveOutput).toContain(`CREATE ${newPath}/public/favicon.ico`);
expect(moveOutput).toContain(`CREATE ${newPath}/src/index.html`); expect(moveOutput).toContain(`CREATE ${newPath}/src/index.html`);
expect(moveOutput).toContain(`CREATE ${newPath}/src/main.ts`); expect(moveOutput).toContain(`CREATE ${newPath}/src/main.ts`);
expect(moveOutput).toContain(`CREATE ${newPath}/src/styles.css`); expect(moveOutput).toContain(`CREATE ${newPath}/src/styles.css`);
@ -52,7 +52,6 @@ describe('Move Angular Project', () => {
); );
expect(moveOutput).toContain(`CREATE ${newPath}/src/app/app.component.ts`); expect(moveOutput).toContain(`CREATE ${newPath}/src/app/app.component.ts`);
expect(moveOutput).toContain(`CREATE ${newPath}/src/app/app.config.ts`); expect(moveOutput).toContain(`CREATE ${newPath}/src/app/app.config.ts`);
expect(moveOutput).toContain(`CREATE ${newPath}/src/assets/.gitkeep`);
}); });
/** /**

View File

@ -185,10 +185,7 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
browser: `apps/${project}/src/main.ts`, browser: `apps/${project}/src/main.ts`,
polyfills: [`zone.js`], polyfills: [`zone.js`],
tsConfig: `apps/${project}/tsconfig.app.json`, tsConfig: `apps/${project}/tsconfig.app.json`,
assets: [ assets: [{ glob: '**/*', input: `apps/${project}/public` }],
`apps/${project}/src/favicon.ico`,
`apps/${project}/src/assets`,
],
styles: [`apps/${project}/src/styles.css`], styles: [`apps/${project}/src/styles.css`],
scripts: [`apps/${project}/src/scripts.js`], scripts: [`apps/${project}/src/scripts.js`],
}, },
@ -197,13 +194,13 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
budgets: [ budgets: [
{ {
type: 'initial', type: 'initial',
maximumWarning: '500kb', maximumWarning: '500kB',
maximumError: '1mb', maximumError: '1MB',
}, },
{ {
type: 'anyComponentStyle', type: 'anyComponentStyle',
maximumWarning: '2kb', maximumWarning: '2kB',
maximumError: '4kb', maximumError: '4kB',
}, },
], ],
outputHashing: 'all', outputHashing: 'all',
@ -229,10 +226,7 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
options: { options: {
polyfills: [`zone.js`, `zone.js/testing`], polyfills: [`zone.js`, `zone.js/testing`],
tsConfig: `apps/${project}/tsconfig.spec.json`, tsConfig: `apps/${project}/tsconfig.spec.json`,
assets: [ assets: [{ glob: '**/*', input: `apps/${project}/public` }],
`apps/${project}/src/favicon.ico`,
`apps/${project}/src/assets`,
],
styles: [`apps/${project}/src/styles.css`], styles: [`apps/${project}/src/styles.css`],
scripts: [`apps/${project}/src/scripts.js`], scripts: [`apps/${project}/src/scripts.js`],
}, },

View File

@ -25,19 +25,19 @@
}, },
"devDependencies": { "devDependencies": {
"@actions/core": "^1.10.0", "@actions/core": "^1.10.0",
"@angular-devkit/architect": "~0.1703.0", "@angular-devkit/architect": "~0.1800.0",
"@angular-devkit/build-angular": "~17.3.0", "@angular-devkit/build-angular": "~18.0.0",
"@angular-devkit/core": "~17.3.0", "@angular-devkit/core": "~18.0.0",
"@angular-devkit/schematics": "~17.3.0", "@angular-devkit/schematics": "~18.0.0",
"@angular-eslint/eslint-plugin": "17.3.0", "@angular-eslint/eslint-plugin": "~17.3.0",
"@angular-eslint/eslint-plugin-template": "17.3.0", "@angular-eslint/eslint-plugin-template": "~17.3.0",
"@angular-eslint/template-parser": "17.3.0", "@angular-eslint/template-parser": "~17.3.0",
"@angular/cli": "~17.3.0", "@angular/cli": "~18.0.0",
"@angular/common": "~17.3.0", "@angular/common": "~18.0.0",
"@angular/compiler": "~17.3.0", "@angular/compiler": "~18.0.0",
"@angular/compiler-cli": "~17.3.0", "@angular/compiler-cli": "~18.0.0",
"@angular/core": "~17.3.0", "@angular/core": "~18.0.0",
"@angular/router": "~17.3.0", "@angular/router": "~18.0.0",
"@babel/core": "^7.23.2", "@babel/core": "^7.23.2",
"@babel/helper-create-regexp-features-plugin": "^7.22.9", "@babel/helper-create-regexp-features-plugin": "^7.22.9",
"@babel/plugin-transform-runtime": "^7.23.2", "@babel/plugin-transform-runtime": "^7.23.2",
@ -92,7 +92,7 @@
"@rollup/plugin-json": "^6.1.0", "@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-url": "^8.0.2", "@rollup/plugin-url": "^8.0.2",
"@schematics/angular": "~17.3.0", "@schematics/angular": "~18.0.0",
"@storybook/addon-essentials": "7.5.3", "@storybook/addon-essentials": "7.5.3",
"@storybook/core-server": "7.5.3", "@storybook/core-server": "7.5.3",
"@storybook/react": "7.5.3", "@storybook/react": "7.5.3",
@ -226,7 +226,7 @@
"mini-css-extract-plugin": "~2.4.7", "mini-css-extract-plugin": "~2.4.7",
"minimatch": "9.0.3", "minimatch": "9.0.3",
"next-sitemap": "^3.1.10", "next-sitemap": "^3.1.10",
"ng-packagr": "~17.3.0", "ng-packagr": "~18.0.0",
"node-fetch": "^2.6.7", "node-fetch": "^2.6.7",
"npm-package-arg": "11.0.1", "npm-package-arg": "11.0.1",
"nuxt": "^3.10.0", "nuxt": "^3.10.0",

View File

@ -8,7 +8,7 @@
"ng-packagr-lite": { "ng-packagr-lite": {
"implementation": "./src/executors/ng-packagr-lite/ng-packagr-lite.impl", "implementation": "./src/executors/ng-packagr-lite/ng-packagr-lite.impl",
"schema": "./src/executors/ng-packagr-lite/schema.json", "schema": "./src/executors/ng-packagr-lite/schema.json",
"description": "Builds an Angular library with support for incremental builds.\n\nThis executor is meant to be used with buildable libraries in an incremental build scenario. It is similar to the `@nx/angular:package` executor but with some key differences:\n- For supported Angular versions lower than v16, it doesn't run `ngcc` automatically (`ngcc` is no longer available from Angular v16 onwards, for lower versions, it needs to be run separately beforehand if needed, this can be done in a `postinstall` hook on `package.json`).\n- It only produces ESM2022 bundles (ESM2020 for supported Angular versions lower than v16)." "description": "Builds an Angular library with support for incremental builds.\n\nThis executor is meant to be used with buildable libraries in an incremental build scenario. It is similar to the `@nx/angular:package` executor but it only produces ESM2022 bundles."
}, },
"package": { "package": {
"implementation": "./src/executors/package/package.impl", "implementation": "./src/executors/package/package.impl",

View File

@ -386,6 +386,15 @@
}, },
"description": "Update the @angular/cli package version to ~17.3.0.", "description": "Update the @angular/cli package version to ~17.3.0.",
"factory": "./src/migrations/update-18-2-0/update-angular-cli" "factory": "./src/migrations/update-18-2-0/update-angular-cli"
},
"update-angular-cli-version-18-0-0": {
"cli": "nx",
"version": "19.1.0-beta.2",
"requires": {
"@angular/core": ">=18.0.0"
},
"description": "Update the @angular/cli package version to ~18.0.0.",
"factory": "./src/migrations/update-19-1-0/update-angular-cli"
} }
}, },
"packageJsonUpdates": { "packageJsonUpdates": {
@ -1805,6 +1814,79 @@
"alwaysAddToPackageJson": false "alwaysAddToPackageJson": false
} }
} }
},
"19.1.0": {
"version": "19.1.0-beta.2",
"x-prompt": "Do you want to update the Angular version to v18?",
"requires": {
"@angular/core": ">=17.3.0 <18.0.0"
},
"packages": {
"@angular-devkit/build-angular": {
"version": "~18.0.0",
"alwaysAddToPackageJson": false
},
"@angular-devkit/core": {
"version": "~18.0.0",
"alwaysAddToPackageJson": false
},
"@angular-devkit/schematics": {
"version": "~18.0.0",
"alwaysAddToPackageJson": false
},
"@angular/pwa": {
"version": "~18.0.0",
"alwaysAddToPackageJson": false
},
"@angular/ssr": {
"version": "~18.0.0",
"alwaysAddToPackageJson": false
},
"@schematics/angular": {
"version": "~18.0.0",
"alwaysAddToPackageJson": false
},
"@angular-devkit/architect": {
"version": "~0.1800.0",
"alwaysAddToPackageJson": false
},
"@angular-devkit/build-webpack": {
"version": "~0.1800.0",
"alwaysAddToPackageJson": false
},
"@angular/core": {
"version": "~18.0.0",
"alwaysAddToPackageJson": true
},
"@angular/material": {
"version": "~18.0.0",
"alwaysAddToPackageJson": false
},
"@angular/cdk": {
"version": "~18.0.0",
"alwaysAddToPackageJson": false
},
"ng-packagr": {
"version": "~18.0.0",
"alwaysAddToPackageJson": false
}
}
},
"19.1.0-jest": {
"version": "19.1.0-beta.2",
"requires": {
"@angular-devkit/build-angular": ">=15.0.0 <19.0.0",
"@angular/compiler-cli": ">=15.0.0 <19.0.0",
"@angular/core": ">=15.0.0 <19.0.0",
"@angular/platform-browser-dynamic": ">=15.0.0 <19.0.0",
"jest": "^29.0.0"
},
"packages": {
"jest-preset-angular": {
"version": "~14.1.0",
"alwaysAddToPackageJson": false
}
}
} }
} }
} }

View File

@ -67,10 +67,10 @@
"piscina": "^4.4.0" "piscina": "^4.4.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular-devkit/build-angular": ">= 15.0.0 < 18.0.0", "@angular-devkit/build-angular": ">= 16.0.0 < 19.0.0",
"@angular-devkit/core": ">= 15.0.0 < 18.0.0", "@angular-devkit/core": ">= 16.0.0 < 19.0.0",
"@angular-devkit/schematics": ">= 15.0.0 < 18.0.0", "@angular-devkit/schematics": ">= 16.0.0 < 19.0.0",
"@schematics/angular": ">= 15.0.0 < 18.0.0", "@schematics/angular": ">= 16.0.0 < 19.0.0",
"esbuild": "^0.19.2", "esbuild": "^0.19.2",
"rxjs": "^6.5.3 || ^7.5.0" "rxjs": "^6.5.3 || ^7.5.0"
}, },

View File

@ -13,6 +13,7 @@ import { from } from 'rxjs';
import { switchMap, tap } from 'rxjs/operators'; import { switchMap, tap } from 'rxjs/operators';
import { getInstalledAngularVersionInfo } from '../../executors/utilities/angular-version-utils'; import { getInstalledAngularVersionInfo } from '../../executors/utilities/angular-version-utils';
import { import {
getDynamicMfManifestFile,
getDynamicRemotes, getDynamicRemotes,
getStaticRemotes, getStaticRemotes,
validateDevRemotes, validateDevRemotes,
@ -29,11 +30,7 @@ export function executeModuleFederationDevSSRBuilder(
readProjectsConfigurationFromProjectGraph(projectGraph); readProjectsConfigurationFromProjectGraph(projectGraph);
const project = workspaceProjects[context.target.project]; const project = workspaceProjects[context.target.project];
let pathToManifestFile = join( let pathToManifestFile: string;
context.workspaceRoot,
project.sourceRoot,
'assets/module-federation.manifest.json'
);
if (options.pathToManifestFile) { if (options.pathToManifestFile) {
const userPathToManifestFile = join( const userPathToManifestFile = join(
context.workspaceRoot, context.workspaceRoot,
@ -50,6 +47,11 @@ export function executeModuleFederationDevSSRBuilder(
} }
pathToManifestFile = userPathToManifestFile; pathToManifestFile = userPathToManifestFile;
} else {
pathToManifestFile = getDynamicMfManifestFile(
project,
context.workspaceRoot
);
} }
validateDevRemotes(options, workspaceProjects); validateDevRemotes(options, workspaceProjects);

View File

@ -1,4 +1,4 @@
import { basename, dirname, join } from 'path'; import { join } from 'path';
import { existsSync, readFileSync } from 'fs'; import { existsSync, readFileSync } from 'fs';
import { logger, ProjectConfiguration } from '@nx/devkit'; import { logger, ProjectConfiguration } from '@nx/devkit';
import { registerTsProject } from '@nx/js/src/internal'; import { registerTsProject } from '@nx/js/src/internal';
@ -8,17 +8,18 @@ export function getDynamicRemotes(
context: import('@angular-devkit/architect').BuilderContext, context: import('@angular-devkit/architect').BuilderContext,
workspaceProjects: Record<string, ProjectConfiguration>, workspaceProjects: Record<string, ProjectConfiguration>,
remotesToSkip: Set<string>, remotesToSkip: Set<string>,
pathToManifestFile = join( pathToManifestFile: string | undefined
context.workspaceRoot,
project.sourceRoot,
'assets/module-federation.manifest.json'
)
): string[] { ): string[] {
pathToManifestFile ??= getDynamicMfManifestFile(
project,
context.workspaceRoot
);
// check for dynamic remotes // check for dynamic remotes
// we should only check for dynamic based on what we generate // we should only check for dynamic based on what we generate
// and fallback to empty array // and fallback to empty array
if (!existsSync(pathToManifestFile)) { if (!pathToManifestFile || !existsSync(pathToManifestFile)) {
return []; return [];
} }
@ -182,3 +183,23 @@ export function validateDevRemotes(
); );
} }
} }
export function getDynamicMfManifestFile(
project: ProjectConfiguration,
workspaceRoot: string
): string | undefined {
// {sourceRoot}/assets/module-federation.manifest.json was the generated
// path for the manifest file in the past. We now generate the manifest
// file at {root}/public/module-federation.manifest.json. This check
// ensures that we can still support the old path for backwards
// compatibility since old projects may still have the manifest file
// at the old path.
return [
join(workspaceRoot, project.root, 'public/module-federation.manifest.json'),
join(
workspaceRoot,
project.sourceRoot,
'assets/module-federation.manifest.json'
),
].find((path) => existsSync(path));
}

View File

@ -498,11 +498,12 @@
}, },
"output": { "output": {
"type": "string", "type": "string",
"default": "",
"description": "Absolute path within the output." "description": "Absolute path within the output."
} }
}, },
"additionalProperties": false, "additionalProperties": false,
"required": ["glob", "input", "output"] "required": ["glob", "input"]
}, },
{ {
"type": "string" "type": "string"

View File

@ -8,7 +8,7 @@
"properties": { "properties": {
"assets": { "assets": {
"type": "array", "type": "array",
"description": "List of static application assets. _Note: only supported in Angular versions >= 15.1.0_", "description": "List of static application assets.",
"default": [], "default": [],
"items": { "items": {
"$ref": "#/definitions/assetPattern" "$ref": "#/definitions/assetPattern"
@ -127,7 +127,7 @@
}, },
"vendorChunk": { "vendorChunk": {
"type": "boolean", "type": "boolean",
"description": "Generate a separate bundle containing only vendor libraries. This option should only be used for development to reduce the incremental compilation time. _Note: supported in Angular versions >= 15.1.0_", "description": "Generate a separate bundle containing only vendor libraries. This option should only be used for development to reduce the incremental compilation time.",
"default": false "default": false
}, },
"verbose": { "verbose": {
@ -192,7 +192,7 @@
}, },
"buildOptimizer": { "buildOptimizer": {
"type": "boolean", "type": "boolean",
"description": "Enables advanced build optimizations. _Note: only supported in Angular versions >= 16.0.0_.", "description": "Enables advanced build optimizations.",
"default": true "default": true
}, },
"namedChunks": { "namedChunks": {
@ -271,11 +271,12 @@
}, },
"output": { "output": {
"type": "string", "type": "string",
"default": "",
"description": "Absolute path within the output." "description": "Absolute path within the output."
} }
}, },
"additionalProperties": false, "additionalProperties": false,
"required": ["glob", "input", "output"] "required": ["glob", "input"]
}, },
{ {
"type": "string" "type": "string"

View File

@ -1,40 +0,0 @@
import { stripIndents } from '@nx/devkit';
import { lt } from 'semver';
import type { VersionInfo } from '../../executors/utilities/angular-version-utils';
import { getInstalledAngularVersionInfo } from '../../executors/utilities/angular-version-utils';
import type { Schema } from './schema';
export function validateOptions(options: Schema): void {
const angularVersionInfo = getInstalledAngularVersionInfo();
validateAssets(options, angularVersionInfo);
validateBuildOptimizer(options, angularVersionInfo);
validateVendorChunk(options, angularVersionInfo);
}
function validateAssets(options: Schema, { version }: VersionInfo): void {
if (
lt(version, '15.1.0') &&
Array.isArray(options.assets) &&
options.assets.length > 0
) {
throw new Error(stripIndents`The "assets" option is supported from Angular >= 15.1.0. You are currently using "${version}".
You can resolve this error by removing the "assets" option or by migrating to Angular 15.1.0.`);
}
}
function validateBuildOptimizer(
options: Schema,
{ major, version }: VersionInfo
): void {
if (major < 16 && options.buildOptimizer) {
throw new Error(stripIndents`The "buildOptimizer" option is supported from Angular >= 16.0.0. You are currently using "${version}".
You can resolve this error by removing the "buildOptimizer" option.`);
}
}
function validateVendorChunk(options: Schema, { version }: VersionInfo): void {
if (lt(version, '15.1.0') && options.vendorChunk) {
throw new Error(stripIndents`The "vendorChunk" option is supported from Angular >= 15.1.0. You are currently using "${version}".
You can resolve this error by removing the "vendorChunk" option or by migrating to Angular 15.1.0.`);
}
}

View File

@ -10,7 +10,6 @@ import { switchMap } from 'rxjs/operators';
import { createTmpTsConfigForBuildableLibs } from '../utilities/buildable-libs'; import { createTmpTsConfigForBuildableLibs } from '../utilities/buildable-libs';
import { mergeCustomWebpackConfig } from '../utilities/webpack'; import { mergeCustomWebpackConfig } from '../utilities/webpack';
import { Schema } from './schema'; import { Schema } from './schema';
import { validateOptions } from './validate-options';
function buildServerApp( function buildServerApp(
options: Schema, options: Schema,
@ -55,7 +54,7 @@ function buildServerAppWithCustomWebpackConfiguration(
switchMap(({ executeServerBuilder }) => switchMap(({ executeServerBuilder }) =>
executeServerBuilder(options, context as any, { executeServerBuilder(options, context as any, {
webpackConfiguration: async (baseWebpackConfig) => { webpackConfiguration: async (baseWebpackConfig) => {
// Angular 15 auto includes code from @angular/platform-server // Angular auto includes code from @angular/platform-server
// This includes the code outside the shared scope created by ModuleFederation // This includes the code outside the shared scope created by ModuleFederation
// This code will be included in the generated code from our generators, // This code will be included in the generated code from our generators,
// maintaining it within the shared scope. // maintaining it within the shared scope.
@ -96,8 +95,6 @@ export function executeWebpackServerBuilder(
options: Schema, options: Schema,
context: import('@angular-devkit/architect').BuilderContext context: import('@angular-devkit/architect').BuilderContext
): Observable<import('@angular-devkit/build-angular').ServerBuilderOutput> { ): Observable<import('@angular-devkit/build-angular').ServerBuilderOutput> {
validateOptions(options);
options.buildLibsFromSource ??= true; options.buildLibsFromSource ??= true;
process.env.NX_BUILD_LIBS_FROM_SOURCE = `${options.buildLibsFromSource}`; process.env.NX_BUILD_LIBS_FROM_SOURCE = `${options.buildLibsFromSource}`;

View File

@ -10,11 +10,12 @@ import {
} from '../utilities/esbuild-extensions'; } from '../utilities/esbuild-extensions';
import type { ApplicationExecutorOptions } from './schema'; import type { ApplicationExecutorOptions } from './schema';
import { validateOptions } from './utils/validate-options'; import { validateOptions } from './utils/validate-options';
import type { buildApplication as buildApplicationFn } from '@angular-devkit/build-angular';
export default async function* applicationExecutor( export default async function* applicationExecutor(
options: ApplicationExecutorOptions, options: ApplicationExecutorOptions,
context: ExecutorContext context: ExecutorContext
) { ): ReturnType<typeof buildApplicationFn> {
validateOptions(options); validateOptions(options);
const { const {
@ -46,7 +47,7 @@ export default async function* applicationExecutor(
{ {
builderName: 'application', builderName: 'application',
description: 'Build an application.', description: 'Build an application.',
optionSchema: await import('./schema.json'), optionSchema: require('./schema.json'),
}, },
context context
); );

View File

@ -600,11 +600,12 @@
}, },
"output": { "output": {
"type": "string", "type": "string",
"default": "",
"description": "Absolute path within the output." "description": "Absolute path within the output."
} }
}, },
"additionalProperties": false, "additionalProperties": false,
"required": ["glob", "input", "output"] "required": ["glob", "input"]
}, },
{ {
"type": "string" "type": "string"

View File

@ -1,3 +1,4 @@
import type { buildEsbuildBrowser as buildEsbuildBrowserFn } from '@angular-devkit/build-angular/src/builders/browser-esbuild';
import { stripIndents, type ExecutorContext } from '@nx/devkit'; import { stripIndents, type ExecutorContext } from '@nx/devkit';
import type { DependentBuildableProjectNode } from '@nx/js/src/utils/buildable-libs-utils'; import type { DependentBuildableProjectNode } from '@nx/js/src/utils/buildable-libs-utils';
import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter'; import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter';
@ -9,7 +10,7 @@ import type { EsBuildSchema } from './schema';
export default async function* esbuildExecutor( export default async function* esbuildExecutor(
options: EsBuildSchema, options: EsBuildSchema,
context: ExecutorContext context: ExecutorContext
) { ): ReturnType<typeof buildEsbuildBrowserFn> {
if (options.plugins) { if (options.plugins) {
const { major: angularMajorVersion, version: angularVersion } = const { major: angularMajorVersion, version: angularVersion } =
getInstalledAngularVersionInfo(); getInstalledAngularVersionInfo();
@ -41,17 +42,15 @@ export default async function* esbuildExecutor(
const plugins = await loadPlugins(pluginPaths, options.tsConfig); const plugins = await loadPlugins(pluginPaths, options.tsConfig);
const { buildEsbuildBrowser } = await import( const { buildEsbuildBrowser } = <
'@angular-devkit/build-angular/src/builders/browser-esbuild/index' typeof import('@angular-devkit/build-angular/src/builders/browser-esbuild')
); >require('@angular-devkit/build-angular/src/builders/browser-esbuild');
const builderContext = await createBuilderContext( const builderContext = await createBuilderContext(
{ {
builderName: 'browser-esbuild', builderName: 'browser-esbuild',
description: 'Build a browser application', description: 'Build a browser application',
optionSchema: await import( optionSchema: require('@angular-devkit/build-angular/src/builders/browser-esbuild/schema.json'),
'@angular-devkit/build-angular/src/builders/browser-esbuild/schema.json'
),
}, },
context context
); );

View File

@ -502,11 +502,12 @@
}, },
"output": { "output": {
"type": "string", "type": "string",
"default": "",
"description": "Absolute path within the output." "description": "Absolute path within the output."
} }
}, },
"additionalProperties": false, "additionalProperties": false,
"required": ["glob", "input", "output"] "required": ["glob", "input"]
}, },
{ {
"type": "string" "type": "string"

View File

@ -29,7 +29,7 @@ export default async function* extractI18nExecutor(
{ {
builderName: 'extrct-i18n', builderName: 'extrct-i18n',
description: 'Extracts i18n messages from source code.', description: 'Extracts i18n messages from source code.',
optionSchema: await import('./schema.json'), optionSchema: require('./schema.json'),
}, },
context context
); );

View File

@ -25,7 +25,10 @@ import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl'; import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl';
import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter'; import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter';
import { executeDevServerBuilder } from '../../builders/dev-server/dev-server.impl'; import { executeDevServerBuilder } from '../../builders/dev-server/dev-server.impl';
import { validateDevRemotes } from '../../builders/utilities/module-federation'; import {
getDynamicMfManifestFile,
validateDevRemotes,
} from '../../builders/utilities/module-federation';
import { extname, join } from 'path'; import { extname, join } from 'path';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
@ -63,9 +66,7 @@ export async function* moduleFederationDevServerExecutor(
{ {
builderName: '@nx/angular:webpack-browser', builderName: '@nx/angular:webpack-browser',
description: 'Build a browser application', description: 'Build a browser application',
optionSchema: await import( optionSchema: require('../../builders/webpack-browser/schema.json'),
'../../builders/webpack-browser/schema.json'
),
}, },
context context
) )
@ -76,12 +77,10 @@ export async function* moduleFederationDevServerExecutor(
return yield* currIter; return yield* currIter;
} }
let pathToManifestFile = join( let pathToManifestFile: string;
context.root, if (!options.pathToManifestFile) {
project.sourceRoot, pathToManifestFile = getDynamicMfManifestFile(project, context.root);
'assets/module-federation.manifest.json' } else {
);
if (options.pathToManifestFile) {
const userPathToManifestFile = join( const userPathToManifestFile = join(
context.root, context.root,
options.pathToManifestFile options.pathToManifestFile

View File

@ -8,13 +8,13 @@ export async function getNgPackagrInstance(
const { major: angularMajorVersion } = getInstalledAngularVersionInfo(); const { major: angularMajorVersion } = getInstalledAngularVersionInfo();
if (angularMajorVersion >= 17) { if (angularMajorVersion >= 17) {
const { WRITE_BUNDLES_TRANSFORM } = await import( const { WRITE_BUNDLES_TRANSFORM } = await import(
'./v17+/ng-package/entry-point/write-bundles.di' './v17+/ng-package/entry-point/write-bundles.di.js'
); );
const { WRITE_PACKAGE_TRANSFORM } = await import( const { WRITE_PACKAGE_TRANSFORM } = await import(
'./v17+/ng-package/entry-point/write-package.di' './v17+/ng-package/entry-point/write-package.di.js'
); );
const { STYLESHEET_PROCESSOR } = await import( const { STYLESHEET_PROCESSOR } = await import(
'../../utilities/ng-packagr/stylesheet-processor.di' '../../utilities/ng-packagr/stylesheet-processor.di.js'
); );
const packagr = ngPackagr(); const packagr = ngPackagr();
@ -28,11 +28,13 @@ export async function getNgPackagrInstance(
} }
const { NX_ENTRY_POINT_PROVIDERS } = await import( const { NX_ENTRY_POINT_PROVIDERS } = await import(
'./pre-v17/ng-package/entry-point/entry-point.di' './pre-v17/ng-package/entry-point/entry-point.di.js'
);
const { nxProvideOptions } = await import(
'./pre-v17/ng-package/options.di.js'
); );
const { nxProvideOptions } = await import('./pre-v17/ng-package/options.di');
const { NX_PACKAGE_PROVIDERS, NX_PACKAGE_TRANSFORM } = await import( const { NX_PACKAGE_PROVIDERS, NX_PACKAGE_TRANSFORM } = await import(
'./pre-v17/ng-package/package.di' './pre-v17/ng-package/package.di.js'
); );
const packagr = new NgPackagr([ const packagr = new NgPackagr([

View File

@ -6,10 +6,6 @@
* - Support Angular Compiler `incrementalDriver` for Angular < 16. * - Support Angular Compiler `incrementalDriver` for Angular < 16.
*/ */
import type {
CompilerOptions,
ParsedConfiguration,
} from '@angular/compiler-cli';
import { BuildGraph } from 'ng-packagr/lib/graph/build-graph'; import { BuildGraph } from 'ng-packagr/lib/graph/build-graph';
import { import {
EntryPointNode, EntryPointNode,
@ -25,21 +21,23 @@ import * as log from 'ng-packagr/lib/utils/log';
import { join } from 'node:path'; import { join } from 'node:path';
import * as ts from 'typescript'; import * as ts from 'typescript';
import { getInstalledAngularVersionInfo } from '../../../../utilities/angular-version-utils'; import { getInstalledAngularVersionInfo } from '../../../../utilities/angular-version-utils';
import { ngCompilerCli } from '../../../../utilities/ng-compiler-cli'; import { loadEsmModule } from '../../../../utilities/module-loader';
import { NgPackagrOptions } from '../ng-package/options.di'; import { NgPackagrOptions } from '../ng-package/options.di';
import { StylesheetProcessor } from '../styles/stylesheet-processor'; import { StylesheetProcessor } from '../styles/stylesheet-processor';
export async function compileSourceFiles( export async function compileSourceFiles(
graph: BuildGraph, graph: BuildGraph,
tsConfig: ParsedConfiguration, tsConfig: any,
moduleResolutionCache: ts.ModuleResolutionCache, moduleResolutionCache: ts.ModuleResolutionCache,
options: NgPackagrOptions, options: NgPackagrOptions,
extraOptions?: Partial<CompilerOptions>, extraOptions?: Partial<ts.CompilerOptions>,
stylesheetProcessor?: StylesheetProcessor stylesheetProcessor?: StylesheetProcessor
) { ) {
const { NgtscProgram, formatDiagnostics } = await ngCompilerCli(); const { NgtscProgram, formatDiagnostics } = await loadEsmModule(
'@angular/compiler-cli'
);
const { cacheDirectory, watch, cacheEnabled } = options; const { cacheDirectory, watch, cacheEnabled } = options;
const tsConfigOptions: CompilerOptions = { const tsConfigOptions: ts.CompilerOptions = {
...tsConfig.options, ...tsConfig.options,
...extraOptions, ...extraOptions,
}; };

View File

@ -6,9 +6,8 @@
* - Added PostCSS plugin needed to support TailwindCSS. * - Added PostCSS plugin needed to support TailwindCSS.
*/ */
import * as browserslist from 'browserslist'; import browserslist from 'browserslist';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import { EsbuildExecutor } from 'ng-packagr/lib/esbuild/esbuild-executor';
import { import {
generateKey, generateKey,
readCacheEntry, readCacheEntry,
@ -16,7 +15,7 @@ import {
} from 'ng-packagr/lib/utils/cache'; } from 'ng-packagr/lib/utils/cache';
import * as log from 'ng-packagr/lib/utils/log'; import * as log from 'ng-packagr/lib/utils/log';
import { dirname, extname, join } from 'path'; import { dirname, extname, join } from 'path';
import * as autoprefixer from 'autoprefixer'; import autoprefixer from 'autoprefixer';
import * as postcssUrl from 'postcss-url'; import * as postcssUrl from 'postcss-url';
import { pathToFileURL } from 'node:url'; import { pathToFileURL } from 'node:url';
import { import {
@ -49,7 +48,8 @@ export class StylesheetProcessor {
private browserslistData: string[]; private browserslistData: string[];
private targets: string[]; private targets: string[];
private postCssProcessor: ReturnType<typeof postcss>; private postCssProcessor: ReturnType<typeof postcss>;
private esbuild = new EsbuildExecutor(); private esbuild =
new (require('ng-packagr/lib/esbuild/esbuild-executor').EsbuildExecutor)();
private styleIncludePaths: string[]; private styleIncludePaths: string[];
constructor( constructor(

View File

@ -354,12 +354,6 @@ type ConditionalExport = {
esm2022?: string; esm2022?: string;
esm?: string; esm?: string;
default?: string; default?: string;
// backward compat for Angular < 16
node?: string;
esm2020?: string;
es2020?: string;
es2015?: string;
}; };
/** /**

View File

@ -3,7 +3,7 @@
"outputCapture": "direct-nodejs", "outputCapture": "direct-nodejs",
"$schema": "https://json-schema.org/schema", "$schema": "https://json-schema.org/schema",
"title": "ng-packagr Target", "title": "ng-packagr Target",
"description": "Builds an Angular library with support for incremental builds.\n\nThis executor is meant to be used with buildable libraries in an incremental build scenario. It is similar to the `@nx/angular:package` executor but with some key differences:\n- For supported Angular versions lower than v16, it doesn't run `ngcc` automatically (`ngcc` is no longer available from Angular v16 onwards, for lower versions, it needs to be run separately beforehand if needed, this can be done in a `postinstall` hook on `package.json`).\n- It only produces ESM2022 bundles (ESM2020 for supported Angular versions lower than v16).", "description": "Builds an Angular library with support for incremental builds.\n\nThis executor is meant to be used with buildable libraries in an incremental build scenario. It is similar to the `@nx/angular:package` executor but it only produces ESM2022 bundles.",
"cli": "nx", "cli": "nx",
"type": "object", "type": "object",
"presets": [ "presets": [

View File

@ -19,7 +19,7 @@ import {
PackageNode, PackageNode,
} from 'ng-packagr/lib/ng-package/nodes'; } from 'ng-packagr/lib/ng-package/nodes';
import { setDependenciesTsConfigPaths } from 'ng-packagr/lib/ts/tsconfig'; import { setDependenciesTsConfigPaths } from 'ng-packagr/lib/ts/tsconfig';
import * as ora from 'ora'; import ora from 'ora';
import * as path from 'path'; import * as path from 'path';
import * as ts from 'typescript'; import * as ts from 'typescript';
import { getInstalledAngularVersionInfo } from '../../../../utilities/angular-version-utils'; import { getInstalledAngularVersionInfo } from '../../../../utilities/angular-version-utils';

View File

@ -8,7 +8,7 @@ export async function getNgPackagrInstance(
const { major: angularMajorVersion } = getInstalledAngularVersionInfo(); const { major: angularMajorVersion } = getInstalledAngularVersionInfo();
if (angularMajorVersion >= 17) { if (angularMajorVersion >= 17) {
const { STYLESHEET_PROCESSOR } = await import( const { STYLESHEET_PROCESSOR } = await import(
'../../utilities/ng-packagr/stylesheet-processor.di' '../../utilities/ng-packagr/stylesheet-processor.di.js'
); );
const packagr = ngPackagr(); const packagr = ngPackagr();
@ -17,11 +17,11 @@ export async function getNgPackagrInstance(
} }
const { NX_ENTRY_POINT_PROVIDERS } = await import( const { NX_ENTRY_POINT_PROVIDERS } = await import(
'./ng-package/entry-point/entry-point.di' './ng-package/entry-point/entry-point.di.js'
); );
const { nxProvideOptions } = await import('./ng-package/options.di'); const { nxProvideOptions } = await import('./ng-package/options.di.js');
const { NX_PACKAGE_PROVIDERS, NX_PACKAGE_TRANSFORM } = await import( const { NX_PACKAGE_PROVIDERS, NX_PACKAGE_TRANSFORM } = await import(
'./ng-package/package.di' './ng-package/package.di.js'
); );
const packagr = new NgPackagr([ const packagr = new NgPackagr([

View File

@ -7,10 +7,6 @@
* - Support Angular Compiler `incrementalDriver` for Angular < 16. * - Support Angular Compiler `incrementalDriver` for Angular < 16.
*/ */
import type {
CompilerOptions,
ParsedConfiguration,
} from '@angular/compiler-cli';
import { BuildGraph } from 'ng-packagr/lib/graph/build-graph'; import { BuildGraph } from 'ng-packagr/lib/graph/build-graph';
import { import {
EntryPointNode, EntryPointNode,
@ -26,22 +22,24 @@ import {
import { join } from 'node:path'; import { join } from 'node:path';
import * as ts from 'typescript'; import * as ts from 'typescript';
import { getInstalledAngularVersionInfo } from '../../../utilities/angular-version-utils'; import { getInstalledAngularVersionInfo } from '../../../utilities/angular-version-utils';
import { ngCompilerCli } from '../../../utilities/ng-compiler-cli'; import { loadEsmModule } from '../../../utilities/module-loader';
import { NgPackagrOptions } from '../ng-package/options.di'; import { NgPackagrOptions } from '../ng-package/options.di';
import { StylesheetProcessor } from '../styles/stylesheet-processor'; import { StylesheetProcessor } from '../styles/stylesheet-processor';
export async function compileSourceFiles( export async function compileSourceFiles(
graph: BuildGraph, graph: BuildGraph,
tsConfig: ParsedConfiguration, tsConfig: any,
moduleResolutionCache: ts.ModuleResolutionCache, moduleResolutionCache: ts.ModuleResolutionCache,
options: NgPackagrOptions, options: NgPackagrOptions,
extraOptions?: Partial<CompilerOptions>, extraOptions?: Partial<ts.CompilerOptions>,
stylesheetProcessor?: StylesheetProcessor, stylesheetProcessor?: StylesheetProcessor,
ngccProcessor?: any ngccProcessor?: any
) { ) {
const { NgtscProgram, formatDiagnostics } = await ngCompilerCli(); const { NgtscProgram, formatDiagnostics } = await loadEsmModule(
'@angular/compiler-cli'
);
const { cacheDirectory, watch, cacheEnabled } = options; const { cacheDirectory, watch, cacheEnabled } = options;
const tsConfigOptions: CompilerOptions = { const tsConfigOptions: ts.CompilerOptions = {
...tsConfig.options, ...tsConfig.options,
...extraOptions, ...extraOptions,
}; };

View File

@ -6,9 +6,8 @@
* - Added PostCSS plugin needed to support TailwindCSS. * - Added PostCSS plugin needed to support TailwindCSS.
*/ */
import * as browserslist from 'browserslist'; import browserslist from 'browserslist';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import { EsbuildExecutor } from 'ng-packagr/lib/esbuild/esbuild-executor';
import { import {
generateKey, generateKey,
readCacheEntry, readCacheEntry,
@ -16,7 +15,7 @@ import {
} from 'ng-packagr/lib/utils/cache'; } from 'ng-packagr/lib/utils/cache';
import * as log from 'ng-packagr/lib/utils/log'; import * as log from 'ng-packagr/lib/utils/log';
import { dirname, extname, join } from 'path'; import { dirname, extname, join } from 'path';
import * as autoprefixer from 'autoprefixer'; import autoprefixer from 'autoprefixer';
import * as postcssUrl from 'postcss-url'; import * as postcssUrl from 'postcss-url';
import { pathToFileURL } from 'node:url'; import { pathToFileURL } from 'node:url';
import { import {
@ -42,7 +41,8 @@ export class StylesheetProcessor {
private browserslistData: string[]; private browserslistData: string[];
private targets: string[]; private targets: string[];
private postCssProcessor: ReturnType<typeof postcss>; private postCssProcessor: ReturnType<typeof postcss>;
private esbuild = new EsbuildExecutor(); private esbuild =
new (require('ng-packagr/lib/esbuild/esbuild-executor').EsbuildExecutor)();
private styleIncludePaths: string[]; private styleIncludePaths: string[];
constructor( constructor(

View File

@ -1,7 +1,5 @@
import type { IndexHtmlTransform } from '@angular-devkit/build-angular/src/utils/index-file/index-html-generator';
import { registerTsProject } from '@nx/js/src/internal'; import { registerTsProject } from '@nx/js/src/internal';
import type { Plugin } from 'esbuild'; import type { Plugin } from 'esbuild';
import type { Connect } from 'vite';
import { loadModule } from './module-loader'; import { loadModule } from './module-loader';
export type PluginSpec = { export type PluginSpec = {
@ -45,7 +43,7 @@ async function loadPlugin(pluginSpec: string | PluginSpec): Promise<Plugin> {
export async function loadMiddleware( export async function loadMiddleware(
middlewareFns: string[] | undefined, middlewareFns: string[] | undefined,
tsConfig: string tsConfig: string
): Promise<Connect.NextHandleFunction[]> { ): Promise<any[]> {
if (!middlewareFns?.length) { if (!middlewareFns?.length) {
return []; return [];
} }
@ -61,7 +59,7 @@ export async function loadMiddleware(
export async function loadIndexHtmlTransformer( export async function loadIndexHtmlTransformer(
indexHtmlTransformerPath: string, indexHtmlTransformerPath: string,
tsConfig: string tsConfig: string
): Promise<IndexHtmlTransform> { ): Promise<any> {
const cleanupTranspiler = registerTsProject(tsConfig); const cleanupTranspiler = registerTsProject(tsConfig);
try { try {

View File

@ -45,7 +45,7 @@ let load: (<T>(modulePath: string | URL) => Promise<T>) | undefined;
* @param modulePath The path of the module to load. * @param modulePath The path of the module to load.
* @returns A Promise that resolves to the dynamically imported module. * @returns A Promise that resolves to the dynamically imported module.
*/ */
export function loadEsmModule<T>(modulePath: string | URL): Promise<T> { export function loadEsmModule<T = any>(modulePath: string | URL): Promise<T> {
load ??= new Function('modulePath', `return import(modulePath);`) as Exclude< load ??= new Function('modulePath', `return import(modulePath);`) as Exclude<
typeof load, typeof load,
undefined undefined

View File

@ -1,7 +0,0 @@
import { loadEsmModule } from './module-loader';
export function ngCompilerCli(): Promise<
typeof import('@angular/compiler-cli')
> {
return loadEsmModule('@angular/compiler-cli');
}

View File

@ -6,7 +6,7 @@
* config at the root of the workspace. * config at the root of the workspace.
*/ */
import * as browserslist from 'browserslist'; import browserslist from 'browserslist';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import { dirname, join } from 'path'; import { dirname, join } from 'path';
const Piscina = require('piscina'); const Piscina = require('piscina');
@ -15,7 +15,7 @@ import { colors } from 'ng-packagr/lib/utils/color';
import { getTailwindConfigPath } from './tailwindcss'; import { getTailwindConfigPath } from './tailwindcss';
import { workspaceRoot } from '@nx/devkit'; import { workspaceRoot } from '@nx/devkit';
import type { PostcssConfiguration } from 'ng-packagr/lib/styles/postcss-configuration'; import type { PostcssConfiguration } from 'ng-packagr/lib/styles/postcss-configuration';
import { gt } from 'semver'; 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 maxWorkersVariable = process.env['NG_BUILD_MAX_WORKERS'];
@ -93,12 +93,31 @@ export class StylesheetProcessor {
const { version: ngPackagrVersion } = const { version: ngPackagrVersion } =
getInstalledPackageVersionInfo('ng-packagr'); getInstalledPackageVersionInfo('ng-packagr');
let tailwindConfigPath: string | undefined;
let postcssConfiguration: PostcssConfiguration | undefined; let postcssConfiguration: PostcssConfiguration | undefined;
if (gt(ngPackagrVersion, '17.2.0')) { if (gte(ngPackagrVersion, '18.0.0')) {
const {
findTailwindConfiguration,
generateSearchDirectories,
loadPostcssConfiguration,
} = require('ng-packagr/lib/styles/postcss-configuration');
let searchDirs = generateSearchDirectories([this.projectBasePath]);
postcssConfiguration = loadPostcssConfiguration(searchDirs);
// (nx-specific): we support loading the TailwindCSS config from the root of the workspace
searchDirs = generateSearchDirectories([
this.projectBasePath,
workspaceRoot,
]);
tailwindConfigPath = findTailwindConfiguration(searchDirs);
} else if (gt(ngPackagrVersion, '17.2.0')) {
const { const {
loadPostcssConfiguration, loadPostcssConfiguration,
} = require('ng-packagr/lib/styles/postcss-configuration'); } = require('ng-packagr/lib/styles/postcss-configuration');
postcssConfiguration = loadPostcssConfiguration(this.projectBasePath); postcssConfiguration = loadPostcssConfiguration(this.projectBasePath);
tailwindConfigPath = getTailwindConfigPath(
this.projectBasePath,
workspaceRoot
);
} }
this.renderWorker = new Piscina({ this.renderWorker = new Piscina({
@ -113,10 +132,7 @@ export class StylesheetProcessor {
}, },
workerData: { workerData: {
postcssConfiguration, postcssConfiguration,
tailwindConfigPath: getTailwindConfigPath( tailwindConfigPath,
this.projectBasePath,
workspaceRoot
),
projectBasePath: this.projectBasePath, projectBasePath: this.projectBasePath,
browserslistData, browserslistData,
targets: transformSupportedBrowsersToTargets(browserslistData), targets: transformSupportedBrowsersToTargets(browserslistData),
@ -200,9 +216,9 @@ export class AsyncStylesheetProcessor {
getInstalledPackageVersionInfo('ng-packagr'); getInstalledPackageVersionInfo('ng-packagr');
let postcssConfiguration: PostcssConfiguration | undefined; let postcssConfiguration: PostcssConfiguration | undefined;
if (ngPackagrVersion === '17.2.0') { if (ngPackagrVersion === '17.2.0') {
const { loadPostcssConfiguration } = await import( const {
'ng-packagr/lib/styles/postcss-configuration' loadPostcssConfiguration,
); } = require('ng-packagr/lib/styles/postcss-configuration');
postcssConfiguration = await loadPostcssConfiguration( postcssConfiguration = await loadPostcssConfiguration(
this.projectBasePath this.projectBasePath
); );

View File

@ -7,19 +7,13 @@
* to be the original tsconfig file. * to be the original tsconfig file.
*/ */
import type {
CompilerOptions,
ParsedConfiguration,
} from '@angular/compiler-cli';
import { resolve } from 'path'; import { resolve } from 'path';
import * as ts from 'typescript'; import * as ts from 'typescript';
import { ngCompilerCli } from './ng-compiler-cli'; import { loadEsmModule } from './module-loader';
async function readDefaultTsConfig( async function readDefaultTsConfig(fileName: string) {
fileName: string
): Promise<ParsedConfiguration> {
// these options are mandatory // these options are mandatory
const extraOptions: CompilerOptions = { const extraOptions: ts.CompilerOptions = {
target: ts.ScriptTarget.ES2020, target: ts.ScriptTarget.ES2020,
experimentalDecorators: true, experimentalDecorators: true,
@ -39,7 +33,7 @@ async function readDefaultTsConfig(
flatModuleOutFile: 'AUTOGENERATED', flatModuleOutFile: 'AUTOGENERATED',
}; };
const { readConfiguration } = await ngCompilerCli(); const { readConfiguration } = await loadEsmModule('@angular/compiler-cli');
return readConfiguration(fileName, extraOptions); return readConfiguration(fileName, extraOptions);
} }
@ -51,7 +45,7 @@ export async function parseRemappedTsConfigAndMergeDefaults(
workspaceRoot: string, workspaceRoot: string,
originalFilePath: string, originalFilePath: string,
remappedFilePath: string remappedFilePath: string
): Promise<ParsedConfiguration> { ) {
const parsedConfiguration = await readDefaultTsConfig(remappedFilePath); const parsedConfiguration = await readDefaultTsConfig(remappedFilePath);
parsedConfiguration.options.configFilePath = resolve( parsedConfiguration.options.configFilePath = resolve(
workspaceRoot, workspaceRoot,

View File

@ -243,8 +243,10 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
"executor": "@angular-devkit/build-angular:application", "executor": "@angular-devkit/build-angular:application",
"options": { "options": {
"assets": [ "assets": [
"apps/my-dir/my-app/src/favicon.ico", {
"apps/my-dir/my-app/src/assets", "glob": "**/*",
"input": "apps/my-dir/my-app/public",
},
], ],
"browser": "apps/my-dir/my-app/src/main.ts", "browser": "apps/my-dir/my-app/src/main.ts",
"index": "apps/my-dir/my-app/src/index.html", "index": "apps/my-dir/my-app/src/index.html",
@ -444,8 +446,10 @@ exports[`app --project-name-and-root-format=derived should generate correctly wh
"executor": "@angular-devkit/build-angular:application", "executor": "@angular-devkit/build-angular:application",
"options": { "options": {
"assets": [ "assets": [
"apps/my-app/src/favicon.ico", {
"apps/my-app/src/assets", "glob": "**/*",
"input": "apps/my-app/public",
},
], ],
"browser": "apps/my-app/src/main.ts", "browser": "apps/my-app/src/main.ts",
"index": "apps/my-app/src/index.html", "index": "apps/my-app/src/index.html",
@ -620,12 +624,12 @@ bootstrapApplication(AppComponent, appConfig).catch((err) =>
`; `;
exports[`app --standalone should generate a standalone app correctly with routing 2`] = ` exports[`app --standalone should generate a standalone app correctly with routing 2`] = `
"import { ApplicationConfig } from '@angular/core'; "import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes'; import { appRoutes } from './app.routes';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [provideRouter(appRoutes) ] providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(appRoutes) ]
}; };
" "
`; `;
@ -698,10 +702,10 @@ bootstrapApplication(AppComponent, appConfig).catch((err) =>
`; `;
exports[`app --standalone should generate a standalone app correctly without routing 2`] = ` exports[`app --standalone should generate a standalone app correctly without routing 2`] = `
"import { ApplicationConfig } from '@angular/core'; "import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [] providers: [provideZoneChangeDetection({ eventCoalescing: true }), ]
}; };
" "
`; `;
@ -753,6 +757,17 @@ describe('AppComponent', () => {
" "
`; `;
exports[`app --standalone should should not use event coalescing in versions lower than v18 1`] = `
"import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(appRoutes) ]
};
"
`;
exports[`app --strict should enable strict type checking: app tsconfig.json 1`] = ` exports[`app --strict should enable strict type checking: app tsconfig.json 1`] = `
{ {
"angularCompilerOptions": { "angularCompilerOptions": {
@ -819,8 +834,8 @@ exports[`app --strict should enable strict type checking: e2e tsconfig.json 1`]
} }
`; `;
exports[`app angular v15 support should import "ApplicationConfig" from "@angular/platform-browser" 1`] = ` exports[`app angular compat support should import "ApplicationConfig" from "@angular/platform-browser" 1`] = `
"import { ApplicationConfig } from '@angular/platform-browser'; "import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes'; import { appRoutes } from './app.routes';
@ -940,8 +955,10 @@ exports[`app nested should create project configs 1`] = `
"executor": "@angular-devkit/build-angular:application", "executor": "@angular-devkit/build-angular:application",
"options": { "options": {
"assets": [ "assets": [
"my-dir/my-app/src/favicon.ico", {
"my-dir/my-app/src/assets", "glob": "**/*",
"input": "my-dir/my-app/public",
},
], ],
"browser": "my-dir/my-app/src/main.ts", "browser": "my-dir/my-app/src/main.ts",
"index": "my-dir/my-app/src/index.html", "index": "my-dir/my-app/src/index.html",
@ -1054,8 +1071,10 @@ exports[`app not nested should create project configs 1`] = `
"executor": "@angular-devkit/build-angular:application", "executor": "@angular-devkit/build-angular:application",
"options": { "options": {
"assets": [ "assets": [
"my-app/src/favicon.ico", {
"my-app/src/assets", "glob": "**/*",
"input": "my-app/public",
},
], ],
"browser": "my-app/src/main.ts", "browser": "my-app/src/main.ts",
"index": "my-app/src/index.html", "index": "my-app/src/index.html",

View File

@ -896,6 +896,19 @@ describe('app', () => {
appTree.read('standalone/src/app/nx-welcome.component.ts', 'utf-8') appTree.read('standalone/src/app/nx-welcome.component.ts', 'utf-8')
).toContain('standalone: true'); ).toContain('standalone: true');
}); });
it('should should not use event coalescing in versions lower than v18', async () => {
updateJson(appTree, 'package.json', (json) => ({
...json,
dependencies: { ...json.dependencies, '@angular/core': '~17.0.0' },
}));
await generateApp(appTree, 'standalone', { standalone: true });
expect(
appTree.read('standalone/src/app/app.config.ts', 'utf-8')
).toMatchSnapshot();
});
}); });
it('should generate correct main.ts', async () => { it('should generate correct main.ts', async () => {
@ -903,6 +916,27 @@ describe('app', () => {
await generateApp(appTree, 'myapp'); await generateApp(appTree, 'myapp');
// ASSERT // ASSERT
expect(appTree.read('myapp/src/main.ts', 'utf-8')).toMatchInlineSnapshot(`
"import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic()
.bootstrapModule(AppModule, {
ngZoneEventCoalescing: true
})
.catch((err) => console.error(err));
"
`);
});
it('should should not use event coalescing in versions lower than v18', async () => {
updateJson(appTree, 'package.json', (json) => ({
...json,
dependencies: { ...json.dependencies, '@angular/core': '~17.0.0' },
}));
await generateApp(appTree, 'myapp');
expect(appTree.read('myapp/src/main.ts', 'utf-8')).toMatchInlineSnapshot(` expect(appTree.read('myapp/src/main.ts', 'utf-8')).toMatchInlineSnapshot(`
"import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; "import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module'; import { AppModule } from './app/app.module';
@ -1190,14 +1224,14 @@ describe('app', () => {
}); });
}); });
describe('angular v15 support', () => { describe('angular compat support', () => {
beforeEach(() => { beforeEach(() => {
appTree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); appTree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
updateJson(appTree, 'package.json', (json) => ({ updateJson(appTree, 'package.json', (json) => ({
...json, ...json,
dependencies: { dependencies: {
...json.dependencies, ...json.dependencies,
'@angular/core': '~15.2.0', '@angular/core': '~16.2.0',
}, },
})); }));
}); });
@ -1207,13 +1241,13 @@ describe('app', () => {
const { devDependencies } = readJson(appTree, 'package.json'); const { devDependencies } = readJson(appTree, 'package.json');
expect(devDependencies['@angular-devkit/build-angular']).toEqual( expect(devDependencies['@angular-devkit/build-angular']).toEqual(
backwardCompatibleVersions.angularV15.angularDevkitVersion backwardCompatibleVersions.angularV16.angularDevkitVersion
); );
expect(devDependencies['@angular-devkit/schematics']).toEqual( expect(devDependencies['@angular-devkit/schematics']).toEqual(
backwardCompatibleVersions.angularV15.angularDevkitVersion backwardCompatibleVersions.angularV16.angularDevkitVersion
); );
expect(devDependencies['@schematics/angular']).toEqual( expect(devDependencies['@schematics/angular']).toEqual(
backwardCompatibleVersions.angularV15.angularDevkitVersion backwardCompatibleVersions.angularV16.angularDevkitVersion
); );
}); });
@ -1264,6 +1298,21 @@ describe('app', () => {
.esModuleInterop .esModuleInterop
).toBeUndefined(); ).toBeUndefined();
}); });
it('should configure the correct assets for versions lower than v18', async () => {
updateJson(appTree, 'package.json', (json) => ({
...json,
dependencies: { ...json.dependencies, '@angular/core': '~17.0.0' },
}));
await generateApp(appTree, 'my-app', { rootProject: true });
const project = readProjectConfiguration(appTree, 'my-app');
expect(project.targets.build.options.assets).toStrictEqual([
'./src/favicon.ico',
'./src/assets',
]);
});
}); });
}); });

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -2,5 +2,7 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module'; import { AppModule } from './app/app.module';
platformBrowserDynamic() platformBrowserDynamic()
.bootstrapModule(AppModule) .bootstrapModule(AppModule<% if (useEventCoalescing) { %>, {
ngZoneEventCoalescing: true
}<% } %>)
.catch((err) => console.error(err)); .catch((err) => console.error(err));

View File

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

View File

@ -36,6 +36,7 @@ export async function createFiles(
angularMajorVersion, angularMajorVersion,
rootOffset, rootOffset,
isUsingApplicationBuilder, isUsingApplicationBuilder,
useEventCoalescing: angularMajorVersion >= 18,
tpl: '', tpl: '',
}; };
@ -46,6 +47,22 @@ export async function createFiles(
substitutions substitutions
); );
if (angularMajorVersion >= 18) {
generateFiles(
tree,
joinPathFragments(__dirname, '../files/base-18+'),
options.appProjectRoot,
substitutions
);
} else {
generateFiles(
tree,
joinPathFragments(__dirname, '../files/base-pre18'),
options.appProjectRoot,
substitutions
);
}
if (options.standalone) { if (options.standalone) {
generateFiles( generateFiles(
tree, tree,

View File

@ -1,4 +1,4 @@
import { addProjectConfiguration, Tree } from '@nx/devkit'; import { addProjectConfiguration, joinPathFragments, Tree } from '@nx/devkit';
import type { AngularProjectConfiguration } from '../../../utils/types'; import type { AngularProjectConfiguration } from '../../../utils/types';
import { getInstalledAngularVersionInfo } from '../../utils/version-utils'; import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
import type { NormalizedSchema } from './normalized-schema'; import type { NormalizedSchema } from './normalized-schema';
@ -64,12 +64,23 @@ export function createProject(tree: Tree, options: NormalizedSchema) {
index: `${options.appProjectSourceRoot}/index.html`, index: `${options.appProjectSourceRoot}/index.html`,
[buildMainOptionName]: `${options.appProjectSourceRoot}/main.ts`, [buildMainOptionName]: `${options.appProjectSourceRoot}/main.ts`,
polyfills: ['zone.js'], polyfills: ['zone.js'],
tsConfig: `${options.appProjectRoot}/tsconfig.app.json`, tsConfig: joinPathFragments(
options.appProjectRoot,
'tsconfig.app.json'
),
inlineStyleLanguage, inlineStyleLanguage,
assets: [ assets:
`${options.appProjectSourceRoot}/favicon.ico`, angularMajorVersion >= 18
`${options.appProjectSourceRoot}/assets`, ? [
], {
glob: '**/*',
input: joinPathFragments(options.appProjectRoot, 'public'),
},
]
: [
`${options.appProjectSourceRoot}/favicon.ico`,
`${options.appProjectSourceRoot}/assets`,
],
styles: [`${options.appProjectSourceRoot}/styles.${options.style}`], styles: [`${options.appProjectSourceRoot}/styles.${options.style}`],
scripts: [], scripts: [],
}, },

View File

@ -18,9 +18,9 @@ export async function componentTestGenerator(
options: ComponentTestSchema options: ComponentTestSchema
) { ) {
ensurePackage('@nx/cypress', nxVersion); ensurePackage('@nx/cypress', nxVersion);
const { assertMinimumCypressVersion } = await import( const { assertMinimumCypressVersion } = <
'@nx/cypress/src/utils/cypress-version' typeof import('@nx/cypress/src/utils/cypress-version')
); >require('@nx/cypress/src/utils/cypress-version');
assertMinimumCypressVersion(10); assertMinimumCypressVersion(10);
const { root } = readProjectConfiguration(tree, options.project); const { root } = readProjectConfiguration(tree, options.project);
const componentDirPath = joinPathFragments(root, options.componentDir); const componentDirPath = joinPathFragments(root, options.componentDir);

View File

@ -74,7 +74,9 @@ async function addFiles(
'support', 'support',
'component.ts' 'component.ts'
); );
const { addMountDefinition } = await import('@nx/cypress/src/utils/config'); const { addMountDefinition } = <
typeof import('@nx/cypress/src/utils/config')
>require('@nx/cypress/src/utils/config');
const updatedCmpContents = await addMountDefinition( const updatedCmpContents = await addMountDefinition(
tree.read(componentFile, 'utf-8') tree.read(componentFile, 'utf-8')
); );
@ -126,9 +128,9 @@ async function configureCypressCT(
let found: FoundTarget = { target: options.buildTarget, config: undefined }; let found: FoundTarget = { target: options.buildTarget, config: undefined };
if (!options.buildTarget) { if (!options.buildTarget) {
const { findBuildConfig } = await import( const { findBuildConfig } = <
'@nx/cypress/src/utils/find-target-options' typeof import('@nx/cypress/src/utils/find-target-options')
); >require('@nx/cypress/src/utils/find-target-options');
found = await findBuildConfig(tree, { found = await findBuildConfig(tree, {
project: options.project, project: options.project,
buildTarget: options.buildTarget, buildTarget: options.buildTarget,
@ -158,9 +160,9 @@ async function configureCypressCT(
ctConfigOptions.buildTarget = found.target; ctConfigOptions.buildTarget = found.target;
} }
const { addDefaultCTConfig, getProjectCypressConfigPath } = await import( const { addDefaultCTConfig, getProjectCypressConfigPath } = <
'@nx/cypress/src/utils/config' typeof import('@nx/cypress/src/utils/config')
); >require('@nx/cypress/src/utils/config');
const cypressConfigPath = getProjectCypressConfigPath( const cypressConfigPath = getProjectCypressConfigPath(
tree, tree,
projectConfig.root projectConfig.root

View File

@ -96,7 +96,9 @@ exports[`Host App Generator --ssr should generate the correct files 2`] = `
import { AppModule } from './app/app.module'; import { AppModule } from './app/app.module';
platformBrowserDynamic() platformBrowserDynamic()
.bootstrapModule(AppModule) .bootstrapModule(AppModule, {
ngZoneEventCoalescing: true,
})
.catch((err) => console.error(err)); .catch((err) => console.error(err));
" "
`; `;
@ -408,13 +410,17 @@ export const appRoutes: Route[] = [
`; `;
exports[`Host App Generator --ssr should generate the correct files for standalone 8`] = ` exports[`Host App Generator --ssr should generate the correct files for standalone 8`] = `
"import { ApplicationConfig } from '@angular/core'; "import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes'; import { appRoutes } from './app.routes';
import { provideClientHydration } from '@angular/platform-browser'; import { provideClientHydration } from '@angular/platform-browser';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [provideClientHydration(), provideRouter(appRoutes)], providers: [
provideClientHydration(),
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(appRoutes),
],
}; };
" "
`; `;
@ -619,13 +625,17 @@ export const appRoutes: Route[] = [
`; `;
exports[`Host App Generator --ssr should generate the correct files for standalone when --typescript=true 8`] = ` exports[`Host App Generator --ssr should generate the correct files for standalone when --typescript=true 8`] = `
"import { ApplicationConfig } from '@angular/core'; "import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes'; import { appRoutes } from './app.routes';
import { provideClientHydration } from '@angular/platform-browser'; import { provideClientHydration } from '@angular/platform-browser';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [provideClientHydration(), provideRouter(appRoutes)], providers: [
provideClientHydration(),
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(appRoutes),
],
}; };
" "
`; `;
@ -716,7 +726,9 @@ exports[`Host App Generator --ssr should generate the correct files when --types
import { AppModule } from './app/app.module'; import { AppModule } from './app/app.module';
platformBrowserDynamic() platformBrowserDynamic()
.bootstrapModule(AppModule) .bootstrapModule(AppModule, {
ngZoneEventCoalescing: true,
})
.catch((err) => console.error(err)); .catch((err) => console.error(err));
" "
`; `;

View File

@ -73,8 +73,9 @@ export class AngularDevkitKarmaMigrator extends BuilderMigrator {
target.options.main = target.options.main =
target.options.main && this.convertAsset(target.options.main); target.options.main && this.convertAsset(target.options.main);
target.options.polyfills = Array.isArray(target.options.polyfills) target.options.polyfills = Array.isArray(target.options.polyfills)
? target.options.polyfills.map((p) => this.convertAsset(p)) ? target.options.polyfills.map((p) => this.convertSourceRootPath(p))
: target.options.polyfills && this.convertAsset(target.options.polyfills); : target.options.polyfills &&
this.convertSourceRootPath(target.options.polyfills);
target.options.tsConfig = target.options.tsConfig =
target.options.tsConfig && target.options.tsConfig &&
joinPathFragments( joinPathFragments(

View File

@ -36,9 +36,9 @@ export abstract class Migrator {
protected convertAsset(asset: string | any): string | any { protected convertAsset(asset: string | any): string | any {
if (typeof asset === 'string') { if (typeof asset === 'string') {
return this.convertSourceRootPath(asset); return this.convertRootPath(asset);
} else { } else {
return { ...asset, input: this.convertSourceRootPath(asset.input) }; return { ...asset, input: this.convertRootPath(asset.input) };
} }
} }
@ -51,6 +51,15 @@ export abstract class Migrator {
: originalPath; : originalPath;
} }
protected convertSourceRootPath(originalPath: string): string {
return originalPath?.startsWith(this.project.oldSourceRoot)
? joinPathFragments(
this.project.newSourceRoot,
originalPath.replace(this.project.oldSourceRoot, '')
)
: originalPath;
}
protected moveFile(from: string, to: string, required: boolean = true): void { protected moveFile(from: string, to: string, required: boolean = true): void {
if (!this.tree.exists(from)) { if (!this.tree.exists(from)) {
if (required) { if (required) {
@ -120,15 +129,6 @@ export abstract class Migrator {
}); });
} }
private convertSourceRootPath(originalPath: string): string {
return originalPath?.startsWith(this.project.oldSourceRoot)
? joinPathFragments(
this.project.newSourceRoot,
originalPath.replace(this.project.oldSourceRoot, '')
)
: originalPath;
}
private getTargetValuesForOption( private getTargetValuesForOption(
target: TargetConfiguration, target: TargetConfiguration,
optionPath: string optionPath: string

View File

@ -257,8 +257,10 @@ export class AppMigrator extends ProjectMigrator<SupportedTargets> {
buildOptions.polyfills = buildOptions.polyfills =
buildOptions.polyfills && buildOptions.polyfills &&
(Array.isArray(buildOptions.polyfills) (Array.isArray(buildOptions.polyfills)
? buildOptions.polyfills.map((asset) => this.convertAsset(asset)) ? buildOptions.polyfills.map((asset) =>
: this.convertAsset(buildOptions.polyfills as string)); this.convertSourceRootPath(asset)
)
: this.convertSourceRootPath(buildOptions.polyfills));
buildOptions.tsConfig = buildOptions.tsConfig =
buildOptions.tsConfig && buildOptions.tsConfig &&
joinPathFragments(this.project.newRoot, basename(buildOptions.tsConfig)); joinPathFragments(this.project.newRoot, basename(buildOptions.tsConfig));

View File

@ -343,7 +343,9 @@ export class E2eMigrator extends ProjectMigrator<SupportedTargets> {
const addPlugin = const addPlugin =
process.env.NX_ADD_PLUGINS !== 'false' && process.env.NX_ADD_PLUGINS !== 'false' &&
nxJson.useInferencePlugins !== false; nxJson.useInferencePlugins !== false;
const { configurationGenerator } = await import('@nx/cypress'); const { configurationGenerator } = <typeof import('@nx/cypress')>(
require('@nx/cypress')
);
await configurationGenerator(this.tree, { await configurationGenerator(this.tree, {
project: this.project.name, project: this.project.name,
linter: this.isProjectUsingEsLint ? Linter.EsLint : Linter.None, linter: this.isProjectUsingEsLint ? Linter.EsLint : Linter.None,

View File

@ -426,7 +426,7 @@ export class AppModule {}
`; `;
exports[`NgRxRootStoreGenerator Standalone APIs should add a facade when --facade=true 1`] = ` exports[`NgRxRootStoreGenerator Standalone APIs should add a facade when --facade=true 1`] = `
"import { ApplicationConfig } from '@angular/core'; "import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes'; import { appRoutes } from './app.routes';
import { provideStore, provideState } from '@ngrx/store'; import { provideStore, provideState } from '@ngrx/store';
@ -436,7 +436,7 @@ import { UsersEffects } from './+state/users.effects';
import { UsersFacade } from './+state/users.facade'; import { UsersFacade } from './+state/users.facade';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [provideEffects(UsersEffects),provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),UsersFacade,provideEffects(),provideStore(),provideRouter(appRoutes) ] providers: [provideEffects(UsersEffects),provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),UsersFacade,provideEffects(),provideStore(),provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(appRoutes) ]
}; };
" "
`; `;
@ -473,7 +473,7 @@ export class UsersFacade {
`; `;
exports[`NgRxRootStoreGenerator Standalone APIs should add a root module and root state when --minimal=false 1`] = ` exports[`NgRxRootStoreGenerator Standalone APIs should add a root module and root state when --minimal=false 1`] = `
"import { ApplicationConfig } from '@angular/core'; "import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes'; import { appRoutes } from './app.routes';
import { provideStore, provideState } from '@ngrx/store'; import { provideStore, provideState } from '@ngrx/store';
@ -482,7 +482,7 @@ import * as fromUsers from './+state/users.reducer';
import { UsersEffects } from './+state/users.effects'; import { UsersEffects } from './+state/users.effects';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [provideEffects(UsersEffects),provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),provideEffects(),provideStore(),provideRouter(appRoutes) ] providers: [provideEffects(UsersEffects),provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),provideEffects(),provideStore(),provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(appRoutes) ]
}; };
" "
`; `;
@ -722,20 +722,20 @@ describe('Users Selectors', () => {
`; `;
exports[`NgRxRootStoreGenerator Standalone APIs should add an empty root module when --minimal=true 1`] = ` exports[`NgRxRootStoreGenerator Standalone APIs should add an empty root module when --minimal=true 1`] = `
"import { ApplicationConfig } from '@angular/core'; "import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes'; import { appRoutes } from './app.routes';
import { provideStore } from '@ngrx/store'; import { provideStore } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects'; import { provideEffects } from '@ngrx/effects';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [provideEffects(),provideStore(),provideRouter(appRoutes) ] providers: [provideEffects(),provideStore(),provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(appRoutes) ]
}; };
" "
`; `;
exports[`NgRxRootStoreGenerator Standalone APIs should instrument the store devtools when "addDevTools: true" 1`] = ` exports[`NgRxRootStoreGenerator Standalone APIs should instrument the store devtools when "addDevTools: true" 1`] = `
"import { ApplicationConfig, isDevMode } from '@angular/core'; "import { ApplicationConfig, provideZoneChangeDetection, isDevMode } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes'; import { appRoutes } from './app.routes';
import { provideStore } from '@ngrx/store'; import { provideStore } from '@ngrx/store';
@ -743,7 +743,7 @@ import { provideEffects } from '@ngrx/effects';
import { provideStoreDevtools } from '@ngrx/store-devtools'; import { provideStoreDevtools } from '@ngrx/store-devtools';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [provideStoreDevtools({ logOnly: !isDevMode() }),provideEffects(),provideStore(),provideRouter(appRoutes) ] providers: [provideStoreDevtools({ logOnly: !isDevMode() }),provideEffects(),provideStore(),provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(appRoutes) ]
}; };
" "
`; `;

View File

@ -756,7 +756,7 @@ bootstrapApplication(AppComponent, appConfig).catch((err) =>
`; `;
exports[`ngrx Standalone APIs should add a root module with feature module when minimal is set to false 2`] = ` exports[`ngrx Standalone APIs should add a root module with feature module when minimal is set to false 2`] = `
"import { ApplicationConfig } from '@angular/core'; "import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes'; import { appRoutes } from './app.routes';
import { provideStore, provideState } from '@ngrx/store'; import { provideStore, provideState } from '@ngrx/store';
@ -765,7 +765,7 @@ import * as fromUsers from './+state/users.reducer';
import { UsersEffects } from './+state/users.effects'; import { UsersEffects } from './+state/users.effects';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [provideEffects(UsersEffects),provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),provideEffects(),provideStore(),provideRouter(appRoutes) ] providers: [provideEffects(UsersEffects),provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),provideEffects(),provideStore(),provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(appRoutes) ]
}; };
" "
`; `;
@ -782,14 +782,14 @@ bootstrapApplication(AppComponent, appConfig).catch((err) =>
`; `;
exports[`ngrx Standalone APIs should add an empty provideStore when minimal and root are set to true 2`] = ` exports[`ngrx Standalone APIs should add an empty provideStore when minimal and root are set to true 2`] = `
"import { ApplicationConfig } from '@angular/core'; "import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes'; import { appRoutes } from './app.routes';
import { provideStore, provideState } from '@ngrx/store'; import { provideStore, provideState } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects'; import { provideEffects } from '@ngrx/effects';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [provideEffects(),provideStore(),provideRouter(appRoutes) ] providers: [provideEffects(),provideStore(),provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(appRoutes) ]
}; };
" "
`; `;
@ -806,7 +806,7 @@ bootstrapApplication(AppComponent, appConfig).catch((err) =>
`; `;
exports[`ngrx Standalone APIs should add facade provider when facade is true 2`] = ` exports[`ngrx Standalone APIs should add facade provider when facade is true 2`] = `
"import { ApplicationConfig } from '@angular/core'; "import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes'; import { appRoutes } from './app.routes';
import { provideStore, provideState } from '@ngrx/store'; import { provideStore, provideState } from '@ngrx/store';
@ -816,7 +816,7 @@ import { UsersEffects } from './+state/users.effects';
import { UsersFacade } from './+state/users.facade'; import { UsersFacade } from './+state/users.facade';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [provideEffects(UsersEffects),provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),provideEffects(),provideStore(),UsersFacade,provideRouter(appRoutes) ] providers: [provideEffects(UsersEffects),provideState(fromUsers.USERS_FEATURE_KEY, fromUsers.usersReducer),provideEffects(),provideStore(),UsersFacade,provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(appRoutes) ]
}; };
" "
`; `;

View File

@ -674,35 +674,35 @@ export const appRoutes: Routes = [{ path: 'home', component: NxWelcomeComponent
...json, ...json,
dependencies: { dependencies: {
...json.dependencies, ...json.dependencies,
'@angular/core': '15.0.0', '@angular/core': '16.0.0',
}, },
})); }));
}); });
it('should install the ngrx 15 packages', async () => { it('should install the ngrx 16 packages', async () => {
await ngrxGenerator(tree, defaultOptions); await ngrxGenerator(tree, defaultOptions);
const packageJson = devkit.readJson(tree, 'package.json'); const packageJson = devkit.readJson(tree, 'package.json');
expect(packageJson.dependencies['@ngrx/store']).toEqual( expect(packageJson.dependencies['@ngrx/store']).toEqual(
backwardCompatibleVersions.angularV15.ngrxVersion backwardCompatibleVersions.angularV16.ngrxVersion
); );
expect(packageJson.dependencies['@ngrx/effects']).toEqual( expect(packageJson.dependencies['@ngrx/effects']).toEqual(
backwardCompatibleVersions.angularV15.ngrxVersion backwardCompatibleVersions.angularV16.ngrxVersion
); );
expect(packageJson.dependencies['@ngrx/entity']).toEqual( expect(packageJson.dependencies['@ngrx/entity']).toEqual(
backwardCompatibleVersions.angularV15.ngrxVersion backwardCompatibleVersions.angularV16.ngrxVersion
); );
expect(packageJson.dependencies['@ngrx/router-store']).toEqual( expect(packageJson.dependencies['@ngrx/router-store']).toEqual(
backwardCompatibleVersions.angularV15.ngrxVersion backwardCompatibleVersions.angularV16.ngrxVersion
); );
expect(packageJson.dependencies['@ngrx/component-store']).toEqual( expect(packageJson.dependencies['@ngrx/component-store']).toEqual(
backwardCompatibleVersions.angularV15.ngrxVersion backwardCompatibleVersions.angularV16.ngrxVersion
); );
expect(packageJson.devDependencies['@ngrx/schematics']).toEqual( expect(packageJson.devDependencies['@ngrx/schematics']).toEqual(
backwardCompatibleVersions.angularV15.ngrxVersion backwardCompatibleVersions.angularV16.ngrxVersion
); );
expect(packageJson.devDependencies['@ngrx/store-devtools']).toEqual( expect(packageJson.devDependencies['@ngrx/store-devtools']).toEqual(
backwardCompatibleVersions.angularV15.ngrxVersion backwardCompatibleVersions.angularV16.ngrxVersion
); );
expect(packageJson.devDependencies['jasmine-marbles']).toBeDefined(); expect(packageJson.devDependencies['jasmine-marbles']).toBeDefined();
}); });

View File

@ -111,7 +111,9 @@ exports[`MF Remote App Generator --ssr should generate the correct files 2`] = `
import { AppModule } from './app/app.module'; import { AppModule } from './app/app.module';
platformBrowserDynamic() platformBrowserDynamic()
.bootstrapModule(AppModule) .bootstrapModule(AppModule, {
ngZoneEventCoalescing: true,
})
.catch((err) => console.error(err)); .catch((err) => console.error(err));
" "
`; `;
@ -333,7 +335,9 @@ exports[`MF Remote App Generator --ssr should generate the correct files when --
import { AppModule } from './app/app.module'; import { AppModule } from './app/app.module';
platformBrowserDynamic() platformBrowserDynamic()
.bootstrapModule(AppModule) .bootstrapModule(AppModule, {
ngZoneEventCoalescing: true,
})
.catch((err) => console.error(err)); .catch((err) => console.error(err));
" "
`; `;

View File

@ -3,7 +3,7 @@
exports[`Init MF --federationType=dynamic should create a host with the correct configurations 1`] = ` exports[`Init MF --federationType=dynamic should create a host with the correct configurations 1`] = `
"import { setRemoteDefinitions } from '@nx/angular/mf'; "import { setRemoteDefinitions } from '@nx/angular/mf';
fetch('/assets/module-federation.manifest.json') fetch('/module-federation.manifest.json')
.then((res) => res.json()) .then((res) => res.json())
.then(definitions => setRemoteDefinitions(definitions)) .then(definitions => setRemoteDefinitions(definitions))
.then(() => import('./bootstrap').catch(err => console.error(err)));" .then(() => import('./bootstrap').catch(err => console.error(err)));"
@ -12,7 +12,7 @@ fetch('/assets/module-federation.manifest.json')
exports[`Init MF --federationType=dynamic should create a host with the correct configurations when --typescriptConfiguration=true 1`] = ` exports[`Init MF --federationType=dynamic should create a host with the correct configurations when --typescriptConfiguration=true 1`] = `
"import { setRemoteDefinitions } from '@nx/angular/mf'; "import { setRemoteDefinitions } from '@nx/angular/mf';
fetch('/assets/module-federation.manifest.json') fetch('/module-federation.manifest.json')
.then((res) => res.json()) .then((res) => res.json())
.then(definitions => setRemoteDefinitions(definitions)) .then(definitions => setRemoteDefinitions(definitions))
.then(() => import('./bootstrap').catch(err => console.error(err)));" .then(() => import('./bootstrap').catch(err => console.error(err)));"

View File

@ -32,14 +32,8 @@ export function checkIsCommaNeeded(mfRemoteText: string) {
export function addRemoteToHost(tree: Tree, options: AddRemoteOptions) { export function addRemoteToHost(tree: Tree, options: AddRemoteOptions) {
if (options.host) { if (options.host) {
const hostProject = readProjectConfiguration(tree, options.host); const hostProject = readProjectConfiguration(tree, options.host);
const pathToMFManifest = joinPathFragments( const pathToMFManifest = getDynamicManifestFile(tree, hostProject);
hostProject.sourceRoot, const hostFederationType = !!pathToMFManifest ? 'dynamic' : 'static';
'assets/module-federation.manifest.json'
);
const hostFederationType = determineHostFederationType(
tree,
pathToMFManifest
);
const isHostUsingTypescriptConfig = tree.exists( const isHostUsingTypescriptConfig = tree.exists(
joinPathFragments(hostProject.root, 'module-federation.config.ts') joinPathFragments(hostProject.root, 'module-federation.config.ts')
@ -60,11 +54,23 @@ export function addRemoteToHost(tree: Tree, options: AddRemoteOptions) {
} }
} }
function determineHostFederationType( function getDynamicManifestFile(
tree: Tree, tree: Tree,
pathToMfManifest: string project: ProjectConfiguration
): 'dynamic' | 'static' { ): string | undefined {
return tree.exists(pathToMfManifest) ? 'dynamic' : 'static'; // {sourceRoot}/assets/module-federation.manifest.json was the generated
// path for the manifest file in the past. We now generate the manifest
// file at {root}/public/module-federation.manifest.json. This check
// ensures that we can still support the old path for backwards
// compatibility since old projects may still have the manifest file
// at the old path.
return [
joinPathFragments(project.root, 'public/module-federation.manifest.json'),
joinPathFragments(
project.sourceRoot,
'assets/module-federation.manifest.json'
),
].find((path) => tree.exists(path));
} }
function addRemoteToStaticHost( function addRemoteToStaticHost(

View File

@ -1,5 +1,4 @@
import type { Tree } from '@nx/devkit'; import { joinPathFragments, type Tree } from '@nx/devkit';
import { joinPathFragments } from '@nx/devkit';
import type { Schema } from '../schema'; import type { Schema } from '../schema';
export function fixBootstrap(tree: Tree, appRoot: string, options: Schema) { export function fixBootstrap(tree: Tree, appRoot: string, options: Schema) {
@ -12,20 +11,27 @@ export function fixBootstrap(tree: Tree, appRoot: string, options: Schema) {
} }
const bootstrapImportCode = `import('./bootstrap').catch(err => console.error(err))`; const bootstrapImportCode = `import('./bootstrap').catch(err => console.error(err))`;
if (options.mfType === 'remote' || options.federationType === 'static') {
tree.write(mainFilePath, `${bootstrapImportCode};`);
} else {
let manifestPath = '/assets/module-federation.manifest.json';
if (
tree.exists(
joinPathFragments(appRoot, 'public/module-federation.manifest.json')
)
) {
manifestPath = '/module-federation.manifest.json';
}
const fetchMFManifestCode = `import { setRemoteDefinitions } from '@nx/angular/mf'; const fetchMFManifestCode = `import { setRemoteDefinitions } from '@nx/angular/mf';
fetch('/assets/module-federation.manifest.json') fetch('${manifestPath}')
.then((res) => res.json()) .then((res) => res.json())
.then(definitions => setRemoteDefinitions(definitions)) .then(definitions => setRemoteDefinitions(definitions))
.then(() => ${bootstrapImportCode});`; .then(() => ${bootstrapImportCode});`;
tree.write( tree.write(mainFilePath, fetchMFManifestCode);
mainFilePath, }
options.mfType === 'host' && options.federationType === 'dynamic'
? fetchMFManifestCode
: `${bootstrapImportCode};`
);
} }
const standaloneBootstrapCode = const standaloneBootstrapCode =

View File

@ -5,17 +5,25 @@ import {
updateProjectConfiguration, updateProjectConfiguration,
} from '@nx/devkit'; } from '@nx/devkit';
import type { Schema } from '../schema'; import type { Schema } from '../schema';
import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
export function setupHostIfDynamic(tree: Tree, options: Schema) { export function setupHostIfDynamic(tree: Tree, options: Schema) {
if (options.federationType === 'static') { if (options.federationType === 'static') {
return; return;
} }
const { major: angularMajorVersion } = getInstalledAngularVersionInfo(tree);
const project = readProjectConfiguration(tree, options.appName); const project = readProjectConfiguration(tree, options.appName);
const pathToMFManifest = joinPathFragments( const pathToMFManifest =
project.sourceRoot, angularMajorVersion >= 18
'assets/module-federation.manifest.json' ? joinPathFragments(
); project.root,
'public/module-federation.manifest.json'
)
: joinPathFragments(
project.sourceRoot,
'assets/module-federation.manifest.json'
);
if (!tree.exists(pathToMFManifest)) { if (!tree.exists(pathToMFManifest)) {
tree.write(pathToMFManifest, '{}'); tree.write(pathToMFManifest, '{}');

View File

@ -518,7 +518,7 @@ describe('Init MF', () => {
'remotes: []' 'remotes: []'
); );
expect( expect(
tree.exists('app1/src/assets/module-federation.manifest.json') tree.exists('app1/public/module-federation.manifest.json')
).toBeTruthy(); ).toBeTruthy();
expect(tree.read('app1/src/main.ts', 'utf-8')).toMatchSnapshot(); expect(tree.read('app1/src/main.ts', 'utf-8')).toMatchSnapshot();
}); });
@ -540,7 +540,7 @@ describe('Init MF', () => {
'remotes: []' 'remotes: []'
); );
expect( expect(
tree.exists('app1/src/assets/module-federation.manifest.json') tree.exists('app1/public/module-federation.manifest.json')
).toBeTruthy(); ).toBeTruthy();
expect(tree.read('app1/src/main.ts', 'utf-8')).toMatchSnapshot(); expect(tree.read('app1/src/main.ts', 'utf-8')).toMatchSnapshot();
}); });
@ -571,7 +571,7 @@ describe('Init MF', () => {
'remotes: []' 'remotes: []'
); );
expect( expect(
readJson(tree, 'app1/src/assets/module-federation.manifest.json') readJson(tree, 'app1/public/module-federation.manifest.json')
).toEqual({ ).toEqual({
remote1: 'http://localhost:4201', remote1: 'http://localhost:4201',
}); });
@ -606,7 +606,7 @@ describe('Init MF', () => {
'remotes: []' 'remotes: []'
); );
expect( expect(
readJson(tree, 'app1/src/assets/module-federation.manifest.json') readJson(tree, 'app1/public/module-federation.manifest.json')
).toEqual({ ).toEqual({
remote1: 'http://localhost:4201', remote1: 'http://localhost:4201',
}); });
@ -645,7 +645,7 @@ describe('Init MF', () => {
'remotes: []' 'remotes: []'
); );
expect( expect(
readJson(tree, 'app1/src/assets/module-federation.manifest.json') readJson(tree, 'app1/public/module-federation.manifest.json')
).toEqual({ ).toEqual({
remote1: 'http://localhost:4201', remote1: 'http://localhost:4201',
}); });
@ -681,7 +681,7 @@ describe('Init MF', () => {
'remotes: []' 'remotes: []'
); );
expect( expect(
readJson(tree, 'app1/src/assets/module-federation.manifest.json') readJson(tree, 'app1/public/module-federation.manifest.json')
).toEqual({ ).toEqual({
remote1: 'http://localhost:4201', remote1: 'http://localhost:4201',
}); });

View File

@ -56,8 +56,6 @@ export async function setupMf(tree: Tree, rawOptions: Schema) {
updateTsConfig(tree, options); updateTsConfig(tree, options);
setupServeTarget(tree, options); setupServeTarget(tree, options);
fixBootstrap(tree, projectConfig.root, options);
if (options.mfType === 'host') { if (options.mfType === 'host') {
setupHostIfDynamic(tree, options); setupHostIfDynamic(tree, options);
updateHostAppRoutes(tree, options); updateHostAppRoutes(tree, options);
@ -78,6 +76,8 @@ export async function setupMf(tree: Tree, rawOptions: Schema) {
} }
} }
fixBootstrap(tree, projectConfig.root, options);
if (!options.skipE2E) { if (!options.skipE2E) {
addCypressOnErrorWorkaround(tree, options); addCypressOnErrorWorkaround(tree, options);
} }

View File

@ -91,8 +91,10 @@ exports[`setupSSR with application builder should create the files correctly for
"executor": "@angular-devkit/build-angular:application", "executor": "@angular-devkit/build-angular:application",
"options": { "options": {
"assets": [ "assets": [
"app1/src/favicon.ico", {
"app1/src/assets", "glob": "**/*",
"input": "app1/public",
},
], ],
"browser": "app1/src/main.ts", "browser": "app1/src/main.ts",
"index": "app1/src/index.html", "index": "app1/src/index.html",
@ -141,14 +143,15 @@ export function app(): express.Express {
// server.get('/api/**', (req, res) => { }); // server.get('/api/**', (req, res) => { });
// Serve static files from /browser // Serve static files from /browser
server.get( server.get(
'*.*', '**',
express.static(browserDistFolder, { express.static(browserDistFolder, {
maxAge: '1y', maxAge: '1y',
index: 'index.html',
}) })
); );
// All regular routes use the Angular engine // All regular routes use the Angular engine
server.get('*', (req, res, next) => { server.get('**', (req, res, next) => {
const { protocol, originalUrl, baseUrl, headers } = req; const { protocol, originalUrl, baseUrl, headers } = req;
commonEngine commonEngine
@ -208,8 +211,10 @@ exports[`setupSSR with application builder should create the files correctly for
"executor": "@angular-devkit/build-angular:application", "executor": "@angular-devkit/build-angular:application",
"options": { "options": {
"assets": [ "assets": [
"app1/src/favicon.ico", {
"app1/src/assets", "glob": "**/*",
"input": "app1/public",
},
], ],
"browser": "app1/src/main.ts", "browser": "app1/src/main.ts",
"index": "app1/src/index.html", "index": "app1/src/index.html",
@ -258,14 +263,15 @@ export function app(): express.Express {
// server.get('/api/**', (req, res) => { }); // server.get('/api/**', (req, res) => { });
// Serve static files from /browser // Serve static files from /browser
server.get( server.get(
'*.*', '**',
express.static(browserDistFolder, { express.static(browserDistFolder, {
maxAge: '1y', maxAge: '1y',
index: 'index.html',
}) })
); );
// All regular routes use the Angular engine // All regular routes use the Angular engine
server.get('*', (req, res, next) => { server.get('**', (req, res, next) => {
const { protocol, originalUrl, baseUrl, headers } = req; const { protocol, originalUrl, baseUrl, headers } = req;
commonEngine commonEngine
@ -350,12 +356,13 @@ export function app(): express.Express {
// Example Express Rest API endpoints // Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { }); // server.get('/api/**', (req, res) => { });
// Serve static files from /browser // Serve static files from /browser
server.get('*.*', express.static(distFolder, { server.get('**', express.static(distFolder, {
maxAge: '1y' maxAge: '1y',
index: 'index.html'
})); }));
// All regular routes use the Angular engine // All regular routes use the Angular engine
server.get('*', (req, res, next) => { server.get('**', (req, res, next) => {
const { protocol, originalUrl, baseUrl, headers } = req; const { protocol, originalUrl, baseUrl, headers } = req;
commonEngine commonEngine
@ -450,12 +457,13 @@ export function app(): express.Express {
// Example Express Rest API endpoints // Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { }); // server.get('/api/**', (req, res) => { });
// Serve static files from /browser // Serve static files from /browser
server.get('*.*', express.static(distFolder, { server.get('**', express.static(distFolder, {
maxAge: '1y' maxAge: '1y',
index: 'index.html'
})); }));
// All regular routes use the Angular engine // All regular routes use the Angular engine
server.get('*', (req, res, next) => { server.get('**', (req, res, next) => {
const { protocol, originalUrl, baseUrl, headers } = req; const { protocol, originalUrl, baseUrl, headers } = req;
commonEngine commonEngine

View File

@ -1,14 +0,0 @@
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
function bootstrap() {
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));
}
if (document.readyState !== 'loading') {
bootstrap();
} else {
document.addEventListener('DOMContentLoaded', bootstrap);
}

View File

@ -20,12 +20,13 @@ export function app(): express.Express {
// Example Express Rest API endpoints // Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { }); // server.get('/api/**', (req, res) => { });
// Serve static files from /browser // Serve static files from /browser
server.get('*.*', express.static(browserDistFolder, { server.get('**', express.static(browserDistFolder, {
maxAge: '1y' maxAge: '1y',
index: 'index.html',
})); }));
// All regular routes use the Angular engine // All regular routes use the Angular engine
server.get('*', (req, res, next) => { server.get('**', (req, res, next) => {
const { protocol, originalUrl, baseUrl, headers } = req; const { protocol, originalUrl, baseUrl, headers } = req;
commonEngine commonEngine

View File

@ -23,12 +23,13 @@ export function app(): express.Express {
// Example Express Rest API endpoints // Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { }); // server.get('/api/**', (req, res) => { });
// Serve static files from /browser // Serve static files from /browser
server.get('*.*', express.static(distFolder, { server.get('**', express.static(distFolder, {
maxAge: '1y' maxAge: '1y',
index: 'index.html'
})); }));
// All regular routes use the Angular engine // All regular routes use the Angular engine
server.get('*', (req, res, next) => { server.get('**', (req, res, next) => {
const { protocol, originalUrl, baseUrl, headers } = req; const { protocol, originalUrl, baseUrl, headers } = req;
commonEngine commonEngine

View File

@ -1,6 +1,6 @@
import { addDependenciesToPackageJson, type Tree } from '@nx/devkit'; import { addDependenciesToPackageJson, type Tree } from '@nx/devkit';
import { gte } from 'semver'; import { gte } from 'semver';
import type { PackageCompatVersions } from '../../../utils/backward-compatible-versions'; import type { VersionMap } from '../../../utils/backward-compatible-versions';
import { import {
getInstalledAngularVersionInfo, getInstalledAngularVersionInfo,
getInstalledPackageVersionInfo, getInstalledPackageVersionInfo,
@ -35,10 +35,11 @@ export function addDependencies(
} else { } else {
dependencies['@nguniversal/express-engine'] = dependencies['@nguniversal/express-engine'] =
getInstalledPackageVersionInfo(tree, '@nguniversal/express-engine') getInstalledPackageVersionInfo(tree, '@nguniversal/express-engine')
?.version ?? (pkgVersions as PackageCompatVersions).ngUniversalVersion; ?.version ??
(pkgVersions as VersionMap['angularV16']).ngUniversalVersion;
devDependencies['@nguniversal/builders'] = devDependencies['@nguniversal/builders'] =
getInstalledPackageVersionInfo(tree, '@nguniversal/builders')?.version ?? getInstalledPackageVersionInfo(tree, '@nguniversal/builders')?.version ??
(pkgVersions as PackageCompatVersions).ngUniversalVersion; (pkgVersions as VersionMap['angularV16']).ngUniversalVersion;
} }
addDependenciesToPackageJson(tree, dependencies, devDependencies); addDependenciesToPackageJson(tree, dependencies, devDependencies);

View File

@ -4,8 +4,6 @@ import {
joinPathFragments, joinPathFragments,
readProjectConfiguration, readProjectConfiguration,
} from '@nx/devkit'; } from '@nx/devkit';
import { lt } from 'semver';
import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
import type { Schema } from '../schema'; import type { Schema } from '../schema';
export function generateSSRFiles( export function generateSSRFiles(
@ -27,7 +25,6 @@ export function generateSSRFiles(
} }
const pathToFiles = joinPathFragments(__dirname, '..', 'files'); const pathToFiles = joinPathFragments(__dirname, '..', 'files');
const { version: angularVersion } = getInstalledAngularVersionInfo(tree);
if (schema.standalone) { if (schema.standalone) {
generateFiles( generateFiles(
@ -39,18 +36,9 @@ export function generateSSRFiles(
} else { } else {
generateFiles( generateFiles(
tree, tree,
joinPathFragments(pathToFiles, 'ngmodule', 'base'), joinPathFragments(pathToFiles, 'ngmodule'),
projectRoot, projectRoot,
{ ...schema, tpl: '' } { ...schema, tpl: '' }
); );
if (lt(angularVersion, '15.2.0')) {
generateFiles(
tree,
joinPathFragments(pathToFiles, 'ngmodule', 'pre-v15-2'),
projectRoot,
{ ...schema, tpl: '' }
);
}
} }
} }

View File

@ -3,7 +3,6 @@ export * from './add-server-file';
export * from './generate-files'; export * from './generate-files';
export * from './generate-server-ts-config'; export * from './generate-server-ts-config';
export * from './normalize-options'; export * from './normalize-options';
export * from './update-app-module';
export * from './update-project-config'; export * from './update-project-config';
export * from './validate-options'; export * from './validate-options';
export * from './add-hydration'; export * from './add-hydration';

View File

@ -1,47 +0,0 @@
import type { Tree } from '@nx/devkit';
import { joinPathFragments, readProjectConfiguration } from '@nx/devkit';
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
import { getInstalledAngularMajorVersion } from '../../utils/version-utils';
import type { Schema } from '../schema';
export function updateAppModule(tree: Tree, schema: Schema) {
const angularMajorVersion = getInstalledAngularMajorVersion(tree);
if (angularMajorVersion >= 16) {
return;
}
ensureTypescript();
const { tsquery } = require('@phenomnomnominal/tsquery');
// read the content of app module
const projectConfig = readProjectConfiguration(tree, schema.project);
const pathToAppModule = joinPathFragments(
projectConfig.sourceRoot,
'app/app.module.ts'
);
const fileContents = tree.read(pathToAppModule, 'utf-8');
const ast = tsquery.ast(fileContents);
const browserModuleImportNodes = tsquery(
ast,
'PropertyAssignment:has(Identifier[name=imports]) > ArrayLiteralExpression Identifier[name=BrowserModule]',
{ visitAllChildren: true }
);
if (browserModuleImportNodes.length === 0) {
throw new Error(
`Could not find BrowserModule declaration in ${pathToAppModule}. Please ensure this is correct.`
);
}
const browserModuleNode = browserModuleImportNodes[0];
const newFileContents = `${fileContents.slice(
0,
browserModuleNode.getEnd()
)}.withServerTransition({ appId: '${schema.appId}' })${fileContents.slice(
browserModuleNode.getEnd(),
fileContents.length
)}`;
tree.write(pathToAppModule, newFileContents);
}

View File

@ -1,14 +1,11 @@
import type { Tree } from '@nx/devkit'; import type { Tree } from '@nx/devkit';
import { readProjectConfiguration, stripIndents } from '@nx/devkit'; import { readProjectConfiguration } from '@nx/devkit';
import { validateProject as validateExistingProject } from '../../utils/validations'; import { validateProject as validateExistingProject } from '../../utils/validations';
import type { Schema } from '../schema'; import type { Schema } from '../schema';
import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
import { lt } from 'semver';
export function validateOptions(tree: Tree, options: Schema): void { export function validateOptions(tree: Tree, options: Schema): void {
validateProject(tree, options.project); validateProject(tree, options.project);
validateBuildTarget(tree, options.project); validateBuildTarget(tree, options.project);
validateHydrationOption(tree, options.hydration);
} }
function validateProject(tree: Tree, project: string): void { function validateProject(tree: Tree, project: string): void {
@ -31,16 +28,3 @@ function validateBuildTarget(tree: Tree, project: string): void {
); );
} }
} }
function validateHydrationOption(tree: Tree, hydration: boolean): void {
if (!hydration) {
return;
}
const installedAngularVersion = getInstalledAngularVersionInfo(tree).version;
if (lt(installedAngularVersion, '16.0.0')) {
throw new Error(stripIndents`The "hydration" option is only supported in Angular >= 16.0.0. You are currently using "${installedAngularVersion}".
You can resolve this error by removing the "hydration" option or by migrating to Angular 16.0.0.`);
}
}

View File

@ -19,8 +19,9 @@
"appId": { "appId": {
"type": "string", "type": "string",
"format": "html-selector", "format": "html-selector",
"description": "The `appId` to use with `withServerTransition`. _Note: This is only used in Angular versions <16.0.0. It's deprecated since Angular 16 and not supported since Angular 17._", "description": "The `appId` to use with `withServerTransition`.",
"default": "serverApp" "default": "serverApp",
"x-deprecated": "This is deprecated and ignored since Angular 16 and not supported since Angular 17."
}, },
"main": { "main": {
"type": "string", "type": "string",
@ -55,7 +56,7 @@
}, },
"hydration": { "hydration": {
"type": "boolean", "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`. _Note: This is only supported in Angular versions >= 16.0.0_." "description": "Set up Hydration for the SSR application. It defaults to `true` for Angular versions >= 17.0.0. Otherwise, it defaults to `false`."
}, },
"skipFormat": { "skipFormat": {
"type": "boolean", "type": "boolean",

View File

@ -48,7 +48,9 @@ describe('setupSSR', () => {
import { AppModule } from './app/app.module'; import { AppModule } from './app/app.module';
platformBrowserDynamic() platformBrowserDynamic()
.bootstrapModule(AppModule) .bootstrapModule(AppModule, {
ngZoneEventCoalescing: true,
})
.catch((err) => console.error(err)); .catch((err) => console.error(err));
" "
`); `);
@ -214,7 +216,9 @@ describe('setupSSR', () => {
import { AppModule } from './app/app.module'; import { AppModule } from './app/app.module';
platformBrowserDynamic() platformBrowserDynamic()
.bootstrapModule(AppModule) .bootstrapModule(AppModule, {
ngZoneEventCoalescing: true
})
.catch((err) => console.error(err)); .catch((err) => console.error(err));
" "
`); `);
@ -464,13 +468,13 @@ describe('setupSSR', () => {
// ASSERT // ASSERT
expect(tree.read('app1/src/app/app.config.ts', 'utf-8')) expect(tree.read('app1/src/app/app.config.ts', 'utf-8'))
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"import { ApplicationConfig } from '@angular/core'; "import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes'; import { appRoutes } from './app.routes';
import { provideClientHydration } from '@angular/platform-browser'; import { provideClientHydration } from '@angular/platform-browser';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [provideClientHydration(),provideRouter(appRoutes) ] providers: [provideClientHydration(),provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(appRoutes) ]
}; };
" "
`); `);
@ -542,12 +546,12 @@ describe('setupSSR', () => {
expect(tree.read('app1/src/app/app.config.ts', 'utf-8')) expect(tree.read('app1/src/app/app.config.ts', 'utf-8'))
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"import { ApplicationConfig } from '@angular/core'; "import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter, withEnabledBlockingInitialNavigation } from '@angular/router'; import { provideRouter, withEnabledBlockingInitialNavigation } from '@angular/router';
import { appRoutes } from './app.routes'; import { appRoutes } from './app.routes';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [provideRouter(appRoutes, withEnabledBlockingInitialNavigation()) ] providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(appRoutes, withEnabledBlockingInitialNavigation()) ]
}; };
" "
`); `);
@ -567,7 +571,7 @@ describe('setupSSR', () => {
updateJson(tree, 'package.json', (json) => ({ updateJson(tree, 'package.json', (json) => ({
...json, ...json,
dependencies: { dependencies: {
'@angular/core': '15.2.0', '@angular/core': '16.2.0',
}, },
})); }));
@ -578,63 +582,22 @@ describe('setupSSR', () => {
const pkgJson = readJson(tree, 'package.json'); const pkgJson = readJson(tree, 'package.json');
expect(pkgJson.dependencies['@angular/ssr']).toBeUndefined(); expect(pkgJson.dependencies['@angular/ssr']).toBeUndefined();
expect(pkgJson.dependencies['@angular/platform-server']).toEqual( expect(pkgJson.dependencies['@angular/platform-server']).toEqual(
backwardCompatibleVersions.angularV15.angularVersion backwardCompatibleVersions.angularV16.angularVersion
); );
expect(pkgJson.dependencies['@nguniversal/express-engine']).toEqual( expect(pkgJson.dependencies['@nguniversal/express-engine']).toEqual(
backwardCompatibleVersions.angularV15.ngUniversalVersion backwardCompatibleVersions.angularV16.ngUniversalVersion
); );
expect(pkgJson.devDependencies['@nguniversal/builders']).toEqual( expect(pkgJson.devDependencies['@nguniversal/builders']).toEqual(
backwardCompatibleVersions.angularV15.ngUniversalVersion backwardCompatibleVersions.angularV16.ngUniversalVersion
); );
}); });
it('should add "withServerTransition" call to app module for angular versions lower than 16', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
updateJson(tree, 'package.json', (json) => ({
...json,
dependencies: { ...json.dependencies, '@angular/core': '^15.2.0' },
}));
await generateTestApplication(tree, {
name: 'app1',
standalone: false,
skipFormat: true,
});
// ACT
await setupSsr(tree, { project: 'app1', skipFormat: true });
// ASSERT
expect(tree.read('app1/src/app/app.module.ts', 'utf-8'))
.toMatchInlineSnapshot(`
"import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { appRoutes } from './app.routes';
import { NxWelcomeComponent } from './nx-welcome.component';
@NgModule({
declarations: [AppComponent, NxWelcomeComponent],
imports: [
BrowserModule.withServerTransition({ appId: 'serverApp' }),
RouterModule.forRoot(appRoutes, { initialNavigation: 'enabledBlocking' }),
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
"
`);
});
it('should set "initialNavigation: enabledBlocking" in "RouterModule.forRoot" options', async () => { it('should set "initialNavigation: enabledBlocking" in "RouterModule.forRoot" options', async () => {
// ARRANGE // ARRANGE
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
updateJson(tree, 'package.json', (json) => ({ updateJson(tree, 'package.json', (json) => ({
...json, ...json,
dependencies: { ...json.dependencies, '@angular/core': '^15.2.0' }, dependencies: { ...json.dependencies, '@angular/core': '^16.2.0' },
})); }));
await generateTestApplication(tree, { await generateTestApplication(tree, {
@ -659,7 +622,7 @@ describe('setupSSR', () => {
@NgModule({ @NgModule({
declarations: [AppComponent, NxWelcomeComponent], declarations: [AppComponent, NxWelcomeComponent],
imports: [ imports: [
BrowserModule.withServerTransition({ appId: 'serverApp' }), BrowserModule,
RouterModule.forRoot(appRoutes, { initialNavigation: 'enabledBlocking' }), RouterModule.forRoot(appRoutes, { initialNavigation: 'enabledBlocking' }),
], ],
providers: [], providers: [],
@ -675,7 +638,7 @@ describe('setupSSR', () => {
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
updateJson(tree, 'package.json', (json) => ({ updateJson(tree, 'package.json', (json) => ({
...json, ...json,
dependencies: { ...json.dependencies, '@angular/core': '^15.2.0' }, dependencies: { ...json.dependencies, '@angular/core': '^16.2.0' },
})); }));
await generateTestApplication(tree, { name: 'app1', skipFormat: true }); await generateTestApplication(tree, { name: 'app1', skipFormat: true });
@ -686,7 +649,7 @@ describe('setupSSR', () => {
// ASSERT // ASSERT
expect(tree.read('app1/src/app/app.config.ts', 'utf-8')) expect(tree.read('app1/src/app/app.config.ts', 'utf-8'))
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"import { ApplicationConfig } from '@angular/platform-browser'; "import { ApplicationConfig } from '@angular/core';
import { provideRouter, withEnabledBlockingInitialNavigation } from '@angular/router'; import { provideRouter, withEnabledBlockingInitialNavigation } from '@angular/router';
import { appRoutes } from './app.routes'; import { appRoutes } from './app.routes';
@ -697,48 +660,12 @@ describe('setupSSR', () => {
`); `);
}); });
it('should wrap bootstrap call for Angular versions lower than 15.2', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
await generateTestApplication(tree, {
name: 'app1',
standalone: false,
skipFormat: true,
});
updateJson(tree, 'package.json', (json) => ({
...json,
dependencies: { '@angular/core': '15.1.0' },
}));
// ACT
await setupSsr(tree, { project: 'app1', skipFormat: true });
// ASSERT
expect(tree.read('app1/src/main.ts', 'utf-8')).toMatchInlineSnapshot(`
"import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
function bootstrap() {
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));
}
if (document.readyState !== 'loading') {
bootstrap();
} else {
document.addEventListener('DOMContentLoaded', bootstrap);
}
"
`);
});
it('should generate a correct server.ts', async () => { it('should generate a correct server.ts', async () => {
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
updateJson(tree, 'package.json', (json) => ({ updateJson(tree, 'package.json', (json) => ({
...json, ...json,
dependencies: { dependencies: {
'@angular/core': '15.2.0', '@angular/core': '16.2.0',
}, },
})); }));
await generateTestApplication(tree, { await generateTestApplication(tree, {
@ -757,7 +684,7 @@ describe('setupSSR', () => {
updateJson(tree, 'package.json', (json) => ({ updateJson(tree, 'package.json', (json) => ({
...json, ...json,
dependencies: { dependencies: {
'@angular/core': '15.2.0', '@angular/core': '16.2.0',
}, },
})); }));
await generateTestApplication(tree, { name: 'app1', skipFormat: true }); await generateTestApplication(tree, { name: 'app1', skipFormat: true });
@ -766,7 +693,7 @@ describe('setupSSR', () => {
expect(tree.read('app1/src/app/app.config.ts', 'utf-8')) expect(tree.read('app1/src/app/app.config.ts', 'utf-8'))
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"import { ApplicationConfig } from '@angular/platform-browser'; "import { ApplicationConfig } from '@angular/core';
import { provideRouter, withEnabledBlockingInitialNavigation } from '@angular/router'; import { provideRouter, withEnabledBlockingInitialNavigation } from '@angular/router';
import { appRoutes } from './app.routes'; import { appRoutes } from './app.routes';

View File

@ -14,7 +14,6 @@ import {
normalizeOptions, normalizeOptions,
setRouterInitialNavigation, setRouterInitialNavigation,
setServerTsConfigOptionsForApplicationBuilder, setServerTsConfigOptionsForApplicationBuilder,
updateAppModule,
updateProjectConfigForApplicationBuilder, updateProjectConfigForApplicationBuilder,
updateProjectConfigForBrowserBuilder, updateProjectConfigForBrowserBuilder,
validateOptions, validateOptions,
@ -35,9 +34,6 @@ export async function setupSsr(tree: Tree, schema: Schema) {
} }
generateSSRFiles(tree, options, isUsingApplicationBuilder); generateSSRFiles(tree, options, isUsingApplicationBuilder);
if (!options.standalone) {
updateAppModule(tree, options);
}
if (options.hydration) { if (options.hydration) {
addHydration(tree, options); addHydration(tree, options);
} }

View File

@ -10,9 +10,9 @@ export async function generateStories(
) { ) {
const project = readProjectConfiguration(tree, options.project); const project = readProjectConfiguration(tree, options.project);
ensurePackage('@nx/cypress', nxVersion); ensurePackage('@nx/cypress', nxVersion);
const { getE2eProjectName } = await import( const { getE2eProjectName } = <
'@nx/cypress/src/utils/project-name' typeof import('@nx/cypress/src/utils/project-name')
); >require('@nx/cypress/src/utils/project-name');
const e2eProjectName = getE2eProjectName( const e2eProjectName = getE2eProjectName(
options.project, options.project,
project.root, project.root,

View File

@ -60,10 +60,10 @@ export function versions(
): PackageLatestVersions | PackageCompatVersions { ): PackageLatestVersions | PackageCompatVersions {
const majorAngularVersion = getInstalledAngularMajorVersion(tree); const majorAngularVersion = getInstalledAngularMajorVersion(tree);
switch (majorAngularVersion) { switch (majorAngularVersion) {
case 15:
return backwardCompatibleVersions.angularV15;
case 16: case 16:
return backwardCompatibleVersions.angularV16; return backwardCompatibleVersions.angularV16;
case 17:
return backwardCompatibleVersions.angularV17;
default: default:
return latestVersions; return latestVersions;
} }

View File

@ -0,0 +1,42 @@
import { readJson, Tree, writeJson } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import updateAngularCli, { angularCliVersion } from './update-angular-cli';
describe('update-angular-cli migration', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
});
it('should update @angular/cli version when defined as a dev dependency', async () => {
writeJson(tree, 'package.json', {
devDependencies: { '@angular/cli': '~13.3.0' },
});
await updateAngularCli(tree);
const { devDependencies } = readJson(tree, 'package.json');
expect(devDependencies['@angular/cli']).toBe(angularCliVersion);
});
it('should update @angular/cli version when defined as a dependency', async () => {
writeJson(tree, 'package.json', {
dependencies: { '@angular/cli': '~13.3.0' },
});
await updateAngularCli(tree);
const { dependencies } = readJson(tree, 'package.json');
expect(dependencies['@angular/cli']).toBe(angularCliVersion);
});
it('should not add @angular/cli to package.json when it is not set', async () => {
const initialPackageJson = readJson(tree, 'package.json');
await updateAngularCli(tree);
const packageJson = readJson(tree, 'package.json');
expect(packageJson).toStrictEqual(initialPackageJson);
});
});

View File

@ -0,0 +1,23 @@
import { formatFiles, Tree, updateJson } from '@nx/devkit';
export const angularCliVersion = '~18.0.0';
export default async function (tree: Tree) {
let shouldFormat = false;
updateJson(tree, 'package.json', (json) => {
if (json.devDependencies?.['@angular/cli']) {
json.devDependencies['@angular/cli'] = angularCliVersion;
shouldFormat = true;
} else if (json.dependencies?.['@angular/cli']) {
json.dependencies['@angular/cli'] = angularCliVersion;
shouldFormat = true;
}
return json;
});
if (shouldFormat) {
await formatFiles(tree);
}
}

View File

@ -1,50 +1,32 @@
import * as latestVersions from './versions'; import * as latestVersions from './versions';
type SupportedVersions = 'angularV15' | 'angularV16'; type SupportedVersions = 'angularV16' | 'angularV17';
export type LatestPackageVersionNames = Exclude< type LatestPackageVersionNames = Exclude<
keyof typeof latestVersions, keyof typeof latestVersions,
'nxVersion' 'nxVersion'
>; >;
export type CompatPackageVersionNames = type CompatPackageVersionNames =
| LatestPackageVersionNames | LatestPackageVersionNames
// Can be removed when dropping support for Angular v16 // Can be removed when dropping support for Angular v16
| 'ngUniversalVersion'; | 'ngUniversalVersion';
export type PackageLatestVersions = Record<LatestPackageVersionNames, string>; export type PackageVersionNames =
export type PackageCompatVersions = Record<CompatPackageVersionNames, string>; | LatestPackageVersionNames
| CompatPackageVersionNames;
export const backwardCompatibleVersions: Record< export type VersionMap = {
SupportedVersions, angularV16: Record<CompatPackageVersionNames, string>;
PackageCompatVersions angularV17: Record<
> = { Exclude<CompatPackageVersionNames, 'ngUniversalVersion'>,
angularV15: { string
angularVersion: '~15.2.0', >;
angularDevkitVersion: '~15.2.0', };
ngPackagrVersion: '~15.2.2',
ngrxVersion: '~15.3.0', export type PackageLatestVersions = Record<LatestPackageVersionNames, string>;
rxjsVersion: '~7.8.0', export type PackageCompatVersions = VersionMap[SupportedVersions];
zoneJsVersion: '~0.12.0',
angularJsVersion: '1.7.9', export const backwardCompatibleVersions: VersionMap = {
tsLibVersion: '^2.3.0',
ngUniversalVersion: '~15.1.0',
corsVersion: '~2.8.5',
typesCorsVersion: '~2.8.5',
expressVersion: '~4.18.2',
typesExpressVersion: '4.17.14',
browserSyncVersion: '^3.0.0',
moduleFederationNodeVersion: '~1.0.5',
angularEslintVersion: '~15.0.0',
tailwindVersion: '^3.0.2',
postcssVersion: '^8.4.5',
postcssUrlVersion: '~10.1.3',
autoprefixerVersion: '^10.4.0',
tsNodeVersion: '10.9.1',
jestPresetAngularVersion: '~13.0.0',
typesNodeVersion: '16.11.7',
jasmineMarblesVersion: '^0.9.2',
jsoncEslintParserVersion: '^2.1.0',
},
angularV16: { angularV16: {
angularVersion: '~16.2.0', angularVersion: '~16.2.0',
angularDevkitVersion: '~16.2.0', angularDevkitVersion: '~16.2.0',
@ -72,4 +54,30 @@ export const backwardCompatibleVersions: Record<
jasmineMarblesVersion: '^0.9.2', jasmineMarblesVersion: '^0.9.2',
jsoncEslintParserVersion: '^2.1.0', jsoncEslintParserVersion: '^2.1.0',
}, },
angularV17: {
angularVersion: '~17.3.0',
angularDevkitVersion: '~17.3.0',
ngPackagrVersion: '~17.3.0',
ngrxVersion: '~17.0.0',
rxjsVersion: '~7.8.0',
zoneJsVersion: '~0.14.3',
angularJsVersion: '1.7.9',
tsLibVersion: '^2.3.0',
corsVersion: '~2.8.5',
typesCorsVersion: '~2.8.5',
expressVersion: '~4.18.2',
typesExpressVersion: '4.17.14',
browserSyncVersion: '^3.0.0',
moduleFederationNodeVersion: '~1.0.5',
angularEslintVersion: '~17.3.0',
tailwindVersion: '^3.0.2',
postcssVersion: '^8.4.5',
postcssUrlVersion: '~10.1.3',
autoprefixerVersion: '^10.4.0',
tsNodeVersion: '10.9.1',
jestPresetAngularVersion: '~14.0.3',
typesNodeVersion: '18.16.9',
jasmineMarblesVersion: '^0.9.2',
jsoncEslintParserVersion: '^2.1.0',
},
}; };

View File

@ -1,6 +1,6 @@
import { ModuleFederationConfig } from '@nx/webpack/src/utils/module-federation'; import { ModuleFederationConfig } from '@nx/webpack/src/utils/module-federation';
import { getModuleFederationConfig } from './utils'; import { getModuleFederationConfig } from './utils';
import ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); import ModuleFederationPlugin from 'webpack/lib/container/ModuleFederationPlugin';
export async function withModuleFederation(options: ModuleFederationConfig) { export async function withModuleFederation(options: ModuleFederationConfig) {
if (global.NX_GRAPH_CREATION) { if (global.NX_GRAPH_CREATION) {

View File

@ -1,16 +1,15 @@
import { coerce, major } from 'semver'; import { coerce, major } from 'semver';
import type { import type {
CompatPackageVersionNames,
LatestPackageVersionNames,
PackageCompatVersions, PackageCompatVersions,
PackageLatestVersions, PackageLatestVersions,
PackageVersionNames,
} from './backward-compatible-versions'; } from './backward-compatible-versions';
import { backwardCompatibleVersions } from './backward-compatible-versions'; import { backwardCompatibleVersions } from './backward-compatible-versions';
import * as versions from './versions'; import * as versions from './versions';
import { angularVersion } from './versions'; import { angularVersion } from './versions';
export function getPkgVersionForAngularMajorVersion( export function getPkgVersionForAngularMajorVersion(
pkgVersionName: LatestPackageVersionNames | CompatPackageVersionNames, pkgVersionName: PackageVersionNames,
angularMajorVersion: number angularMajorVersion: number
): string { ): string {
return angularMajorVersion < major(coerce(angularVersion)) return angularMajorVersion < major(coerce(angularVersion))

View File

@ -1,8 +1,8 @@
export const nxVersion = require('../../package.json').version; export const nxVersion = require('../../package.json').version;
export const angularVersion = '~17.3.0'; export const angularVersion = '~18.0.0';
export const angularDevkitVersion = '~17.3.0'; export const angularDevkitVersion = '~18.0.0';
export const ngPackagrVersion = '~17.3.0'; export const ngPackagrVersion = '~18.0.0';
export const ngrxVersion = '~17.0.0'; export const ngrxVersion = '~17.0.0';
export const rxjsVersion = '~7.8.0'; export const rxjsVersion = '~7.8.0';
export const zoneJsVersion = '~0.14.3'; export const zoneJsVersion = '~0.14.3';
@ -23,7 +23,7 @@ export const postcssUrlVersion = '~10.1.3';
export const autoprefixerVersion = '^10.4.0'; export const autoprefixerVersion = '^10.4.0';
export const tsNodeVersion = '10.9.1'; export const tsNodeVersion = '10.9.1';
export const jestPresetAngularVersion = '~14.0.3'; export const jestPresetAngularVersion = '~14.1.0';
export const typesNodeVersion = '18.16.9'; export const typesNodeVersion = '18.16.9';
export const jasmineMarblesVersion = '^0.9.2'; export const jasmineMarblesVersion = '^0.9.2';

View File

@ -10,6 +10,9 @@
{ {
"path": "./tsconfig.lib.json" "path": "./tsconfig.lib.json"
}, },
{
"path": "./tsconfig.lib.runtime.json"
},
{ {
"path": "./tsconfig.spec.json" "path": "./tsconfig.spec.json"
} }

View File

@ -1,7 +1,8 @@
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
"module": "commonjs", "moduleResolution": "node16",
"module": "node16",
"outDir": "../../dist/out-tsc", "outDir": "../../dist/out-tsc",
"declaration": true, "declaration": true,
"types": ["node"] "types": ["node"]
@ -12,7 +13,11 @@
"**/*_spec.ts", "**/*_spec.ts",
"**/*_test.ts", "**/*_test.ts",
"jest.config.ts", "jest.config.ts",
"test-setup.ts" "test-setup.ts",
"index.ts",
"src/runtime/**/*",
"mf/**/*",
"testing/**/*"
], ],
"include": ["**/*.ts"] "include": ["**/*.ts"]
} }

View File

@ -1,8 +1,10 @@
{ {
"extends": "./tsconfig.lib.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
"module": "esnext" "module": "esnext",
"declaration": true
}, },
"include": ["index.ts", "src/runtime/**/*", "mf/**/*", "testing/**/*"],
"angularCompilerOptions": { "angularCompilerOptions": {
"compilationMode": "partial" "compilationMode": "partial"
} }

View File

@ -19,9 +19,10 @@ import { connectExistingRepoToNxCloudPrompt } from '../../../connect/connect-to-
// key is major Angular version and value is Nx version to use // key is major Angular version and value is Nx version to use
const nxAngularLegacyVersionMap: Record<number, string> = { const nxAngularLegacyVersionMap: Record<number, string> = {
14: '~17.0.0', 14: '~17.0.0',
15: '~19.0.0',
}; };
// min major angular version supported in latest Nx // min major angular version supported in latest Nx
const minMajorAngularVersionSupported = 15; const minMajorAngularVersionSupported = 16;
// version when the Nx CLI changed from @nrwl/tao & @nrwl/cli to nx // version when the Nx CLI changed from @nrwl/tao & @nrwl/cli to nx
const versionWithConsolidatedPackages = '13.9.0'; const versionWithConsolidatedPackages = '13.9.0';
// version when packages were rescoped from @nrwl/* to @nx/* // version when packages were rescoped from @nrwl/* to @nx/*

View File

@ -138,8 +138,6 @@ function createNxJson(
inputs: ['default', '^production'], inputs: ['default', '^production'],
}; };
} }
// Angular 15 workspaces still support defaultProject. Support was removed in Angular 16.
nxJson.defaultProject = angularJson.defaultProject;
writeJsonFile(join(repoRoot, 'nx.json'), nxJson); writeJsonFile(join(repoRoot, 'nx.json'), nxJson);
} }

View File

@ -7,6 +7,7 @@ exports[`preset should create files (preset = angular-monorepo) 1`] = `
"tsconfig.app.json", "tsconfig.app.json",
"tsconfig.editor.json", "tsconfig.editor.json",
"tsconfig.json", "tsconfig.json",
"public",
".eslintrc.json", ".eslintrc.json",
"jest.config.ts", "jest.config.ts",
"tsconfig.spec.json", "tsconfig.spec.json",
@ -15,8 +16,6 @@ exports[`preset should create files (preset = angular-monorepo) 1`] = `
exports[`preset should create files (preset = angular-monorepo) 2`] = ` exports[`preset should create files (preset = angular-monorepo) 2`] = `
[ [
"assets",
"favicon.ico",
"index.html", "index.html",
"styles.css", "styles.css",
"app", "app",

View File

@ -4,4 +4,4 @@ export const typescriptVersion = '~5.4.2';
// TODO: remove when preset generation is reworked and // TODO: remove when preset generation is reworked and
// deps are not installed from workspace // deps are not installed from workspace
export const angularCliVersion = '~17.3.0'; export const angularCliVersion = '~18.0.0';

3624
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff