Skip to content

Commit 112fa7d

Browse files
committed
feat(@angular/build): add experimentalNodeless option for SSR builds
Introduced 'experimentalNodeless' boolean option to specify Node.js usage for SSR applications. Setting this option to false enables deployment in non-Node.js environments, such as Edge Workers. **Note:** that this feature does not include polyfills for Node.js modules and is experimental, subject to future changes.
1 parent 6ce5c69 commit 112fa7d

File tree

4 files changed

+32
-14
lines changed

4 files changed

+32
-14
lines changed

packages/angular/build/src/builders/application/options.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,10 +264,11 @@ export async function normalizeOptions(
264264
if (options.ssr === true) {
265265
ssrOptions = {};
266266
} else if (typeof options.ssr === 'object') {
267-
const { entry } = options.ssr;
267+
const { entry, experimentalNodeless = false } = options.ssr;
268268

269269
ssrOptions = {
270270
entry: entry && path.join(workspaceRoot, entry),
271+
nodeless: experimentalNodeless,
271272
};
272273
}
273274

packages/angular/build/src/builders/application/schema.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,11 @@
518518
"entry": {
519519
"type": "string",
520520
"description": "The server entry-point that when executed will spawn the web server."
521+
},
522+
"experimentalNodeless": {
523+
"type": "boolean",
524+
"description": "Indicate whether the build is intended for use with Node.js. Setting this to 'false' is especially beneficial for deploying the server application in environments that do not utilize Node.js, such as workers. Please note that this feature does not provide polyfills for Node.js modules. Additionally, it is experimental, and the schematics may undergo changes in future versions.",
525+
"default": false
521526
}
522527
},
523528
"additionalProperties": false

packages/angular/build/src/tools/esbuild/application-code-bundle.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,10 @@ export function createServerPolyfillBundleOptions(
160160
): BundlerOptionsFactory | undefined {
161161
const serverPolyfills: string[] = [];
162162
const polyfillsFromConfig = new Set(options.polyfills);
163+
const isNodeless = !!options.ssrOptions?.nodeless;
164+
163165
if (!isZonelessApp(options.polyfills)) {
164-
serverPolyfills.push('zone.js/node');
166+
serverPolyfills.push(isNodeless ? 'zone.js' : 'zone.js/node');
165167
}
166168

167169
if (
@@ -190,22 +192,24 @@ export function createServerPolyfillBundleOptions(
190192

191193
const buildOptions: BuildOptions = {
192194
...polyfillBundleOptions,
193-
platform: 'node',
195+
platform: isNodeless ? 'neutral' : 'node',
194196
outExtension: { '.js': '.mjs' },
195197
// Note: `es2015` is needed for RxJS v6. If not specified, `module` would
196198
// match and the ES5 distribution would be bundled and ends up breaking at
197199
// runtime with the RxJS testing library.
198200
// More details: https://github.com/angular/angular-cli/issues/25405.
199201
mainFields: ['es2020', 'es2015', 'module', 'main'],
200202
entryNames: '[name]',
201-
banner: {
202-
js: [
203-
// Note: Needed as esbuild does not provide require shims / proxy from ESModules.
204-
// See: https://github.com/evanw/esbuild/issues/1921.
205-
`import { createRequire } from 'node:module';`,
206-
`globalThis['require'] ??= createRequire(import.meta.url);`,
207-
].join('\n'),
208-
},
203+
banner: isNodeless
204+
? undefined
205+
: {
206+
js: [
207+
// Note: Needed as esbuild does not provide require shims / proxy from ESModules.
208+
// See: https://github.com/evanw/esbuild/issues/1921.
209+
`import { createRequire } from 'node:module';`,
210+
`globalThis['require'] ??= createRequire(import.meta.url);`,
211+
].join('\n'),
212+
},
209213
target,
210214
entryPoints: {
211215
'polyfills.server': namespace,
@@ -373,6 +377,12 @@ export function createSsrEntryCodeBundleOptions(
373377
const ssrEntryNamespace = 'angular:ssr-entry';
374378
const ssrInjectManifestNamespace = 'angular:ssr-entry-inject-manifest';
375379
const ssrInjectRequireNamespace = 'angular:ssr-entry-inject-require';
380+
381+
const inject: string[] = [ssrInjectManifestNamespace];
382+
if (!ssrOptions.nodeless) {
383+
inject.unshift(ssrInjectRequireNamespace);
384+
}
385+
376386
const buildOptions: BuildOptions = {
377387
...getEsBuildServerCommonOptions(options),
378388
target,
@@ -390,7 +400,7 @@ export function createSsrEntryCodeBundleOptions(
390400
styleOptions,
391401
),
392402
],
393-
inject: [ssrInjectRequireNamespace, ssrInjectManifestNamespace],
403+
inject,
394404
};
395405

396406
buildOptions.plugins ??= [];
@@ -492,7 +502,7 @@ export function createSsrEntryCodeBundleOptions(
492502
function getEsBuildServerCommonOptions(options: NormalizedApplicationBuildOptions): BuildOptions {
493503
return {
494504
...getEsBuildCommonOptions(options),
495-
platform: 'node',
505+
platform: options.ssrOptions?.nodeless ? 'neutral' : 'node',
496506
outExtension: { '.js': '.mjs' },
497507
// Note: `es2015` is needed for RxJS v6. If not specified, `module` would
498508
// match and the ES5 distribution would be bundled and ends up breaking at

packages/angular/build/src/tools/esbuild/bundler-context.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,9 @@ export class BundlerContext {
423423
}
424424

425425
get #platformIsServer(): boolean {
426-
return this.#esbuildOptions?.platform === 'node';
426+
return (
427+
this.#esbuildOptions?.platform === 'node' || this.#esbuildOptions?.platform === 'neutral'
428+
);
427429
}
428430

429431
/**

0 commit comments

Comments
 (0)