In my previous articles, I mainly focused on implementing the backend. While working on it, I also put in the minimum effort to create a frontend just enough to visualise the flow of the Reversi game server.

As I mentioned before, I wanted to stick with vanilla TypeScript as much as possible. Over time, though, I ended up with a single app.ts file containing over 500 lines of code, which became messy and unreadable. It was time to split up app.ts so that the code would be better organised and easier to navigate making changes.

The most intuitive way to split things up was to extract interfaces into definitions.ts, move WebSocket-related logic into websocket.ts, and keep the main logic inside app.ts.

One of the benefits of writing unit tests is that it gives you confidence when making change, especially during refactoring. On the backend, I’ve certainly writeen plenty of tests while building the core logic. But as a JavaScript beginner, I had no idea how to test frontend code. Therefore, I spent some time researching unit testing in JavaScript and landed on Jest, a popular JavaScript testing framework.

As soon as I wrote my first test, I hit an error.

  ● Test suite failed to run

    TypeError: Cannot set properties of null (setting 'onclick')

      416 |
      417 | initServerMessageHandlers();
    > 418 | registerButton.onclick = register;

In every programming langauge I’ve used before, like Java, Go and C, there’s a main method/function as a clear point. But there’s no such thing in JavaScript. The whole script is the main function. In my original code, I had set up things like button listeners directly in the global scope. So when Jest ran the test suite, it executed everything in app.ts, includingg code that assumed the DOM already existed. That’s why the error was triggered.

To fix this, I extracted all the logic from app.ts into a new file, game.ts, leaving app.ts with just 3 lines.

import { initServerMessageHandlers, initButtonEvents } from "./game.js";

initServerMessageHandlers();
initButtonEvents();

Now, nothing runs in the global scope. When I run a test on game.ts, only the functions being tested are executed.

But then I ran into another issue: I had a few global variables in game.ts. These could be modified during tests, causing side effects that corrupted other test results. This was something you definitely want to avoid.

To solve this, I created a state object to bundle all the global variables. With a reset() function, all the state variables inside can be reset. By calling reset() inside beforeEach(), I ensured that every test starts with a clean state.

This approach to managing state reminded me of working with Next.js (and React), where I frequently used useState to manage frontend state. If I continue managing Reversi state this way, it’s almost like building my own mini version of useState.

Am I reinventing the wheel? Maybe.

In the early days, developers use vanilla JavaScript to manipulate the DOM. But because JavaScript isn’t a typed language, careless type-related mistakes were common. TypeScript was introduced as a linter to solve this problem by making JavaScript feel like a typed langauge.

As frontend requirements grew more complex. The vanilla JavaScript file became bloated. Developers created boilerplate code to manage complexity. Over time, this boilerplates evolved into JavaScript frameworks. But frontend development evolved so rapidly that it’s become overwhelming, especially for newcomers like me.

On the other hand, many frontend developers today have never worked with vanilla JavaScript. They reach for a framework by default, even for something as simple as a landing page that could be built with just HTML, CSS and simple JavaScript.

Working with vanilla TypeScript helped me understand frontend development much better. While frameworks like React and Next.js do speed things up, they also add complexity, especially in terms of dependencies, where one library might require several others just to function.

I’m not a frontend developer. I build frontend application just for fun and learning. I don’t need speed. I’ll stay as vanilla as possible and focus on learning the fundamentals of frontend development.

Going slow to go fast.


Roadmap

#FeaturesStatus
1Console version of local multiplayer
2Adopt Gorilla for WebSocket
3E2E backend flow from registering to announcing result
4Basic frontend rendering
5Unhappy flows handling, e.g., client(s) leaving unexpectedly
6Cosmetic fine tune for frontend
7Security features
8Production ready
?Others, e.g., User account, Ranking, DB/Redis/AWS adoption