From 5fffee981819973b5bb11665d78f89a873bfc839 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Tue, 15 Apr 2025 14:02:26 -0400 Subject: [PATCH 1/4] refactor: add `baseline_browserslist` tool This tool uses `baseline-browser-mapping` to generate a `browserslist` configuration at build time from a given widely-available Baseline date. I opted _not_ to use "downstream browsers". This refers to browsers like Opera, which technically uses Blink and shares the same featureset as Chrome for a particular version. I decided against this to maintain a stricter interpretation of Baseline, given that those browsers are not included in Baseline today. Developers can manually widen their own `.browserslistrc` if they really want to and are comfortable accepting the risks that brings. Using `baseline-browser-mapping` as part of the build process means there is a potential risk that a bugfix in the package generates a different browserslist file which leads to a different build configuration that causes a problem for existing users. However, it's also just as likely (if not moreso) to fix a problem than cause one, so I'm inclined to call that WAI. If it becomes an issue in the future, we can potentially check in the generated `.browserslistrc` file itself rather than the Baseline date, meaning the list of browsers would be frozen until we explicitly update it between majors. --- .bazelignore | 1 + WORKSPACE | 1 + pnpm-lock.yaml | 24 +++++++++ pnpm-workspace.yaml | 1 + tools/baseline_browserslist/BUILD.bazel | 52 +++++++++++++++++++ .../baseline_browserslist.bzl | 26 ++++++++++ .../generate_browserslist.mts | 38 ++++++++++++++ .../generate_browserslist_spec.mts | 27 ++++++++++ tools/baseline_browserslist/index.mts | 15 ++++++ tools/baseline_browserslist/package.json | 6 +++ .../baseline_browserslist/tsconfig-build.json | 8 +++ .../baseline_browserslist/tsconfig-test.json | 4 ++ tools/baseline_browserslist/tsconfig.json | 4 ++ 13 files changed, 207 insertions(+) create mode 100644 tools/baseline_browserslist/BUILD.bazel create mode 100644 tools/baseline_browserslist/baseline_browserslist.bzl create mode 100644 tools/baseline_browserslist/generate_browserslist.mts create mode 100644 tools/baseline_browserslist/generate_browserslist_spec.mts create mode 100644 tools/baseline_browserslist/index.mts create mode 100644 tools/baseline_browserslist/package.json create mode 100644 tools/baseline_browserslist/tsconfig-build.json create mode 100644 tools/baseline_browserslist/tsconfig-test.json create mode 100644 tools/baseline_browserslist/tsconfig.json diff --git a/.bazelignore b/.bazelignore index e5c2c9453d9d..5f43add393c9 100644 --- a/.bazelignore +++ b/.bazelignore @@ -17,4 +17,5 @@ packages/ngtools/webpack/node_modules packages/schematics/angular/node_modules modules/testing/builder/node_modules tests/node_modules +tools/baseline_browserslist/node_modules tools/legacy-rnjs/node_modules diff --git a/WORKSPACE b/WORKSPACE index 9754913f609a..8271c2116486 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -169,6 +169,7 @@ npm_translate_lock( "//packages/ngtools/webpack:package.json", "//packages/schematics/angular:package.json", "//tests:package.json", + "//tools/baseline_browserslist:package.json", ], lifecycle_hooks_envs = { # TODO: Standardize browser management for `rules_js` diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 186f70a2c62d..e38f1c12efcc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -874,6 +874,12 @@ importers: specifier: 1.2.2 version: 1.2.2 + tools/baseline_browserslist: + devDependencies: + baseline-browser-mapping: + specifier: ^2.2.0 + version: 2.2.0 + packages: '@ampproject/remapping@2.3.0': @@ -1990,6 +1996,9 @@ packages: cpu: [x64] os: [win32] + '@mdn/browser-compat-data@6.0.6': + resolution: {integrity: sha512-awlDnCGbtdkyLieMpIKJQEgK7mxnL3he4UHm5AGn+asofiemlx4LVHM0FsYmp6O1irAClPNS135zZLhD2SZi+A==} + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} cpu: [arm64] @@ -3420,6 +3429,9 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} + baseline-browser-mapping@2.2.0: + resolution: {integrity: sha512-tLVamSyLn6h5kp7aDzBIPiNnx+ighswrdRs9ug3aeBgSXFQG2nFj1EfWvWTD1Y/ra9GDS8znPwMxTEitF6cC/g==} + basic-ftp@5.0.5: resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} engines: {node: '>=10.0.0'} @@ -7654,6 +7666,9 @@ packages: weak-lru-cache@1.2.2: resolution: {integrity: sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==} + web-features@2.33.0: + resolution: {integrity: sha512-oLzTO29Ax9TyQGNoNxpC+2Hj9if7lm2tuuAiEAb01BxcBt7yH40LAmIDg5PtuJ39lnwqm4wELATIKhj6WlJJpQ==} + web-streams-polyfill@3.3.3: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} @@ -9183,6 +9198,8 @@ snapshots: '@lmdb/lmdb-win32-x64@3.2.6': optional: true + '@mdn/browser-compat-data@6.0.6': {} + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': optional: true @@ -10837,6 +10854,11 @@ snapshots: base64id@2.0.0: {} + baseline-browser-mapping@2.2.0: + dependencies: + '@mdn/browser-compat-data': 6.0.6 + web-features: 2.33.0 + basic-ftp@5.0.5: {} batch@0.6.1: {} @@ -15751,6 +15773,8 @@ snapshots: weak-lru-cache@1.2.2: optional: true + web-features@2.33.0: {} + web-streams-polyfill@3.3.3: {} webdriver-js-extender@2.1.0: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ed02a080e9db..977541458634 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -15,3 +15,4 @@ packages: - packages/ngtools/webpack - modules/testing/builder - tests + - tools/baseline_browserslist diff --git a/tools/baseline_browserslist/BUILD.bazel b/tools/baseline_browserslist/BUILD.bazel new file mode 100644 index 000000000000..b8afa2926fa2 --- /dev/null +++ b/tools/baseline_browserslist/BUILD.bazel @@ -0,0 +1,52 @@ +load("@aspect_rules_js//js:defs.bzl", "js_binary") +load("@aspect_rules_ts//ts:defs.bzl", "ts_config") +load("@npm2//:defs.bzl", "npm_link_all_packages") +load("//tools:defaults.bzl", "jasmine_test", "ts_project") + +npm_link_all_packages() + +js_binary( + name = "baseline_browserslist", + data = [":baseline_browserslist_lib"], + entry_point = "index.mjs", + visibility = ["//:__subpackages__"], +) + +ts_project( + name = "baseline_browserslist_lib", + srcs = [ + "generate_browserslist.mts", + "index.mts", + ], + source_map = True, + tsconfig = ":build_tsconfig", + deps = [":node_modules/baseline-browser-mapping"], +) + +ts_project( + name = "baseline_browserslist_test_lib", + testonly = True, + srcs = ["generate_browserslist_spec.mts"], + tsconfig = ":test_tsconfig", + deps = [":baseline_browserslist_lib"], +) + +jasmine_test( + name = "baseline_browserslist_test", + data = [":baseline_browserslist_test_lib"], +) + +ts_config( + name = "build_tsconfig", + src = "tsconfig-build.json", + deps = [ + "//:build-tsconfig-esm", + "//:node_modules/@types/node", + ], +) + +ts_config( + name = "test_tsconfig", + src = "tsconfig-test.json", + deps = ["//:test-tsconfig-esm"], +) diff --git a/tools/baseline_browserslist/baseline_browserslist.bzl b/tools/baseline_browserslist/baseline_browserslist.bzl new file mode 100644 index 000000000000..144b33153828 --- /dev/null +++ b/tools/baseline_browserslist/baseline_browserslist.bzl @@ -0,0 +1,26 @@ +"""Generates a `browserslist` configuration from a Baseline date.""" + +load("@aspect_rules_js//js:defs.bzl", "js_run_binary") + +def baseline_browserslist(name, baseline, out, **kwargs): + """Generates a `browserslist` configuration from a Baseline date. + + Args: + name: Name of this target. + baseline: A string date in "YYYY-MM-DD" format of the Baseline widely + available browser set to use in the generated `browserslist`. + out: Name of the output browserslist file. Prefer using `.browserslistrc` + for the output as the `browserslist` package seems to not like files + with a different name, even when explicitly provided. + + See: https://web.dev/baseline + """ + + js_run_binary( + name = name, + srcs = [], + tool = Label(":baseline_browserslist"), + stdout = out, + args = [baseline], + **kwargs + ) diff --git a/tools/baseline_browserslist/generate_browserslist.mts b/tools/baseline_browserslist/generate_browserslist.mts new file mode 100644 index 000000000000..68dc8d7e2c5b --- /dev/null +++ b/tools/baseline_browserslist/generate_browserslist.mts @@ -0,0 +1,38 @@ +/** + * @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.dev/license + */ + +import { getCompatibleVersions } from 'baseline-browser-mapping'; + +// Map `baseline-browser-mapping` browsers to `browserslist` browsers. +const browsers: Record = { + chrome: 'Chrome', + chrome_android: 'ChromeAndroid', + edge: 'Edge', + firefox: 'Firefox', + firefox_android: 'FirefoxAndroid', + safari: 'Safari', + safari_ios: 'iOS', +}; + +/** + * Generates the `browserslist` configuration for the given Baseline date. + * + * @param date The Baseline "widely available" date to generate a `browserslist` + * configuration for. Uses `YYYY-MM-DD` format. + * @returns The `browserslist` configuration file content. + */ +export function generateBrowserslist(date: string): string { + // Generate a `browserslist` configuration. + return getCompatibleVersions({ + widelyAvailableOnDate: date, + includeDownstreamBrowsers: false, + }) + .filter(({ browser }) => browsers[browser]) + .map(({ browser, version }) => `${browsers[browser]} >= ${version}`) + .join('\n'); +} diff --git a/tools/baseline_browserslist/generate_browserslist_spec.mts b/tools/baseline_browserslist/generate_browserslist_spec.mts new file mode 100644 index 000000000000..c46f243f405c --- /dev/null +++ b/tools/baseline_browserslist/generate_browserslist_spec.mts @@ -0,0 +1,27 @@ +/** + * @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.dev/license + */ + +import { generateBrowserslist } from './generate_browserslist.mjs'; + +describe('generate_browserslist', () => { + describe('generateBrowserslist', () => { + it('generates a `browserslist` file', () => { + expect(generateBrowserslist('2025-03-31').trim()).toBe( + ` +Chrome >= 107 +ChromeAndroid >= 107 +Edge >= 107 +Firefox >= 104 +FirefoxAndroid >= 104 +Safari >= 16 +iOS >= 16 + `.trim(), + ); + }); + }); +}); diff --git a/tools/baseline_browserslist/index.mts b/tools/baseline_browserslist/index.mts new file mode 100644 index 000000000000..78d5aec5e3d5 --- /dev/null +++ b/tools/baseline_browserslist/index.mts @@ -0,0 +1,15 @@ +/** + * @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.dev/license + */ + +import { generateBrowserslist } from './generate_browserslist.mjs'; + +const [baselineDate] = process.argv.slice(2); +const browserslist = generateBrowserslist(baselineDate); + +// eslint-disable-next-line no-console +console.log(browserslist); diff --git a/tools/baseline_browserslist/package.json b/tools/baseline_browserslist/package.json new file mode 100644 index 000000000000..6480f833ecf9 --- /dev/null +++ b/tools/baseline_browserslist/package.json @@ -0,0 +1,6 @@ +{ + "type": "module", + "devDependencies": { + "baseline-browser-mapping": "^2.2.0" + } +} diff --git a/tools/baseline_browserslist/tsconfig-build.json b/tools/baseline_browserslist/tsconfig-build.json new file mode 100644 index 000000000000..acbbe1ae9949 --- /dev/null +++ b/tools/baseline_browserslist/tsconfig-build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig-build-esm.json", + "include": ["**.mts"], + "exclude": ["**_spec.mts"], + "compilerOptions": { + "types": ["node"] + } +} diff --git a/tools/baseline_browserslist/tsconfig-test.json b/tools/baseline_browserslist/tsconfig-test.json new file mode 100644 index 000000000000..48ba7db3fd84 --- /dev/null +++ b/tools/baseline_browserslist/tsconfig-test.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig-test-esm.json", + "include": ["**_spec.mts"] +} diff --git a/tools/baseline_browserslist/tsconfig.json b/tools/baseline_browserslist/tsconfig.json new file mode 100644 index 000000000000..53c965a91293 --- /dev/null +++ b/tools/baseline_browserslist/tsconfig.json @@ -0,0 +1,4 @@ +{ + "files": [], + "references": ["./tsconfig-build.json", "./tsconfig-test.json"] +} From 63ec56cf03806308048c5597bf8e99771fdd2509 Mon Sep 17 00:00:00 2001 From: Doug Parker Date: Fri, 4 Apr 2025 16:45:21 -0700 Subject: [PATCH 2/4] feat(@angular/build): expand browser support policy to widely available Baseline This commit includes a Baseline widely-available date which is hard-coded in `buid_vars.bzl` and used to generate a `.browserslistrc` file at build-time. Using a browser outside of Angular's minimum defined browser set is still allowed as we expect that _most_ of the time this will work just fine. However, we log a warning to be clear to users that they are outside Angular's supported browserset. I've currently pinned Angular to the March 31st baseline, but this will likely be updated again as we get closer to the v20 release. The current set of browsers generated are: ``` Chrome >= 107 ChromeAndroid >= 107 Edge >= 107 Firefox >= 104 FirefoxAndroid >= 104 Safari >= 16 iOS >= 16 ``` --- constants.bzl | 7 ++++ .../projects/hello-world-app/.browserslistrc | 4 --- packages/angular/build/BUILD.bazel | 9 +++++ .../tests/behavior/browser-support_spec.ts | 36 ++++++++++++++----- .../build/src/utils/supported-browsers.ts | 36 +++++++++++++------ .../tests/behavior/browser-support_spec.ts | 36 ++++++++++++++----- 6 files changed, 97 insertions(+), 31 deletions(-) delete mode 100644 modules/testing/builder/projects/hello-world-app/.browserslistrc diff --git a/constants.bzl b/constants.bzl index 87028bf0a607..836bee1080e8 100644 --- a/constants.bzl +++ b/constants.bzl @@ -8,6 +8,13 @@ ANGULAR_FW_VERSION = "^20.0.0-next.0" ANGULAR_FW_PEER_DEP = "^20.0.0 || ^20.0.0-next.0" NG_PACKAGR_PEER_DEP = "^20.0.0 || ^20.0.0-next.0" +# Baseline widely-available date in `YYYY-MM-DD` format which defines Angular's +# browser support. This date serves as the source of truth for the Angular CLI's +# default browser set used to determine what downleveling is necessary. +# +# See: https://web.dev/baseline +BASELINE_DATE = "2025-03-31" + SNAPSHOT_REPOS = { "@angular/cli": "angular/cli-builds", "@angular/pwa": "angular/angular-pwa-builds", diff --git a/modules/testing/builder/projects/hello-world-app/.browserslistrc b/modules/testing/builder/projects/hello-world-app/.browserslistrc deleted file mode 100644 index 7fd7c3b8783f..000000000000 --- a/modules/testing/builder/projects/hello-world-app/.browserslistrc +++ /dev/null @@ -1,4 +0,0 @@ -# We want to run tests large with ever green browser so that -# we never trigger differential loading as this will slow down the tests. - -last 2 Chrome versions \ No newline at end of file diff --git a/packages/angular/build/BUILD.bazel b/packages/angular/build/BUILD.bazel index c842065f865a..2eab4b6d3491 100644 --- a/packages/angular/build/BUILD.bazel +++ b/packages/angular/build/BUILD.bazel @@ -1,7 +1,9 @@ load("@devinfra//bazel/api-golden:index_rjs.bzl", "api_golden_test_npm_package") load("@npm2//:defs.bzl", "npm_link_all_packages") +load("//:constants.bzl", "BASELINE_DATE") load("//tools:defaults.bzl", "copy_to_bin", "jasmine_test", "npm_package", "ts_project") load("//tools:ts_json_schema.bzl", "ts_json_schema") +load("//tools/baseline_browserslist:baseline_browserslist.bzl", "baseline_browserslist") licenses(["notice"]) @@ -39,6 +41,12 @@ copy_to_bin( srcs = glob(["**/schema.json"]), ) +baseline_browserslist( + name = "angular_browserslist", + out = ".browserslistrc", + baseline = BASELINE_DATE, +) + RUNTIME_ASSETS = glob( include = [ "src/**/schema.json", @@ -49,6 +57,7 @@ RUNTIME_ASSETS = glob( ) + [ "builders.json", "package.json", + ":angular_browserslist", ] ts_project( diff --git a/packages/angular/build/src/builders/application/tests/behavior/browser-support_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/browser-support_spec.ts index 6ba660677104..e281ca8caeb9 100644 --- a/packages/angular/build/src/builders/application/tests/behavior/browser-support_spec.ts +++ b/packages/angular/build/src/builders/application/tests/behavior/browser-support_spec.ts @@ -84,12 +84,12 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { }); it('warns when IE is present in browserslist', async () => { - await harness.appendToFile( + await harness.writeFile( '.browserslistrc', ` - IE 9 - IE 11 - `, + IE 9 + IE 11 + `, ); harness.useTarget('build', { @@ -102,10 +102,30 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { expect(logs).toContain( jasmine.objectContaining({ level: 'warn', - message: - `One or more browsers which are configured in the project's Browserslist ` + - 'configuration will be ignored as ES5 output is not supported by the Angular CLI.\n' + - 'Ignored browsers: ie 11, ie 9', + message: jasmine.stringContaining('ES5 output is not supported'), + }), + ); + + // Don't duplicate the error. + expect(logs).not.toContain( + jasmine.objectContaining({ + message: jasmine.stringContaining("fall outside Angular's browser support"), + }), + ); + }); + + it("warns when targeting a browser outside Angular's minimum support", async () => { + await harness.writeFile('.browserslistrc', 'Chrome >= 100'); + + harness.useTarget('build', BASE_OPTIONS); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + expect(logs).toContain( + jasmine.objectContaining({ + level: 'warn', + message: jasmine.stringContaining("fall outside Angular's browser support"), }), ); }); diff --git a/packages/angular/build/src/utils/supported-browsers.ts b/packages/angular/build/src/utils/supported-browsers.ts index 30c3d502fcdd..a8546a039082 100644 --- a/packages/angular/build/src/utils/supported-browsers.ts +++ b/packages/angular/build/src/utils/supported-browsers.ts @@ -12,15 +12,16 @@ export function getSupportedBrowsers( projectRoot: string, logger: { warn(message: string): void }, ): string[] { - browserslist.defaults = [ - 'last 2 Chrome versions', - 'last 1 Firefox version', - 'last 2 Edge major versions', - 'last 2 Safari major versions', - 'last 2 iOS major versions', - 'last 2 Android major versions', - 'Firefox ESR', - ]; + // Read the browserslist configuration containing Angular's browser support policy. + const angularBrowserslist = browserslist(undefined, { + path: require.resolve('../../.browserslistrc'), + }); + + // Use Angular's configuration as the default. + browserslist.defaults = angularBrowserslist; + + // Get the minimum set of browser versions supported by Angular. + const minimumBrowsers = new Set(angularBrowserslist); // Get browsers from config or default. const browsersFromConfigOrDefault = new Set(browserslist(undefined, { path: projectRoot })); @@ -28,19 +29,32 @@ export function getSupportedBrowsers( // Get browsers that support ES6 modules. const browsersThatSupportEs6 = new Set(browserslist('supports es6-module')); + const nonEs6Browsers: string[] = []; const unsupportedBrowsers: string[] = []; for (const browser of browsersFromConfigOrDefault) { if (!browsersThatSupportEs6.has(browser)) { + // Any browser which does not support ES6 is explicitly ignored, as Angular will not build successfully. browsersFromConfigOrDefault.delete(browser); + nonEs6Browsers.push(browser); + } else if (!minimumBrowsers.has(browser)) { + // Any other unsupported browser we will attempt to use, but provide no support for. unsupportedBrowsers.push(browser); } } - if (unsupportedBrowsers.length) { + if (nonEs6Browsers.length) { logger.warn( `One or more browsers which are configured in the project's Browserslist configuration ` + 'will be ignored as ES5 output is not supported by the Angular CLI.\n' + - `Ignored browsers: ${unsupportedBrowsers.join(', ')}`, + `Ignored browsers:\n${nonEs6Browsers.join(', ')}`, + ); + } + + if (unsupportedBrowsers.length) { + logger.warn( + `One or more browsers which are configured in the project's Browserslist configuration ` + + "fall outside Angular's browser support for this version.\n" + + `Unsupported browsers:\n${unsupportedBrowsers.join(', ')}`, ); } diff --git a/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/browser-support_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/browser-support_spec.ts index 5fb8e9d4cd0b..f24c83f2d96b 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/browser-support_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/browser-support_spec.ts @@ -65,12 +65,12 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { }); it('warns when IE is present in browserslist', async () => { - await harness.appendToFile( + await harness.writeFile( '.browserslistrc', ` - IE 9 - IE 11 - `, + IE 9 + IE 11 + `, ); harness.useTarget('build', { @@ -83,10 +83,30 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { expect(logs).toContain( jasmine.objectContaining({ level: 'warn', - message: - `One or more browsers which are configured in the project's Browserslist ` + - 'configuration will be ignored as ES5 output is not supported by the Angular CLI.\n' + - 'Ignored browsers: ie 11, ie 9', + message: jasmine.stringContaining('ES5 output is not supported'), + }), + ); + + // Don't duplicate the error. + expect(logs).not.toContain( + jasmine.objectContaining({ + message: jasmine.stringContaining("fall outside Angular's browser support"), + }), + ); + }); + + it("warns when targeting a browser outside Angular's minimum support", async () => { + await harness.writeFile('.browserslistrc', 'Chrome >= 100'); + + harness.useTarget('build', BASE_OPTIONS); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + expect(logs).toContain( + jasmine.objectContaining({ + level: 'warn', + message: jasmine.stringContaining("fall outside Angular's browser support"), }), ); }); From 0f34912e0907276df441bb9daf712d7bca6987da Mon Sep 17 00:00:00 2001 From: Doug Parker Date: Fri, 4 Apr 2025 17:14:57 -0700 Subject: [PATCH 3/4] refactor(@schematics/angular): generate Baseline date in `ng generate config browserslist` This changes `ng generate config browserslist` to no longer generate a list of browsers used by Angular, but instead generate a dependency on `browserslist-config-baseline` and configures the date to match Angular. This used to generate a `.browserslistrc` file, however since the config is a single line and `browserslist-config-baseline` requires a separate config in the `package.json`, it feels a little more ergonomic to put both in the `package.json` file instead. --- packages/schematics/angular/BUILD.bazel | 9 +++++++++ .../angular/config/files/.browserslistrc.template | 10 ++-------- packages/schematics/angular/config/index.ts | 6 +++++- packages/schematics/angular/config/index_spec.ts | 2 +- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/schematics/angular/BUILD.bazel b/packages/schematics/angular/BUILD.bazel index 365017ed0906..16632748036a 100644 --- a/packages/schematics/angular/BUILD.bazel +++ b/packages/schematics/angular/BUILD.bazel @@ -4,8 +4,10 @@ # found in the LICENSE file at https://angular.dev/license load("@npm2//:defs.bzl", "npm_link_all_packages") +load("//:constants.bzl", "BASELINE_DATE") load("//tools:defaults.bzl", "copy_to_bin", "jasmine_test", "npm_package", "ts_project") load("//tools:ts_json_schema.bzl", "ts_json_schema") +load("//tools/baseline_browserslist:baseline_browserslist.bzl", "baseline_browserslist") licenses(["notice"]) @@ -42,11 +44,18 @@ copy_to_bin( srcs = glob(["**/schema.json"]), ) +baseline_browserslist( + name = "angular_browserslist", + out = "config/.browserslistrc", + baseline = BASELINE_DATE, +) + RUNTIME_ASSETS = [ "collection.json", "migrations/migration-collection.json", "package.json", "utility/latest-versions/package.json", + ":angular_browserslist", ] + glob( include = [ "*/schema.json", diff --git a/packages/schematics/angular/config/files/.browserslistrc.template b/packages/schematics/angular/config/files/.browserslistrc.template index 4ec7f1adf73c..d11dabc56bdf 100644 --- a/packages/schematics/angular/config/files/.browserslistrc.template +++ b/packages/schematics/angular/config/files/.browserslistrc.template @@ -2,16 +2,10 @@ # For additional information regarding the format and rule options, please see: # https://github.com/browserslist/browserslist#queries -# For the full list of supported browsers by the Angular framework, please see: +# For Angular's browser support policy, please see: # https://angular.dev/reference/versions#browser-support # You can see what browsers were selected by your queries by running: # npx browserslist -last 2 Chrome versions -last 1 Firefox version -last 2 Edge major versions -last 2 Safari major versions -last 2 iOS major versions -last 2 Android major versions -Firefox ESR +<%= config %> diff --git a/packages/schematics/angular/config/index.ts b/packages/schematics/angular/config/index.ts index 638766e67a42..5878bd8c498a 100644 --- a/packages/schematics/angular/config/index.ts +++ b/packages/schematics/angular/config/index.ts @@ -17,6 +17,7 @@ import { strings, url, } from '@angular-devkit/schematics'; +import { readFile } from 'node:fs/promises'; import { posix as path } from 'node:path'; import { relativePathToWorkspaceRoot } from '../utility/paths'; import { getWorkspace as readWorkspace, updateWorkspace } from '../utility/workspace'; @@ -42,10 +43,13 @@ function addBrowserslistConfig(options: ConfigOptions): Rule { throw new SchematicsException(`Project name "${options.project}" doesn't not exist.`); } + // Read Angular's default vendored `.browserslistrc` file. + const config = await readFile(path.join(__dirname, '.browserslistrc'), 'utf8'); + return mergeWith( apply(url('./files'), [ filter((p) => p.endsWith('.browserslistrc.template')), - applyTemplates({}), + applyTemplates({ config }), move(project.root), ]), ); diff --git a/packages/schematics/angular/config/index_spec.ts b/packages/schematics/angular/config/index_spec.ts index f7f7b335ad68..c9349bcb609d 100644 --- a/packages/schematics/angular/config/index_spec.ts +++ b/packages/schematics/angular/config/index_spec.ts @@ -94,7 +94,7 @@ describe('Config Schematic', () => { describe(`when 'type' is 'browserslist'`, () => { it('should create a .browserslistrc file', async () => { const tree = await runConfigSchematic(ConfigType.Browserslist); - expect(tree.exists('projects/foo/.browserslistrc')).toBeTrue(); + expect(tree.readContent('projects/foo/.browserslistrc')).toContain('Chrome >='); }); }); }); From 9c1962a9a095e7e5da611416b3c17ebe08386b00 Mon Sep 17 00:00:00 2001 From: Doug Parker Date: Fri, 4 Apr 2025 17:40:08 -0700 Subject: [PATCH 4/4] docs: update release docs to describe how to upgrade the browser policy --- docs/process/release.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/process/release.md b/docs/process/release.md index 3e89c2900261..e8867c909ce3 100644 --- a/docs/process/release.md +++ b/docs/process/release.md @@ -145,3 +145,34 @@ will block the next weekly release. accept the invite for the new package. Once Wombat accepts the invite, regular automated releases should work as expected. + +## Updating Browser Support + +Angular's browser support is defined by a [Baseline](https://web.dev/baseline) +"widely available" date. Before a new major version is released, this should be +updated to approximately the current date. + +A few weeks before a major (around feature freeze): + +1. Update `BASELINE_DATE` in + [`/constants.bzl`](/constants.bzl) to the end of the most recent month. + - For example, if it is currently May 12th, set `baselineThreshold` to April + 30th. + - Picking a date at the end of a month makes it easier to cross-reference + Angular's support with other tools (like MDN) which state Baseline support + using month specificity. + - You can view the generated `browserlist` configuration with: + ```shell + bazel build //packages/angular/build:angular_browserslist + cat dist/bin/packages/angular/build/.browserslistrc + ``` + - Commit and merge the change, no other alterations or automation is + necessary in the CLI repo. +2. Update + [`/.browserslistrc`](https://github.com/ng-packagr/ng-packagr/tree/main/.browserslistrc) + in the `ng-packagr` repo. + - Use the generated configuration from above. +3. Update + [`angular.dev` documentation](https://github.com/angular/angular/tree/main/adev/src/content/reference/versions.md#browser-support) + to specify the date used and link to [browsersl.ist](https://browsersl.ist) + with the generated configuration.