@@ -11,6 +11,10 @@ var mkdirp = require('mkdirp');
1111var srcURL = require ( 'source-map-url' ) ;
1212var MatcherCollection = require ( 'matcher-collection' ) ;
1313var debug = require ( 'debug' ) ( 'broccoli-uglify-sourcemap' ) ;
14+ var queue = require ( 'async-promise-queue' ) ;
15+ var workerpool = require ( 'workerpool' ) ;
16+
17+ var processFile = require ( './lib/process-file' ) ;
1418
1519module . exports = UglifyWriter ;
1620
@@ -19,6 +23,8 @@ UglifyWriter.prototype.constructor = UglifyWriter;
1923
2024const silent = process . argv . indexOf ( '--silent' ) !== - 1 ;
2125
26+ const worker = queue . async . asyncify ( ( doWork ) => doWork ( ) ) ;
27+
2228function UglifyWriter ( inputNodes , options ) {
2329 if ( ! ( this instanceof UglifyWriter ) ) {
2430 return new UglifyWriter ( inputNodes , options ) ;
@@ -34,6 +40,14 @@ function UglifyWriter (inputNodes, options) {
3440 } ,
3541 } ) ;
3642
43+ // consumers of this plugin can opt-in to async and concurrent behavior
44+ // TODO docs in the README
45+ this . async = ( this . options . async === true ) ;
46+ this . concurrency = this . options . concurrency || Number ( process . env . JOBS ) || Math . max ( require ( 'os' ) . cpus ( ) . length - 1 , 1 ) ;
47+
48+ // create a worker pool using an external worker script
49+ this . pool = workerpool . pool ( path . join ( __dirname , 'lib' , 'worker.js' ) , { maxWorkers : this . concurrency } ) ;
50+
3751 this . inputNodes = inputNodes ;
3852
3953 var exclude = this . options . exclude ;
@@ -53,6 +67,9 @@ var MatchNothing = {
5367UglifyWriter . prototype . build = function ( ) {
5468 var writer = this ;
5569
70+ // when options.async === true, allow processFile() operations to complete asynchronously
71+ var pendingWork = [ ] ;
72+
5673 this . inputPaths . forEach ( function ( inputPath ) {
5774 walkSync ( inputPath ) . forEach ( function ( relativePath ) {
5875 if ( relativePath . slice ( - 1 ) === '/' ) {
@@ -64,7 +81,15 @@ UglifyWriter.prototype.build = function () {
6481 mkdirp . sync ( path . dirname ( outFile ) ) ;
6582
6683 if ( relativePath . slice ( - 3 ) === '.js' && ! writer . excludes . match ( relativePath ) ) {
67- writer . processFile ( inFile , outFile , relativePath , writer . outputPath ) ;
84+ // wrap this in a function so it doesn't actually run yet, and can be throttled
85+ var uglifyOperation = function ( ) {
86+ return writer . processFile ( inFile , outFile , relativePath , writer . outputPath ) ;
87+ } ;
88+ if ( writer . async ) {
89+ pendingWork . push ( uglifyOperation ) ;
90+ return ;
91+ }
92+ return uglifyOperation ( ) ;
6893 } else if ( relativePath . slice ( - 4 ) === '.map' ) {
6994 if ( writer . excludes . match ( relativePath . slice ( 0 , - 4 ) + '.js' ) ) {
7095 // ensure .map files for excluded JS paths are also copied forward
@@ -77,70 +102,24 @@ UglifyWriter.prototype.build = function () {
77102 } ) ;
78103 } ) ;
79104
80- return this . outputPath ;
105+ return queue ( worker , pendingWork , writer . concurrency )
106+ . then ( ( /* results */ ) => {
107+ // files are finished processing, shut down the workers
108+ writer . pool . terminate ( ) ;
109+ return writer . outputPath ;
110+ } )
111+ . catch ( ( e ) => {
112+ // make sure to shut down the workers on error
113+ writer . pool . terminate ( ) ;
114+ throw e ;
115+ } ) ;
81116} ;
82117
83118UglifyWriter . prototype . processFile = function ( inFile , outFile , relativePath , outDir ) {
84- var src = fs . readFileSync ( inFile , 'utf-8' ) ;
85- var mapName = path . basename ( outFile ) . replace ( / \. j s $ / , '' ) + '.map' ;
86-
87- var mapDir ;
88- if ( this . options . sourceMapDir ) {
89- mapDir = path . join ( outDir , this . options . sourceMapDir ) ;
90- } else {
91- mapDir = path . dirname ( path . join ( outDir , relativePath ) ) ;
92- }
93-
94- let options = defaults ( { } , this . options . uglify ) ;
95- if ( options . sourceMap ) {
96- let filename = path . basename ( inFile ) ;
97- let url = this . options . sourceMapDir ? '/' + path . join ( this . options . sourceMapDir , mapName ) : mapName ;
98-
99- let sourceMap = { filename, url } ;
100-
101- if ( srcURL . existsIn ( src ) ) {
102- let url = srcURL . getFrom ( src ) ;
103- let sourceMapPath = path . join ( path . dirname ( inFile ) , url ) ;
104- if ( fs . existsSync ( sourceMapPath ) ) {
105- sourceMap . content = JSON . parse ( fs . readFileSync ( sourceMapPath ) ) ;
106- } else if ( ! silent ) {
107- console . warn ( `[WARN] (broccoli-uglify-sourcemap) "${ url } " referenced in "${ relativePath } " could not be found` ) ;
108- }
109- }
110-
111- options = defaults ( options , { sourceMap } ) ;
112- }
113-
114- var start = new Date ( ) ;
115- debug ( '[starting]: %s %dKB' , relativePath , ( src . length / 1000 ) ) ;
116- var result = UglifyJS . minify ( src , options ) ;
117- var end = new Date ( ) ;
118- var total = end - start ;
119- if ( total > 20000 && ! silent ) {
120- console . warn ( '[WARN] (broccoli-uglify-sourcemap) Minifying: `' + relativePath + '` took: ' + total + 'ms (more than 20,000ms)' ) ;
121- }
122-
123- if ( result . error ) {
124- result . error . filename = relativePath ;
125- throw result . error ;
126- }
127-
128- debug ( '[finished]: %s %dKB in %dms' , relativePath , ( result . code . length / 1000 ) , total ) ;
129-
130- if ( options . sourceMap ) {
131- var newSourceMap = JSON . parse ( result . map ) ;
132-
133- newSourceMap . sources = newSourceMap . sources . map ( function ( path ) {
134- // If out output file has the same name as one of our original
135- // sources, they will shadow eachother in Dev Tools. So instead we
136- // alter the reference to the upstream file.
137- if ( path === relativePath ) {
138- path = path . replace ( / \. j s $ / , '-orig.js' ) ;
139- }
140- return path ;
141- } ) ;
142- mkdirp . sync ( mapDir ) ;
143- fs . writeFileSync ( path . join ( mapDir , mapName ) , JSON . stringify ( newSourceMap ) ) ;
119+ // don't run this in the workerpool if concurrency is disabled (can set JOBS <= 1)
120+ if ( this . async && this . concurrency > 1 ) {
121+ // each of these arguments is a string, which can be sent to the worker process as-is
122+ return this . pool . exec ( 'processFileParallel' , [ inFile , outFile , relativePath , outDir , silent , this . options ] ) ;
144123 }
145- fs . writeFileSync ( outFile , result . code ) ;
124+ return processFile ( inFile , outFile , relativePath , outDir , silent , this . options ) ;
146125} ;
0 commit comments