- Notifications
You must be signed in to change notification settings - Fork290
how to build modular applications with browserify
License
browserify/browserify-handbook
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
This document covers how to usebrowserify to buildmodular applications.
browserify is a tool for compilingnode-flavored commonjs modulesfor the browser.
You can use browserify to organize your code and use third-party libraries evenif you don't usenode itself in any other capacity exceptfor bundling and installing packages with npm.
The module system that browserify uses is the same as node, sopackages published tonpm that were originally intended foruse in node but not browsers will work just fine in the browser too.
Increasingly, people are publishing modules to npm which are intentionallydesigned to work in both node and in the browser using browserify and manypackages on npm are intended for use in just the browser.npm is for all javascript,front or backend alike.
- introduction
- table of contents
- node packaged manuscript
- node packaged modules
- development
- builtins
- transforms
- package.json
- finding good modules
- organizing modules
- testing in node and the browser
- bundling
- shimming
- partitioning
- compiler pipeline
- plugins
You can install this handbook with npm, appropriately enough. Just do:
npm install -g browserify-handbookNow you will have abrowserify-handbook command that will open this readmefile in your$PAGER. Otherwise, you may continue reading this document as youare presently doing.
Before we can dive too deeply into how to use browserify and how it works, it isimportant to first understand how thenode-flavored versionof the commonjs module system works.
In node, there is arequire() function for loading code from other files.
If you install a module withnpm:
npm install uniqThen in a filenums.js we canrequire('uniq'):
var uniq = require('uniq');var nums = [ 5, 2, 1, 3, 2, 5, 4, 2, 0, 1 ];console.log(uniq(nums));The output of this program when run with node is:
$ node nums.js[ 0, 1, 2, 3, 4, 5 ]You can require relative files by requiring a string that starts with a.. Forexample, to load a filefoo.js frommain.js, inmain.js you can do:
varfoo=require('./foo.js');console.log(foo(4));
Iffoo.js was in the parent directory, you could use../foo.js instead:
varfoo=require('../foo.js');console.log(foo(4));
or likewise for any other kind of relative path. Relative paths are alwaysresolved with respect to the invoking file's location.
Note thatrequire() returned a function and we assigned that return value to avariable calleduniq. We could have picked any other name and it would haveworked the same.require() returns the exports of the module name that youspecify.
Howrequire() works is unlike many other module systems where imports are akinto statements that expose themselves as globals or file-local lexicals withnames declared in the module itself outside of your control. Under the nodestyle of code import withrequire(), someone reading your program can easilytell where each piece of functionality came from. This approach scales muchbetter as the number of modules in an application grows.
To export a single thing from a file so that other files may import it, assignover the value atmodule.exports:
module.exports=function(n){returnn*111};
Now when some modulemain.js loads yourfoo.js, the return value ofrequire('./foo.js') will be the exported function:
varfoo=require('./foo.js');console.log(foo(5));
This program will print:
555You can export any kind of value withmodule.exports, not just functions.
For example, this is perfectly fine:
module.exports=555
and so is this:
varnumbers=[];for(vari=0;i<100;i++)numbers.push(i);module.exports=numbers;
There is another form of doing exports specifically for exporting items onto anobject. Here,exports is used instead ofmodule.exports:
exports.beep=function(n){returnn*1000}exports.boop=555
This program is the same as:
module.exports.beep=function(n){returnn*1000}module.exports.boop=555
becausemodule.exports is the same asexports and is initially set to anempty object.
Note however that you can't do:
// this doesn't workexports=function(n){returnn*1000}
because the export value lives on themodule object, and so assigning a newvalue forexports instead ofmodule.exports masks the original reference.
Instead if you are going to export a single item, always do:
// insteadmodule.exports=function(n){returnn*1000}
If you're still confused, try to understand how modules work inthe background:
varmodule={exports:{}};// If you require a module, it's basically wrapped in a function(function(module,exports){exports=function(n){returnn*1000};}(module,module.exports))console.log(module.exports);// it's still an empty object :(
Most of the time, you will want to export a single function or constructor withmodule.exports because it's usually best for a module to do one thing.
Theexports feature was originally the primary way of exporting functionalityandmodule.exports was an afterthought, butmodule.exports proved to be muchmore useful in practice at being more direct, clear, and avoiding duplication.
In the early days, this style used to be much more common:
foo.js:
exports.foo=function(n){returnn*111}
main.js:
varfoo=require('./foo.js');console.log(foo.foo(5));
but note that thefoo.foo is a bit superfluous. Usingmodule.exports itbecomes more clear:
foo.js:
module.exports=function(n){returnn*111}
main.js:
varfoo=require('./foo.js');console.log(foo(5));
To run a module in node, you've got to start from somewhere.
In node you pass a file to thenode command to run a file:
$ node robot.jsbeep boopIn browserify, you do this same thing, but instead of running the file, yougenerate a stream of concatenated javascript files on stdout that you can writeto a file with the> operator:
$ browserify robot.js > bundle.jsNowbundle.js contains all the javascript thatrobot.js needs to work.Just plop it into a single script tag in some html:
<html><body><scriptsrc="bundle.js"></script></body></html>
Bonus: if you put your script tag right before the</body>, you can use all ofthe dom elements on the page without waiting for a dom onready event.
There are many more things you can do with bundling. Check out the bundlingsection elsewhere in this document.
Browserify starts at the entry point files that you give it and searches for anyrequire() calls it finds usingstatic analysisof the source code'sabstract syntax tree.
For everyrequire() call with a string in it, browserify resolves those modulestrings to file paths and then searches those file paths forrequire() callsrecursively until the entire dependency graph is visited.
Each file is concatenated into a single javascript file with a minimalrequire() definition that maps the statically-resolved names to internal IDs.
This means that the bundle you generate is completely self-contained and haseverything your application needs to work with a pretty negligible overhead.
Note: If yourrequire() contains anything other than a string literal (i.e. a variable) then it cannot be read at time of bundling, so the module being required will not be concatenated into your bundle and likely cause a runtime error.
For more details about how browserify works, check out the compiler pipelinesection of this document.
node has a clever algorithm for resolving modules that is unique among rivalplatforms.
Instead of resolving packages from an array of system search paths like how$PATH works on the command line, node's mechanism is local by default.
If yourequire('./foo.js') from/beep/boop/bar.js, node willlook forfoo.js in/beep/boop. Paths that start with a./ or../ are always local to the file that callsrequire().
If however you require a non-relative name such asrequire('xyz') from/beep/boop/foo.js, node searches these paths in order, stopping at the firstmatch and raising an error if nothing is found:
/beep/boop/node_modules/xyz/beep/node_modules/xyz/node_modules/xyzFor eachxyz directory that exists, node will first look for anxyz/package.json to see if a"main" field exists. The"main" field defineswhich file should take charge if yourequire() the directory path.
For example, if/beep/node_modules/xyz is the first match and/beep/node_modules/xyz/package.json has:
{ "name": "xyz", "version": "1.2.3", "main": "lib/abc.js"}then the exports from/beep/node_modules/xyz/lib/abc.js will be returned byrequire('xyz').
If there is nopackage.json or no"main" field,index.js is assumed:
/beep/node_modules/xyz/index.jsIf you need to, you can reach into a package to pick out a particular file. Forexample, to load thelib/clone.js file from thedat package, just do:
var clone = require('dat/lib/clone.js')The recursive node_modules resolution will find the firstdat package up thedirectory hierarchy, then thelib/clone.js file will be resolved from there.Thisrequire('dat/lib/clone.js') approach will work from any location whereyou canrequire('dat').
node also has a mechanism for searching an array of paths, but this mechanism isdeprecated and you should be usingnode_modules/ unless you have a very goodreason not to.
The great thing about node's algorithm and how npm installs packages is that youcan never have a version conflict, unlike almost every other platform. npminstalls the dependencies of each package intonode_modules.
Each library gets its own localnode_modules/ directory where its dependenciesare stored and each dependency's dependencies has its ownnode_modules/directory, recursively all the way down.
This means that packages can successfully use different versions of libraries inthe same application, which greatly decreases the coordination overheadnecessary to iterate on APIs. This feature is very important for an ecosystemlike npm where there is no central authority to manage how packages arepublished and organized. Everyone may simply publish as they see fit and notworry about how their dependency version choices might impact other dependenciesincluded in the same application.
You can leverage hownode_modules/ works to organize your own localapplication modules too. See theavoiding ../../../../../../.. section formore.
Browserify is a build step that you can run before deploying your code. Itgenerates a single bundle file that has everything in it.
Here are some other ways of implementing module systems for the browser and whattheir strengths and weaknesses are:
Instead of a module system, each file defines properties on the window globalobject or develops an internal namespacing scheme.
This approach does not scale well without extreme diligence since each new fileneeds an additional<script> tag in all of the html pages where theapplication will be rendered. Further, the files tend to be very order-sensitivebecause some files need to be included before other files that expect globals toalready be present in the environment.
It can be difficult to refactor or maintain applications built this way.On the plus side, all browsers natively support this approach and no server-sidetooling is required.
This approach tends to be very slow since each<script> tag initiates anew round-trip http request.
Instead of window globals, all the scripts are concatenated beforehand on theserver. The code is still order-sensitive and difficult to maintain, but loadsmuch faster because only a single http request for a single<script> tag needsto execute.
Without source maps, exceptions thrown will have offsets that can't be easilymapped back to their original files.
Instead of using<script> tags, every file is wrapped with adefine()function and callback.This is AMD.
The first argument is an array of modules to load that maps to each argumentsupplied to the callback. Once all the modules are loaded, the callback fires.
define(['jquery'],function($){returnfunction(){};});
You can give your module a name in the first argument so that other modules caninclude it.
There is a commonjs sugar syntax that stringifies each callback and scans it forrequire() callswith a regexp.
Code written this way is much less order-sensitive than concatenation or globalssince the order is resolved by explicit dependency information.
For performance reasons, most of the time AMD is bundled server-side into asingle file and during development it is more common to actually use theasynchronous feature of AMD.
If you're going to have a build step for performance and a sugar syntax forconvenience, why not scrap the whole AMD business altogether and bundlecommonjs? With tooling you can resolve modules to address order-sensitivity andyour development and production environments will be much more similar and lessfragile. The CJS syntax is nicer and the ecosystem is exploding because of nodeand npm.
You can seamlessly share code between node and the browser. You just need abuild step and some tooling for source maps and auto-rebuilding.
Plus, we can use node's module lookup algorithms to save us from versionmismatch problems so that we can have multiple conflicting versions of differentrequired packages in the same application and everything will still work. Tosave bytes down the wire you can dedupe, which is covered elsewhere in thisdocument.
Concatenation has some downsides, but these can be very adequately addressedwith development tooling.
Browserify supports a--debug/-d flag andopts.debug parameter to enablesource maps. Source maps tell the browser to convert line and column offsets forexceptions thrown in the bundle file back into the offsets and filenames of theoriginal sources.
The source maps include all the original file contents inline so that you cansimply put the bundle file on a web server and not need to ensure that all theoriginal source contents are accessible from the web server with paths set upcorrectly.
The downside of inlining all the source files into the inline source map is thatthe bundle is twice as large. This is fine for debugging locally but notpractical for shipping source maps to production. However, you can useexorcist to pull the inline source map outinto a separatebundle.map.js file:
browserify main.js --debug| exorcist bundle.map.js> bundle.js
Running a command to recompile your bundle every time can be slow and tedious.Luckily there are many tools to solve this problem. Some of these tools supportlive-reloading to various degrees and others have a more traditional manualrefresh cycle.
These are just a few of the tools you can use, but there are many more on npm!There are many different tools here that encompass many different tradeoffs anddevelopment styles. It can be a little bit more work up-front to find the toolsthat resonate most strongly with your own personal expectations and experience,but I think this diversity helps programmers to be more effective and providesmore room for creativity and experimentation. I think diversity in tooling and asmaller browserify core is healthier in the medium to long term than picking afew "winners" by including them in browserify core (which creates all kinds ofhavoc in meaningful versioning and bitrot in core).
That said, here are a few modules you might want to consider for setting up abrowserify development workflow. But keep an eye out for other tools not (yet)on this list!
You can usewatchify interchangeably withbrowserify but instead of writingto an output file once, watchify will write the bundle file and then watch allof the files in your dependency graph for changes. When you modify a file, thenew bundle file will be written much more quickly than the first time because ofaggressive caching.
You can use-v to print a message every time a new bundle is written:
$ watchify browser.js -d -o static/bundle.js -v610598 bytes written to static/bundle.js 0.23s610606 bytes written to static/bundle.js 0.10s610597 bytes written to static/bundle.js 0.14s610606 bytes written to static/bundle.js 0.08s610597 bytes written to static/bundle.js 0.08s610597 bytes written to static/bundle.js 0.19sHere is a handy configuration for using watchify and browserify with thepackage.json "scripts" field:
{"build":"browserify browser.js -o static/bundle.js","watch":"watchify browser.js -o static/bundle.js --debug --verbose",}To build the bundle for production donpm run build and to watch files forduring development donpm run watch.
If you would rather spin up a web server that automatically recompiles your codewhen you modify it, check outbeefy.
Just give beefy an entry file:
beefy main.jsand it will set up shop on an http port.
In a similar spirit to beefy but in a more minimal form iswzrd.
Justnpm install -g wzrd then you can do:
wzrd app.jsand open uphttp://localhost:9966 in your browser.
If you are using express, check outbrowserify-middlewareorenchilada.
They both provide middleware you can drop into an express application forserving browserify bundles.
livereactload is a tool forreactthat automatically updates your web page state when you modify your code.
livereactload is just an ordinary browserify transform that you can load with-t livereactload, but you should consult theproject readmefor more information.
browserify-hmr is a plugin for doing hot module replacement (hmr).
Files can mark themselves as accepting updates. If you modify a file thataccepts updates of itself, or if you modify a dependency of a file that acceptsupdates, then the file is re-executed with the new code.
For example, if we have a file,main.js:
document.body.textContent=require('./msg.js')if(module.hot)module.hot.accept()
and a filemsg.js:
module.exports='hey'
We can watchmain.js for changes and load thebrowserify-hmr plugin:
$ watchify main.js -p browserify-hmr -o public/bundle.js -dvand serve up the static file contents inpublic/ with a static file server:
$ ecstatic public -p 8000Now if we loadhttp://localhost:8000, we see the messagehey on the page.
If we changemsg.js to be:
module.exports='wow'
then a second later, the page updates to showwow all by itself.
Browserify-HMR can be used withreact-hot-transform toautomatically allow all React components to be updated live in addition to codeusing themodule.hot API. Unlikelivereactload, only modifiedfiles are re-executed instead of the whole bundle on each modification.
budo is a browserify development server with a stronger focus on incremental bundling and LiveReload integration (including CSS injection).
Install it like so:
npm install budo -g
And run it on your entry file:
budo app.jsThis starts the server athttp://localhost:9966 with a defaultindex.html, incrementally bundling your source on filesave. The requests are delayed until the bundle has finished, so you won't be served stale or empty bundles if you refresh the page mid-update.
To enable LiveReload and have the browser refresh on JS/HTML/CSS changes, you can run it like so:
budo app.js --liveYou can just use the API directly from an ordinaryhttp.createServer() fordevelopment too:
varbrowserify=require('browserify');varhttp=require('http');http.createServer(function(req,res){if(req.url==='/bundle.js'){res.setHeader('content-type','application/javascript');varb=browserify(__dirname+'/main.js').bundle();b.on('error',console.error);b.pipe(res);}elseres.writeHead(404,'not found')});
If you use grunt, you'll probably want to use thegrunt-browserify plugin.
If you use gulp, you should use the browserify API directly.
Here isa guide for getting startedwith gulp and browserify.
Here is a guide on how tomake browserify builds fast with watchify usinggulpfrom the official gulp recipes.
In order to make more npm modules originally written for node work in thebrowser, browserify provides many browser-specific implementations of node corelibraries:
- assert
- buffer
- console
- constants
- crypto
- domain
- events
- http
- https
- os
- path
- punycode
- querystring
- stream
- string_decoder
- timers
- tty
- url
- util
- vm
- zlib
events, stream, url, path, and querystring are particularly useful in a browserenvironment.
Additionally, if browserify detects the use ofBuffer,process,global,__filename, or__dirname, it will include a browser-appropriate definition.
So even if a module does a lot of buffer and stream operations, it will probablyjust work in the browser, so long as it doesn't do any server IO.
If you haven't done any node before, here are some examples of what each ofthose globals can do. Note too that these globals are only actually defined whenyou or some module you depend on uses them.
In node all the file and network APIs deal with Buffer chunks. In browserify theBuffer API is provided bybuffer, whichuses augmented typed arrays in a very performant way with fallbacks for oldbrowsers.
Here's an example of usingBuffer to convert a base64 string to hex:
var buf = Buffer('YmVlcCBib29w', 'base64');var hex = buf.toString('hex');console.log(hex);This example will print:
6265657020626f6f70In node,process is a special object that handles information and control forthe running process such as environment, signals, and standard IO streams.
Of particular consequence is theprocess.nextTick() implementation thatinterfaces with the event loop.
In browserify the process implementation is handled by theprocess module which just providesprocess.nextTick() and little else.
Here's whatprocess.nextTick() does:
setTimeout(function () { console.log('third');}, 0);process.nextTick(function () { console.log('second');});console.log('first');This script will output:
firstsecondthirdprocess.nextTick(fn) is likesetTimeout(fn, 0), but faster becausesetTimeout is artificially slower in javascript engines for compatibility reasons.
In node,global is the top-level scope where global variables are attachedsimilar to howwindow works in the browser. In browserify,global is just analias for thewindow object.
__filename is the path to the current file, which is different for each file.
To prevent disclosing system path information, this path is rooted at theopts.basedir that you pass tobrowserify(), which defaults to thecurrent working directory.
If we have amain.js:
varbar=require('./foo/bar.js');console.log('here in main.js, __filename is:',__filename);bar();
and afoo/bar.js:
module.exports=function(){console.log('here in foo/bar.js, __filename is:',__filename);};
then running browserify starting atmain.js gives this output:
$ browserify main.js | nodehere in main.js, __filename is: /main.jshere in foo/bar.js, __filename is: /foo/bar.js__dirname is the directory of the current file. Like__filename,__dirnameis rooted at theopts.basedir.
Here's an example of how__dirname works:
main.js:
require('./x/y/z/abc.js');console.log('in main.js __dirname='+__dirname);
x/y/z/abc.js:
console.log('in abc.js, __dirname='+__dirname);
output:
$ browserify main.js | nodein abc.js, __dirname=/x/y/zin main.js __dirname=/Instead of browserify baking in support for everything, it supports a flexibletransform system that are used to convert source files in-place.
This way you canrequire() files written in coffee script or templates andeverything will be compiled down to javascript.
To usecoffeescript for example, you can use thecoffeeify transform.Make sure you've installed coffeeify first withnpm install coffeeify then do:
$ browserify -t coffeeify main.coffee > bundle.jsor with the API you can do:
var b = browserify('main.coffee');b.transform('coffeeify');The best part is, if you have source maps enabled with--debug oropts.debug, the bundle.js will map exceptions back into the original coffeescript source files. This is very handy for debugging with firebug or chromeinspector.
Transforms implement a simple streaming interface. Here is a transform thatreplaces$CWD with theprocess.cwd():
varthrough=require('through2');module.exports=function(file){returnthrough(function(buf,enc,next){this.push(buf.toString('utf8').replace(/\$CWD/g,process.cwd()));next();});};
The transform function fires for everyfile in the current package and returnsa transform stream that performs the conversion. The stream is written to and bybrowserify with the original file contents and browserify reads from the streamto obtain the new contents.
Simply save your transform to a file or make a package and then add it with-t ./your_transform.js.
For more information about how streams work, check out thestream handbook.
You can define a"browser" field in the package.json of any package that willtell browserify to override lookups for the main field and for individualmodules.
If you have a module with a main entry point ofmain.js for node but have abrowser-specific entry point atbrowser.js, you can do:
{"name":"mypkg","version":"1.2.3","main":"main.js","browser":"browser.js"}Now when somebody doesrequire('mypkg') in node, they will get the exportsfrommain.js, but when they dorequire('mypkg') in a browser, they will getthe exports frombrowser.js.
Splitting up whether you are in the browser or not with a"browser" field inthis way is greatly preferable to checking whether you are in a browser atruntime because you may want to load different modules based on whether you arein node or the browser. If therequire() calls for both node and the browserare in the same file, browserify's static analysis will include everythingwhether you use those files or not.
You can do more with the "browser" field as an object instead of a string.
For example, if you only want to swap out a single file inlib/ with abrowser-specific version, you could do:
{"name":"mypkg","version":"1.2.3","main":"main.js","browser": {"lib/foo.js":"lib/browser-foo.js" }}or if you want to swap out a module used locally in the package, you can do:
{"name":"mypkg","version":"1.2.3","main":"main.js","browser": {"fs":"level-fs-browser" }}You can ignore files (setting their contents to the empty object) by settingtheir values in the browser field tofalse:
{"name":"mypkg","version":"1.2.3","main":"main.js","browser": {"winston":false }}The browser fieldonly applies to the current package. Any mappings you putwill not propagate down to its dependencies or up to its dependents. Thisisolation is designed to protect modules from each other so that when yourequire a module you won't need to worry about any system-wide effects it mighthave. Likewise, you shouldn't need to worry about how your local configurationmight adversely affect modules far away deep into your dependency graph.
You can configure transforms to be automatically applied when a module is loadedin a package'sbrowserify.transform field. For example, we can automaticallyapply thebrfs transform with thispackage.json:
{"name":"mypkg","version":"1.2.3","main":"main.js","browserify": {"transform": ["brfs" ] }}Now in ourmain.js we can do:
varfs=require('fs');varsrc=fs.readFileSync(__dirname+'/foo.txt','utf8');module.exports=function(x){returnsrc.replace(x,'zzz')};
and thefs.readFileSync() call will be inlined by brfs without consumers ofthe module having to know. You can apply as many transforms as you like in thetransform array and they will be applied in order.
Like the"browser" field, transforms configured in package.json will onlyapply to the local package for the same reasons.
Sometimes a transform takes configuration options on the command line. To apply thesefrom package.json you can do the following.
on the command line
browserify -t coffeeify \ -t [ browserify-ngannotate --ext .coffee --bar ] \ index.coffee > index.jsin package.json
"browserify": {"transform": ["coffeeify", ["browserify-ngannotate", {"ext":".coffee","bar":true}] ]}
Here aresome useful heuristicsfor finding good modules on npm that work in the browser:
I can install it with npm
code snippet on the readme using require() - from a quick glance I should seehow to integrate the library into what I'm presently working on
has a very clear, narrow idea about scope and purpose
knows when to delegate to other libraries - doesn't try to do too many things itself
written or maintained by authors whose opinions about software scope,modularity, and interfaces I generally agree with (often a faster shortcutthan reading the code/docs very closely)
inspecting which modules depend on the library I'm evaluating - this is bakedinto the package page for modules published to npm
Other metrics like number of stars on github, project activity, or a slicklanding page, are not as reliable.
People used to think that exporting a bunch of handy utility-style things wouldbe the main way that programmers would consume code because that is the primaryway of exporting and importing code on most other platforms and indeed stillpersists even on npm.
However, thiskitchen-sink mentalitytoward including a bunch of thematically-related but separable functionalityinto a single package appears to be an artifact for the difficulty ofpublishing and discovery in a pre-github, pre-npm era.
There are two other big problems with modules that try to export a bunch offunctionality all in one place under the auspices of convenience: demarcationturf wars and finding which modules do what.
Packages that are grab-bags of featureswaste a ton of time policing boundariesabout which new features belong and don't belong.There is no clear natural boundary of the problem domain in this kind of packageabout what the scope is, it's allsomebody's smug opinion.
Node, npm, and browserify are not that. They are avowedly à la carte,participatory, and would rather celebrate disagreement and the dizzyingproliferation of new ideas and approaches than try to clamp down in the name ofconformity, standards, or "best practices".
Nobody who needs to do gaussian blur ever thinks "hmm I guess I'll start checkinggeneric mathematics, statistics, image processing, and utility libraries to seewhich one has gaussian blur in it. Was it stats2 or image-pack-utils ormaths-extra or maybe underscore has that one?"No. None of this. Stop it. Theynpm search gaussian and they immediately seendarray-gaussian-filter andit does exactly what they want and then they continue on with their actualproblem instead of getting lost in the weeds of somebody's neglected grandutility fiefdom.
Not everything in an application properly belongs on the public npm and theoverhead of setting up a private npm or git repo is still rather large in manycases. Here are some approaches for avoiding the../../../../../../../relative paths problem.
The simplest thing you can do is to symlink your app root directory into yournode_modules/ directory.
Did you know thatsymlinks work on windowstoo?
To link alib/ directory in your project root intonode_modules, do:
ln -s ../lib node_modules/appand now from anywhere in your project you'll be able to require files inlib/by doingrequire('app/foo.js') to getlib/foo.js.
One caveat though: transformations such asreactify defined on the command line or in the mainpackage.json are not applied to code required like this. You need to definethe transformations also inlib/package.json.
People sometimes object to putting application-specific modules intonode_modules because it is not obvious how to check in your internal moduleswithout also checking in third-party modules from npm.
The answer is quite simple! If you have a.gitignore file that ignoresnode_modules:
node_modulesYou can just add an exception with! for each of your internal applicationmodules:
node_modules/*!node_modules/foo!node_modules/barPlease note that you can'tunignore a subdirectory,if the parent is already ignored. So instead of ignoringnode_modules,you have to ignore every directoryinsidenode_modules with thenode_modules/* trick, and then you can add your exceptions.
Now anywhere in your application you will be able torequire('foo') orrequire('bar') without having a very large and fragile relative path.
If you have a lot of modules and want to keep them more separate from thethird-party modules installed by npm, you can just put them all under adirectory innode_modules such asnode_modules/app:
node_modules/app/foonode_modules/app/barNow you will be able torequire('app/foo') orrequire('app/bar') fromanywhere in your application.
In your.gitignore, just add an exception fornode_modules/app:
node_modules/*!node_modules/appIf your application had transforms configured in package.json, you'll need tocreate a separate package.json with its own transform field in yournode_modules/foo ornode_modules/app/foo component directory becausetransforms don't apply across module boundaries. This will make your modulesmore robust against configuration changes in your application and it will beeasier to independently reuse the packages outside of your application.
You might see some places talk about using the$NODE_PATH environment variableoropts.paths to add directories for node and browserify to look in to findmodules.
Unlike most other platforms, using a shell-style array of path directories with$NODE_PATH is not as favorable in node compared to making effective use of thenode_modules directory.
This is because your application is more tightly coupled to a runtimeenvironment configuration so there are more moving parts and your applicationwill only work when your environment is setup correctly.
node and browserify both support but discourage the use of$NODE_PATH.
There are manybrowserify transformsyou can use to do many things. Commonly, transforms are used to includenon-javascript assets into bundle files.
One way of including any kind of asset that works in both node and the browseris brfs.
brfs uses static analysis to compile the results offs.readFile() andfs.readFileSync() calls down to source contents at compile time.
For example, thismain.js:
varfs=require('fs');varhtml=fs.readFileSync(__dirname+'/robot.html','utf8');console.log(html);
applied through brfs would become something like:
varfs=require('fs');varhtml="<b>beep boop</b>";console.log(html);
when run through brfs.
This is handy because you can reuse the exact same code in node and the browser,which makes sharing modules and testing much simpler.
fs.readFile() andfs.readFileSync() accept the same arguments as in node,which makes including inline image assets as base64-encoded strings very easy:
varfs=require('fs');varimdata=fs.readFileSync(__dirname+'/image.png','base64');varimg=document.createElement('img');img.setAttribute('src','data:image/png;base64,'+imdata);document.body.appendChild(img);
If you have some css you want to inline into your bundle, you can do that toowith the assistance of a module such asinsert-css:
varfs=require('fs');varinsertStyle=require('insert-css');varcss=fs.readFileSync(__dirname+'/style.css','utf8');insertStyle(css);
Inserting css this way works fine for small reusable modules that you distributewith npm because they are fully-contained, but if you want a more holisticapproach to asset management using browserify, check outatomify andparcelify.
Putting these ideas about code organization together, we can build a reusable UIcomponent that we can reuse across our application or in other applications.
Here is a bare-bones example of an empty widget module:
module.exports=Widget;functionWidget(opts){if(!(thisinstanceofWidget))returnnewWidget(opts);this.element=document.createElement('div');}Widget.prototype.appendTo=function(target){if(typeoftarget==='string')target=document.querySelector(target);target.appendChild(this.element);};
Handy javascript constructor tip: you can include athis instanceof Widgetcheck like above to let people consume your module withnew Widget orWidget(). It's nice because it hides an implementation detail from your APIand you still get the performance benefits and indentation wins of usingprototypes.
To use this widget, just userequire() to load the widget file, instantiateit, and then call.appendTo() with a css selector string or a dom element.
Like this:
varWidget=require('./widget.js');varw=Widget();w.appendTo('#container');
and now your widget will be appended to the DOM.
Creating HTML elements procedurally is fine for very simple content but getsvery verbose and unclear for anything bigger. Luckily there are many transformsavailable to ease importing HTML into your javascript modules.
Let's extend our widget example usingbrfs. Wecan also usedomify to turn the string thatfs.readFileSync() returns into an html dom element:
varfs=require('fs');vardomify=require('domify');varhtml=fs.readFileSync(__dirname+'/widget.html','utf8');module.exports=Widget;functionWidget(opts){if(!(thisinstanceofWidget))returnnewWidget(opts);this.element=domify(html);}Widget.prototype.appendTo=function(target){if(typeoftarget==='string')target=document.querySelector(target);target.appendChild(this.element);};
and now our widget will load awidget.html, so let's make one:
<divclass="widget"><h1class="name"></h1><divclass="msg"></div></div>
It's often useful to emit events. Here's how we can emit events using thebuilt-inevents module and theinheritsmodule:
varfs=require('fs');vardomify=require('domify');varinherits=require('inherits');varEventEmitter=require('events').EventEmitter;varhtml=fs.readFileSync(__dirname+'/widget.html','utf8');inherits(Widget,EventEmitter);module.exports=Widget;functionWidget(opts){if(!(thisinstanceofWidget))returnnewWidget(opts);this.element=domify(html);}Widget.prototype.appendTo=function(target){if(typeoftarget==='string')target=document.querySelector(target);target.appendChild(this.element);this.emit('append',target);};
Now we can listen for'append' events on our widget instance:
varWidget=require('./widget.js');varw=Widget();w.on('append',function(target){console.log('appended to: '+target.outerHTML);});w.appendTo('#container');
We can add more methods to our widget to set elements on the html:
varfs=require('fs');vardomify=require('domify');varinherits=require('inherits');varEventEmitter=require('events').EventEmitter;varhtml=fs.readFileSync(__dirname+'/widget.html','utf8');inherits(Widget,EventEmitter);module.exports=Widget;functionWidget(opts){if(!(thisinstanceofWidget))returnnewWidget(opts);this.element=domify(html);}Widget.prototype.appendTo=function(target){if(typeoftarget==='string')target=document.querySelector(target);target.appendChild(this.element);};Widget.prototype.setName=function(name){this.element.querySelector('.name').textContent=name;}Widget.prototype.setMessage=function(msg){this.element.querySelector('.msg').textContent=msg;}
If setting element attributes and content gets too verbose, check outhyperglue.
Now finally, we can toss ourwidget.js andwidget.html intonode_modules/app-widget. Since our widget uses thebrfs transform, we can create apackage.jsonwith:
{"name":"app-widget","version":"1.0.0","private":true,"main":"widget.js","browserify": {"transform": ["brfs" ] },"dependencies": {"brfs":"^1.1.1","inherits":"^2.0.1" }}And now whenever werequire('app-widget') from anywhere in our application,brfs will be applied to ourwidget.js automatically!Our widget can even maintain its own dependencies. This way we can updatedependencies in one widget without worrying about breaking changes cascadingover into other widgets.
Make sure to add an exclusion in your.gitignore fornode_modules/app-widget:
node_modules/*!node_modules/app-widgetYou can read more aboutshared rendering in node and thebrowser if youwant to learn about sharing rendering logic between node and the browser usingbrowserify and some streaming html libraries.
Testing modular code is very easy! One of the biggest benefits of modularity isthat your interfaces become much easier to instantiate in isolation and so it'seasy to make automated tests.
Unfortunately, few testing libraries play nicely out of the box with modules andtend to roll their own idiosyncratic interfaces with implicit globals and obtuseflow control that get in the way of a clean design with good separation.
People also make a huge fuss about "mocking" but it's usually not necessary ifyou design your modules with testing in mind. Keeping IO separate from youralgorithms, carefully restricting the scope of your module, and acceptingcallback parameters for different interfaces can all make your code much easierto test.
For example, if you have a library that does both IO and speaks a protocol,consider separating the IO layer from theprotocolusing an interface likestreams.
Your code will be easier to test and reusable in different contexts that youdidn't initially envision. This is a recurring theme of testing: if your code ishard to test, it is probably not modular enough or contains the wrong balance ofabstractions. Testing should not be an afterthought, it should inform yourwhole design and it will help you to write better interfaces.
Tape was specifically designed from the start to work well in both node andbrowserify. Suppose we have anindex.js with an async interface:
module.exports=function(x,cb){setTimeout(function(){cb(x*100);},1000);};
Here's how we can test this module usingtape.Let's put this file intest/beep.js:
vartest=require('tape');varhundreder=require('../');test('beep',function(t){t.plan(1);hundreder(5,function(n){t.equal(n,500,'5*100 === 500');});});
Because the test file lives intest/, we can require theindex.js in theparent directory by doingrequire('../').index.js is the default place thatnode and browserify look for a module if there is no package.json in thatdirectory with amain field.
We canrequire() tape like any other library after it has been installed withnpm install tape.
The string'beep' is an optional name for the test.The 3rd argument tot.equal() is a completely optional description.
Thet.plan(1) says that we expect 1 assertion. If there are not enoughassertions or too many, the test will fail. An assertion is a comparisonliket.equal(). tape has assertion primitives for:
- t.equal(a, b) - compare a and b strictly with
=== - t.deepEqual(a, b) - compare a and b recursively
- t.ok(x) - fail if
xis not truthy
and more! You can always add an additional description argument.
Running our module is very simple! To run the module in node, just runnode test/beep.js:
$ node test/beep.jsTAP version 13# beepok 1 5*100 === 5001..1# tests 1# pass 1# okThe output is printed to stdout and the exit code is 0.
To run our code in the browser, just do:
$ browserify test/beep.js > bundle.jsthen plopbundle.js into a<script> tag:
<script src="bundle.js"></script>and load that html in a browser. The output will be in the debug console whichyou can open with F12, ctrl-shift-j, or ctrl-shift-k depending on the browser.
This is a bit cumbersome to run our tests in a browser, but you can install thetestling command to help. First do:
npm install -g testlingAnd now just dobrowserify test/beep.js | testling:
$ browserify test/beep.js | testlingTAP version 13# beepok 1 5*100 === 5001..1# tests 1# pass 1# oktestling will launch a real browser headlessly on your system to run the tests.
Now suppose we want to add another file,test/boop.js:
vartest=require('tape');varhundreder=require('../');test('fraction',function(t){t.plan(1);hundreder(1/20,function(n){t.equal(n,5,'1/20th of 100');});});test('negative',function(t){t.plan(1);hundreder(-3,function(n){t.equal(n,-300,'negative number');});});
Here our test has 2test() blocks. The second test block won't start toexecute until the first is completely finished, even though it is asynchronous.You can even nest test blocks by usingt.test().
We can runtest/boop.js with node directly as withtest/beep.js, but if wewant to run both tests, there is a minimal command-runner we can use that comeswith tape. To get thetape command do:
npm install -g tapeand now you can run:
$ tape test/*.jsTAP version 13# beepok 1 5*100 === 500# fractionok 2 1/20th of 100# negativeok 3 negative number1..3# tests 3# pass 3# okand you can just passtest/*.js to browserify to run your tests in thebrowser:
$ browserify test/* | testlingTAP version 13# beepok 1 5*100 === 500# fractionok 2 1/20th of 100# negativeok 3 negative number1..3# tests 3# pass 3# okPutting together all these steps, we can configurepackage.json with a testscript:
{"name":"hundreder","version":"1.0.0","main":"index.js","devDependencies": {"tape":"^2.13.1","testling":"^1.6.1" },"scripts": {"test":"tape test/*.js","test-browser":"browserify test/*.js | testlingify" }}Now you can donpm test to run the tests in node andnpm run test-browser torun the tests in the browser. You don't need to worry about installing commandswith-g when you usenpm run: npm automatically sets up the$PATH for allpackages installed locally to the project.
If you have some tests that only run in node and some tests that only run in thebrowser, you could have subdirectories intest/ such astest/server andtest/browser with the tests that run both places just intest/. Then youcould just add the relevant directory to the globs:
{"name":"hundreder","version":"1.0.0","main":"index.js","devDependencies": {"tape":"^2.13.1","testling":"^1.6.1" },"scripts": {"test":"tape test/*.js test/server/*.js","test-browser":"browserify test/*.js test/browser/*.js | testling" }}and now server-specific and browser-specific tests will be run in addition tothe common tests.
If you want something even slicker, check outprova once you have gotten the basicconcepts.
The core assert module is a fine way to write simple tests too, although it cansometimes be tricky to ensure that the correct number of callbacks have fired.
You can solve that problem with tools likemacgyver but it is appropriately DIY.
A simple way to check code coverage in browserify is to use thecoverify transform.
$ browserify -t coverify test/*.js | node | coverifyor to run your tests in a real browser:
$ browserify -t coverify test/*.js | testling | coverifycoverify works by transforming the source of each package so that eachexpression is wrapped in a__coverageWrap() function.
Each expression in the program gets a unique ID and the__coverageWrap()function will printCOVERED $FILE $ID the first time the expression isexecuted.
Before the expressions run, coverify prints aCOVERAGE $FILE $NODES message tolog the expression nodes across the entire file as character ranges.
Here's what the output of a full run looks like:
$ browserify -t coverify test/whatever.js | nodeCOVERAGE "/home/substack/projects/defined/test/whatever.js" [[14,28],[14,28],[0,29],[41,56],[41,56],[30,57],[95,104],[95,105],[126,146],[126,146],[115,147],[160,194],[160,194],[152,195],[200,217],[200,218],[76,220],[59,221],[59,222]]COVERED "/home/substack/projects/defined/test/whatever.js" 2COVERED "/home/substack/projects/defined/test/whatever.js" 1COVERED "/home/substack/projects/defined/test/whatever.js" 0COVERAGE "/home/substack/projects/defined/index.js" [[48,49],[55,71],[51,71],[73,76],[92,104],[92,118],[127,139],[120,140],[172,195],[172,196],[0,204],[0,205]]COVERED "/home/substack/projects/defined/index.js" 11COVERED "/home/substack/projects/defined/index.js" 10COVERED "/home/substack/projects/defined/test/whatever.js" 5COVERED "/home/substack/projects/defined/test/whatever.js" 4COVERED "/home/substack/projects/defined/test/whatever.js" 3COVERED "/home/substack/projects/defined/test/whatever.js" 18COVERED "/home/substack/projects/defined/test/whatever.js" 17COVERED "/home/substack/projects/defined/test/whatever.js" 16TAP version 13# whateverCOVERED "/home/substack/projects/defined/test/whatever.js" 7COVERED "/home/substack/projects/defined/test/whatever.js" 6COVERED "/home/substack/projects/defined/test/whatever.js" 10COVERED "/home/substack/projects/defined/test/whatever.js" 9COVERED "/home/substack/projects/defined/test/whatever.js" 8COVERED "/home/substack/projects/defined/test/whatever.js" 13COVERED "/home/substack/projects/defined/test/whatever.js" 12COVERED "/home/substack/projects/defined/test/whatever.js" 11COVERED "/home/substack/projects/defined/index.js" 0COVERED "/home/substack/projects/defined/index.js" 2COVERED "/home/substack/projects/defined/index.js" 1COVERED "/home/substack/projects/defined/index.js" 5COVERED "/home/substack/projects/defined/index.js" 4COVERED "/home/substack/projects/defined/index.js" 3COVERED "/home/substack/projects/defined/index.js" 7COVERED "/home/substack/projects/defined/index.js" 6COVERED "/home/substack/projects/defined/test/whatever.js" 15COVERED "/home/substack/projects/defined/test/whatever.js" 14ok 1 should be equal1..1# tests 1# pass 1# okThese COVERED and COVERAGE statements are just printed on stdout and they can befed into thecoverify command to generate prettier output:
$ browserify -t coverify test/whatever.js | node | coverifyTAP version 13# whateverok 1 should be equal1..1# tests 1# pass 1# ok# /home/substack/projects/defined/index.js: line 6, column 9-32 console.log('whatever'); ^^^^^^^^^^^^^^^^^^^^^^^^# coverage: 30/31 (96.77 %)To include code coverage into your project, you can add an entry into thepackage.json scripts field:
{"scripts": {"test":"tape test/*.js","coverage":"browserify -t coverify test/*.js | node | coverify" }}There is also acovert package thatsimplifies the browserify and coverify setup:
{"scripts": {"test":"tape test/*.js","coverage":"covert test/*.js" }}To install coverify or covert as a devDependency, runnpm install -D coverify ornpm install -D covert.
This section covers bundling in more detail.
Bundling is the step where starting from the entry files, all the source filesin the dependency graph are walked and packed into a single output file.
One of the first things you'll want to tweak is how the files that npm installsare placed on disk to avoid duplicates.
When you do a clean install in a directory, npm will ordinarily factor outsimilar versions into the topmost directory where 2 modules share a dependency.However, as you install more packages, new packages will not be factored outautomatically. You can however use thenpm dedupe command to factor outpackages for an already-installed set of packages innode_modules/. You couldalso removenode_modules/ and install from scratch again if problems withduplicates persist.
browserify will not include the same exact file twice, but compatible versionsmay differ slightly. browserify is also not version-aware, it will include theversions of packages exactly as they are laid out innode_modules/ accordingto therequire() algorithm that node uses.
You can use thebrowserify --list andbrowserify --deps commands to furtherinspect which files are being included to scan for duplicates.
You can use thetinyify plugin to applya decent set of zero-config optimizations to your bundle. It will drasticallyreduce output size.
$ browserify foo.js --plugin tinyify > bundle.jstinyify includesbrowser-pack-flat,which does not follow the Node module loading behaviour as closely as thedefaultbrowser-pack does. Ifthere are timing issues in your tinyified bundle output, you can add the--no-flat flag to revert to the default behaviour:
$ browserify foo.js --plugin [ tinyify --no-flat ] > bundle.jsAll kinds of other optimizations will still be applied so you should still seevery significant bundle size wins.
You can generate UMD bundles with--standalone that will work in node, thebrowser with globals, and AMD environments.
Just add--standalone NAME to your bundle command:
$ browserify foo.js --standalone xyz > bundle.jsThis command will export the contents offoo.js under the external module namexyz. If a module system is detected in the host environment, it will be used.Otherwise a window global namedxyz will be exported.
You can use dot-syntax to specify a namespace hierarchy:
$ browserify foo.js --standalone foo.bar.baz > bundle.jsIf there is already afoo or afoo.bar in the host environment in windowglobal mode, browserify will attach its exports onto those objects. The AMD andmodule.exports modules will behave the same.
Note however that standalone only works with a single entry or directly-requiredfile.
In browserify parlance, "ignore" means: replace the definition of a module withan empty object. "exclude" means: remove a module completely from a dependency graph.
Another way to achieve many of the same goals as ignore and exclude is the"browser" field in package.json, which is covered elsewhere in this document.
Ignoring is an optimistic strategy designed to stub in an empty definition fornode-specific modules that are only used in some code paths. For example, if amodule requires a library that only works in node but for a specific chunk ofthe code:
varfs=require('fs');varpath=require('path');varmkdirp=require('mkdirp');exports.convert=convert;functionconvert(src){returnsrc.replace(/beep/g,'boop');}exports.write=function(src,dst,cb){fs.readFile(src,function(err,src){if(err)returncb(err);mkdirp(path.dirname(dst),function(err){if(err)returncb(err);varout=convert(src);fs.writeFile(dst,out,cb);});});};
browserify already "ignores" the'fs' module by returning an empty object, butthe.write() function here won't work in the browser without an extra step likea static analysis transform or a runtime storage fs abstraction.
However, if we really want theconvert() function but don't want to seemkdirp in the final bundle, we can ignore mkdirp withb.ignore('mkdirp') orbrowserify --ignore mkdirp. The code will still work in the browser if wedon't callwrite() becauserequire('mkdirp') won't throw an exception, justreturn an empty object.
Generally speaking it's not a good idea for modules that are primarilyalgorithmic (parsers, formatters) to do IO themselves but these tricks can letyou use those modules in the browser anyway.
To ignorefoo on the command-line do:
browserify --ignore fooTo ignorefoo from the api with some bundle instanceb do:
b.ignore('foo')
Another related thing we might want is to completely remove a module from theoutput so thatrequire('modulename') will fail at runtime. This is useful ifwe want to split things up into multiple bundles that will defer in a cascade topreviously-definedrequire() definitions.
For example, if we have a vendored standalone bundle for jquery that we don't want to appear inthe primary bundle:
$ npm install jquery$ browserify -r jquery > jquery-bundle.jsthen we want to justrequire('jquery') in amain.js:
var$=require('jquery');$(window).click(function(){document.body.bgColor='red'});
defering to the jquery dist bundle so that we can write:
<scriptsrc="jquery-bundle.js"></script><scriptsrc="bundle.js"></script>
and not have the jquery definition show up inbundle.js, then while compilingthemain.js, you can--exclude jquery:
browserify main.js --exclude jquery > bundle.jsTo excludefoo on the command-line do:
browserify --exclude fooTo excludefoo from the api with some bundle instanceb do:
b.exclude('foo')
Unfortunately, some packages are not written with node-style commonjs exports.For modules that export their functionality with globals or AMD, there arepackages that can help automatically convert these troublesome packages intosomething that browserify can understand.
One way to automatically convert non-commonjs packages is withbrowserify-shim.
browserify-shim is loaded as atransform and also reads a"browserify-shim" field frompackage.json.
Suppose we need to use a troublesome third-party library we've placed in./vendor/foo.js that exports its functionality as a window global calledFOO. We can set up ourpackage.json with:
{"browserify": {"transform":"browserify-shim" },"browserify-shim": {"./vendor/foo.js":"FOO" }}and now when werequire('./vendor/foo.js'), we get theFOO variable that./vendor/foo.js tried to put into the global scope, but that attempt wasshimmed away into an isolated context to prevent global pollution.
We could even use thebrowser field to makerequire('foo')work instead of always needing to use a relative path to load./vendor/foo.js:
{"browser": {"foo":"./vendor/foo.js" },"browserify": {"transform":"browserify-shim" },"browserify-shim": {"foo":"FOO" }}Nowrequire('foo') will return theFOO export that./vendor/foo.js triedto place on the global scope.
Most of the time, the default method of bundling where one or more entry filesmap to a single bundled output file is perfectly adequate, particularlyconsidering that bundling minimizes latency down to a single http request tofetch all the javascript assets.
However, sometimes this initial penalty is too high for parts of a website thatare rarely or never used by most visitors such as an admin panel.This partitioning can be accomplished with the technique covered in theignoring and excluding section, but factoring outshared dependencies manually can be tedious for a large and fluid dependencygraph.
Luckily, there are plugins that can automatically factor browserify output intoseparate bundle payloads.
factor-bundle splits browserifyoutput into multiple bundle targets based on entry-point. For each entry-point,an entry-specific output file is built. Files that are needed by two or more ofthe entry files get factored out into a common bundle.
For example, suppose we have 2 pages: /x and /y. Each page has an entry point,x.js for /x andy.js for /y.
We then generate page-specific bundlesbundle/x.js andbundle/y.js withbundle/common.js containing the dependencies shared by bothx.js andy.js:
browserify x.js y.js -p [ factor-bundle -o bundle/x.js -o bundle/y.js ] \ -o bundle/common.jsNow we can simply put 2 script tags on each page. On /x we would put:
<scriptsrc="/bundle/common.js"></script><scriptsrc="/bundle/x.js"></script>
and on page /y we would put:
<scriptsrc="/bundle/common.js"></script><scriptsrc="/bundle/y.js"></script>
You could also load the bundles asynchronously with ajax or by inserting ascript tag into the page dynamically but factor-bundle only concerns itself withgenerating the bundles, not with loading them.
partition-bundle handlessplitting output into multiple bundles like factor-bundle, but includes abuilt-in loader using a specialloadjs() function.
partition-bundle takes a json file that maps source files to bundle files:
{ "entry.js": ["./a"], "common.js": ["./b"], "common/extra.js": ["./e", "./d"]}Then partition-bundle is loaded as a plugin and the mapping file, outputdirectory, and destination url path (required for dynamic loading) are passedin:
browserify -p [ partition-bundle --map mapping.json \ --output output/directory --url directory ]Now you can add:
<scriptsrc="entry.js"></script>
to your page to load the entry file. From inside the entry file, you candynamically load other bundles with aloadjs() function:
a.addEventListener('click',function(){loadjs(['./e','./d'],function(e,d){console.log(e,d);});});
Since version 5, browserify exposes its compiler pipeline as alabeled-stream-splicer.
This means that transformations can be added or removed directly into theinternal pipeline. This pipeline provides a clean interface for advancedcustomizations such as watching files or factoring bundles from multiple entrypoints.
For example, we could replace the built-in integer-based labeling mechanism withhashed IDs by first injecting a pass-through transform after the "deps" havebeen calculated to hash source files. Then we can use the hashes we captured tocreate our own custom labeler, replacing the built-in "label" transform:
varbrowserify=require('browserify');varthrough=require('through2');varshasum=require('shasum');varb=browserify('./main.js');varhashes={};varhasher=through.obj(function(row,enc,next){hashes[row.id]=shasum(row.source);this.push(row);next();});b.pipeline.get('deps').push(hasher);varlabeler=through.obj(function(row,enc,next){row.id=hashes[row.id];Object.keys(row.deps).forEach(function(key){row.deps[key]=hashes[row.deps[key]];});this.push(row);next();});b.pipeline.get('label').splice(0,1,labeler);b.bundle().pipe(process.stdout);
Now instead of getting integers for the IDs in the output format, we get filehashes:
$ node bundle.js(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({"5f0a0e3a143f2356582f58a70f385f4bde44f04b":[function(require,module,exports){var foo = require('./foo.js');var bar = require('./bar.js');console.log(foo(3) + bar(4));},{"./bar.js":"cba5983117ae1d6699d85fc4d54eb589d758f12b","./foo.js":"736100869ec2e44f7cfcf0dc6554b055e117c53c"}],"cba5983117ae1d6699d85fc4d54eb589d758f12b":[function(require,module,exports){module.exports = function (n) { return n * 100 };},{}],"736100869ec2e44f7cfcf0dc6554b055e117c53c":[function(require,module,exports){module.exports = function (n) { return n + 1 };},{}]},{},["5f0a0e3a143f2356582f58a70f385f4bde44f04b"]);Note that the built-in labeler does other things like checking for the external,excluded configurations so replacing it will be difficult if you depend on thosefeatures. This example just serves as an example for the kinds of things you cando by hacking into the compiler pipeline.
Each phase in the browserify pipeline has a label that you can hook onto. Fetcha label with.get(name) to return alabeled-stream-splicerhandle at the appropriate label. Once you have a handle, you can.push(),.pop(),.shift(),.unshift(), and.splice() your own transform streamsinto the pipeline or remove existing transform streams.
The recorder is used to capture the inputs sent to thedeps phase so that theycan be replayed on subsequent calls to.bundle(). Unlike in previous releases,v5 can generate bundle output multiple times. This is very handy for tools likewatchify that re-bundle when a file has changed.
Thedeps phase expects entry andrequire() files or objects as input andcallsmodule-deps to generate a streamof json output for all of the files in the dependency graph.
module-deps is invoked with some customizations here such as:
- setting up the browserify transform key for package.json
- filtering out external, excluded, and ignored files
- setting the default extensions for
.jsand.jsonplus options configuredin theopts.extensionsparameter in the browserify constructor - configuring a globalinsert-module-globalstransform to detect and implement
process,Buffer,global,__dirname,and__filename - setting up the list of node builtins which are shimmed by browserify
This transform addsmodule.exports= in front of files with a.jsonextension.
This transform removes byte order markers, which are sometimes used by windowstext editors to indicate the endianness of files. These markers are ignored bynode, so browserify ignores them for compatibility.
This transform checks for syntax errors using thesyntax-error package to giveinformative syntax errors with line and column numbers.
This phase usesdeps-sort to sortthe rows written to it in order to make the bundles deterministic.
The transform at this phase uses dedupe information provided bydeps-sort in thesort phase toremove files that have duplicate contents.
This phase converts file-based IDs which might expose system path informationand inflate the bundle size into integer-based IDs.
Thelabel phase will also normalize path names based on theopts.basedir orprocess.cwd() to avoid exposing system path information.
This phase emits a'dep' event for each row after thelabel phase.
Ifopts.debug was given to thebrowserify() constructor, this phase willtransform input to addsourceRoot andsourceFile properties which are usedbybrowser-pack in thepack phase.
This phase converts rows with'id' and'source' parameters as input (amongothers) and generates the concatenated javascript bundle as outputusingbrowser-pack.
This is an empty phase at the end where you can easily tack on custom posttransformations without interfering with existing mechanics.
browser-unpack converts a compiledbundle file back into a format very similar to the output ofmodule-deps.
This is very handy if you need to inspect or transform a bundle that has alreadybeen compiled.
For example:
$browserifysrc/main.js|browser-unpack[{"id":1,"source":"module.exports = function (n) { return n * 100 };","deps":{}},{"id":2,"source":"module.exports = function (n) { return n + 1 };","deps":{}},{"id":3,"source":"var foo = require('./foo.js');\nvar bar = require('./bar.js');\n\nconsole.log(foo(3) + bar(4));","deps":{"./bar.js":1,"./foo.js":2},"entry":true}]
This decomposition is needed by tools such asfactor-bundleandbundle-collapser.
When loaded, plugins have access to the browserify instance itself.
Plugins should be used sparingly and only in cases where a transform or globaltransform is not powerful enough to perform the desired functionality.
You can load a plugin with-p on the command-line:
$ browserify main.js -p foo > bundle.jswould load a plugin calledfoo.foo is resolved withrequire(), so to loada local file as a plugin, preface the path with a./ and to load a plugin fromnode_modules/foo, just do-p foo.
You can pass options to plugins with square brackets around the entire pluginexpression, including the plugin name as the first argument:
$ browserify one.js two.js \ -p [ factor-bundle -o bundle/one.js -o bundle/two.js ] \ > common.jsThis command-line syntax is parsed by thesubarg package.
To see a list of browserify plugins, browse npm for packages with the keyword"browserify-plugin":http://npmjs.org/browse/keyword/browserify-plugin
To author a plugin, write a package that exports a single function that willreceive a bundle instance and options object as arguments:
// example pluginmodule.exports=function(b,opts){// ...}
Plugins operate on the bundle instanceb directly by listening for events orsplicing transforms into the pipeline. Plugins should not overwrite bundlemethods unless they have a very good reason.
About
how to build modular applications with browserify
Resources
License
Code of conduct
Security policy
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.
