Skip to content

Commit 1a2767a

Browse files
committed
feat(@schematics/angular): Applications are zoneless by default
This change updates applications to omit the ZoneJS dependency by default. It depends on angular/angular#63382, which allows us to also exclude the `provideZonelessChangeDetection` provider. This change also includes the addition of `provideZoneChangeDetection` in the `initTestEnvironment` when ZoneJS is detected in the configuration (either on window or in the polyfills).
1 parent a60de9b commit 1a2767a

File tree

21 files changed

+87
-85
lines changed

21 files changed

+87
-85
lines changed

packages/angular/build/src/builders/karma/application_builder.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,14 +436,18 @@ async function initializeApplication(
436436
externalDependencies: options.externalDependencies,
437437
};
438438

439+
const usesZoneJS = buildOptions.polyfills.includes('zone.js');
439440
const virtualTestBedInit = createVirtualModulePlugin({
440441
namespace: 'angular:test-bed-init',
441442
loadContent: async () => {
442443
const contents: string[] = [
443444
// Initialize the Angular testing environment
445+
`import { NgModule${usesZoneJS ? ', provideZoneChangeDetection' : ''} } from '@angular/core';`,
444446
`import { getTestBed } from '@angular/core/testing';`,
445447
`import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';`,
446-
`getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting(), {`,
448+
`@NgModule({ providers: [${usesZoneJS ? 'provideZoneChangeDetection(), ' : ''}], })`,
449+
`export class TestModule {}`,
450+
`getTestBed().initTestEnvironment([BrowserTestingModule, TestModule], platformBrowserTesting(), {`,
447451
` errorOnUnknownElements: true,`,
448452
` errorOnUnknownProperties: true,`,
449453
'});',

packages/angular/build/src/builders/unit-test/runners/vitest/build-options.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@ import { OutputHashing } from '../../../application/schema';
1313
import { NormalizedUnitTestBuilderOptions, injectTestingPolyfills } from '../../options';
1414
import { findTests, getTestEntrypoints } from '../../test-discovery';
1515
import { RunnerOptions } from '../api';
16+
import { u } from 'tar';
1617

1718
function createTestBedInitVirtualFile(
1819
providersFile: string | undefined,
1920
projectSourceRoot: string,
21+
polyfills: string[] = [],
2022
): string {
23+
const usesZoneJS = polyfills.includes('zone.js');
2124
let providersImport = 'const providers = [];';
2225
if (providersFile) {
2326
const relativePath = path.relative(projectSourceRoot, providersFile);
@@ -28,15 +31,15 @@ function createTestBedInitVirtualFile(
2831

2932
return `
3033
// Initialize the Angular testing environment
31-
import { NgModule } from '@angular/core';
34+
import { NgModule${usesZoneJS ? ', provideZoneChangeDetection' : ''} } from '@angular/core';
3235
import { getTestBed, ɵgetCleanupHook as getCleanupHook } from '@angular/core/testing';
3336
import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';
3437
${providersImport}
3538
// Same as https://github.com/angular/angular/blob/05a03d3f975771bb59c7eefd37c01fa127ee2229/packages/core/testing/srcs/test_hooks.ts#L21-L29
3639
beforeEach(getCleanupHook(false));
3740
afterEach(getCleanupHook(true));
3841
@NgModule({
39-
providers,
42+
providers: [${usesZoneJS ? 'provideZoneChangeDetection(), ' : ''}...providers],
4043
})
4144
export class TestModule {}
4245
getTestBed().initTestEnvironment([BrowserTestingModule, TestModule], platformBrowserTesting(), {
@@ -113,7 +116,11 @@ export async function getVitestBuildOptions(
113116

114117
buildOptions.polyfills = injectTestingPolyfills(buildOptions.polyfills);
115118

116-
const testBedInitContents = createTestBedInitVirtualFile(providersFile, projectSourceRoot);
119+
const testBedInitContents = createTestBedInitVirtualFile(
120+
providersFile,
121+
projectSourceRoot,
122+
buildOptions.polyfills,
123+
);
117124

118125
return {
119126
buildOptions,

packages/angular_devkit/build_angular/src/builders/jest/init-test-bed.mjs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,16 @@
99
// TODO(dgp1130): These imports likely don't resolve in stricter package environments like `pnpm`, since they are resolved relative to
1010
// `@angular-devkit/build-angular` rather than the user's workspace. Should look into virtual modules to support those use cases.
1111

12+
import { provideZoneChangeDetection, NgModule } from '@angular/core';
1213
import { getTestBed } from '@angular/core/testing';
1314
import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';
1415

15-
getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting(), {
16+
@NgModule({
17+
providers: [typeof window.Zone !== 'undefined' ? provideZoneChangeDetection() : []],
18+
})
19+
class TestModule {}
20+
21+
getTestBed().initTestEnvironment([BrowserTestingModule, TestModule], platformBrowserTesting(), {
1622
errorOnUnknownElements: true,
1723
errorOnUnknownProperties: true,
1824
});

packages/angular_devkit/build_angular/src/builders/karma/browser_builder.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,14 +149,18 @@ async function initializeBrowser(
149149
return [karma, (await webpackConfigurationTransformer?.(config)) ?? config];
150150
}
151151

152-
function getBuiltInMainFile(): string {
152+
function getBuiltInMainFile(includeZoneProvider = false): string {
153153
const content = Buffer.from(
154154
`
155+
import { NgModule${includeZoneProvider ? ', provideZoneChangeDetection' : ''} } from '@angular/core';
155156
import { getTestBed } from '@angular/core/testing';
156157
import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';
157158
159+
@NgModule({providers: [${includeZoneProvider ? 'provideZoneChangeDetection(), ' : ''}]})
160+
export class TestModule {}
161+
158162
// Initialize the Angular testing environment.
159-
getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting(), {
163+
getTestBed().initTestEnvironment([BrowserTestingModule, TestModule], platformBrowserTesting(), {
160164
errorOnUnknownElements: true,
161165
errorOnUnknownProperties: true
162166
});

packages/angular_devkit/build_angular/src/builders/web-test-runner/jasmine_runner.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9+
import { NgModule } from '@angular/core';
910
import { getTestBed } from '@angular/core/testing';
1011
import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';
1112
import {
@@ -63,8 +64,13 @@ export async function runJasmineTests(jasmineEnv) {
6364
// eslint-disable-next-line no-undef
6465
jasmine.DEFAULT_TIMEOUT_INTERVAL = config.defaultTimeoutInterval;
6566

67+
@NgModule({
68+
providers: [typeof window.Zone !== 'undefined' ? provideZoneChangeDetection() : []],
69+
})
70+
class TestModule {}
71+
6672
// Initialize `TestBed` automatically for users. This assumes we already evaluated `zone.js/testing`.
67-
getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting(), {
73+
getTestBed().initTestEnvironment([BrowserTestingModule, TestModule], platformBrowserTesting(), {
6874
errorOnUnknownElements: true,
6975
errorOnUnknownProperties: true,
7076
});

packages/schematics/angular/application/files/module-files/src/app/app-module.ts.template

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NgModule, provideBrowserGlobalErrorListeners<% if(zoneless) { %>, provideZonelessChangeDetection<% } %> } from '@angular/core';
1+
import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';
22
import { BrowserModule } from '@angular/platform-browser';
33
<% if (routing) { %>
44
import { AppRoutingModule } from './app-routing-module';<% } %>
@@ -13,8 +13,7 @@ import { App } from './app';
1313
AppRoutingModule<% } %>
1414
],
1515
providers: [
16-
provideBrowserGlobalErrorListeners()<% if (zoneless) { %>,
17-
provideZonelessChangeDetection()<% } %>
16+
provideBrowserGlobalErrorListeners()
1817
],
1918
bootstrap: [App]
2019
})

packages/schematics/angular/application/files/module-files/src/app/app__suffix__.spec.ts.template

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
<% if(zoneless) { %>import { provideZonelessChangeDetection } from '@angular/core';
2-
<% } %>import { TestBed } from '@angular/core/testing';<% if (routing) { %>
1+
import { TestBed } from '@angular/core/testing';<% if (routing) { %>
32
import { RouterModule } from '@angular/router';<% } %>
43
import { App } from './app';
54

@@ -11,8 +10,7 @@ describe('App', () => {
1110
],<% } %>
1211
declarations: [
1312
App
14-
],<% if(zoneless) { %>
15-
providers: [provideZonelessChangeDetection()]<% } %>
13+
],
1614
}).compileComponents();
1715
});
1816

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { ApplicationConfig, provideBrowserGlobalErrorListeners, <% if(!zoneless) { %>provideZoneChangeDetection<% } else { %>provideZonelessChangeDetection<% } %> } from '@angular/core';<% if (routing) { %>
1+
import { ApplicationConfig, provideBrowserGlobalErrorListeners<% if(!zoneless) { %>, provideZoneChangeDetection<% } %> } from '@angular/core';<% if (routing) { %>
22
import { provideRouter } from '@angular/router';
33

44
import { routes } from './app.routes';<% } %>
55

66
export const appConfig: ApplicationConfig = {
77
providers: [
8-
provideBrowserGlobalErrorListeners(),
9-
<% if(zoneless) { %>provideZonelessChangeDetection()<% } else { %>provideZoneChangeDetection({ eventCoalescing: true })<% } %>,
8+
provideBrowserGlobalErrorListeners(),<% if(!zoneless) { %>
9+
provideZoneChangeDetection({ eventCoalescing: true }),<% } %>
1010
<% if (routing) {%>provideRouter(routes)<% } %>
1111
]
1212
};

packages/schematics/angular/application/files/standalone-files/src/app/app__suffix__.spec.ts.template

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
<% if(zoneless) { %>import { provideZonelessChangeDetection } from '@angular/core';
2-
<% } %>import { TestBed } from '@angular/core/testing';
1+
import { TestBed } from '@angular/core/testing';
32
import { App } from './app';
43

54
describe('App', () => {
65
beforeEach(async () => {
76
await TestBed.configureTestingModule({
8-
imports: [App],<% if(zoneless) { %>
9-
providers: [provideZonelessChangeDetection()]<% } %>
7+
imports: [App],
108
}).compileComponents();
119
});
1210

packages/schematics/angular/application/index_spec.ts

Lines changed: 5 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ describe('Application Schematic', () => {
296296
expect(pkg.devDependencies['less']).toEqual(latestVersions['less']);
297297
});
298298

299-
it('should include zone.js if "zoneless" option is not present', async () => {
299+
it('should _not_ include zone.js if "zoneless" option is not present', async () => {
300300
const tree = await schematicRunner.runSchematic(
301301
'application',
302302
{
@@ -307,7 +307,7 @@ describe('Application Schematic', () => {
307307
);
308308

309309
const pkg = JSON.parse(tree.readContent('/package.json'));
310-
expect(pkg.dependencies['zone.js']).toEqual(latestVersions['zone.js']);
310+
expect(pkg.dependencies['zone.js']).toBeUndefined();
311311
});
312312

313313
it('should not include zone.js if "zoneless" option is true', async () => {
@@ -800,7 +800,7 @@ describe('Application Schematic', () => {
800800
);
801801
});
802802

803-
it('should add provideZonelessChangeDetection() in app-module.ts when zoneless is true', async () => {
803+
it('should not add provideZonelessChangeDetection() in app-module.ts when zoneless is true', async () => {
804804
const tree = await schematicRunner.runSchematic(
805805
'application',
806806
{
@@ -812,53 +812,10 @@ describe('Application Schematic', () => {
812812
);
813813
const path = '/projects/foo/src/app/app-module.ts';
814814
const fileContent = tree.readContent(path);
815-
expect(fileContent).toContain('provideZonelessChangeDetection()');
816-
});
817-
818-
it('should not add provideZonelessChangeDetection() in app-module.ts when zoneless is false', async () => {
819-
const tree = await schematicRunner.runSchematic(
820-
'application',
821-
{
822-
...defaultOptions,
823-
zoneless: false,
824-
standalone: false,
825-
},
826-
workspaceTree,
827-
);
828-
const path = '/projects/foo/src/app/app-module.ts';
829-
const fileContent = tree.readContent(path);
830-
expect(fileContent).not.toContain('provideZonelessChangeDetection()');
831-
});
832-
833-
it('should add provideZonelessChangeDetection() when zoneless is true', async () => {
834-
const tree = await schematicRunner.runSchematic(
835-
'application',
836-
{
837-
...defaultOptions,
838-
zoneless: true,
839-
},
840-
workspaceTree,
841-
);
842-
const path = '/projects/foo/src/app/app.config.ts';
843-
const fileContent = tree.readContent(path);
844-
expect(fileContent).toContain('provideZonelessChangeDetection()');
845-
});
846-
847-
it('should not add provideZonelessChangeDetection() when zoneless is false', async () => {
848-
const tree = await schematicRunner.runSchematic(
849-
'application',
850-
{
851-
...defaultOptions,
852-
zoneless: false,
853-
},
854-
workspaceTree,
855-
);
856-
const path = '/projects/foo/src/app/app.config.ts';
857-
const fileContent = tree.readContent(path);
858815
expect(fileContent).not.toContain('provideZonelessChangeDetection()');
859816
});
860817

861-
it('should not add provideZoneChangeDetection when zoneless is true', async () => {
818+
it('should not add any change detection provider when zoneless is true', async () => {
862819
const tree = await schematicRunner.runSchematic(
863820
'application',
864821
{
@@ -869,7 +826,7 @@ describe('Application Schematic', () => {
869826
);
870827
const path = '/projects/foo/src/app/app.config.ts';
871828
const fileContent = tree.readContent(path);
872-
expect(fileContent).not.toContain('provideZoneChangeDetection');
829+
expect(fileContent).not.toMatch(/provideZone(less)?ChangeDetection/gi);
873830
});
874831
});
875832

0 commit comments

Comments
 (0)