From 223a5c389b6ab153915bdf162c77449b497c8d44 Mon Sep 17 00:00:00 2001 From: ManfredSteyer Date: Thu, 14 Mar 2019 17:57:41 +0100 Subject: [PATCH] feat(@angular-devkit/build-angular): updating webpack config for differential loading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This updates a webpack configuration which has been created by a webpack-based builder for differential loading. For this, it duplicates the existing configuration and modifies e. g. the name of the bundles to generate (` [name].modern.js` instead of `[name].js`), the resolve array, and the Angular Compiler Plugin’s reference to the tsconfig.json. Because of this strategy, just one webpack config needs to be maintained per builder. The other one is retrieved from it by this logic. This is esp. important if the CLI supports several bundles in future, e. g. ES5, ES2015, ES2016, ES2017, etc. This PR is currently not used but it is the foundation for further ones. Also see: https://docs.google.com/document/d/13k84oGwrEjwPyAiAjUgaaM7YHJrzYXz7Cbt6CwRp9N4/edit?ts=5c652052#heading=h.gijrsdjw51q5 --- .../create-webpack-multi-config.ts | 185 ++++++++++++++++++ .../webpack_config_spec.ts | 46 +++++ .../test-differential-loading/main.ts | 0 .../tsconfig.test.json | 21 ++ .../tsconfig.test.legacy.json | 21 ++ 5 files changed, 273 insertions(+) create mode 100644 packages/angular_devkit/build_angular/src/differential-loading/create-webpack-multi-config.ts create mode 100644 packages/angular_devkit/build_angular/test/differential-loading/webpack_config_spec.ts create mode 100644 tests/angular_devkit/build_angular/test-differential-loading/main.ts create mode 100644 tests/angular_devkit/build_angular/test-differential-loading/tsconfig.test.json create mode 100644 tests/angular_devkit/build_angular/test-differential-loading/tsconfig.test.legacy.json diff --git a/packages/angular_devkit/build_angular/src/differential-loading/create-webpack-multi-config.ts b/packages/angular_devkit/build_angular/src/differential-loading/create-webpack-multi-config.ts new file mode 100644 index 000000000000..2d7c424c2788 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/differential-loading/create-webpack-multi-config.ts @@ -0,0 +1,185 @@ +/** + * @license + * Copyright Google Inc. 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 + */ + + +// This updates a webpack configuration which has been created by a +// webpack-based builder for differential loading. It duplicates an +// existing one and modifies e. g. the name of the bundles to generate +// ([name].modern.js instead of [name].js), the resolve array, and +// the Angular Compiler Plugin’s reference to the tsconfig.json to use. +// +// see: https://docs.google.com/document/d/13k84oGwrEjwPyAiAjUgaaM7YHJrzYXz7Cbt6CwRp9N4/ + + +import { AngularCompilerPlugin } from '@ngtools/webpack'; +import * as webpack from 'webpack'; + +const StatsPlugin = require('stats-webpack-plugin'); + + +export function createWebpackMultiConfig(config: webpack.Configuration): webpack.Configuration[] { + + const legacyConfig: webpack.Configuration = { + ...config, + output: buildLegacyOutput(config), + plugins: buildLegacyPlugins(config), + resolve: buildLegacyResolve(config), + }; + + const modernConfig: webpack.Configuration = { + ...config, + output: buildModernOutput(config), + plugins: buildModernPlugins(config), + entry: buildModernEntry(config), + resolve: buildModernResolve(config), + }; + + const newConfig = [legacyConfig, modernConfig]; + + return newConfig; +} + +function buildLegacyOutput(config: webpack.Configuration): webpack.Output | undefined { + + if (!config.output || !config.output.filename) { + return config.output; + } + + const filename = config.output.filename.replace('[name]', '[name].legacy'); + + return { ...config.output, filename }; +} + +function buildModernOutput(config: webpack.Configuration): webpack.Output | undefined { + + if (!config.output || !config.output.filename) { + return config.output; + } + + const filename = config.output.filename.replace('[name]', '[name].modern'); + + return { ...config.output, filename }; +} + +function buildModernResolve(config: webpack.Configuration): webpack.Resolve | undefined { + + if (!config.resolve || !config.resolve.mainFields) { + return config.resolve; + } + + const modernResolve = { ...config.resolve }; + modernResolve.mainFields = ['es2015', 'module', 'browser', 'main']; + + return modernResolve; +} + +function buildLegacyResolve(config: webpack.Configuration): webpack.Resolve | undefined { + + if (!config.resolve || !config.resolve.mainFields) { + return config.resolve; + } + + const modernResolve = { ...config.resolve }; + modernResolve.mainFields = ['module', 'browser', 'main']; + + return modernResolve; +} + +function buildModernEntry(config: webpack.Configuration): webpack.Entry | undefined { + const entry = config.entry; + + if (!entry) { + return undefined; + } + + const polyfillsEntry = entry as webpack.Entry; + + const polyfills: string[] = polyfillsEntry['polyfills'] as string[]; + + if (!polyfills) { + return { ...polyfillsEntry }; + } + + const tweakPolyfills = (file: string) => { + + // TODO: Do we need an own polyfill file for modern browsers? If yes, where is it created? + + if (file.endsWith('polyfills.ts')) { + return file.replace('polyfills.ts', 'polyfills.modern.ts'); + } else { + return file; + } + }; + + const modernPolyfills = polyfills.map(tweakPolyfills); + const modernEntry = { ...entry as webpack.Entry, polyfills: modernPolyfills }; + + return modernEntry; +} + +function buildLegacyPlugins(config: webpack.Configuration): webpack.Plugin[] | undefined { + const plugins = config.plugins; + + if (!plugins) { + return plugins; + } + + const legacyPlugins = [...plugins]; + + const acpIndex = plugins.findIndex(p => p.constructor.name === 'AngularCompilerPlugin'); + const acp = plugins[acpIndex] as AngularCompilerPlugin; + const legacyAcp = buildLegacyAngularCompilerPlugin(acp); + legacyPlugins[acpIndex] = legacyAcp; + + return legacyPlugins; +} + +function buildModernPlugins(config: webpack.Configuration): webpack.Plugin[] { + const plugins = config.plugins; + + if (!plugins) { + return []; + } + + const modernPlugins = [...plugins]; + + const statsPluginIndex = plugins.findIndex(p => p.constructor.name === 'StatsPlugin'); + if (statsPluginIndex > -1) { + const modernStatsPlugin = buildModernStatsPlugin(); + modernPlugins[statsPluginIndex] = modernStatsPlugin; + } + + return modernPlugins; +} + +function buildModernStatsPlugin(): webpack.Plugin { + const modernStatsPlugin = new StatsPlugin(); + modernStatsPlugin.output = 'stats.modern.json'; + + return modernStatsPlugin; +} + +function buildLegacyAngularCompilerPlugin(acp: AngularCompilerPlugin): AngularCompilerPlugin { + const options = acp.options; + const modernOptions = { ...options }; + + // TODO: Where shall we create this tsconfig with target=es5? + // Schematics? On the fly if it does not exist? + + let tsConfigPath = modernOptions.tsConfigPath; + if (tsConfigPath.endsWith('.json')) { + tsConfigPath = tsConfigPath.substr(0, tsConfigPath.length - 5); + } + tsConfigPath += '.legacy.json'; + + modernOptions.tsConfigPath = tsConfigPath; + + const legacyAcp = new AngularCompilerPlugin(modernOptions); + + return legacyAcp; +} diff --git a/packages/angular_devkit/build_angular/test/differential-loading/webpack_config_spec.ts b/packages/angular_devkit/build_angular/test/differential-loading/webpack_config_spec.ts new file mode 100644 index 000000000000..750e47d88153 --- /dev/null +++ b/packages/angular_devkit/build_angular/test/differential-loading/webpack_config_spec.ts @@ -0,0 +1,46 @@ +/** + * @license + * Copyright Google Inc. 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 { AngularCompilerPlugin } from '@ngtools/webpack'; +import * as path from 'path'; +import { + createWebpackMultiConfig, +} from '../../src/differential-loading/create-webpack-multi-config'; + +const devkitRoot = (global as any)._DevKitRoot; // tslint:disable-line:no-any +const workspaceRoot = path.join( + devkitRoot, + 'tests/angular_devkit/build_angular/test-differential-loading/'); + +const tsConfig = path.join(workspaceRoot, 'tsconfig.test.json'); + +describe('differential loading webpack config', () => { + + it('will be updated', () => { + + const config = { + output: { + filename: '[name].js', + }, + resolve: { + mainFields: ['main'], + }, + entry: { + 'main': 'main.js', + }, + plugins: [ + new AngularCompilerPlugin({tsConfigPath: tsConfig }), + ], + }; + + const newConfig = createWebpackMultiConfig(config); + + expect(Array.isArray(newConfig)).toBe(true); + expect(newConfig.length).toBe(2); + }); +}); diff --git a/tests/angular_devkit/build_angular/test-differential-loading/main.ts b/tests/angular_devkit/build_angular/test-differential-loading/main.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/angular_devkit/build_angular/test-differential-loading/tsconfig.test.json b/tests/angular_devkit/build_angular/test-differential-loading/tsconfig.test.json new file mode 100644 index 000000000000..944d93dc8215 --- /dev/null +++ b/tests/angular_devkit/build_angular/test-differential-loading/tsconfig.test.json @@ -0,0 +1,21 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es2015", + "module": "esnext", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2017", + "dom" + ] + } +} diff --git a/tests/angular_devkit/build_angular/test-differential-loading/tsconfig.test.legacy.json b/tests/angular_devkit/build_angular/test-differential-loading/tsconfig.test.legacy.json new file mode 100644 index 000000000000..cabc4606d16f --- /dev/null +++ b/tests/angular_devkit/build_angular/test-differential-loading/tsconfig.test.legacy.json @@ -0,0 +1,21 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es5", + "module": "esnext", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2017", + "dom" + ] + } +}