Skip to content

Commit 9beb878

Browse files
alan-agius4dgp1130
authored andcommitted
feat(@schematics/angular): remove Browserslist configuration files from projects
The Browserslist configuration file is redundant as we set the defaults directly in @angular-devkit/build-angular. https://github.com/angular/angular-cli/blob/8da926966e9f414ceecf60b89acd475ce1b55fc5/packages/angular_devkit/build_angular/src/utils/supported-browsers.ts#L12-L19 With this commit, we remove the `.browserlistrc` configuration file from the schematics application template and through a migration in existing projects when the Browserslist query result matches the default. Users needing a finer grain support should still create a `.browserlistrc` in the root directory of the project.
1 parent 71ff22c commit 9beb878

File tree

7 files changed

+190
-24
lines changed

7 files changed

+190
-24
lines changed

packages/angular_devkit/build_angular/src/builders/browser/specs/browser-support_spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ describe('Browser Builder browser support', () => {
2121
afterEach(async () => host.restore().toPromise());
2222

2323
it('warns when IE is present in browserslist', async () => {
24-
host.appendToFile('.browserslistrc', '\nIE 9');
24+
host.writeMultipleFiles({
25+
'.browserslistrc': '\nIE 9',
26+
});
2527

2628
const logger = new logging.Logger('');
2729
const logs: string[] = [];

packages/schematics/angular/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ ts_library(
7878
"//packages/angular_devkit/schematics/tasks",
7979
"//packages/schematics/angular/third_party/github.com/Microsoft/TypeScript",
8080
"@npm//@types/node",
81+
"@npm//browserslist",
8182
"@npm//jsonc-parser",
8283
],
8384
)

packages/schematics/angular/application/files/.browserslistrc.template

Lines changed: 0 additions & 16 deletions
This file was deleted.

packages/schematics/angular/application/index_spec.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ describe('Application Schematic', () => {
547547
const tree = await schematicRunner
548548
.runSchematicAsync('application', options, workspaceTree)
549549
.toPromise();
550-
const exists = tree.exists('/projects/my-cool/.browserslistrc');
550+
const exists = tree.exists('/projects/my-cool/tsconfig.app.json');
551551
expect(exists).toBeTrue();
552552
});
553553

@@ -556,7 +556,7 @@ describe('Application Schematic', () => {
556556
const tree = await schematicRunner
557557
.runSchematicAsync('application', options, workspaceTree)
558558
.toPromise();
559-
const exists = tree.exists('/projects/foo/my-cool/.browserslistrc');
559+
const exists = tree.exists('/projects/foo/my-cool/tsconfig.app.json');
560560
expect(exists).toBeTrue();
561561
});
562562

@@ -565,7 +565,7 @@ describe('Application Schematic', () => {
565565
const tree = await schematicRunner
566566
.runSchematicAsync('application', options, workspaceTree)
567567
.toPromise();
568-
const exists = tree.exists('/projects/my-cool/.browserslistrc');
568+
const exists = tree.exists('/projects/my-cool/tsconfig.app.json');
569569
expect(exists).toBeTrue();
570570
});
571571

@@ -574,7 +574,7 @@ describe('Application Schematic', () => {
574574
const tree = await schematicRunner
575575
.runSchematicAsync('application', options, workspaceTree)
576576
.toPromise();
577-
const exists = tree.exists('/projects/foo/my-cool/.browserslistrc');
577+
const exists = tree.exists('/projects/foo/my-cool/tsconfig.app.json');
578578
expect(exists).toBeTrue();
579579
});
580580

@@ -584,8 +584,7 @@ describe('Application Schematic', () => {
584584
.runSchematicAsync('application', options, workspaceTree)
585585
.toPromise();
586586

587-
const exists = tree.exists('/projects/foo.bar_buz/.browserslistrc');
588-
expect(exists).toBeTrue();
587+
expect(tree.exists('/projects/foo.bar_buz/tsconfig.app.json')).toBeTrue();
589588
});
590589

591590
it('should support creating scoped application', async () => {
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
{
2-
"schematics": {}
2+
"schematics": {
3+
"remove-browserslist-config": {
4+
"version": "15.0.0",
5+
"factory": "./update-15/remove-browserslist-config",
6+
"description": "Remove Browserslist configuration files that matches the Angular CLI default configuration."
7+
}
8+
}
39
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { Path, join } from '@angular-devkit/core';
10+
import { DirEntry, Rule } from '@angular-devkit/schematics';
11+
12+
const validBrowserslistConfigFilenames = new Set(['browserslist', '.browserslistrc']);
13+
14+
export const DEFAULT_BROWSERS = [
15+
'last 1 Chrome version',
16+
'last 1 Firefox version',
17+
'last 2 Edge major versions',
18+
'last 2 Safari major versions',
19+
'last 2 iOS major versions',
20+
'Firefox ESR',
21+
];
22+
23+
function* visit(directory: DirEntry): IterableIterator<Path> {
24+
for (const path of directory.subfiles) {
25+
if (validBrowserslistConfigFilenames.has(path)) {
26+
yield join(directory.path, path);
27+
}
28+
}
29+
30+
for (const path of directory.subdirs) {
31+
if (path === 'node_modules') {
32+
continue;
33+
}
34+
35+
yield* visit(directory.dir(path));
36+
}
37+
}
38+
39+
export default function (): Rule {
40+
return async (tree, { logger }) => {
41+
let browserslist: typeof import('browserslist') | undefined;
42+
43+
try {
44+
browserslist = (await import('browserslist')).default;
45+
} catch {
46+
logger.warn('Skipping migration because the "browserslist" package could not be loaded.');
47+
48+
return;
49+
}
50+
51+
// Set the defaults to match the defaults in build-angular.
52+
browserslist.defaults = DEFAULT_BROWSERS;
53+
54+
const defaultSupportedBrowsers = new Set(browserslist(DEFAULT_BROWSERS));
55+
const es5Browsers = new Set(browserslist(['supports es6-module']));
56+
57+
for (const path of visit(tree.root)) {
58+
const { defaults: browsersListConfig, ...otherConfigs } = browserslist.parseConfig(
59+
tree.readText(path),
60+
);
61+
62+
if (Object.keys(otherConfigs).length) {
63+
// The config contains additional sections.
64+
continue;
65+
}
66+
67+
const browserslistInProject = browserslist(
68+
// Exclude from the list ES5 browsers which are not supported.
69+
browsersListConfig.map((s) => `${s} and supports es6-module`),
70+
{
71+
ignoreUnknownVersions: true,
72+
},
73+
);
74+
75+
if (defaultSupportedBrowsers.size !== browserslistInProject.length) {
76+
continue;
77+
}
78+
79+
const shouldDelete = browserslistInProject.every((browser) =>
80+
defaultSupportedBrowsers.has(browser),
81+
);
82+
83+
if (shouldDelete) {
84+
// All browsers are the same as the default config.
85+
// Delete file as it's redundant.
86+
tree.delete(path);
87+
}
88+
}
89+
};
90+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { EmptyTree } from '@angular-devkit/schematics';
10+
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
11+
import { DEFAULT_BROWSERS } from './remove-browserslist-config';
12+
13+
describe('Migration to delete Browserslist configurations', () => {
14+
const schematicName = 'remove-browserslist-config';
15+
16+
const schematicRunner = new SchematicTestRunner(
17+
'migrations',
18+
require.resolve('../migration-collection.json'),
19+
);
20+
21+
let tree: UnitTestTree;
22+
23+
beforeEach(() => {
24+
tree = new UnitTestTree(new EmptyTree());
25+
});
26+
27+
describe('given the Browserslist config matches the default', () => {
28+
it('should delete ".browserslistrc" file', async () => {
29+
tree.create('/src/app/.browserslistrc', DEFAULT_BROWSERS.join('\n'));
30+
expect(tree.exists('/src/app/.browserslistrc')).toBeTrue();
31+
32+
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
33+
expect(newTree.exists('/src/app/.browserslistrc')).toBeFalse();
34+
});
35+
36+
it(`should not delete "browserslist" in 'node_modules'`, async () => {
37+
tree.create('/node_modules/browserslist', DEFAULT_BROWSERS.join('\n'));
38+
tree.create('/node_modules/.browserslistrc', DEFAULT_BROWSERS.join('\n'));
39+
40+
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
41+
expect(newTree.exists('/node_modules/browserslist')).toBeTrue();
42+
expect(newTree.exists('/node_modules/.browserslistrc')).toBeTrue();
43+
});
44+
});
45+
46+
describe('given the Browserslist config does not match the default', () => {
47+
it('should not delete "browserslist"', async () => {
48+
tree.create('/src/app/browserslist', 'last 1 Chrome version');
49+
50+
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
51+
expect(newTree.exists('/src/app/browserslist')).toBeTrue();
52+
});
53+
54+
it('should not delete ".browserslistrc"', async () => {
55+
tree.create('/src/app/.browserslistrc', 'last 1 Chrome version');
56+
57+
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
58+
expect(newTree.exists('/src/app/.browserslistrc')).toBeTrue();
59+
});
60+
61+
it('should delete ".browserslistrc" file when it only includes non supported ES5 browsers', async () => {
62+
tree.create('/src/app/.browserslistrc', [...DEFAULT_BROWSERS, 'IE 10'].join('\n'));
63+
expect(tree.exists('/src/app/.browserslistrc')).toBeTrue();
64+
65+
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
66+
expect(newTree.exists('/src/app/.browserslistrc')).toBeFalse();
67+
});
68+
69+
it('should not delete ".browserslistrc" file when it includes additional config sections', async () => {
70+
tree.create(
71+
'/src/app/.browserslistrc',
72+
`
73+
${DEFAULT_BROWSERS.join('\n')}
74+
[modern]
75+
last 1 chrome version
76+
`,
77+
);
78+
expect(tree.exists('/src/app/.browserslistrc')).toBeTrue();
79+
80+
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
81+
expect(newTree.exists('/src/app/.browserslistrc')).toBeTrue();
82+
});
83+
});
84+
});

0 commit comments

Comments
 (0)