Skip to content

Commit 601c51f

Browse files
committed
feat(@angular/cli): optimize stylesheets after full bundling
1 parent eb4c9e7 commit 601c51f

File tree

6 files changed

+121
-17
lines changed

6 files changed

+121
-17
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@
4747
"autoprefixer": "^6.5.3",
4848
"chalk": "~2.2.0",
4949
"circular-dependency-plugin": "^4.2.1",
50+
"clean-css": "^4.1.9",
5051
"common-tags": "^1.3.1",
5152
"copy-webpack-plugin": "^4.1.1",
5253
"core-object": "^3.1.0",
5354
"css-loader": "^0.28.1",
54-
"cssnano": "^3.10.0",
5555
"denodeify": "^1.2.1",
5656
"ember-cli-string-utils": "^1.0.0",
5757
"enhanced-resolve": "^3.4.1",

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

+6-14
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import {
66
import { extraEntryParser, getOutputHashFormat } from './utils';
77
import { WebpackConfigOptions } from '../webpack-config';
88
import { pluginArgs, postcssArgs } from '../../tasks/eject';
9+
import { CleanCssWebpackPlugin } from '../../plugins/cleancss-webpack-plugin';
910

10-
const cssnano = require('cssnano');
1111
const postcssUrl = require('postcss-url');
1212
const autoprefixer = require('autoprefixer');
1313
const ExtractTextPlugin = require('extract-text-webpack-plugin');
@@ -48,15 +48,6 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
4848
const deployUrl = wco.buildOptions.deployUrl || '';
4949

5050
const postcssPluginCreator = function() {
51-
// safe settings based on: https://github.com/ben-eb/cssnano/issues/358#issuecomment-283696193
52-
const importantCommentRe = /@preserve|@licen[cs]e|[@#]\s*source(?:Mapping)?URL|^!/i;
53-
const minimizeOptions = {
54-
autoprefixer: false, // full pass with autoprefixer is run separately
55-
safe: true,
56-
mergeLonghand: false, // version 3+ should be safe; cssnano currently uses 2.x
57-
discardComments : { remove: (comment: string) => !importantCommentRe.test(comment) }
58-
};
59-
6051
return [
6152
postcssUrl([
6253
{
@@ -87,15 +78,12 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
8778
]),
8879
autoprefixer(),
8980
customProperties({ preserve: true })
90-
].concat(
91-
minimizeCss ? [cssnano(minimizeOptions)] : []
92-
);
81+
];
9382
};
9483
(postcssPluginCreator as any)[postcssArgs] = {
9584
variableImports: {
9685
'autoprefixer': 'autoprefixer',
9786
'postcss-url': 'postcssUrl',
98-
'cssnano': 'cssnano',
9987
'postcss-custom-properties': 'customProperties'
10088
},
10189
variables: { minimizeCss, baseHref, deployUrl }
@@ -225,6 +213,10 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
225213
extraPlugins.push(new SuppressExtractedTextChunksWebpackPlugin());
226214
}
227215

216+
if (minimizeCss) {
217+
extraPlugins.push(new CleanCssWebpackPlugin({ sourceMap: cssSourceMap }));
218+
}
219+
228220
return {
229221
entry: entryPoints,
230222
module: { rules },

packages/@angular/cli/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@
3535
"autoprefixer": "^6.5.3",
3636
"chalk": "~2.2.0",
3737
"circular-dependency-plugin": "^4.2.1",
38+
"clean-css": "^4.1.9",
3839
"common-tags": "^1.3.1",
3940
"copy-webpack-plugin": "^4.1.1",
4041
"core-object": "^3.1.0",
4142
"css-loader": "^0.28.1",
42-
"cssnano": "^3.10.0",
4343
"denodeify": "^1.2.1",
4444
"ember-cli-string-utils": "^1.0.0",
4545
"exports-loader": "^0.6.3",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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 { Compiler } from 'webpack';
9+
import { RawSource, SourceMapSource } from 'webpack-sources';
10+
11+
const CleanCSS = require('clean-css');
12+
13+
interface Chunk {
14+
files: string[];
15+
}
16+
17+
export interface CleanCssWebpackPluginOptions {
18+
sourceMap: boolean;
19+
}
20+
21+
export class CleanCssWebpackPlugin {
22+
23+
constructor(private options: Partial<CleanCssWebpackPluginOptions> = {}) {}
24+
25+
apply(compiler: Compiler): void {
26+
compiler.plugin('compilation', (compilation: any) => {
27+
compilation.plugin('optimize-chunk-assets',
28+
(chunks: Array<Chunk>, callback: (err?: Error) => void) => {
29+
30+
const cleancss = new CleanCSS({
31+
compatibility: 'ie9',
32+
level: 2,
33+
inline: false,
34+
returnPromise: true,
35+
sourceMap: this.options.sourceMap,
36+
});
37+
38+
const files: string[] = [...compilation.additionalChunkAssets];
39+
40+
chunks.forEach(chunk => {
41+
if (chunk.files && chunk.files.length > 0) {
42+
files.push(...chunk.files);
43+
}
44+
});
45+
46+
const actions = files
47+
.filter(file => file.endsWith('.css'))
48+
.map(file => {
49+
const asset = compilation.assets[file];
50+
if (!asset) {
51+
return Promise.resolve();
52+
}
53+
54+
let content: string;
55+
let map: any;
56+
if (asset.sourceAndMap) {
57+
const sourceAndMap = asset.sourceAndMap();
58+
content = sourceAndMap.source;
59+
map = sourceAndMap.map;
60+
} else {
61+
content = asset.source();
62+
}
63+
64+
if (content.length === 0) {
65+
return Promise.resolve();
66+
}
67+
68+
return Promise.resolve()
69+
.then(() => cleancss.minify(content, map))
70+
.then((output: any) => {
71+
let hasWarnings = false;
72+
if (output.warnings && output.warnings.length > 0) {
73+
compilation.warnings.push(...output.warnings);
74+
hasWarnings = true;
75+
}
76+
77+
if (output.errors && output.errors.length > 0) {
78+
output.errors
79+
.forEach((error: string) => compilation.errors.push(new Error(error)));
80+
return;
81+
}
82+
83+
// generally means invalid syntax so bail
84+
if (hasWarnings && output.stats.minifiedSize === 0) {
85+
return;
86+
}
87+
88+
let newSource;
89+
if (output.sourceMap) {
90+
newSource = new SourceMapSource(
91+
output.styles,
92+
file,
93+
output.sourceMap.toString(),
94+
content,
95+
map,
96+
);
97+
} else {
98+
newSource = new RawSource(output.styles);
99+
}
100+
101+
compilation.assets[file] = newSource;
102+
});
103+
});
104+
105+
Promise.all(actions)
106+
.then(() => callback())
107+
.catch(err => callback(err));
108+
});
109+
});
110+
}
111+
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Exports the webpack plugins we use internally.
22
export { BaseHrefWebpackPlugin } from '../lib/base-href-webpack/base-href-webpack-plugin';
3+
export { CleanCssWebpackPlugin, CleanCssWebpackPluginOptions } from './cleancss-webpack-plugin';
34
export { GlobCopyWebpackPlugin, GlobCopyWebpackPluginOptions } from './glob-copy-webpack-plugin';
45
export { NamedLazyChunksWebpackPlugin } from './named-lazy-chunks-webpack-plugin';
56
export { ScriptsWebpackPlugin, ScriptsWebpackPluginOptions } from './scripts-webpack-plugin';

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ class JsonWebpackSerializer {
190190
this._addImport('webpack.optimize', 'ModuleConcatenationPlugin');
191191
break;
192192
case angularCliPlugins.BaseHrefWebpackPlugin:
193+
case angularCliPlugins.CleanCssWebpackPlugin:
193194
case angularCliPlugins.NamedLazyChunksWebpackPlugin:
194195
case angularCliPlugins.ScriptsWebpackPlugin:
195196
case angularCliPlugins.SuppressExtractedTextChunksWebpackPlugin:
@@ -561,7 +562,6 @@ export default Task.extend({
561562
'webpack',
562563
'autoprefixer',
563564
'css-loader',
564-
'cssnano',
565565
'exports-loader',
566566
'file-loader',
567567
'html-webpack-plugin',

0 commit comments

Comments
 (0)