diff --git a/packages/angular_devkit/build_angular/package.json b/packages/angular_devkit/build_angular/package.json index 725a359dab1d..522e98c6606d 100644 --- a/packages/angular_devkit/build_angular/package.json +++ b/packages/angular_devkit/build_angular/package.json @@ -13,7 +13,8 @@ "@angular-devkit/core": "0.0.0", "@babel/core": "7.12.10", "@babel/generator": "7.12.11", - "@babel/plugin-transform-runtime": "7.12.10", + "@babel/plugin-transform-async-to-generator": "7.12.1", + "@babel/plugin-transform-runtime": "7.12.10", "@babel/preset-env": "7.12.11", "@babel/runtime": "7.12.5", "@babel/template": "7.12.7", 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 f0e12ec93a9e..bd5ffcc6b52f 100644 --- a/packages/angular_devkit/build_angular/src/babel/presets/application.ts +++ b/packages/angular_devkit/build_angular/src/babel/presets/application.ts @@ -170,7 +170,6 @@ export default function (api: unknown, options: ApplicationPresetOptions) { if (options.forceAsyncTransformation) { // Always transform async/await to support Zone.js - // tslint:disable-next-line: no-implicit-dependencies plugins.push(require('@babel/plugin-transform-async-to-generator').default); needRuntimeTransform = true; } 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 e18d81ed4d2d..56187e719a9e 100644 --- a/packages/angular_devkit/build_angular/src/babel/webpack-loader.ts +++ b/packages/angular_devkit/build_angular/src/babel/webpack-loader.ts @@ -9,6 +9,7 @@ import { custom } from 'babel-loader'; import { ScriptTarget } from 'typescript'; interface AngularCustomOptions { + forceAsyncTransformation: boolean; forceES5: boolean; shouldLink: boolean; } @@ -27,7 +28,8 @@ async function checkLinking( source: string, ): Promise<{ hasLinkerSupport?: boolean; requiresLinking: boolean }> { // @angular/core and @angular/compiler will cause false positives - if (/[\\\/]@angular[\\\/](?:compiler|core)/.test(path)) { + // Also, TypeScript files do not require linking + if (/[\\\/]@angular[\\\/](?:compiler|core)|\.tsx?$/.test(path)) { return { requiresLinking: false }; } @@ -82,11 +84,15 @@ export default custom(() => { // Analyze for ES target processing let forceES5 = false; + let forceAsyncTransformation = false; const esTarget = scriptTarget as ScriptTarget; if (esTarget < ScriptTarget.ES2015) { - forceES5 = true; + // TypeScript files will have already been downlevelled + forceES5 = !/\.tsx?$/.test(this.resourcePath); + } else if (esTarget >= ScriptTarget.ES2017) { + forceAsyncTransformation = source.includes('async'); } - shouldProcess ||= forceES5; + shouldProcess ||= forceAsyncTransformation || forceES5; // Add provided loader options to default base options const options: Record = { @@ -100,7 +106,7 @@ export default custom(() => { options.ignore = [() => true]; } - return { custom: { forceES5, shouldLink }, loader: options }; + return { custom: { forceAsyncTransformation, forceES5, shouldLink }, loader: options }; }, config(configuration, { customOptions }) { return { @@ -112,6 +118,7 @@ export default custom(() => { { angularLinker: customOptions.shouldLink, forceES5: customOptions.forceES5, + forceAsyncTransformation: customOptions.forceAsyncTransformation, diagnosticReporter: (type, message) => { switch (type) { case 'error': diff --git a/packages/angular_devkit/build_angular/src/browser/tests/behavior/typescript-target_spec.ts b/packages/angular_devkit/build_angular/src/browser/tests/behavior/typescript-target_spec.ts new file mode 100644 index 000000000000..5de9f6166f9e --- /dev/null +++ b/packages/angular_devkit/build_angular/src/browser/tests/behavior/typescript-target_spec.ts @@ -0,0 +1,93 @@ +/** + * @license + * Copyright Google Inc. 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 { buildWebpackBrowser } from '../../index'; +import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { + describe('Behavior: "TypeScript Configuration - target"', () => { + it('downlevels async functions when targetting ES2017', async () => { + // Set TypeScript configuration target to ES2017 to enable native async + await harness.modifyFile('src/tsconfig.app.json', (content) => { + const tsconfig = JSON.parse(content); + if (!tsconfig.compilerOptions) { + tsconfig.compilerOptions = {}; + } + tsconfig.compilerOptions.target = 'es2017'; + + return JSON.stringify(tsconfig); + }); + + // Add a JavaScript file with async code + await harness.writeFile( + 'src/async-test.js', + 'async function testJs() { console.log("from-async-js-function"); }', + ); + + // Add an async function to the project as well as JavaScript file + await harness.modifyFile( + 'src/main.ts', + (content) => + 'import "./async-test";\n' + + content + + `\nasync function testApp(): Promise { console.log("from-async-app-function"); }`, + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result, logs } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + expect(logs).not.toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching('Zone.js does not support native async/await in ES2017+'), + }), + ); + + harness.expectFile('dist/main.js').content.not.toMatch(/\sasync\s/); + harness.expectFile('dist/main.js').content.toContain('"from-async-app-function"'); + harness.expectFile('dist/main.js').content.toContain('"from-async-js-function"'); + }); + + it('downlevels async functions when targetting greater than ES2017', async () => { + // Set TypeScript configuration target greater than ES2017 to enable native async + await harness.modifyFile('src/tsconfig.app.json', (content) => { + const tsconfig = JSON.parse(content); + if (!tsconfig.compilerOptions) { + tsconfig.compilerOptions = {}; + } + tsconfig.compilerOptions.target = 'es2020'; + + return JSON.stringify(tsconfig); + }); + + // Add an async function to the project + await harness.writeFile( + 'src/main.ts', + 'async function test(): Promise { console.log("from-async-function"); }', + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result, logs } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + expect(logs).not.toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching('Zone.js does not support native async/await in ES2017+'), + }), + ); + + harness.expectFile('dist/main.js').content.not.toMatch(/\sasync\s/); + harness.expectFile('dist/main.js').content.toContain('"from-async-function"'); + }); + }); +}); 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 781e4e64d70b..41dda40f1221 100644 --- a/packages/angular_devkit/build_angular/src/webpack/configs/common.ts +++ b/packages/angular_devkit/build_angular/src/webpack/configs/common.ts @@ -544,7 +544,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration { sideEffects: true, }, { - test: /\.[cm]?js$/, + test: /\.[cm]?js$|\.tsx?$/, exclude: [/[\/\\](?:core-js|\@babel|tslib|web-animations-js)[\/\\]/, /(ngfactory|ngstyle)\.js$/], use: [ { diff --git a/packages/angular_devkit/build_angular/src/webpack/configs/typescript.ts b/packages/angular_devkit/build_angular/src/webpack/configs/typescript.ts index aa3c0317b2f5..11986ccd7f9b 100644 --- a/packages/angular_devkit/build_angular/src/webpack/configs/typescript.ts +++ b/packages/angular_devkit/build_angular/src/webpack/configs/typescript.ts @@ -79,6 +79,7 @@ function createIvyPlugin( compilerOptions, fileReplacements, emitNgModuleScope: !optimize, + suppressZoneJsIncompatibilityWarning: true, }); } @@ -159,6 +160,7 @@ function _createAotPlugin( directTemplateLoading: true, ...options, compilerOptions, + suppressZoneJsIncompatibilityWarning: true, }; pluginOptions = _pluginOptionsOverrides(buildOptions, pluginOptions); diff --git a/yarn.lock b/yarn.lock index 6d8d387ee942..1be38e8142d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -645,7 +645,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-async-to-generator@^7.12.1": +"@babel/plugin-transform-async-to-generator@7.12.1", "@babel/plugin-transform-async-to-generator@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz#3849a49cc2a22e9743cbd6b52926d30337229af1" integrity sha512-SDtqoEcarK1DFlRJ1hHRY5HvJUj5kX4qmtpMAm2QnhOlyuMC4TMdCRgW6WXpv93rZeYNeLP22y8Aq2dbcDRM1A==