How to create compact and efficient javascript using RollupJS

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.htmlinto 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.jsin 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)

    Footnote
    This 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
For more information on the option format, see the Rollup's Wiki .

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 jsthat will contain our generated file main.min.js.

We will see that the package was created correctly by opening build/index.htmlin our browser:

image

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.jsthere 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 webpacksayGoodbyeTo () , 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 .babelrcin 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:

  1. We use 2 spaces instead of 4.
  2. 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:

image

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:

  1. rollup-plugin-node-resolve, which allows you to load third-party modules from node_modules.
  2. 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:

image

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.jsimport 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 -cto 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:

image

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:

image

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.

Also popular now: