Reputation: 21905
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
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:
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:
require('<npm name>')
calls for third party libraries--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 require
d 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 require
s 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
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