Making the web faster and more user-friendly
Create a JavaScript library using ES2015 modules, Gulp, Rollup and Jest
Let's see how to write a JavaScript library using ES2015 transpiled with Babel, featuring ES modules packed up using Rollup via Gulp, and Jest to test your code.
Here I'm sharing the experience I had while developing my vanilla-lazyload script, since at the time I didn't find anything that explained how to use those libraries together, especially Rollup and Jest.
Installing Gulp #
Gulp is a task runner similar to Grunt for automating painful or time-consuming tasks in your development workflow. I chose to migrate from Grunt to Gulp because the latter is blazing fast, being it based on Node's streams instead of the file system.
In order to use Gulp, you need to install Gulp locally to your project, to do so:
npm install --save-dev gulp
A good option to use Gulp is to install its command line executable with:
npm install -g gulp-cli
For more information, I would suggest to read Gulp for beginners on CSS tricks.
Configuring gulp #
Gulp execution requires a configuration file named gulpfile.js
. Create an empty one for now, I'm going to show you what to put in it, step by step.
1. Linting source files #
Being JavaScript a dynamic and loosely-typed language, is especially prone to developer errors. Linting tools like ESLint allow developers to discover problems with their JavaScript code without executing it.
To install ESLint for Gulp, just run:
npm install --save-dev gulp-eslint
Then in your empty gulpfile.js
add:
var gulp = require("gulp");
var eslint = require("gulp-eslint");
gulp.task("default", function () {
process.env.NODE_ENV = "release";
return gulp.src("./src/**/*.js")
// ----------- linting --------------
.pipe(eslint())
.pipe(eslint.format())
.pipe(eslint.failAfterError()) // --> fails if errors
// --> pipe more stuff here
});
NOTE: Setting the process.env.NODE_ENV
variable is necessary to match the task we're running in the Babel configuration. I'll explain this in a while.
2. Bundling modules with Rollup #
Rollup is a module bundler for JavaScript which compiles small pieces of code into something larger, such as a library. It uses the new standardized format for code modules included in the ES2015 revision of JavaScript, today.
Long story short, Rollup reads your main JavaScript file and generates a bigger file with all the modules included, automatically.
Install rollup with:
npm install --save-dev gulp-rollup gulp-rename
Then in your gulpfile, just add a pipe command:
// ----------- rolling up --------------
.pipe(rollup({
format: "umd",
moduleName: "LazyLoad",
entry: "./src/lazyload.js"
}))
This means that Rollup has to produce a umd
type of module, the produced module name will be LazyLoad
, and the entry point to start looking for dependencies is ./src/lazyload.js
.
Since I want to distribute a version of the script which is not transpiled in ES5, I save it to lazyload.es2015.js
piping the rename
command and the dest
one.
.pipe(rename("lazyload.es2015.js"))
.pipe(gulp.dest(destFolder)) // --> writing rolledup
destFolder
is a JavaScript variable and it's just set to ./dist
inside our gulpfile.js
.
NOTE: Webpack is a similar bundling script which might be preferrable to bundle complex applications, but I noticed that the final code generated by Webpack would be much heavier, so Rollup worked better for me.
3. Transpiling to ES5 with Babel #
Until now, we have bundled all our modules in a single file which will work in modern browsers only. Of course we also want to produce a file which is readable by not so modern browsers.
To install babel and its ES2015 preset, plus a plugin to transform Object.assign()
to ES5, do:
npm install --save-dev babel-core gulp-babel babel-preset-es2015 babel-plugin-transform-object-assign
Then, to babelize our previously rolled-up JavaScript and save it to lazyload.js
, let's require gulp-babel
and add the following pipe to our gulpfile.js
:
var babel = require("gulp-babel");
// ----------- babelizing --------------
.pipe(babel())
.pipe(rename("lazyload.js"))
.pipe(gulp.dest(destFolder)) // --> writing babelized es5 js
The babel configuration has to be stored in an external .babelrc
file, that in my case looks like that:
{
"ignore": [
"node_modules/**"
],
"env": {
"test": {
"presets": [
"es2015"
],
"plugins": ["transform-object-assign"]
},
"release": {
"presets": [
["es2015", {
"modules": false
}]
],
"sourceMap": false,
"plugins": ["transform-object-assign"]
}
}
}
You may have noticed that I configured two env
s differently:
release
is the one used by Rollup, in Gulp. Remember? We set it in thegulpfile.js
usingprocess.env.NODE_ENV = "release"
test
is the one used by Jest (explanation below)
4. Minifying with Uglify #
Uglify JS is a JavaScript compressor toolkit commonly used to minify the distribution version of JavaScript libraries.
npm install --save-dev gulp-uglify
Then, add the following require and pipe to our gulpfile.js
:
var uglify = require("gulp-uglify");
// ----------- minifying --------------
.pipe(uglify())
.pipe(rename("lazyload.min.js"))
.pipe(gulp.dest(destFolder)); // --> writing uglified
All together now #
This is how your gulpfile.js
should look like at the end:
var gulp = require("gulp");
var eslint = require("gulp-eslint");
var rollup = require("gulp-rollup");
var rename = require("gulp-rename");
var babel = require("gulp-babel");
var uglify = require("gulp-uglify");
var destFolder = "./dist";
gulp.task("default", function () {
process.env.NODE_ENV = "release";
return gulp.src("./src/**/*.js")
// ----------- linting --------------
.pipe(eslint())
.pipe(eslint.format())
.pipe(eslint.failAfterError()) // --> failing if errors
// ----------- rolling up --------------
.pipe(rollup({
format: "umd",
moduleName: "LazyLoad",
entry: "./src/lazyload.js"
}))
.pipe(rename("lazyload.es2015.js"))
.pipe(gulp.dest(destFolder)) // --> writing rolledup
// ----------- babelizing --------------
.pipe(babel())
.pipe(rename("lazyload.js"))
.pipe(gulp.dest(destFolder)) // --> writing babelized ES5
// ----------- minifying --------------
.pipe(uglify())
.pipe(rename("lazyload.min.js"))
.pipe(gulp.dest(destFolder)); // --> writing uglified
});
gulp.task("watch", function () {
gulp.watch("./src/**/*.js", ["default"]);
// Other watchers
});
The "watch"
part is so that if we run the gulp watch
command, gulp
watches the src
folder for changes and re-creates the dist files automatically. Sweet.
Running Gulp #
You can now build your library executing Gulp in single run:
gulp
Or make it watch for changes in your src
files if you're still developing:
gulp watch
Making Jest work along with Rollup #
I'll write another in-depth post about how I used Jest to test modules which would become private uglified variables and functions, but the point here is how to make Jest work together with Rollup, since the two require different Babel configuration.
As we saw, the Babel configuration is split in two env
s and the one set and used by Jest is test
. This is because Jest just needs the presets
object to contain ["es2015"]
, whereas rollup requires ["es2015", {"modules": false}]
.
If you did what I explained correctly, Jest should work out of the box.
npm install --save-dev jest
npm install -g jest-cli
Then just launch jest like that:
jest
More information on Jest on the Get Started page.
Further steps #
A more advanced configuration would be to configure npm
to be the command line interface for the build and the tests, so that we could run npm run build
and npm run test
to launch either the build or the tests, or better include the testing process in the build so whenever our tests fail, the build fails too. I will do that as next steps.