mcpField
C
client-side-js

client-side-js

github.com/val-town开发1041 次安装5 周前更新
代码仓库

关于 client-side-js

面向 Val Town 开发者的客户端 JavaScript 技能,无需构建步骤即可向浏览器提供模块。内容涵盖转译、导入、不可变缓存和验证。

主要功能

  1. 01提供并转译 TS、TSX 和 JSX
  2. 02使用带版本的不可变文件 URL
  3. 03通过明确 URL 解析浏览器导入
  4. 04验证响应类型和浏览器错误

适用场景

  1. 01无需打包器构建浏览器界面
  2. 02从 Val 提供本地模块和资源
  3. 03缓存客户端文件以供重复访问

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/val-town/plugins --skill client-side-js

安装后在 Claude Code 中输入/client-side-js确认已加载。

详细介绍

客户端 JavaScript

Val Town 没有构建步骤,也没有打包器。客户端模块就是 Val 中的一个文件,通过 HTTP 提供服务;Val Town 会在每次请求时转译该文件。只需让 <script type="module"> 指向一个返回该文件的路由,浏览器就会运行它。无需进行任何配置(没有 webpack/vite/esbuild)。

提供模块服务

std/utils 中的 serveFile 会读取文件,并使用正确的 Content-Type 提供服务。对于 .ts、.tsx 和 .jsx 文件,它会转译为 JavaScript——移除类型、编译 JSX——并以 text/javascript 提供服务。你提供的是源文件;浏览器收到的是可运行的 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"));

然后从 HTML 中加载它:

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

服务路径和文件所在位置都由你决定。常见的快捷方式是使用通配符,一次提供整个目录中的模块和资源:

app.get("/client/**/*", (c) => serveFile(c.req.path));

serveFile 默认使用当前 Val。如果你从非入口文件调用它,且路径无法解析,请将 import.meta.url 作为第二个参数传入。

默认方式:带版本号、不可变缓存的模块

serveImmutableFile 可以利用浏览器的不可变缓存加快 Val 前端速度;发布时会递增 Val 的版本号,从而自动使缓存失效。实测:重复访问耗时从 665ms 降至 157ms,且无需请求任何资源。

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

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

在永不缓存的 HTML 外壳中,为入口模块添加版本号: immutableFileUrl("/frontend/index.tsx") → /__immutable/42/frontend/index.tsx (42 = Val 当前的版本号)。相对导入会在相同前缀下解析,因此只需为入口添加版本号——一个路由和一个带版本号的 URL 就能覆盖整个客户端依赖图。

  • 发布后,旧版本 URL 会返回 404(类似 Next.js 的构建资源);重新加载即可获取新版本。
  • 想要为现有 Val 添加此功能,又不修改外壳?也可以让旧文件路由指向 serveImmutableFile——裸路径会重定向(302)到带版本号的路径,每次页面浏览仅需一次重定向。
替代方案:直接从 esm.town 提供服务

每个 Val 文件都有一个公开的 esm.town URL,可按需转译,因此你可以跳过 serveFile,直接让脚本指向该 URL:

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

通常更推荐使用 serveFile,因为模块会通过你控制的路径从同源提供服务,而且你无需硬编码自己的 Val URL。

浏览器如何解析导入

转译器不会打包或重写导入——只会移除类型和 JSX。因此,客户端模块中的每个导入都必须是浏览器可以通过 URL 获取的内容:

  • 本地导入需要显式扩展名。 import { x } from "./util.ts" 会解析为 /util.ts(或相对于所提供服务的路径),并且该文件也必须通过同一路由或通配符提供服务。省略扩展名(./util)会返回 404。

  • 第三方依赖需要完整的 ESM URL。 像 import React from "react" 这样的裸模块说明符无法在浏览器中解析。请从 esm.sh 等 CDN 导入,并固定版本:

    import { createRoot } from "https://esm.sh/[email protected]/client";
    

如果想在客户端代码中使用裸模块说明符,也可以在 HTML 中添加 导入映射。

此模式适用于任何客户端代码——React、原生 DOM 脚本、canvas 游戏循环、Alpine、htmx。区别仅在于导入内容;对于没有依赖的普通 .ts 模块,完全无需从 CDN 加载任何内容。

React 特别说明

将所有 React 系列导入固定为相同版本(18.2.0),并在依赖 React 的库上添加 [email protected],[email protected]。副本版本不一致会导致 Cannot read properties of null (reading 'useState')。JSX 和样式约定请参阅 react-ui 技能。

注意事项

  • 不要把应用逻辑写在内联 <script> 代码块或模板字符串 HTML 中。 将客户端代码放在真正的 .ts/.tsx 文件中,以便进行类型检查、lint 和代码审查。少量内联引导代码可以接受;应用代码则不行。
  • 不要使用打包器或构建命令。 无需添加构建步骤。
  • Hono 的 serveStatic 在 Val Town 上无法使用——请改用 serveFile。

验证更改

获取模块的 URL(例如 /app.tsx),并确认其返回的是 text/javascript,而不是 HTML 或错误信息。将 https://esm.town/v/std/catch 添加到 HTML 外壳中,以便将浏览器错误传递到 get_logs,然后加载页面并检查日志。两项都完成后,才能报告更改已完成。

评论

支持 Markdown