Ian Obermiller

Part time hacker, full time dad.

Using Babel to Transpile an NPM Module

Posted 2015-06-01

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.