# Chapter 1: Tooling for web games in 2026 (/guides/handmade-web-games/tooling-for-web-games-in-2026)





Although tooling in the JS ecosystem moves fast, a small, well-chosen toolchain can hold up well over time. In this chapter you'll get a solid, reusable starting point with minimal tooling from which you can build just about any browser game. Our examples use [Bun](https://bun.sh/) to install dependencies and run scripts, so if you do not have it installed, start there. Or if you already have a preferred runtime, alternatives like Node and Deno are fine substitutes.

## The bundler [#the-bundler]

**[Vite](https://vite.dev/)** 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-the-project]

Scaffold out a minimal app by running the following command:

```bash
bun create vite
```

Pick the "vanilla" option and follow the rest of the prompts.

Your project should look roughly like:

<Files>
  <File name="index.html" />

  <File name="package.json" />

  <Folder name="public">
    <File name="favicon.svg" />

    <File name="icons.svg" />
  </Folder>

  <Folder name="src">
    <Folder name="assets">
      <File name="hero.png" />

      <File name="typescript.svg" />

      <File name="vite.svg" />
    </Folder>

    <File name="counter.ts" />

    <File name="main.ts" />

    <File name="style.css" />
  </Folder>

  <File name="tsconfig.json" />
</Files>

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

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

### Optional configuration tweaks [#optional-configuration-tweaks]

#### Update `tsconfig.json` [#update-tsconfigjson]

<Scrollycoding>
  <slot>
    <>    </>
  </slot>

  <slot path="steps">
    Vite scaffolds a sensible default [`tsconfig.json`](https://www.typescriptlang.org/tsconfig) to tell the TypeScript compiler how to handle your code. These defaults are fine as-is, but I recommend one small update...
  </slot>

  <slot path="steps">
    Add a `paths` alias to your project's source root so that instead of `import example from '../../../../something'` you'll be able to simply write `import example from '~/something'`. This becomes more useful as your project grows, as it allows you to reorganize your files without messing up the import paths.
  </slot>
</Scrollycoding>

#### Update `vite.config.ts` [#update-viteconfigts]

Next, install [`vite-plugin-image-optimizer`](https://github.com/FatehAK/vite-plugin-image-optimizer) <small className="opacity-75">\[GitHub]</small> and its peer dependencies [`sharp`](https://github.com/lovell/sharp) and [`svgo`](https://github.com/svg/svgo). The plugin will automatically compress any image assets added to your project source at build-time.

```bash
bun add vite-plugin-image-optimizer sharp svgo
```

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

<Scrollycoding>
  <slot>
    <>    </>
  </slot>

  <slot path="steps">
    Vite generates a minimal config to get you started.
  </slot>

  <slot path="steps">
    Add the image optimizer plugin you just installed. Import `ViteImageOptimizer` and pass it into the `plugins` array.
  </slot>

  <slot path="steps">
    <>
      Enable [source maps](https://developer.mozilla.org/en-US/docs/Glossary/Source_map) <small className="opacity-75">\[MDN]</small> with `sourcemap: true`.

      <details>
        <summary>
          Why? 

          *(A philosophical detour on source maps)*
        </summary>

        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...

        <WordWrappedCode title="example.min.js" lang="js">
          {`!function(e,t,l,n){const c=n/100,o=48*c;e.fillStyle="#1e1e2e",e.fillRect(t,l-12,48,6);const f=c<.3;e.fillStyle=f?"red":"green",e.fillRect(t,l-12,o,6)}(document.createElement("canvas").getContext("2d"),0,16,50)`}
        </WordWrappedCode>

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

        ```js title="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](https://adarkroom.doublespeakgames.com/) way back in 2014 and realized that building a game like this was *maybe&#x2A; within reach. That game had readable, &#x2A;**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.
      </details>
    </>
  </slot>

  <slot path="steps">
    Enable `tsconfigPaths` so your path alias from the `tsconfig.json` works with Vite.
  </slot>
</Scrollycoding>

#### Update `package.json` [#update-packagejson]

<Scrollycoding>
  <slot>
    <>    </>
  </slot>

  <slot path="steps">
    Vite's default `dev` script uses NodeJS instead of Bun as the runtime.
  </slot>

  <slot path="steps">
    Change Vite's default `dev` command to `bunx --bun vite` to make Vite run on Bun instead of Node during development. [(Why?)](https://bun.com/docs/guides/ecosystem/vite)
  </slot>
</Scrollycoding>

## Start the dev server [#start-the-dev-server]

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

```bash
bun dev
```

Then open the URL displayed in your terminal, which will probably be like [`http://localhost:5173`](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 [#add-some-starter-code]

### Update `main.ts` [#update-maints]

<Scrollycoding>
  <slot>
    <>    </>
  </slot>

  <slot path="steps">
    Start from your placeholder:
  </slot>

  <slot path="steps">
    Create a new `canvas` element and attach it to `document.body`.
  </slot>

  <slot path="steps">
    Read the `width` and `height` of the canvas so we can fill it. `getBoundingClientRect` returns an object with many position-related properties.
  </slot>

  <slot path="steps">
    Fill the canvas with a blue rectangle. We'll cover this more in Chapter 2.
  </slot>
</Scrollycoding>

You should see a blue rectangle in the top-left corner. That's the canvas at its default size. The last step is to make it fill the window.

### Update `index.html` [#update-indexhtml]

<Scrollycoding>
  <slot>
    <>    </>
  </slot>

  <slot path="steps">
    Start from Vite's generated HTML.
  </slot>

  <slot path="steps">
    Add an inline `<style>`. This stretches the canvas to fill available space and ensures there won't be scrollbars.
  </slot>
</Scrollycoding>

If you see a full-page blue rectangle, you have a working starting point for just about any web game. Before moving on, initialize your git repository if you haven't already. If you're new to version control, [this beginner's guide to git](https://webtuu.com/blog/04/a-laymans-introduction-to-git) is a good place to start.

## Up next [#up-next]

We'll cover painting to the canvas using `CanvasRenderingContext2D` in [chapter 2 →](/guides/handmade-web-games/intro-to-canvasrenderingcontext2d).
