@@ -2,34 +2,93 @@ import { Compiler as WebpackCompiler } from "webpack";
2
2
import * as path from "path" ;
3
3
import * as async from "async" ;
4
4
import * as fs from "fs" ;
5
+ import * as postcss from "postcss" ;
5
6
import { Source , RawSource , SourceMapSource , ConcatSource } from "webpack-sources" ;
6
7
import { RawSourceMap } from "source-map" ;
7
8
import * as convertSourceMap from "convert-source-map" ;
8
9
import * as debugGenerator from 'debug' ;
9
10
10
11
const debug = debugGenerator ( "css-blocks:webpack:assets" ) ;
11
12
13
+ export type PostcssProcessor =
14
+ Array < postcss . Plugin < any > >
15
+ | ( ( assetPath : string ) => Array < postcss . Plugin < any > >
16
+ | Promise < Array < postcss . Plugin < any > > > ) ;
17
+
18
+ export type GenericProcessor =
19
+ ( source : Source , assetPath : string ) => Source | Promise < Source > ;
20
+
21
+ export interface PostcssProcessorOption {
22
+ postcss : PostcssProcessor ;
23
+ }
24
+
25
+ export interface GenericProcessorOption {
26
+ processor : GenericProcessor ;
27
+ }
28
+
29
+ export type PostProcessorOption = PostcssProcessorOption | GenericProcessorOption | ( PostcssProcessorOption & GenericProcessorOption ) ;
30
+
31
+ function isPostcssProcessor ( processor : PostProcessorOption ) : processor is PostcssProcessorOption {
32
+ return ! ! ( < PostcssProcessorOption > processor ) . postcss ;
33
+ }
34
+
35
+ function isGenericProcessor ( processor : PostProcessorOption ) : processor is GenericProcessorOption {
36
+ return ! ! ( < GenericProcessorOption > processor ) . processor ;
37
+ }
38
+
39
+ export interface CssSourceOptions {
40
+ /**
41
+ * The name of the chunk to which the asset should belong.
42
+ * If omitted, the asset won't belong to a any chunk. */
43
+ chunk : string | undefined ;
44
+
45
+ /** the source path to the css asset. */
46
+ source : string | string [ ] ;
47
+
48
+ /**
49
+ * Post-process the concatenated file with the specified postcss plugins.
50
+ */
51
+ // TODO: enable
52
+ // postProcess?: PostProcessorOption;
53
+ }
54
+ export interface ConcatenationOptions {
55
+ /**
56
+ * A list of assets to be concatenated.
57
+ */
58
+ sources : Array < string > ;
59
+
60
+ /**
61
+ * When true, the files that are concatenated are left in the build.
62
+ * Defaults to false.
63
+ */
64
+ preserveSourceFiles ?: boolean ;
65
+
66
+ /**
67
+ * Post-process the concatenated file with the specified postcss plugins.
68
+ *
69
+ * If postcss plugins are provided in conjunction with a generic processor
70
+ * the postcss plugins will be ran first.
71
+ */
72
+ postProcess ?: PostProcessorOption ;
73
+ }
74
+
12
75
/**
13
76
* Options for managing CSS assets without javascript imports.
14
77
*/
15
78
export interface CssAssetsOptions {
16
79
/** Maps css files from a source location to a webpack asset location. */
17
80
cssFiles : {
18
- [ assetPath : string ] : string | {
19
- /** The name of the chunk to which the asset should belong. If omitted, the asset won't belong to a any chunk. */
20
- chunk : string | undefined ;
21
- /** the source path to the css asset. */
22
- source : string | string [ ] ;
23
- } ;
81
+ [ assetPath : string ] : string | CssSourceOptions ;
24
82
} ;
25
83
/**
26
84
* Maps several webpack assets to a new concatenated asset and manages their
27
85
* sourcemaps. The concatenated asset will belong to all the chunks to which
28
86
* the assets belonged.
29
87
*/
30
88
concat : {
31
- [ concatAssetPath : string ] : string [ ] ;
89
+ [ concatAssetPath : string ] : string [ ] | ConcatenationOptions ;
32
90
} ;
91
+
33
92
/**
34
93
* When true, any source maps related to the assets are written out as
35
94
* additional files or inline depending on the value of `inlineSourceMaps`.
@@ -42,66 +101,9 @@ export interface CssAssetsOptions {
42
101
inlineSourceMaps : boolean ; // defaults to false
43
102
}
44
103
45
- function assetAsSource ( contents : string , filename : string ) : Source {
46
- let sourcemap : convertSourceMap . SourceMapConverter | undefined ;
47
- if ( / s o u r c e M a p p i n g U R L / . test ( contents ) ) {
48
- sourcemap = convertSourceMap . fromSource ( contents ) ||
49
- convertSourceMap . fromMapFileComment ( contents , path . dirname ( filename ) ) ;
50
- }
51
- if ( sourcemap ) {
52
- let sm : RawSourceMap = sourcemap . toObject ( ) ;
53
- contents = convertSourceMap . removeComments ( contents ) ;
54
- contents = convertSourceMap . removeMapFileComments ( contents ) ;
55
- return new SourceMapSource ( contents , filename , sm ) ;
56
- } else {
57
- return new RawSource ( contents ) ;
58
- }
59
- }
60
- function assetFilesAsSource ( filenames : string [ ] , callback : ( err : Error | undefined , source ?: ConcatSource ) => void ) {
61
- let assetSource = new ConcatSource ( ) ;
62
- let assetFiles = filenames . slice ( ) ;
63
- let eachAssetFile = ( err ?: Error ) => {
64
- if ( err ) {
65
- callback ( err ) ;
66
- } else {
67
- const nextAssetFile = assetFiles . shift ( ) ;
68
- if ( nextAssetFile ) {
69
- processAsset ( nextAssetFile , eachAssetFile ) ;
70
- } else {
71
- callback ( undefined , assetSource ) ;
72
- }
73
- }
74
- } ;
75
- const firstAssetFile = assetFiles . shift ( ) ;
76
- if ( firstAssetFile ) {
77
- processAsset ( firstAssetFile , eachAssetFile ) ;
78
- } else {
79
- callback ( new Error ( "No asset files provided." ) ) ;
80
- }
81
- function processAsset ( assetPath : string , assetCallback : ( err ?: Error ) => void ) {
82
- fs . readFile ( assetPath , "utf-8" , ( err , data ) => {
83
- if ( err ) {
84
- assetCallback ( err ) ;
85
- } else {
86
- assetSource . add ( assetAsSource ( data , assetPath ) ) ;
87
- assetCallback ( ) ;
88
- }
89
- } ) ;
90
- }
91
- }
92
-
93
- function assetFileAsSource ( sourcePath : string , callback : ( err : Error | undefined , source ?: Source ) => void ) {
94
- fs . readFile ( sourcePath , "utf-8" , ( err , contents ) => {
95
- if ( err ) {
96
- callback ( err ) ;
97
- } else {
98
- try {
99
- callback ( undefined , assetAsSource ( contents , sourcePath ) ) ;
100
- } catch ( e ) {
101
- callback ( e ) ;
102
- }
103
- }
104
- } ) ;
104
+ interface SourceAndMap {
105
+ source : string ;
106
+ map ?: RawSourceMap ;
105
107
}
106
108
107
109
export class CssAssets {
@@ -165,13 +167,16 @@ export class CssAssets {
165
167
debug ( "concatenating assets" ) ;
166
168
if ( ! this . options . concat ) return ;
167
169
let concatFiles = Object . keys ( this . options . concat ) ;
168
- concatFiles . forEach ( ( concatFile ) => {
170
+ let postProcessResults = new Array < Promise < void > > ( ) ;
171
+ for ( let concatFile of concatFiles ) {
169
172
let concatSource = new ConcatSource ( ) ;
170
- let inputFiles = this . options . concat [ concatFile ] ;
173
+ let concatenation = this . options . concat [ concatFile ] ;
174
+ let inputFiles = Array . isArray ( concatenation ) ? concatenation : concatenation . sources ;
175
+ let concatenationOptions = Array . isArray ( concatenation ) ? { sources : concatenation } : concatenation ;
171
176
let missingFiles = inputFiles . filter ( f => ( ! compilation . assets [ f ] ) ) ;
172
177
let chunks = new Set < any > ( ) ;
173
178
if ( missingFiles . length === 0 ) {
174
- inputFiles . forEach ( inputFile => {
179
+ for ( let inputFile of inputFiles ) {
175
180
let asset = compilation . assets [ inputFile ] ;
176
181
concatSource . add ( asset ) ;
177
182
let chunksWithInputAsset = compilation . chunks . filter ( ( chunk : any ) => ( < Array < string > > chunk . files ) . indexOf ( inputFile ) >= 0 ) ;
@@ -180,16 +185,35 @@ export class CssAssets {
180
185
let files : string [ ] = chunk . files ;
181
186
chunk . files = files . filter ( file => file !== inputFile ) ;
182
187
} ) ;
183
- delete compilation . assets [ inputFile ] ;
184
- } ) ;
185
- compilation . assets [ concatFile ] = concatSource ;
188
+ if ( ! concatenationOptions . preserveSourceFiles ) {
189
+ delete compilation . assets [ inputFile ] ;
190
+ }
191
+ }
192
+ if ( concatenationOptions . postProcess ) {
193
+ postProcessResults . push ( postProcess ( concatenationOptions . postProcess , concatSource , concatFile ) . then ( source => {
194
+ compilation . assets [ concatFile ] = source ;
195
+ } ) ) ;
196
+ } else {
197
+ compilation . assets [ concatFile ] = concatSource ;
198
+ }
199
+ }
200
+ for ( let chunk of chunks ) {
201
+ let files : Array < string > = chunk . files ;
202
+ if ( files . indexOf ( concatFile ) >= 0 ) continue ;
203
+ files . push ( concatFile ) ;
186
204
}
187
- chunks . forEach ( chunk => {
188
- chunk . files . push ( concatFile ) ;
205
+ }
206
+ if ( postProcessResults . length > 0 ) {
207
+ Promise . all ( postProcessResults ) . then ( ( ) => {
208
+ cb ( ) ;
209
+ } , error => {
210
+ cb ( error ) ;
189
211
} ) ;
190
- } ) ;
191
- cb ( ) ;
212
+ } else {
213
+ cb ( ) ;
214
+ }
192
215
} ) ;
216
+
193
217
// sourcemap output for css files
194
218
// Emit all css files with sourcemaps when the `emitSourceMaps` option
195
219
// is set to true (default). By default source maps are generated as a
@@ -205,18 +229,7 @@ export class CssAssets {
205
229
let assetPaths = Object . keys ( compilation . assets ) . filter ( p => / \. c s s $ / . test ( p ) ) ;
206
230
assetPaths . forEach ( assetPath => {
207
231
let asset = compilation . assets [ assetPath ] ;
208
- let source , map ;
209
- // sourceAndMap is supposedly more efficient when implemented.
210
- if ( asset . sourceAndMap ) {
211
- let sourceAndMap = asset . sourceAndMap ( ) ;
212
- source = sourceAndMap . source ;
213
- map = sourceAndMap . map ;
214
- } else {
215
- source = asset . source ( ) ;
216
- if ( asset . map ) {
217
- map = asset . map ( ) ;
218
- }
219
- }
232
+ let { source, map} = sourceAndMap ( asset ) ;
220
233
if ( map ) {
221
234
let comment ;
222
235
if ( this . options . inlineSourceMaps ) {
@@ -232,4 +245,129 @@ export class CssAssets {
232
245
cb ( ) ;
233
246
} ) ;
234
247
}
235
- }
248
+ }
249
+
250
+ function assetAsSource ( contents : string , filename : string ) : Source {
251
+ let sourcemap : convertSourceMap . SourceMapConverter | undefined ;
252
+ if ( / s o u r c e M a p p i n g U R L / . test ( contents ) ) {
253
+ sourcemap = convertSourceMap . fromSource ( contents ) ||
254
+ convertSourceMap . fromMapFileComment ( contents , path . dirname ( filename ) ) ;
255
+ }
256
+ if ( sourcemap ) {
257
+ let sm : RawSourceMap = sourcemap . toObject ( ) ;
258
+ contents = convertSourceMap . removeComments ( contents ) ;
259
+ contents = convertSourceMap . removeMapFileComments ( contents ) ;
260
+ return new SourceMapSource ( contents , filename , sm ) ;
261
+ } else {
262
+ return new RawSource ( contents ) ;
263
+ }
264
+ }
265
+
266
+ function assetFilesAsSource ( filenames : string [ ] , callback : ( err : Error | undefined , source ?: ConcatSource ) => void ) {
267
+ let assetSource = new ConcatSource ( ) ;
268
+ let assetFiles = filenames . slice ( ) ;
269
+ let eachAssetFile = ( err ?: Error ) => {
270
+ if ( err ) {
271
+ callback ( err ) ;
272
+ } else {
273
+ const nextAssetFile = assetFiles . shift ( ) ;
274
+ if ( nextAssetFile ) {
275
+ processAsset ( nextAssetFile , eachAssetFile ) ;
276
+ } else {
277
+ callback ( undefined , assetSource ) ;
278
+ }
279
+ }
280
+ } ;
281
+ const firstAssetFile = assetFiles . shift ( ) ;
282
+ if ( firstAssetFile ) {
283
+ processAsset ( firstAssetFile , eachAssetFile ) ;
284
+ } else {
285
+ callback ( new Error ( "No asset files provided." ) ) ;
286
+ }
287
+ function processAsset ( assetPath : string , assetCallback : ( err ?: Error ) => void ) {
288
+ fs . readFile ( assetPath , "utf-8" , ( err , data ) => {
289
+ if ( err ) {
290
+ assetCallback ( err ) ;
291
+ } else {
292
+ assetSource . add ( assetAsSource ( data , assetPath ) ) ;
293
+ assetCallback ( ) ;
294
+ }
295
+ } ) ;
296
+ }
297
+ }
298
+
299
+ function assetFileAsSource ( sourcePath : string , callback : ( err : Error | undefined , source ?: Source ) => void ) {
300
+ fs . readFile ( sourcePath , "utf-8" , ( err , contents ) => {
301
+ if ( err ) {
302
+ callback ( err ) ;
303
+ } else {
304
+ try {
305
+ callback ( undefined , assetAsSource ( contents , sourcePath ) ) ;
306
+ } catch ( e ) {
307
+ callback ( e ) ;
308
+ }
309
+ }
310
+ } ) ;
311
+ }
312
+
313
+ function sourceAndMap ( asset : Source ) : SourceAndMap {
314
+ // sourceAndMap is supposedly more efficient when implemented.
315
+ if ( asset . sourceAndMap ) {
316
+ return asset . sourceAndMap ( ) ;
317
+ } else {
318
+ let source = asset . source ( ) ;
319
+ let map : RawSourceMap | undefined = undefined ;
320
+ if ( asset . map ) {
321
+ map = asset . map ( ) ;
322
+ }
323
+ return { source, map } ;
324
+ }
325
+ }
326
+
327
+ function makePostcssProcessor (
328
+ plugins : PostcssProcessor
329
+ ) : GenericProcessor {
330
+ return ( asset : Source , assetPath : string ) => {
331
+ let { source, map } = sourceAndMap ( asset ) ;
332
+ let pluginsPromise : Promise < Array < postcss . Plugin < any > > > ;
333
+ if ( typeof plugins === "function" ) {
334
+ pluginsPromise = Promise . resolve ( plugins ( assetPath ) ) ;
335
+ } else {
336
+ if ( plugins . length > 0 ) {
337
+ pluginsPromise = Promise . resolve ( plugins ) ;
338
+ } else {
339
+ return Promise . resolve ( asset ) ;
340
+ }
341
+ }
342
+ return pluginsPromise . then ( plugins => {
343
+ let processor = postcss ( plugins ) ;
344
+ let result = processor . process ( source , {
345
+ to : assetPath ,
346
+ map : { prev : map , inline : false , annotation : false }
347
+ } ) ;
348
+
349
+ return result . then ( ( result ) => {
350
+ return new SourceMapSource ( result . css , assetPath , result . map . toJSON ( ) , source , map ) ;
351
+ } ) ;
352
+ } ) ;
353
+ } ;
354
+ }
355
+
356
+ function process ( processor : GenericProcessor , asset : Source , assetPath : string ) {
357
+ return Promise . resolve ( processor ( asset , assetPath ) ) ;
358
+ }
359
+
360
+ function postProcess ( option : PostProcessorOption , asset : Source , assetPath : string ) : Promise < Source > {
361
+ let promise : Promise < Source > ;
362
+ if ( isPostcssProcessor ( option ) ) {
363
+ promise = process ( makePostcssProcessor ( option . postcss ) , asset , assetPath ) ;
364
+ } else {
365
+ promise = Promise . resolve ( asset ) ;
366
+ }
367
+ if ( isGenericProcessor ( option ) ) {
368
+ promise = promise . then ( asset => {
369
+ return process ( option . processor , asset , assetPath ) ;
370
+ } ) ;
371
+ }
372
+ return promise ;
373
+ }
0 commit comments