public
Last active

announcing browserify v2

  • Download Gist
v2.markdown
Markdown

UPDATE: official blog post location at http://browserify.org/announcing_browserify_v2

Browserify has a new website! This inaugural post details the new direction that browserify has taking in the just-released version 2.0 and the rationale behind each change.

static resolution

The biggest change in v2 is a move from dynamic module resolution inside the browser to pre-computed module resolution tables. Switching to static module resolution means browserify won't need to send its require() resolution algorithm down the wire. Instead each bundled file has a dependency map that resolves require() strings to internal module IDs.

For an example of how small the footprint is now, browser-pack, the module browserify v2 uses for packing dependency data into a single bundle has a footprint of just 196 bytes:

$ echo [] | browser-pack | wc -c
196

There is some additional ceremony around each file to define module, exports, __filename, and __dirname, but this decoration is only added as necessary by insert-module-globals which uses lexical-scope to analyze the scope variables in the AST.

external require

Static module resolution fixes a lot of v1 bugs for the browserify -r foo > bundle.js use case where people just want to generate a script tag that exports a require() function by use in other scripts. v1 had problems when the rebasing to remove system-specific paths would accidentally factor out too much of the path such that browser-side, the path for something like -r dnode would show up as just /browser.js since all the files would be based out of node_modules/dnode. v2 fixes this problem by collapsing all internal module names to integers and statically resolving require strings to integers on a per-module basis.

For instance a file that does require('foo') and require('./lib/bar.js') might generate a dependency map like:

{ "foo": 2, "./lib/bar.js": 5 }

Then all the require() defined in module-scope needs to define is

Modules that are intended to be used from outside the bundle by using -r are just given a string id instead of an integer. Much simpler than before!

multi-bundle fallbacks

Using static resolution with a minimal prelude makes using browserify for multi-file bundles where file definitions span multiple files much more feasible.

Splitting bundles up into multiple files can be useful for factoring out common dependencies in multi-page applications to save on page load times or to factor out infrequently-changing pieces to exploit browser caching more effectively.

The require() algorithm in v2 supports a fallback routine where it will call whatever require() was already defined if the current require definition can't find the relevant module in its local set of files.

more modules

In v2 there's a continued emphasis on splitting out functionality into tiny reusable modules that each do just one thing and can stand on their own independently of browserify.

Through v1, detective, resolve, deputy, and commondir have been spun out or grew alongside browserify core. Despite their narrow scope, these modules have been used as dependencies by other modules aside from browserify.

New in v2, the job of dependency data gathering has been delegated to module-deps, which uses required to build the dependency graph. At the other end, browser-pack turns the dependency data into a bundle. In the middle, insert-module-globals takes care of the detecting and defining globals that node modules expect using lexical-scope to statically analyze the AST.

more unix

The great thing about all these tiny modules in v2 is that browserify has become a thin wrapper around this basic pipeline:

$ module-deps main.js | insert-module-globals main.js | browser-pack > bundle.js

It's so unix.

You can actually execute this command and it can do most of what browserify offers! Just run npm install -g module-deps insert-module-globals browser-pack.

The core browserify library is just 180 sloc and it should now be very easy for anybody to come along and write their own browserify using the resuable components published to npm as a starting point.

When it's very easy to disagree and create your own fork, everybody can get a much better feel for what the solution landscape actually looks like. By encouraging experimentation, solutions should start to converge more around the natural trade-offs imposed by the fundamental fabric of computation and less around the fleeting sway of institutional backing and fashionable APIs.

One of my goals in starting browserify was to bring this kind of radical reuse that I had grown used to working with node and npm to the browser. In v2, browserify can finally champion these very ideals in its own source code.

asynchronous

Browserify v1 does synchronous i/o for a number of historical reasons, but this can now be avoided more easily. required does asynchronous i/o, and resolve added an async api in 0.3 so browserify v2 can be entirely asynchronous with the streaming api that it picks up from browser-pack.

An async browserify means that if you want to generate bundles programmatically in your web server for development or if you want write a browserify build server, you won't need to worry about synchronous calls clogging up your event loop.

browser field

v2 supports the browser field proposal written by Roman Shtylman.

In prior releases, browserify would look at the package.json for a "browserify" field to define a browser-specific version of the "main".

For backwards compatibility the "browserify" field still works but has been deprecated in favor of the "browser" field. Most modules shouldn't need a "browser" field at all but it's useful to have when the same module provides its functionality to node and browsers in different ways without resorting to #ifdef-esque hacks.

cut features

Part of making v2 more modular and focused involved cutting a lot of features.

Aliases are gone. Custom require extensions are gone. coffeescript is gone.

Most of the API surface area has been eliminated. The API docs used to be so big that they had a separate markdown file. Now the whole API fits on a single screen in the readme.

One of the worst ideas in browserify, the ad-hoc http server middleware to host bundles is finally gone.

Default support for coffee-script is gone. You can still use coffee script in your program, you'll just need to either compile to js or hook the source transformation into the bundle pipeline yourself.

Remember that if you disagree with these cuts which I expect many people will, with the v2 refactoring it's much easier to roll your own vision of how browserify should be using the underlying new libraries as a starting place.

coming soon

Since all the modules in the bundling pipeline are just streams, in a future release user code will be able to hook into this pipeline with custom through streams to add pre-processing, file watching, and other crazy ideas.

--watch was removed and I would like to see userspace modules fill this niche with the stream hooks.

v2 is all about making browserify more like node itself: a small but independently useful core with fancy extras pushed off into separate modules so that core can focus on doing one thing really well.

tldr;

browserify v2: less features, more unix!

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.