Monday, 5 February 2024 - Amsterdam

The basics of publishing on NPM

Rollup rollupjs.org makes it easy to bundle the library code into different bundle types.

Why different bundles?

The code will be packaged as:

These each have different use cases depending on the developer looking to use this library.

Common JS

CommonJS is typically used on the server, think NodeJS App using the require() syntax.

ECMA Script modules

These are often imported in a client side app. Example: a ReactJS app created with for example create-react-app. The library can easily be imported using the import test from "test" syntax.

Universal Module definition

Here the typical usecase is a plain website that uses the <script src="test"></script> syntax. Bundling for UMD typically also needs a name value. The library is accessible through a global object. Example: window.test if “test” was the name provided when bundling.

Getting started

Create a new empty folder where the project will live. Inside it create the folders src and dist. Raw dev code is stored in src while the compiled bundles live in dist.

NPM init

Initialise the project with the following:

# initialise the npm project
npm init -y

The y flag forces the default answer to all the NPM questions. The name property will use the name of the project folder. I’ll use “test-component” for mine.

Installing Rollup

Rollup is a development dependency. It isn’t bundled with the production code.

# Install Rollup as a development dependency
npm i -D rollup

The following configuration file helps us to specify where the bundles will be stored. For the UMD bundle a name property is required. In this case react and react-dom are listed as dependencies. These will be used in a follow-up article where the component is converted into a ReactJS component.

// rollup.config.js

import resolve from "@rollup/plugin-node-resolve";
import { babel } from "@rollup/plugin-babel";
import autoprefixer from "autoprefixer";
import postcss from "rollup-plugin-postcss";

const dist = "dist"; // output folder

export default {
    input: "src/main.js",
    external: ["react", "react-dom"], // specify external dependencies

    // Define the output bundles that will be created.
    // Each array entry is a bundle.
    output: [
        {
            file: `${dist}/bundle.cjs.js`,
            format: "cjs", // CommonJS
            exports: "auto", // How to handle exports (details below)
        },
        {
            file: `${dist}/bundle.umd.js`,
            format: "umd", // Universal Module Definition
            name: "reactRollup", // The name that will be used to attach it to the global object
            exports: "auto",
            globals: {
                react: "React",
            },
        },
        {
            file: `${dist}/bundle.esm.js`, // ECMAScript Module
            format: "esm",
            exports: "auto",
        },
    ],
    plugins: [
        resolve(), // find in node_modules
        babel({ exclude: "node_modules/**", babelHelpers: "bundled" }), // convert new JS syntax to ES5.
        postcss({
            plugins: [autoprefixer()],
            extract: false,
            modules: true,
        }),
    ],
    external: ["react", "react-dom"], // Specify external dependencies.
};

Modify the package.json and add these three properties.

// /package.json
{
    ...,
    "main": "dist/bundle.cjs.js",
    "browser": "dist/bundle.esm.js",
    "module": "dist/bundle.umd.js",
}

NPM always requires the main property. It is pointing to the location of the generated CommonJS file. The browser property indicates where the ECMAScript module can be found. Last is the module pointing to it’s corresponding file. This will allow anyone to use our library from https://unpkg.com/

At the beginning of the configuration file certain packages are imported. Those need to be installed, along with a few other supporting packages. Run the following command to install them as development dependencies.

# run in root directory

npm i -D @rollup/plugin-node-resolve @rollup/plugin-babel autoprefixer rollup-plugin-postcss @babel/core postcss

exports: "auto" controls how the export statement in a module is handled. More information on exports: "auto" here: https://www.npmjs.com/package/@rollup/plugin-commonjs

Create a simple main.js file

Testing the setup requires a few more steps. First a main.js file is need. A simple one will do for now.

// src/main.js

const testFunction = function () {
    console.log("this is a simple test function");
};

export default testFunction;

Add the following script to the package.json

// package.json

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

With the package.json saved, execute the script.

# run build
npm run build

This creates the three bundles in the dist directory. Open each of them to the code that was generated there.


Testing the component

Since the function is very simple, only a simple test setup will be done for now. Create an example folder in the root directory and add the file server.js.

# Create an examples folder
mkdir -p examples/server

# Create a file in it called server.js
touch examples/server/server.js

Using NodeJS we can execute server.js. It will import and use our library component thereby proving that it’s working as expected. This example/server directory is effectively a project within our parent project.

We’ll need to init npm here to.

# initialise NPM in the example/server folder

cd examples/server # move into the examples/server older
npm init -y

That will provide a new package.json file. In it add the start script:


// examples/server/package.json

{
    ...,
    "scripts": {
        "start": "node ./server.js",
    },
    ...
}

Running the script now will not do anything since there is nothing in the server.js file. Let’s add a simple console.log statement to ensure all is well.

// examples/server/server.js

console.log("This is the server.js file saying hello");

While still in the examples/server/ folder, save the file and run it.

# examples/server/
# Save the changes to the server.js file and start it

npm start

The log statement shows up directly in the console. Now we’ll need to import our library component.

Importing our library component

While still in /examples/server install the following:

# /examples/server/

npm i ../../../test-component # The name of the directory that the root project is in

Now that the component has been installed, it can be imported in server.js and used.

// /examples/server/server.js

const testFunction = require("test-component");
testFunction();
console.log("This is the server.js file saying hello");

Run the start command again in the examples folder.

# /examples/server
npm start

There should be two console log statements in the terminal.