Handmade Web Games

Chapter 1: Tooling for web games in 2026

tl;dr: Use Vite with an image optimization plugin.

I'd love to be able to recommend a fully dependency-free path to building web games, but right now without a bit of tooling it's significantly more difficult. So in this chapter we'll set up a simple, reusable starting point from which you can build just about any browser game. Our examples require Node.js with npm. (Alternatives like Bun and Deno also work.)

The bundler

Vite is a bundler. It takes the code from your many source files and assembles them into a single "bundle", hence the name. In 2026, bundlers also do a whole lot more. Vite will also...

  • Transpile TypeScript source code into JavaScript that the browser can run.
  • Run a local dev server with live reloading and hot module replacement.
  • Generate source maps for debugging.
  • Optimize assets for production builds.
  • Allow you to import assets and optionally inline them directly into your bundled code. Images, sound effects, wasm files, and fonts are much easier to work with when using Vite.

Scaffold the project

Scaffold out a minimal app by running the following command:

npm create vite@latest

After naming your project in Vite's CLI, pick the "vanilla" and "TypeScript" options.

$ npm create vite@latest

◇  Project name:
│  asteroids

◇  Select a framework:
│  Vanilla

◇  Select a variant:
│  TypeScript

◇  Install with npm and start now?
│  No

Enter the project directory and install its dependencies:

cd asteroids
npm install

Your project should look roughly like:

index.html
package.json
package-lock.json
counter.ts
main.ts
style.css
tsconfig.json

Go ahead and delete everything in the src/ folder, then add a new placeholder main.ts for us to return to later. You can put whatever you want in there for now.

src/main.ts
console.log("it works!");

Optional configuration tweaks

Update vite.config.ts

Install vite-plugin-image-optimizer [GitHub] and its peer dependencies sharp and svgo. The plugin will automatically compress any image assets added to your project source at build-time.

npm install vite-plugin-image-optimizer sharp svgo

Now create or edit vite.config.ts in your project root:

Default config

Start with a minimal config.

Image optimizer

Add the image optimizer plugin you just installed. Import ViteImageOptimizer and pass it into the plugins array.

Source maps

Enable source maps [MDN] with sourcemap: true.

Why? (A philosophical detour on source maps)

One of the defining characteristics of the web is that you can read the full source code of any webpage you load by simply clicking "View Source" or "Inspect Element" in your browser.

However, modern websites and games use source code minification to reduce bundle sizes, which makes it difficult to learn anything interesting from the source directly.

See for yourself. The following minified code...

example.min.js
loading...

...was the compressed output of this source code:

example.js
// draw a health bar above the character
function drawHealthBar(ctx, x, y, health) {
  const maxHealth = 100;
  const barWidth = 48;
  const barHeight = 6;
  const healthPercent = health / maxHealth;
  const width = barWidth * healthPercent;

  // dark background
  ctx.fillStyle = "#1e1e2e";
  ctx.fillRect(x, y - 12, barWidth, barHeight);

  // green when healthy, red when critical
  const isCritical = healthPercent < 0.3;
  ctx.fillStyle = isCritical ? "red" : "green";
  ctx.fillRect(x, y - 12, width, barHeight);
}

const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");

let currentHealth = 50;
let x = 0;
let y = 16;

drawHealthBar(ctx, x, y, currentHealth);

With source maps, you get the best of both worlds: players can load your game quickly thanks to the minified bundle, and curious game developers can read and learn from your source in their dev tools.

Minification and obfuscation won't stop people from reverse engineering your games, but they will stop curious minds from learning from you. So embrace the open ethos of the web and enable source maps for your games!

To make it personal for a moment: my own interest in building web games started when I clicked View Source on A Dark Room way back in 2014 and realized that building a game like this was maybe within reach. That game had readable, unminified, familiar-looking source code--and very little of it! You could be responsible for sparking a similar moment for the visitor that clicks View Source on your game someday.

Start the dev server

First, start your local dev server so you can see changes live as you make them:

npm run dev

Then open the URL displayed in your terminal, which will probably be like http://localhost:5173. You'll just see a blank page for now. If you open the browser console, you should see the log you added in main.ts.

Add some starter code

Update main.ts

Return to main.ts

Start from your placeholder:

Create and mount a canvas

Create a new canvas element and attach it to document.body.

Measure the canvas

Read the width and height of the canvas so we can fill it. getBoundingClientRect returns an object with position-related properties.

Paint the background

Fill the canvas with a blue rectangle. We'll cover this more in Chapter 2.

You should now see a blue rectangle in the top-left corner, which is the canvas at its default size. Next, we'll expand it to fill the entire window.

Update index.html

Base

Start from Vite's generated HTML.

Add a canvas style block

Add an inline <style>. This stretches the canvas to fill available space and ensures there won't be scrollbars.

If you see a full-page blue rectangle, you have successfully initialized all the tooling you'll need to get started building web games.

Finally, make sure everything builds successfully for production:

npm run build

Up next

We'll cover painting to the canvas using CanvasRenderingContext2D in chapter 2 →.

On this page