diff --git a/packages/angular_devkit/build_angular/src/babel/presets/application.ts b/packages/angular_devkit/build_angular/src/babel/presets/application.ts index 234f8fa7052c..ca1a6caf960d 100644 --- a/packages/angular_devkit/build_angular/src/babel/presets/application.ts +++ b/packages/angular_devkit/build_angular/src/babel/presets/application.ts @@ -47,7 +47,7 @@ export interface ApplicationPresetOptions { linkerPluginCreator: typeof import('@angular/compiler-cli/linker/babel').createEs2015LinkerPlugin; }; - forceES5?: boolean; + forcePresetEnv?: boolean; forceAsyncTransformation?: boolean; instrumentCode?: { includedBasePath: string; @@ -59,6 +59,7 @@ export interface ApplicationPresetOptions { wrapDecorators: boolean; }; + supportedBrowsers?: string[]; diagnosticReporter?: DiagnosticReporter; } @@ -178,14 +179,13 @@ export default function (api: unknown, options: ApplicationPresetOptions) { ); } - if (options.forceES5) { + if (options.forcePresetEnv) { presets.push([ require('@babel/preset-env').default, { bugfixes: true, modules: false, - // Comparable behavior to tsconfig target of ES5 - targets: { ie: 9 }, + targets: options.supportedBrowsers, exclude: ['transform-typeof-symbol'], }, ]); diff --git a/packages/angular_devkit/build_angular/src/babel/webpack-loader.ts b/packages/angular_devkit/build_angular/src/babel/webpack-loader.ts index a19f499141b9..aa2845fd666e 100644 --- a/packages/angular_devkit/build_angular/src/babel/webpack-loader.ts +++ b/packages/angular_devkit/build_angular/src/babel/webpack-loader.ts @@ -72,18 +72,26 @@ export default custom(() => { return { async customOptions(options, { source, map }) { - const { i18n, scriptTarget, aot, optimize, instrumentCode, ...rawOptions } = - options as AngularBabelLoaderOptions; + const { + i18n, + scriptTarget, + aot, + optimize, + instrumentCode, + supportedBrowsers, + ...rawOptions + } = options as AngularBabelLoaderOptions; // Must process file if plugins are added let shouldProcess = Array.isArray(rawOptions.plugins) && rawOptions.plugins.length > 0; const customOptions: ApplicationPresetOptions = { forceAsyncTransformation: false, - forceES5: false, + forcePresetEnv: false, angularLinker: undefined, i18n: undefined, instrumentCode: undefined, + supportedBrowsers, }; // Analyze file for linking @@ -107,20 +115,35 @@ export default custom(() => { // Analyze for ES target processing const esTarget = scriptTarget as ScriptTarget | undefined; - if (esTarget !== undefined) { - if (esTarget < ScriptTarget.ES2015) { - customOptions.forceES5 = true; - } else if (esTarget >= ScriptTarget.ES2017 || /\.[cm]?js$/.test(this.resourcePath)) { - // Application code (TS files) will only contain native async if target is ES2017+. - // However, third-party libraries can regardless of the target option. - // APF packages with code in [f]esm2015 directories is downlevelled to ES2015 and - // will not have native async. - customOptions.forceAsyncTransformation = - !/[\\/][_f]?esm2015[\\/]/.test(this.resourcePath) && source.includes('async'); - } - shouldProcess ||= customOptions.forceAsyncTransformation || customOptions.forceES5 || false; + const isJsFile = /\.[cm]?js$/.test(this.resourcePath); + + // The below should be dropped when we no longer support ES5 TypeScript output. + if (esTarget === ScriptTarget.ES5) { + // This is needed because when target is ES5 we change the TypeScript target to ES2015 + // because it simplifies build-optimization passes. + // @see https://github.com/angular/angular-cli/blob/22af6520834171d01413d4c7e4a9f13fb752252e/packages/angular_devkit/build_angular/src/webpack/plugins/typescript.ts#L51-L56 + customOptions.forcePresetEnv = true; + // Comparable behavior to tsconfig target of ES5 + customOptions.supportedBrowsers = ['IE 9']; + } else if (isJsFile) { + // Applications code ES version can be controlled using TypeScript's `target` option. + // However, this doesn't effect libraries and hence we use preset-env to downlevel ES fetaures + // based on the supported browsers in browserlist. + customOptions.forcePresetEnv = true; } + if ((esTarget !== undefined && esTarget >= ScriptTarget.ES2017) || isJsFile) { + // Application code (TS files) will only contain native async if target is ES2017+. + // However, third-party libraries can regardless of the target option. + // APF packages with code in [f]esm2015 directories is downlevelled to ES2015 and + // will not have native async. + customOptions.forceAsyncTransformation = + !/[\\/][_f]?esm2015[\\/]/.test(this.resourcePath) && source.includes('async'); + } + + shouldProcess ||= + customOptions.forceAsyncTransformation || customOptions.forcePresetEnv || false; + // Analyze for i18n inlining if ( i18n && diff --git a/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/typescript-target_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/typescript-target_spec.ts index 538d30a40aa4..68cd9304c2f8 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/typescript-target_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/typescript-target_spec.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import { logging } from '@angular-devkit/core'; import { buildWebpackBrowser } from '../../index'; import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; @@ -141,12 +142,12 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { await harness.writeFile( 'src/main.ts', ` - (async () => { - for await (const o of [1, 2, 3]) { - console.log("for await...of"); - } - })(); - `, + (async () => { + for await (const o of [1, 2, 3]) { + console.log("for await...of"); + } + })(); + `, ); harness.useTarget('build', { @@ -176,14 +177,14 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { await harness.writeFile( 'src/es2015-syntax.js', ` - class foo { - bar() { - console.log('baz'); - } - } - - (new foo()).bar(); - `, + class foo { + bar() { + console.log('baz'); + } + } + + (new foo()).bar(); + `, ); harness.useTarget('build', { @@ -198,5 +199,55 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { expect(result?.success).toBe(true); }); + + it('a deprecation warning should be issued when targetting ES5', async () => { + await harness.modifyFile('src/tsconfig.app.json', (content) => { + const tsconfig = JSON.parse(content); + if (!tsconfig.compilerOptions) { + tsconfig.compilerOptions = {}; + } + tsconfig.compilerOptions.target = 'es5'; + + return JSON.stringify(tsconfig); + }); + await harness.writeFiles({ + 'src/tsconfig.worker.json': `{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/worker", + "lib": [ + "es2018", + "webworker" + ], + "types": [] + }, + "include": [ + "**/*.worker.ts", + ] + }`, + 'src/app/app.worker.ts': ` + /// + + const prefix: string = 'Data: '; + addEventListener('message', ({ data }) => { + postMessage(prefix + data); + }); + `, + }); + + harness.useTarget('build', { + ...BASE_OPTIONS, + webWorkerTsConfig: 'src/tsconfig.worker.json', + }); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + const deprecationMessages = logs.filter(({ message }) => + message.startsWith('DEPRECATED: ES5 output is deprecated'), + ); + + expect(deprecationMessages).toHaveSize(1); + }); }); }); diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build_localize_replaced_watch_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build_localize_replaced_watch_spec.ts index 5ada84dcc828..cc6981723e5a 100644 --- a/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build_localize_replaced_watch_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build_localize_replaced_watch_spec.ts @@ -55,7 +55,7 @@ describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => { const buildCount = await harness .execute() .pipe( - timeout(BUILD_TIMEOUT), + timeout(BUILD_TIMEOUT * 2), concatMap(async ({ result }, index) => { expect(result?.success).toBe(true); 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 d81636c09d45..2c33d03bae47 100644 --- a/packages/angular_devkit/build_angular/src/webpack/configs/common.ts +++ b/packages/angular_devkit/build_angular/src/webpack/configs/common.ts @@ -378,6 +378,7 @@ export async function getCommonConfig(wco: WebpackConfigOptions): Promise = {};