name: fmode-code-lit description: "Lit 原生 ESM 无构建前端。触发词:lit 组件、无构建、ESM。" schema_version: 1.0.0 level: system category: development version: 1.0.0 author: Yuyang001 (FmodeAgent)
用无构建/无打包/纯原生 ESM + Lit 3.x 开发前端:源码即产物,浏览器直跑;依赖走 FMODE Repose,业务码走 OBS+CDN。分层(组件/服务/工具)、signals 响应式、@lit/context DI、History 路由、多标签 KeepAlive。
新建/初始化 lit 项目 · 写 lit 组件 · 无构建前端 · 原生 ESM+importmap · WebComponent · signals · context DI · 多标签 KeepAlive · 部署 OBS+CDN。
禁装饰器(@customElement/@property/@state/@consume/@provide),用静态字段+编程式 API。
scaffold.mjs 初始化 / install-rules.mjs 装 .claude/rules / new.mjs 生成组件服务页 / deploy.sh 部署。
组件 kebab-case.js 类 PascalCase · 页面 page-*.js · 服务 *-service.js 单例 · 相对导入带 .js · 全局 signal+SignalWatcher · DI 用 @lit/context · 子目录 · CDN History 回退 200 禁 302。
依赖→FMODE Repose(.js text/javascript、CORS 放行、HTTP/2);业务码→obsutil sync src/→OBS(public-read)+刷 CDN;index.html no-cache。
装饰器/CORS/MIME 非 JS/多版本混用/CDN 302/base href 缺失/漏 .js/signal 与 property 混用/老浏览器无 ImportMap。
来源:tongjisheji/report-review 首次交付 6 页全部错乱的复盘(2026-10-29) 一句话:
createRenderRoot(){return this}不是“让 Tailwind 生效的小开关”,是渲染模型切换。切完以后<slot>、:host、::slotted、样式隔离全部失效,但代码看起来和普通 Lit 一样、编辑器也不报错。
<slot>没有 shadow root 就没有投影。<slot> 退化成普通未知元素,assignedSlot 为 null,子节点原封不动滞留在宿主元素上——外壳和内容成了兄弟节点,谁也没包住谁,于是滚动区空、导航错、高度链断。
自检一行:document.querySelector('main').children.length,为 0 就是投影断了。
页面元素由外壳自己按路由产出,让它天然成为 <main> 的子节点,父子关系由渲染函数保证:
// app-shell.js
_renderRoute() {
const { name, params } = parseRoute(this.path);
switch (name) {
case 'dashboard': return html`<page-dashboard></page-dashboard>`;
case 'project-detail': return html`<page-project-detail .code=${params.code||''}></page-project-detail>`;
}
}
// render 里:<main ...><div class="page-enter key-route-${this.active}">${this._renderRoute()}</div></main>
app-root 退化成只负责挂载:render() { return html\` }`。
light DOM 下只能自己搬节点。现成实现见 references/light-slot.js。顺序不能错:
capture(host) 在 connectedCallback 中、渲染前 调用,把 childNodes 存到 host.__lightIncomingproject(host) 在 updated() 中、渲染后 调用,把暂存的节点塞进 [data-light-slot="body"] 等容器dataset.projected 做幂等,否则每次重渲染都重复搬、节点来回跳display: block; min-width: 0自定义元素是未知元素,浏览器默认按 inline 处理:不吃 height/width(h-full/flex-1 全失效)、不形成块级盒子(内部 flex/grid 失去参照)、相邻元素间出现空白间隙。
app-shell, page-dashboard, page-analysis, data-table, kpi-card, empty-state, ... {
display: block; min-width: 0; /* min-width:0 让 flex 子项可收缩,否则长表格撑破父容器 */
}
两个例外必须排除:section-card、chart-box 的 class 自带 flex h-full flex-col,写死 display: block 会覆盖掉、把高度链断掉。
height:100% + min-height,不用 flex:1chart-box { height: 100%; min-height: 260px; } /* 父级无确定高度时 flex:1 算出来是 0,ECharts 拿到 0 高度直接不画 */
node scripts/check-elements.mjs 自动校验所有自定义元素都声明了 display —— 自动检查比文档写三遍有用。node --check 检查语法:少一个闭标签,lit-html 会把后面所有兄弟节点当成它的子节点吞掉,整页塌陷,且症状和渲染模型错误混在一起,排查方向严重跑偏。/dev/XXX/ 子路径全 404。用 __BASE__ 占位 + document.write 注入。探测目录不覆盖已有文件;依赖解析失败明确报错。