Publishing NPM package with Rollup, Babel, Flow, Jest and ESLint
Here is a description of common tools and files used to create a modern NPM package. Don't hesitate to clone my npm-lib-package-boilerplate repository to check the results before spending any time on reading this.
Let's start with a list of development dependencies which I'm going to use throughout this article:
- Babel - ES6-8 features transpiled to ES5
- Jest - running tests and generate coverage reports
- Flow - Javascript type checking
- ESLint - Javascript linting
- Travis CI - continuous integration
- Rollup - package files building
At the end of this text you should be more or less familiar with a purpose and usage of all packages listed above.
NOTE: This build is aimed at generating NodeJS/Browser libraries but is not suitable for building/publishing web applications.
Babel
Transpile (converts) ES6-8 Javascript syntax into ES5 compliant code.
NOTE: These instructions are for Babel@6 despite the fact that Babel@7 is already available. It is so due to the fact that the latter is still not supported by all other dependencies. Especially Jest support for Babel@7 is not complete.
Here is a command to execute in order to install Babel:
yarn add --dev babel-core babel-cli babel-preset-env babel-plugin-transform-object-rest-spread
Short explanation:
babel-preset-env
- a starting point for all Babel custom presets (instructions what should be transpiled and how)babel-plugin-transform-object-rest-spread
- if you use spread operator anywhere you will need this one
Finally an example of .babelrc file:
{
// ensures most commonly used transpilations e.g. import
"presets": ["env"],
// some additional plugins: Flow and Rewire (described in the Jest section)
"plugins": ["transform-flow-strip-types", "babel-plugin-rewire"]
}
It will only be used for tests. You won't need it during build process because it will be handled by a Rollup.
Jest
Next important package after Babel transpiler is Jest. Facebook alternative to the well known Mocha test runner. Here is an installation command:
yarn add --dev jest babel-jest babel-plugin-rewire
First two packages are more or less self-explanatory but the last one babel-plugin-rewire
is in my opinion particularly useful. It let's you test even non-exported functions by getting their definitions through __get__
method.
As usual you will require some configuration file, which is named jest.config.js. It might be auto-generated with:
yarn jest --init
For start I would suggest you to leave jest.config.js without any changes as it should work in most cases.
Flow
Type checking the Facebook way. As for React based development Flow seems to me as more natural choice than Typescript, I've decided to use it also for my non-React projects.
yarn add --dev flow-bin flow-typed babel-plugin-transform-flow-strip-types
flow-typed
is a library with typings (something similar to Typescript DefinitelyTyped)babel-plugin-transform-flow-strip-types
allows for using flow with Babel transpiler by stripping flow annotations when necessary
Flow default configuration available in .flowconfig should match your base requirements.
After all packages are in installed with Yarn execute yarn flow-typed install
to get most recent types definitions.
ESLint
Having some linter is always a good idea so I've decided to go with probably most popular one which is ESLint. Just execute:
yarn add --dev eslint eslint-config-airbnb-base eslint-plugin-flowtype eslint-plugin-jest babel-eslint eslint-plugin-import
As you can see we have to install a plugin for every package used in development: Flow, Jest and Babel to prevent false positives. As it is a description of creating non-React package you should probably use eslint-config-airbnb-base
instead of eslint-config-airbnb
for base of your linting rules.
What is more eslint-plugin-import
package is required to support ES6 import statements.
ESLint configuration is stored in .eslintrc:
{
"extends": [
"airbnb-base",
"plugin:jest/recommended",
"plugin:flowtype/recommended",
],
"plugins": [
"jest",
"flowtype",
],
"rules": {
// special case for rewire __get__
"no-underscore-dangle": ["error", { "allow": ["__get__"] }]
},
}
Travis CI
Continuous integration is a nice and useful addition to every publicly available project. In order to use Travis CI you only have to register with your Github account on Travis CI, which is FREE for every open-source project. After registration you should select your repository and activate it (which is fairly simple and intuitive).
In order to make continuous integration work just add a file .travis.yml to the root folder of your project. It should have a content similar to the one presented below:
language: node_js
node_js:
- '8'
script:
- yarn test
- yarn build
install:
- yarn install
branches:
only:
- master
This file tell Travis to:
- use language
node_js
- in version
8
(it will be selected withnvm
by Travis) yarn install
packages (with Yarn - instead of default NPM installation)- run scripts
yarn test
andyarn build
- they will determine the process output, if any of them fails entire building result will be negative - and use only
master
branch of your repo
One thing worth noting during package configuration is a behaviour of prepublish
hook which you may be tempted to implement in package.json file. It might be not so obvious that prepublish
scripts will be run also after initial installation, not only before package publishing. You would probably like to use prepublishOnly
instead of prepublish
.
Rollup
Finally, one of the least known packages. Rollup, a package building tool without complicated configuration. Here is a command to install Rollup and all recommended plugins:
yarn add --dev rollup rollup-plugin-babel@3 rollup-plugin-flow rollup-plugin-cpy babel-plugin-external-helpers
After finishing installation create one fairly simple rollup.config.js file. Here is a commented example:
// plugins that we are going to use
import babel from 'rollup-plugin-babel';
import copy from 'rollup-plugin-cpy';
import flow from 'rollup-plugin-flow';
// list of plugins used during building process
const plugins = targets => ([
// remove flow annotations from output
flow(),
// use Babel to transpile to ES5
babel({
// ignore node_modules/ in transpilation process
exclude: 'node_modules/**',
// ignore .babelrc (if defined) and use options defined here
babelrc: false,
// use recommended babel-preset-env without es modules enabled
// and with possibility to set custom targets e.g. { node: '8' }
presets: [['env', { modules: false, targets }]],
// solve a problem with spread operator transpilation https://github.com/rollup/rollup/issues/281
plugins: ['babel-plugin-transform-object-rest-spread'],
// removes comments from output
comments: false,
}),
// copy Flow definitions from source to destination directory
copy({
files: ['src/*.flow'],
dest: 'lib',
}),
]);
// packages that should be treated as external dependencies, not bundled
const external = []; // e.g. ['axios']
export default [{
// source file / entrypoint
input: 'src/index.js',
// output configuration
output: {
// name visible for other scripts
name: 'npmLibPackageExample',
// output file location
file: 'lib/index.esm.js',
// format of generated JS file, also: esm, and others are available
format: 'esm',
// add sourcemaps
sourcemap: true,
},
external,
// build es modules for node 8
plugins: plugins({ node: '8' }),
}, {
input: 'src/index.js',
output: {
name: 'npmLibPackageExample',
file: 'lib/index.js',
format: 'cjs',
sourcemap: true,
},
external,
// build common JS for node 6
plugins: plugins({ node: '6' }),
}];
Some more or less important findings are:
- use
rollup-plugin-cpy
instead ofrollup-plugin-copy
in order to avoid non-existing output directory problem rollup-plugin-babel@3
you may use version 4 if you upgrade Babel to version 7- as you may noticed as a result of this Rollup configuration you will get two files: ES module standard and CommonJS one
Scripts
Custom scripts defined inside package.json which are used during development and publishing. Here is a list:
"clean": "rimraf lib"
- removelib/
directory with build results"test": "yarn jest src"
- run tests defined insidesrc/
directory with Jest"test:watch": "yarn jest src --watch --notify"
- run tests and watch for changes as well as display notifications with results"cover": "jest src --coverage"
- generate test coverage report"lint": "eslint src"
- run linting with ESLint"build": "yarn rollup -c"
- build output using Rollup"precommit": "yarn flow src && yarn lint && yarn test"
- run before each commit to ensure commited code quality"prepublishOnly": "yarn clean && yarn lint && yarn test && yarn build"
- run ONLY beforeyarn publish
to ensure quality and most recent output
Other tools and files
List of useful tools/files:
rimraf
- cross-platform directory cleaninghusky
- additional Git hooks e.g. testing code just before making a commit.editorconfig
- recommended editor configuration.npmignore
- list of ignored files/dirs when publishing to NPM registryLICENSE
- info about license e.g. MIT
Another important thing is last part of package.json file:
"husky": {
"hooks": {
"pre-commit": "yarn precommit"
}
}
These are lines which actually trigger yarn precommit
before each Git commit thanks to husky provided hooks.
You will find all files/tools listed above in my boilerplate project on Github or on NPM
Sources
- https://codeburst.io/publish-your-own-npm-package-ff918698d450 - great article with slightly different setup
- https://github.com/rollup/rollup/wiki/pkg.module - ES module, CommonJS and others explained
- https://github.com/rollup/rollup/issues/281 - Rollup spread operator transpilation
- https://github.com/rollup/rollup-plugin-babel#configuring-babel
- https://github.com/meuter/rollup-plugin-copy/issues/7 - rollup-plugin-copy problem