Asset bundling

The live seat map and the JSON checkout need a bit of JavaScript on the concert page: the seat picker. Margaret works with esbuild: you build your scripts and styles with esbuild, and Margaret embeds the result in your binary and checks every asset you reference while it builds.

The seat picker

The concert page lists every seat as a checkbox. The seat picker opens the WebSocket from the previous chapter, disables the seats that are taken, pauses the updates while the tab is hidden, and posts the order as JSON when the customer buys:

import "../css/seat_picker.css";

const picker = document.querySelector<HTMLFormElement>(".seat-picker")!;
const remaining = picker.querySelector(".seat-picker__remaining")!;
const liveUrl = new URL(
  `${window.location.pathname}/live`,
  window.location.href,
);

liveUrl.protocol = window.location.protocol === "https:" ? "wss:" : "ws:";

const socket = new WebSocket(liveUrl);

socket.addEventListener("open", () => {
  socket.send(JSON.stringify({ id: 1, method: "watch_seats", params: {} }));
});

socket.addEventListener("message", (event) => {
  const { result } = JSON.parse(event.data);

  remaining.textContent = `${result.remaining} tickets left`;

  for (const seat of result.seats) {
    picker.querySelector<HTMLInputElement>(
      `input[name="seat"][value="${seat}"]`,
    )!.disabled = true;
  }
});

document.addEventListener("visibilitychange", () => {
  socket.send(
    JSON.stringify({
      method: "visibility",
      params: { visible: document.visibilityState === "visible" },
    }),
  );
});

picker.addEventListener("submit", async (event) => {
  event.preventDefault();

  const form = new FormData(picker);
  const seats = form.getAll("seat").map(Number);
  const response = await fetch(picker.action, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ email: form.get("email"), seats }),
  });

  if (response.ok) {
    window.location.href = (await response.json()).order_url;
  }
});

Building with esbuild

Run esbuild from your crate's directory, next to Cargo.toml. Write the output to assets/, and ask for a metafile called esbuild-meta.json:

$ esbuild resources/css/box_office.css resources/ts/seat_picker.ts \
    --bundle --format=esm --outdir=assets \
    --entry-names='[name]_[hash]' --metafile=esbuild-meta.json

The metafile tells Margaret what esbuild built. As soon as it exists, Margaret generates an asset! macro. Run esbuild before cargo build, so the build sees the latest assets.

Using assets in views

asset! takes the path of a source file. Add it to the page's AssetBag, and the layout renders the matching