How to Build and Publish ES6 Modules Today, with Babel and Rollup

The ES2015 specification, also referred to as ES6, has been approved by ECMA International back in June 2015. In April 2016, Node.js Foundation team has released Node.js framework v6 supporting 93% of ES6 language features, thanks to V8 v5.0.

It’s not hard to argue that having a JavaScript library written by using ES6+ syntax and existing language features instead of relying on 3rd party libraries and polyfills has clear benefits. Such as having less verbose, more readable code, using fewer abstractions, having the codebase that is easy to maintain and extend, being able to develop your library faster, first to market in the Lean Startup terminology.

If you’re developing a brand new JavaScript library (npm module) targeting Node.js platform, it might be a good idea to publish it on NPM optimized for Node.js v6 environment, and optionally provide a fallback for developers who are still forced to use v5 and earlier versions of Node.js. So that, Node 6 users could import your library as regular:

const MyLibrary = require('my-library');

Yet being sure that the code will operate well in Node.js 6 environment. Whereas Node 0.x, 4.x, 5.x users would import ES5 version of your library instead (transpiled from ES6 to ES5 via Babel):

var MyLibrary = require('my-library/legacy');

In addition to that, it is highly recommended to include yet another version of the library into your NPM package that is using ES2015 modules syntax. Modules haven’t landed yet in Node.js and V8 but are widely used in Node.js and frontend communities thanks to module bundlers such as Webpack, Browserify, JSPM and Babel compiler.

In order to do so, you need to compile the source code into a distributable format optimized for Node.js 6 but additionally make sure that the import/export statements in the original source code are not transpiled into ES5 module.exports syntax. Let me show you how to do it with Babel and Rollup.

The directory structure of your project may look as follows:

.
├── /dist/ # Temp folder for compiled output
│ ├── /legacy/ # Legacy bundle(s) for Node 0.x, 4.x
│ │ ├── /main.js # ES5.1 bundle for Node 0.x, 4.x
│ │ └── /package.json # Legacy NPM module settings
│ ├── /main.js # ES6 bundle /w CommonJS for Node v6
│ ├── /main.mjs # ES6 bundle /w Modules for cool kids
│ ├── /main.browser.js # ES5.1 bundle for browsers
│ ├── /my-library.js # UMD bundle for browsers
│ ├── /my-library.min.js # UMD bundle, minified and optimized
│ └── /package.json # NPM module settings
├── /node_modules/ # 3rd-party libraries and utilities
├── /src/ # ES2015+ source code
│ ├── /main.js # The main entry point
│ ├── /sub-module-a.js # A module referenced in main.js
│ └── /sub-module-b.js # A module referenced in main.js
├── /test/ # Unit and end-to-end tests
├── /tools/ # Build automation scripts and utilities
│ └── /build.js # Builds the project with Babel/Rollup
└── package.json # Project settings

Where you have the “src” folder containing ES2015+ source code of your library, and a “dist” (or “build”) folder, that is created on the fly when you build the project. From that “dist” folder you will publish your NPM library containing CommonJS, ES6 and UMD bundles compiled with Babel and Rollup.

The “package.json” file will contain references to these bundles:

{
"name": "my-library",
"version": "1.0.0",
"main": "main.js",
"jsnext:main": "main.mjs",
"browser": "main.browser.js",
...
}

“tools/build.js” script is a handy way to configure that compilation step. It may look as follows:

Now you can build your library by running “node tools/build” (assuming you have Node.js 6 installed on your local machine) and publish it from inside the “dist” folder to NPM registry.

I hope this post will be helpful for developers trying to figure out what’s the best way to publish ES6 on NPM. You can also find a pre-configured NPM library boilerplate here: https://github.com/kriasoft/babel-starter-kit

If you think something is missing or inaccurate, please comment below. Happy coding!