Marius Schulz
Marius Schulz
Front End Engineer

npm, ECMAScript 2015, and Babel

Publishing an npm package written in vanilla ECMAScript 5 is quite simple: write the code, add a package.json file, and run npm publish with your npm credentials set up. However, in the era of transpilers, more and more projects are written in ECMAScript 2015 and down-leveled to ECMAScript 5 using a transpiler such as Babel or TypeScript.

The problem with these projects is that you don't want to publish the raw source code as an npm package, but the transpiled version that doesn't contain any ECMAScript 2015 language constructs anymore (or type annotations, in the case of TypeScript). ECMAScript 2015 is not widely supported yet, which is why JavaScript code is down-leveled to ECMAScript 5 first so that current browsers and Node.js environments can execute it.

#Writing an ECMAScript 2015 Module

Let's say we've written a very simple math module that exports as its default value an object defining a square method:

export default {
  square: x => x * x,
};

The above module makes use of two ECMAScript 2015 language features: it exports a value via the native module system, and it implements square via an arrow function. Neither construct is widely understood by today's browsers. Using Babel, our math module can be transpiled into the following CommonJS module:

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true,
});
exports["default"] = {
  square: function square(x) {
    return x * x;
  },
};
module.exports = exports["default"];

This module can now be loaded via require calls or through any dependency loader that supports CommonJS syntax.

#Using Babel via Gulp

In a typical JavaScript project, a build system like Gulp is used to run various tasks, one of which is Babel's transpilation process. While the babel executable could be run on its own, Gulp provides a simpler way to run it on every file change, which is what you usually do during development. Here's a simple gulpfile.js:

var gulp = require("gulp");
var babel = require("gulp-babel");

var libDir = "lib/";
var jsFiles = "src/**/*.js";

gulp.task("babel", function () {
  return gulp.src(jsFiles).pipe(babel()).pipe(gulp.dest(libDir));
});

gulp.task("babel-watch", function () {
  gulp.watch(jsFiles, ["babel"]);
});

gulp.task("default", ["babel", "babel-watch"]);

Whenever the babel task is run, all JavaScript files in the src/ directory will be transpiled and written into the lib/ directory. The babel-watch task can be started during development to add a file watcher that kicks off Babel's transpilation process whenever a .js file changes.

#Excluding Files from Packages

The lib/ directory, which contains the transpiled output generated by Babel, is usually not checked into version control. Therefore, the .gitignore typically contains the following lines:

lib/
node_modules/

On the other hand, the npm package should not contain the src/ directory, but it should contain lib/ directory. By default, npm will exclude files ignored in the .gitignore from the package. That behavior can be overridden by providing a .npmignore file. In this case, an empty .npmignore can be created for the single purpose of overriding the .gitignore. Additionally, the files property in package.json is used to allowlist all files to be included in the package:

{
  "name": "npm-babel-example",
  "files": ["lib/"],
  "...": "..."
}

Note that certain files are always included, regardless of whether or not they have been allowlisted or excluded. According to the npm documentation, these files are:

  • package.json
  • README (and its variants)
  • CHANGELOG (and its variants)
  • LICENSE / LICENCE

#Publishing an NPM Package

Lastly, we need to make sure all JavaScript are transpiled before publishing the package to the npm Registry. Add the following command to the "scripts" section of package.json:

{
  "scripts": {
    "prepublish": "gulp babel"
  }
}

When publishing a package, npm looks for a script command named "prepublish" and runs it, if present. For the simple transpilation process we have defined, only gulp babel needs to be executed.

And that's it! This is how to write, transpile, and publish npm packages using ECMAScript 2015, Babel, and Gulp. You can find the full source code for this example on GitHub and the transpiled package on npm: