• Projects
  • Service
  • About
  • branding.bz
  • Podcast
  • Tips
  • FAQ
  • Recruit
  • Download
  • Contact
  • branding.bz(ブランド構築SaaS)
  • DESIGN NOW(デザインメディア)
  • X
  • LinkedIn
  • Spotify
  • Facebook

213-0011 神奈川県川崎市高津区久本3-6-7-303

© 2026 ID INC. All rights reserved

claude-skills/スキル
SKILLOfficialdeployment

client-side-js

プラグイン
valtown
ソース
GitHub で見る ↗
説明

次のような場合に使用: ブラウザで動作する JavaScript をシップする必要がある場合。React アプリ、vanilla DOM スクリプト、キャンバス/ゲーム、htmx/Alpine、または単一のインラインコード以上の規模のクライアント側モジュール(ブラウザ上で実行されるコード)が対象です。Val Town がビルド手順なしで変換済みの .ts/.tsx/.jsx モジュールをどのように配信するか、ブラウザがそのインポートをどのように解決するか、そして外部ライブラリの依存関係をどのように読み込むかについて説明します。

原文を表示

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 module beyond a single inline snippet. Explains how Val Town serves transpiled .ts/.tsx/.jsx modules with no build step, how the browser resolves their imports, and how to load third-party deps.

ユースケース
  • ブラウザで動作するJavaScriptを配信する
  • Reactアプリを構築する
  • vanillaなDOM操作スクリプトを実行する
  • キャンバス/ゲームを開発する
  • クライアント側モジュールの依存関係を解決する
本文(日本語訳)

クライアント側JavaScript

Val Townはビルド処理とバンドラーを持たないプラットフォームです。クライアント側のモジュール(プログラム)は単なるファイルであり、HTTPで配信されます。Val Townは要求に応じてそのファイルを自動変換します。<script type="module"> タグでそのファイルを返すルート(URL経路)を指定すれば、ブラウザが実行します。設定は一切不要です(webpack/vite/esbuildといったツールは不要)。

モジュールを配信する

std/utils の serveFile 関数は、ファイルを読み込んで正しい Content-Type(ファイル形式情報)付きで配信します。.ts、.tsx、.jsx ファイルについてはJavaScriptに自動変換します。型情報を削除し、JSXをコンパイルして、text/javascript 形式で配信します。ソースファイルを配信すれば、ブラウザが実行可能なJavaScriptを受け取ります。

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

// HTTPハンドラ内で、クライアント側モジュールをURLパスで配信
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 を第2引数として渡してください。

デフォルト:バージョン付きの不変キャッシュ

serveImmutableFile を使うと、ブラウザがファイルを永続的にキャッシュできるため、フロントエンドが高速になります。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の現在のバージョン)。相対的なインポートは同じプリフィックス下で解決するため、エントリーだけにスタンプを付ければ十分です。1つのルートと1つのスタンプ付きURLで、クライアント側のモジュール全体をカバーできます。

  • 公開後、古いバージョンのURLは404エラーになります(Next.jsのビルドアセットと同様)。ページを再読み込みすると新しいバージョンが反映されます。
  • 既存のvalを改造する際に、HTMLシェルを変更したくない場合は、古いファイルルートも serveImmutableFile に向ければ、通常のパスが自動的にバージョン付きの領域にリダイレクト(URL転送)されます。ページビューごとに1回のリダイレクトで済みます。

別の方法: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(モジュール形式のURL)が必要です。 import React from "react" のような短い形式(ベア指定子)はブラウザでは解決しません。esm.shといったCDNから、バージョンを固定してインポートしてください:

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

    HTMLのインポートマップを使えば、クライアント側コードで短い形式を書くこともできます。

同じモデルは、あらゆるクライアント側コード—React、素のDOM操作、キャンバス描画、Alpine、htmxなど—で動作します。インポートの形式だけが異なります。CDNから読み込むものがない素のTypeScriptモジュールなら、何も準備は不要です。

React固有の注意点

React関連のインポートはすべて同じバージョン(18.2.0)に固定し、Reactに依存するライブラリには ?deps=react@18.2.0,react-dom@18.2.0 をクエリパラメータとして付与してください。バージョン違いのコピーが混在すると、Cannot read properties of null (reading 'useState') エラーが発生します。JSXとスタイリングの慣例については、react-ui スキルを参照してください。

やってはいけないこと

  • インライン <script> タグやテンプレート文字列内に、アプリケーションロジック(処理)を書かない。 型チェック、文法チェック(リント)、コード審査ができるよう、クライアント側コードは実ファイル(.ts/.tsx)に記述してください。初期化処理(ブートストラップ)の数行はインラインでも構いませんが、アプリ本体は別です。
  • ビルド処理やコマンドは不要。 ビルドステップを追加するものは何もありません。
  • Honoの serveStatic は Val Town では動作しません。代わりに serveFile を使用してください。

変更を確認する

モジュールのURL(例:/app.tsx)をリクエストして、text/javascript が返ってくることを確認してください。HTMLやエラーが返された場合は正常ではありません。HTMLシェルに https://esm.town/v/std/catch を追加すれば、ブラウザのエラーが get_logs にパイプ(転送)されます。ページを開いてログを確認してください。両方の確認なしに、変更完了と報告しないでください。

原文(English)を表示

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.

原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。