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")`。 + +插件样式经 `