Easily ship your JavaScript packages

If you’re into JavaScript you’ve used at least one package from NPM. It’s easy to install and maintain, and it saves a lot of work from your end. The endless number of packages keeps growing every day as is the community maintaining them. But how do you create a package of your own and how to you ship it so it can be used as a standalone script in the browser, a module in CommonJS or an ES6 module?

TL;DR

You can find an example configuration where I used RollupJS in my Better Dates project on Github. To install the package just run follwoing comand

$ npm i @stino/better-dates

In the following part of the post we assume you already have NodeJS installed.

Create your package

To create your own package you’ll have to initialise one on your local machine. Open your preferred terminal (mine is iTerm) and go to the project’s directory. Enter following command and change [user_name] with your NPM username.

$ npm init [email protected][user_name] --y

This will create a package.json file in the root of your project. This is where all the dependencies and package settings will be stored. The --y flag will automatically use the default settings provided by NPM. You can leave it out or change them later in your package.json file.

Installing dependencies

We’ll need some tools to generate the correct files for our package. First in line are Babel packages which will compile our code so different environments can handle them.

$ npm i @babel/core @babel/preset-env --save-dev

The second is RollupJS and some plugins for it. These will bundle our JavaScript files.

$ npm i rollup rollup-plugin-babel rollup-plugin-commonjs rollup-plugin-node-resolve rollup-plugin-terser -D

Both --save-dev and -D add the files to the development dependencies of your package.json file.

Configuring Babel

For most of the files we’re going to bundle we’ll use the same babel configuration. To make life a bit easier we’ll add a .babelrc file to the root of our project containing the following code.

{
  "presets": [
    "@babel/preset-env"
  ]
}

Defining the types

Before we can continue with Rollup, we’ll add the files we wish to bundle to our package.json file. I’ll use [package_name], which is clearly the name of the package you can choose. Set the main file to ./dist/[package_name].cjs.js. Then add 2 more properties to the file so you have something like this:

{
  //...
  "main": "./dist/[package_name].cjs.js",
  "browser": "./dist/[package_name].umd.js",
  "module": "./dist/[package_name].es.js"
  //...
}

Configuring Rollup

First of all, create a rollup.config.js file in the root of your project and import all the files and packages we’ll need to run the bundler. Next to that we’ll define some constants which can be used over the entire configuration.

// Get information from our package.json
import pkg from "./package.json";

// Import the plugins for RollupJS
import resolve from "rollup-plugin-node-resolve";
import commonjs from "rollup-plugin-commonjs";
import babel from "rollup-plugin-babel";
import { terser } from "rollup-plugin-terser";

// Set the file where we start from
const input = "./src/index.js";

// Set files to exclude from our bundle
const exclude = "node_modules/**";

We would rollup.config.js to export an array of objects with our configuration for each type of file we want to bundle, let’s create the export at the end of the file

export default [
  // This is where our configuration objects will live
]

ES6

Now let’s start configuring our Rollup to bundle all the files we need. We’ll start with the ES6 version which is easiest, because well, I write most of my packages in ES6 already.

{
  // Will get the input file we defined earlier
  input,
  output: {
    // Get the file name from our package.json
    file: pkg.module,
    // Define the format we would like to have
    format: "es"
  },
  plugins: [
    resolve(),
    commonjs(),
    // Tell Babel to exclude the files we defined earlier
    babel({ exclude })
  ]
}

Common JS

For CommonJS packages, we’ll need another Babel configuration. We won’t change this in our .babelrc file since this is the only case where the configuration is different.

{
  input,
  output: {
    file: pkg.main,
    format: "cjs"
  },
  plugins: [
    resolve(),
    commonjs(),
    babel({
      exclude,
      babelrc: false,
      presets: [
        ['@babel/env',
          {
            modules: false,
            useBuiltIns: "usage",
            targets: 'maintained node versions'
          }]
      ]
    })
  ]
}

For browsers

If we want our application to be available to use directly into browsers, we can add the following object to our array

{
  input,
  output: {
    file: pkg.browser,
    format: "umd",
    // A name should be defined for our document
    name: "[package_name]"
  },
  plugins: [
    resolve(),
    commonjs(),
    babel({ exclude })
  ]
}

Minified version for browsers

This is the final variant we’re going to create. To make the file load a bit quicker in browsers, we want to offer a minified file of course.

{
  input,
  output: {
    file: pkg.browser.replace(/\.js$/, '.min.js'),
    format: "umd",
    name: "[package_name]"
  },
  plugins: [
    resolve(),
    commonjs(),
    babel({ exclude }),
    // This plugin will minified our JS
    terser()
  ]
}