Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/versioned-assets-skills.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@valtown/skills": patch
---

Teach the immutable asset-caching pattern (`serveImmutableFile` / `immutableFileUrl` from `std/utils`) in the `client-side-js`, `http-endpoints`, and `react-ui` skills: the never-cached HTML shell stamps `/__immutable/<version>/...` URLs, served with `Cache-Control: immutable`; publishing bumps the version, invalidating automatically (old-version URLs 404). Measured: repeat visits 665ms → 157ms with zero asset requests.
24 changes: 24 additions & 0 deletions plugin/skills/client-side-js/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,30 @@ app.get("/client/**/*", (c) => serveFile(c.req.path));
`serveFile` defaults to the current val. If you call it from a non-entrypoint file
and paths don't resolve, pass `import.meta.url` as the second argument.

## Default: versioned, immutably cached modules

`serveImmutableFile` makes your val's frontend faster by letting browsers cache
files immutably; publishing bumps the val's version, which invalidates
automatically. Measured: repeat visits **665ms → 157ms with zero asset requests**.

```ts
import { immutableFileUrl, serveImmutableFile } from "https://esm.town/v/std/utils/index.ts";

app.get("/__immutable/*", (c) => serveImmutableFile(c.req.path));
```

In the never-cached HTML shell, stamp the entry module:
`immutableFileUrl("/frontend/index.tsx")` → `/__immutable/42/frontend/index.tsx`
(42 = the val's current version). Relative imports resolve under the same prefix,
so only the entry needs stamping — one route and one stamped URL cover the whole
client graph.

- Old-version URLs 404 after a publish (like Next.js build assets); a reload
picks up the new version.
- Retrofitting an existing val without touching its shell? Also point its old
file route at `serveImmutableFile` — bare paths then 302 into versioned space,
at one redirect per page view.

### Alternative: serve directly from esm.town

Every val file already has a public esm.town URL that transpiles on demand, so you
Expand Down
7 changes: 4 additions & 3 deletions plugin/skills/http-endpoints/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ When using Hono, export `app.fetch` (not `app`):

```ts
import { Hono } from "npm:hono";
import { parseVal, serveFile } from "https://esm.town/v/std/utils/index.ts";
import { parseVal, serveImmutableFile } from "https://esm.town/v/std/utils/index.ts";

const app = new Hono();

app.get("/", (c) => c.text("hello"));

// Serve all frontend files, transpiled, with correct content types
app.get("/frontend/**/*", (c) => serveFile(c.req.path));
// Immutable asset caching (see the client-side-js skill): serves the
// current-version URLs your HTML shell stamps with immutableFileUrl()
app.get("/__immutable/*", (c) => serveImmutableFile(c.req.path));

// View source redirect
app.get("/source", (c) => c.redirect(parseVal().links.self.val));
Expand Down
17 changes: 17 additions & 0 deletions plugin/skills/react-ui/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,23 @@ Then use Tailwind classes directly in JSX:

Avoid inline `<style>` tags, CSS-in-JS objects, or separate `.css` files, unless the user says otherwise.

## Serving assets: versioned + immutable

Serve client modules with `serveImmutableFile` from `std/utils` — browsers cache
them immutably, and publishing bumps the val's version, which invalidates
automatically (full pattern: the `client-side-js` skill). A never-cached
`frontend/root.tsx` shell (hono/jsx) stamps the entry and favicon with
`immutableFileUrl`:

```tsx
// frontend/root.tsx — in the Root() HTML shell:
<script src={immutableFileUrl("/frontend/index.tsx")} type="module" />

// index.ts:
app.get("/", (c) => c.html(Root()));
app.get("/__immutable/*", (c) => serveImmutableFile(c.req.path));
```

## View source link

Every UI val should expose a way for users to see and remix its source. Both parts are required:
Expand Down