From d56d04bef2c3dd6d715d343ba0a946cd8277f2dd Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Thu, 1 Dec 2022 12:30:20 -0500 Subject: [PATCH 1/2] refactor(@angular-devkit/build-angular): update browserslist conversion to latest esbuild browsers The conversion of browserslist targets to esbuild targets has been updated to reflect additions to esbuild. Additional browsers are now supported and the major/minor versions have been normalized. The later of which ensures that `.0` major versions are not misinterpreted as ranges rather than specific versions. --- .../build_angular/src/utils/esbuild-targets.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/angular_devkit/build_angular/src/utils/esbuild-targets.ts b/packages/angular_devkit/build_angular/src/utils/esbuild-targets.ts index 276adf234690..1147738e93a9 100644 --- a/packages/angular_devkit/build_angular/src/utils/esbuild-targets.ts +++ b/packages/angular_devkit/build_angular/src/utils/esbuild-targets.ts @@ -14,10 +14,19 @@ export function transformSupportedBrowsersToTargets(supportedBrowsers: string[]) const transformed: string[] = []; // https://esbuild.github.io/api/#target - const esBuildSupportedBrowsers = new Set(['safari', 'firefox', 'edge', 'chrome', 'ios', 'node']); + const esBuildSupportedBrowsers = new Set([ + 'chrome', + 'edge', + 'firefox', + 'ie', + 'ios', + 'node', + 'opera', + 'safari', + ]); for (const browser of supportedBrowsers) { - let [browserName, version] = browser.split(' '); + let [browserName, version] = browser.toLowerCase().split(' '); // browserslist uses the name `ios_saf` for iOS Safari whereas esbuild uses `ios` if (browserName === 'ios_saf') { @@ -33,6 +42,11 @@ export function transformSupportedBrowsersToTargets(supportedBrowsers: string[]) // esbuild only supports numeric versions so `TP` is converted to a high number (999) since // a Technology Preview (TP) of Safari is assumed to support all currently known features. version = '999'; + } else if (!version.includes('.')) { + // A lone major version is considered by esbuild to include all minor versions. However, + // browserslist does not and is also inconsistent in its `.0` version naming. For example, + // Safari 15.0 is named `safari 15` but Safari 16.0 is named `safari 16.0`. + version += '.0'; } transformed.push(browserName + version); From 65a5094d06608006ab9d6a2e61625200cd933fe3 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Thu, 1 Dec 2022 12:34:00 -0500 Subject: [PATCH 2/2] fix(@angular-devkit/build-angular): downlevel class fields with Safari <= v15 for esbuild To provide a workaround for a Safari bug involving class fields and variable scoping, the esbuild-based browser application builder will now downlevel class fields if Safari (desktop or iOS) v15.x or earlier is within the target browsers for an application. This is an esbuild variant of the fix for the Webpack-based builder. For more details regarding the issue, please see: #24357 --- .../src/builders/browser-esbuild/index.ts | 65 +++++++++++++++---- 1 file changed, 52 insertions(+), 13 deletions(-) diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts index 53159f520fb2..2508c1279cff 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts @@ -253,19 +253,7 @@ function createCodeBundleOptions( entryNames: outputNames.bundles, assetNames: outputNames.media, target, - supported: { - // Native async/await is not supported with Zone.js. Disabling support here will cause - // esbuild to downlevel async/await and for await...of to a Zone.js supported form. However, esbuild - // does not currently support downleveling async generators. Instead babel is used within the JS/TS - // loader to perform the downlevel transformation. - // NOTE: If esbuild adds support in the future, the babel support for async generators can be disabled. - 'async-await': false, - // V8 currently has a performance defect involving object spread operations that can cause signficant - // degradation in runtime performance. By not supporting the language feature here, a downlevel form - // will be used instead which provides a workaround for the performance issue. - // For more details: https://bugs.chromium.org/p/v8/issues/detail?id=11536 - 'object-rest-spread': false, - }, + supported: getFeatureSupport(target), mainFields: ['es2020', 'browser', 'module', 'main'], conditions: ['es2020', 'es2015', 'module'], resolveExtensions: ['.ts', '.tsx', '.mjs', '.js'], @@ -314,6 +302,57 @@ function createCodeBundleOptions( }; } +/** + * Generates a syntax feature object map for Angular applications based on a list of targets. + * A full set of feature names can be found here: https://esbuild.github.io/api/#supported + * @param target An array of browser/engine targets in the format accepted by the esbuild `target` option. + * @returns An object that can be used with the esbuild build `supported` option. + */ +function getFeatureSupport(target: string[]): BuildOptions['supported'] { + const supported: Record = { + // Native async/await is not supported with Zone.js. Disabling support here will cause + // esbuild to downlevel async/await and for await...of to a Zone.js supported form. However, esbuild + // does not currently support downleveling async generators. Instead babel is used within the JS/TS + // loader to perform the downlevel transformation. + // NOTE: If esbuild adds support in the future, the babel support for async generators can be disabled. + 'async-await': false, + // V8 currently has a performance defect involving object spread operations that can cause signficant + // degradation in runtime performance. By not supporting the language feature here, a downlevel form + // will be used instead which provides a workaround for the performance issue. + // For more details: https://bugs.chromium.org/p/v8/issues/detail?id=11536 + 'object-rest-spread': false, + }; + + // Detect Safari browser versions that have a class field behavior bug + // See: https://github.com/angular/angular-cli/issues/24355#issuecomment-1333477033 + // See: https://github.com/WebKit/WebKit/commit/e8788a34b3d5f5b4edd7ff6450b80936bff396f2 + let safariClassFieldScopeBug = false; + for (const browser of target) { + let majorVersion; + if (browser.startsWith('ios')) { + majorVersion = Number(browser.slice(3, 5)); + } else if (browser.startsWith('safari')) { + majorVersion = Number(browser.slice(6, 8)); + } else { + continue; + } + // Technically, 14.0 is not broken but rather does not have support. However, the behavior + // is identical since it would be set to false by esbuild if present as a target. + if (majorVersion === 14 || majorVersion === 15) { + safariClassFieldScopeBug = true; + break; + } + } + // If class field support cannot be used set to false; otherwise leave undefined to allow + // esbuild to use `target` to determine support. + if (safariClassFieldScopeBug) { + supported['class-field'] = false; + supported['class-static-field'] = false; + } + + return supported; +} + function createGlobalStylesBundleOptions( options: NormalizedBrowserOptions, target: string[],