refactor(@angular/cli): add webpack integrated scripts plugin

This commit is contained in:
Charles Lyding 2017-12-10 21:25:36 -05:00 committed by Mike Brocchi
parent 91f3f5b6fc
commit 8cea8ffdc2
10 changed files with 186 additions and 163 deletions

99
package-lock.json generated
View File

@ -174,6 +174,16 @@
"integrity": "sha1-WJKKYh0BTOarWcWpxBBx9zKLDKk=",
"dev": true
},
"@types/loader-utils": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@types/loader-utils/-/loader-utils-1.1.0.tgz",
"integrity": "sha512-VR4oHG6TzhpemxtBDf0BD8xlOiPo2B6zcFEA2Jjmgf1RqSrHLAiteIksV3YvpVn0Pd4HxV1B3LQ6Mf2pGTyZ7g==",
"dev": true,
"requires": {
"@types/node": "6.0.88",
"@types/webpack": "3.0.11"
}
},
"@types/lodash": {
"version": "4.14.74",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.74.tgz",
@ -239,6 +249,12 @@
"@types/mime": "2.0.0"
}
},
"@types/source-list-map": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz",
"integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==",
"dev": true
},
"@types/source-map": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/@types/source-map/-/source-map-0.5.1.tgz",
@ -271,6 +287,17 @@
"@types/uglify-js": "2.6.29"
}
},
"@types/webpack-sources": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.3.tgz",
"integrity": "sha512-yS052yVjjyIjwcUqIEe2+JxbWsw27OM8UFb1fLUGacGYtqMRwgAx2qk41VTE/nPMjw/xfD0JiHPD0Q99dlrInA==",
"dev": true,
"requires": {
"@types/node": "6.0.88",
"@types/source-list-map": "0.1.2",
"@types/source-map": "0.5.1"
}
},
"JSONStream": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz",
@ -1030,11 +1057,6 @@
}
}
},
"charenc": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
"integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc="
},
"chokidar": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz",
@ -1753,11 +1775,6 @@
"which": "1.3.0"
}
},
"crypt": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
"integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs="
},
"cryptiles": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz",
@ -5333,16 +5350,6 @@
"resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz",
"integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw="
},
"md5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz",
"integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=",
"requires": {
"charenc": "0.0.2",
"crypt": "0.0.2",
"is-buffer": "1.1.5"
}
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@ -9254,58 +9261,6 @@
}
}
},
"webpack-concat-plugin": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/webpack-concat-plugin/-/webpack-concat-plugin-1.4.2.tgz",
"integrity": "sha512-HdV2xOq4twtL2ThR9NSCCQ888v1JBMpJfm3k2mA1I5LkS2+/6rv8q/bb9yTSaR0fVaMtANZi4Wkz0xc33MAt6w==",
"requires": {
"md5": "2.2.1",
"uglify-js": "2.8.29"
},
"dependencies": {
"camelcase": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz",
"integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk="
},
"cliui": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
"integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=",
"requires": {
"center-align": "0.1.3",
"right-align": "0.1.3",
"wordwrap": "0.0.2"
}
},
"uglify-js": {
"version": "2.8.29",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz",
"integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=",
"requires": {
"source-map": "0.5.7",
"uglify-to-browserify": "1.0.2",
"yargs": "3.10.0"
}
},
"wordwrap": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz",
"integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8="
},
"yargs": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz",
"integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=",
"requires": {
"camelcase": "1.2.1",
"cliui": "2.1.0",
"decamelize": "1.2.0",
"window-size": "0.1.0"
}
}
}
},
"webpack-core": {
"version": "0.6.9",
"resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.9.tgz",

View File

@ -95,7 +95,6 @@
"uglifyjs-webpack-plugin": "~1.1.2",
"url-loader": "^0.6.2",
"webpack": "~3.10.0",
"webpack-concat-plugin": "^1.4.2",
"webpack-dev-middleware": "~1.12.0",
"webpack-dev-server": "~2.9.3",
"webpack-merge": "^4.1.0",
@ -116,6 +115,7 @@
"@types/glob": "^5.0.29",
"@types/jasmine": "2.5.45",
"@types/lodash": "~4.14.50",
"@types/loader-utils": "^1.1.0",
"@types/minimist": "^1.2.0",
"@types/mock-fs": "^3.6.30",
"@types/node": "^6.0.84",
@ -123,6 +123,7 @@
"@types/semver": "^5.3.30",
"@types/source-map": "^0.5.0",
"@types/webpack": "^3.0.5",
"@types/webpack-sources": "^0.1.3",
"conventional-changelog": "1.1.0",
"dtsgenerator": "^0.9.1",
"eslint": "^3.11.0",

View File

@ -2,13 +2,12 @@ import * as webpack from 'webpack';
import * as path from 'path';
import * as CopyWebpackPlugin from 'copy-webpack-plugin';
import { NamedLazyChunksWebpackPlugin } from '../../plugins/named-lazy-chunks-webpack-plugin';
import { InsertConcatAssetsWebpackPlugin } from '../../plugins/insert-concat-assets-webpack-plugin';
import { extraEntryParser, getOutputHashFormat, AssetPattern } from './utils';
import { isDirectory } from '../../utilities/is-directory';
import { requireProjectModule } from '../../utilities/require-project-module';
import { WebpackConfigOptions } from '../webpack-config';
import { ScriptsWebpackPlugin } from '../../plugins/scripts-webpack-plugin';
const ConcatPlugin = require('webpack-concat-plugin');
const ProgressPlugin = require('webpack/lib/ProgressPlugin');
const CircularDependencyPlugin = require('circular-dependency-plugin');
const SilentError = require('silent-error');
@ -65,23 +64,16 @@ export function getCommonConfig(wco: WebpackConfigOptions) {
// Add a new asset for each entry.
globalScriptsByEntry.forEach((script) => {
const hash = hashFormat.chunk !== '' && !script.lazy ? '.[hash]' : '';
extraPlugins.push(new ConcatPlugin({
uglify: buildOptions.target === 'production' ? { sourceMapIncludeSources: true } : false,
sourceMap: buildOptions.sourcemaps,
// Lazy scripts don't get a hash, otherwise they can't be loaded by name.
const hash = script.lazy ? '' : hashFormat.script;
extraPlugins.push(new ScriptsWebpackPlugin({
name: script.entry,
// Lazy scripts don't get a hash, otherwise they can't be loaded by name.
fileName: `[name]${script.lazy ? '' : hash}.bundle.js`,
filesToConcat: script.paths
sourceMap: buildOptions.sourcemaps,
filename: `${script.entry}${hash}.bundle.js`,
scripts: script.paths,
basePath: projectRoot,
}));
});
// Insert all the assets created by ConcatPlugin in the right place in index.html.
extraPlugins.push(new InsertConcatAssetsWebpackPlugin(
globalScriptsByEntry
.filter((el) => !el.lazy)
.map((el) => el.entry)
));
}
// process asset entries

View File

@ -81,8 +81,8 @@ export function getOutputHashFormat(option: string, length = 20): HashFormat {
const hashFormats: { [option: string]: HashFormat } = {
none: { chunk: '', extract: '', file: '' , script: '' },
media: { chunk: '', extract: '', file: `.[hash:${length}]`, script: '' },
bundles: { chunk: `.[chunkhash:${length}]`, extract: `.[contenthash:${length}]`, file: '' , script: '.[hash]' },
all: { chunk: `.[chunkhash:${length}]`, extract: `.[contenthash:${length}]`, file: `.[hash:${length}]`, script: '.[hash]' },
bundles: { chunk: `.[chunkhash:${length}]`, extract: `.[contenthash:${length}]`, file: '' , script: `.[hash:${length}]` },
all: { chunk: `.[chunkhash:${length}]`, extract: `.[contenthash:${length}]`, file: `.[hash:${length}]`, script: `.[hash:${length}]` },
};
/* tslint:enable:max-line-length */
return hashFormats[option] || hashFormats['none'];

View File

@ -53,6 +53,7 @@
"less-loader": "^4.0.5",
"license-webpack-plugin": "^1.0.0",
"lodash": "^4.11.1",
"loader-utils": "1.1.0",
"memory-fs": "^0.4.1",
"minimatch": "^3.0.4",
"node-modules-path": "^1.0.0",
@ -77,7 +78,6 @@
"uglifyjs-webpack-plugin": "~1.1.2",
"url-loader": "^0.6.2",
"webpack": "~3.10.0",
"webpack-concat-plugin": "^1.4.2",
"webpack-dev-middleware": "~1.12.0",
"webpack-dev-server": "~2.9.3",
"webpack-merge": "^4.1.0",

View File

@ -1,52 +0,0 @@
// Add assets from `ConcatPlugin` to index.html.
export class InsertConcatAssetsWebpackPlugin {
// Priority list of where to insert asset.
private insertAfter = [
/polyfills(\.[0-9a-f]{20})?\.bundle\.js/,
/inline(\.[0-9a-f]{20})?\.bundle\.js/,
];
constructor(private entryNames: string[]) { }
apply(compiler: any): void {
compiler.plugin('compilation', (compilation: any) => {
compilation.plugin('html-webpack-plugin-before-html-generation',
(htmlPluginData: any, callback: any) => {
const fileNames = this.entryNames.map((entryName) => {
const fileName = htmlPluginData.assets.webpackConcat
&& htmlPluginData.assets.webpackConcat[entryName];
if (!fileName) {
// Something went wrong and the asset was not correctly added.
throw new Error(`Cannot find file for ${entryName} script.`);
}
if (htmlPluginData.assets.publicPath) {
if (htmlPluginData.assets.publicPath.endsWith('/')) {
return htmlPluginData.assets.publicPath + fileName;
}
return htmlPluginData.assets.publicPath + '/' + fileName;
}
return fileName;
});
let insertAt = 0;
// TODO: try to figure out if there are duplicate bundle names when adding and throw
for (let el of this.insertAfter) {
const jsIdx = htmlPluginData.assets.js.findIndex((js: string) => js.match(el));
if (jsIdx !== -1) {
insertAt = jsIdx + 1;
break;
}
}
htmlPluginData.assets.js.splice(insertAt, 0, ...fileNames);
callback(null, htmlPluginData);
});
});
}
}

View File

@ -0,0 +1,140 @@
/**
* @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 { Compiler, loader } from 'webpack';
import { CachedSource, ConcatSource, OriginalSource, RawSource, Source } from 'webpack-sources';
import { interpolateName } from 'loader-utils';
import * as path from 'path';
const Chunk = require('webpack/lib/Chunk');
export interface ScriptsWebpackPluginOptions {
name: string;
sourceMap: boolean;
scripts: string[];
filename: string;
basePath: string;
}
interface ScriptOutput {
filename: string;
source: CachedSource;
}
export class ScriptsWebpackPlugin {
private _lastBuildTime?: number;
private _cachedOutput?: ScriptOutput;
constructor(private options: Partial<ScriptsWebpackPluginOptions> = {}) {}
shouldSkip(compilation: any, scripts: string[]): boolean {
if (this._lastBuildTime == undefined) {
this._lastBuildTime = Date.now();
return false;
}
for (let i = 0; i < scripts.length; i++) {
const scriptTime = compilation.fileTimestamps[scripts[i]];
if (!scriptTime || scriptTime > this._lastBuildTime) {
this._lastBuildTime = Date.now();
return false;
}
}
return true;
}
private _insertOutput(compilation: any, { filename, source }: ScriptOutput, cached = false) {
const chunk = new Chunk();
chunk.rendered = !cached;
chunk.id = this.options.name;
chunk.ids = [chunk.id];
chunk.name = this.options.name;
chunk.isInitial = () => true;
chunk.files.push(filename);
compilation.chunks.push(chunk);
compilation.assets[filename] = source;
}
apply(compiler: Compiler): void {
if (!this.options.scripts || this.options.scripts.length === 0) {
return;
}
const scripts = this.options.scripts
.filter(script => !!script)
.map(script => path.resolve(this.options.basePath || '', script));
compiler.plugin('this-compilation', (compilation: any) => {
compilation.plugin('additional-assets', (callback: (err?: Error) => void) => {
if (this.shouldSkip(compilation, scripts)) {
if (this._cachedOutput) {
this._insertOutput(compilation, this._cachedOutput, true);
}
compilation.fileDependencies.push(...scripts);
callback();
return;
}
const sourceGetters = scripts.map(fullPath => {
return new Promise<Source>((resolve, reject) => {
compilation.inputFileSystem.readFile(fullPath, (err: Error, data: Buffer) => {
if (err) {
reject(err);
return;
}
const content = data.toString();
let source;
if (this.options.sourceMap) {
// TODO: Look for source map file (for '.min' scripts, etc.)
let adjustedPath = fullPath;
if (this.options.basePath) {
adjustedPath = path.relative(this.options.basePath, fullPath);
}
source = new OriginalSource(content, adjustedPath);
} else {
source = new RawSource(content);
}
resolve(source);
});
});
});
Promise.all(sourceGetters)
.then(sources => {
const concatSource = new ConcatSource();
sources.forEach(source => {
concatSource.add(source);
concatSource.add('\n');
});
const combinedSource = new CachedSource(concatSource);
const filename = interpolateName(
{ resourcePath: 'scripts.js' } as loader.LoaderContext,
this.options.filename,
{ content: combinedSource.source() },
);
const output = { filename, source: combinedSource };
this._insertOutput(compilation, output);
this._cachedOutput = output;
compilation.fileDependencies.push(...scripts);
callback();
})
.catch((err: Error) => callback(err));
});
});
}
}

View File

@ -1,6 +1,6 @@
// Exports the webpack plugins we use internally.
export { BaseHrefWebpackPlugin } from '../lib/base-href-webpack/base-href-webpack-plugin';
export { GlobCopyWebpackPlugin, GlobCopyWebpackPluginOptions } from './glob-copy-webpack-plugin';
export { InsertConcatAssetsWebpackPlugin } from './insert-concat-assets-webpack-plugin';
export { NamedLazyChunksWebpackPlugin } from './named-lazy-chunks-webpack-plugin';
export { ScriptsWebpackPlugin, ScriptsWebpackPluginOptions } from './scripts-webpack-plugin';
export { SuppressExtractedTextChunksWebpackPlugin } from './suppress-entry-chunks-webpack-plugin';

View File

@ -26,7 +26,6 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
const SubresourceIntegrityPlugin = require('webpack-subresource-integrity');
const SilentError = require('silent-error');
const CircularDependencyPlugin = require('circular-dependency-plugin');
const ConcatPlugin = require('webpack-concat-plugin');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const Task = require('../ember-cli/lib/models/task');
@ -159,18 +158,6 @@ class JsonWebpackSerializer {
return plugin.options;
}
private _concatPlugin(plugin: any) {
const options = plugin.settings;
if (!options || !options.filesToConcat) {
return options;
}
const filesToConcat = options.filesToConcat
.map((file: string) => path.relative(process.cwd(), file));
return { ...options, filesToConcat };
}
private _uglifyjsPlugin(plugin: any) {
return plugin.options;
}
@ -204,6 +191,7 @@ class JsonWebpackSerializer {
break;
case angularCliPlugins.BaseHrefWebpackPlugin:
case angularCliPlugins.NamedLazyChunksWebpackPlugin:
case angularCliPlugins.ScriptsWebpackPlugin:
case angularCliPlugins.SuppressExtractedTextChunksWebpackPlugin:
this._addImport('@angular/cli/plugins/webpack', plugin.constructor.name);
break;
@ -249,10 +237,6 @@ class JsonWebpackSerializer {
args = this._licenseWebpackPlugin(plugin);
this._addImport('license-webpack-plugin', 'LicenseWebpackPlugin');
break;
case ConcatPlugin:
args = this._concatPlugin(plugin);
this.variableImports['webpack-concat-plugin'] = 'ConcatPlugin';
break;
case UglifyJSPlugin:
args = this._uglifyjsPlugin(plugin);
this.variableImports['uglifyjs-webpack-plugin'] = 'UglifyJsPlugin';
@ -594,7 +578,6 @@ export default Task.extend({
'stylus-loader',
'url-loader',
'circular-dependency-plugin',
'webpack-concat-plugin',
'copy-webpack-plugin',
'uglifyjs-webpack-plugin',
].forEach((packageName: string) => {

View File

@ -15,6 +15,10 @@ export function packageChunkSort(appConfig: any) {
extraEntryParser(appConfig.styles, './', 'styles').forEach(pushExtraEntries);
}
if (appConfig.scripts) {
extraEntryParser(appConfig.scripts, './', 'scripts').forEach(pushExtraEntries);
}
entryPoints.push(...['vendor', 'main']);
function sort(left: any, right: any) {