Skip to content

Commit 74704aa

Browse files
author
benholloway
committed
Merge branch 'feature/incremental-js-build' into feature/upgrade-transforms
Conflicts: npm-shrinkwrap.json package.json tasks/javascript.js
2 parents 114b5f2 + a2cff2e commit 74704aa

File tree

3 files changed

+112
-197
lines changed

3 files changed

+112
-197
lines changed

lib/build/browserify.js

Lines changed: 104 additions & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,126 @@
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

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@
4747
"bower-directory": "latest",
4848
"browser-sync": "latest",
4949
"browserify": "latest",
50+
"browserify-anonymous-labeler": "latest",
5051
"browserify-esprima-tools": "latest",
52+
"browserify-incremental-plugin": "latest",
5153
"browserify-nginject": "latest",
5254
"browserify-transform-tools": "latest",
5355
"chalk": "latest",

0 commit comments

Comments
 (0)