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
ornpm
for package management; Iāll be usingnpm
but the steps I list should also work foryarn
- 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-deps
3
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ā!
- 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.ā©
- 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 yourstart
stanza and explicitly install the dependencies it mentions.ā© - 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.ā©