All Articles

Getting Started with NodeCG: Setting Up Your Development Environment

As the next edition of The Race Against Time draws nearer, I’m reminded that there’s always so much work to be done. There are lots of different responsibilities to running the event, but the one that’s most familiar to me is overlay development 😅

I’ve talked a little bit about overlay development before, and today I’m going to dig into the details of how you can develop your own overlay—or at least how to get started.

A neat thing about the development setup is that it should mostly be the same for other frontend development!

A few things before I get started:

  • I’ve committed all these changes to GitHub if you want to see the steps in order
  • I’m assuming that you’re familiar with yarn or npm for package management; I’ll be using npm but the steps I list should also work for yarn
  • Most of the work today sets up a general front-end development environment. I’ll be talking about the NodeCG-specific bits later, but you can also checkout more details on getting an environment setup in the NodeCG docs
  • Some of the libraries I use in my setup aren’t quite ready for primetime (i.e. they’re nightly releases). As a result, I had to do some non-standard things to get the libraries to cooperate. I’ll try to call these out as I go.

With that being said, let’s get everything started by going to whatever folder you’re going to start developing in and npm init.

Parcel (2)

Parcel is a web application bundler which is intended to be fast and have zero-configuration. We’ll be using it to collect all and bundle our overlay dependencies, and to serve our dependency files during development. It supports hot module replacement (HMR) which will really help us as we make changes iteratively.

npm install --save-dev parcel@nightly

At the time of writing, Parcel 2 hasn’t been released yet, and in testing the beta version had some issues for our particular cases, so we’ll be installing the nightlies.1

NodeCG packages are also called bundles and expect there to be top-level folders for graphics (Interactive elements that will appear on screen), dashboard (Controls for our interactive elements for an internal dashboard) and extensions (for backend code). For now, we won’t worry about extensions or dashboard and we’ll set up our system for graphics.

We’ll start by creating some dummy files in our project at src/graphics:


<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8" />
    <div id="container">Nothing here</div>
    <script src="./index.tsx"></script>


interface User {
  id: number;
  name: string;

const user: User = {
  id: 0,
  name: "Guardian",

Lastly, we need to make a small change to our package.json to make it easier to run our Parcel example:

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

This will tell parcel to bundle and serve any HTML files in whatever folder they were in at the project root. That means that our file will be served at localhost:1234/graphics/index.html.

Let’s try it out by running npm run start. Eventually, the output should say something like this2:

ℹ️ Server running at http://localhost:1234
✨ Built in XXXms

And if you check out your graphic, it should render (in all it’s underwhelming glory)! 🤣

You’ll also notice that even though we didn’t configure anything, Parcel automatically converted our TypeScript to JavaScript. Magical~ ✨

Auto-magic conversion is good, but what if we want to do more with TypeScript?

Getting Serious About TypeScript

By default, Parcel uses babel to convert TypeScript to JavaScript. I don’t have strong feelings about using or avoiding Babel, but in the Parcel documentation you can see that with the initial setup, Parcel won’t do any typechecking and that sucks. Why bother with types if they aren’t going to be checked?! 🙃

So, we’re going to fix that (and thankfully, the same doc has details on how to make changes). We need to do a few different things:

  • Install typescript and use it for Parcel transpilation**
  • Install the TypeScript validator so Parcel validates types (and, if needed, fails) after bundles are generated

Let’s start by installing our packages:

npm install --save-dev typescript@^4.2.0 @parcel/transformer-typescript-tsc@nightly @parcel/validator-typescript@nightly

We’re specifically installing typescript 4.2+ to help us with some steps later. If npm starts complaining about unmet peer dependencies, you can ignore them via --legacy-peer-deps3

Next we need to tell Parcel to use TypeScript. In .parcelrc:

  "extends": "@parcel/config-default",
  "transformers": {
    "*.{ts,tsx}": ["@parcel/transformer-typescript-tsc"]
  "validators": {
    "*.{ts,tsx}": ["@parcel/validator-typescript"]

…And we need to tell the TypeScript compiler which files to include in transpilation. In tsconfig.json:

  "include": ["src/**/*"]

If we run Parcel again, we probably won’t notice any difference… unless we purposely make some invalid TypeScript, so let’s do that!

In index.tsx, let’s change our user:

const user: User = {
  name: "Guardian",
  id: 0,
  banana: "true"  // Not a real property!

If we try to run Parcel now (or validate via npx tsc --noEmit) then our TypeScript transpilation will fail!

error TS2322: Type ’{ id: number; name: string; banana: string; }’ is not assignable to type ‘User’. Object literal may only specify known properties, and ‘banana’ does not exist in type ‘User’

Fantastic! We’ve got TypeScript running, what else would be useful? Maybe some UI library, since we’re going to be making graphics?


You can pick whatever tools you like, but I decided to use (P)react because I’m familiar with React. Preact is an API compatible alternative to React which will help us to write graphics for our overlay. If you’re curious about the differences between React and Preact there are more details here.

Fortunately, setting up Preact is fairly straightforward and we can follow their documentation! 👍

…But since you’re here, let’s list all the steps.

Install preact: npm install --save-dev preact@^10.0.0

Add an alias for preact in package.json so that libraries will use preact as a dependency instead of react:

"alias": {
  "react": "preact/compat",
  "react-dom/test-utils": "preact/test-utils",
  "react-dom": "preact/compat"

And lastly, we’ll write a really simple component in index.tsx to verify everything is working:4

// Previous code above
import { h, render } from "preact";

const container = document.getElementById("container");

  <div>{} (ID: {})</div>,

I’m using JSX syntax here (which is effectively HTML in JavaScript), but if you don’t want to use it Preact outlines some alternatives here. If you do decide to use it, you’ll likely see the following error:

error TS17004: Cannot use JSX unless the ‘—jsx’ flag is provided

Fortunately, the documentation has us covered again and we just need to tell TypeScript to use JSX syntax in out tsconfig.json:

"compilerOptions": {
  "jsx": "react-jsx",
  "jsxImportSource": "preact",
  "skipLibCheck": true,

And voila! Our tools are cooperating again, and if we run Parcel again we should see our new, slightly-less-underwhelming graphic.

…But can we do more? 🤔💭

Probably! Even aside from getting into NodeCG, there’re still some useful steps that I ran out of time to cover in terms of setting up an environment. Hopefully this was helpful for you and I’ll try to get to those parts in a follow-up post! 😅

2021-03-18: The next step is now covered in “Cleaning up the environment”! 2021-05-31: I’ve learned a few things about Parcel so it’s worth following this up with “Parcel Problems”!

  1. Why use Parcel 2 instead of Parcel 1? I honestly don’t remember. Parcel 1 can’t serve different globs to different directories, but I don’t think that was the only reason.
  2. Parcel may also automatically install some dependencies, or try to install them and fail. If you don’t want this behaviour, you can add --no-autoinstall to the end of the line in your start stanza and explicitly install the dependencies it mentions.
  3. Generally, you should always ensure that peer dependencies are met, but from what I’ve observed the peer-dependencies for Parcel’s typescript tools are really far behind, and newer versions definitely worked for me.