Authoring a JavaScript library that works everywhere using Rollup

In this article, we aim to create and publish a library that can be used, without any changes in the code, in both client side and server side applications.

We need to fulfill the following use cases:

  1. The library is written in ES6+, using import and export keywords
  2. The library can be used with a <script> tag
  3. The library can be used in a web application that uses a modern bundler.
  4. The library can be used in a node application.

Technically, this means the library needs to work in the following contexts:

Using a <script> tag:

Using RequireJS:

In a web application using a bundler such as webpack:

In a node application:

Note: In the web application using a bundler, there is no way to import the whole library and call individual functions on it (import lib from 'library'; lib.sayHello();), and that’s completely intentional. We want consumers to call just the bits they use so tree-shaking can do its work and dead code is eliminated when bundling the final application. Remember, in the case of an application using a modern bundler, the consuming web application will also produce a bundle for deployment, and we want that to be as small as possible, so we’ll spare our consumers from having to include code that is not used in their application.

To achieve all this, we’re going to use rollup.js. The main reasons is that Rollup is very fast (although not the fastest), requires minimal configuration, and supports everything we need through its convenient plugin system.

Once our library is written, we will use Rollup to export the code in the following three formats:

  1. UMD (Universal Module Definition): this will support the use of a script tag, and RequireJS. As the consuming app will not be transpiling or bundling the code themselves, we need to provide a version of our library that is minified and transpiled for wide browser support.
  2. ESM (ES2015 Module): this will allow bundlers to import our application, elimintate dead code and transpile it down to the level they choose. We’re still compiling the code, but just providing it in a format that’s convenient for consumers and letting them decide what to do next. This will allow the import keyword to work.
  3. CJS (CommonJS): the format of choice for Node.js. No tree-shaking needed here as code size doesn’t matter as much, this format allows the use of the require keyword in a node application.

For each of these format, we will also provide a source map so consumers can debug the library should they need to.

The first step is to create a project:

Next we need to add some dependencies.

Obviously , we need rollup.

We know that we need to transpile the code for the UMD format, so let’s install babel:

We also need rollup to use babel and to minify the code, so let’s install the necessary plugins to use babel and terser:

$ npm install @rollup/plugin-babel rollup-plugin-terser --save-dev

And finally, we want to be able to use the import/export syntax in our library in the style of node: this allows use to write import fn from './fn' instead of import fn from './fn/index.js' and of course to use modules from the node_modules directory (which we are not doing here).

$ npm install @rollup/plugin-node-resolve --save-dev

The final list of dependencies of our library should look something like this:

We also need a directory for our source code, a configuration file for babel, and a configuration file for rollup:

The babel configuration is going to be very simple, we just need to tell babel that we want to use the most recent version of JavaScript:

For Rollup, we need to import the necessary plugins:

And we’re also going to import the package.json, so we can use the name field when exporting the UMD bundle:

import pkg from "./package.json";

Our rollup.config.js is going to do two things:

For UMD: take the code, process it and run it through babel (transpile) and terser (minify), and export it as a UMD consumable file.

For CJS/ESM: take the code, process it, and export it as an ESM module, and as a CJS module. Remember, in this case, we don’t need to transpile or minify. Node doesn’t need it, and for ESM, the consumer will do that.

In all instances however, we generate a sourcemap.

Note the exports: "named" option in all configs, there’s a longer explanation in rollup’s documentation, essentially this tells rollup that we’re using named exports over default exports. Long story short, this allows the widest possible compatibility, and makes tree shaking happen. If you use a linter, make sure to configure it to favor named exports over default exports (this doesn’t apply to applications, only to libraries, it’s completely fine to use default exports and even mix default/named exports for applications).

The complete rollup file looks like this. And because the name is taken from the package.json, you can actually use this file almost as is, as long as the entry point is src/index.js and the name is set accordingly in the output of the UMD module.

Now that we have our dependencies, configured babel and rollup, time to write the code.

We’re going to layout our files like so:

And the code is going to be extremely simple:

Next we need to call rollup and tell it to do its job. For convenience’s sake, we’re going to create two npm scripts, one for building the library, and a dev task which will recompile the code every time a change is made:

Finally we need to describe how the application is exported, both for npmjs to make it available, and for consumers to use.

We’re going to define three values in package.json:

The files option that tells npm what to pack (this can be tested using npm pack ), the main option that point to a CJS module, and the module option, which while not standard, has become the norm for ESM modules.

And that’s it!

To build the library, just run npm run build and while developing, you can use npm run dev . The export can be tested using npm pack

Testing the library

Using the script tag, simply create an HTML file, and open it in a browser. You will see the word “hello” printed in the console.

Using Requires.JS, create a small webapp and serve it using serve :



The word ‘hello’ will be printed in the console. Once the module is published, the file my-library.min.js will be available from

From node, outside of the library directory, create a js file and require the module by pointing to the my-library directory (not the dist folder!):

If you take it a step further and debug the application, the source map will kick in too!

From a web application using webpack, like a React application:

In the dependencies section of the package.json, simply add this line:

And run yarn install

In src/App.js, import and invoke the hello function only:

Run the React application using yarn start and open the JavaScript console, and you should see the “hello” word printed.

Now to make sure tree shaking works, run yarn build . The React application will be bundled and put into the build directory. If you do a search in files for the hello keyword, you will see that it is in a js file with a long complicated name, but the keyword goodbye cannot be found. This shows that webpack only pulled in the necessary code. And since we are using a named export in our library, consumers of our library cannot write import myLibrary from 'my-library'; and mistakenly import the entire package while only using a fraction of it.

Hope this helps, let me know if you have any questions in the comments!



Previously Paris and Geneva, currently New York. Can also be found at

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store