Skip to content

Commit 530d927

Browse files
committed
fix(@angular/build): add define option to dev-server
This change introduces a `define` option to the dev-server builder, allowing users to specify global constants that will be replaced at build time. This provides a convenient way to configure environment-specific variables directly from the dev-server configuration. (cherry picked from commit 4211534)
1 parent 091d1c0 commit 530d927

File tree

7 files changed

+133
-2
lines changed

7 files changed

+133
-2
lines changed

goldens/public-api/angular/build/index.api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ export enum BuildOutputFileType {
112112
export type DevServerBuilderOptions = {
113113
allowedHosts?: AllowedHosts;
114114
buildTarget: string;
115+
define?: {
116+
[key: string]: string;
117+
};
115118
headers?: {
116119
[key: string]: string;
117120
};

modules/testing/builder/src/builder-harness.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,24 @@ export class BuilderHarness<T> {
162162
return this;
163163
}
164164

165+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
166+
modifyTarget<O extends object = any>(
167+
targetName: string,
168+
modifier: (options: O) => O | void,
169+
): this {
170+
const target = this.builderTargets.get(targetName);
171+
if (!target) {
172+
throw new Error(`Target "${targetName}" not found.`);
173+
}
174+
175+
const newOptions = modifier(target.options as O);
176+
if (newOptions) {
177+
target.options = newOptions as json.JsonObject;
178+
}
179+
180+
return this;
181+
}
182+
165183
execute(
166184
options: Partial<BuilderHarnessExecutionOptions> = {},
167185
): Observable<BuilderHarnessExecutionResult> {

packages/angular/build/src/builders/dev-server/options.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export async function normalizeOptions(
9393
poll,
9494
open,
9595
verbose,
96+
define,
9697
watch,
9798
liveReload,
9899
hmr,
@@ -114,6 +115,7 @@ export async function normalizeOptions(
114115
poll,
115116
open,
116117
verbose,
118+
define,
117119
watch,
118120
liveReload: !!liveReload,
119121
hmr: hmr ?? !!liveReload,

packages/angular/build/src/builders/dev-server/schema.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@
5353
}
5454
]
5555
},
56+
"define": {
57+
"description": "Defines global identifiers that will be replaced with a specified constant value when found in any JavaScript or TypeScript code including libraries. The value will be used directly. String values must be put in quotes. Identifiers within Angular metadata such as Component Decorators will not be replaced.",
58+
"type": "object",
59+
"additionalProperties": {
60+
"type": "string"
61+
}
62+
},
5663
"headers": {
5764
"type": "object",
5865
"description": "Custom HTTP headers to be added to all responses.",
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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 { executeDevServer } from '../../index';
10+
import { executeOnceAndFetch } from '../execute-fetch';
11+
import { describeServeBuilder } from '../jasmine-helpers';
12+
import { BASE_OPTIONS, DEV_SERVER_BUILDER_INFO } from '../setup';
13+
14+
describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupTarget) => {
15+
describe('option: "define"', () => {
16+
beforeEach(() => {
17+
setupTarget(harness);
18+
19+
// Application code
20+
harness.writeFile(
21+
'src/main.ts',
22+
`
23+
// @ts-ignore
24+
console.log(TEST);
25+
// @ts-ignore
26+
console.log(BUILD);
27+
// @ts-ignore
28+
console.log(SERVE);
29+
`,
30+
);
31+
});
32+
33+
it('should replace global identifiers in the application', async () => {
34+
harness.useTarget('serve', {
35+
...BASE_OPTIONS,
36+
define: {
37+
TEST: JSON.stringify('test123'),
38+
},
39+
});
40+
41+
const { result, response } = await executeOnceAndFetch(harness, '/main.js');
42+
43+
expect(result?.success).toBeTrue();
44+
const content = await response?.text();
45+
expect(content).toContain('console.log("test123")');
46+
});
47+
48+
it('should merge "define" option from dev-server and build', async () => {
49+
harness.modifyTarget('build', (options) => {
50+
options.define = {
51+
BUILD: JSON.stringify('build'),
52+
};
53+
});
54+
55+
harness.useTarget('serve', {
56+
...BASE_OPTIONS,
57+
define: {
58+
SERVE: JSON.stringify('serve'),
59+
},
60+
});
61+
62+
const { result, response } = await executeOnceAndFetch(harness, '/main.js');
63+
64+
expect(result?.success).toBeTrue();
65+
const content = await response?.text();
66+
expect(content).toContain('console.log("build")');
67+
expect(content).toContain('console.log("serve")');
68+
});
69+
70+
it('should overwrite "define" option from build with the one from dev-server', async () => {
71+
harness.modifyTarget('build', (options) => {
72+
options.define = {
73+
TEST: JSON.stringify('build'),
74+
};
75+
});
76+
77+
harness.useTarget('serve', {
78+
...BASE_OPTIONS,
79+
define: {
80+
TEST: JSON.stringify('serve'),
81+
},
82+
});
83+
84+
const { result, response } = await executeOnceAndFetch(harness, '/main.js');
85+
86+
expect(result?.success).toBeTrue();
87+
const content = await response?.text();
88+
expect(content).toContain('console.log("serve")');
89+
expect(content).not.toContain('console.log("build")');
90+
});
91+
});
92+
});

packages/angular/build/src/builders/dev-server/vite/index.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export type BuilderAction = (
4949
* Build options that are also present on the dev server but are only passed
5050
* to the build.
5151
*/
52-
const CONVENIENCE_BUILD_OPTIONS = ['watch', 'poll', 'verbose'] as const;
52+
const CONVENIENCE_BUILD_OPTIONS = ['watch', 'poll', 'verbose', 'define'] as const;
5353

5454
// eslint-disable-next-line max-lines-per-function
5555
export async function* serveWithVite(
@@ -75,7 +75,15 @@ export async function* serveWithVite(
7575
for (const optionName of CONVENIENCE_BUILD_OPTIONS) {
7676
const optionValue = serverOptions[optionName];
7777
if (optionValue !== undefined) {
78-
rawBrowserOptions[optionName] = optionValue;
78+
if (optionName === 'define' && rawBrowserOptions[optionName]) {
79+
// Define has merging behavior within the application
80+
for (const [key, value] of Object.entries(optionValue)) {
81+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
82+
(rawBrowserOptions[optionName] as any)[key] = value;
83+
}
84+
} else {
85+
rawBrowserOptions[optionName] = optionValue;
86+
}
7987
}
8088
}
8189

packages/angular_devkit/build_angular/src/builders/dev-server/builder.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ export function execute(
9797
normalizedOptions as typeof normalizedOptions & {
9898
hmr: boolean;
9999
allowedHosts: true | string[];
100+
define: { [key: string]: string } | undefined;
100101
},
101102
builderName,
102103
(options, context, codePlugins) => {

0 commit comments

Comments
 (0)