Skip to content

Commit bbfd05c

Browse files
alan-agius4Keen Yee Liau
authored and
Keen Yee Liau
committedApr 22, 2020
feat(@schematics/angular): add migration to remove deprecated es5BrowserSupport option
1 parent 69aa460 commit bbfd05c

File tree

5 files changed

+243
-0
lines changed

5 files changed

+243
-0
lines changed
 

‎packages/schematics/angular/BUILD.bazel

+4
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ ts_library(
6464
"//packages/angular_devkit/schematics",
6565
"//packages/angular_devkit/schematics/tasks",
6666
"//packages/schematics/angular/third_party/github.com/Microsoft/TypeScript",
67+
"@npm//@types/browserslist",
68+
"@npm//@types/caniuse-lite",
6769
"@npm//@types/node",
6870
"@npm//rxjs",
6971
"@npm//tslint",
@@ -98,6 +100,8 @@ ts_library(
98100
"//packages/angular_devkit/schematics",
99101
"//packages/angular_devkit/schematics/testing",
100102
"//packages/schematics/angular/third_party/github.com/Microsoft/TypeScript",
103+
"@npm//@types/browserslist",
104+
"@npm//@types/caniuse-lite",
101105
"@npm//@types/jasmine",
102106
"@npm//@types/node",
103107
"@npm//rxjs",

‎packages/schematics/angular/migrations/migration-collection.json

+5
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@
6969
"version": "10.0.0-beta.1",
7070
"factory": "./update-10/update-dependencies",
7171
"description": "Workspace dependencies updates."
72+
},
73+
"remove-es5-browser-support-option": {
74+
"version": "10.0.0-beta.2",
75+
"factory": "./update-10/remove-es5-browser-support",
76+
"description": "Remove deprecated 'es5BrowserSupport' browser builder option. The inclusion for ES5 polyfills will be determined from the browsers listed in the browserslist configuration."
7277
}
7378
}
7479
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. 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, getSystemPath, logging, normalize, resolve, workspaces } from '@angular-devkit/core';
10+
import { Rule } from '@angular-devkit/schematics';
11+
import { getWorkspace, updateWorkspace } from '../../utility/workspace';
12+
import { Builders, ProjectType } from '../../utility/workspace-models';
13+
14+
export default function (): Rule {
15+
return async (host, context) => {
16+
const workspace = await getWorkspace(host);
17+
18+
for (const [projectName, project] of workspace.projects) {
19+
if (project.extensions.projectType !== ProjectType.Application) {
20+
// Only interested in application projects
21+
continue;
22+
}
23+
24+
for (const [, target] of project.targets) {
25+
// Only interested in Angular Devkit Browser builder
26+
if (target?.builder !== Builders.Browser) {
27+
continue;
28+
}
29+
30+
const isES5Needed = await isES5SupportNeeded(
31+
resolve(
32+
normalize(host.root.path),
33+
normalize(project.root),
34+
),
35+
);
36+
37+
// Check options
38+
if (target.options) {
39+
target.options = removeE5BrowserSupportOption(projectName, target.options, isES5Needed, context.logger);
40+
}
41+
42+
// Go through each configuration entry
43+
if (!target.configurations) {
44+
continue;
45+
}
46+
47+
for (const [configurationName, options] of Object.entries(target.configurations)) {
48+
target.configurations[configurationName] = removeE5BrowserSupportOption(
49+
projectName,
50+
options,
51+
isES5Needed,
52+
context.logger,
53+
configurationName,
54+
);
55+
}
56+
}
57+
}
58+
59+
return updateWorkspace(workspace);
60+
};
61+
}
62+
63+
type TargetOptions = workspaces.TargetDefinition['options'];
64+
65+
function removeE5BrowserSupportOption(
66+
projectName: string,
67+
options: TargetOptions,
68+
isES5Needed: boolean | undefined,
69+
logger: logging.LoggerApi,
70+
configurationName = '',
71+
): TargetOptions {
72+
if (typeof options?.es5BrowserSupport !== 'boolean') {
73+
return options;
74+
}
75+
76+
const configurationPath = configurationName ? `configurations.${configurationName}.` : '';
77+
78+
if (options.es5BrowserSupport && isES5Needed === false) {
79+
logger.warn(
80+
`Project '${projectName}' doesn't require ES5 support, but '${configurationPath}es5BrowserSupport' was set to 'true'.\n` +
81+
`ES5 polyfills will no longer be added when building this project${configurationName ? ` with '${configurationName}' configuration.` : '.'}\n` +
82+
`If ES5 polyfills are needed, add the supported ES5 browsers in the browserslist configuration.`,
83+
);
84+
} else if (!options.es5BrowserSupport && isES5Needed === true) {
85+
logger.warn(
86+
`Project '${projectName}' requires ES5 support, but '${configurationPath}es5BrowserSupport' was set to 'false'.\n` +
87+
`ES5 polyfills will be added when building this project${configurationName ? ` with '${configurationName}' configuration.` : '.'}\n` +
88+
`If ES5 polyfills are not needed, remove the unsupported ES5 browsers from the browserslist configuration.`,
89+
);
90+
}
91+
92+
return {
93+
...options,
94+
es5BrowserSupport: undefined,
95+
};
96+
}
97+
98+
/**
99+
* True, when one or more browsers requires ES5 support
100+
*/
101+
async function isES5SupportNeeded(projectRoot: Path): Promise<boolean | undefined> {
102+
// y: feature is fully available
103+
// n: feature is unavailable
104+
// a: feature is partially supported
105+
// x: feature is prefixed
106+
const criteria = [
107+
'y',
108+
'a',
109+
];
110+
111+
try {
112+
// tslint:disable-next-line:no-implicit-dependencies
113+
const browserslist = await import('browserslist');
114+
const supportedBrowsers = browserslist(undefined, {
115+
path: getSystemPath(projectRoot),
116+
});
117+
118+
// tslint:disable-next-line:no-implicit-dependencies
119+
const { feature, features } = await import('caniuse-lite');
120+
const data = feature(features['es6-module']);
121+
122+
return supportedBrowsers
123+
.some(browser => {
124+
const [agentId, version] = browser.split(' ');
125+
126+
const browserData = data.stats[agentId];
127+
const featureStatus = (browserData && browserData[version]) as string | undefined;
128+
129+
// We are only interested in the first character
130+
// Ex: when 'a #4 #5', we only need to check for 'a'
131+
// as for such cases we should polyfill these features as needed
132+
return !featureStatus || !criteria.includes(featureStatus.charAt(0));
133+
});
134+
} catch {
135+
return undefined;
136+
}
137+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. 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+
import { JsonObject } from '@angular-devkit/core';
9+
import { EmptyTree } from '@angular-devkit/schematics';
10+
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
11+
import { BuilderTarget, Builders, ProjectType, WorkspaceSchema } from '../../utility/workspace-models';
12+
13+
function getBuildTarget(tree: UnitTestTree): BuilderTarget<Builders.Browser, JsonObject> {
14+
return JSON.parse(tree.readContent('/angular.json')).projects.app.architect.build;
15+
}
16+
17+
function createWorkSpaceConfig(tree: UnitTestTree, es5BrowserSupport: boolean | undefined) {
18+
const angularConfig: WorkspaceSchema = {
19+
version: 1,
20+
projects: {
21+
app: {
22+
root: '',
23+
sourceRoot: 'src',
24+
projectType: ProjectType.Application,
25+
prefix: 'app',
26+
architect: {
27+
build: {
28+
builder: Builders.Browser,
29+
options: {
30+
es5BrowserSupport,
31+
sourceMaps: true,
32+
buildOptimizer: false,
33+
// tslint:disable-next-line:no-any
34+
} as any,
35+
configurations: {
36+
one: {
37+
es5BrowserSupport,
38+
vendorChunk: false,
39+
buildOptimizer: true,
40+
},
41+
two: {
42+
es5BrowserSupport,
43+
vendorChunk: false,
44+
buildOptimizer: true,
45+
sourceMaps: false,
46+
},
47+
// tslint:disable-next-line:no-any
48+
} as any,
49+
},
50+
},
51+
},
52+
},
53+
};
54+
55+
tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2));
56+
}
57+
58+
describe(`Migration to remove deprecated 'es5BrowserSupport' option`, () => {
59+
const schematicName = 'remove-es5-browser-support-option';
60+
61+
const schematicRunner = new SchematicTestRunner(
62+
'migrations',
63+
require.resolve('../migration-collection.json'),
64+
);
65+
66+
let tree: UnitTestTree;
67+
beforeEach(() => {
68+
tree = new UnitTestTree(new EmptyTree());
69+
});
70+
71+
it(`should remove option when set to 'false'`, async () => {
72+
createWorkSpaceConfig(tree, false);
73+
74+
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
75+
const { options, configurations } = getBuildTarget(newTree);
76+
77+
expect(options.es5BrowserSupport).toBeUndefined();
78+
expect(configurations).toBeDefined();
79+
expect(configurations?.one.es5BrowserSupport).toBeUndefined();
80+
expect(configurations?.two.es5BrowserSupport).toBeUndefined();
81+
});
82+
83+
it(`should remove option and when set to 'true'`, async () => {
84+
createWorkSpaceConfig(tree, true);
85+
86+
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
87+
const { options, configurations } = getBuildTarget(newTree);
88+
89+
expect(options.es5BrowserSupport).toBeUndefined();
90+
expect(configurations).toBeDefined();
91+
expect(configurations?.one.es5BrowserSupport).toBeUndefined();
92+
expect(configurations?.two.es5BrowserSupport).toBeUndefined();
93+
});
94+
});

‎tests/legacy-cli/e2e/tests/update/update-7.0.ts

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ export default async function() {
1111
// Create new project from previous version files.
1212
// We must use the original NPM packages to force a real update.
1313
await createProjectFromAsset('7.0-project', true);
14+
// Update to version 8, to use the self update CLI feature.
15+
await ng('update', '@angular/cli@8');
16+
1417
fs.writeFileSync('.npmrc', 'registry = http://localhost:4873', 'utf8');
1518

1619
// Update the CLI.

0 commit comments

Comments
 (0)
Please sign in to comment.