How to publish a js and css library to npm using rollup

Share on:

NPM Logo

In this guide, we will discuss how we can create a javascript library and publish it to npm. In a previous post we had built a small script for creating a nested tree menu. We will convert that into a npm package here.

Step 1: Initialize the directory and the repo.

Let’s create a folder for the library and add a README.md file there. This would be a good time to do git init and create a remote github repo to track it as well. (If you are new to this, you can check github howto here)

Step 2: Add scripts and stylesheets inside the src folder

In this guide we intend to build the package and then use it from an example project. We will create 2 separate folders. One for the package and one for the example.

Inside the package directory we will create separate folders for the source files and the built files. In modern javascript workflow, you rarely distribute the handcoded source files directly. The source files are passed through bundlers which perform various transformations and optimizations and provide output files which can then be used in the browser or other environments. So we will go ahead and create two folders names src - for the source files and dist - for the output files. The dist folder need not be touched by us. It will be populated by the bundler.

Inside the src folder let’s create 2 subfolders - js and styles to hold the javascript files and the stylesheets respectively.

We will copy the contents of the style tag in our nested tree menu example to src/styles/main.scss. We are using the scss extension so that we can use sass specific features in the future. But for now, our stylesheet is actually just pure css only.


    .listree-submenu-heading {
        cursor: pointer;
    }
    ul.listree {
        list-style: none;
    }
    ul.listree-submenu-items {
        list-style: none;
        border-left: 1px dashed black;
        white-space: nowrap;
        margin-right: 4px;
        padding-left: 20px;
    }
    div.listree-submenu-heading.collapsed:before {
        content: "+";
        margin-right: 4px;
    }
    div.listree-submenu-heading.expanded:before {
        content: "-";
        margin-right: 4px;
    }

    .scrollable-menu {
        height: auto;
        max-width: 800px;
        overflow-y: hidden;
    }

Similarly we will also copy the content of the style tag into src/js/index.js.


    import '../styles/main.scss';

    function listree() {
        const subMenuHeadings = document.getElementsByClassName("listree-submenu-heading");
        Array.from(subMenuHeadings).forEach(function(subMenuHeading){
          subMenuHeading.classList.add("collapsed");
          subMenuHeading.nextElementSibling.style.display = "none";
          subMenuHeading.addEventListener('click', function(event){
            event.preventDefault();
            const subMenuList = event.target.nextElementSibling;
            if(subMenuList.style.display=="none"){
              subMenuHeading.classList.remove("collapsed");
              subMenuHeading.classList.add("expanded");
              subMenuList.style.display = "block";
            }
            else {
              subMenuHeading.classList.remove("expanded");
              subMenuHeading.classList.add("collapsed");
              subMenuList.style.display = "none";
            }
            event.stopPropagation();
          });
        });
    }

    export default listree;

We have made two changes to the content copied from the script tag.

  1. We are importing the main.scss at the top. This will tell our bundler how to locate the stylesheets so that they can be minified.
  2. We have added an export default listree statement at the bottom, which lets us export our function as an ES module.

Step 3: Initialize the npm package

Now let’s call npm init from the root folder of the repo. This will create a package.json which we will edit to modify the main key and add 2 more keys - module and browser. We will also set the files property to whitelist the dist folder to be added to the package.

    {
      "name": "listree",
      "version": "0.0.6",
      "description": "Package to convert a nested list into a tree menu",
      "module": "dist/listree.esm.min.js",
      "browser": "dist/listree.umd.min.js",
      "main": "dist/listree.esm.min.js",
      "repository": {
        "type": "git",
        "url": "git+https://github.com/suryasankar/listree.git"
      },
      "author": "Surya Sankar",
      "license": "MIT",
      "bugs": {
        "url": "https://github.com/suryasankar/listree/issues"
      },
      "homepage": "https://github.com/suryasankar/listree#readme",
      "files": [
        "dist"
      ]
    }

The main key represents the entrypoint of the package. If we were building a library which was meant to be used on the server side using NodeJS, we would point this to a CommonJS output format. But here we are building a purely frontend library which has no use outside the browser context. So we will set it to point to the ES module format output - which would make it accessible via import statements.So the value is set to dist/listree.esm.min.js. The file doesn’t exist yet and we will generate it using our bundler in the subsequent steps.

The module entry point is meant to be used to point to ES module output specifically. This is useful in the cases when the main is set to point to a CommonJS output. In our case we pointed main already to an .esm file. But anyway we will set the module property also to point to dist/listree.esm.min.js.

The browser entry should point to the script file that can be loaded directly in a browser script tag. We will set this to dist/listree.umd.min.js where umd stands for universal module definition.

Both the entrypoint files - esm as well as umd will be generated using a bundler as follows

Step 4: Install rollup

We are going to use rollup as the bundler for generating our package distribution files. There are various bundling systems available - webpack being the most popular right now. But rollup is more suited for generating redistributable libraries.

    npm install --save-dev rollup rollup-plugin-scss rollup-plugin-terser

Apart from rollup, we are also installing 2 more plugins here - one for converting scss to css and the terser plugin for minimizing the generated files

Step 5: Create a rollup config js file

We instruct the rollup bundler using a rollup.config.js file

    import { terser } from "rollup-plugin-terser";
    import scss from 'rollup-plugin-scss'
    import pkg from './package.json';

    export default {
        input: 'src/js/index.js',
        plugins: [
            terser(),
            scss({
                output: 'dist/listree.min.css',
                outputStyle: "compressed"
            }),
        ],
        output: [
            {
                name: 'listree',
                file: pkg.browser,
                format: 'umd',
            },
            { 
                file: pkg.module,
                format: 'es' 
            },         
        ],
    };

In the above config, we are specifying the input as the index.js we had created inside src/js. We don’t have to specify a separate input for css since we have already imported the styles inside index.js itself.

We have also configured 2 different outputs, each of which corresponds to one of the entrypoints specified in package.json. Just by mentioning the formats like above, rollup will take care of converting our source into that format.

We also need to minify the generated output files for distribution, which is done by the terser plugin. And finally we generate a minified output css by using the scss plugin.

Step 6: Build the output files

Now that all the configuration is done, we can call the rollup command line script with various options. Calling rollup -c will build the package. But instead of directly calling it, we will instead set the commands corresponding to various scripts in the package.json itself by specifying a scripts array as follows

    {

      "scripts": {
        "build": "rollup -c",
        "watch": "rollup -c -w"
      }
    }

Now we can just call npm run build and it will generate all the output files inside dist/ folder. We now need to verify it works

In order to check how the package works when it is imported by another application, we can create a sample application and import this package.

We will create a file umd.html inside the examples folder. Let’s copy the following html content in the file

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" type="text/css" href="./node_modules/listree/dist/listree.min.css"/>
        <title>Listree Example</title>
      </head>
      <body>
        <ul class="listree">
            <li>
                <div class="listree-submenu-heading">Metrics</div>
                <ul class="listree-submenu-items">
                <li>
                    <div class="listree-submenu-heading">Daily Metrics</div>
                    <ul class="listree-submenu-items">
                    <li>
                        <div class="listree-submenu-heading">Daily Order Metrics</div>
                        <ul class="listree-submenu-items">
                        <li>
                            <div class="listree-submenu-heading">Categorywise Daily order metrics</div>
                            <ul class="listree-submenu-items">
                            <li>
                                <a href="">Categorywise daily order count</a>
                            </li>
                            <li>
                                <a href="">Categorywise daily bookings</a>
                            </li>
                            </ul>
                        </li>
                        <li>
                            <div class="listree-submenu-heading">Storewise Daily order metrics</div>
                            <ul class="listree-submenu-items">
                            <li>
                                <a href="">Storewise daily order count</a>
                            </li>
                            <li>
                                <a href="">Storewise daily bookings</a>
                            </li>
                            </ul>
                        </li>
                        </ul>
                    </li>
                    <li>
                        <div class="listree-submenu-heading">Daily Invoice Metrics</div>
                        <ul class="listree-submenu-items">
                        <li>
                            <div class="listree-submenu-heading">Categorywise Daily invoice metrics</div>
                            <ul class="listree-submenu-items">
                            <li>
                                <a href="">Categorywise daily invoice count</a>
                            </li>
                            <li>
                                <a href="">Categorywise daily revenue</a>
                            </li>
                            </ul>
                        </li>
                        <li>
                            <div class="listree-submenu-heading">Storewise Daily invoice metrics</div>
                            <ul class="listree-submenu-items">
                            <li>
                                <a href="">Storewise daily invoice count</a>
                            </li>
                            <li>
                                <a href="">Storewise daily revenue</a>
                            </li>
                            </ul>
                        </li>
                        </ul>
                    </li>
                    </ul>
                </li>
                <li>
                    <div class="listree-submenu-heading">Monthly Metrics</div>
                    <ul class="listree-submenu-items">
                    <li>
                        <div class="listree-submenu-heading">Monthly Order Metrics</div>
                        <ul class="listree-submenu-items">
                        <li>
                            <div class="listree-submenu-heading">Categorywise Monthly order metrics</div>
                            <ul class="listree-submenu-items">
                            <li>
                                <a href="">Categorywise monthly order count</a>
                            </li>
                            <li>
                                <a href="">Categorywise monthly bookings</a>
                            </li>
                            </ul>
                        </li>
                        <li>
                            <div class="listree-submenu-heading">Storewise Monthly order metrics</div>
                            <ul class="listree-submenu-items">
                            <li>
                                <a href="">Storewise monthly order count</a>
                            </li>
                            <li>
                                <a href="">Storewise monthly bookings</a>
                            </li>
                            </ul>
                        </li>
                        </ul>
                    </li>
                    <li>
                        <div class="listree-submenu-heading">Monthly Invoice Metrics</div>
                        <ul class="listree-submenu-items">
                        <li>
                            <div class="listree-submenu-heading">Categorywise Monthly invoice metrics</div>
                            <ul class="listree-submenu-items">
                            <li>
                                <a href="">Categorywise monthly invoice count</a>
                            </li>
                            <li>
                                <a href="">Categorywise monthly revenue</a>
                            </li>
                            </ul>
                        </li>
                        <li>
                            <div class="listree-submenu-heading">Storewise Monthly invoice metrics</div>
                            <ul class="listree-submenu-items">
                            <li>
                                <a href="">Storewise monthly invoice count</a>
                            </li>
                            <li>
                                <a href="">Storewise monthly revenue</a>
                            </li>
                            </ul>
                        </li>
                        </ul>
                    </li>
                    </ul>
                </li>
                </ul>
            </li>
            <li>
                <div class="listree-submenu-heading">Settings</div>
                <ul class="listree-submenu-items">
                <li>
                    <div class="listree-submenu-heading">Personal Settings</div>
                    <ul class="listree-submenu-items">
                    <li><a href="">Password Settings</a></li>
                    <li><a href="">Viewing Preferences</a></li>
                    </ul>
                </li>
                <li>
                    <div class="listree-submenu-heading">Org Settings</div>
                    <ul class="listree-submenu-items">
                    <li><a href="">Teams</a></li>
                    <li><a href="">Billing</a></li>
                    </ul>
                </li>
                </ul>
            </li>
        </ul>
        <script src="./node_modules/listree/dist/listree.umd.min.js"></script>
        <script>
            listree();
        </script>
      </body>
    </html>

Note that the file above references the js and css files from node_modules. But we haven’t published our package to npm yet. So how do we make sure that the node_modules folder inside examples will contain the listree package?

NPM provides the command npm link just for this. If you are familiar with python’s package environment, this has the same effect as running python setup.py develop from inside a package folder. It essentially installs the package in whichever place you want to test it from - in the development mode. So if any change is made in the package code, that will be instantly available for the invoking application to test as well. This ensures that we don’t have to publish every time we want to test a small change we have made. So what we are going to do instead is to link the package locally.

So first we will run npm link inside our package folder. Once this command succeeds the package is linked to the global node modules folder in the development machine.

Now in order to use our package in another package that we are developing, we will go inside the examples folder and run npm link listree there. This will create a node_modules folder inside the examples folder with listree package included inside. On opening the examples/umd.html file in browser, we can see that the functionality works as expected.

Step 8: Testing the import of the package as an ES6 module by using webpack

In order the check the package’s behavior as an ES6 module, we will create a src/index.js file in the examples folder with the following content


import listree from 'listree';


listree();
console.log("Initialized My Listree");

In order to make this script loadable via a script tag, we will use webpack to transform and bundle it. We use webpack instead of rollup here because webpack is more suited for application bundling out of the box (while rollup is more suitable for library bundling) and it is more appropriate to test our package’s behavior when it is bundled via webpack since webpack is the most commonly used package bundler by web developers.

We will first have to install webpack and webpack-cli by running npm install -g webpack webpack-cli

Then we add a webpack.config.js like this

    const path = require('path');

    module.exports = {
      mode: 'development',
      entry: './src/index.js',
      output: {
        filename: 'mylistree.js',
        path: path.resolve(__dirname, 'dist')
      }
    };

If we run webpack, it will create a dist/mylistree.js file. We will now copy our umd.html to examples/es.html and replace the script tag at the bottom which refers to the listree.umd.min.js with a script tag which refers this dist/mylistree.js file. That is, we will replace this

    <script src="./node_modules/listree/dist/listree.umd.min.js"></script>
    <script>
        listree();
    </script>

with this -

    <script src="dist/mylistree.js"></script>

In the second case, we don’t need to invoke the listree() function as we have already invoked it inside our index.js which got bundled as mylistree.js. Now if we open examples/es.html in the browser, we can see that the functionality works as expected.

Note that we have checked both the html files by loading them directly using file:// protocol. If we want to test them in the context of them being served via a webserver, we can use webpack’s html and devserver plugins to do the same. But let’s keep that as a task for a future post for the sake of brevity.

Step 9: Push to github repo

Now that we have completed creating the package, let’s also do a push to github. We also need to edit the .gitignore to exclude the dist and node_modules folders.

    node_modules/
    dist/

It is ok to version commit the package-lock.json file though.

Step 10: Publish the package to npm

To do this, we will first create an account on https://www.npmjs.com/ ( only if we don’t already have one). Then on the local development machine, we execute the command npm login. This will ask us to enter the username, password and email. Once entered, we are ready to publish the package.

By just calling npm publish inside the package folder and we can see the package getting published to the npm repository.

Step 11: Use the published package in the html

A great thing about publishing to npm is that our package gets CDN support out of the box. Publishing a package to npm makes it automatically available via various content delivery networks like Unpkg, CDNJS and JSDelivr. We can choose any of these. For example, if we decide to use unpkg, we can access the umd js version of our package like this - https://unpkg.com/listree/dist/listree.umd.min.js. We can test this by modifying both the css and the js tag urls in our example html files to point to this CDN links instead of the local files, and we will see that the functionality still works as expected.

Thus we have created and published a package to NPM repository and successfully used the same in an application. That concludes this tutorial. The code of the package created for this tutorial is available at https://github.com/SuryaSankar/listree. Please consider starring the repo if you find the package or this tutorial useful. The package itself is available for use in npm under the name listree here. You can install and use it by just calling npm install listree in your project.

comments powered by Disqus