browserify-require-async
v0.4.0
Published
Browserify transform to handle require.async calls.
Downloads
108
Readme
browserify-require-async
Browserify transform to handle require.async
calls.
Highly experimental.
Install
npm install browserify-require-async --save
Usage
Following application will require ./foo
with standard mechanism, but modules path
, url
, querystring
and ./bar
will be required asynchronously and parsed with provided configuration options.
Requiring path
provides you with the error callback so you can handle any potential errors.
var foo = require('./foo');
require.async('path', function ( path ) {
path.join('foo', 'bar');
}, function ( err ) {
console.error(err);
});
require.async(['url', 'querystring'], function ( url, qs ) {
var parsedUrl = url.parse('http://example.com');
var parsedQs = qs.parse('foo=1&bar=2');
});
require.async('./bar', function ( bar ) {
// Do something with "bar"
});
Transform
package.json
{
"browserify": {
"transform": [
"browserify-require-async"
]
},
"browserify-require-async": {
"outputDir": "/"
}
}
Some configuration options like custom output filename can’t be set with package.json
configuration, so it’s best to define it with Node module.
Node module
var browserify = require('browserify');
var bra = require('browserify-require-async');
var b = browserify('./index.js');
b.transform(bra, {
// Custom config
});
b.bundle().pipe(fs.createWriteStream('./bundle.js'));
API
url
Type: String
Default: /
Base URL for every asynchronously loaded module.
outputDir
Type: String
Default:
Directory where asynchronously loaded modules will be written.
setOutputFile
Type: Function
Returns: String
Set file name of the module. By default, file name is created as MD5 hash of filename and last modified time.
| Argument | Type | Description |
| --- | --- | --- |
| hash
| String
| MD5 hash of filename and last modified time. |
| opts
| Object
| File and directory information. |
opts
| Argument | Type | Description |
| --- | --- | --- |
| inputDir
| String
| Input directory. |
| inputFile
| String
| Input file. |
extensions
Type: Array
Default: ['.js']
List of file extensions which will be considered when parsing module content.
exclude
Type: Array
Default: ['**/node_modules/**']
List of multimatch expressions (files) which will be exluded from parsing. Useful when you use recursive parsing or you want to exlude node_modules
files.
By default, all files inside node_modules
(local or global) will be excluded, as per default Browserify behavior. If you want to operate on some module inside node_modules
, you should explicitly declare it (e.g. to operate on files for module foo
, use !**/node_modules/foo/**
; note that using only that value will override default behavior, to have default and new behavior, you should concatenate arrays/values).
looseParseMode
Type: Boolean
Default: false
By default, transform will use default settings when parsing files with acorn (through falafel). Sometimes there are files with specific syntax errors which can be adjusted with acorn’s loose mode. Setting this option to true will first use default settings, and if those settings fail, it will try to use loose mode settings, and if that fails, it will inform you of error which caused failed parsing.
rethrowError
Type: Boolean
Default: false
By default, loader expects you to handle errors in error callback and will "swallow" errors if they are not properly handled. Set this option to true
to rethrow error and show those errors in e.g. your web console (useful for testing purposes).
setup
Type: Function
Returns: Browserify instance
By default, transform will setup some minimum requirements for asynchronous module Browserify instance (external require and such). This callback is useful if you need to define some custom transforms, requires, plugins and any other feature provided with Browserify.
| Argument | Type | Description |
| --- | --- | --- |
| instance
| Browserify
| Browserify instance, with some original instance options applied (debug
). |
| opts
| Object
| File and directory information. |
opts
| Argument | Type | Description |
| --- | --- | --- |
| inputDir
| String
| Input directory. |
| inputFile
| String
| Input file. |
| outputDir
| String
| Output directory. |
| outputFile
| String
| Output file. |
bundle
Type: Function
Returns: Optionally Stream
By default, transform will use standard Browserify bundling and writing to file system. This callback is useful if you need to do some postprocessing on bundle stream such as running Gulp tasks.
If you return Stream
, transform will write it to proper output location. Otherwise, you can handle writing yourself with standard file system modules or with task runner such as Gulp.
| Argument | Type | Description |
| --- | --- | --- |
| bundle
| Browserify
| Browserify instance, with some original instance options applied (debug
). |
| opts
| Object
| File and directory information. |
opts
| Argument | Type | Description |
| --- | --- | --- |
| inputDir
| String
| Input directory. |
| inputFile
| String
| Input file. |
| outputDir
| String
| Output directory. |
| outputFile
| String
| Output file. |
Examples
Transform
Custom instance setup.
var browserify = require('browserify');
var cssify = require('cssify');
var bra = require('browserify-require-async');
var b = browserify('./index.js');
b.transform(bra, {
setup: function () {
var b = browserify();
b.transform(cssify);
return b;
}
});
b.bundle().pipe(fs.createWriteStream('./bundle.js'));
Custom bundle setup.
var browserify = require('browserify');
var bra = require('browserify-require-async');
var gulp = require('gulp');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
var uglify = require('gulp-uglify');
var b = browserify('./index.js');
b.transform(bra, {
bundle: function ( b, opts ) {
b.bundle()
.pipe(source(opts.outputFile))
.pipe(buffer())
.pipe(uglify())
.pipe(gulp.dest(opts.outputDir));
}
});
b.bundle().pipe(fs.createWriteStream('./bundle.js'));
Returning Stream from custom bundle. If the stream is Vinyl stream (like when you use Gulp tasks), you need to convert it to standard text stream.
var browserify = require('browserify');
var bra = require('browserify-require-async');
var gulp = require('gulp');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
var uglify = require('gulp-uglify');
var vinylToStream = require('vinyl-to-stream');
var b = browserify('./index.js');
b.transform(bra, {
bundle: function ( b, opts ) {
return b.bundle()
.pipe(source(opts.outputFile))
.pipe(buffer())
.pipe(uglify())
.pipe(vinylToStream())
}
});
b.bundle().pipe(fs.createWriteStream('./bundle.js'));
Node usage
Node doesn’t support require.async
so if you want to have universal JavaScript code, use polyfill.
Caveats
First level only
Transform is applied only on first level instances. If you have asynchronous module requests inside asynchronous module requests, you must explictly apply transformation. One way to do it is to make it recursive.
var browserify = require('browserify');
var bra = require('browserify-require-async');
var config = [bra, {
setup: function () {
var b = browserify();
b.transform.apply(b, config);
return b;
}
}];
var b = browserify('./index.js');
b.transform.apply(b, config);
Usage with bundle-collapser
If you’re using bundle-collapser, local bundle requires won’t be properly collapsed and you will receive errors. Unfortunately, bundle-collapse in its current iteration doesn’t provide us with the option of setting custom parsing mecahnism, but I’m maintaining a fork which can do just that and you can use it.
If you find this useful, consider upvoting issue on upstream so it can be merged!
Here’s definition for asynchronous module collapsing.
var browserify = require('browserify');
var collapse = require('bundle-collapser-extended/plugin');
var b = browserify();
b.plugin(collapse, {
preset: [
'browserify-require-async'
]
});
Loader slimming
By default, every bundle which uses asynchronous loading will also include custom loader. Loader is needed only in one place and can be exposed through global require, so you can use a require/external combination provided with Browserify to require it only once.
var browserify = require('browserify');
var b = browserify('./index.js');
var main = browserify('./main.js');
b.require('browserify-require-async/loader');
main.external('browserify-require-async/loader');
b.bundle().pipe(fs.createWriteStream('./bundle.js'));
main.bundle().pipe(fs.createWriteStream('./main.bundle.js'));
Watch mode
By default, generated filename is a hash of changed file stats. This is inconvenient in development/watch mode since bundle source won’t be properly updated. To avoid this, you can have condition in watch mode and production mode which will produce different output file.
var browserify = require('browserify');
var bra = require('browserify-require-async');
var b = browserify('./index.js');
b.transform(bra, {
setOutputFile: function ( hash, opts ) {
if ( process.env.NODE_ENV === 'development' ) {
return opts.inputFile;
}
return hash + '.js';
}
});
b.bundle().pipe(fs.createWriteStream('./bundle.js'));
Gulp, multiple bundles and done callback
When using build tools like Gulp, handling multiple bundles and done callbacks can be properly done following way:
var gulp = require('gulp');
var es = require('event-stream');
gulp.task('script', function ( done ) {
var tasks = []; // Top level bundles array streams
var subTasks = []; // Async level bundles array streams
// Your Gulp tasks
es.merge(tasks)
.on('data', function () {})
.on('end', function () {
es.merge(subTasks)
.on('data', function () {})
.on('end', done);
});
});
Q&A
This is similar to Webpack code splitting?
Yes, but with different version of syntax and aligned with standard Browserify features.
Why not require.ensure
like Webpack?
I’ve found it harder to parse file content for all the standard require
references and transforming them to custom require
calls, but it can probably be done.
It’s also possible to create Babel plugin which will transform require.ensure
to require.async
and then afterwards apply this transformation.
Also, I think that require.async
closesly aligns with proposed ES6 System.import
syntax (uses Promises, callback arguments are exports, …) so it’s easier to reason about and write code which is somewhat future-friendly. And also, it’s a candidate for Babel plugin, even easier to write than require.ensure
one.
References
License
MIT © Ivan Nikolić