Using Babel to Transpile an NPM Module
You want to use the latest features of ES6, ES7, and beyond for your next JavaScript library project, right? Or, you may want to use CoffeeScript or some other transpiler. Problem is, you don't want the burden to be on the user of your module to run the transpiler or use a specific build tool that will, e.g. browserify. Here is our list of requirements when transpiling a module:
- You can use the latest features or language to author the module
- NPM users consume plain (usually ES5-compatible) JavaScript
- Users can also install from git directly through NPM, e.g.
https://github.com/FormidableLabs/radium.git
- Must work cross-platform (Unix-like and Windows)
We've successfully used this technique to transpile Radium, and both the Windows build via AppVeyor and the Linux build via Travis pass.
1. Directory setup
This guide will assume that your source code is in the src/
directory, and that your published code will live in the lib/
directory.
2. Package main
In package.json
, change your main file to be in lib
instead of src
, e.g. lib/index.js
instead of src/index.js
:
{
"name": "YourPackage",
"main": "lib/index.js"
}
3. prepublish script
To ensure the lib
directory is populated when you publish your module, add the NPM-defined prepublish hook to your package.json
's scripts
section. We'll delete lib
then call babel
.
{
"scripts": {
"prepublish": "rimraf lib && babel --stage 0 src/ -d lib/"
}
}
Notice that we delete the lib
directory before beginning to avoid any artifacts. We also use the <a href="https://www.npmjs.com/package/rimraf">rimraf</a>
module instead of rm -rf
to keep it cross-platform.
4. postinstall script
To make sure users can install directly from git, we'll want a postinstall
hook as well. The trick here, though, is that we don't want to run babel
on postinstall
if the user is installing from NPM, which doesn't have src
and already has lib
built:
{
"scripts": {
"postinstall": "node -e \"require('fs').stat('lib', function(e,s){process.exit(e || !s.isDirectory() ? 1 : 0)})\" || npm run prepublish"
}
}
Again, to keep it cross-platform, we call into node
to check the existence of the lib
directory. The code, expanded and commented, is fairly simple:
var fs require('fs');
// Run stat on the lib directory
fs.stat('lib', function(error, stats) {
// There will be an error if the directory does not exist.
// If it does, make sure it isn't a file.
if (!error || stats.isDirectory) {
// Return a success code, so that the following command doesn't
// need to run since it is chained with ||
process.exit(0);
}
// Return an error code, so that the next command will run
process.exit(1);
});
5. Move devDependencies into dependencies
One unfortunate side-effect of this method is that you now need to put any modules required by prepublish
into your dependencies
section, instead of devDependencies
. In this case, we just need babel
, babel-core
, and rimraf
. Make sure you test installing into a clean directory directly from git to make sure you get this right.
6. Ignore files
Finally, you should add lib
to your .gitignore
so you don't check in transpiled code, and src
to your .npmignore
so your npm module doesn't include untranspiled code.