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:

src/graphics/index.html:

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

src/graphics/index.tsx:

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?

(P)react

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");

render(
  <div>{user.name} (ID: {user.id})</div>,
  container
);

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.ā†©