-
Notifications
You must be signed in to change notification settings - Fork 12k
/
Copy pathoptimize-css-webpack-plugin.ts
121 lines (105 loc) · 3.4 KB
/
optimize-css-webpack-plugin.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/**
* @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 * as cssNano from 'cssnano';
import { ProcessOptions, Result } from 'postcss';
import { Compiler, compilation } from 'webpack';
import { RawSource, Source, SourceMapSource } from 'webpack-sources';
export interface OptimizeCssWebpackPluginOptions {
sourceMap: boolean;
test: (file: string) => boolean;
}
function hook(
compiler: Compiler,
action: (
compilation: compilation.Compilation,
chunks: compilation.Chunk[],
) => Promise<void | void[]>,
) {
compiler.hooks.compilation.tap(
'optimize-css-webpack-plugin',
(compilation: compilation.Compilation) => {
compilation.hooks.optimizeChunkAssets.tapPromise('optimize-css-webpack-plugin', chunks =>
action(compilation, chunks),
);
},
);
}
export class OptimizeCssWebpackPlugin {
private readonly _options: OptimizeCssWebpackPluginOptions;
constructor(options: Partial<OptimizeCssWebpackPluginOptions>) {
this._options = {
sourceMap: false,
test: file => file.endsWith('.css'),
...options,
};
}
apply(compiler: Compiler): void {
hook(compiler, (compilation: compilation.Compilation, chunks: compilation.Chunk[]) => {
const files: string[] = [...compilation.additionalChunkAssets];
chunks.forEach(chunk => {
if (chunk.files && chunk.files.length > 0) {
files.push(...chunk.files);
}
});
const actions = files
.filter(file => this._options.test(file))
.map(async file => {
const asset = compilation.assets[file] as Source;
if (!asset) {
return;
}
let content: string;
// tslint:disable-next-line: no-any
let map: any;
if (this._options.sourceMap && asset.sourceAndMap) {
const sourceAndMap = asset.sourceAndMap();
content = sourceAndMap.source;
map = sourceAndMap.map;
} else {
content = asset.source();
}
if (content.length === 0) {
return;
}
const cssNanoOptions: cssNano.CssNanoOptions = {
preset: 'default',
};
const postCssOptions: ProcessOptions = {
from: file,
map: map && { annotation: false, prev: map },
};
const output = await new Promise<Result>((resolve, reject) => {
// the last parameter is not in the typings
// tslint:disable-next-line: no-any
(cssNano.process as any)(content, postCssOptions, cssNanoOptions)
.then(resolve)
.catch(reject);
});
const warnings = output.warnings();
if (warnings.length) {
compilation.warnings.push(...warnings.map(({ text }) => text));
}
let newSource;
if (output.map) {
newSource = new SourceMapSource(
output.css,
file,
// tslint:disable-next-line: no-any
output.map.toString() as any,
content,
map,
);
} else {
newSource = new RawSource(output.css);
}
compilation.assets[file] = newSource;
});
return Promise.all(actions);
});
}
}