From 79d6d09a0554d5ad76ba9f4be32eb5503b95c1c0 Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Tue, 15 Jul 2025 15:41:00 -0700 Subject: [PATCH 1/2] feat(@schematics/angular): Applications are zoneless by default This change updates applications to omit the ZoneJS dependency by default. It depends on https://github.com/angular/angular/pull/62655, which allows us to also exclude the `provideZonelessChangeDetection` provider. --- .../src/app/app-module.ts.template | 5 +- .../module-files/src/app/app.spec.ts.template | 6 +-- .../src/app/app.config.ts.template | 6 +-- .../src/app/app.spec.ts.template | 6 +-- .../angular/application/index_spec.ts | 53 ++----------------- .../angular/application/schema.json | 3 +- .../schematics/angular/ng-new/schema.json | 3 +- .../e2e/initialize/500-create-project.ts | 2 +- .../generate/application/application-basic.ts | 2 +- .../application/application-zoneless.ts | 4 +- 10 files changed, 20 insertions(+), 70 deletions(-) diff --git a/packages/schematics/angular/application/files/module-files/src/app/app-module.ts.template b/packages/schematics/angular/application/files/module-files/src/app/app-module.ts.template index 6adc80524f72..8b91651c49d5 100644 --- a/packages/schematics/angular/application/files/module-files/src/app/app-module.ts.template +++ b/packages/schematics/angular/application/files/module-files/src/app/app-module.ts.template @@ -1,4 +1,4 @@ -import { NgModule, provideBrowserGlobalErrorListeners<% if(zoneless) { %>, provideZonelessChangeDetection<% } %> } from '@angular/core'; +import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; <% if (routing) { %> import { AppRoutingModule } from './app-routing-module';<% } %> @@ -13,8 +13,7 @@ import { App } from './app'; AppRoutingModule<% } %> ], providers: [ - provideBrowserGlobalErrorListeners()<% if (zoneless) { %>, - provideZonelessChangeDetection()<% } %> + provideBrowserGlobalErrorListeners() ], bootstrap: [App] }) diff --git a/packages/schematics/angular/application/files/module-files/src/app/app.spec.ts.template b/packages/schematics/angular/application/files/module-files/src/app/app.spec.ts.template index 43a982f1f3b1..155b20acd0d6 100644 --- a/packages/schematics/angular/application/files/module-files/src/app/app.spec.ts.template +++ b/packages/schematics/angular/application/files/module-files/src/app/app.spec.ts.template @@ -1,5 +1,4 @@ -<% if(zoneless) { %>import { provideZonelessChangeDetection } from '@angular/core'; -<% } %>import { TestBed } from '@angular/core/testing';<% if (routing) { %> +import { TestBed } from '@angular/core/testing';<% if (routing) { %> import { RouterModule } from '@angular/router';<% } %> import { App } from './app'; @@ -11,8 +10,7 @@ describe('App', () => { ],<% } %> declarations: [ App - ],<% if(zoneless) { %> - providers: [provideZonelessChangeDetection()]<% } %> + ], }).compileComponents(); }); diff --git a/packages/schematics/angular/application/files/standalone-files/src/app/app.config.ts.template b/packages/schematics/angular/application/files/standalone-files/src/app/app.config.ts.template index 638b2acd363a..8f0e1b0dc23a 100644 --- a/packages/schematics/angular/application/files/standalone-files/src/app/app.config.ts.template +++ b/packages/schematics/angular/application/files/standalone-files/src/app/app.config.ts.template @@ -1,12 +1,12 @@ -import { ApplicationConfig, provideBrowserGlobalErrorListeners, <% if(!zoneless) { %>provideZoneChangeDetection<% } else { %>provideZonelessChangeDetection<% } %> } from '@angular/core';<% if (routing) { %> +import { ApplicationConfig, provideBrowserGlobalErrorListeners<% if(!zoneless) { %>, provideZoneChangeDetection<% } %> } from '@angular/core';<% if (routing) { %> import { provideRouter } from '@angular/router'; import { routes } from './app.routes';<% } %> export const appConfig: ApplicationConfig = { providers: [ - provideBrowserGlobalErrorListeners(), - <% if(zoneless) { %>provideZonelessChangeDetection()<% } else { %>provideZoneChangeDetection({ eventCoalescing: true })<% } %>, + provideBrowserGlobalErrorListeners(),<% if(!zoneless) { %> + provideZoneChangeDetection({ eventCoalescing: true }),<% } %> <% if (routing) {%>provideRouter(routes)<% } %> ] }; diff --git a/packages/schematics/angular/application/files/standalone-files/src/app/app.spec.ts.template b/packages/schematics/angular/application/files/standalone-files/src/app/app.spec.ts.template index 808723635d96..5e235dfb423e 100644 --- a/packages/schematics/angular/application/files/standalone-files/src/app/app.spec.ts.template +++ b/packages/schematics/angular/application/files/standalone-files/src/app/app.spec.ts.template @@ -1,12 +1,10 @@ -<% if(zoneless) { %>import { provideZonelessChangeDetection } from '@angular/core'; -<% } %>import { TestBed } from '@angular/core/testing'; +import { TestBed } from '@angular/core/testing'; import { App } from './app'; describe('App', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [App],<% if(zoneless) { %> - providers: [provideZonelessChangeDetection()]<% } %> + imports: [App], }).compileComponents(); }); diff --git a/packages/schematics/angular/application/index_spec.ts b/packages/schematics/angular/application/index_spec.ts index 6db8671aabb6..edc883068b00 100644 --- a/packages/schematics/angular/application/index_spec.ts +++ b/packages/schematics/angular/application/index_spec.ts @@ -296,7 +296,7 @@ describe('Application Schematic', () => { expect(pkg.devDependencies['less']).toEqual(latestVersions['less']); }); - it('should include zone.js if "zoneless" option is not present', async () => { + it('should _not_ include zone.js if "zoneless" option is not present', async () => { const tree = await schematicRunner.runSchematic( 'application', { @@ -307,7 +307,7 @@ describe('Application Schematic', () => { ); const pkg = JSON.parse(tree.readContent('/package.json')); - expect(pkg.dependencies['zone.js']).toEqual(latestVersions['zone.js']); + expect(pkg.dependencies['zone.js']).toBeUndefined(); }); it('should not include zone.js if "zoneless" option is true', async () => { @@ -800,7 +800,7 @@ describe('Application Schematic', () => { ); }); - it('should add provideZonelessChangeDetection() in app-module.ts when zoneless is true', async () => { + it('should not add provideZonelessChangeDetection() in app-module.ts when zoneless is true', async () => { const tree = await schematicRunner.runSchematic( 'application', { @@ -812,53 +812,10 @@ describe('Application Schematic', () => { ); const path = '/projects/foo/src/app/app-module.ts'; const fileContent = tree.readContent(path); - expect(fileContent).toContain('provideZonelessChangeDetection()'); - }); - - it('should not add provideZonelessChangeDetection() in app-module.ts when zoneless is false', async () => { - const tree = await schematicRunner.runSchematic( - 'application', - { - ...defaultOptions, - zoneless: false, - standalone: false, - }, - workspaceTree, - ); - const path = '/projects/foo/src/app/app-module.ts'; - const fileContent = tree.readContent(path); - expect(fileContent).not.toContain('provideZonelessChangeDetection()'); - }); - - it('should add provideZonelessChangeDetection() when zoneless is true', async () => { - const tree = await schematicRunner.runSchematic( - 'application', - { - ...defaultOptions, - zoneless: true, - }, - workspaceTree, - ); - const path = '/projects/foo/src/app/app.config.ts'; - const fileContent = tree.readContent(path); - expect(fileContent).toContain('provideZonelessChangeDetection()'); - }); - - it('should not add provideZonelessChangeDetection() when zoneless is false', async () => { - const tree = await schematicRunner.runSchematic( - 'application', - { - ...defaultOptions, - zoneless: false, - }, - workspaceTree, - ); - const path = '/projects/foo/src/app/app.config.ts'; - const fileContent = tree.readContent(path); expect(fileContent).not.toContain('provideZonelessChangeDetection()'); }); - it('should not add provideZoneChangeDetection when zoneless is true', async () => { + it('should not add any change detection provider when zoneless is true', async () => { const tree = await schematicRunner.runSchematic( 'application', { @@ -869,7 +826,7 @@ describe('Application Schematic', () => { ); const path = '/projects/foo/src/app/app.config.ts'; const fileContent = tree.readContent(path); - expect(fileContent).not.toContain('provideZoneChangeDetection'); + expect(fileContent).not.toMatch(/provideZone(less)?ChangeDetection/gi); }); }); }); diff --git a/packages/schematics/angular/application/schema.json b/packages/schematics/angular/application/schema.json index 85952ef00e3c..7765f50c7594 100644 --- a/packages/schematics/angular/application/schema.json +++ b/packages/schematics/angular/application/schema.json @@ -120,9 +120,8 @@ }, "zoneless": { "description": "Generate an application that does not use `zone.js`.", - "x-prompt": "Do you want to create a 'zoneless' application without zone.js (Developer Preview)?", "type": "boolean", - "default": false + "default": true } }, "required": ["name"] diff --git a/packages/schematics/angular/ng-new/schema.json b/packages/schematics/angular/ng-new/schema.json index d6381afce198..14f567c83203 100644 --- a/packages/schematics/angular/ng-new/schema.json +++ b/packages/schematics/angular/ng-new/schema.json @@ -141,9 +141,8 @@ }, "zoneless": { "description": "Create an initial application that does not utilize `zone.js`.", - "x-prompt": "Do you want to create a 'zoneless' application without zone.js (Developer Preview)?", "type": "boolean", - "default": false + "default": true } }, "required": ["name", "version"] diff --git a/tests/legacy-cli/e2e/initialize/500-create-project.ts b/tests/legacy-cli/e2e/initialize/500-create-project.ts index 837f54efbcde..dfc48be927a7 100644 --- a/tests/legacy-cli/e2e/initialize/500-create-project.ts +++ b/tests/legacy-cli/e2e/initialize/500-create-project.ts @@ -20,7 +20,7 @@ export default async function () { // Ensure local test registry is used when outside a project await setNPMConfigRegistry(true); - await ng('new', 'test-project', '--skip-install'); + await ng('new', 'test-project', '--skip-install', '--no-zoneless'); await expectFileToExist(join(process.cwd(), 'test-project')); process.chdir('./test-project'); diff --git a/tests/legacy-cli/e2e/tests/generate/application/application-basic.ts b/tests/legacy-cli/e2e/tests/generate/application/application-basic.ts index 615e08426c2b..bff84ac1e37b 100644 --- a/tests/legacy-cli/e2e/tests/generate/application/application-basic.ts +++ b/tests/legacy-cli/e2e/tests/generate/application/application-basic.ts @@ -3,7 +3,7 @@ import { ng } from '../../../utils/process'; import { useCIChrome } from '../../../utils/project'; export default function () { - return ng('generate', 'application', 'app2') + return ng('generate', 'application', 'app2', '--no-zoneless') .then(() => expectFileToMatch('angular.json', /\"app2\":/)) .then(() => useCIChrome('app2', 'projects/app2')) .then(() => ng('test', 'app2', '--watch=false')); diff --git a/tests/legacy-cli/e2e/tests/generate/application/application-zoneless.ts b/tests/legacy-cli/e2e/tests/generate/application/application-zoneless.ts index b7ceaddf3cb1..721ce8fe3599 100644 --- a/tests/legacy-cli/e2e/tests/generate/application/application-zoneless.ts +++ b/tests/legacy-cli/e2e/tests/generate/application/application-zoneless.ts @@ -2,12 +2,12 @@ import { ng } from '../../../utils/process'; import { useCIChrome } from '../../../utils/project'; export default async function () { - await ng('generate', 'app', 'standalone', '--zoneless', '--standalone'); + await ng('generate', 'app', 'standalone', '--standalone'); await useCIChrome('standalone', 'projects/standalone'); await ng('test', 'standalone', '--watch=false'); await ng('build', 'standalone'); - await ng('generate', 'app', 'ngmodules', '--zoneless', '--no-standalone', '--skip-install'); + await ng('generate', 'app', 'ngmodules', '--no-standalone', '--skip-install'); await useCIChrome('ngmodules', 'projects/ngmodules'); await ng('test', 'ngmodules', '--watch=false'); await ng('build', 'ngmodules'); From bba6ec943aef271a0144ab00f86deb1030984e81 Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Mon, 21 Jul 2025 14:11:35 -0700 Subject: [PATCH 2/2] fixup! feat(@schematics/angular): Applications are zoneless by default --- .../e2e/tests/build/app-shell/app-shell-ngmodule.ts | 10 +++++++++- tests/legacy-cli/e2e/tests/build/jit-ngmodule.ts | 11 +++++++++-- .../build/server-rendering/express-engine-ngmodule.ts | 9 ++++++++- .../e2e/tests/commands/builder-project-by-cwd.ts | 4 ++-- .../commands/project-cannot-be-determined-by-cwd.ts | 4 ++-- tests/legacy-cli/e2e/tests/misc/multiple-targets.ts | 2 +- 6 files changed, 31 insertions(+), 9 deletions(-) diff --git a/tests/legacy-cli/e2e/tests/build/app-shell/app-shell-ngmodule.ts b/tests/legacy-cli/e2e/tests/build/app-shell/app-shell-ngmodule.ts index b1b6cfab499d..1175f083dcfe 100644 --- a/tests/legacy-cli/e2e/tests/build/app-shell/app-shell-ngmodule.ts +++ b/tests/legacy-cli/e2e/tests/build/app-shell/app-shell-ngmodule.ts @@ -7,7 +7,15 @@ import { updateJsonFile } from '../../../utils/project'; const snapshots = require('../../../ng-snapshot/package.json'); export default async function () { - await ng('generate', 'app', 'test-project-two', '--routing', '--no-standalone', '--skip-install'); + await ng( + 'generate', + 'app', + 'test-project-two', + '--routing', + '--no-standalone', + '--skip-install', + '--no-zoneless', + ); await ng('generate', 'app-shell', '--project', 'test-project-two'); const isSnapshotBuild = getGlobalVariable('argv')['ng-snapshots']; diff --git a/tests/legacy-cli/e2e/tests/build/jit-ngmodule.ts b/tests/legacy-cli/e2e/tests/build/jit-ngmodule.ts index 910f8993a16d..8dda4d6a02d6 100644 --- a/tests/legacy-cli/e2e/tests/build/jit-ngmodule.ts +++ b/tests/legacy-cli/e2e/tests/build/jit-ngmodule.ts @@ -3,8 +3,15 @@ import { ng } from '../../utils/process'; import { updateJsonFile, useCIChrome, useCIDefaults } from '../../utils/project'; export default async function () { - await ng('generate', 'app', 'test-project-two', '--no-standalone', '--skip-install'); - await ng('generate', 'private-e2e', '--related-app-name=test-project-two'); + await ng( + 'generate', + 'app', + 'test-project-two', + '--no-standalone', + '--skip-install', + '--no-zoneless', + ); + await ng('generate', 'private-e2e', '--related-app-name=test-project-two', '--no-zoneless'); // Setup testing to use CI Chrome. await useCIChrome('test-project-two', './projects/test-project-two/e2e'); diff --git a/tests/legacy-cli/e2e/tests/build/server-rendering/express-engine-ngmodule.ts b/tests/legacy-cli/e2e/tests/build/server-rendering/express-engine-ngmodule.ts index f05d2182bbd2..f8b45482535e 100644 --- a/tests/legacy-cli/e2e/tests/build/server-rendering/express-engine-ngmodule.ts +++ b/tests/legacy-cli/e2e/tests/build/server-rendering/express-engine-ngmodule.ts @@ -15,7 +15,14 @@ export default async function () { // forcibly remove in case another test doesn't clean itself up await rimraf('node_modules/@angular/ssr'); - await ng('generate', 'app', 'test-project-two', '--no-standalone', '--skip-install'); + await ng( + 'generate', + 'app', + 'test-project-two', + '--no-standalone', + '--skip-install', + '--no-zoneless', + ); await ng('generate', 'private-e2e', '--related-app-name=test-project-two'); // Setup testing to use CI Chrome. diff --git a/tests/legacy-cli/e2e/tests/commands/builder-project-by-cwd.ts b/tests/legacy-cli/e2e/tests/commands/builder-project-by-cwd.ts index 6033f4542391..519274db5458 100644 --- a/tests/legacy-cli/e2e/tests/commands/builder-project-by-cwd.ts +++ b/tests/legacy-cli/e2e/tests/commands/builder-project-by-cwd.ts @@ -3,8 +3,8 @@ import { expectFileToExist } from '../../utils/fs'; import { ng } from '../../utils/process'; export default async function () { - await ng('generate', 'app', 'second-app', '--skip-install'); - await ng('generate', 'app', 'third-app', '--skip-install'); + await ng('generate', 'app', 'second-app', '--skip-install', '--no-zoneless'); + await ng('generate', 'app', 'third-app', '--skip-install', '--no-zoneless'); const startCwd = process.cwd(); try { diff --git a/tests/legacy-cli/e2e/tests/commands/project-cannot-be-determined-by-cwd.ts b/tests/legacy-cli/e2e/tests/commands/project-cannot-be-determined-by-cwd.ts index 5f63a5ff0467..5f9b4e787e9d 100644 --- a/tests/legacy-cli/e2e/tests/commands/project-cannot-be-determined-by-cwd.ts +++ b/tests/legacy-cli/e2e/tests/commands/project-cannot-be-determined-by-cwd.ts @@ -13,8 +13,8 @@ export default async function () { delete workspaceJson.projects['test-project']; }); - await ng('generate', 'app', 'second-app', '--skip-install'); - await ng('generate', 'app', 'third-app', '--skip-install'); + await ng('generate', 'app', 'second-app', '--skip-install', '--no-zoneless'); + await ng('generate', 'app', 'third-app', '--skip-install', '--no-zoneless'); const startCwd = process.cwd(); diff --git a/tests/legacy-cli/e2e/tests/misc/multiple-targets.ts b/tests/legacy-cli/e2e/tests/misc/multiple-targets.ts index a99a37d54b06..512ca748db50 100644 --- a/tests/legacy-cli/e2e/tests/misc/multiple-targets.ts +++ b/tests/legacy-cli/e2e/tests/misc/multiple-targets.ts @@ -2,7 +2,7 @@ import { expectFileToExist } from '../../utils/fs'; import { ng } from '../../utils/process'; export default async function () { - await ng('generate', 'app', 'secondary-app'); + await ng('generate', 'app', 'secondary-app', '--no-zoneless'); await ng('build', 'secondary-app', '--configuration=development'); await expectFileToExist('dist/secondary-app/browser/index.html'); await expectFileToExist('dist/secondary-app/browser/main.js');