沙盒(Playgrounds):瀏覽器裡的多檔 Web 實驗場

發布: 約 6 分鐘

本站有一塊叫沙盒的頁面,路徑 /playgrounds/(程式裡叫 Playgrounds)。目標很具體:在瀏覽器裡編輯多檔輕量 Web 專案、立刻在畫布看到結果,必要時再掛 edge functions 形的 /api/*,以及可選的 coding agent/REPL。資料權威在本機 OPFS,本站不當專案雲端硬碟。

這篇整理它怎麼疊起來。要手玩請直接開沙盒;這裡不逐步教學。

打開沙盒

目錄

展開目錄

殼頁、工作專案、畫布

介面上大致三塊:

區塊做什麼
殼頁CodeMirror 編輯、檔案樹、專案匯入/匯出、把當前專案快照餵給畫布
工作專案OPFS 裡的一組檔案;權威儲存,可含文字與二進位
畫布同源 iframe,用 URL /playgrounds/canvas/<projectId>/… 載入,像從網路上開一個站

畫布入口固定專案根目錄的 index.html。相對路徑的 ESM、CSS、圖、字型走瀏覽器原生解析,不必每次在殼頁手改模組路徑。

早期若用 srcdoc+改寫 import,相對資源與快取行為都很彆扭。現況改成:專用 Service Workersw-canvas.js,scope 在 /playgrounds/,只攔截 /playgrounds/canvas/)在記憶體裡依殼頁 postMessage 過來的快照回 Response。HTML 回應帶一點 CSP(base-uriobject-srcframe-ancestors 等)。殼頁的離線 SW 快取 canvas 路徑,避免跟這條虛擬站台搶。

信任模型也寫死:這是本機實驗場,畫布與殼頁同源,專案腳本碰得到同 origin 儲存與 parent。不做多租戶硬隔離;殼頁也不放秘密。

OPFS 當專案權威

每個專案一個 id,檔案進 Origin Private File System。重點:

  • 文字/二進位都可存;編輯器只開判為文字的檔。
  • ZIP import/export 原樣帶 binary;可自 GitHub public repo 拉檔寫入(之後與遠端無關)。
  • 不支援 OPFS 的瀏覽器直接失敗,默默退回 localStorage 假裝同一套儲存。

另外還有幾塊也落在 OPFS、但跟「專案原始碼樹」分開:模擬 KV、checkpoint、D1、Secrets 各有自己的前綴目錄。clone 專案時,KV/Secrets 不跟著複製——避免把 session 或密鑰當原始碼一起拷走。

functions.js:Workers 形的 /api/*

畫布裡的 fetch("/api/…") 會被橋到 /playgrounds/canvas/<projectId>/api/…,由殼頁執行該專案根目錄的 functions.js

export default {
  async fetch(request, env, ctx) {
    // …
  },
};

形狀對齊 Cloudflare Workers 的直覺:env 可注入模擬 KV(Durable:寫 OPFS)、以及(在現行 Agent 專案)env.HOST。另有模擬 D1(sql.js 子集)與 Secrets(殼頁 UI 寫入;HOST 只能列名、讀不到值)。沒有 functions.js 時 API 回 503。

這條路的用意是:靜態前端+一點 serverless 形後端,可以在分頁裡驗證;真要上線再部署到真正的 edge,而不是把沙盒當成正式託管。

Coding agent:雙執行面+env.HOST

若啟用 agent,殼頁維持兩條執行面:

  • 工作專案:編輯器+原畫布,被改、被驗的那份程式。
  • 現行 Agent:另一個沙盒專案,UI 跑在左側 Agent 區(同樣走 canvas SW/index.html),不是殼頁手寫的 chat 元件。

Agent 跟環境互動的正式通道只有一條:

Agent UI → fetch("/api/…") → 該專案 functions.js → env.HOST

HOST 提供版本化能力(apiVersioncapabilities):列檔、讀寫(可選 expectedHash)、search、重載畫布、讀/等 console、checkpoint、建立/複製/開啟專案等。約束包括:

  • 只有現行 Agent 專案才注入 HOST
  • 禁止經 HOST 寫入現行 Agent 自己的檔案(避免熱改執行中的自己);
  • 刪專案:HOST 只刪帶 agentManaged 標記、由 agent create/clone 出來的專案。

LLM 走 BYOK:endpoint/model/key 留在 Agent 專案 UI 的儲存裡,本站不代打、不存 key。

下方面板:Console、Python、JavaScript

面板實作要點
Console畫布 runtime 的 log/error,殼頁轉送進 buffer;agent 也可經 HOST 讀
Python隔離 Pyodide Worker(CDN 釘版);人類 xterm REPL 與 HOST.runPython 共用同一 Worker;%pip 允許清單、%run 可把專案 .py 同步進 Pyodide FS
JavaScript另一個 Worker 沙盒(與畫布 runtime 分開);REPL+%run 專案 .js(含相對 ESM);無 npm

曾經試過在下方塞 v86/Alpine 當「真 Linux」。對「輕量 Web+functions」主軸槓桿低(慢、無網、無編譯鏈),後來整段拿掉;人類路徑改成上面兩個 REPL,也HOST.shell

邊界(技術上不做的)

  • 不當部署環境、不當完整 Node/WebContainer。
  • 不做通用 CORS proxy/站內代抓任意外網。
  • 不承諾模擬 KV/D1 與雲端 Cloudflare 全相容。
  • 頁面 noindex,也不進 XML sitemap——它是站內實驗頁,不是要靠搜尋引擎養流量的薄頁。

結構可以再拆細講(畫布 SW 協定、HOST 錯誤碼、Pyodide 與專案 FS 同步)。這篇先把骨架放在桌上;要按的從沙盒進去即可。

Sampot (山姆鍋)

獨立軟體開發者,專長分散式系統、Web 應用與雲端服務架構。目前一人開發NT² Vault:結構化數位資產保管箱。工作之餘關注開源軟體的發展與應用,偶爾在這裡碎念與分享。