From 6c6d93373237b9c22f3c5004e7afc1c946fd496b Mon Sep 17 00:00:00 2001 From: imccyu Date: Tue, 4 Aug 2026 14:15:32 +0800 Subject: [PATCH 1/2] fix(client): load plugin bundles as external scripts --- apps/web/tests/built-boot.snapshot.ts | 8 +- apps/web/tests/search-card.snapshot.ts | 6 +- apps/web/vite.config.ts | 3 + packages/client/README.i18n.yaml | 4 +- packages/client/README.md | 2 +- packages/client/README.zh.md | 2 +- packages/client/hmr/README.i18n.yaml | 4 +- packages/client/hmr/README.md | 4 +- packages/client/hmr/README.zh.md | 4 +- packages/client/hmr/package.json | 2 +- packages/client/hmr/src/client/index.ts | 6 +- packages/client/modules/README.i18n.yaml | 4 +- packages/client/modules/README.md | 4 +- packages/client/modules/README.zh.md | 4 +- .../client/modules/src/client/manifest.ts | 30 +++---- packages/client/modules/src/client/system.ts | 87 ++++++------------- packages/client/modules/src/index.ts | 19 ++-- packages/client/modules/tests/loader.spec.ts | 62 ++++++------- .../client/modules/tests/node-half.spec.ts | 60 +++++++++++-- packages/client/tsdown.client.ts | 21 ++++- .../ui-trajectory/tests/client-bundle.spec.ts | 5 +- packages/client/web/README.i18n.yaml | 4 +- packages/client/web/README.md | 2 +- packages/client/web/README.zh.md | 2 +- packages/client/web/src/boot.tsx | 10 +-- scripts/client-bundle-purity.spec.ts | 52 +++++++++++ 26 files changed, 252 insertions(+), 159 deletions(-) diff --git a/apps/web/tests/built-boot.snapshot.ts b/apps/web/tests/built-boot.snapshot.ts index bdf0d4f7d1..c452295d23 100644 --- a/apps/web/tests/built-boot.snapshot.ts +++ b/apps/web/tests/built-boot.snapshot.ts @@ -1,7 +1,7 @@ // @vitest-environment jsdom // The built-bundle boot smoke: the ONE assembled-jsdom test that loads the // real `packages/client/*/lib/client.js` artifacts through AppWebEntry's -// ModuleLoader path (fetchBundle/executeBundle) and proves the boot graph +// ModuleLoader path (loadBundle) and proves the boot graph // assembles — staged activation across the immediately tier and the inject // layers, per-plugin CSS injection, and a rendered journey reaching chat // content from the keyless FixtureApiClient transport. @@ -91,11 +91,11 @@ it('boots the built plugin graph and renders a fixture session end to end', asyn win.__DSH_BOOT__ = { rev: 'fx', entries: PLUGINS.map(({ dir: _dir, ...plugin }) => plugin) } act(() => { const entry = new AppWebEntry(root, { - fetchBundle: (url) => { + loadBundle: async (url) => { const code = bundles.get(url) - return code === undefined ? Promise.reject(new Error(`missing built bundle ${url}`)) : Promise.resolve(code) + if (code === undefined) throw new Error(`missing built bundle ${url}`) + ;(0, eval)(code) }, - executeBundle: (code) => { (0, eval)(code) }, }) void entry.run() unmount = () => { entry.dispose() } diff --git a/apps/web/tests/search-card.snapshot.ts b/apps/web/tests/search-card.snapshot.ts index 0315a29e37..2f72c8275c 100644 --- a/apps/web/tests/search-card.snapshot.ts +++ b/apps/web/tests/search-card.snapshot.ts @@ -128,11 +128,11 @@ describe('assembled search card', () => { win.__DSH_BOOT__ = { rev: 'fx', entries: PLUGINS.map(({ dir: _dir, ...plugin }) => plugin) } act(() => { const entry = new AppWebEntry(root, { - fetchBundle: (url) => { + loadBundle: async (url) => { const code = bundles.get(url) - return code === undefined ? Promise.reject(new Error(`missing built bundle ${url}`)) : Promise.resolve(code) + if (code === undefined) throw new Error(`missing built bundle ${url}`) + ;(0, eval)(code) }, - executeBundle: (code) => { (0, eval)(code) }, }) void entry.run() unmount = () => { entry.dispose() } diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 3f920ac46b..842317911e 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -20,6 +20,9 @@ function rejectStandaloneServe(): Plugin { export default defineConfig({ plugins: [rejectStandaloneServe(), react()], + build: { + sourcemap: true, + }, resolve: { // Workspace packages resolve to SOURCE: package.json exports point at lib // for Node/type consumers, but the browser bundle must compile src directly diff --git a/packages/client/README.i18n.yaml b/packages/client/README.i18n.yaml index 0bac8a9924..210228ae88 100644 --- a/packages/client/README.i18n.yaml +++ b/packages/client/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/client/README.md -README.md: b111d67fa49e06227e324a33bd53417ad28c3a5b -README.zh.md: b498008eb82f6ab357718f2af761f38e51140ef8 +README.md: 31d884c04a8b0233713b77b82b3d9cc7052003ca +README.zh.md: 9c95db529306519136d6d68758889350e5dd65e4 diff --git a/packages/client/README.md b/packages/client/README.md index b111d67fa4..31d884c04a 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -11,7 +11,7 @@ The browser side of the dsh web GUI: shell kernel, module system, wire consumer, | `web-react/` | Shell-side React glue: `createSlotRenderer` + `SessionProvider` render seats | (renderer install) | | `connection/` | Wire consumer both ends: browser `ctx.connection` (shared api client + stream loop) and the node half mounting the `/api` route with its browser-trust fence | `ctx.connection` | | `runtime/` | Client cordis boot and React-free object services: slots, Sessions, Workspaces, per-session bindings | `ctx.slots` `ctx.sessions` `ctx.workspaces` | -| `hmr/` | Dev-only hot reload for fetch-arrival client plugins (`--dev` graphs) | (dev entry) | +| `hmr/` | Dev-only hot reload for script-loaded client plugins (`--dev` graphs) | (dev entry) | | `locale/` | Browser locale preference (`zh`/`en`) plus the ns×locale dictionary registry | `ctx.locale` | | `ui-slots/` | Slot registry pure core: SlotMap merging, single `register` API, the four-share props family | (types + core) | | `ui-theme/` | Theme preference over the `--dsw-*` token stylesheets (`light`/`dark`/`system`) | `ctx.theme` | diff --git a/packages/client/README.zh.md b/packages/client/README.zh.md index b498008eb8..9c95db5293 100644 --- a/packages/client/README.zh.md +++ b/packages/client/README.zh.md @@ -11,7 +11,7 @@ dsh web GUI 的浏览器侧:shell 内核、模块系统、协议消费层、 | `web-react/` | shell 侧 React 胶水:`createSlotRenderer` + `SessionProvider` 渲染座位 | (渲染器安装) | | `connection/` | 协议两端的消费者:浏览器侧 `ctx.connection`(共享 api 客户端 + 流循环),node 半侧挂载带浏览器信任栅栏的 `/api` 路由 | `ctx.connection` | | `runtime/` | 客户端 cordis 启动与无 React 对象服务:slots、Session、Workspace、逐会话绑定 | `ctx.slots` `ctx.sessions` `ctx.workspaces` | -| `hmr/` | 仅开发用的 fetch 到达型客户端插件热重载(`--dev` 图) | (开发条目) | +| `hmr/` | 仅开发用的外部脚本加载型客户端插件热重载(`--dev` 图) | (开发条目) | | `locale/` | 浏览器语言偏好(`zh`/`en`)与 ns×locale 词典注册表 | `ctx.locale` | | `ui-slots/` | slot 注册表纯核心:SlotMap 合并、单一 `register` API、四份额 props 族 | (类型 + 核心) | | `ui-theme/` | 基于 `--dsw-*` token 样式表的主题偏好(`light`/`dark`/`system`) | `ctx.theme` | diff --git a/packages/client/hmr/README.i18n.yaml b/packages/client/hmr/README.i18n.yaml index 4eef7fa3c2..ce015be85f 100644 --- a/packages/client/hmr/README.i18n.yaml +++ b/packages/client/hmr/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/client/hmr/README.md -README.md: 2b2f63c25cbf3a46babef78a4dfb52f859156887 -README.zh.md: 58fbad900d9ab86a9d28979f691f24de29e9b6f4 +README.md: f91a6c6f685c88a1ea19312985ad3e933222192a +README.zh.md: 1d20a22d211c13d62089fb5618f40636ab7ae60a diff --git a/packages/client/hmr/README.md b/packages/client/hmr/README.md index 2b2f63c25c..f91a6c6f68 100644 --- a/packages/client/hmr/README.md +++ b/packages/client/hmr/README.md @@ -2,9 +2,9 @@ English | [中文](README.zh.md) -Hot reload for fetch-arrival client plugins. A static-arrival entry composed only into `--dev` graphs (`dsh web --dev`); production graphs omit the row, so the shell-bundled code stays inert. +Hot reload for script-loaded client plugins. A static-arrival entry composed only into `--dev` graphs (`dsh web --dev`); production graphs omit the row, so the shell-bundled code stays inert. -The browser half subscribes to the system SSE channel (`GET /plugins/events`) and reloads one plugin per `rebuilt` frame, serialized through a queue (the bundle handoff slot is single). The sequence per frame — `prefetch` (fetch the new bundle before touching anything), `invalidate`, `registry.delete` (before the fiber: a bare fiber dispose trips the vendored Loader's self-dispose branch, which would mark the entry disabled), drain the old fiber, delete `entry.fiber`, remove owned `