# Web Component 组件库规范(wc:Lit 实现的浏览器交互层) > 组件库 = 一批**用 `customElements.define()` 注册的自定义元素**,内部封装 **DOM / 交互 / 样式**, > 逻辑**全部来自核心库 [core](./core-library.md)**。产物是标准 Web Component —— > 在 Lit、Vue、React、Angular、原生 HTML 里都能**直接写 ``**,无需跑任何前端框架。 > > 与页面内组件([components.md](./components.md))的区别:页面组件服务于「一个应用」; > **组件库组件服务于「多个应用 / 多个框架」**,因此契约更严、实现更克制。 ## 零、wc 必须用 Lit 框架特性开发(最重要的一条) > ⚠️ **本章所有 wc 组件必须用 Lit 框架特性(`LitElement` / `html` / `css` / Shadow DOM / `static properties` / > Lit 响应式)开发。禁止手写原生 Web Component。** > > - **Lit 本身就是 Web Component 的实现层**:`LitElement` 继承自 `ReactiveElement`(再继承 `HTMLElement`), > `html` 产出 Shadow DOM 里的模板,`customElements.define()` 注册的是标准自定义元素。 > 所以「用 Lit 写」与「产出标准 Web Component、跨框架可用」**不矛盾** —— 前者是实现方式,后者是产物形态。 > - **✅ 写法**:`class X extends LitElement` + `static properties` + `static styles = css\`\`` + `render() { return html\`\`\` }`。 > - **❌ 禁止的原生写法**(等价重造 Lit,且丢掉 Lit 的模板缓存 / 属性转换 / 响应式更新): > - `class X extends HTMLElement` > - `this.attachShadow({ mode: 'open' })` + 手动 `innerHTML` / `document.createElement` > - 手动实现 `observedAttributes` / `attributeChangedCallback` / `connectedCallback` 里刷 DOM > - 字符串拼接模板 + `this.shadowRoot.querySelector(...).textContent = ...` > - **「原生」只出现在宿主侧**:`plain.html` 里 ` ``` **B. Lit 页面里用 Context(仅 Lit 宿主)** —— 见 [context-di.md](./context-di.md)。组件库内部用 `ContextConsumer` 兜底读注入的实例,读不到时用属性。 > 原则:**组件库只定义 shape,不拥有实例**。这也让组件在 Node / SSR 里并不被实例化,逻辑仍留在 core。 ## 六、跨框架使用方式 组件的**唯一运行要求**:浏览器能执行 ESM + `customElements`。下图是同一份 `dist` 在四个宿主里的用法: ```html ``` ```html console.log(e.detail)}> ``` ```jsx // ③ React 19:自定义元素已原生支持(prop 走 attribute/property 自动映射) // ④ React 18-:需包装层(ref + addEventListener),或复用 @lit/react 的 createComponent ``` ```vue ``` | 宿主 | 属性绑定 | 事件 | 注意事项 | |------|----------|------|----------| | 原生 / Lit | `.prop=`(Lit) / `el.prop =` | `addEventListener` / `@event` | Lit 必须 **`.` 前缀**绑定复杂值 | | Vue 3 | `:prop`(复杂值透传为 property) | `@kebab-event` | 配置 `isCustomElement`(构建型 Vue) | | React 19+ | `prop={...}`(已映射到 property) | `onEvent={...}`(`dc-done` → `onDcDone`) | 事件名注意大小写映射 | | React 18- | 需 wrapper(`createComponent`) | ref + `addEventListener` | 低版本 React 会把未知 prop 当 attribute | > **验收口诀**:组件库必须在**一个非 Lit 宿主**(纯 HTML 或 Vue)里也能跑,否则就不是跨框架组件库。 ## 七、样式隔离与主题 - **Shadow DOM 是默认**:组件内部样式不泄漏、外部项目样式也不进来。 - **主题三件套**(按优先级): 1. **CSS 自定义属性**:`:host { --dc-accent: #2b6cb0 }`,外部可在祖先或宿主上覆盖; 2. **`::part()`**:`part="log"` 暴露需要精细调整的节点; 3. **`slots`**:允许宿主替换整块内容(如标题、空状态)。 - **禁止**:`document.adoptedStyleSheets` 注入全局样式;`!important` 向外压制;依赖宿主 CSS 类名。 - 设计令牌(颜色/间距/字号)建议作为**包的静态 CSS** 发布一份(如 `theme.css`),宿主 `` 引入即可,变量名统一前缀(`--fm-`)。 ## 八、无构建分发 组件库与 core 一样:**无构建**、源码即产物,托管到 **FMODE Repose**,用 importmap 引入。 ``` /npm/designer-chat/v1.0.0/ index.js # 入口:import 所有组件并注册(或单组件入口) designer-chat.js # 单组件 theme.css # 可选:设计令牌 ``` ```json { "imports": { "designer-chat": "/npm/designer-chat/v1.0.0/index.js", "fmode-code-agent": "/npm/fmode-code-agent/v2.0.0/src/index.js" } } ``` - `index.js` **只做 import + 注册**:副作用仅「注册自定义元素」,无渲染、无请求。 - **lit 依赖不内置**:组件库 import 裸 `lit`,由宿主的**公共 importmap** 解析 → 全局单实例 lit(否则重复注册 / 多版本冲突,见 [pitfalls.md 第三节](./pitfalls.md))。 - **core 依赖版本化**:组件库在 importmap 里映射自己需要的 core 版本;同一页面若用了两个 core major,映射两个标识符(见 [core-library.md 第六节](./core-library.md))。 - 发布 checklist 中增加:**在一个非 Lit 宿主里 Smoke**(见第九节)。 ## 九、验收 & Test | 层 | 怎么做 | |----|--------| | core 逻辑 | `node --test`(不依赖 DOM,见 [core-library.md 第四节](./core-library.md)) | | 组件契约 | 浏览器里断言:`customElements.get('x-y')` 存在;改属性 → 渲染变化;点按钮 → 事件带 `detail` 且 `composed` | | 跨框架 | 同一页面用**纯 HTML** 与 **Lit** 各挂一次;两个入口渲染结果一致 | | 样式隔离 | 宿主页全局 `p { color: red }` **不影响**组件内部;`--dc-accent` 能改主题 | | 幂等 | 同名脚本 import 两次不报 `define` 冲突 | | 无装饰器 | `node --check` 全过;`grep` 无 `@property` 等 | | **Lit 实现** | `grep` 注册元素的文件均 `import ... from 'lit'` 且 `extends LitElement`;无 `extends HTMLElement` / `attachShadow(` / `observedAttributes` | ## 十、红线清单 - ❌ 组件里写业务逻辑 / 请求 / 协议解析 → 下沉 core,由宿主注入 I/O - ❌ 依赖宿主的 CSS 类名 / 全局样式 / 全局 window 变量 - ❌ 暴露内部状态写入方法(`el.setMessages()`),只暴露语义属性 + 语义事件 - ❌ 事件漏 `composed: true` → 穿过 Shadow DOM 后宿主收不到 - ❌ 无前缀标签名(`` 与别的库撞车) - ❌ `customElements.define` 不判重 → 二次加载 `NotSupportedError` - ❌ 组件库内置 lit 副本 → 宿主多实例 lit,见 [pitfalls.md](./pitfalls.md) - ❌ 装饰器 - ❌ 手写原生 wc(`extends HTMLElement` / `attachShadow()` / `observedAttributes` / 字符串模板刷 `innerHTML`)→ 必须 `extends LitElement` + `html` + `css`,见第〇节 ## 相关 - [core-library.md](./core-library.md) —— 组件库依赖的逻辑层 - [components.md](./components.md) —— 页面内组件;组件库是它的「契约加严版」 - [architecture.md](./architecture.md) —— 三分层总图 - [context-di.md](./context-di.md) —— Lit 宿主中的注入 - [deployment.md](./deployment.md) / [pitfalls.md](./pitfalls.md) —— 分发与多版本冲突