Skip to content

Commit cdfa7ca

Browse files
clydinangular-robot[bot]
authored andcommitted
fix(@angular-devkit/build-angular): allow multiple polyfills with esbuild-based builder
Previously when using the esbuild-based browser application, the `polyfills` option was limited to only one entry. The option now can be used with multiple entries and has full support for package resolution. This provides equivalent behavior to the current default Webpack-based builder.
1 parent 50b9e59 commit cdfa7ca

File tree

3 files changed

+106
-18
lines changed

3 files changed

+106
-18
lines changed

packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ function createCodeBundleOptions(
313313
const {
314314
workspaceRoot,
315315
entryPoints,
316+
polyfills,
316317
optimizationOptions,
317318
sourcemapOptions,
318319
tsconfig,
@@ -327,7 +328,7 @@ function createCodeBundleOptions(
327328
tailwindConfiguration,
328329
} = options;
329330

330-
return {
331+
const buildOptions: BuildOptions = {
331332
absWorkingDir: workspaceRoot,
332333
bundle: true,
333334
format: 'esm',
@@ -391,6 +392,39 @@ function createCodeBundleOptions(
391392
'ngJitMode': jit ? 'true' : 'false',
392393
},
393394
};
395+
396+
if (polyfills?.length) {
397+
const namespace = 'angular:polyfills';
398+
buildOptions.entryPoints = {
399+
...buildOptions.entryPoints,
400+
['polyfills']: namespace,
401+
};
402+
403+
buildOptions.plugins?.unshift({
404+
name: 'angular-polyfills',
405+
setup(build) {
406+
build.onResolve({ filter: /^angular:polyfills$/ }, (args) => {
407+
if (args.kind !== 'entry-point') {
408+
return null;
409+
}
410+
411+
return {
412+
path: 'entry',
413+
namespace,
414+
};
415+
});
416+
build.onLoad({ filter: /./, namespace }, () => {
417+
return {
418+
contents: polyfills.map((file) => `import '${file.replace(/\\/g, '/')}';`).join('\n'),
419+
loader: 'js',
420+
resolveDir: workspaceRoot,
421+
};
422+
});
423+
},
424+
});
425+
}
426+
427+
return buildOptions;
394428
}
395429

396430
/**

packages/angular_devkit/build_angular/src/builders/browser-esbuild/options.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { createRequire } from 'node:module';
1212
import path from 'node:path';
1313
import { normalizeAssetPatterns, normalizeOptimization, normalizeSourceMaps } from '../../utils';
1414
import { normalizeCacheOptions } from '../../utils/normalize-cache';
15-
import { normalizePolyfills } from '../../utils/normalize-polyfills';
1615
import { generateEntryPoints } from '../../utils/package-chunk-sort';
1716
import { getIndexInputFile, getIndexOutputFile } from '../../utils/webpack-browser-config';
1817
import { normalizeGlobalStyles } from '../../webpack/utils/helpers';
@@ -46,19 +45,6 @@ export async function normalizeOptions(
4645
const cacheOptions = normalizeCacheOptions(projectMetadata, workspaceRoot);
4746

4847
const mainEntryPoint = path.join(workspaceRoot, options.main);
49-
50-
// Currently esbuild do not support multiple files per entry-point
51-
const [polyfillsEntryPoint, ...remainingPolyfills] = normalizePolyfills(
52-
options.polyfills,
53-
workspaceRoot,
54-
);
55-
56-
if (remainingPolyfills.length) {
57-
context.logger.warn(
58-
`The 'polyfills' option currently does not support multiple entries by this experimental builder. The first entry will be used.`,
59-
);
60-
}
61-
6248
const tsconfig = path.join(workspaceRoot, options.tsConfig);
6349
const outputPath = path.join(workspaceRoot, options.outputPath);
6450
const optimizationOptions = normalizeOptimization(options.optimization);
@@ -133,9 +119,6 @@ export async function normalizeOptions(
133119
const entryPoints: Record<string, string> = {
134120
main: mainEntryPoint,
135121
};
136-
if (polyfillsEntryPoint) {
137-
entryPoints['polyfills'] = polyfillsEntryPoint;
138-
}
139122

140123
let indexHtmlOptions;
141124
if (options.index) {
@@ -162,6 +145,7 @@ export async function normalizeOptions(
162145
extractLicenses,
163146
inlineStyleLanguage = 'css',
164147
poll,
148+
polyfills,
165149
preserveSymlinks,
166150
statsJson,
167151
stylePreprocessorOptions,
@@ -182,6 +166,7 @@ export async function normalizeOptions(
182166
inlineStyleLanguage,
183167
jit: !aot,
184168
stats: !!statsJson,
169+
polyfills: polyfills === undefined || Array.isArray(polyfills) ? polyfills : [polyfills],
185170
poll,
186171
// If not explicitly set, default to the Node.js process argument
187172
preserveSymlinks: preserveSymlinks ?? process.execArgv.includes('--preserve-symlinks'),
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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.io/license
7+
*/
8+
9+
import { buildEsbuildBrowser } from '../../index';
10+
import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup';
11+
12+
describeBuilder(buildEsbuildBrowser, BROWSER_BUILDER_INFO, (harness) => {
13+
describe('Option: "polyfills"', () => {
14+
it('uses a provided TypeScript file', async () => {
15+
harness.useTarget('build', {
16+
...BASE_OPTIONS,
17+
polyfills: 'src/polyfills.ts',
18+
});
19+
20+
const { result } = await harness.executeOnce();
21+
22+
expect(result?.success).toBe(true);
23+
24+
harness.expectFile('dist/polyfills.js').toExist();
25+
});
26+
27+
it('uses a provided JavaScript file', async () => {
28+
await harness.writeFile('src/polyfills.js', `console.log('main');`);
29+
30+
harness.useTarget('build', {
31+
...BASE_OPTIONS,
32+
polyfills: 'src/polyfills.js',
33+
});
34+
35+
const { result } = await harness.executeOnce();
36+
37+
expect(result?.success).toBe(true);
38+
39+
harness.expectFile('dist/polyfills.js').content.toContain(`console.log("main")`);
40+
});
41+
42+
it('fails and shows an error when file does not exist', async () => {
43+
harness.useTarget('build', {
44+
...BASE_OPTIONS,
45+
polyfills: 'src/missing.ts',
46+
});
47+
48+
const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false });
49+
50+
expect(result?.success).toBe(false);
51+
expect(logs).toContain(
52+
jasmine.objectContaining({ message: jasmine.stringMatching('Could not resolve') }),
53+
);
54+
55+
harness.expectFile('dist/polyfills.js').toNotExist();
56+
});
57+
58+
it('resolves module specifiers in array', async () => {
59+
harness.useTarget('build', {
60+
...BASE_OPTIONS,
61+
polyfills: ['zone.js', 'zone.js/testing'],
62+
});
63+
64+
const { result } = await harness.executeOnce();
65+
expect(result?.success).toBeTrue();
66+
harness.expectFile('dist/polyfills.js').toExist();
67+
});
68+
});
69+
});

0 commit comments

Comments
 (0)