1+ /**
2+ * A factory for gulp plugins that share a common cache and are multiton for composition root files.
3+ */
14'use strict' ;
25
3- var fs = require ( 'fs' ) ,
4- path = require ( 'path' ) ;
5-
6- var through = require ( 'through2' ) ,
7- q = require ( 'q' ) ,
8- merge = require ( 'lodash.merge' ) ,
9- transformTools = require ( 'browserify-transform-tools' ) ,
10- browserify = require ( 'browserify' ) ,
11- convert = require ( 'convert-source-map' ) ,
12- gutil = require ( 'gulp-util' ) ,
13- slash = require ( 'gulp-slash' ) ,
14- bowerDir = require ( 'bower-directory' ) ;
6+ var fs = require ( 'fs' ) ,
7+ path = require ( 'path' ) ;
8+
9+ var through = require ( 'through2' ) ,
10+ q = require ( 'q' ) ,
11+ merge = require ( 'lodash.merge' ) ,
12+ transformTools = require ( 'browserify-transform-tools' ) ,
13+ labelerPlugin = require ( 'browserify-anonymous-labeler' ) ,
14+ browserifyIncremental = require ( 'browserify-incremental-plugin' ) ,
15+ browserify = require ( 'browserify' ) ,
16+ convert = require ( 'convert-source-map' ) ,
17+ gutil = require ( 'gulp-util' ) ,
18+ slash = require ( 'gulp-slash' ) ,
19+ bowerDir = require ( 'bower-directory' ) ;
1520
1621/**
17- * Create a context that (by default) will share a cache.
18- * @returns {{create:function} A context with shared cache
22+ * Create a context that will share cache and options.
23+ * @param {object } opt A hash of options for browserify
24+ * @returns {{each:function, all:function} A set of gulp plugins that operate with the given options
1925 */
20- function init ( ) {
26+ function create ( opt ) {
2127
2228 // share a common cache
2329 var caches = {
2430 cache : { } ,
2531 packageCache : { }
2632 } ;
27- var internalCache = { } ;
2833
29- // complete
34+ // create a shared context for incremental compile
35+ var incrementalPlugin = browserifyIncremental . pluginFactory ( ) ;
36+
37+ // user options override defaults but we always use debug mode and our own cache
38+ var options = merge ( {
39+ anonymous : false ,
40+ bowerRelative : false ,
41+ projectRelative : false ,
42+ transforms : [ ] ,
43+ sourceMapBase : null
44+ } , opt , caches , {
45+ debug : true
46+ } ) ;
47+
48+ // multiton instances within this collection
49+ var instances = { } ;
50+
51+ // return some gulp plugins
3052 return {
31- create : create
53+ each : each ,
54+ all : all
3255 } ;
3356
3457 /**
35- * Create a context that will share options.
36- * @param {object } opt A hash of options for browserify
37- * @returns {{each:function, all:function} A set of gulp plugins that operate with the given options
58+ * A stream that produces a bundle for each file in the stream
59+ * @returns {stream.Through }
3860 */
39- function create ( opt ) {
40-
41- // user options override defaults but we always use debug mode and our own cache
42- var options = merge ( {
43- anonymous : false ,
44- bowerRelative : false ,
45- projectRelative : false ,
46- transforms : [ ] ,
47- sourceMapBase : null
48- } , opt , caches , {
49- debug : true
50- } ) ;
51-
52- // multiton instances within this collection
53- var instances = { } ;
54-
55- // return some gulp plugins
56- return {
57- each : each ,
58- all : all
59- } ;
60-
61- /**
62- * A stream that produces a bundle for each file in the stream
63- * @returns {stream.Through }
64- */
65- function each ( ) {
66- var messages = [ ] ;
67-
68- function transform ( file , encoding , done ) {
69- /* jshint validthis:true */
70- getInstance ( file . path )
71- . bundle ( this , file . relative )
72- . catch ( function onError ( errors ) {
73- messages . push . apply ( messages , errors ) ;
74- } )
75- . finally ( done ) ;
76- }
77-
78- function flush ( done ) {
79- printMessages ( messages ) ;
80- done ( ) ;
81- }
82-
83- return through . obj ( transform , flush ) ;
61+ function each ( ) {
62+ var messages = [ ] ;
63+
64+ function transform ( file , encoding , done ) {
65+ /* jshint validthis:true */
66+ getInstance ( file . path )
67+ . bundle ( this , file . relative )
68+ . catch ( function onError ( errors ) {
69+ messages . push . apply ( messages , errors ) ;
70+ } )
71+ . finally ( done ) ;
8472 }
8573
86- /**
87- * A stream that produces a bundle containing all files in the stream
88- * @param {string } outPath A relative path for the output file
89- * @returns {stream.Through }
90- */
91- function all ( outPath ) {
92- var files = [ ] ;
74+ function flush ( done ) {
75+ printMessages ( messages ) ;
76+ done ( ) ;
77+ }
9378
94- function transform ( file , encoding , done ) {
95- files . push ( file . path ) ;
96- done ( ) ;
97- }
79+ return through . obj ( transform , flush ) ;
80+ }
9881
99- function flush ( done ) {
100- /* jshint validthis:true */
101- if ( files . length ) {
102- getInstance ( files )
103- . bundle ( this , outPath )
104- . catch ( printMessages )
105- . finally ( done ) ;
106- } else {
107- done ( ) ; // no files
108- }
109- }
82+ /**
83+ * A stream that produces a bundle containing all files in the stream
84+ * @param {string } outPath A relative path for the output file
85+ * @returns {stream.Through }
86+ */
87+ function all ( outPath ) {
88+ var files = [ ] ;
11089
111- return through . obj ( transform , flush ) ;
90+ function transform ( file , encoding , done ) {
91+ files . push ( file . path ) ;
92+ done ( ) ;
11293 }
11394
114- /**
115- * Get an instance of the multiton that match the given files in any order
116- * @param {Array.<string>|string } fileOrFiles A file path or array thereof
117- * @returns {{bundle: function} } An instance for the given files
118- */
119- function getInstance ( fileOrFiles ) {
120- var files = [ ] . concat ( fileOrFiles ) ;
121- var key = JSON . stringify ( files . sort ( ) ) ;
122- if ( key in instances ) {
123- return instances [ key ] ;
95+ function flush ( done ) {
96+ /* jshint validthis:true */
97+ if ( files . length ) {
98+ getInstance ( files )
99+ . bundle ( this , outPath )
100+ . catch ( printMessages )
101+ . finally ( done ) ;
124102 } else {
125- var instance = instances [ key ] = createInstance ( files , options ) ;
126- return instance ;
103+ done ( ) ; // no files
127104 }
128105 }
106+
107+ return through . obj ( transform , flush ) ;
108+ }
109+
110+ /**
111+ * Get an instance of the multiton that match the given files in any order
112+ * @param {Array.<string>|string } fileOrFiles A file path or array thereof
113+ * @returns {{bundle: function} } An instance for the given files
114+ */
115+ function getInstance ( fileOrFiles ) {
116+ var files = [ ] . concat ( fileOrFiles ) ;
117+ var key = JSON . stringify ( files . sort ( ) ) ;
118+ if ( key in instances ) {
119+ return instances [ key ] ;
120+ } else {
121+ var instance = instances [ key ] = createInstance ( files ) ;
122+ return instance ;
123+ }
129124 }
130125
131126 /**
@@ -134,17 +129,19 @@ function init() {
134129 * @param options A hash of options for both browserify and internal settings
135130 * @returns {function } a bundle method
136131 */
137- function createInstance ( files , options ) {
132+ function createInstance ( files ) {
138133
139134 // browserify must be debug mode
140135 var browserifyOpts = merge ( { } , options ) ;
141136
142137 // create bundler
143138 var bundler = browserify ( browserifyOpts ) ;
144139
145- // must setup pipeline on every reset
146- bundler . on ( 'reset' , setupPipeline ) ;
147- setupPipeline ( ) ;
140+ // add plugins
141+ if ( options . anonymous ) {
142+ bundler . plugin ( labelerPlugin ) ;
143+ }
144+ bundler . plugin ( incrementalPlugin ) ;
148145
149146 // transforms with possible options
150147 // transform, [opt], transform, [opt], ...
@@ -168,24 +165,6 @@ function init() {
168165 bundle : bundle
169166 } ;
170167
171- /**
172- * Setup the browserify pipeline
173- */
174- function setupPipeline ( ) {
175-
176- // ensure anonymous module paths when we are minifying
177- if ( options . anonymous ) {
178- bundler . pipeline
179- . get ( 'label' )
180- . push ( anonymousLabeler ( ) ) ;
181- }
182-
183- // populate the module-deps cache to achieve incremental compile
184- var deps = bundler . pipeline . get ( 'deps' ) ;
185- deps . push ( populateCache ( deps . _streams [ 0 ] . cache ) ) ;
186- // TODO this cache is inexplicably different from options.cache
187- }
188-
189168 /**
190169 * Compile any number of files into a bundle
191170 * @param {stream.Through } stream A stream to push files to
@@ -274,7 +253,7 @@ function init() {
274253 * If the file path is outside the project directory then just return its name.
275254 * @param {string } filePath The input path string
276255 * @param {number } An index for <code>Array.map()</code> type operations
277- * @param {object } The array for <code>Array.map()</code> type operations
256+ * @param {Array } The array for <code>Array.map()</code> type operations
278257 * @return {string } The transformed file path
279258 */
280259 function rootRelative ( filePath , i , array ) {
@@ -289,73 +268,10 @@ function init() {
289268 }
290269 return result ;
291270 }
292-
293- /**
294- * A pipeline 'deps' stage that populates cache for incremental compile.
295- * Called on fully transformed row but only when there is no cache hit.
296- * @param cache The cache used by module-deps
297- * @returns {stream.Through } a through stream
298- */
299- function populateCache ( cache ) {
300- function transform ( row , encoding , done ) {
301- /* jshint validthis:true */
302- var filename = row . file ;
303-
304- // set the new transformed row output
305- internalCache [ filename ] = {
306- input : fs . readFileSync ( filename ) . toString ( ) ,
307- output : {
308- id : filename ,
309- source : row . source ,
310- deps : merge ( { } , row . deps ) ,
311- file : filename
312- }
313- } ;
314-
315- // we need to use a getter as it is the only hook at which we can perform comparison
316- // getters cannot be redefined so we create on first access and retain, hence the need
317- // for the internal cache to store the value above
318- if ( ! cache . hasOwnProperty ( filename ) ) {
319- Object . defineProperty ( cache , filename , {
320- get : function ( ) {
321- // file read and comparison is in the order of 100us
322- var cached = internalCache [ filename ] ;
323- var input = fs . readFileSync ( filename ) . toString ( ) ;
324- var isMatch = ( cached . input === input ) ;
325- return isMatch ? cached . output : undefined ;
326- }
327- } ) ;
328- }
329-
330- // complete
331- this . push ( row ) ;
332- done ( ) ;
333- }
334- return through . obj ( transform ) ;
335- }
336271 }
337272}
338273
339- module . exports = init ;
340-
341- /**
342- * A pipeline labeler that ensures that final file names are anonymousd in the final output
343- * @returns {stream.Through } A through stream for the labelling stage
344- */
345- function anonymousLabeler ( ) {
346- return through . obj ( function ( row , enc , next ) {
347- Object . keys ( row . deps ) . forEach ( function ( key ) {
348- var value = row . deps [ key ] ;
349- row . deps [ String ( value ) ] = value ;
350- row . source = row . source
351- . split ( key )
352- . join ( value ) ;
353- delete row . deps [ key ] ;
354- } ) ;
355- this . push ( row ) ;
356- next ( ) ;
357- } ) ;
358- }
274+ module . exports = create ;
359275
360276/**
361277 * A require transform that simulates bower components (and optionally current project) as node modules
0 commit comments