How to use Bun with p5.js

Use Bun to build a p5.js sketch with TypeScript.

How to use Bun with p5.js
Bun + p5.js

Now that Bun 1.0 has been released I tried using it with p5.js. I will be writing this post as I go along.

In other posts I've shown how to use parcel, typescript and p5js but these required other dependencies to be installed.

I want to try Bun, as you don't need to install anything else other Bun itself.

Bun is a fast, all-in-one toolkit for running, building, testing, and debugging JavaScript and TypeScript, from a single file to a full-stack application.

After installing I scaffolded a new project

mkdir p5-project
cd p5-project
bun create .

As Bun is also a package manager I was able install p5.

bun install p5 @types/p5

As Bun has support for TypeScript I also installed @types/p5 as I want to still use TS.

Now I created a index.ts file and import p5 and created a simple sketch.

import p5 from "p5";

const sketch = function (p: p5) {
  p.setup = function () {
    p.createCanvas(p.windowWidth, p.windowHeight);
    p.background("black");
    p.noLoop();
  };

  p.draw = function () {
    p.strokeWeight(5);
    p.fill("red");
    p.rect(p.width / 2, p.height / 2, 100, 100);
  };
};

new p5(sketch);

To start building I ran the following

bun build --watch index.ts --outdir ./public

As of writing this article I'm not able to find a way to start a development server like webpack-dev-server and it seems this will arrive in "next couple of months" with something described as Bun.App.

The Bun Bundler | Bun Blog
The Bun toolkit gets a new addition. Bun’s fast native bundler is now in beta.

In the meantime we can use Bun itself to create a small webserver to serve the bundled JavaScript, but first I created a index.html file

<!doctype html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <style>
    body {
      margin: 0;
      height: 100vh;
    }
  </style>
  <title>Bun & p5js</title>
</head>

<body>
  <script type="module" src="./src/index.ts"></script>
</body>

</html>
index.html

Notice that ./src/index.ts won't work as is so I created a devServer.ts that  combines Bun.build with Bun.serve into a single module. It bundles the above code in index.ts and then creates a HTTP server to serve the files.

As Bun uses hash in the naming of the output assests, I had to dynamically replace the src/index.js value in index.html with what Bun generates.

This was possible as Bun.build returns a result object with all assets.

const build = await Bun.build({
  entrypoints: ["./src/index.ts"],
  outdir: "./dist",
  naming: "[dir]/[name]-[hash].[ext]",
  sourcemap: "inline",
});

build.outputs.forEach((i) => console.log(i.path));

const hash = build.outputs.find((i) => i.kind == "entry-point")?.hash;

const inputHtml = Bun.file("index.html");
const outputHtml = Bun.file("dist/index.html");
let indexHtmlContent = await inputHtml.text();
indexHtmlContent = indexHtmlContent.replace("src/index.ts", `index-${hash}.js`);
await Bun.write(outputHtml, indexHtmlContent);

Bun.serve({
  development: true,
  port: 3000, // This is the default
  fetch(req) {
    const path = new URL(req.url).pathname;
    if (path === "/") {
      return new Response(Bun.file("./dist/index.html"));
    }

    const file = Bun.file(`dist${path}`);
    return new Response(file);
  },
});

devServer.ts

Then visiting http://localhost:3000 I can see the p5 canvas with a single red rectangle in the center.

Unfortunately every time I update index.ts I have to restart the devServer.ts as the --watch option only watches for changes in devServer.ts.

There doesn't seem to a config option for Bun.build to watch for changes in files configured in entrypoints to then retrigger build.

I could split Bun.build into a seperate file an run it via bun run --watch build.ts but then I won't have access to the BuildOutput in devServer.ts 🤷

Bun has me excited with the future of the JavaScript ecosystem.