Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions goldens/public-api/angular/build/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ export enum BuildOutputFileType {
export type DevServerBuilderOptions = {
allowedHosts?: AllowedHosts;
buildTarget: string;
define?: {
[key: string]: string;
};
headers?: {
[key: string]: string;
};
Expand Down
18 changes: 18 additions & 0 deletions modules/testing/builder/src/builder-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,24 @@ export class BuilderHarness<T> {
return this;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
modifyTarget<O extends object = any>(
targetName: string,
modifier: (options: O) => O | void,
): this {
const target = this.builderTargets.get(targetName);
if (!target) {
throw new Error(`Target "${targetName}" not found.`);
}

const newOptions = modifier(target.options as O);
if (newOptions) {
target.options = newOptions as json.JsonObject;
}

return this;
}

execute(
options: Partial<BuilderHarnessExecutionOptions> = {},
): Observable<BuilderHarnessExecutionResult> {
Expand Down
2 changes: 2 additions & 0 deletions packages/angular/build/src/builders/dev-server/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export async function normalizeOptions(
poll,
open,
verbose,
define,
watch,
liveReload,
hmr,
Expand All @@ -114,6 +115,7 @@ export async function normalizeOptions(
poll,
open,
verbose,
define,
watch,
liveReload: !!liveReload,
hmr: hmr ?? !!liveReload,
Expand Down
7 changes: 7 additions & 0 deletions packages/angular/build/src/builders/dev-server/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@
}
]
},
"define": {
"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.",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"headers": {
"type": "object",
"description": "Custom HTTP headers to be added to all responses.",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/

import { executeDevServer } from '../../index';
import { executeOnceAndFetch } from '../execute-fetch';
import { describeServeBuilder } from '../jasmine-helpers';
import { BASE_OPTIONS, DEV_SERVER_BUILDER_INFO } from '../setup';

describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupTarget) => {
describe('option: "define"', () => {
beforeEach(() => {
setupTarget(harness);

// Application code
harness.writeFile(
'src/main.ts',
`
// @ts-ignore
console.log(TEST);
// @ts-ignore
console.log(BUILD);
// @ts-ignore
console.log(SERVE);
`,
);
});

it('should replace global identifiers in the application', async () => {
harness.useTarget('serve', {
...BASE_OPTIONS,
define: {
TEST: JSON.stringify('test123'),
},
});

const { result, response } = await executeOnceAndFetch(harness, '/main.js');

expect(result?.success).toBeTrue();
const content = await response?.text();
expect(content).toContain('console.log("test123")');
});

it('should merge "define" option from dev-server and build', async () => {
harness.modifyTarget('build', (options) => {
options.define = {
BUILD: JSON.stringify('build'),
};
});

harness.useTarget('serve', {
...BASE_OPTIONS,
define: {
SERVE: JSON.stringify('serve'),
},
});

const { result, response } = await executeOnceAndFetch(harness, '/main.js');

expect(result?.success).toBeTrue();
const content = await response?.text();
expect(content).toContain('console.log("build")');
expect(content).toContain('console.log("serve")');
});

it('should overwrite "define" option from build with the one from dev-server', async () => {
harness.modifyTarget('build', (options) => {
options.define = {
TEST: JSON.stringify('build'),
};
});

harness.useTarget('serve', {
...BASE_OPTIONS,
define: {
TEST: JSON.stringify('serve'),
},
});

const { result, response } = await executeOnceAndFetch(harness, '/main.js');

expect(result?.success).toBeTrue();
const content = await response?.text();
expect(content).toContain('console.log("serve")');
expect(content).not.toContain('console.log("build")');
});
});
});
12 changes: 10 additions & 2 deletions packages/angular/build/src/builders/dev-server/vite/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export type BuilderAction = (
* Build options that are also present on the dev server but are only passed
* to the build.
*/
const CONVENIENCE_BUILD_OPTIONS = ['watch', 'poll', 'verbose'] as const;
const CONVENIENCE_BUILD_OPTIONS = ['watch', 'poll', 'verbose', 'define'] as const;

// eslint-disable-next-line max-lines-per-function
export async function* serveWithVite(
Expand All @@ -75,7 +75,15 @@ export async function* serveWithVite(
for (const optionName of CONVENIENCE_BUILD_OPTIONS) {
const optionValue = serverOptions[optionName];
if (optionValue !== undefined) {
rawBrowserOptions[optionName] = optionValue;
if (optionName === 'define' && rawBrowserOptions[optionName]) {
// Define has merging behavior within the application
for (const [key, value] of Object.entries(optionValue)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(rawBrowserOptions[optionName] as any)[key] = value;
}
} else {
rawBrowserOptions[optionName] = optionValue;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export function execute(
normalizedOptions as typeof normalizedOptions & {
hmr: boolean;
allowedHosts: true | string[];
define: { [key: string]: string } | undefined;
},
builderName,
(options, context, codePlugins) => {
Expand Down