Create a Browserify bundler with multiple entries based on a glob pattern. Rebuild incrementally watching for changes, including additions and deletions of files that match the glob pattern.
npm install gulp-browserify-watchify-glob@1.0.1
Create a Browserify bundler with multiple entries based on a glob pattern. Rebuild incrementally watching for changes, including additions and deletions of files that match the glob pattern.
With just browserify, you can define a bundle based on a fixed set of entries.
const testBundler = browserify({
entries: ['test1.js', 'test2.js'],
debug: true,
});
You can build the bundle as often as you want from this definition, but only manually.
testBundler.bundle();
If you combine browserify with gulp, your setup might look like this.
const vinylStream = require('vinyl-source-stream');
const testEntries = ['test1.js', 'test2.js'];
const testBundler = browserify({
entries: testEntries,
debug: true,
});
function bundleTests() {
testBundler.bundle()
.pipe(vinylStream('testBundle.js'))
.pipe(gulp.dest('test-directory'));
}
You can watch for changes in the entries and their dependencies by using watchify. This gives you efficient, automatic, incremental rebuilds.
const testEntries = ['test1.js', 'test2.js'];
const testBundler = browserify({
entries: testEntries,
debug: true,
cache: {},
packageCache: {},
}).plugin('watchify');
function bundleTests() {
return testBundler.bundle()
.pipe(vinylStream('testBundle.js'))
.pipe(gulp.dest('test-directory'));
}
function watch(done) {
testBundler.on('update', bundleTests);
// You don't often see the following lines. They ensure a clean
// exit when you stop the watch task with a keyboard interrupt.
const finish = () => done(null, testBundler.close());
process.once('SIGINT', finish);
process.once('SIGTERM', finish);
}
You might have lots of entries, for example when you have lots of test modules. If this is the case, your entries are best enumerated with a glob.
const glob = require('glob');
const testEntriesGlob = 'src/**/*-test.js';
const testEntries = glob.sync(testEntriesGlob);
// ['src/apple-test.js', 'src/banana-test.js', 'src/cherry/drop-test.js', ...]
Alas, the set of entries becomes fixed again directly after the line above. If you add a test module while the watch task is running, watchify will not include the new module in the bundle. Even worse, if you delete an entry, the build will break.
You could try to work around this by using gulp.watch instead of watchify.
function bundleTests() {
return browserify({
entries: glob.sync(testEntriesGlob),
debug: true,
}).bundle()
.pipe(vinylStream('testBundle.js'))
.pipe(gulp.dest('test-directory'));
}
function watch(done) {
const watcher = gulp.watch(testEntriesGlob, bundleTests);
const finish = () => done(null, watcher.close());
process.once('SIGINT', finish);
process.once('SIGTERM', finish);
}
Unfortunately, this only watches the entries; it doesn't detect changes in the dependencies. To make matters worse, the rebuild is not incremental anymore; bundleTests
redefines and rebuilds the bundle entirely from scratch every time.
gulp-browserify-watchify-glob lets you have the best of both worlds.
const globbedBrowserify = require('gulp-browserify-watchify-glob');
const testBundler = globbedBrowserify({
entries: testEntriesGlob,
debug: true,
});
function bundleTests() {
return testBundler.bundle()
.pipe(vinylStream('testBundle.js'))
.pipe(gulp.dest('test-directory'));
}
function watch(done) {
testBundler.watch(bundleTests)();
const finish = () => done(null, testBundler.close());
process.once('SIGINT', finish);
process.once('SIGTERM', finish);
}
Finally, you can have a dynamic set of entries and rebuild incrementally, whenever an entry or any of its dependencies is modified and whenever an entry is added or deleted.
Install:
yarn add gulp-browserify-watchify-glob -D
# or
npm i gulp-browserify-watchify-glob -D
Import:
const globbedBrowserify = require('gulp-browserify-watchify-glob');
// or
import globbedBrowserify from 'gulp-browserify-watchify-glob';
globbedBrowserify
is a drop-in replacement for the browserify
constructor. It accepts the same options and it returns a browserify
instance that you can invoke the same methods on.
const bundler = globbedBrowserify({
entries: 'src/**/*-test.js',
debug: true,
// cache and packageCache default to {}
}).transform(/* whatever */);
There are two main differences:
entries
option, which may be a glob pattern or an array of glob patterns.watch
method that you should use instead of other watching mechanisms, such as watchify and gulp.watch.The watch
method causes the bundler to start watching all files that match the glob pattern(s) in the entries and all of their dependencies. It accepts two arguments, an update task and a kickoff task.
const wrappedKickoff = bundler.watch(updateTask, kickoffTask);
The kickoff task is optional. If omitted, it is assumed to be the same as the update task. Both tasks should be functions that meet the requirements of a gulp task and both should call bundler.bundle()
internally. The kickoff task should contain the necessary steps for the first build while the update task will be invoked on every update.
function updateTask() {
// main option one: return a stream that starts with the build output
return bundler.bundle()
.pipe(...);
}
function kickoffTask(done) {
// main option two: handle the complete build output with a callback
bundler.bundle((error, buffer) => {
if (error) return done(error);
doSomethingAsyncWith(buffer, done);
});
}
The return value wrappedKickoff
is a wrapper of the kickoff task that still needs to be invoked in order to trigger the first build. This is not done automatically to facilitate async workflows. If you want to trigger the first build immediately, simply invoke the return value:
bundler.watch(updateTask, kickoffTask)();
Once the watch
method has been invoked, the bundler also has a close
method which you can invoke to stop watching the entries glob(s) and the dependencies.
bundler.close();
gulp-browserify-watchify-glob wraps the tasks to prevent race conditions. If you have another (third, fourth...) task that also invokes bundler.bundle()
, wrap it so that builds will not be triggered while a previous build is still in progress.
const wrappedOtherTask = bundler.wrap(otherTask);
Browserify was not designed with this use case in mind. I rely on browserify implementation details in order to make the magic work.