Skip to content

Commit 6518767

Browse files
committed
feat(@angular/cli): support sourcemaps and minification in scripts
1 parent d85652e commit 6518767

File tree

12 files changed

+201
-854
lines changed

12 files changed

+201
-854
lines changed

package-lock.json

+73-795
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@
8686
"rsvp": "^3.0.17",
8787
"rxjs": "^5.4.2",
8888
"sass-loader": "^6.0.3",
89-
"script-loader": "^0.7.0",
9089
"semver": "^5.3.0",
9190
"silent-error": "^1.0.0",
9291
"source-map": "^0.5.6",
@@ -100,6 +99,7 @@
10099
"url-loader": "^0.5.7",
101100
"walk-sync": "^0.3.1",
102101
"webpack": "~3.4.1",
102+
"webpack-concat-plugin": "1.4.0",
103103
"webpack-dev-middleware": "^1.11.0",
104104
"webpack-dev-server": "~2.5.1",
105105
"webpack-merge": "^4.1.0",

packages/@angular/cli/models/webpack-configs/common.ts

+35-6
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import * as webpack from 'webpack';
22
import * as path from 'path';
33
import { GlobCopyWebpackPlugin } from '../../plugins/glob-copy-webpack-plugin';
44
import { NamedLazyChunksWebpackPlugin } from '../../plugins/named-lazy-chunks-webpack-plugin';
5+
import { InsertConcatAssetsWebpackPlugin } from '../../plugins/insert-concat-assets-webpack-plugin';
56
import { extraEntryParser, getOutputHashFormat } from './utils';
67
import { WebpackConfigOptions } from '../webpack-config';
78

9+
const ConcatPlugin = require('webpack-concat-plugin');
810
const ProgressPlugin = require('webpack/lib/ProgressPlugin');
911
const CircularDependencyPlugin = require('circular-dependency-plugin');
1012

@@ -15,7 +17,6 @@ const CircularDependencyPlugin = require('circular-dependency-plugin');
1517
*
1618
* require('source-map-loader')
1719
* require('raw-loader')
18-
* require('script-loader')
1920
* require('url-loader')
2021
* require('file-loader')
2122
* require('@angular-devkit/build-optimizer')
@@ -45,12 +46,40 @@ export function getCommonConfig(wco: WebpackConfigOptions) {
4546
// process global scripts
4647
if (appConfig.scripts.length > 0) {
4748
const globalScripts = extraEntryParser(appConfig.scripts, appRoot, 'scripts');
48-
49-
// add entry points and lazy chunks
50-
globalScripts.forEach(script => {
51-
let scriptPath = `script-loader!${script.path}`;
52-
entryPoints[script.entry] = (entryPoints[script.entry] || []).concat(scriptPath);
49+
const globalScriptsByEntry = globalScripts
50+
.reduce((prev: { entry: string, paths: string[], lazy: boolean }[], curr) => {
51+
52+
let existingEntry = prev.find((el) => el.entry === curr.entry);
53+
if (existingEntry) {
54+
existingEntry.paths.push(curr.path);
55+
// All entries have to be lazy for the bundle to be lazy.
56+
existingEntry.lazy = existingEntry.lazy && curr.lazy;
57+
} else {
58+
prev.push({ entry: curr.entry, paths: [curr.path], lazy: curr.lazy });
59+
}
60+
return prev;
61+
}, []);
62+
63+
64+
// Add a new asset for each entry.
65+
globalScriptsByEntry.forEach((script) => {
66+
const hash = hashFormat.chunk !== '' && !script.lazy ? '.[hash]' : '';
67+
extraPlugins.push(new ConcatPlugin({
68+
uglify: buildOptions.target === 'production' ? { sourceMapIncludeSources: true } : false,
69+
sourceMap: buildOptions.sourcemaps,
70+
name: script.entry,
71+
// Lazy scripts don't get a hash, otherwise they can't be loaded by name.
72+
fileName: `[name]${script.lazy ? '' : hash}.bundle.js`,
73+
filesToConcat: script.paths
74+
}));
5375
});
76+
77+
// Insert all the assets created by ConcatPlugin in the right place in index.html.
78+
extraPlugins.push(new InsertConcatAssetsWebpackPlugin(
79+
globalScriptsByEntry
80+
.filter((el) => !el.lazy)
81+
.map((el) => el.entry)
82+
));
5483
}
5584

5685
// process asset entries

packages/@angular/cli/models/webpack-configs/utils.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,16 @@ export interface HashFormat {
7373
chunk: string;
7474
extract: string;
7575
file: string;
76+
script: string;
7677
}
7778

7879
export function getOutputHashFormat(option: string, length = 20): HashFormat {
7980
/* tslint:disable:max-line-length */
8081
const hashFormats: { [option: string]: HashFormat } = {
81-
none: { chunk: '', extract: '', file: '' },
82-
media: { chunk: '', extract: '', file: `.[hash:${length}]` },
83-
bundles: { chunk: `.[chunkhash:${length}]`, extract: `.[contenthash:${length}]`, file: '' },
84-
all: { chunk: `.[chunkhash:${length}]`, extract: `.[contenthash:${length}]`, file: `.[hash:${length}]` },
82+
none: { chunk: '', extract: '', file: '' , script: '' },
83+
media: { chunk: '', extract: '', file: `.[hash:${length}]`, script: '' },
84+
bundles: { chunk: `.[chunkhash:${length}]`, extract: `.[contenthash:${length}]`, file: '' , script: '.[hash]' },
85+
all: { chunk: `.[chunkhash:${length}]`, extract: `.[contenthash:${length}]`, file: `.[hash:${length}]`, script: '.[hash]' },
8586
};
8687
/* tslint:enable:max-line-length */
8788
return hashFormats[option] || hashFormats['none'];

packages/@angular/cli/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@
7171
"rsvp": "^3.0.17",
7272
"rxjs": "^5.4.2",
7373
"sass-loader": "^6.0.3",
74-
"script-loader": "^0.7.0",
7574
"semver": "^5.1.0",
7675
"silent-error": "^1.0.0",
7776
"source-map-loader": "^0.2.0",
@@ -85,6 +84,7 @@
8584
"url-loader": "^0.5.7",
8685
"walk-sync": "^0.3.1",
8786
"webpack": "~3.4.1",
87+
"webpack-concat-plugin": "1.4.0",
8888
"webpack-dev-middleware": "^1.11.0",
8989
"webpack-dev-server": "~2.5.1",
9090
"webpack-merge": "^4.1.0",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Add assets from `ConcatPlugin` to index.html.
2+
3+
export class InsertConcatAssetsWebpackPlugin {
4+
// Priority list of where to insert asset.
5+
private insertAfter = [
6+
/polyfills(\.[0-9a-f]{20})?\.bundle\.js/,
7+
/inline(\.[0-9a-f]{20})?\.bundle\.js/,
8+
];
9+
10+
constructor(private entryNames: string[]) { }
11+
12+
apply(compiler: any): void {
13+
compiler.plugin('compilation', (compilation: any) => {
14+
compilation.plugin('html-webpack-plugin-before-html-generation',
15+
(htmlPluginData: any, callback: any) => {
16+
17+
const fileNames = this.entryNames.map((entryName) => {
18+
const fileName = htmlPluginData.assets.webpackConcat
19+
&& htmlPluginData.assets.webpackConcat[entryName];
20+
21+
if (!fileName) {
22+
// Something went wrong and the asset was not correctly added.
23+
throw new Error(`Cannot find file for ${entryName} script.`);
24+
}
25+
26+
return fileName;
27+
});
28+
29+
let insertAt = 0;
30+
31+
// TODO: try to figure out if there are duplicate bundle names when adding and throw
32+
for (let el of this.insertAfter) {
33+
const jsIdx = htmlPluginData.assets.js.findIndex((js: string) => js.match(el));
34+
if (jsIdx !== -1) {
35+
insertAt = jsIdx + 1;
36+
break;
37+
}
38+
}
39+
40+
htmlPluginData.assets.js.splice(insertAt, 0, ...fileNames);
41+
callback(null, htmlPluginData);
42+
});
43+
});
44+
}
45+
}

packages/@angular/cli/plugins/webpack.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@ module.exports = {
88
require('../plugins/suppress-entry-chunks-webpack-plugin')
99
.SuppressExtractedTextChunksWebpackPlugin,
1010
NamedLazyChunksWebpackPlugin:
11-
require('../plugins/named-lazy-chunks-webpack-plugin').NamedLazyChunksWebpackPlugin
11+
require('../plugins/named-lazy-chunks-webpack-plugin').NamedLazyChunksWebpackPlugin,
12+
InsertConcatAssetsWebpackPlugin:
13+
require('../plugins/insert-concat-assets-webpack-plugin').InsertConcatAssetsWebpackPlugin
1214
};

packages/@angular/cli/tasks/eject.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
2323
const SilentError = require('silent-error');
2424
const licensePlugin = require('license-webpack-plugin');
2525
const CircularDependencyPlugin = require('circular-dependency-plugin');
26+
const ConcatPlugin = require('webpack-concat-plugin');
2627
const Task = require('../ember-cli/lib/models/task');
2728

2829
const ProgressPlugin = require('webpack/lib/ProgressPlugin');
@@ -85,6 +86,10 @@ class JsonWebpackSerializer {
8586
};
8687
}
8788

89+
private _insertConcatAssetsWebpackPluginSerialize(value: any): any {
90+
return value.entryNames;
91+
}
92+
8893
private _commonsChunkPluginSerialize(value: any): any {
8994
let minChunks = value.minChunks;
9095
switch (typeof minChunks) {
@@ -151,6 +156,10 @@ class JsonWebpackSerializer {
151156
};
152157
}
153158

159+
private _concatPlugin(plugin: any) {
160+
return plugin.settings;
161+
}
162+
154163
private _pluginsReplacer(plugins: any[]) {
155164
return plugins.map(plugin => {
156165
let args = plugin.options || undefined;
@@ -186,6 +195,10 @@ class JsonWebpackSerializer {
186195
args = this._globCopyWebpackPluginSerialize(plugin);
187196
this._addImport('@angular/cli/plugins/webpack', 'GlobCopyWebpackPlugin');
188197
break;
198+
case angularCliPlugins.InsertConcatAssetsWebpackPlugin:
199+
args = this._insertConcatAssetsWebpackPluginSerialize(plugin);
200+
this._addImport('@angular/cli/plugins/webpack', 'InsertConcatAssetsWebpackPlugin');
201+
break;
189202
case webpack.optimize.CommonsChunkPlugin:
190203
args = this._commonsChunkPluginSerialize(plugin);
191204
this._addImport('webpack.optimize', 'CommonsChunkPlugin');
@@ -212,6 +225,9 @@ class JsonWebpackSerializer {
212225
case licensePlugin:
213226
args = this._licenseWebpackPlugin(plugin);
214227
this.variableImports['license-webpack-plugin'] = 'licensePlugin';
228+
case ConcatPlugin:
229+
args = this._concatPlugin(plugin);
230+
this.variableImports['webpack-concat-plugin'] = 'ConcatPlugin';
215231
default:
216232
if (plugin.constructor.name == 'AngularServiceWorkerPlugin') {
217233
this._addImport('@angular/service-worker/build/webpack', plugin.constructor.name);
@@ -515,13 +531,13 @@ export default Task.extend({
515531
'postcss-url',
516532
'raw-loader',
517533
'sass-loader',
518-
'script-loader',
519534
'source-map-loader',
520535
'istanbul-instrumenter-loader',
521536
'style-loader',
522537
'stylus-loader',
523538
'url-loader',
524539
'circular-dependency-plugin',
540+
'webpack-concat-plugin',
525541
].forEach((packageName: string) => {
526542
packageJson['devDependencies'][packageName] = ourPackageJson['dependencies'][packageName];
527543
});

packages/@angular/cli/utilities/package-chunk-sort.ts

+1-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ExtraEntry, extraEntryParser } from '../models/webpack-configs/utils';
22

33
// Sort chunks according to a predefined order:
4-
// inline, polyfills, all scripts, all styles, vendor, main
4+
// inline, polyfills, all styles, vendor, main
55
export function packageChunkSort(appConfig: any) {
66
let entryPoints = ['inline', 'polyfills', 'sw-register'];
77

@@ -11,10 +11,6 @@ export function packageChunkSort(appConfig: any) {
1111
}
1212
};
1313

14-
if (appConfig.scripts) {
15-
extraEntryParser(appConfig.scripts, './', 'scripts').forEach(pushExtraEntries);
16-
}
17-
1814
if (appConfig.styles) {
1915
extraEntryParser(appConfig.styles, './', 'styles').forEach(pushExtraEntries);
2016
}
+16-16
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
import {
22
writeMultipleFiles,
33
expectFileToMatch,
4-
appendToFile
4+
appendToFile,
5+
expectFileMatchToExist
56
} from '../../utils/fs';
67
import { ng } from '../../utils/process';
78
import { updateJsonFile } from '../../utils/project';
89
import { oneLineTrim } from 'common-tags';
910

1011
export default function () {
1112
return writeMultipleFiles({
12-
'src/string-script.js': 'console.log(\'string-script\');',
13+
'src/string-script.js': 'console.log(\'string-script\'); var number = 1+1;',
1314
'src/input-script.js': 'console.log(\'input-script\');',
1415
'src/lazy-script.js': 'console.log(\'lazy-script\');',
1516
'src/pre-rename-script.js': 'console.log(\'pre-rename-script\');',
16-
'src/pre-rename-lazy-script.js': 'console.log(\'pre-rename-lazy-script\');',
17-
'src/common-entry-script.js': 'console.log(\'common-entry-script\');',
18-
'src/common-entry-style.css': '.common-entry-style { color: red }',
17+
'src/pre-rename-lazy-script.js': 'console.log(\'pre-rename-lazy-script\');'
1918
})
2019
.then(() => appendToFile('src/main.ts', 'import \'./string-script.js\';'))
2120
.then(() => updateJsonFile('.angular-cli.json', configJson => {
@@ -25,10 +24,8 @@ export default function () {
2524
{ input: 'input-script.js' },
2625
{ input: 'lazy-script.js', lazy: true },
2726
{ input: 'pre-rename-script.js', output: 'renamed-script' },
28-
{ input: 'pre-rename-lazy-script.js', output: 'renamed-lazy-script', lazy: true },
29-
{ input: 'common-entry-script.js', output: 'common-entry' }
27+
{ input: 'pre-rename-lazy-script.js', output: 'renamed-lazy-script', lazy: true }
3028
];
31-
app['styles'] = [{ input: 'common-entry-style.css', output: 'common-entry' }];
3229
}))
3330
.then(() => ng('build', '--extract-css'))
3431
// files were created successfully
@@ -37,21 +34,24 @@ export default function () {
3734
.then(() => expectFileToMatch('dist/lazy-script.bundle.js', 'lazy-script'))
3835
.then(() => expectFileToMatch('dist/renamed-script.bundle.js', 'pre-rename-script'))
3936
.then(() => expectFileToMatch('dist/renamed-lazy-script.bundle.js', 'pre-rename-lazy-script'))
40-
.then(() => expectFileToMatch('dist/common-entry.bundle.js', 'common-entry-script'))
41-
.then(() => expectFileToMatch('dist/common-entry.bundle.css', '.common-entry-style'))
4237
// index.html lists the right bundles
43-
.then(() => expectFileToMatch('dist/index.html', oneLineTrim`
44-
<link href="common-entry.bundle.css" rel="stylesheet"/>
45-
`))
4638
.then(() => expectFileToMatch('dist/index.html', oneLineTrim`
4739
<script type="text/javascript" src="inline.bundle.js"></script>
4840
<script type="text/javascript" src="polyfills.bundle.js"></script>
4941
<script type="text/javascript" src="scripts.bundle.js"></script>
5042
<script type="text/javascript" src="renamed-script.bundle.js"></script>
51-
<script type="text/javascript" src="common-entry.bundle.js"></script>
5243
<script type="text/javascript" src="vendor.bundle.js"></script>
5344
<script type="text/javascript" src="main.bundle.js"></script>
5445
`))
55-
// ensure scripts aren't using script-loader when imported from the app
56-
.then(() => expectFileToMatch('dist/main.bundle.js', 'console.log(\'string-script\');'));
46+
// Ensure scripts can be separately imported from the app.
47+
.then(() => expectFileToMatch('dist/main.bundle.js', 'console.log(\'string-script\');'))
48+
// Verify uglify, sourcemaps and hashes. Lazy scripts should not get hashes.
49+
.then(() => ng('build', '--prod', '--sourcemap'))
50+
.then(() => expectFileMatchToExist('dist', /scripts\.[0-9a-f]{20}\.bundle\.js/))
51+
.then(fileName => expectFileToMatch(`dist/${fileName}`, 'var number=2;'))
52+
.then(() => expectFileMatchToExist('dist', /scripts\.[0-9a-f]{20}\.bundle\.js\.map/))
53+
.then(() => expectFileMatchToExist('dist', /renamed-script\.[0-9a-f]{20}\.bundle\.js/))
54+
.then(() => expectFileMatchToExist('dist', /renamed-script\.[0-9a-f]{20}\.bundle\.js.map/))
55+
.then(() => expectFileToMatch('dist/lazy-script.bundle.js', 'lazy-script'))
56+
.then(() => expectFileToMatch('dist/renamed-lazy-script.bundle.js', 'pre-rename-lazy-script'));
5757
}

tests/e2e/tests/build/styles/extract-css.ts

+2-13
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@ export default function () {
2121
'src/input-style.css': '.input-style { color: red }',
2222
'src/lazy-style.css': '.lazy-style { color: red }',
2323
'src/pre-rename-style.css': '.pre-rename-style { color: red }',
24-
'src/pre-rename-lazy-style.css': '.pre-rename-lazy-style { color: red }',
25-
'src/common-entry-style.css': '.common-entry-style { color: red }',
26-
'src/common-entry-script.js': 'console.log(\'common-entry-script\');'
24+
'src/pre-rename-lazy-style.css': '.pre-rename-lazy-style { color: red }'
2725
}))
2826
.then(() => updateJsonFile('.angular-cli.json', configJson => {
2927
const app = configJson['apps'][0];
@@ -32,10 +30,8 @@ export default function () {
3230
{ input: 'input-style.css' },
3331
{ input: 'lazy-style.css', lazy: true },
3432
{ input: 'pre-rename-style.css', output: 'renamed-style' },
35-
{ input: 'pre-rename-lazy-style.css', output: 'renamed-lazy-style', lazy: true },
36-
{ input: 'common-entry-style.css', output: 'common-entry' }
33+
{ input: 'pre-rename-lazy-style.css', output: 'renamed-lazy-style', lazy: true }
3734
];
38-
app['scripts'] = [{ input: 'common-entry-script.js', output: 'common-entry' }];
3935
}))
4036
.then(() => ng('build', '--extract-css'))
4137
// files were created successfully
@@ -44,23 +40,19 @@ export default function () {
4440
.then(() => expectFileToMatch('dist/lazy-style.bundle.css', '.lazy-style'))
4541
.then(() => expectFileToMatch('dist/renamed-style.bundle.css', '.pre-rename-style'))
4642
.then(() => expectFileToMatch('dist/renamed-lazy-style.bundle.css', '.pre-rename-lazy-style'))
47-
.then(() => expectFileToMatch('dist/common-entry.bundle.css', '.common-entry-style'))
48-
.then(() => expectFileToMatch('dist/common-entry.bundle.js', 'common-entry-script'))
4943
// there are no js entry points for css only bundles
5044
.then(() => expectToFail(() => expectFileToExist('dist/style.bundle.js')))
5145
.then(() => expectToFail(() => expectFileToExist('dist/lazy-style.bundle.js')))
5246
.then(() => expectToFail(() => expectFileToExist('dist/renamed-style.bundle.js')))
5347
.then(() => expectToFail(() => expectFileToExist('dist/renamed-lazy-style.bundle.js')))
5448
// index.html lists the right bundles
5549
.then(() => expectFileToMatch('dist/index.html', oneLineTrim`
56-
<link href="common-entry.bundle.css" rel="stylesheet"/>
5750
<link href="styles.bundle.css" rel="stylesheet"/>
5851
<link href="renamed-style.bundle.css" rel="stylesheet"/>
5952
`))
6053
.then(() => expectFileToMatch('dist/index.html', oneLineTrim`
6154
<script type="text/javascript" src="inline.bundle.js"></script>
6255
<script type="text/javascript" src="polyfills.bundle.js"></script>
63-
<script type="text/javascript" src="common-entry.bundle.js"></script>
6456
<script type="text/javascript" src="vendor.bundle.js"></script>
6557
<script type="text/javascript" src="main.bundle.js"></script>
6658
`))
@@ -72,13 +64,10 @@ export default function () {
7264
.then(() => expectFileToMatch('dist/lazy-style.bundle.js', '.lazy-style'))
7365
.then(() => expectFileToMatch('dist/renamed-style.bundle.js', '.pre-rename-style'))
7466
.then(() => expectFileToMatch('dist/renamed-lazy-style.bundle.js', '.pre-rename-lazy-style'))
75-
.then(() => expectFileToMatch('dist/common-entry.bundle.js', '.common-entry-style'))
76-
.then(() => expectFileToMatch('dist/common-entry.bundle.js', 'common-entry-script'))
7767
// index.html lists the right bundles
7868
.then(() => expectFileToMatch('dist/index.html', oneLineTrim`
7969
<script type="text/javascript" src="inline.bundle.js"></script>
8070
<script type="text/javascript" src="polyfills.bundle.js"></script>
81-
<script type="text/javascript" src="common-entry.bundle.js"></script>
8271
<script type="text/javascript" src="styles.bundle.js"></script>
8372
<script type="text/javascript" src="renamed-style.bundle.js"></script>
8473
<script type="text/javascript" src="vendor.bundle.js"></script>

0 commit comments

Comments
 (0)