JavaScript n'a pas de mécanisme natif d'import de modules, tout du moins pas avant ECMAScript 6. En attendant, des palliatifs ont été développés dont CommonJS et AMD.
Browserify est une application Node permettant, via une étape de build côté serveur, d'utiliser les modules CommonJS et les paquets npm
dans le navigateur :
Use a node-style
require()
to organize your browser code and load modules installed by npm.browserify will recursively analyze all the
require()
calls in your app in order to build a bundle you can serve up to the browser in a single<script>
tag.
Installation
On a donc besoin de node
, de npm
et de browserify au niveau global pour pouvoir l'utiliser en ligne de commande :
$ sudo npm install -g browserify
Pour lister et vérifier les paquets installés globalement :
$ npm list -global --depth=0
Pour mettre à jour browserify au niveau global :
$ sudo npm update -global browserify
Fonctionnement
La commande browserify
prend un ou plusieurs fichiers JavaScript en entrée et génère un flux des fichiers JavaScript concaténés sur la sortie standard qu'il est possible de rediriger avec >
:
$ browserify src/main.js > dist/bundle.js
Le résultat bundle.js
contient maintenant toutes les dépendances (exprimées via require()
) dont main.js
a besoin pour fonctionner et prend la forme d'une Immediately-Invoked Function Expression (IIFE) dont le premier argument est un objet qui fait correspondre pour chaque module importé un numéro unique (en tant que clé) et un tableau de 2 éléments (en tant que valeur) dans lequel :
- le premier élément est le code source du module enveloppé dans une closure
- le second élément (optionnel) est un autre objet qui fait correspondre les dépendances du module lui même avec un des numéros uniques
Comme chaque module est enveloppé automatiquement dans une closure on peut désormais utiliser 'use strict'
en début de fichier sans faire gueuler JSHint
, plus besoin de wrapper nos modules dans des IIFE
à la main :)
Pour plus de détails je vous conseille de lire How Browserify Works et le manuel Browserify.
Les fonctions require
, exports
et module.exports
Dans Node.js l'élément fondamental est le module
qui correspond simplement à un fichier JavaScript qui peut exposer tout ou partie de son code par l'intermédiaire de module.exports
et exports
. Les parties exposées peuvent ensuite être importées dans d'autres modules avec require
.
module.exports
est une spécificité de Node.js qui n'existe pas dans la spécification de CommonJS, ce qui vaut à son système de module l'appellation de node-flavored version of the commonjs module system dans le manuel Browserify.
D'après la documentation de Node.js, exports
est une référence à module.exports
:
The exports variable that is available within a module starts as a reference to module.exports. As with any variable, if you assign a new value to it, it is no longer bound to the previous value.
Voici ce qui peut arriver en cas de rebinding sauvage :
var module = {};
var exports = module.exports = {};
module.exports.foo = 'foo';
console.log(module.exports.foo); // 'foo'
console.log(exports.foo); // 'foo'
exports = 'bar'; // rebinding via a simple operation of assignment
console.log(exports.foo); // undefined
console.log(module.exports.foo); // 'foo'
La documentation de Node.js précise :
As a guideline, if the relationship between
exports
andmodule.exports
seems like magic to you, ignoreexports
and only usemodule.exports
.
Toujours est-il qu'il est possible de tirer parti de cette différence avec subtilité :
- Node.js, Require and Exports
- Export This: Interface Design Patterns for Node.js Modules
- Node.JS Module Patterns with Simple Examples
- Export a Global to the Window Object With Browserify
Mappage de source
Revenons à browserify qui va effectuer une concaténation et produire un fichier unique pouvant compliquer le débogage.
Les source maps sont une solution à ce problème et sont supportées par les outils de développement des navigateurs modernes (dont IE depuis la version 11).
browserify fait partie des outils supportant la génération d'un mappage de source et propose une option --debug
pour l'activer.
Transformations
browserify donne aussi la possibilité d'appliquer des transformations sur le code source pendant la phase de build avec l'option --transform
.
À titre d'exemple il est possible d'utiliser des modules AMD avec deamdify
, de compiler du JSX
en JavaScript avec reactify
ou d'écrire son code en EcmaScript 6 et de dire à browserify de le passer à la moulinette du transpileur 6to5ify
babelify
:
$ npm install babelify
$ browserify --transform babelify src/main.js > dist/bundle.js
Automatisation
Vu que browserify recherche les dépendances externes dans le fichier package.json
, on peut dire au revoir à Bower
et ne conserver qu'un seul gestionnaire de paquets (ce qui, avec un peu de recul, s'impose comme une évidence).
Le fait de pouvoir appliquer des transformations permet également de considérablement réduire la verbosité du script de build.
Sa capacité à être utilisé en ligne de commande permet de l'intégrer dans a peu près n'importe quel système : Makefile
, npm
, Grunt, Gulp, etc.
En ce qui me concerne, j'ai intégré browserify dans mon Gruntfile.js
:
browserify: {
dev: {
options: {
browserifyOptions: {
debug: true
}
},
src: '<%= paths.src %>/main.js',
dest: '<%= paths.dist %>/js/bundle.js'
},
prod: {
options: {
browserifyOptions: {
debug: false
}
},
src: '<%= paths.src %>/main.js',
dest: '<%= paths.dist %>/js/bundle.js'
}
},