Chad Johnson
Chad Johnson

Reputation: 21905

How can I use the same library in Node AND a browser while keeping my library modular?

I am building a JavaScript library that I would like to be consumable by both Node and browsers.

My library consists of a few modules as well as several [Handlebars] templates and nothing that can't run in a browser. Rather that condensing all the modules into one source file and also adding the templates into that source file (via nasty string concatenation), I'd really like to keep things separated out in their own, individual source files. So I'd have

src/
  module1.js
  module2.js
  templates/
    one.handlebars
    two.handlebars

Then, maybe I would package everything up via a build process into one dist file (dist/mylibrary.js).

What would be a common solution to this? Browserify? Grunt? Something else?

Note that I'd like to use this library in an AngularJS app, and I'd like to keep this library vanilla.

Upvotes: 3

Views: 416

Answers (2)

aelr
aelr

Reputation: 375

I went through this process recently and spent more time than I was happy with fighting to find a good solution. These three requirements were important:

  1. How do I handle third party libraries across Node.JS and the browser?
  2. How do I maintain one repo whose tests can be executed on Node.JS or a browser? My library wasn't interacting with the DOM or any Node.JS core modules, so this was achievable.
  3. How do I keep the build time down so that I'm not continuously waiting for rebuild/test cycles to complete.

The problem, I found, was, "What do you want a require('<third party lib>') call to do in each environment?" Between npm, bower, and component, I couldn't find all of my dependencies in any single package manager. Some dependencies supported CommonJS in the browser, but others didn't. One had different scripts for Node.JS and the browser. Another detected Node.JS in an unusual way that didn't work with Browserify.

I wanted a single solution that worked in any case. The solution that I decided to use was to:

  1. Always use Node.JS require('<npm name>') calls for third party libraries
  2. To make this work in the browser in all cases, use Browserify's --require feature to alias all dependencies to their npm expected names, while loading a shim that gets the globally exposed version of the dependency. This means that on the browser, all dependencies must be loaded before the built lib is executed.

With this approach, all Node.JS dependencies live in node_modules, with versions managed through package.json, and all of my browser dependencies could live in a vendor folder, regardless of where they came from. Or they could all be served from a CDN. And by keeping the third party libraries out of the browser build, the rebuilds are quick, especially when using watchify(browserify's watch mode).

Example: (using watchify and showing shims for async and lazy.js)

watchify --require ./shims/async.js:async --require ./shims/async.js:lazy.js index.js --outfile dist/lib.js

Shims structure:

shims/
  index.js
  async.js
  lazy.js

Shim scripts:

// shims/index.js
module.exports = function retrieveGlobal(globalKey){
  // Uses self so that this can be used in a web worker
  var module = self[globalKey];
  delete self[globalKey]; // Optional
  return module;
}

// shims/async.js
var shim = require('./')
module.exports = shim('async')

// shims/lazy.js
var shim = require('./')
module.exports = shim('Lazy')

Additional dependencies require only another shim file and a restart of watchify with the new shim.

As for tests, I had a file that simply required each (Mocha) test suite under test/ and used watchify to build that file. I loaded the watchify destination file into the files array in karma.conf.js with the dependencies included before it. As long as the file that requires each test suite isn't in the test/ directory itself, running mocha to run the tests in Node.JS will still work fine.

Then all you need to do is run:

watchify <"--require" arguments> tests.js -o tests-built.js && karma start && mocha --watch

...and now you're a cross-platform TDD JavaScript master :)

Upvotes: 2

Diego
Diego

Reputation: 4433

Use Browserify :)

I tried several options when working in this library: https://github.com/dfernandez79/barman

After trying different patterns (search on the web for UMD).

I found that browserify does it for me. For example if you use Browserify with Grunt, you only need to pass standalone: true to the Browserify task, and you'll get AMD support for free.

Upvotes: 0

Related Questions