Browserify big projects with minimal rage

Converting to commonjs seems like a lot of work

  • What if I do token replacement?
  • What if I need per-page bundles?
  • What if I use modules that aren't on npm?
  • What if I wrote my code before module patterns?

(when dinosaurs roamed the earth)

It's actually not.

Forget what you know.

Make a new entry point

You'll thank me later.

// thing.js
function Thing () { ... }

// helpers.js
Thing.prototype.someHelper = function () { ... }

// utils.js
Thing.prototype.someUtil = function () { ... }
// thing.js
function Thing () { ... }

// helpers.js
Thing.prototype.someHelper = function () { ... }

// utils.js
Thing.prototype.someUtil = function () { ... }

// index.js
window.Thing = module.exports = require('./thing')

require('./helpers')
require('./utils')
// thing.js
module.exports = Thing
function Thing () { ... }

// helpers.js
Thing.prototype.someHelper = function () { ... }

// utils.js
Thing.prototype.someUtil = function () { ... }

// index.js
window.Thing = module.exports = require('./thing')

require('./helpers')
require('./utils')

You put a thing on window? But globals are bad!

Gradual conversion

// index.js
window.Thing = module.exports = require('./thing')

require('./helpers')
require('./utils')

// thing.js
module.exports = Thing
function Thing () { ... }

// helpers.js
var Thing = require('./thing')
Thing.prototype.someHelper = function () { ... }

// utils.js
Thing.prototype.someUtil = function () { ... }
// index.js
window.Thing = module.exports = require('./thing')

require('./helpers')
require('./utils')

// thing.js
module.exports = Thing
function Thing () { ... }

// helpers.js
var Thing = require('./thing')
Thing.prototype.someHelper = function () { ... }

// utils.js
var Thing = require('./thing')
Thing.prototype.someUtil = function () { ... }
// index.js
window.Thing = module.exports = require('./thing')

require('./helpers')
require('./utils')

// thing.js
module.exports = Thing
function Thing () { ... }

// helpers.js
Thing.prototype.someHelper = function () { ... }

// utils.js
Thing.prototype.someUtil = function () { ... }
// index.js
module.exports = require('./thing')

require('./helpers')
require('./utils')

// thing.js
module.exports = Thing
function Thing () { ... }

// helpers.js
var Thing = require('./thing')
Thing.prototype.someHelper = function () { ... }

// utils.js
var Thing = require('./thing')
Thing.prototype.someUtil = function () { ... }

Don't fear transforms

browserify -t includify app.js > bundle.js

includify

(What is this, the 90s?)

// thing.js
function Thing () { ... }
function OtherThing () { ... }

// helpers.js
Thing.prototype.someHelper = function () { ... }

// utils.js
OtherThing.prototype.someUtil = function () { ... }
// index.js
includify('thing.js')
includify('helpers.js')
includify('utils.js')

exports.Thing = Thing
exports.OtherThing = OtherThing

// thing.js
function Thing () { ... }
function OtherThing () { ... }

// helpers.js
Thing.prototype.someHelper = function () { ... }

// utils.js
OtherThing.prototype.someUtil = function () { ... }

exposify

(Sweep those globals under the rug)

EXPOSIFY_CONFIG='{"jquery":"$"}' browserify -t exposify app.js > bundle.js
<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>

// main.js
var $ = require('jquery')

$(function () {
  $('body').html('<blink>jQuery, woo!</blink>')
  
  // Re-implement blink tag
  setInterval(function () {
    var blink = $('blink')
    var on = blink.css('display') == 'block'
    blink.css('display', on ? 'none' : 'block')
  }, 500)
})

envify

(Hard-coding, dynamic-style)

browserify app.js -t [ envify --NODE_ENV development ] > bundle.js
// config.js
module.exports = {
  apiUrl: process.env.API_URL
}

// app.js
var request = require('request')
var $ = require('jquery')

var config = require('./config')

$(function () {
  request(config.apiUrl + '/hello/world', function (err, _, body) {
    $('#response').text(body)
  })
})
// config.js
module.exports = {
  apiUrl: 'http://example.com/api/v1'
}

// app.js
var request = require('request')
var $ = require('jquery')

var config = require('./config')

$(function () {
  request(config.apiUrl + '/hello/world', function (err, _, body) {
    $('#response').text(body)
  })
})

deamdify

(Conflicting module patterns, yay!)

browserify -t deamdify app.js > bundle.js
// main.js
define(['./time-log'], function (tlog) {
    tlog('%j', {
        foo: 'bar'
    })
})

// time-log.js
define(['./format-log', 'moment'], function (flog, moment) {
    return function (format) {
        var args = Array.prototype.slice.call(arguments, 1)
        args.unshift(moment().format())
        args.unshift('%s: ' + format)
        flog.apply(null, args)
    }
})

// format-log.js
define(['util'], function (util) {
    return function (format) {
        var args = Array.prototype.slice.call(arguments, 1)
        console.log(util.format.apply(util, [format].concat(args)))
    }
})
// main.js
var tlog = require('./time-log')
tlog('%j', {
    foo: 'bar'
})

// time-log.js
define(function (require) {
    var flog = require('./format-log')
    var moment = require('moment')
    return function (format) {
        var args = Array.prototype.slice.call(arguments, 1)
        args.unshift(moment().format())
        args.unshift('%s: ' + format)
        flog.apply(null, args)
    }
})

// format-log.js
define(['util'], function (util) {
    return function (format) {
        var args = Array.prototype.slice.call(arguments, 1)
        console.log(util.format.apply(util, [format].concat(args)))
    }
})
// main.js
var tlog = require('./time-log')
tlog('%j', {
    foo: 'bar'
})

// time-log.js
var flog = require('./format-log')
var moment = require('moment')

module.exports = function (format) {
    var args = Array.prototype.slice.call(arguments, 1)
    args.unshift(moment().format())
    args.unshift('%s: ' + format)
    flog.apply(null, args)
}

// format-log.js
define(['util'], function (util) {
    return function (format) {
        var args = Array.prototype.slice.call(arguments, 1)
        console.log(util.format.apply(util, [format].concat(args)))
    }
})
// main.js
var tlog = require('./time-log')
tlog('%j', {
    foo: 'bar'
})

// time-log.js
var flog = require('./format-log')
var moment = require('moment')

module.exports = function (format) {
    var args = Array.prototype.slice.call(arguments, 1)
    args.unshift(moment().format())
    args.unshift('%s: ' + format)
    flog.apply(null, args)
}

// format-log.js
var util = require('util')

module.exports = function (format) {
    var args = Array.prototype.slice.call(arguments, 1)
    console.log(util.format.apply(util, [format].concat(args)))
}
// main.js
var tlog = require('./time-log')
tlog('%j', {
    foo: 'bar'
})

// time-log.js
define(['./format-log', 'moment'], function (flog, moment) {
    return function (format) {
        var args = Array.prototype.slice.call(arguments, 1)
        args.unshift(moment().format())
        args.unshift('%s: ' + format)
        flog.apply(null, args)
    }
})

// format-log.js
define(['util'], function (util) {
    return function (format) {
        var args = Array.prototype.slice.call(arguments, 1)
        console.log(util.format.apply(util, [format].concat(args)))
    }
})

debowerify

(We need more package managers, right?)

browserify -t debowerify app.js > bundle.js

decomponentify

(Well, we got more package managers!)

browserify -t decomponentify app.js > bundle.js

Writing reusable modules

{
    "browserify": {
        "transform": [
            "deamdify",
            "debowerify"
        ]
    }
}

Transform dependency

Browser entrypoint

{
    "browser": "./browser.js"
}

Factoring out common code

browserify home.js profile.js \
  -p [ factor-bundle -o bundle/home.js -o bundle/profile.js ] \
  -o bundle/common.js

factor-bundle

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

var b = browserify([
  './home.js',
  './profile.js'
])

b.plugin('factor-bundle', {
  outputs: [
    'bundle/home.js',
    'bundle/profile.js'
  ]
})

b.bundle().pipe(fs.createWriteStream('bundle/common.js'))

Using with build tools

var gulp = require('gulp')
var browserify = require('gulp-browserify')

gulp.task('scripts', function() {
  gulp.src('src/js/app.js')
    .pipe(browserify({
      transform: ['includify'],
      debug: !gulp.env.production
    }))
    .pipe(gulp.dest('./build/js'))
})

gulp-browserify

grunt.initConfig({
  browserify: {
    dist: {
      files: {
        'build/bundle.js': ['client/app.js'],
      },
      options: {
        transform: ['deamdify']
      }
    }
  }
})

grunt-browserify

var browserify = require('broccoli-browserify');
tree = browserify(tree, {
  entries: ['client/app.js'],
  outputFile: 'build/bundle.js',
  browserify: {
    transform: ['deamdify']
  }
});

broccoli-browserify

talk.end()

Browserifying big projects with minimal rage

By Stephen Belanger

Browserifying big projects with minimal rage

  • 1,342