How to create compact and efficient javascript using RollupJS
Recently, more and more often, along with other javascript builders, I began to meet rollupJS. And even began to use it to write modules used in the main project of the company. Therefore, I want to share with you the translation to become about this compact and convenient collector.
The style is copyright.
Learn about using Rollup as a more compact and efficient alternative to webpack and Browserify to combine JavaScript files.
At the end of this guide, we will configure Rollup for:
- combining our script,
- delete unused code,
- transpiling it to work with old browsers,
- support for using Node modules in the browser,
- work with environment variables and
- optimizing our code to reduce the size of the output file
Prerequisites
- Basic knowledge of JavaScript.
- An initial introduction to ES2015 modules will not hurt either.
- Your computer must be installed
npm
. (Don't you have one? Install Node.js here .)
What is Rollup?
As the developers themselves describe:
Rollup is the next generation tool for batch processing JavaScript modules. Build your application or library with ES2015 modules, then merge them into a single file for efficient use in browsers and Node.js. This is similar to using Browserify and webpack. You can also call Rollup a build tool that is on a par with tools like Grunt and Gulp. However, it is important to note that although you can use Grunt and Gulp to solve JavaScript batch processing tasks, these tools will use similar functionality from Rollup, Browserify, or webpack.
Why can Rollup help you?
Что может делать Rollup точно, дак это формировать по настоящему оптимизированные по размеру файлы. И если без скучных и нудных формальностей, то подытожить можно так: по сравнению с другими средствами для пакетной обработки JavaScript, Rollup почти всегда будет создавать меньший по объему пакет, и делать это быстрее.
Это происходит потому, что Rollup основан на модулях ES2015, которые являются более эффективными, чем модули CommonJS, которые используются в Browserify и webpack. Кроме того, Rollup гораздо проще удалить неиспользуемый код из модулей используя tree-shaking, что в итоге означает, что только тот код, который нам действительно нужен, будет включен в окончательный пакет.
Tree-shaking becomes very effective when we use third-party libraries or frameworks that have dozens of available functions and methods. If we use only one or two methods - for example, lodash or jQuery - then loading the library completely entails a lot of unnecessary overhead.
Browserify and webpack currently include a lot of unused code when building. But Rollup does not do this - only what we actually use is included in the assembly.
UPDATE (2016-08-22)
To clarify: Rollup can only use tree-shaking on ES modules. Tree-shaking cannot be applied to CommonJS modules, which at the time of writing are both lodash and jQuery. Nevertheless, tree-shaking is one of the advantages of Rollup along with the main one in the form of speed / performance ratio. See also Richard Harris's explanation and more information by Nolan Lawson .
Note
Partly due to Rollup's efficiency, webpack 2 will have tree-shaking support.
How to use Rollup to process and build JavaScript files?
To show how Rollup is effective, let's look at the process of creating an extremely simple project that uses Rollup to build JavaScript.
STEP 0: CREATING A PROJECT WITH JAVASCRIPT AND CSS.
First, we need to have a code that we can work with. In this tutorial, we will work with a small application available on GitHub .
The folder structure looks like this: You can install the application with which we will work during this guide by running the following command in your terminal.
learn-rollup/
├── build/
│ └── index.html
├── src/
│ ├── scripts/
│ │ ├── modules/
│ │ │ ├── mod1.js
│ │ │ └── mod2.js
│ │ └── main.js
│ └── styles/
│ └── main.css
└── package.json
# Move to the folder where you keep your dev projects.
cd /path/to/your/projects
# Clone the starter branch of the app from GitHub.
git clone -b step-0 --single-branch https://github.com/jlengstorf/learn-rollup.git
# The files are downloaded to /path/to/your/projects/learn-rollup/
Note
If you are not cloning repo, be sure to copy the contents
build/index.html
into your own code. This guide does not cover HTML.STEP 1: INSTALL ROLLUP AND CREATE A CONFIGURATION FILE.
To get started, install Rollup with the following command:
npm install --save-dev rollup
Then create a new file with the name
rollup.config.js
in the folder learn-rollup
. Add the following to it.export default {
entry: 'src/scripts/main.js',
dest: 'build/js/main.min.js',
format: 'iife',
sourceMap: 'inline',
};
Let's talk about each option in this configuration:
- Entry is the file we want Rollup to process. In most applications, this will be the main JavaScript file that initializes everything and is the entry point.
- Dest is the place where processed scripts will be saved.
- Format - Rollup supports several output formats. Since we are working in a browser, we want to use immediately called functions (IIFE)FootnoteThis is a rather complicated concept to understand, but in a nutshell, we want our code to be inside its own scope, which prevents conflicts with other scripts. IIFE is a closure that contains all of our code in its own scope.
- SourceMap - This is very useful for debugging to provide a source code map. This option adds a map to the generated file, which simplifies this task.
Note
CHECKING ROLLUP CONFIGURATION
After we have created the configuration file, we can verify that everything works by running the following command in our terminal:
./node_modules/.bin/rollup -c
This will create a new folder called
build
in your project with a subfolder js
that will contain our generated file main.min.js
. We will see that the package was created correctly by opening
build/index.html
in our browser:Note
At this stage, only modern browsers will work without errors. In order for this code to work with older browsers that do not support ES2015 / ES6, we need to add some plugins.
Parsing the Generated Package
Using tree-shaking makes Rollup a powerful tool, and as a result, there is no unused code from the modules we refer to in the output file. For example,
src/scripts/modules/mod1.js
there is a function sayGoodbyeTo ()
that is not used in our application - and since it is never used, Rollup does not include it in the final package:The code
(function () {
'use strict';
/**
* Says hello.
* @param {String} name a name
* @return {String} a greeting for `name`
*/
function sayHelloTo( name ) {
const toSay = `Hello, ${name}!`;
return toSay;
}
/**
* Adds all the values in an array.
* @param {Array} arr an array of numbers
* @return {Number} the sum of all the array values
*/
const addArray = arr => {
const result = arr.reduce((a, b) => a + b, 0);
return result;
};
// Import a couple modules for testing.
// Run some functions from our imported modules.
const result1 = sayHelloTo('Jason');
const result2 = addArray([1, 2, 3, 4]);
// Print the results on the page.
const printTarget = document.getElementsByClassName('debug__output')[0];
printTarget.innerText = `sayHelloTo('Jason') => ${result1}\n\n`
printTarget.innerText += `addArray([1, 2, 3, 4]) => ${result2}`;
}());
//# sourceMappingURL=data:application/json;charset=utf-8;base64,...
In other build tools, this is not always the case, and the generated packages can be really large if we include a large library, such as lodash , to reference one or two functions.
For example, a function is enabled using webpack
sayGoodbyeTo ()
, and the resulting package is more than twice the size that Rollup generates.Footnote
However, it is important to remember that when we are dealing with such a small test application, doubling the file size does not take much time. For comparison, at the moment this size is ~ 3KB versus ~ 8KB
STEP 2: INSTALL BABEL TO USE NEW JAVASCRIPT FEATURES NOW
At the moment, we have code that will work only in modern browsers, and will not work in some browsers, the version of which lags behind several versions - and this is not ideal.
Fortunately, Babel can help us. This project allows you to translate new JavaScript features ( ES6 / ES2015, etc. ) into ES5, and this code will work in almost any browser that can still be used today.
If you have never used Babel, your developer life will change forever. Access to new JavaScript features makes the language easier, cleaner and more enjoyable in general.
So let's make it part of our build process so we don’t have to think about it anymore.
INSTALL NECESSARY MODULES
First, we need to install the Babel Rollup plugin and the corresponding Babel presets .
# Install Rollup’s Babel plugin.
npm install --save-dev rollup-plugin-babel
# Install the Babel preset for transpiling ES2015.
npm install --save-dev babel-preset-es2015
# Install Babel’s external helpers for module support.
npm install --save-dev babel-plugin-external-helpers
Note
Babel Presets is a collection of Babel plugins that tell Babel what we really want to convey
CREATION
.babelrc
. Then create a new file with a name
.babelrc
in the root directory of the project (learn-rollup/
). Inside, add the following JSON:{
"presets": [
[
"es2015",
{
"modules": false
}
]
],
"plugins": [
"external-helpers"
]
}
This tells Babel which preset he should use during compilation.
Note
In earlier versions of npm (2.15.11) you can see the error with the preset
es2015-rollup
. If you cannot upgrade npm
, see this issue for an alternative configuration .babelrc
.UPDATE (2016-11-13)
The video
.babelrc
uses an outdated configuration. See. This pull request for configuration changes , and the changes for the in package.json
.UPDATE
rollup.config.js
.To add Babel to Rollup, you need to update rollup.config.js. Inside, we import the Babel plugin, and then add it to a new configuration property called plugins, which will contain an array of plugins.
// Rollup plugins
import babel from 'rollup-plugin-babel';
export default {
entry: 'src/scripts/main.js',
dest: 'build/js/main.min.js',
format: 'iife',
sourceMap: 'inline',
plugins: [
babel({
exclude: 'node_modules/**',
}),
],
};
To avoid translating third-party scripts, we set the property
exclude
to ignore the directory node_modules
. CHECK THE OUTPUT PACKAGE
With everything installed and configured, we can rebuild the package:
./node_modules/.bin/rollup -c
When we look at the result, it looks about the same. But there are several key differences: for example, look at the function
addArray ()
:
var addArray = function addArray(arr) {
var result = arr.reduce(function (a, b) {
return a + b;
}, 0);
return result;
};
See how Babel converted the bold arrow for a function
(arr.reduce ((a, b) => a + b, 0))
to a regular function. This is a transpilation in action: the result is the same, but the code is now supported in IE9.
Important
Babel also offers babel-polyfill , which makes things seem to be
Array.prototype.reduce ()
available in IE8 and earlier.STEP 3: ADD ESLINT FOR JAVASCRIPT ERROR TEST
It is always useful to use linter for your code, as it provides consistent coding practice and helps you find complex errors, such as missing operator or parentheses.
For this project we will use ESLint .
INSTALLING
THE MODULE To use ESLint, we need to install the ESLint Rollup plugin :
npm install --save-dev rollup-plugin-eslint
GENERATION
.eslintrc.json
. To make sure that we get only the errors that we need, we must first configure ESLint. Fortunately, we can automatically create most of this configuration by running the following command:
Terminal
$ ./node_modules/.bin/eslint --init
? How would you like to configure ESLint? Answer questions about your style
? Are you using ECMAScript 6 features? Yes
? Are you using ES6 modules? Yes
? Where will your code run? Browser
? Do you use CommonJS? No
? Do you use JSX? No
? What style of indentation do you use? Spaces
? What quotes do you use for strings? Single
? What line endings do you use? Unix
? Do you require semicolons? Yes
? What format do you want your config file to be in? JSON
Successfully created .eslintrc.json file in /Users/jlengstorf/dev/code.lengstorf.com/projects/learn-rollup
If you answer the questions as shown above, you will get the following result in
.eslintrc.json
:.eslintrc.json
{
"env": {
"browser": true,
"es6": true
},
"extends": "eslint:recommended",
"parserOptions": {
"sourceType": "module"
},
"rules": {
"indent": [
"error",
4
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
]
}
}
TWEAK
.eslintrc.json
However, we must make some adjustments to avoid errors for our project:
- We use 2 spaces instead of 4.
- Later we will use a global variable called
ENV
, so we need to whitelist it.
Make the following changes to your setting
.eslintrc.json
— property globals
and property setting indent
:.eslintrc.json
{
"env": {
"browser": true,
"es6": true
},
"globals": {
"ENV": true
},
"extends": "eslint:recommended",
"parserOptions": {
"sourceType": "module"
},
"rules": {
"indent": [
"error",
2
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
]
}
}
UPDATE
rollup.config.js
Then import the ESLint plugin and add it to your Rollup configuration:
// Rollup plugins
import babel from 'rollup-plugin-babel';
import eslint from 'rollup-plugin-eslint';
export default {
entry: 'src/scripts/main.js',
dest: 'build/js/main.min.js',
format: 'iife',
sourceMap: 'inline',
plugins: [
eslint({
exclude: [
'src/styles/**',
]
}),
babel({
exclude: 'node_modules/**',
}),
],
};
CHECKING THE RESULTS IN THE CONSOLE
When we run
./node_modules/.bin/rollup -c
, nothing seems to be happening. The fact is that the application code in its current form passes linter without problems. But if we introduce a problem, such as removing a semicolon, we will see how ESLint helps:
$ ./node_modules/.bin/rollup -c
/Users/jlengstorf/dev/code.lengstorf.com/projects/learn-rollup/src/scripts/main.js
12:64 error Missing semicolon semi
1 problem (1 error, 0 warnings)
Potentially, what might contain a hidden error is now immediately visible, including the file, row and column where the problem is detected.
Although this does not save us from all our errors during debugging, it greatly speeds up the process, excluding errors caused by obvious typos.
Footnote
Probably many of us spent many hours catching errors that ended up being something as stupid as a name with an error, it is difficult to exaggerate the increase in work efficiency from using the linter.
STEP 4: ADDING A PLUGIN FOR PROCESSING NOT ES-MODULES
This is important if you use Node-style modules in your assembly. Without using this plugin, you will receive an error message when connecting this library using
require
. ADDING NODE MODULES AS DEPENDENCIES
It would be easy to hack this sample project without reference to a third-party module, but this is not going to reduce it in real projects. Therefore, to make our Rollup configuration really useful, let's make sure that we can also refer to third-party modules in our code.
For simplicity, we will add a simple registrar to our code using the package
debug
. Start by installing it:
npm install --save debug
Note
Since this will be indicated in the main project, it is important to use --save, which will avoid a production error where devDependencies will be ignored.
Then, inside
src/scripts/main.js
, let's add some simple logging:main.js
// Import a couple modules for testing.
import { sayHelloTo } from './modules/mod1';
import addArray from './modules/mod2';
// Import a logger for easier debugging.
import debug from 'debug';
const log = debug('app:log');
// Enable the logger.
debug.enable('*');
log('Logging is enabled!');
// Run some functions from our imported modules.
const result1 = sayHelloTo('Jason');
const result2 = addArray([1, 2, 3, 4]);
// Print the results on the page.
const printTarget = document.getElementsByClassName('debug__output')[0];
printTarget.innerText = `sayHelloTo('Jason') => ${result1}\n\n`;
printTarget.innerText += `addArray([1, 2, 3, 4]) => ${result2}`;
When we start rollup, we get a warning:
$ ./node_modules/.bin/rollup -c
Treating 'debug' as external dependency
No name was provided for external module 'debug' in options.globals – guessing 'debug'
And if we run our index.html again, we will see that there is a ReferenceError error in the console:
Here, rubbish. This did not work.
This is due to the fact that Nodal Node modules use CommonJS, which is incompatible with Rollup. To solve this problem, we need to add a couple of plugins to handle Node dependencies and CommonJS modules.
INSTALLING THESE MODULES
To get around this problem, we need to add two plugins:
rollup-plugin-node-resolve
, which allows you to load third-party modules fromnode_modules
.rollup-plugin-commonjs
, which provides support for connecting CommonJS modules.
Install both plugins with the following command:
npm install --save-dev rollup-plugin-node-resolve rollup-plugin-commonjs
UPDATE
rollup.config.js
Then import it into your Rollup configuration:
rollup.config.js
// Rollup plugins
import babel from 'rollup-plugin-babel';
import eslint from 'rollup-plugin-eslint';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
export default {
entry: 'src/scripts/main.js',
dest: 'build/js/main.min.js',
format: 'iife',
sourceMap: 'inline',
plugins: [
resolve({
jsnext: true,
main: true,
browser: true,
}),
commonjs(),
eslint({
exclude: [
'src/styles/**',
]
}),
babel({
exclude: 'node_modules/**',
}),
],
};
Note
The property
jsnext
provides easy migration of ES2015 modules for Node packages . Properties main
and browser
help the plugin decide which files should be used for the package.CHECKING THE RESULTS IN THE
CONSOLE Rebuild the command
./node_modules/.bin/rollup -c
, then check the browser again to see the result:STEP 5: ADDING A PLUGIN PROVIDING REPLACEMENT OF THE ENVIRONMENT
Environment variables add a lot of additional "tricks" during development and give us the opportunity to do things like turn off / on logging, implement only dev-scripts and much more.
So let's make sure Rollup allows us to use them.
ADDING CONDITIONS FOR
ENV
В main.js
Let's use the environment variable and enable logging only if we are not in production mode. B
src/scripts/main.js
, change the way we initialize our log()
:
// Import a logger for easier debugging.
import debug from 'debug';
const log = debug('app:log');
// The logger should only be disabled if we’re not in production.
if (ENV !== 'production') {
// Enable the logger.
debug.enable('*');
log('Logging is enabled!');
} else {
debug.disable();
}
However, after we rebuild our project
(./node_modules/.bin/rollup -c)
and look at the browser, we will see what this gives us ReferenceError
for ENV
. This should not be surprising, because we have not identified it anywhere. But if we try something like
ENV = production ./node_modules/.bin/rollup -c
, it still won’t work. This is because setting the environment variable in this way makes it available only to Rollup, and not to the package created using Rollup. We need to use the plugin to pass our environment variables to the package.
INSTALLING THESE MODULES
Start with the installation
rollup-plugin-replace
, which is essentially just a find-and-replace utility. It can do many things, but for our purposes, we simply find the appearance of an environment variable and replace it with the actual value (for example, all occurrencesENV
will be replaced by "production" in the assembly).npm install --save-dev rollup-plugin-replace
UPDATE rollup.config.js
Let's
rollup.config.js
import it into it and add it to our list of plugins. The configuration is quite simple: we can just add a list of key-value pairs, where the key is the string to replace, and the value is what it should replace.
rollup.config.js
// Rollup plugins
import babel from 'rollup-plugin-babel';
import eslint from 'rollup-plugin-eslint';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import replace from 'rollup-plugin-replace';
export default {
entry: 'src/scripts/main.js',
dest: 'build/js/main.min.js',
format: 'iife',
sourceMap: 'inline',
plugins: [
resolve({
jsnext: true,
main: true,
browser: true,
}),
commonjs(),
eslint({
exclude: [
'src/styles/**',
]
}),
babel({
exclude: 'node_modules/**',
}),
replace({
exclude: 'node_modules/**',
ENV: JSON.stringify(process.env.NODE_ENV || 'development'),
}),
],
};
In our configuration, we describe ENV as process.env.NODE_ENV — the usual way to set environment variables in Node applications — or “development”. We use JSON.stringify () to get the value enclosed in double quotes.
To eliminate problems with third-party code, we also set the exclude property to ignore our node_modules directory and all packages that it contains. (Thanks to @wesleycoder on this subject ).
CHECK THE RESULTS
Let's rebuild the build and check the browser. The log in the console should not differ from what it was before. This is good - it means that we used the default value.
To make sure that the setup works, run the rebuild in production mode:
NODE_ENV=production ./node_modules/.bin/rollup -c
Note
On Windows, use
SET NODE_ENV = production ./node_modules/.bin/rollup -c
to avoid errors when working with environment variables. If you have problems with this command, see this problem for more information.Make sure that after reloading the page, nothing is written to the console:
STEP 6: ADD UGLIFYJS TO COMPRESS AND MINIFY THE GENERATED SCRIPT
The final step that we will cover in this guide is to add UglifyJS to minify and compress our package. This can significantly reduce its size by deleting comments, reducing the names of variables, and other types of "cleaning" code - which makes it more or less unreadable to people, but much more efficient for delivery over the network.
INSTALLING THE Plugin
We will use UglifyJS to compress the package using
rollup-plugin-uglify
. Install it with the following command:
npm install --save-dev rollup-plugin-uglify
UPDATE
rollup.config.js
Now let's add Uglify to our Rollup configuration. For convenience, debugging during development, let's do uglification only for production mode:
rollup.config.js
// Rollup plugins
import babel from 'rollup-plugin-babel';
import eslint from 'rollup-plugin-eslint';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import replace from 'rollup-plugin-replace';
import uglify from 'rollup-plugin-uglify';
export default {
entry: 'src/scripts/main.js',
dest: 'build/js/main.min.js',
format: 'iife',
sourceMap: 'inline',
plugins: [
resolve({
jsnext: true,
main: true,
browser: true,
}),
commonjs(),
eslint({
exclude: [
'src/styles/**',
]
}),
babel({
exclude: 'node_modules/**',
}),
replace({
ENV: JSON.stringify(process.env.NODE_ENV || 'development'),
}),
(process.env.NODE_ENV === 'production' && uglify()),
],
};
In our case, we load
uglify ()
when NODE_ENV
"production" is set. CHECKING THE MINIED ASSEMBLY
Save the configuration, and run Rollup in production mode:
NODE_ENV=production ./node_modules/.bin/rollup -c
The contents of the output file does not look very nice, but it is much smaller. Here is a screenshot of what it looks like
build/js/main.min.js
: Previously, our package was ~ 42 KB. After running it through UglifyJS, its size decreased to ~ 29 KB - we simply saved more than 30% of the file size without additional effort.
→ Source
Personal impression:
The general impression of rollup was quite positive for me, it may not exceed webpack in the general set of features (but it all depends on the purpose of use), but, in my opinion, it is easier and easier to learn. Not to mention, such features announced by the developers as speed of work, more efficient layout of the output code (tree-shaking), etc. But the most important indicator for me is that it coped with all the needs of my projects, which I’ll write about in the following articles, where I used it quite flexibly.