Skip to content

Instantly share code, notes, and snippets.

@metasansana
Last active August 29, 2015 14:22
Show Gist options
  • Save metasansana/6bd6c70ac9491dd6bea9 to your computer and use it in GitHub Desktop.
Save metasansana/6bd6c70ac9491dd6bea9 to your computer and use it in GitHub Desktop.

browserify for webpack users

There's been a strange explosion in misinformation about browserify recently, particularly in comparisons to webpack.

Generally speaking, most of this confusion stems from how webpack is more willing to pull features into its core to ease discoverability while browserify is more likely to push features out to userland instead.

I think that longer-term, separability has more benefits from a maintenance and experimentation perspective, but that doesn't mean that you can't do pretty much everything webpack can do in the browserify ecosystem. It just means that there might be several different ways of doing that task to serve different use-cases. Diversity is good!. Diversity means that you can choose an approach that more closely matches the trade-offs that you need, but you might need to put in more work to evaluate the different options.

Hopefully this document will help somewhat with finding options to evaluate.

bundle splitting

Webpack, to its credit, provided some useful ideas about how to handle splitting bundles up into multiple pages automatically. However, this idea was quickly taken up in browserify using the plugin API into factor-bundle.

If you want to, you can still split up bundles using -x and -r, but for creating multi-page bundles, it's pretty easy to automate that away with factor-bundle in a very similar way. In both cases for browserify and webpack you will end up with 2 <script> tags you can include on each page.

To split up common assets from entry points x.js and y.js into bundles bundle/x.js and bundle/y.js with a common bundle bundle/common.js, in webpack you can edit the webpack config with:

var webpack = require('webpack');
module.exports = {
    entry: {
        x: "./x.js",
        y: "./y.js",
    },
    output: {
        path: "bundle",
        filename: "[name].js"
    },
    plugins: [
        new webpack.optimize.CommonsChunkPlugin("common.js")
    ]
};

or in browserify with factor-bundle you can just do:

browserify x.js y.js -p [ factor-bundle -o bundle/x.js -o y.js ] \
  -o bundle/common.js

or from the API, that would be:

var browserify = require('browserify');
var fs = require('fs');

var files = [ 'x.js', 'y.js' ];
var b = browserify(files);
b.plugin('factor-bundle', { outputs: [ 'bundle/x.js', 'bundle/y.js' ] });
b.bundle().pipe(fs.createWriteStream('bundle/common.js'));

To load bundles asynchronously you can easily whip something up with xhr and eval() or for a more automatic option there's partition bundle.

stdout by default

This is a small thing, but writing to stdout by default can be very handy.

Instead of how in webpack you do:

webpack main.js bundle.js

In browserify you can also write to a file without using the shell:

browserify main.js -o bundle.js

but if you don't specify -o, output goes to stdout. This is a very handy feature! It means that for example you can pipe to a minifier like uglify in a very simple and straightforward way:

browserify main.js | uglifyjs -cm > bundle.js

This way, browserify doesn't need to include uglify by default, unlike webpack. Users are free to use different minifiers and will not be beholden to whatever version of uglilify that browserify ships with.

Plus you can make fancier pipelines without mucking about reading browserify core docs. Want to make both a minified and a minified+gzipped version?

browserify main.js | uglifyjs -cm | tee bundle.js | gzip > bundle.js.gz

These tricks work with watchify too:

watchify main.js -o 'uglifyjs -cm | tee bundle.js | gzip > bundle.js.gz' -dv

and factor-bundle:

browserify files/*.js \
    -p [ ../ -o 'uglifyjs -cm | tee bundle/`basename $FILE` | gzip > bundle/`basename $FILE`.gz' ] \
    | uglifyjs -cm | tee bundle/common.js | gzip > bundle/common.js.gz

Or you can use the API directly.

transforms instead of loaders

Webpack uses "loaders" to preprocess files, while browserify uses transforms.

In webpack you might do:

{
    module: {
        loaders: [
            { test: /\.coffee$/, loader: "coffee-loader" }
        ]
    }
}

but this configuration applies globally across an entire project. This is considered rather risky in browserify land and reserved for special occasions. In browserify, you would do:

browserify -t coffeeify main.coffee > bundle.js

but this transform would only apply to local files, not to modules installed with npm and placed in node_modules/. This is because those modules were written by other folks with different opinions about how to structure their projects. In browserify-land, decisions about how to transform source code are made very locally at the package level, whereas loaders in webpack are global.

Additionally in browserify-land, any package may have a package.json that specifies special transforms to use. These transforms are applied automatically and transparently with the reasoning that the people who wrote the code for those packages know best what transformations are necessary to make the code work. Webpack takes the opposite opinion: that application developers should be responsible for all the loader transformations across the entire dependency graph. I don't think this idea will pan out at scale the way that it already has in the browserify ecosystem.

Webpack loaders also care about file extensions to an unusual degree and are trying to make a central registry of file extensions to loaders. In browserify, file extensions are handled at the individual application and transform level because transformations might apply to the same file extensions in different contexts: one to handle inlining static assets, another to expand file globs.

Handily, transforms can also be configured from both package.json, the api, and the command-line.

inline source maps

In webpack you can do:

webpack --devtool inline-source-map main.js bundle.js

whereas in browserify you would do:

browserify --debug main.js > bundle.js

Inline source maps are the default in browserify because they require no additional configuration about how or where to serve up extra static assets.

external source maps

In webpack, separate source maps appear with:

webpack --devtool eval main.js bundle.js

which implicitly creates an external source map file that you will need to configure to be serve from the appropriate web root.

This can be done in browserify with an external tool, exorcist:

browserify --debug main.js | exorcist bundle.js.map > bundle.js

This creates a file bundle.js.map that bundle.js will point at as an external source map.

AMD

In webpack, AMD "just works" when the files that webpack needs are hosted in the proper directory.

In browserify, you can obtain AMD with deamdify or browserify-shim.

fast watching

In webpack lingo this is confusingly called "incremental compilation" and is built into webpack core with:

webpack --watch main.js bundle.js

In browserify, you can just do:

watchify main.js -o bundle.js -v

to watch and also print out a message when a build is complete. Unless something is very misconfigured, rebuilding with watchify after the first build should take a few hundred milliseconds at most, similar to webpack.

css

So many times people keep claiming that browserify "doesn't care about css". This is true, but very misleading. Browserify itself does not concern itself with css, but has plenty of extensibility points for different approaches toward modular css to bloom.

Webpack has one solution: put everything into the webpack asset graph. browserify has many approaches. I think that many different approaches map much better to the diversity of requirements that people have for building modular components.

css transforms

First of all, there are lots of css transforms. Some of them are generic like brfs that you can combine with other libraries like insert-css:

var fs = require('fs');
var insertcss = require('insert-css');
insertcss(fs.readFileSync(__dirname + '/style.css', 'utf8'));

and then just compile with brfs:

browserify -t brfs main.js > bundle.js

or you can use format-specific css preprocessors such as lessify:

require('./whatever.less');

and then:

browserify -t lessify main.js > bundle.js

parcelify

parcelify is a very overlooked but powerful answer to handling css in a modular way. parcelify uses browserify to build a dependency graph and automatically reads package.json files as necessary to concatenate all the necessary css.

To make a package with css assets, just put:

{
  "name" : "my-module",
  "version": "1.5.0",
  "style" : "*.css"
}

into the package.json for the module.

Now when you require('my-module') elsewhere starting from an entry point main.js and compile with browserify like usual:

browserify main.js > bundle.js

you'll also be able to run:

parcelify main.js -c bundle.css

to compile your css all in one big bundle!

There are options to apply css transforms and do file watching too.

npm-css

You can also use npm-css to have @import for css files similarly to require() in javascript code:

@import "typeahead";
@import "./foo.css";

.foo {
  color: red
}

which will resolve typeahead from node_modules and foo.css from the same directory as the css file just like how require() behaves.

npm-css doesn't actually have anything to do with browserify at all! It just so happens to pair nicely for handling css. Instead of a main field in package.json, use a style field to specify a css entry point.

atomify

atomify provides a slightly more opinionated solution, insofar as it's possible to do that in the browserify ecosystem, for handling both javascript and css. It can resolve @import statements with the node require algorithm and uses rework behind the scenes to provide prefixes, inline images, and variables.

points of divergence

browserify and webpack disagree one some fundamentals. browserify prefers:

  • more local decisions for better composition
  • node and npm compatability
  • userland experimentation and diversity instead of a large core

composability

Browserify takes great pains to compose well at scale across many packages from many different authors. To do this, it's very important to make decisions locally instead of globally. When people mouth off that something like webpack is "more powerful" than browserify, first of all that doesn't mean anything. Second of all, don't assume that power is always good. The bigger a project gets and the deeper its dependency graph, the more important structuring effects at scale becomes. When transforms can operate globally over the entire graph, it's hard to predict where those transformations could fail or interfere with existing code. It's still possible to achieve these kinds of global actions with plugins and global transforms because occasionally nothing else will do, but ordinary transforms with narrower effect are much more highly encouraged.

overloading require

webpack encorages overloading require() to mean something very different from what it means in node:

require("coffee!./cup.coffee");

This will make it very hard to reuse your code in anything that is not webpack, particularly node.

browserify will never encourage this, opting for simple node-style require() and node compatability where possible

node and npm compatability

browserify puts in a lot of tiny, subtle features so that modules from npm with mostly "just work". The module lookup algorithm is exactly the same, including tiny things like the order of operations and default extensions like .json. Node core modules that make sense in a browser context like path and util also just work without any special configuration.

According to some sophisticated static analysis done by airportyh, around half of the packages on npm are likely to just work with browserify. You can search this subset of packages on browserifysearch.org.

in closing

Be wary of bullet-point engineering.

Comparing technologies strictly on what checks all the boxes might seem like it will solve your problems faster, but lumping too many features together is a recipe for scope creep and inflexibility later on.

read more

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment