0.4.0 • Published 8 years ago

browserify-require-async v0.4.0

Weekly downloads
2
License
MIT
Repository
github
Last release
8 years ago

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.

ArgumentTypeDescription
hashStringMD5 hash of filename and last modified time.
optsObjectFile and directory information.

opts

ArgumentTypeDescription
inputDirStringInput directory.
inputFileStringInput 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.

ArgumentTypeDescription
instanceBrowserifyBrowserify instance, with some original instance options applied (debug).
optsObjectFile and directory information.

opts

ArgumentTypeDescription
inputDirStringInput directory.
inputFileStringInput file.
outputDirStringOutput directory.
outputFileStringOutput 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.

ArgumentTypeDescription
bundleBrowserifyBrowserify instance, with some original instance options applied (debug).
optsObjectFile and directory information.

opts

ArgumentTypeDescription
inputDirStringInput directory.
inputFileStringInput file.
outputDirStringOutput directory.
outputFileStringOutput 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ć