工具
命令行与工具
命令行(在 xxd-ui-ki 根目录)
| 命令 | 做什么 |
|---|---|
| pnpm xxd new <目录> --name 名字 | 从模板新建 MV3 插件,组件拷进 vendor/xxd/ |
| pnpm xxd sync <目录…> | 拷最新构建;按 xxd.config.json 写令牌 / 图标 |
| pnpm xxd status <目录…> | 每个项目用的版本,谁落后 |
| pnpm xxd adopt <目录> | 老项目改为引入令牌、旧名做别名 |
| pnpm xxd ls / doc <组件> [--react] | 查组件 |
| pnpm xxd add <block> | 把 React 区块装进项目 |
| pnpm --filter @xxd/site visual | 截图回归:每个页面 × 4 种外观,对比基线 |
插件套件
Chrome 插件套件
@xxd/extension 让每个小小东插件用同一套外观:亮/暗、直角/圆角、主色、三语,以及同样的组件。
插件不能加载远程代码(MV3),所以组件文件拷进每个插件的 vendor/xxd/,每个插件锁定自己用的版本。
新建一个插件
cd xxd-ui-ki
pnpm xxd new ~/js/插件-今日待办 --name 今日待办 --name-en "Today" --slug today
得到一个可以直接「加载已解压的扩展程序」的 MV3 插件:
sidepanel.html:tab 在最上面(没有顶栏)、内容、底部 dock;侧边栏默认紧凑密度options.html:设置页(搜索 ⌘F + 外观组)content.js:往网页里放 UI 的示例(Shadow DOM)_locales/zh_CN · zh_TW · envendor/xxd/:tokens.cssxxd.cssxxd.shadow.cssxxd.jsboot.jsicons.jsextension.jsVERSIONLICENSE
上架前补上 manifest.json 里的 icons(16/32/48/128)。名字用四个朴素的字说清它做什么。
更新组件
pnpm xxd sync ~/js/插件-今日待办 ~/js/xPoster # 拷最新构建,写 VERSION
pnpm xxd status ~/js/插件-今日待办 ~/js/xPoster # 谁落后了
vendor/xxd/ 不要手改。xxd-chrome-publish / cx-chrome-zip 打包时会按引用带上用到的文件,vendor/xxd/LICENSE 让许可检查通过。
老项目:xxd.config.json
放在项目根目录(插件或网站都行):
| 字段 | 作用 |
|---|---|
vendorDir |
组件拷到哪;插件默认 vendor/xxd,网站写 false 不拷 |
tokensTargets |
把 tokens.css 原样写到这些路径(老路径被测试、网站引用,不想改时)。xPoster:["src/xxd-theme.css", "website/xxd-theme.css"] |
layeredTokensRegions |
把 layered 令牌写进这些 CSS 文件开头的标记区域:项目已有的变量永远优先,只多出新令牌(会员站) |
layeredTokensExclude |
额外跳过的令牌名(sync 已经会自动跳过「项目用了但没声明」的名字) |
iconNodesTargets |
把图标数据写到这些路径(会员站 src/lib/xxd-icon-nodes.js) |
adopt |
pnpm xxd adopt <目录>:{ file, aliases }。删掉 file 里与令牌亮暗两种值都相同的 :root 声明(改由令牌提供),aliases 把旧名换成 var(--令牌)。改完用 scripts/verify/css-vars-diff.cjs 比较前后,应当零变化 |
页面里怎么接
<head>
<!-- 首帧前应用外观(读 localStorage 镜像);data-key 每个插件一个 -->
<script src="vendor/xxd/boot.js" data-key="today-ui" data-default-density="compact"></script>
<link rel="stylesheet" href="vendor/xxd/xxd.css">
<script src="vendor/xxd/icons.js"></script>
<script src="vendor/xxd/xxd.js"></script> <!-- window.Xxd,自动 install(document) -->
<script src="vendor/xxd/extension.js"></script> <!-- window.XxdExtension -->
</head>
<body class="xxd-page">
只想换令牌、界面暂时不动:只引 vendor/xxd/tokens.css 和 boot.js(图片排序的试点就是这样)。
外观设置
真正的来源是 chrome.storage.sync,localStorage 镜像只为首帧不闪;侧边栏、设置页、popup 之间实时同步。
const { createAppearanceStore, uiLocale } = window.XxdExtension;
const store = createAppearanceStore({ key: "today-ui" });
await store.load();
Xxd.mountAppearance(document.getElementById("settings"), {
locale: uiLocale(), // zh-CN | zh-Hant | en
get: store.get,
set: (patch) => store.set(patch),
showDensity: false,
lockCorners: false, // 产品要锁直角时 true
});
往网页里放 UI
一律放进 Shadow DOM,样式互不污染;外观属性写到 host 上,不碰宿主页面的 <html>。
const store = createAppearanceStore({ key: "today-ui", apply: false });
await store.load();
const host = document.createElement("div");
document.documentElement.append(host);
const ui = await XxdExtension.mountXxdUI(host, {
cssHref: chrome.runtime.getURL("vendor/xxd/xxd.shadow.css"),
settings: store.get(),
});
ui.root.innerHTML = `<button class="xxd-btn" data-variant="primary" data-size="sm">导出 MD</button>`;
store.subscribe((s) => ui.update(s));
manifest.json 里把 vendor/xxd/xxd.shadow.css 加进 web_accessible_resources。xxd.js 作为 content script 加载时不会自动接管宿主页面。
xxd.shadow.css 自己就挡得住宿主页面(O8,pnpm --filter @xxd/site audit 里有一张恶意宿主页做门禁):
- 宿主页面的继承样式(
text-transform、letter-spacing、text-align、行高、字体、光标……)在 Shadow 的顶层元素上重置; - 令牌在 Shadow 内部再声明一次:宿主页面就算写了
div { --ink: … }打到 host 上,也改不了我们的颜色和圆角; data-theme/data-corners/data-finish写在 host 上都生效;- tooltip、toast、「已复制」气泡、彩纸都挂在锚点所在的 Shadow 里(
Xxd.toast(msg, { within: ui.root })可以直接传 ShadowRoot)。
行为 JS 的语言
原生组件生成的文字(上一页、关闭、复制失败、图片查看器……)跟 <html lang> 走三语;插件里 i18n.js 已经写好 lang。要固定就 Xxd.setLocale("en")。参数和 data-* 文案仍然优先。
三语
<span data-i18n="tabSettings">设置</span>
<input data-i18n-attr="placeholder:searchPlaceholder">
XxdExtension.applyI18n(document) 用 chrome.i18n 替换;找不到翻译时保留 HTML 里原来的文字。
截图回归
每个组件页、模式页、模板页在亮 / 暗 × 直角 / 圆角四种外观下截图,存在 packages/xxd-site/visual/baseline/;改了组件后运行 pnpm --filter @xxd/site visual,差异图写到 visual/diff/。确认是有意的改动再 visual:update。
给 AI 看
- /llms.txt:全站目录,每页一行。
- 每页右侧「本页的 Markdown」:同一页的 .md 版本(
/md/components/button.md)。 - /api/registry.json:原生组件注册表(类名、属性、示例、规则)。
- 命令行:
pnpm xxd doc Dialog;Skill:xxd-ui。