Bundling Dependencies

If you’re not familiar with the idea, module bundling is:

[The] process of stitching together a group of modules (and their dependencies) into a single file (or group of files) in the correct order.

Preethi Kasireddy

There’s a healthy ecosystem of module bundlers designed for JavaScript projects, especially projects focused on web development. Even though Nova extensions don’t target a web browser, they do use a JavaScript extension runtime, which means we can use the same bundlers to bring external modules to our projects.

In this guide we’ll use Rollup.js, but if you’re familiar with other bundlers like Webpack or Browserify, the same principles will apply.

Installing devDependencies

Our top-level project will use the rollupjs package and a handful of Rollup plugins:

so let’s install those now using NPM:

npm install --save-dev rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs
  "devDependencies": {
    "@rollup/plugin-commonjs": "^11.0.1",
    "@rollup/plugin-node-resolve": "^7.0.0",
    "rollup": "^1.30.1"
  },
  "dependencies": {
		"chroma-js": "^2.1.0"
  },

Configuring Rollup.js

Next, we need to tell Rollup where to look for our entry point script, and what we want it to do at build time.

Create a new file rollup.config.js a the root of your top-level project:

(nova-novachrome)
 ├── ...
 ├── package.json
 ├── package.lock.json
 ├── README.md
 └── rollup.config.js

then open it and paste the following:

const commonjs = require( "@rollup/plugin-commonjs" );
const resolve = require( "@rollup/plugin-node-resolve" );

module.exports =
{
	input: "src/Scripts/main.js",

	plugins: [
		commonjs(),
		resolve(),
	],

	output: {
		file: "Novachrome.novaextension/Scripts/main.dist.js",
		format: "cjs"
	},
}

What does this do?

Our configuration tells Rollup to:

  • Begin by looking in our source script src/Scripts/main.js
  • Convert any CommonJS-style require statements it encounters to a format Rollup understands
  • Use node_modules to resolve any dependencies
  • Create main.dist.js as the output file inside our extension bundle using the CommonJS format

In a nutshell, we’re compiling our source file and its external dependencies into a single file inside our extension bundle in a format that Nova supports.

Let’s take it for a spin! Open a new Local Terminal tab and run the following command:

$ npx rollup -c

src/scripts/main.js → Extension.novaextension/scripts/main.dist.js...
created Novachrome.novaextension/scripts/main.dist.js in 334ms

If necessary, activate the extension again (Extensions > Activate Project as Extension) then open the extension console:

#663399

It worked! We successfully bundled imported chroma-js into our main.js source file:

const chroma = require( "chroma-js" );
console.log( chroma("#36036a").brighten().hex() );

and compiled it into our extension bundle.

Developing a Workflow

It’s convenient to work in a source file, compile into the extension bundle, and see things work as when the extension automatically reloads.

However, editing source files, switching to a terminal, and running npx rollup -c every time we want to see a change is tedious.

Here are some tips for how to make development a little smoother.

NPM Scripts

Because our top-level folder is an NPM project, we can use all the same tools we’d use during development on non-Nova extension projects.

Let’s install onchange, a package that will watch for changes on disk:

npm install --save-dev onchange

Next, open package.json and add two new scripts, build and watch:

"scripts": {
  "build": "npx rollup -c",
  "watch": "onchange -i \"src/**/*.js\" -- npm run build"
},

Switch to a local terminal and run the watch script:

$ npm run watch

> nova-novachrome@0.1.0 watch /Users/ashur/Developer/nova-novachrome
> onchange "src/**/*.js" -- npm run build

Make a change in src/Scripts/main.js:

chroma.temperature(3500);

and take a peek at the extension console:

#ffc38a

Our watcher sees the change, rebuilds our extension source, causing Nova to reload the extension.


Next…
Part 4: Nova Run Tasks
Previously…
Part 2: Structuring Your Project

This article was last updated on July 27, 2020