Skip to content

fix(@angular-devkit/build-angular): downlevel libraries based on the browserslist configurations #23185

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 24, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -59,6 +59,7 @@ export interface ApplicationPresetOptions {
wrapDecorators: boolean;
};

supportedBrowsers?: string[];
diagnosticReporter?: DiagnosticReporter;
}

Expand Down Expand Up @@ -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'],
},
]);
Expand Down
53 changes: 38 additions & 15 deletions packages/angular_devkit/build_angular/src/babel/webpack-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,26 @@ export default custom<ApplicationPresetOptions>(() => {

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
Expand All @@ -107,20 +115,35 @@ export default custom<ApplicationPresetOptions>(() => {

// 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 &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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', {
Expand Down Expand Up @@ -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', {
Expand All @@ -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': `
/// <reference lib="webworker" />

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);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ export async function getCommonConfig(wco: WebpackConfigOptions): Promise<Config
scriptTarget,
aot: buildOptions.aot,
optimize: buildOptions.buildOptimizer,
supportedBrowsers: buildOptions.supportedBrowsers,
instrumentCode: codeCoverage
? {
includedBasePath: sourceRoot,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ function ensureIvy(wco: WebpackConfigOptions): void {
wco.tsConfig.options.enableIvy = true;
}

let es5TargetWarningsShown = false;
export function createIvyPlugin(
wco: WebpackConfigOptions,
aot: boolean,
Expand Down Expand Up @@ -53,6 +54,13 @@ export function createIvyPlugin(
// as for third-party libraries. This greatly reduces the complexity of static analysis.
if (wco.scriptTarget < ScriptTarget.ES2015) {
compilerOptions.target = ScriptTarget.ES2015;
if (!es5TargetWarningsShown) {
wco.logger.warn(
'DEPRECATED: ES5 output is deprecated. Please update TypeScript `target` compiler option to ES2015 or later.',
);

es5TargetWarningsShown = true;
}
}

const fileReplacements: Record<string, string> = {};
Expand Down