Use npm packages in the browser

A convenient way to use CommonJS npm packages in client-side JavaScript using Browserify.

Sometimes you might need to use the functionality already implemented in a “good old” CommonJS npm package in a client-side JavaScript. Unfortunately there’s no require to directly use it there, so another solution is needed.

I recently needed my own human-readable package in a client-side script. Here is one possible convenient way how this can be accomplished using Browserify.

Initial situation

Lets assume you have the following client-side JavaScript that dynamically adds rows to a table of uploaded files and their size given in bytes.

// File: js/upload-utils.js

function createUploadTableEntry(filename, size) {
    var uploadTable = document.getElementById('upload-table');
    var row = uploadTable.insertRow();
    var cellName = row.insertCell();
    cellName.textContent = filename;
    var cellSize = row.insertCell();
    cellSize.textContent = size;
};

Resulting in a table looking like that:

Now it would be great to change the size column to a more readable expression using our recently created package human-readable.

Preparing the code

To do so using Browserify we first have to add the package we want to use to our project.

npm i @tsmx/human-readable --save

Note: Since the package dependency is only needed for creating the browserified JavaScript at development time --save-dev should also be enough in most cases.

Now in our NodeJS project we add a small wrapper script which imports the package and exports exactly the functionality we need in our existing client-side code for building the table. In our case it is a simple function creating a readable string out of an amount of bytes.

// File: browser-utils/human-readable.js

const hr = require('@tsmx/human-readable');

module.exports.getReadableSize = function (bytes) {
    return hr.fromBytes(bytes);
};

This file should be created in a folder which is committed to your code repo but neither deployed in production nor should it be served static as an asset by the server.

In my case the folder is browser-utils which I added to my .gcloudignore to ignore it for production deployment and which is not served static by express – in contrast to the js folder where all client-side scripts reside.

Browserifying it

Once that is done we’re ready to generate a client-side JavaScript by using Browserify. For that you first need to install Browserify.

npm i -g browserify

In our browser-utils folder we then call Browserify to pack our wrapper class and create the client-side script.

browserify human-readable.js --standalone hr >  ../js/human-readable-utils.js

This creates a new script human-readable-utils.js in the static served folder js for client-side usage. Note the standalone option: with this Browserify creates a “named” UMD module that can be used in other modules/scripts. In this case the name is hr. You can omit this option if your client-side script is not used by any other script. Here it is needed because I want to use a function from the generated script human-readable-utils.js in the already existing script upload-utils.js. For more details see the Browserify options docs.

The final file/folder structure looks like that:

path-to-your-app/
├── js/                  # served static e.g. via express.static 
│   ├── ...
│   ├── human-readable-utils.js
│   └── upload-utils.js
├── browser-utils/       # not served, not deployed in production 
│   ├── ...
│   └── human-readable.js
├── app.js
└── package.json

Having the browserified client-side script we can change the existing script and use our exported function via the hr name prefix.

// File: js/upload-utils.js

function createUploadTableEntry(filename, size) {
    var uploadTable = document.getElementById('upload-table');
    var row = uploadTable.insertRow();
    var cellName = row.insertCell();
    cellName.textContent = filename;
    var cellSize = row.insertCell();
    cellSize.textContent = hr.getReadableSize(size); // use browserified function
};

Last thing you have to do is to make sure that the browserified bundle is loaded before the existing code so that it is known and can be used.

<script src="/js/human-readable-utils.js"></script>
<script src="/js/upload-utils.js"></script>

That’s it – we’re done. Reloading the page the npm package is now used to create human readable string for the file sizes.

Improving the development process with watchify

So far so good. We’re now able to use our npm package in the browser in a client-side JavaScript. One drawback is that we always have to browserify the script again once we changed something in the underlying wrapper script. This could easily be forgotten…

To mitigate this issue we can use watchify during development. It’s like a watchdog for changed files which automatically triggers the browserifying process. It takes the same arguments the browserify command does except that -o is mandatory. First install it as a global package.

$ npm i -g watchify

Then in the main folder of project the run it in, e.g. in a VS Code terminal.

$ watchify ./browser-utils/human-readable.js --standalone hr -v -o ./js/human-readable-utils.js
5583 bytes written to ./js/human-readable-utils.js (0.03 seconds) at 8:49:12

Watchify now will create a new browserified script each time the source is saved. To integrate it a bit more in your development and deployment process I suggest to create script entries in package.json for both, one-time browserify and continously watchify.

...
"scripts": {
    "start": "node app.js",
    "build-utils": "browserify ./browser-utils/human-readable.js --standalone hr > ./js/human-readable-utils.js",
    "build-utils-dev": "watchify ./browser-utils/human-readable.js --standalone hr -v -o ./js/human-readable-utils.js"
  },
...

This allows you to access this commands via npm run build-tools and npm run build-tools-dev where needed or also via the VS Code task runner features (F1 –> task –> …).

Additionally you could set-up a task in VS Code to run the watchify process everytime you open a specific folder.

Useful links