Skip to content

Commit 6efd5b7

Browse files
clydinfilipesilva
authored andcommitted
feat(@angular/cli): optimize stylesheets after full bundling
1 parent 90e2e80 commit 6efd5b7

File tree

6 files changed

+121
-17
lines changed

6 files changed

+121
-17
lines changed

package.json

Lines changed: 1 addition & 1 deletion
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

Lines changed: 6 additions & 14 deletions
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');
@@ -45,15 +45,6 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
4545
const deployUrl = wco.buildOptions.deployUrl || '';
4646

4747
const postcssPluginCreator = function() {
48-
// safe settings based on: https://github.com/ben-eb/cssnano/issues/358#issuecomment-283696193
49-
const importantCommentRe = /@preserve|@licen[cs]e|[@#]\s*source(?:Mapping)?URL|^!/i;
50-
const minimizeOptions = {
51-
autoprefixer: false, // full pass with autoprefixer is run separately
52-
safe: true,
53-
mergeLonghand: false, // version 3+ should be safe; cssnano currently uses 2.x
54-
discardComments : { remove: (comment: string) => !importantCommentRe.test(comment) }
55-
};
56-
5748
return [
5849
postcssUrl([
5950
{
@@ -84,15 +75,12 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
8475
]),
8576
autoprefixer(),
8677
customProperties({ preserve: true })
87-
].concat(
88-
minimizeCss ? [cssnano(minimizeOptions)] : []
89-
);
78+
];
9079
};
9180
(postcssPluginCreator as any)[postcssArgs] = {
9281
variableImports: {
9382
'autoprefixer': 'autoprefixer',
9483
'postcss-url': 'postcssUrl',
95-
'cssnano': 'cssnano',
9684
'postcss-custom-properties': 'customProperties'
9785
},
9886
variables: { minimizeCss, baseHref, deployUrl }
@@ -222,6 +210,10 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
222210
extraPlugins.push(new SuppressExtractedTextChunksWebpackPlugin());
223211
}
224212

213+
if (minimizeCss) {
214+
extraPlugins.push(new CleanCssWebpackPlugin({ sourceMap: cssSourceMap }));
215+
}
216+
225217
return {
226218
entry: entryPoints,
227219
module: { rules },

packages/@angular/cli/package.json

Lines changed: 1 addition & 1 deletion
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",
Lines changed: 111 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 1 deletion
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:
@@ -565,7 +566,6 @@ export default Task.extend({
565566
'webpack',
566567
'autoprefixer',
567568
'css-loader',
568-
'cssnano',
569569
'exports-loader',
570570
'file-loader',
571571
'html-webpack-plugin',

0 commit comments

Comments
 (0)