Skip to content

Commit

Permalink
feat(@schematics/angular): remove Browserslist configuration files fr…
Browse files Browse the repository at this point in the history
…om 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.
  • Loading branch information
alan-agius4 authored and dgp1130 committed Sep 14, 2022
1 parent 71ff22c commit 9beb878
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 24 deletions.
Expand Up @@ -21,7 +21,9 @@ describe('Browser Builder browser support', () => {
afterEach(async () => host.restore().toPromise());

it('warns when IE is present in browserslist', async () => {
host.appendToFile('.browserslistrc', '\nIE 9');
host.writeMultipleFiles({
'.browserslistrc': '\nIE 9',
});

const logger = new logging.Logger('');
const logs: string[] = [];
Expand Down
1 change: 1 addition & 0 deletions packages/schematics/angular/BUILD.bazel
Expand Up @@ -78,6 +78,7 @@ ts_library(
"//packages/angular_devkit/schematics/tasks",
"//packages/schematics/angular/third_party/github.com/Microsoft/TypeScript",
"@npm//@types/node",
"@npm//browserslist",
"@npm//jsonc-parser",
],
)
Expand Down

This file was deleted.

11 changes: 5 additions & 6 deletions packages/schematics/angular/application/index_spec.ts
Expand Up @@ -547,7 +547,7 @@ describe('Application Schematic', () => {
const tree = await schematicRunner
.runSchematicAsync('application', options, workspaceTree)
.toPromise();
const exists = tree.exists('/projects/my-cool/.browserslistrc');
const exists = tree.exists('/projects/my-cool/tsconfig.app.json');
expect(exists).toBeTrue();
});

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

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

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

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

const exists = tree.exists('/projects/foo.bar_buz/.browserslistrc');
expect(exists).toBeTrue();
expect(tree.exists('/projects/foo.bar_buz/tsconfig.app.json')).toBeTrue();
});

it('should support creating scoped application', async () => {
Expand Down
@@ -1,3 +1,9 @@
{
"schematics": {}
"schematics": {
"remove-browserslist-config": {
"version": "15.0.0",
"factory": "./update-15/remove-browserslist-config",
"description": "Remove Browserslist configuration files that matches the Angular CLI default configuration."
}
}
}
@@ -0,0 +1,90 @@
/**
* @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.io/license
*/

import { Path, join } from '@angular-devkit/core';
import { DirEntry, Rule } from '@angular-devkit/schematics';

const validBrowserslistConfigFilenames = new Set(['browserslist', '.browserslistrc']);

export const DEFAULT_BROWSERS = [
'last 1 Chrome version',
'last 1 Firefox version',
'last 2 Edge major versions',
'last 2 Safari major versions',
'last 2 iOS major versions',
'Firefox ESR',
];

function* visit(directory: DirEntry): IterableIterator<Path> {
for (const path of directory.subfiles) {
if (validBrowserslistConfigFilenames.has(path)) {
yield join(directory.path, path);
}
}

for (const path of directory.subdirs) {
if (path === 'node_modules') {
continue;
}

yield* visit(directory.dir(path));
}
}

export default function (): Rule {
return async (tree, { logger }) => {
let browserslist: typeof import('browserslist') | undefined;

try {
browserslist = (await import('browserslist')).default;
} catch {
logger.warn('Skipping migration because the "browserslist" package could not be loaded.');

return;
}

// Set the defaults to match the defaults in build-angular.
browserslist.defaults = DEFAULT_BROWSERS;

const defaultSupportedBrowsers = new Set(browserslist(DEFAULT_BROWSERS));
const es5Browsers = new Set(browserslist(['supports es6-module']));

for (const path of visit(tree.root)) {
const { defaults: browsersListConfig, ...otherConfigs } = browserslist.parseConfig(
tree.readText(path),
);

if (Object.keys(otherConfigs).length) {
// The config contains additional sections.
continue;
}

const browserslistInProject = browserslist(
// Exclude from the list ES5 browsers which are not supported.
browsersListConfig.map((s) => `${s} and supports es6-module`),
{
ignoreUnknownVersions: true,
},
);

if (defaultSupportedBrowsers.size !== browserslistInProject.length) {
continue;
}

const shouldDelete = browserslistInProject.every((browser) =>
defaultSupportedBrowsers.has(browser),
);

if (shouldDelete) {
// All browsers are the same as the default config.
// Delete file as it's redundant.
tree.delete(path);
}
}
};
}
@@ -0,0 +1,84 @@
/**
* @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.io/license
*/

import { EmptyTree } from '@angular-devkit/schematics';
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
import { DEFAULT_BROWSERS } from './remove-browserslist-config';

describe('Migration to delete Browserslist configurations', () => {
const schematicName = 'remove-browserslist-config';

const schematicRunner = new SchematicTestRunner(
'migrations',
require.resolve('../migration-collection.json'),
);

let tree: UnitTestTree;

beforeEach(() => {
tree = new UnitTestTree(new EmptyTree());
});

describe('given the Browserslist config matches the default', () => {
it('should delete ".browserslistrc" file', async () => {
tree.create('/src/app/.browserslistrc', DEFAULT_BROWSERS.join('\n'));
expect(tree.exists('/src/app/.browserslistrc')).toBeTrue();

const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
expect(newTree.exists('/src/app/.browserslistrc')).toBeFalse();
});

it(`should not delete "browserslist" in 'node_modules'`, async () => {
tree.create('/node_modules/browserslist', DEFAULT_BROWSERS.join('\n'));
tree.create('/node_modules/.browserslistrc', DEFAULT_BROWSERS.join('\n'));

const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
expect(newTree.exists('/node_modules/browserslist')).toBeTrue();
expect(newTree.exists('/node_modules/.browserslistrc')).toBeTrue();
});
});

describe('given the Browserslist config does not match the default', () => {
it('should not delete "browserslist"', async () => {
tree.create('/src/app/browserslist', 'last 1 Chrome version');

const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
expect(newTree.exists('/src/app/browserslist')).toBeTrue();
});

it('should not delete ".browserslistrc"', async () => {
tree.create('/src/app/.browserslistrc', 'last 1 Chrome version');

const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
expect(newTree.exists('/src/app/.browserslistrc')).toBeTrue();
});

it('should delete ".browserslistrc" file when it only includes non supported ES5 browsers', async () => {
tree.create('/src/app/.browserslistrc', [...DEFAULT_BROWSERS, 'IE 10'].join('\n'));
expect(tree.exists('/src/app/.browserslistrc')).toBeTrue();

const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
expect(newTree.exists('/src/app/.browserslistrc')).toBeFalse();
});

it('should not delete ".browserslistrc" file when it includes additional config sections', async () => {
tree.create(
'/src/app/.browserslistrc',
`
${DEFAULT_BROWSERS.join('\n')}
[modern]
last 1 chrome version
`,
);
expect(tree.exists('/src/app/.browserslistrc')).toBeTrue();

const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
expect(newTree.exists('/src/app/.browserslistrc')).toBeTrue();
});
});
});

0 comments on commit 9beb878

Please sign in to comment.