Skip to content

Commit 7a8c946

Browse files
committed
fix(@angular-devkit/build-angular): make zone.js optional in server and app-shell builders
This commit refactors the server and Jest builders to make `zone.js` an optional dependency, preventing errors in projects that do not include it. For server builds, `zone.js/node` is now only imported if `zone.js` is detected in the project's dependencies.
1 parent 6846024 commit 7a8c946

File tree

15 files changed

+53
-44
lines changed

15 files changed

+53
-44
lines changed

packages/angular_devkit/build_angular/src/builders/app-shell/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ async function _renderUniversal(
4343

4444
// Locate zone.js to load in the render worker
4545
const root = context.workspaceRoot;
46-
const zonePackage = require.resolve('zone.js', { paths: [root] });
46+
let zonePackage: string | undefined;
47+
48+
try {
49+
zonePackage = require.resolve('zone.js', { paths: [root] });
50+
} catch {}
4751

4852
const projectName = context.target && context.target.project;
4953
if (!projectName) {

packages/angular_devkit/build_angular/src/builders/app-shell/render-worker.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { workerData } from 'node:worker_threads';
1717
* This is passed as workerData when setting up the worker via the `piscina` package.
1818
*/
1919
const { zonePackage } = workerData as {
20-
zonePackage: string;
20+
zonePackage: string | undefined;
2121
};
2222

2323
interface ServerBundleExports {
@@ -136,8 +136,9 @@ function isBootstrapFn(
136136
* @returns A promise resolving to the render function of the worker.
137137
*/
138138
async function initialize() {
139-
// Setup Zone.js
140-
await import(zonePackage);
139+
if (zonePackage) {
140+
await import(zonePackage);
141+
}
141142

142143
// Return the render function for use
143144
return render;

packages/angular_devkit/build_angular/src/builders/jest/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export default createBuilder(
133133
// the environment for fake async to work correctly.
134134
// Third, we initialize `TestBed`. This is dependent on fake async being set up correctly beforehand.
135135
`--setupFilesAfterEnv="<rootDir>/jest-global.mjs"`,
136-
...(options.polyfills ? [`--setupFilesAfterEnv="<rootDir>/polyfills.mjs"`] : []),
136+
...(options.polyfills?.length ? [`--setupFilesAfterEnv="<rootDir>/polyfills.mjs"`] : []),
137137
`--setupFilesAfterEnv="<rootDir>/init-test-bed.mjs"`,
138138

139139
// Don't run any infrastructure files as tests, they are manually loaded where needed.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
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';
12+
import { NgModule, provideZoneChangeDetection } from '@angular/core';
1313
import { getTestBed } from '@angular/core/testing';
1414
import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';
1515

packages/angular_devkit/build_angular/src/builders/prerender/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ async function getRoutes(
5757
}
5858
}
5959

60+
let zonePackage: string | undefined;
61+
try {
62+
zonePackage = require.resolve('zone.js/node', { paths: [workspaceRoot] });
63+
} catch {}
64+
6065
if (discoverRoutes) {
6166
const renderWorker = new Piscina({
6267
filename: require.resolve('./routes-extractor-worker'),
@@ -65,7 +70,7 @@ async function getRoutes(
6570
indexFile,
6671
outputPath,
6772
serverBundlePath,
68-
zonePackage: require.resolve('zone.js', { paths: [workspaceRoot] }),
73+
zonePackage,
6974
} as RoutesExtractorWorkerData,
7075
recordTiming: false,
7176
});

packages/angular_devkit/build_angular/src/builders/prerender/render-worker.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ interface ServerBundleExports {
5151
* This is passed as workerData when setting up the worker via the `piscina` package.
5252
*/
5353
const { zonePackage } = workerData as {
54-
zonePackage: string;
54+
zonePackage: string | undefined;
5555
};
5656

5757
/**
@@ -163,8 +163,10 @@ function isBootstrapFn(
163163
* @returns A promise resolving to the render function of the worker.
164164
*/
165165
async function initialize() {
166-
// Setup Zone.js
167-
await import(zonePackage);
166+
if (zonePackage) {
167+
// Setup Zone.js
168+
await import(zonePackage);
169+
}
168170

169171
// Return the render function for use
170172
return render;

packages/angular_devkit/build_angular/src/builders/prerender/routes-extractor-worker.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import * as path from 'node:path';
1515
import { workerData } from 'node:worker_threads';
1616

1717
export interface RoutesExtractorWorkerData {
18-
zonePackage: string;
18+
zonePackage: string | undefined;
1919
indexFile: string;
2020
outputPath: string;
2121
serverBundlePath: string;
@@ -74,8 +74,10 @@ async function extract(): Promise<string[]> {
7474
* @returns A promise resolving to the extract function of the worker.
7575
*/
7676
async function initialize() {
77-
// Setup Zone.js
78-
await import(zonePackage);
77+
if (zonePackage) {
78+
// Setup Zone.js
79+
await import(zonePackage);
80+
}
7981

8082
return extract;
8183
}

packages/angular_devkit/build_angular/src/builders/server/index.ts

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,6 @@ async function initialize(
198198
const originalOutputPath = options.outputPath;
199199
// Assets are processed directly by the builder except when watching
200200
const adjustedOptions = options.watch ? options : { ...options, assets: [] };
201-
202201
const { config, projectRoot, projectSourceRoot, i18n } =
203202
await generateI18nBrowserWebpackConfigFromContext(
204203
{
@@ -271,23 +270,18 @@ function getPlatformServerExportsConfig(wco: BrowserWebpackConfigOptions): Parti
271270
// Add `@angular/platform-server` exports.
272271
// This is needed so that DI tokens can be referenced and set at runtime outside of the bundle.
273272

274-
// Only add `@angular/platform-server` exports when it is installed.
275-
// In some cases this builder is used when `@angular/platform-server` is not installed.
276-
// Example: when using `@nguniversal/common/clover` which does not need `@angular/platform-server`.
277-
278-
return isPackageInstalled(wco.root, '@angular/platform-server')
279-
? {
280-
module: {
281-
rules: [
282-
{
283-
loader: require.resolve('./platform-server-exports-loader'),
284-
include: [path.resolve(wco.root, wco.buildOptions.main)],
285-
options: {
286-
angularSSRInstalled: isPackageInstalled(wco.root, '@angular/ssr'),
287-
},
288-
},
289-
],
273+
return {
274+
module: {
275+
rules: [
276+
{
277+
loader: require.resolve('./platform-server-exports-loader'),
278+
include: [path.resolve(wco.root, wco.buildOptions.main)],
279+
options: {
280+
angularSSRInstalled: isPackageInstalled(wco.root, '@angular/ssr'),
281+
isZoneJsInstalled: isPackageInstalled(wco.root, 'zone.js'),
282+
},
290283
},
291-
}
292-
: {};
284+
],
285+
},
286+
};
293287
}

packages/angular_devkit/build_angular/src/builders/server/platform-server-exports-loader.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@
1212
* @see https://github.com/webpack/webpack/issues/15936.
1313
*/
1414
export default function (
15-
this: import('webpack').LoaderContext<{ angularSSRInstalled: boolean }>,
15+
this: import('webpack').LoaderContext<{
16+
angularSSRInstalled: boolean;
17+
isZoneJsInstalled: boolean;
18+
}>,
1619
content: string,
1720
map: Parameters<import('webpack').LoaderDefinitionFunction>[1],
1821
) {
19-
const { angularSSRInstalled } = this.getOptions();
22+
const { angularSSRInstalled, isZoneJsInstalled } = this.getOptions();
2023

2124
let source = `${content}
2225
@@ -30,6 +33,11 @@ export default function (
3033
`;
3134
}
3235

36+
if (isZoneJsInstalled) {
37+
source = `import 'zone.js/node';
38+
${source}`;
39+
}
40+
3341
this.callback(null, source, map);
3442

3543
return;

packages/angular_devkit/build_angular/src/builders/ssr-dev-server/specs/proxy_spec.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ describe('Serve SSR Builder', () => {
3232

3333
host.writeMultipleFiles({
3434
'src/main.server.ts': `
35-
import 'zone.js/node';
36-
3735
import { CommonEngine } from '@angular/ssr/node';
3836
import * as express from 'express';
3937
import { resolve, join } from 'node:path';

0 commit comments

Comments
 (0)