Skip to content

Commit ceba2fb

Browse files
committed
feat(@angular/build): rename experimentalPlatform to platform in application builder
The `experimentalPlatform` option within the `ssr` option of the application builder has been renamed to `platform`. This change promotes the feature from experimental status while maintaining its functionality for specifying the target server platform. A migration has been added to automatically update existing configuration to the new option name.
1 parent 76448a3 commit ceba2fb

File tree

7 files changed

+202
-12
lines changed

7 files changed

+202
-12
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ import { getProjectRootPaths, normalizeDirectoryPath } from '../../utils/project
2828
import { addTrailingSlash, joinUrlParts, stripLeadingSlash } from '../../utils/url';
2929
import {
3030
Schema as ApplicationBuilderOptions,
31-
ExperimentalPlatform,
3231
I18NTranslation,
3332
OutputHashing,
3433
OutputMode,
3534
OutputPathClass,
35+
Platform,
3636
} from './schema';
3737

3838
/**
@@ -292,11 +292,11 @@ export async function normalizeOptions(
292292
if (options.ssr === true) {
293293
ssrOptions = {};
294294
} else if (typeof options.ssr === 'object') {
295-
const { entry, experimentalPlatform = ExperimentalPlatform.Node } = options.ssr;
295+
const { entry, platform = Platform.Node } = options.ssr;
296296

297297
ssrOptions = {
298298
entry: entry && path.join(workspaceRoot, entry),
299-
platform: experimentalPlatform,
299+
platform,
300300
};
301301
}
302302

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@
603603
"type": "string",
604604
"description": "The server entry-point that when executed will spawn the web server."
605605
},
606-
"experimentalPlatform": {
606+
"platform": {
607607
"description": "Specifies the platform for which the server bundle is generated. This affects the APIs and modules available in the server-side code. \n\n- `node`: (Default) Generates a bundle optimized for Node.js environments. \n- `neutral`: Generates a platform-neutral bundle suitable for environments like edge workers, and other serverless platforms. This option avoids using Node.js-specific APIs, making the bundle more portable. \n\nPlease 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.",
608608
"default": "node",
609609
"enum": ["node", "neutral"]

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

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

9-
import type { BuildOptions, PartialMessage, Plugin } from 'esbuild';
9+
import type { BuildOptions, Plugin } from 'esbuild';
1010
import assert from 'node:assert';
1111
import { createHash } from 'node:crypto';
1212
import { extname, relative } from 'node:path';
1313
import type { NormalizedApplicationBuildOptions } from '../../builders/application/options';
14-
import { ExperimentalPlatform } from '../../builders/application/schema';
14+
import { Platform } from '../../builders/application/schema';
1515
import { allowMangle } from '../../utils/environment-options';
1616
import { toPosixPath } from '../../utils/path';
1717
import {
@@ -158,7 +158,7 @@ export function createServerPolyfillBundleOptions(
158158
): BundlerOptionsFactory | undefined {
159159
const serverPolyfills: string[] = [];
160160
const polyfillsFromConfig = new Set(options.polyfills);
161-
const isNodePlatform = options.ssrOptions?.platform !== ExperimentalPlatform.Neutral;
161+
const isNodePlatform = options.ssrOptions?.platform !== Platform.Neutral;
162162

163163
if (!isZonelessApp(options.polyfills)) {
164164
serverPolyfills.push(isNodePlatform ? 'zone.js/node' : 'zone.js');
@@ -295,7 +295,7 @@ export function createServerMainCodeBundleOptions(
295295

296296
// Mark manifest and polyfills file as external as these are generated by a different bundle step.
297297
(buildOptions.external ??= []).push(...SERVER_GENERATED_EXTERNALS);
298-
const isNodePlatform = options.ssrOptions?.platform !== ExperimentalPlatform.Neutral;
298+
const isNodePlatform = options.ssrOptions?.platform !== Platform.Neutral;
299299

300300
if (!isNodePlatform) {
301301
// `@angular/platform-server` lazily depends on `xhr2` for XHR usage with the HTTP client.
@@ -387,7 +387,7 @@ export function createSsrEntryCodeBundleOptions(
387387
const pluginOptions = createCompilerPluginOptions(options, sourceFileCache, loadResultCache);
388388
const ssrEntryNamespace = 'angular:ssr-entry';
389389
const ssrInjectManifestNamespace = 'angular:ssr-entry-inject-manifest';
390-
const isNodePlatform = options.ssrOptions?.platform !== ExperimentalPlatform.Neutral;
390+
const isNodePlatform = options.ssrOptions?.platform !== Platform.Neutral;
391391

392392
const jsBanner: string[] = [];
393393
if (options.externalDependencies?.length) {
@@ -505,7 +505,7 @@ export function createSsrEntryCodeBundleOptions(
505505
}
506506

507507
function getEsBuildServerCommonOptions(options: NormalizedApplicationBuildOptions): BuildOptions {
508-
const isNodePlatform = options.ssrOptions?.platform !== ExperimentalPlatform.Neutral;
508+
const isNodePlatform = options.ssrOptions?.platform !== Platform.Neutral;
509509

510510
const commonOptions = getEsBuildCommonOptions(options);
511511
commonOptions.define ??= {};

packages/schematics/angular/migrations/migration-collection.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"encapsulation": false,
33
"schematics": {
44
"use-application-builder": {
5-
"version": "21.0.0",
5+
"version": "22.0.0",
66
"factory": "./use-application-builder/migration",
77
"description": "Migrate application projects to the new build system. Application projects that are using the '@angular-devkit/build-angular' package's 'browser' and/or 'browser-esbuild' builders will be migrated to use the new 'application' builder. You can read more about this, including known issues and limitations, here: https://angular.dev/tools/cli/build-system-migration",
88
"optional": true,
@@ -13,6 +13,11 @@
1313
"version": "21.0.0",
1414
"factory": "./karma/migration",
1515
"description": "Remove any karma configuration files that only contain the default content. The default configuration is automatically available without a specific project file."
16+
},
17+
"update-workspace-config": {
18+
"version": "22.0.0",
19+
"factory": "./update-workspace-config/migration",
20+
"description": "Update the angular workspace configuration."
1621
}
1722
}
1823
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import { isJsonObject } from '@angular-devkit/core';
10+
import { Rule } from '@angular-devkit/schematics';
11+
import { allTargetOptions, updateWorkspace } from '../../utility/workspace';
12+
import { Builders, ProjectType } from '../../utility/workspace-models';
13+
14+
/**
15+
* Migration to update the angular workspace configuration.
16+
*/
17+
export default function (): Rule {
18+
return updateWorkspace((workspace) => {
19+
for (const [, project] of workspace.projects) {
20+
if (project.extensions['projectType'] !== ProjectType.Application) {
21+
continue;
22+
}
23+
24+
for (const [, target] of project.targets) {
25+
if (
26+
target.builder !== Builders.Application &&
27+
target.builder !== Builders.BuildApplication
28+
) {
29+
continue;
30+
}
31+
32+
for (const [, options] of allTargetOptions(target)) {
33+
const ssr = options['ssr'];
34+
if (!ssr || !isJsonObject(ssr)) {
35+
continue;
36+
}
37+
38+
const platform = ssr['experimentalPlatform'];
39+
if (platform) {
40+
ssr['platform'] = platform;
41+
delete ssr['experimentalPlatform'];
42+
}
43+
}
44+
}
45+
}
46+
});
47+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import { EmptyTree } from '@angular-devkit/schematics';
10+
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
11+
import { Builders, ProjectType } from '../../utility/workspace-models';
12+
13+
describe('Migration to update the angular workspace configuration', () => {
14+
const schematicName = 'update-workspace-config';
15+
const schematicRunner = new SchematicTestRunner(
16+
'migrations',
17+
require.resolve('../migration-collection.json'),
18+
);
19+
20+
let tree: UnitTestTree;
21+
beforeEach(() => {
22+
tree = new UnitTestTree(new EmptyTree());
23+
});
24+
25+
it('should rename experimentalPlatform to platform in application builder', async () => {
26+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
27+
const angularConfig: any = {
28+
version: 1,
29+
projects: {
30+
app: {
31+
root: '',
32+
sourceRoot: 'src',
33+
projectType: ProjectType.Application,
34+
prefix: 'app',
35+
architect: {
36+
build: {
37+
builder: Builders.Application,
38+
options: {
39+
ssr: {
40+
entry: 'src/server.ts',
41+
experimentalPlatform: 'neutral',
42+
},
43+
},
44+
configurations: {
45+
production: {
46+
ssr: {
47+
experimentalPlatform: 'node',
48+
},
49+
},
50+
},
51+
},
52+
},
53+
},
54+
},
55+
};
56+
57+
tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2));
58+
59+
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
60+
const config = JSON.parse(newTree.readContent('/angular.json'));
61+
const options = config.projects.app.architect.build.options;
62+
const prodOptions = config.projects.app.architect.build.configurations.production;
63+
64+
expect(options.ssr.platform).toBe('neutral');
65+
expect(options.ssr.experimentalPlatform).toBeUndefined();
66+
expect(prodOptions.ssr.platform).toBe('node');
67+
expect(prodOptions.ssr.experimentalPlatform).toBeUndefined();
68+
});
69+
70+
it('should work with @angular/build:application', async () => {
71+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
72+
const angularConfig: any = {
73+
version: 1,
74+
projects: {
75+
app: {
76+
root: '',
77+
sourceRoot: 'src',
78+
projectType: ProjectType.Application,
79+
prefix: 'app',
80+
architect: {
81+
build: {
82+
builder: Builders.BuildApplication,
83+
options: {
84+
ssr: {
85+
experimentalPlatform: 'neutral',
86+
},
87+
},
88+
},
89+
},
90+
},
91+
},
92+
};
93+
94+
tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2));
95+
96+
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
97+
const config = JSON.parse(newTree.readContent('/angular.json'));
98+
const options = config.projects.app.architect.build.options;
99+
100+
expect(options.ssr.platform).toBe('neutral');
101+
expect(options.ssr.experimentalPlatform).toBeUndefined();
102+
});
103+
104+
it('should not change other builders', async () => {
105+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
106+
const angularConfig: any = {
107+
version: 1,
108+
projects: {
109+
app: {
110+
root: '',
111+
sourceRoot: 'src',
112+
projectType: ProjectType.Application,
113+
prefix: 'app',
114+
architect: {
115+
build: {
116+
builder: Builders.Browser,
117+
options: {
118+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
119+
ssr: {
120+
experimentalPlatform: 'neutral',
121+
},
122+
} as any,
123+
},
124+
},
125+
},
126+
},
127+
};
128+
129+
tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2));
130+
131+
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
132+
const config = JSON.parse(newTree.readContent('/angular.json'));
133+
const options = config.projects.app.architect.build.options;
134+
135+
expect(options.ssr.experimentalPlatform).toBe('neutral');
136+
expect(options.ssr.platform).toBeUndefined();
137+
});
138+
});

tests/e2e/tests/build/server-rendering/server-routes-output-mode-server-platform-neutral.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export default async function () {
9595
await updateJsonFile('angular.json', (json) => {
9696
const buildTarget = json['projects']['test-project']['architect']['build'];
9797
const options = buildTarget['options'];
98-
options['ssr']['experimentalPlatform'] = 'neutral';
98+
options['ssr']['platform'] = 'neutral';
9999
options['outputMode'] = 'server';
100100
options['security'] ??= {};
101101
options['security']['allowedHosts'] = ['localhost'];

0 commit comments

Comments
 (0)