Skip to content

Commit 32a096f

Browse files
manfredsteyeralexeagle
authored andcommitted
feat(@angular-devkit/build-angular): differential loading
This PR adds differential loading to the browser builder. First, it checks if differential loading is needed. This is the case if the compilation target is ES2015 while the browserslist points to ES5 browsers. For providing differential loading, it calls the methods for creating the webpack config for each compilation target (e. g. ES5 and ES2015). The needed differences between those configurations are defined using parameters also added by this PR. Then it calls webpack for each of them and merges the results This feature is currently hidden behind a flag. To activate it, set the differentialLoading flag in browser/index.ts.
1 parent 4b3c663 commit 32a096f

File tree

13 files changed

+424
-110
lines changed

13 files changed

+424
-110
lines changed

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@
8282
"@bazel/karma": "~0.26.0",
8383
"@bazel/typescript": "~0.26.0",
8484
"@ngtools/json-schema": "^1.1.0",
85+
"@types/browserslist": "^4.4.0",
86+
"@types/caniuse-api": "^3.0.0",
8587
"@types/copy-webpack-plugin": "^4.4.1",
8688
"@types/express": "^4.16.0",
8789
"@types/glob": "^7.0.0",

packages/angular_devkit/build_angular/package.json

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
"@ngtools/webpack": "0.0.0",
1515
"ajv": "6.10.0",
1616
"autoprefixer": "9.5.1",
17+
"browserslist": "4.5.2",
18+
"caniuse-api": "3.0.0",
1719
"circular-dependency-plugin": "5.0.2",
1820
"clean-css": "4.2.1",
1921
"copy-webpack-plugin": "5.0.2",
@@ -83,5 +85,8 @@
8385
"popper.js": "^1.14.1",
8486
"protractor": "~5.4.0",
8587
"zone.js": "^0.9.0"
88+
},
89+
"peerDependencies": {
90+
"typescript": ">=2.7 < 3.4"
8691
}
8792
}

packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,14 @@ export interface BuildOptions {
7373
lazyModules: string[];
7474
platform?: 'browser' | 'server';
7575
fileReplacements: NormalizedFileReplacement[];
76-
/** @deprecated use only for compatibility in 8.x; will be removed in 9.0 */
76+
/** @deprecated use only for compatibility in 8.x; will be removed in 9.0 */
7777
rebaseRootRelativeCssUrls?: boolean;
78+
79+
/* Append script target version to filename. */
80+
esVersionInFileName?: boolean;
81+
82+
/* When specified it will be used instead of the script target in the tsconfig.json. */
83+
scriptTargetOverride?: ts.ScriptTarget;
7884
}
7985

8086
export interface WebpackTestOptions extends BuildOptions {

packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts

+28-15
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import { tags } from '@angular-devkit/core';
99
import * as CopyWebpackPlugin from 'copy-webpack-plugin';
1010
import * as path from 'path';
11+
import * as ts from 'typescript';
1112
import {
1213
Configuration,
1314
ContextReplacementPlugin,
@@ -21,8 +22,8 @@ import { CleanCssWebpackPlugin } from '../../plugins/cleancss-webpack-plugin';
2122
import { ScriptsWebpackPlugin } from '../../plugins/scripts-webpack-plugin';
2223
import { findAllNodeModules, findUp } from '../../utilities/find-up';
2324
import { requireProjectModule } from '../../utilities/require-project-module';
24-
import { BuildOptions, WebpackConfigOptions } from '../build-options';
25-
import { getOutputHashFormat, normalizeExtraEntryPoints } from './utils';
25+
import { WebpackConfigOptions } from '../build-options';
26+
import { getEsVersionForFileName, getOutputHashFormat, normalizeExtraEntryPoints } from './utils';
2627

2728
const ProgressPlugin = require('webpack/lib/ProgressPlugin');
2829
const CircularDependencyPlugin = require('circular-dependency-plugin');
@@ -55,35 +56,48 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
5556
const extraPlugins: any[] = [];
5657
const entryPoints: { [key: string]: string[] } = {};
5758

59+
const targetInFileName = getEsVersionForFileName(
60+
buildOptions.scriptTargetOverride,
61+
buildOptions.esVersionInFileName,
62+
);
63+
5864
if (buildOptions.main) {
5965
entryPoints['main'] = [path.resolve(root, buildOptions.main)];
6066
}
6167

68+
const es5Polyfills = path.join(__dirname, '..', 'es5-polyfills.js');
6269
if (buildOptions.es5BrowserSupport) {
63-
entryPoints['polyfills.es5'] = [path.join(__dirname, '..', 'es5-polyfills.js')];
70+
entryPoints['polyfills.es5'] = [es5Polyfills];
71+
if (!buildOptions.aot) {
72+
entryPoints['polyfills.es5'].push(path.join(__dirname, '..', 'es5-jit-polyfills.js'));
73+
}
74+
}
75+
76+
if (buildOptions.es5BrowserSupport === undefined
77+
&& buildOptions.scriptTargetOverride === ts.ScriptTarget.ES5) {
78+
entryPoints['polyfills'] = [es5Polyfills];
79+
if (!buildOptions.aot) {
80+
entryPoints['polyfills'].push(path.join(__dirname, '..', 'es5-jit-polyfills.js'));
81+
}
6482
}
6583

6684
if (buildOptions.polyfills) {
67-
entryPoints['polyfills'] = [path.resolve(root, buildOptions.polyfills)];
85+
entryPoints['polyfills'] = [
86+
...(entryPoints['polyfills'] || []),
87+
path.resolve(root, buildOptions.polyfills),
88+
];
6889
}
6990

7091
if (!buildOptions.aot) {
7192
entryPoints['polyfills'] = [
7293
...(entryPoints['polyfills'] || []),
7394
path.join(__dirname, '..', 'jit-polyfills.js'),
7495
];
75-
76-
if (buildOptions.es5BrowserSupport) {
77-
entryPoints['polyfills.es5'] = [
78-
...entryPoints['polyfills.es5'],
79-
path.join(__dirname, '..', 'es5-jit-polyfills.js'),
80-
];
81-
}
8296
}
8397

8498
if (buildOptions.profile || process.env['NG_BUILD_PROFILING']) {
8599
extraPlugins.push(new debug.ProfilingPlugin({
86-
outputPath: path.resolve(root, 'chrome-profiler-events.json'),
100+
outputPath: path.resolve(root, `chrome-profiler-events${targetInFileName}.json`),
87101
}));
88102
}
89103

@@ -104,7 +118,6 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
104118
}
105119

106120
existingEntry.paths.push(resolvedPath);
107-
108121
} else {
109122
prev.push({
110123
bundleName,
@@ -177,7 +190,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
177190
}
178191

179192
if (buildOptions.statsJson) {
180-
extraPlugins.push(new StatsPlugin('stats.json', 'verbose'));
193+
extraPlugins.push(new StatsPlugin(`stats${targetInFileName}.json`, 'verbose'));
181194
}
182195

183196
let sourceMapUseRule;
@@ -305,7 +318,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
305318
futureEmitAssets: true,
306319
path: path.resolve(root, buildOptions.outputPath as string),
307320
publicPath: buildOptions.deployUrl,
308-
filename: `[name]${hashFormat.chunk}.js`,
321+
filename: `[name]${targetInFileName}${hashFormat.chunk}.js`,
309322
// cast required until typings include `futureEmitAssets` property
310323
} as Output,
311324
watch: buildOptions.watch,

packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/typescript.ts

+38-17
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,37 @@ import {
1515
PLATFORM
1616
} from '@ngtools/webpack';
1717
import { buildOptimizerLoader } from './common';
18-
import { WebpackConfigOptions } from '../build-options';
18+
import { WebpackConfigOptions, BuildOptions } from '../build-options';
19+
20+
function _pluginOptionsOverrides(
21+
buildOptions: BuildOptions,
22+
pluginOptions: AngularCompilerPluginOptions
23+
): AngularCompilerPluginOptions {
24+
const compilerOptions = {
25+
...(pluginOptions.compilerOptions || {})
26+
}
1927

28+
const hostReplacementPaths: { [replace: string]: string } = {};
29+
if (buildOptions.fileReplacements) {
30+
for (const replacement of buildOptions.fileReplacements) {
31+
hostReplacementPaths[replacement.replace] = replacement.with;
32+
}
33+
}
34+
35+
if (buildOptions.scriptTargetOverride) {
36+
compilerOptions.target = buildOptions.scriptTargetOverride;
37+
}
38+
39+
if (buildOptions.preserveSymlinks) {
40+
compilerOptions.preserveSymlinks = true;
41+
}
42+
43+
return {
44+
...pluginOptions,
45+
hostReplacementPaths,
46+
compilerOptions
47+
};
48+
}
2049

2150
function _createAotPlugin(
2251
wco: WebpackConfigOptions,
@@ -25,13 +54,8 @@ function _createAotPlugin(
2554
extract = false,
2655
) {
2756
const { root, buildOptions } = wco;
28-
options.compilerOptions = options.compilerOptions || {};
29-
30-
if (wco.buildOptions.preserveSymlinks) {
31-
options.compilerOptions.preserveSymlinks = true;
32-
}
3357

34-
let i18nInFile = buildOptions.i18nFile
58+
const i18nInFile = buildOptions.i18nFile
3559
? path.resolve(root, buildOptions.i18nFile)
3660
: undefined;
3761

@@ -54,22 +78,14 @@ function _createAotPlugin(
5478
}
5579
}
5680

57-
const hostReplacementPaths: { [replace: string]: string } = {};
58-
if (buildOptions.fileReplacements) {
59-
for (const replacement of buildOptions.fileReplacements) {
60-
hostReplacementPaths[replacement.replace] = replacement.with;
61-
}
62-
}
63-
64-
const pluginOptions: AngularCompilerPluginOptions = {
81+
let pluginOptions: AngularCompilerPluginOptions = {
6582
mainPath: useMain ? path.join(root, buildOptions.main) : undefined,
6683
...i18nFileAndFormat,
6784
locale: buildOptions.i18nLocale,
6885
platform: buildOptions.platform === 'server' ? PLATFORM.Server : PLATFORM.Browser,
6986
missingTranslation: buildOptions.i18nMissingTranslation,
7087
sourceMap: buildOptions.sourceMap.scripts,
7188
additionalLazyModules,
72-
hostReplacementPaths,
7389
nameLazyFiles: buildOptions.namedChunks,
7490
forkTypeChecker: buildOptions.forkTypeChecker,
7591
contextElementDependencyConstructor: require('webpack/lib/dependencies/ContextElementDependency'),
@@ -78,6 +94,9 @@ function _createAotPlugin(
7894
importFactories: buildOptions.experimentalImportFactories,
7995
...options,
8096
};
97+
98+
pluginOptions = _pluginOptionsOverrides(buildOptions, pluginOptions);
99+
81100
return new AngularCompilerPlugin(pluginOptions);
82101
}
83102

@@ -112,7 +131,7 @@ export function getAotConfig(wco: WebpackConfigOptions, extract = false) {
112131
export function getTypescriptWorkerPlugin(wco: WebpackConfigOptions, workerTsConfigPath: string) {
113132
const { buildOptions } = wco;
114133

115-
const pluginOptions: AngularCompilerPluginOptions = {
134+
let pluginOptions: AngularCompilerPluginOptions = {
116135
skipCodeGeneration: true,
117136
tsConfigPath: workerTsConfigPath,
118137
mainPath: undefined,
@@ -127,5 +146,7 @@ export function getTypescriptWorkerPlugin(wco: WebpackConfigOptions, workerTsCon
127146
discoverLazyRoutes: false,
128147
};
129148

149+
pluginOptions = _pluginOptionsOverrides(buildOptions, pluginOptions);
150+
130151
return new AngularCompilerPlugin(pluginOptions);
131152
}

packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/utils.ts

+12
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import * as path from 'path';
1212
import { basename, normalize } from '@angular-devkit/core';
1313
import { ExtraEntryPoint, ExtraEntryPointClass } from '../../../browser/schema';
1414
import { SourceMapDevToolPlugin } from 'webpack';
15+
import * as ts from 'typescript';
1516

1617
export const ngAppResolve = (resolvePath: string): string => {
1718
return path.resolve(process.cwd(), resolvePath);
@@ -89,3 +90,14 @@ export function getSourceMapDevTool(
8990
append: hiddenSourceMap ? false : undefined,
9091
});
9192
}
93+
94+
/**
95+
* Returns an ES version file suffix to differentiate between various builds.
96+
*/
97+
export function getEsVersionForFileName(
98+
scriptTargetOverride: ts.ScriptTarget | undefined,
99+
esVersionInFileName = false,
100+
): string {
101+
return scriptTargetOverride && esVersionInFileName ?
102+
'-' + ts.ScriptTarget[scriptTargetOverride].toLowerCase() : '';
103+
}

0 commit comments

Comments
 (0)