Skip to content

Commit 2395c7a

Browse files
committed
feat(@angular/build): expand browser support policy to widely available Baseline
This uses `bl2bl` to generate a `browserslist` configuration from a given widely available Baseline date. The date is hard-coded in `browserslist/package.json`, generating 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 had to apply a local patch to `bl2bl` as it would unconditionally attempt to write to `package.json` which is read-only in the Bazel file system since it is an input listed in `srcs`. I'll follow up with the maintainers to apply this fix upstream and remove the patch. I've 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 ``` 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 `bl2bl` as part of the build process means there is a potential risk that a bugfix in `bl2bl` 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.
1 parent eaa36c3 commit 2395c7a

File tree

9 files changed

+298
-152
lines changed

9 files changed

+298
-152
lines changed

modules/testing/builder/projects/hello-world-app/.browserslistrc

-4
This file was deleted.

package.json

+4
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
"ajv": "8.17.1",
100100
"ansi-colors": "4.1.3",
101101
"beasties": "0.3.2",
102+
"bl2bl": "^0.2.0",
102103
"buffer": "6.0.3",
103104
"esbuild": "0.25.2",
104105
"esbuild-wasm": "0.25.2",
@@ -167,6 +168,9 @@
167168
"protobufjs": "*"
168169
}
169170
}
171+
},
172+
"patchedDependencies": {
173+
"bl2bl": "patches/bl2bl.patch"
170174
}
171175
},
172176
"resolutions": {

packages/angular/build/BUILD.bazel

+9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
load("@devinfra//bazel/api-golden:index_rjs.bzl", "api_golden_test_npm_package")
2+
load("@npm2//:bl2bl/package_json.bzl", bl2bl_bin = "bin")
23
load("@npm2//:defs.bzl", "npm_link_all_packages")
34
load("//tools:defaults2.bzl", "copy_to_bin", "jasmine_test", "npm_package", "ts_project")
45
load("//tools:ts_json_schema.bzl", "ts_json_schema")
@@ -39,6 +40,13 @@ copy_to_bin(
3940
srcs = glob(["**/schema.json"]),
4041
)
4142

43+
bl2bl_bin.bl2bl(
44+
name = "angular_browserslist",
45+
srcs = ["src/baseline/package.json"],
46+
outs = ["src/baseline/.browserslistrc"],
47+
chdir = "%s/src/baseline" % package_name(),
48+
)
49+
4250
RUNTIME_ASSETS = glob(
4351
include = [
4452
"src/**/schema.json",
@@ -49,6 +57,7 @@ RUNTIME_ASSETS = glob(
4957
) + [
5058
"builders.json",
5159
"package.json",
60+
":angular_browserslist",
5261
]
5362

5463
ts_project(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"// 1": "This defines the `bl2bl` configuration which generates a",
3+
"// 2": " `.browserslistrc` file based on the configured Baseline date.",
4+
"// 3": " This date serves as the source of truth for Angular CLI's default",
5+
"// 4": " browser set used to determine what downleveling is necessary",
6+
"bl2bl": {
7+
"baselineThreshold": "2025-03-31",
8+
"useBrowserslistrc": true,
9+
"downstreamBrowsers": false,
10+
"savePrevious": false
11+
}
12+
}

packages/angular/build/src/builders/application/tests/behavior/browser-support_spec.ts

+28-8
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,12 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
8484
});
8585

8686
it('warns when IE is present in browserslist', async () => {
87-
await harness.appendToFile(
87+
await harness.writeFile(
8888
'.browserslistrc',
8989
`
90-
IE 9
91-
IE 11
92-
`,
90+
IE 9
91+
IE 11
92+
`,
9393
);
9494

9595
harness.useTarget('build', {
@@ -102,10 +102,30 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
102102
expect(logs).toContain(
103103
jasmine.objectContaining({
104104
level: 'warn',
105-
message:
106-
`One or more browsers which are configured in the project's Browserslist ` +
107-
'configuration will be ignored as ES5 output is not supported by the Angular CLI.\n' +
108-
'Ignored browsers: ie 11, ie 9',
105+
message: jasmine.stringContaining('ES5 output is not supported'),
106+
}),
107+
);
108+
109+
// Don't duplicate the error.
110+
expect(logs).not.toContain(
111+
jasmine.objectContaining({
112+
message: jasmine.stringContaining("fall outside Angular's browser support"),
113+
}),
114+
);
115+
});
116+
117+
it("warns when targeting a browser outside Angular's minimum support", async () => {
118+
await harness.writeFile('.browserslistrc', 'Chrome >= 100');
119+
120+
harness.useTarget('build', BASE_OPTIONS);
121+
122+
const { result, logs } = await harness.executeOnce();
123+
expect(result?.success).toBeTrue();
124+
125+
expect(logs).toContain(
126+
jasmine.objectContaining({
127+
level: 'warn',
128+
message: jasmine.stringContaining("fall outside Angular's browser support"),
109129
}),
110130
);
111131
});

packages/angular/build/src/utils/supported-browsers.ts

+26-11
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,55 @@
77
*/
88

99
import browserslist from 'browserslist';
10+
import * as path from 'node:path';
1011

1112
export function getSupportedBrowsers(
1213
projectRoot: string,
1314
logger: { warn(message: string): void },
1415
): string[] {
15-
browserslist.defaults = [
16-
'last 2 Chrome versions',
17-
'last 1 Firefox version',
18-
'last 2 Edge major versions',
19-
'last 2 Safari major versions',
20-
'last 2 iOS major versions',
21-
'last 2 Android major versions',
22-
'Firefox ESR',
23-
];
16+
// Read the browserslist configuration containing Angular's browser support policy.
17+
const angularBrowserslist = browserslist(undefined, {
18+
path: path.join(require.resolve('../baseline/.browserslistrc')),
19+
});
20+
21+
// Use Angular's configuration as the default.
22+
browserslist.defaults = angularBrowserslist;
23+
24+
// Get the minimum set of browser versions supported by Angular.
25+
const minimumBrowsers = new Set(browserslist(angularBrowserslist));
2426

2527
// Get browsers from config or default.
2628
const browsersFromConfigOrDefault = new Set(browserslist(undefined, { path: projectRoot }));
2729

2830
// Get browsers that support ES6 modules.
2931
const browsersThatSupportEs6 = new Set(browserslist('supports es6-module'));
3032

33+
const nonEs6Browsers: string[] = [];
3134
const unsupportedBrowsers: string[] = [];
3235
for (const browser of browsersFromConfigOrDefault) {
3336
if (!browsersThatSupportEs6.has(browser)) {
37+
// Any browser which does not support ES6 is explicitly ignored, as Angular will not build successfully.
3438
browsersFromConfigOrDefault.delete(browser);
39+
nonEs6Browsers.push(browser);
40+
} else if (!minimumBrowsers.has(browser)) {
41+
// Any other unsupported browser we will attempt to use, but provide no support for.
3542
unsupportedBrowsers.push(browser);
3643
}
3744
}
3845

39-
if (unsupportedBrowsers.length) {
46+
if (nonEs6Browsers.length) {
4047
logger.warn(
4148
`One or more browsers which are configured in the project's Browserslist configuration ` +
4249
'will be ignored as ES5 output is not supported by the Angular CLI.\n' +
43-
`Ignored browsers: ${unsupportedBrowsers.join(', ')}`,
50+
`Ignored browsers:\n${nonEs6Browsers.join(', ')}`,
51+
);
52+
}
53+
54+
if (unsupportedBrowsers.length) {
55+
logger.warn(
56+
`One or more browsers which are configured in the project's Browserslist configuration ` +
57+
"fall outside Angular's browser support for this version.\n" +
58+
`Unsupported browsers:\n${unsupportedBrowsers.join(', ')}`,
4459
);
4560
}
4661

patches/bl2bl.patch

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
diff --git a/bin/cli.js b/bin/cli.js
2+
index d1a554e6de9e9b4497ffeae5476b3e838c79b408..2bd1955d7120a7992fe6676b1d4da47a7be1b153 100755
3+
--- a/bin/cli.js
4+
+++ b/bin/cli.js
5+
@@ -114,6 +114,7 @@ import("baseline-browser-mapping").then((bbm) => {
6+
});
7+
8+
// Behaviour varies depending on whether userBrowserslistrc=true or false
9+
+ const originallyHadBrowserslistrc = Boolean(packageJSON["browserslist"]);
10+
if (!bl2blConfig.useBrowserslistrc) {
11+
// if false, add baselineVersions to packageJSON object for later
12+
packageJSON["browserslist"] = browserslistOutput;
13+
@@ -149,7 +150,7 @@ import("baseline-browser-mapping").then((bbm) => {
14+
}
15+
16+
// Whatever happens, update package.json as long as the packageJSON object isn't null
17+
- if (packageJSON != null) {
18+
+ if (packageJSON != null && (!bl2blConfig.useBrowserslistrc || originallyHadBrowserslistrc)) {
19+
fs.writeFileSync(
20+
process.cwd() + "/package.json",
21+
JSON.stringify(packageJSON, null, 2),

0 commit comments

Comments
 (0)