diff --git a/goldens/public-api/angular_devkit/build_angular/index.md b/goldens/public-api/angular_devkit/build_angular/index.md index a8f748bc8504..e7a6a890e1a1 100644 --- a/goldens/public-api/angular_devkit/build_angular/index.md +++ b/goldens/public-api/angular_devkit/build_angular/index.md @@ -53,7 +53,7 @@ export interface BrowserBuilderOptions { outputHashing?: OutputHashing; outputPath: string; poll?: number; - polyfills?: string; + polyfills?: Polyfills; preserveSymlinks?: boolean; progress?: boolean; resourcesOutputPath?: string; @@ -180,7 +180,7 @@ export interface KarmaBuilderOptions { karmaConfig: string; main: string; poll?: number; - polyfills?: string; + polyfills?: Polyfills_2; preserveSymlinks?: boolean; progress?: boolean; reporters?: string[]; diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/options.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/options.ts index 3727079d833c..55433039db7c 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/options.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/options.ts @@ -9,6 +9,7 @@ import { BuilderContext } from '@angular-devkit/architect'; import * as path from 'path'; import { normalizeAssetPatterns, normalizeOptimization, normalizeSourceMaps } from '../../utils'; +import { normalizePolyfills } from '../../utils/normalize-polyfills'; import { Schema as BrowserBuilderOptions, OutputHashing } from '../browser/schema'; /** @@ -33,10 +34,21 @@ export async function normalizeOptions( workspaceRoot, (projectMetadata.sourceRoot as string | undefined) ?? 'src', ); - // Normalize options const mainEntryPoint = path.join(workspaceRoot, options.main); - const polyfillsEntryPoint = options.polyfills && path.join(workspaceRoot, options.polyfills); + + // Currently esbuild do not support multiple files per entry-point + const [polyfillsEntryPoint, ...remainingPolyfills] = normalizePolyfills( + options.polyfills, + workspaceRoot, + ); + + if (remainingPolyfills.length) { + context.logger.warn( + `The 'polyfills' option currently does not support multiple entries by this experimental builder. The first entry will be used.`, + ); + } + const tsconfig = path.join(workspaceRoot, options.tsConfig); const outputPath = path.join(workspaceRoot, options.outputPath); const optimizationOptions = normalizeOptimization(options.optimization); diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/schema.json b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/schema.json index ce8d5d1d510a..ea0ec43e5598 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/schema.json +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/schema.json @@ -17,8 +17,22 @@ "description": "The full path for the main entry point to the app, relative to the current workspace." }, "polyfills": { - "type": "string", - "description": "The full path for the polyfills file, relative to the current workspace." + "description": "Polyfills to be included in the build.", + "oneOf": [ + { + "type": "array", + "description": "A list of polyfills to include in the build. Can be a full path for a file, relative to the current workspace or module specifier. Example: 'zone.js'.", + "items": { + "type": "string", + "uniqueItems": true + }, + "default": [] + }, + { + "type": "string", + "description": "The full path for the polyfills file, relative to the current workspace or a module specifier. Example: 'zone.js'." + } + ] }, "tsConfig": { "type": "string", diff --git a/packages/angular_devkit/build_angular/src/builders/browser/schema.json b/packages/angular_devkit/build_angular/src/builders/browser/schema.json index 35168f55a59d..12c9d6792afe 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser/schema.json +++ b/packages/angular_devkit/build_angular/src/builders/browser/schema.json @@ -17,8 +17,22 @@ "description": "The full path for the main entry point to the app, relative to the current workspace." }, "polyfills": { - "type": "string", - "description": "The full path for the polyfills file, relative to the current workspace." + "description": "Polyfills to be included in the build.", + "oneOf": [ + { + "type": "array", + "description": "A list of polyfills to include in the build. Can be a full path for a file, relative to the current workspace or module specifier. Example: 'zone.js'.", + "items": { + "type": "string", + "uniqueItems": true + }, + "default": [] + }, + { + "type": "string", + "description": "The full path for the polyfills file, relative to the current workspace or a module specifier. Example: 'zone.js'." + } + ] }, "tsConfig": { "type": "string", diff --git a/packages/angular_devkit/build_angular/src/builders/browser/tests/options/polyfills_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/polyfills_spec.ts index e47b22531437..fbf3e5ad850a 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser/tests/options/polyfills_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/options/polyfills_spec.ts @@ -54,5 +54,16 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { harness.expectFile('dist/polyfills.js').toNotExist(); }); + + it('resolves module specifiers in array', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + polyfills: ['zone.js', 'zone.js/testing'], + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + harness.expectFile('dist/polyfills.js').toExist(); + }); }); }); diff --git a/packages/angular_devkit/build_angular/src/builders/karma/schema.json b/packages/angular_devkit/build_angular/src/builders/karma/schema.json index b5e44c9dace5..35a9de5054ee 100644 --- a/packages/angular_devkit/build_angular/src/builders/karma/schema.json +++ b/packages/angular_devkit/build_angular/src/builders/karma/schema.json @@ -17,8 +17,22 @@ "description": "The name of the Karma configuration file." }, "polyfills": { - "type": "string", - "description": "The name of the polyfills file." + "description": "Polyfills to be included in the build.", + "oneOf": [ + { + "type": "array", + "description": "A list of polyfills to include in the build. Can be a full path for a file, relative to the current workspace or module specifier. Example: 'zone.js'.", + "items": { + "type": "string", + "uniqueItems": true + }, + "default": [] + }, + { + "type": "string", + "description": "The full path for the polyfills file, relative to the current workspace or a module specifier. Example: 'zone.js'." + } + ] }, "assets": { "type": "array", diff --git a/packages/angular_devkit/build_angular/src/utils/build-options.ts b/packages/angular_devkit/build_angular/src/utils/build-options.ts index 49e1212bd2c3..cacfd5746da2 100644 --- a/packages/angular_devkit/build_angular/src/utils/build-options.ts +++ b/packages/angular_devkit/build_angular/src/utils/build-options.ts @@ -58,7 +58,7 @@ export interface BuildOptions { statsJson: boolean; hmr?: boolean; main: string; - polyfills?: string; + polyfills: string[]; budgets: Budget[]; assets: AssetPatternClass[]; scripts: ScriptElement[]; diff --git a/packages/angular_devkit/build_angular/src/utils/normalize-builder-schema.ts b/packages/angular_devkit/build_angular/src/utils/normalize-builder-schema.ts index 138b25bc4aaa..43189d3a370c 100644 --- a/packages/angular_devkit/build_angular/src/utils/normalize-builder-schema.ts +++ b/packages/angular_devkit/build_angular/src/utils/normalize-builder-schema.ts @@ -20,6 +20,7 @@ import { normalizeFileReplacements, } from './normalize-file-replacements'; import { NormalizedOptimizationOptions, normalizeOptimization } from './normalize-optimization'; +import { normalizePolyfills } from './normalize-polyfills'; import { normalizeSourceMaps } from './normalize-source-maps'; import { getSupportedBrowsers } from './supported-browsers'; @@ -32,6 +33,7 @@ export type NormalizedBrowserBuilderSchema = BrowserBuilderSchema & assets: AssetPatternClass[]; fileReplacements: NormalizedFileReplacement[]; optimization: NormalizedOptimizationOptions; + polyfills: string[]; }; export function normalizeBrowserSchema( @@ -42,8 +44,6 @@ export function normalizeBrowserSchema( metadata: json.JsonObject, logger: logging.LoggerApi, ): NormalizedBrowserBuilderSchema { - const normalizedSourceMapOptions = normalizeSourceMaps(options.sourceMap || false); - return { ...options, cache: normalizeCacheOptions(metadata, workspaceRoot), @@ -55,7 +55,8 @@ export function normalizeBrowserSchema( ), fileReplacements: normalizeFileReplacements(options.fileReplacements || [], workspaceRoot), optimization: normalizeOptimization(options.optimization), - sourceMap: normalizedSourceMapOptions, + sourceMap: normalizeSourceMaps(options.sourceMap || false), + polyfills: normalizePolyfills(options.polyfills, workspaceRoot), preserveSymlinks: options.preserveSymlinks === undefined ? process.execArgv.includes('--preserve-symlinks') diff --git a/packages/angular_devkit/build_angular/src/utils/normalize-polyfills.ts b/packages/angular_devkit/build_angular/src/utils/normalize-polyfills.ts new file mode 100644 index 000000000000..8eb75745b0b9 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/utils/normalize-polyfills.ts @@ -0,0 +1,28 @@ +/** + * @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.io/license + */ + +import { existsSync } from 'fs'; +import { resolve } from 'path'; + +export function normalizePolyfills( + polyfills: string[] | string | undefined, + root: string, +): string[] { + if (!polyfills) { + return []; + } + + const polyfillsList = Array.isArray(polyfills) ? polyfills : [polyfills]; + + return polyfillsList.map((p) => { + const resolvedPath = resolve(root, p); + + // If file doesn't exist, let the bundle resolve it using node module resolution. + return existsSync(resolvedPath) ? resolvedPath : p; + }); +} diff --git a/packages/angular_devkit/build_angular/src/webpack/configs/common.ts b/packages/angular_devkit/build_angular/src/webpack/configs/common.ts index 50a8ba90c61f..98f5dd550c50 100644 --- a/packages/angular_devkit/build_angular/src/webpack/configs/common.ts +++ b/packages/angular_devkit/build_angular/src/webpack/configs/common.ts @@ -77,7 +77,7 @@ export async function getCommonConfig(wco: WebpackConfigOptions): Promise jasmine -> zone.js/testing. + const zoneTestingEntryPoint = 'zone.js/testing'; + const polyfillsExludingZoneTesting = polyfills.filter((p) => p !== zoneTestingEntryPoint); + + if (Array.isArray(entryPoints['polyfills'])) { + entryPoints['polyfills'].push(...polyfillsExludingZoneTesting); + } else { + entryPoints['polyfills'] = polyfillsExludingZoneTesting; + } + + if (polyfillsExludingZoneTesting.length !== polyfills.length) { + if (Array.isArray(entryPoints['main'])) { + entryPoints['main'].unshift(zoneTestingEntryPoint); } else { - entryPoints['polyfills'] = [projectPolyfills]; + entryPoints['main'] = [zoneTestingEntryPoint, entryPoints['main'] as string]; } } } diff --git a/packages/schematics/angular/application/files/src/polyfills.ts.template b/packages/schematics/angular/application/files/src/polyfills.ts.template deleted file mode 100644 index 429bb9ef2d34..000000000000 --- a/packages/schematics/angular/application/files/src/polyfills.ts.template +++ /dev/null @@ -1,53 +0,0 @@ -/** - * This file includes polyfills needed by Angular and is loaded before the app. - * You can add your own extra polyfills to this file. - * - * This file is divided into 2 sections: - * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. - * 2. Application imports. Files imported after ZoneJS that should be loaded before your main - * file. - * - * The current setup is for so-called "evergreen" browsers; the last versions of browsers that - * automatically update themselves. This includes recent versions of Safari, Chrome (including - * Opera), Edge on the desktop, and iOS and Chrome on mobile. - * - * Learn more in https://angular.io/guide/browser-support - */ - -/*************************************************************************************************** - * BROWSER POLYFILLS - */ - -/** - * By default, zone.js will patch all possible macroTask and DomEvents - * user can disable parts of macroTask/DomEvents patch by setting following flags - * because those flags need to be set before `zone.js` being loaded, and webpack - * will put import in the top of bundle, so user need to create a separate file - * in this directory (for example: zone-flags.ts), and put the following flags - * into that file, and then add the following code before importing zone.js. - * import './zone-flags'; - * - * The flags allowed in zone-flags.ts are listed here. - * - * The following flags will work for all browsers. - * - * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame - * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick - * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames - * - * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js - * with the following flag, it will bypass `zone.js` patch for IE/Edge - * - * (window as any).__Zone_enable_cross_context_check = true; - * - */ - -/*************************************************************************************************** - * Zone JS is required by default for Angular itself. - */ -import 'zone.js'; // Included with Angular CLI. - - -/*************************************************************************************************** - * APPLICATION IMPORTS - */ diff --git a/packages/schematics/angular/application/files/src/test.ts.template b/packages/schematics/angular/application/files/src/test.ts.template index 863549a71308..0e689016906e 100644 --- a/packages/schematics/angular/application/files/src/test.ts.template +++ b/packages/schematics/angular/application/files/src/test.ts.template @@ -1,6 +1,4 @@ // This file is required by karma.conf.js and loads recursively all the .spec and framework files - -import 'zone.js/testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, diff --git a/packages/schematics/angular/application/files/tsconfig.app.json.template b/packages/schematics/angular/application/files/tsconfig.app.json.template index 1966d893bf6d..7e2267cbe2d5 100644 --- a/packages/schematics/angular/application/files/tsconfig.app.json.template +++ b/packages/schematics/angular/application/files/tsconfig.app.json.template @@ -6,8 +6,7 @@ "types": [] }, "files": [ - "src/main.ts", - "src/polyfills.ts" + "src/main.ts" ], "include": [ "src/**/*.d.ts" diff --git a/packages/schematics/angular/application/files/tsconfig.spec.json.template b/packages/schematics/angular/application/files/tsconfig.spec.json.template index 0e856e624157..c9c241170103 100644 --- a/packages/schematics/angular/application/files/tsconfig.spec.json.template +++ b/packages/schematics/angular/application/files/tsconfig.spec.json.template @@ -8,8 +8,7 @@ ] }, "files": [ - "src/test.ts", - "src/polyfills.ts" + "src/test.ts" ], "include": [ "src/**/*.spec.ts", diff --git a/packages/schematics/angular/application/index.ts b/packages/schematics/angular/application/index.ts index 88485f9cc3ff..e804269d4545 100644 --- a/packages/schematics/angular/application/index.ts +++ b/packages/schematics/angular/application/index.ts @@ -158,7 +158,7 @@ function addAppToWorkspaceFile( outputPath: `dist/${folderName}`, index: `${sourceRoot}/index.html`, main: `${sourceRoot}/main.ts`, - polyfills: `${sourceRoot}/polyfills.ts`, + polyfills: ['zone.js'], tsConfig: `${projectRoot}tsconfig.app.json`, inlineStyleLanguage, assets: [`${sourceRoot}/favicon.ico`, `${sourceRoot}/assets`], @@ -211,7 +211,7 @@ function addAppToWorkspaceFile( builder: Builders.Karma, options: { main: `${sourceRoot}/test.ts`, - polyfills: `${sourceRoot}/polyfills.ts`, + polyfills: ['zone.js', 'zone.js/testing'], tsConfig: `${projectRoot}tsconfig.spec.json`, karmaConfig: `${projectRoot}karma.conf.js`, inlineStyleLanguage, diff --git a/packages/schematics/angular/application/index_spec.ts b/packages/schematics/angular/application/index_spec.ts index 0e1bfc6f0665..7055f485e7ca 100644 --- a/packages/schematics/angular/application/index_spec.ts +++ b/packages/schematics/angular/application/index_spec.ts @@ -60,7 +60,6 @@ describe('Application Schematic', () => { '/projects/foo/src/favicon.ico', '/projects/foo/src/index.html', '/projects/foo/src/main.ts', - '/projects/foo/src/polyfills.ts', '/projects/foo/src/styles.css', '/projects/foo/src/test.ts', '/projects/foo/src/app/app.module.ts', @@ -157,7 +156,7 @@ describe('Application Schematic', () => { .runSchematicAsync('application', defaultOptions, workspaceTree) .toPromise(); const { files, extends: _extends } = readJsonFile(tree, '/projects/foo/tsconfig.app.json'); - expect(files).toEqual(['src/main.ts', 'src/polyfills.ts']); + expect(files).toEqual(['src/main.ts']); expect(_extends).toBe('../../tsconfig.json'); }); @@ -166,7 +165,7 @@ describe('Application Schematic', () => { .runSchematicAsync('application', defaultOptions, workspaceTree) .toPromise(); const { files, extends: _extends } = readJsonFile(tree, '/projects/foo/tsconfig.spec.json'); - expect(files).toEqual(['src/test.ts', 'src/polyfills.ts']); + expect(files).toEqual(['src/test.ts']); expect(_extends).toBe('../../tsconfig.json'); }); @@ -270,7 +269,6 @@ describe('Application Schematic', () => { '/projects/foo/src/favicon.ico', '/projects/foo/src/index.html', '/projects/foo/src/main.ts', - '/projects/foo/src/polyfills.ts', '/projects/foo/src/styles.css', '/projects/foo/src/app/app.module.ts', '/projects/foo/src/app/app.component.ts', @@ -300,7 +298,6 @@ describe('Application Schematic', () => { '/projects/foo/src/favicon.ico', '/projects/foo/src/index.html', '/projects/foo/src/main.ts', - '/projects/foo/src/polyfills.ts', '/projects/foo/src/styles.css', '/projects/foo/src/app/app.module.ts', '/projects/foo/src/app/app.component.css', @@ -331,7 +328,6 @@ describe('Application Schematic', () => { '/projects/foo/src/favicon.ico', '/projects/foo/src/index.html', '/projects/foo/src/main.ts', - '/projects/foo/src/polyfills.ts', '/projects/foo/src/styles.css', '/projects/foo/src/app/app.module.ts', '/projects/foo/src/app/app.component.html', @@ -413,7 +409,6 @@ describe('Application Schematic', () => { '/src/favicon.ico', '/src/index.html', '/src/main.ts', - '/src/polyfills.ts', '/src/styles.css', '/src/test.ts', '/src/app/app.module.ts', @@ -437,7 +432,7 @@ describe('Application Schematic', () => { const buildOpt = prj.architect.build.options; expect(buildOpt.index).toEqual('src/index.html'); expect(buildOpt.main).toEqual('src/main.ts'); - expect(buildOpt.polyfills).toEqual('src/polyfills.ts'); + expect(buildOpt.polyfills).toEqual(['zone.js']); expect(buildOpt.tsConfig).toEqual('tsconfig.app.json'); const testOpt = prj.architect.test.options; @@ -515,7 +510,7 @@ describe('Application Schematic', () => { expect(appTsConfig.extends).toEqual('./tsconfig.json'); const specTsConfig = readJsonFile(tree, '/tsconfig.spec.json'); expect(specTsConfig.extends).toEqual('./tsconfig.json'); - expect(specTsConfig.files).toEqual(['src/test.ts', 'src/polyfills.ts']); + expect(specTsConfig.files).toEqual(['src/test.ts']); }); it(`should create correct paths when 'newProjectRoot' is blank`, async () => { @@ -532,7 +527,7 @@ describe('Application Schematic', () => { const buildOpt = project.architect.build.options; expect(buildOpt.index).toEqual('foo/src/index.html'); expect(buildOpt.main).toEqual('foo/src/main.ts'); - expect(buildOpt.polyfills).toEqual('foo/src/polyfills.ts'); + expect(buildOpt.polyfills).toEqual(['zone.js']); expect(buildOpt.tsConfig).toEqual('foo/tsconfig.app.json'); const appTsConfig = readJsonFile(tree, '/foo/tsconfig.app.json'); diff --git a/packages/schematics/angular/library/files/src/test.ts.template b/packages/schematics/angular/library/files/src/test.ts.template index 406f37d5a7a5..0e689016906e 100644 --- a/packages/schematics/angular/library/files/src/test.ts.template +++ b/packages/schematics/angular/library/files/src/test.ts.template @@ -1,7 +1,4 @@ // This file is required by karma.conf.js and loads recursively all the .spec and framework files - -import 'zone.js'; -import 'zone.js/testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, diff --git a/packages/schematics/angular/library/index.ts b/packages/schematics/angular/library/index.ts index a6959bdf70cb..69524593bcc2 100644 --- a/packages/schematics/angular/library/index.ts +++ b/packages/schematics/angular/library/index.ts @@ -110,6 +110,7 @@ function addLibToWorkspaceFile( options: { main: `${projectRoot}/src/test.ts`, tsConfig: `${projectRoot}/tsconfig.spec.json`, + polyfills: ['zone.js', 'zone.js/testing'], karmaConfig: `${projectRoot}/karma.conf.js`, }, }, diff --git a/tests/legacy-cli/e2e/tests/build/disk-cache-purge.ts b/tests/legacy-cli/e2e/tests/build/disk-cache-purge.ts index 5cc3b6d0606c..bba2ad7e826b 100644 --- a/tests/legacy-cli/e2e/tests/build/disk-cache-purge.ts +++ b/tests/legacy-cli/e2e/tests/build/disk-cache-purge.ts @@ -9,7 +9,6 @@ export default async function () { // No need to include all applications code to verify disk cache existence. await writeFile('src/main.ts', 'console.log(1);'); - await writeFile('src/polyfills.ts', 'console.log(1);'); // Enable cache for all environments await updateJsonFile('angular.json', (config) => { diff --git a/tests/legacy-cli/e2e/tests/build/disk-cache.ts b/tests/legacy-cli/e2e/tests/build/disk-cache.ts index 559313163187..1873905646ca 100644 --- a/tests/legacy-cli/e2e/tests/build/disk-cache.ts +++ b/tests/legacy-cli/e2e/tests/build/disk-cache.ts @@ -10,7 +10,6 @@ export default async function () { // No need to include all applications code to verify disk cache existence. await writeFile('src/main.ts', 'console.log(1);'); - await writeFile('src/polyfills.ts', 'console.log(1);'); try { // Should be enabled by default.