All Articles

Getting Started with NodeCG: Parcel Problems

To be completely honest, I haven’t been able to do a lot of NodeCG development… and blog-wise, I haven’t even started talking about how NodeCG actually works!

But in the few times that I’ve been able to sit down and work, I have learned a few things about Parcel that I think are worth calling out, especially since they seem to mostly impact NodeCG development the most. Especially since I first started talking about setup.

Let’s start with the fix, and then explain why it’s needed. 😎👉👉

(Oh yeah, and these changes will also be reflected in my example repository)

The Fix

First, we need to install concurrently1.

npm install --save-dev concurrently

concurrently allows us to run multiple commands… well, concurrently! We’ll also need to change our scripts stanza of package.json. Previously, it looked like this:

"scripts": {
  "start": "parcel serve './src/**/*.html' --dist-dir=."

We’ll want to change it to this:

"scripts": {
  "watch:graphics": "parcel watch 'src/graphics/*.html' --dist-dir='graphics' --public-url='.'",
  "build:graphics": "parcel build 'src/graphics/*.html' --dist-dir='graphics' --public-url='.'"

And lastly, instead of running npm start, we’ll want to run npx concurrently 'npm:watch*'.

And that’s it! There are likely some changes and tweask you’ll want to make if you also have dashboards, and I’ll probably expand on this when I start working on NodeCG extensions, but this should fix a lot of the problems I ran into after actually working in my setup.


I was really confused when I actually went to work in the previous setup. It worked fine when I had graphics, but not when I also had dashboard pages.

More specifically, when I looked at the source code in the generated HTML, the paths were not as I expected. Instead of something like this:

<script src='my-bundle.js'></script>

I was seeing this:

<script src='graphics/my-bundle.js'></script>

What was the issue then 🤔💭

This lead me on a long path of learning about Parcel, specifically its plugin system and entrypoints.

When we run parcel serve './src/**/*.html' --dist-dir=., as in the initial example, that tells Parcel two things:

  1. Put the generated “bundles” in the root (.) directory
  2. Specify a ‘glob’ of HTML files as entry points

Globs are sort of like a regular expression for files and file paths. In our case, we’re saying “give me all .html files in any directory under src“.

Alright, so far so good. But why did it work when there was one folder, but not when there were two folders? Why did different paths get generated in our bundles?

As I dug into the source code, I learned about a subtle behaviour that probably makes sense for other projects, but not for NodeCG.

let entryRoot =
  initialOptions.entryRoot != null
    ? path.resolve(initialOptions.entryRoot)
    : getRootDir(entries);

That snippet is from the code that decides the root path for bundles. As I scanned through the codebase, I eventually discovered that, effectively, only the getRootDir branch is ever reached.

And when digging into that code, I learned that when generating the path for bundles, it will use the root of all the entry points.

Oops 🙃

Now, this isn’t a secret behaviour—It’s technically spelled out in the Module Resolution section of the documentation. But it wasn’t really clear to me what was happening until I tried a whole bunch of things. In fact, Parcel has an alternative way to handle targets it just doesn’t work with globs yet

What that all means is that if you want to have behaviour where the root path is set to just the folder you’re working with, you need to explicitly pass it in:

parcel watch 'src/graphics/*.html' --dist-dir='graphics' --public-url='.'

*sigh* 😫

Anyway, I figured all this out so you don’t have to! I hope this helped out anyone having issues, and maybe even helped to understand Parcel a bit better.

Personally, I’m hoping that I don’t encounter anything like this again in future development. It took up a lot of time unexpectedly, and if I’m being honest, if it happens again, you’ll probably see a blog post about how to get NodeCG setup with something like Snowpack 🤣

  1. You can also use something like npm-run-all if you want; Concurrently seemed just a bit easier for me to use.