跳到主要内容
知仓学习社ZHICANG

client-side-js

Use when a val needs to ship JavaScript that runs in the browser — React apps, vanilla DOM scripts, canvas/games, htmx/Alpine, or any client-side mo…

不碰外部(只输出文字)无严重或高危命中hashgraph-online/awesome-codex-plugins

它会碰到什么

扫了多少1 个文本文件,5 KB
它会碰到什么不碰外部(只输出文字)
命中总数0 处
命中统计严重 0 · 高 0 · 中 0 · 低 0

这一栏是扫描器报的事实,不是结论。命中多不等于有毒(安全工具、规则库、示例脚本本来就会包含危险写法),命中少也不等于干净。它和你手上的凭据、文件、网络有什么关系,需要你自己看。

技能内容

Client-side JavaScript

Val Town has no build step and no bundler. A client-side module is just a file

in your val that you serve over HTTP; Val Town transpiles it per request. You point

a <script type="module"> at a route that returns the file, and the browser runs

it. There is nothing to configure (no webpack/vite/esbuild).

Serving a module

serveFile from std/utils reads a file and serves it with the correct

Content-Type. For .ts, .tsx, and .jsx it transpiles to JavaScript

strips types, compiles JSX — and serves text/javascript. You serve the source

file; the browser receives runnable JS.

import { serveFile } from "https://esm.town/v/std/utils/index.ts";

// in any HTTP handler — serve a client module at some URL path
app.get("/app.tsx", (c) => serveFile("/app.tsx"));

Then load it from your HTML:

<script type="module" src="/app.tsx"></script>

The path you serve at and the file's location are up to you. A common shortcut is a

wildcard that serves a whole directory of modules and assets:

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.

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

can skip serveFile and point a script straight at it:

<script type="module" src="https://esm.town/v/youruser/yourval/app.tsx"></script>

serveFile is usually preferred because the module is served same-origin from a

path you control, and you don't have to hardcode your own val URL.

How imports resolve in the browser

The transpiler does not bundle or rewrite imports — it only strips types and JSX.

So every import in a client module must be something the browser can fetch as a

URL:

  • Local imports need explicit extensions. import { x } from "./util.ts"

resolves to /util.ts (or relative to the served path) and must be served too —

by the same route or a wildcard. Omitting the extension (./util) 404s.

  • Third-party deps need full ESM URLs. Bare specifiers like `import React from

"react"` don't resolve in the browser. Import from a CDN such as esm.sh, with

versions pinned:

  import { createRoot } from "https://esm.sh/react-dom@18.2.0/client";

An import map

in the HTML is an option if you want bare specifiers in client code.

The same model works for any client code — React, vanilla DOM scripts, a canvas

game loop, Alpine, htmx. Only the imports differ; for a plain .ts module with no

dependencies there's nothing to load from a CDN at all.

React specifics

Pin all React-family imports to the same version (18.2.0) and pass

?deps=react@18.2.0,react-dom@18.2.0 on libraries that depend on React. Mismatched

copies cause Cannot read properties of null (reading 'useState'). See the

react-ui skill for JSX and styling conventions.

What not to do

  • No app logic in inline <script> blobs or template-string HTML. Put client

code in real .ts/.tsx files so it's typed, linted, and reviewable. A few lines

of inline bootstrap are fine; the app is not.

  • No bundler / build command. There is no build step to add.
  • serveStatic from Hono does not work on Val Town — use serveFile.

Verifying changes

Fetch the module's URL (e.g. /app.tsx) and confirm it returns text/javascript,

not HTML or an error. Add https://esm.town/v/std/catch to the HTML shell to pipe

browser errors into get_logs, then load the page and check the logs. Don't report

the change as done without both.

想直接用这个技能?

本站把开放许可(MIT / Apache 等)的技能按仓库打包整理到网盘,点一下转存到你自己的网盘,不用一个个从 GitHub 拉。许可未声明的技能只给原始仓库链接,不打包。