Skip to content

Commit 3633e02

Browse files
committed
feat(@angular/cli): use clean-css to optimize stylesheets
1 parent 215ba9b commit 3633e02

File tree

7 files changed

+130
-16
lines changed

7 files changed

+130
-16
lines changed

package-lock.json

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

package.json

+2-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",
@@ -123,6 +123,7 @@
123123
"@types/semver": "^5.3.30",
124124
"@types/source-map": "^0.5.0",
125125
"@types/webpack": "^3.0.5",
126+
"@types/webpack-sources": "^0.1.3",
126127
"conventional-changelog": "1.1.0",
127128
"dtsgenerator": "^0.9.1",
128129
"eslint": "^3.11.0",

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,102 @@
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', (chunks: Array<Chunk>, callback: () => void) => {
28+
29+
const cleancss = new CleanCSS({
30+
compatibility: 'ie9',
31+
level: 2,
32+
inline: false,
33+
returnPromise: true,
34+
sourceMap: this.options.sourceMap,
35+
});
36+
37+
const files: string[] = [...compilation.additionalChunkAssets];
38+
39+
chunks.forEach(chunk => {
40+
if (chunk.files && chunk.files.length > 0) {
41+
files.push(...chunk.files);
42+
}
43+
});
44+
45+
const actions = files
46+
.filter(file => file.endsWith('.css'))
47+
.map(file => {
48+
const asset = compilation.assets[file];
49+
50+
let content: string;
51+
let map: any;
52+
if (asset.sourceAndMap) {
53+
const sourceAndMap = asset.sourceAndMap();
54+
content = sourceAndMap.source;
55+
map = sourceAndMap.map;
56+
} else {
57+
content = asset.source();
58+
}
59+
60+
return cleancss
61+
.minify(content, map)
62+
.then((output: any) => {
63+
let hasWarnings = false;
64+
if (output.warnings && output.warnings.length > 0) {
65+
compilation.warnings.push(...output.warnings);
66+
hasWarnings = true;
67+
}
68+
69+
if (output.errors && output.errors.length > 0) {
70+
output.errors
71+
.forEach((error: string) => compilation.errors.push(new Error(error)));
72+
return;
73+
}
74+
75+
// generally means invalid syntax so bail
76+
if (hasWarnings && output.stats.minifiedSize === 0) {
77+
return;
78+
}
79+
80+
let newSource;
81+
if (output.sourceMap) {
82+
newSource = new SourceMapSource(
83+
output.styles,
84+
file,
85+
output.sourceMap.toString(),
86+
content,
87+
map,
88+
);
89+
} else {
90+
newSource = new RawSource(output.styles);
91+
}
92+
93+
compilation.assets[file] = newSource;
94+
})
95+
.catch((error: Error) => compilation.errors.push(error));
96+
});
97+
98+
Promise.all(actions).then(() => callback());
99+
});
100+
});
101+
}
102+
}

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 { InsertConcatAssetsWebpackPlugin } from './insert-concat-assets-webpack-plugin';
56
export { NamedLazyChunksWebpackPlugin } from './named-lazy-chunks-webpack-plugin';

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

+1
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ class JsonWebpackSerializer {
203203
this._addImport('webpack.optimize', 'ModuleConcatenationPlugin');
204204
break;
205205
case angularCliPlugins.BaseHrefWebpackPlugin:
206+
case angularCliPlugins.CleanCssWebpackPlugin:
206207
case angularCliPlugins.NamedLazyChunksWebpackPlugin:
207208
case angularCliPlugins.SuppressExtractedTextChunksWebpackPlugin:
208209
this._addImport('@angular/cli/plugins/webpack', plugin.constructor.name);

0 commit comments

Comments
 (0)