Repository navigation
ESM CDN Architecture
日期:2026-10-08
範圍:Tonic UI 使用公開 esm.sh,應用以 Vite 或 Webpack 建置。本頁只記錄配送與啟動期選擇,不建立另一份通用 Release plan。
Tonic UI 整組切換;React 與 Emotion 各自維持 CDN-first。 Local Tonic 不能順便打包另一份 React/Emotion。
| Runtime family | 包含 | CDN 載入失敗時 |
|---|---|---|
| React | React、React DOM、client 與 JSX runtimes | 只切換這組到同源 vendor build |
| Emotion | React、styled、cache 等 Emotion packages 及所用 subpaths | 只切換這組到同源 vendor build;沿用已選 React |
| Tonic UI | 全部所用 @tonic-ui/*、internal subpaths 及其啟動依賴圖 |
全部切換到 local Tonic app build;沿用已選 React/Emotion |
任一 Tonic package、internal entry 或遞迴依賴失敗,都不能留下部分 Tonic packages 在 CDN。這是同一個 JavaScript realm 內的 module identity 規則,不是只看版本號相同。
同一份 source、lockfile 與 production 設定產生四組輸出:
-
react/:多入口 local React/React DOM vendor;共享 chunks,不能逐入口各自打包一份 React。 -
emotion/:多入口 local Emotion vendor;React family 保留 external。 -
tonic-cdn/:app 與 widget 的 React、Emotion、Tonic imports 都保留 external。 -
tonic-local/:app 與 widget 的 Tonic dependencies 打包在共享 local chunks;React、Emotion 仍保留 external。
App 與 widget 是普通 ESM entries,不使用 Module Federation、Wujie 或 MFE server。所有 local chunks、CSS、bootstrap 與 runtime manifest 由既有 static hosting/app server 配送。CDN root URLs 使用這次安裝的 exact package versions,不查詢 latest。
flowchart TD
Source["App/Widget Source<br/>同一份 Lockfile"] --> Build["Vite 或 Webpack"]
Build --> React["Local React Vendor<br/>共享 chunks"]
Build --> Emotion["Local Emotion Vendor<br/>React external"]
Build --> TonicCDN["CDN Tonic App/Widget<br/>三組 runtime external"]
Build --> TonicLocal["Local Tonic App/Widget<br/>React/Emotion external"]
Build --> Manifest["同源 Runtime Manifest<br/>版本/entry/CSS/group maps"]
Manifest --> Loader["Classic Bootstrap<br/>先安裝一張 import map"]
Loader --> Probe["依序載入 React → Emotion → Tonic<br/>每組 CDN-first 或已強制 local"]
Probe -->|"某組 CDN 失敗/逾時"| Reload["只標記失敗組為 local<br/>重載 demo document,丟棄舊 realm"]
Reload --> Loader
Probe -->|"全部成功"| App["載入所選 Tonic App/Widget<br/>成功後才 mount"]
- 讀取同源 descriptor。某組沒有可用 CDN descriptor(
cdn: null),直接選 local。 - 合併各組所選 maps;任何重複 specifier 都視為 descriptor 錯誤。在第一次 module import 前安裝一次 import map。
- 依序 probe React、Emotion、全部所用 Tonic entries。Emotion 使用已選 React;Tonic 使用已選 React/Emotion。
- 某組 CDN import 失敗或超時:保留錯誤,用
__local=group重載同一頁;其他組仍 CDN-first。多組失敗時累積標記。 - Native imports 不能取消或安全地換綁定。必須丟棄舊 document/realm,不能原地換 import map 然後載入另一份 runtime。
- 全部 probes 成功後,載入與 Tonic source 配對的 app/widget 及其 CSS,再呼叫
mount。 - 已強制 local 的組不再試 CDN;local、app assets、CSS 或 mount 錯誤顯示啟動錯誤,不自動切回 CDN。
__local 標記只在目前 URL 生效,重新整理會保留它;demo 的 Retry CDN-first 會移除標記。產品 routing 不能丟掉 bootstrap 參數。每組最多切換一次,最多三次 fallback 重載;不是背景重試迴圈。
- HTTP 200 不足以判定可用。Browser 必須完成實際 module graph 的下載、linking 與 evaluation;CORS、subpath 或遞迴依賴錯誤也會使所屬組失敗。
- 示範的 8 秒 budget 是每組的啟動時間限制。公開 esm.sh 冷建置或無快取 waterfall 可能超時;這時該組選 local 是預期行為,不代表版本不存在。
- 固定 root versions 不等於 lock 完整遞迴依賴圖。示範使用 selective external;不是 production graph audit 或完整產品驗收。
- 本地 workspace 修改不會自動出現在公開 esm.sh。CDN/local 一致性仍須由既有 package release 與 app 驗收流程確認。
- 已快取 CDN modules 即使網路中斷仍可能成功。驗證真實故障時須停用 browser cache。
packages/react-docs/demos/cdn-runtime/ 提供專用 yarn build:cdn-runtime,輸出到 public/demos/cdn-runtime/。展示頁位於 Getting Started → CDN-first runtime。
Docs 的 iframe 只隔離文件網站自己的 React runtime。Demo 本身可獨立開啟,不依賴 MFE。App 與另一個 ESM widget 共享 React hooks、Tonic provider/theme 及 Emotion 樣式;故障選單可展示各組 fallback。
重新載入會丟失未持久化狀態。本例處理列出的啟動 module inventory,不涵蓋任意後續 lazy imports/執行期 exceptions;不要在 mount 後隱藏 app bug 或自動混載另一組 runtime。
Vite/Webpack 設定與獨立 demo記錄具體設定、descriptor、操作命令與已觀察的驗證結果。