From cd2d7dea0cac1f85a294f6b98c16baca8c0df5e4 Mon Sep 17 00:00:00 2001
From: imccyu <276526105+imccyu@users.noreply.github.com>
Date: Mon, 19 Oct 2026 19:28:18 +0800
Subject: [PATCH] docs(notes): serve client plugin package assets through the
plugins route
---
...6-10-19-client-plugin-package-assets.zh.md | 207 ++++++++++++++++++
1 file changed, 207 insertions(+)
create mode 100644 .agents/notes/proposed/architecture/2026-10-19-client-plugin-package-assets.zh.md
diff --git a/.agents/notes/proposed/architecture/2026-10-19-client-plugin-package-assets.zh.md b/.agents/notes/proposed/architecture/2026-10-19-client-plugin-package-assets.zh.md
new file mode 100644
index 0000000000..79a646b66c
--- /dev/null
+++ b/.agents/notes/proposed/architecture/2026-10-19-client-plugin-package-assets.zh.md
@@ -0,0 +1,207 @@
+# Agent Note: Client 插件包资源经 Host 路由按 URL 加载
+
+Status: proposed
+
+[English](2026-10-19-client-plugin-package-assets.md) | 中文
+
+## Problem
+
+动态 Client 插件的浏览器半侧由 [client 打包预设](../../../../packages/client/tsdown.client.ts) 打成 lazy-CJS factory,经 [Client 模块系统](../../../../packages/client/modules/README.zh.md) 的 `/plugins` 路由加载。这条路由只提供 `client.js` 和包内 chunk,没有读取包内其他文件的路径,所以插件代码引用的图片只能由预设转成 base64 data URL 打进 bundle;样式里用到的图片则由包级构建配置替换成内联样式。
+
+代价:
+
+- ui-settings-account 的引导插图让 `client.js` 达到 3.4 MB,其中图片原始字节 2.4 MB,转成 base64 还要再大三分之一。不管插图显不显示,这些字节都随 JS 一起下载和解析。
+- ui-plugin-manager 的功能预览(约 305 KB SVG)同样打进 bundle。[功能预览 Note](../../implemented/feature/2026-10-14-built-in-plugin-feature-previews.zh.md) 当时没有把图片放进各 bundle 包,理由之一就是缺少 Host 读取路径。
+- ui-chat 为一张鲸鱼图在 [包级 tsdown 配置](../../../../packages/client/ui-chat/tsdown.config.ts) 里自写了替换插件;每个要在样式里用图片的包都得重复这类构建代码。
+
+## Proposal
+
+插件包把原始资源放在 `src/client/assets/`。预设把被引用的资源产出到 `lib/assets/`,并把 JS import 和插件 CSS 中的 `url()` 都编译成指向 Host 路由的 URL;包通过 exports `./assets/*` 发布这些产物;[`ClientModuleRegistry`](../../../../packages/client/modules/src/index.ts) 在现有 `/plugins` 前缀下提供 `/plugins/<包名>/assets/<路径>`。
+
+本期范围是我们自己的 Client 代码(Cordis 插件、slot 组件、service)及其样式表引用的图片。以下不在本期:
+
+- 三方库自己加载的资源,如 PDF.js 的字体、cmap、wasm 和 worker,以及依赖包样式表里的 `url()`;
+- 插件元数据图标:[package-meta](../../../../packages/boot/app-boot/src/package-meta.ts) 仍返回 data URL;
+- static-linked 包:由 Vite 壳处理资源。
+
+### 开发者写法
+
+| 位置 | 内容 |
+|---|---|
+| `src/client/assets/**` | 原始资源 |
+| `lib/assets/**` | 预设产出,只含本次构建被引用的资源 |
+| `package.json` `exports` | `"./assets/*": "./lib/assets/*"` |
+| `package.json` `files` | `lib/assets` |
+
+```tsx
+// src/client/OnboardingWelcomeStep.tsx
+import welcome from './assets/onboarding/welcome.webp'
+
+
+```
+
+```css
+/* src/client/chat/ChatView.module.css */
+.whale { mask: url('../assets/running-whale@2x.png') center / 100% 100% no-repeat; }
+```
+
+构建产物里只有 URL:
+
+```js
+var welcome_default = "plugins/@deepseek-ai/dsh-client-ui-settings-account/assets/onboarding/welcome-3f9a0c1d2e4b.webp";
+```
+
+```css
+.x1y2_whale{mask:url("plugins/@deepseek-ai/dsh-client-ui-chat/assets/running-whale%402x-8c41d07e5a9f.png") 50%/100% 100% no-repeat}
+```
+
+URL 在构建时就已确定:JS 侧是一个模块常量,CSS 侧是改写后的样式文本。组件不需要新的 hook、inject 或 service。
+
+### 扩展名与类型声明
+
+| 扩展名 | `content-type` |
+|---|---|
+| `.svg` | `image/svg+xml` |
+| `.png` | `image/png` |
+| `.webp` | `image/webp` |
+| `.jpg`、`.jpeg` | `image/jpeg` |
+
+这和插件元数据图标接受的格式相同。扩展名表在代码中只定义一处,放在 Client 模块系统里,预设和 Host 共用。
+
+类型声明放在 Client 编译共享的类型根 `scripts/types/client-assets/`,登记到 [`tsconfig.base.client.json`](../../../../tsconfig.base.client.json) 的 `types`,与现有的 `client-build-environment` 同一机制。每个扩展名声明为 `export default string`,各包自带的 `assets.d.ts` 删除。测试断言声明的扩展名与扩展名表一致。表外扩展名的 import 没有类型声明,构建也会失败。
+
+### 构建
+
+`dsh-image-inline` 换成 `dsh-package-asset`,只作用于动态 bundle 的浏览器 face:
+
+1. 按 importer 的源码位置解析相对路径。构建读取的是 `lib/types/`,沿用现有的 `lib/types` → `src` 换算。
+2. 文件的真实路径必须在本包的 `src/client/assets/` 内,否则构建失败;引用他包或 npm 依赖里的图片同样失败。
+3. `package.json` 的 `exports["./assets/*"]` 必须恰好是 `./lib/assets/*`,否则构建失败。
+4. rev 取文件字节 sha1 的前 12 位 hex,与现有 rev 同长度。
+5. 按产出规则决定它在 `lib/assets/` 下的文件名:相对 `src/client/assets/` 的路径,在扩展名前插入 `-`(`icons/whale@2x.png` → `icons/whale@2x-.png`),作为构建产物 emit;同一文件只 emit 一次。
+6. 模块代码为 `export default "plugins/<包名>/assets/<产出路径>"`,路径逐段 `encodeURIComponent`(例如 `@` 写成 `%40`),不带查询串;包名原样输出。
+
+产出规则集中在预设里。以后增加命名后缀、格式转换或压缩时只改预设,import 方拿到的始终是产出文件的 URL。浏览器 face 是 `lib/assets/` 的唯一写入方,每次构建后删除本次没有产出的文件,所以发布内容与实际引用一致。实验包隔离检查照常覆盖这些产物。
+
+插件 CSS 的三条管线(module、global、`?inline`)在 lightningcss 编译时访问每个 `url()`:
+
+- 样式表真实路径在本包 `src/` 内时,相对路径 `url()` 按样式表所在目录解析,再走上面的步骤 2–5,改写为 `url("plugins/<包名>/assets/<产出路径>")`;解析不到 `src/client/assets/` 内的表内文件就构建失败。
+- `data:`、`#片段`、带协议的 URL、以 `/` 开头的路径和空 `url('')` 原样保留。
+- 依赖包的样式表不改写,例如 FortuneSheet 的 `url("EwaAntH.gif")`。
+
+插件样式经 `