# 风险与坑位
> 无构建方案的红线。踩中任何一条,症状往往隐蔽(白屏 / 404 / 路由丢失),
> 排查时先按本表逐条对照。
## 〇、用了装饰器语法(`@customElement` 等)
**症状**:整个模块 `SyntaxError: Invalid or unexpected token` / `Unexpected character '@'`,白屏。
**原因**:装饰器是 **TS/Babel 编译期**特性,浏览器原生 JS **不执行**。Lit 官方文档默认「装饰器 + 构建」,
无构建下照抄即挂。
**对策**:一律用 `static properties` + `customElements.define()` + `ContextConsumer` 编程式 API。
详见 [components.md](./components.md) 第〇节。**本项目所有示例均无装饰器**。
## 一、CORS(最高频)**症状**:模块加载失败、`Failed to fetch dynamically imported module`、控制台 CORS 报错。
**原因**:FMODE Repose 未放行业务域名。
**对策**:Repose 的 CORS 必须允许所有业务域名;跨域模块请求带 `Origin`,响应需 `Access-Control-Allow-Origin`。
> ESM 模块请求是 **CORS 请求**(比普通脚本严格),勿用「能下载就能跑」判断。
## 二、Content-Type 不是 `text/javascript`
**症状**:`Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of ...`。
**原因**:依赖仓把 `.js` 当 `text/plain` / `application/octet-stream` 返回。
**对策**:Repose 上传时显式设 `Content-Type: text/javascript; charset=utf-8`。
## 三、多版本 lit 混用
**症状**:自定义元素重复注册报错 `Failed to execute 'define' ... has already been used`;或响应式失效。
**原因**:importmap / 直接路径引到了两个不同版本的 lit(如 `lit@3.1` 与 `lit@3.2` 各被加载一次,`LitElement` 类不同)。
**对策**:**全业务统一 importmap**,只允许一个 lit 版本;所有包都从同一 Repose 版本段解析。除 `lit` 本身,注意 `lit-html` / `lit-element` / `@lit/reactive-element` 也要**单实例**(importmap 里补全子路径映射)。
## 四、History 路由被 CDN 302
**症状**:深链接刷新后地址栏变成 `/index.html`,路由参数丢失;微信 OAuth 回调失败。
**原因**:CDN 回退用 301/302 重定向。
**对策**:回退**必须 200** 返回 `index.html`,地址栏不变。见 [deployment.md](./deployment.md)。
## 四·一、`new URL(path, baseURI)` 丢部署前缀(子路径部署最高频)
**症状**:子路径部署下(`/dev/myapp/`)导航跳到域名根 `https://cdn/students`,刷新 404;
根部署时正常。
**原因**:`new URL` 的相对解析:`input` 以 `/` 开头时是绝对路径,会**整体替换** base 的
path,**不保留** base 的目录前缀。
**对策**:把应用内路径**拼到 base 的 pathname 上**再解析;导航一律走 `utils/router.js`。
见 [routing-best-practices.md 坑 1](./routing-best-practices.md#坑-1任意的-new-urlpath-baseuri-会丢掉部署前缀)。
## 四·二、`@lit-labs/router` 污染子路径部署的路由状态
**症状**:引入后路由偶发跳错/停错页/点内部链接整页刷新,根部署正常、二级路径异常。
**原因**:它装全局 `` click 拦截器自行 `pushState`;且用**未剥前缀**的 `location.pathname`
匹配(路由表是应用内路径)。
**对策**:子路径部署**自写** `utils/router.js`(~60 行);根部署可选。
见 [routing-best-practices.md 坑 2](./routing-best-practices.md#坑-2lit-labsrouter-在子路径部署下污染路由状态)。
## 五、子目录部署缺 ``(或三处前缀不一致)
**症状**:`./app.js`、相对 import 全部 404(被解析到域名根)。
**原因**:子目录部署(`/admin/`)未写 ``;或 ``、
OBS 上传前缀、importmap 路径前缀**三处不一致**。
**对策**:三处由**同一变量派生**,部署脚本里 `grep` 硬校验;`navigate()` 基于 base 拼 URL。
见 [routing-best-practices.md 坑 4](./routing-best-practices.md#坑-4base-href-与实际部署前缀不一致三处必须同源)。
## 六、相对 import 漏写 `.js` 后缀
**症状**:`Failed to resolve module specifier './foo'` / 404。
**原因**:浏览器原生 ESM **不做扩展名补全**。
**对策**:相对导入一律带 `.js`(`import x from './foo.js'`)。裸标识符(`lit`)走 importmap。
## 七、Signal 与 Lit property 混用
**症状**:更新时机错乱、该刷不刷、或刷两次。
**原因**:把「组件本地 UI 状态」也塞进全局 signal,或把「跨组件业务状态」放在 `@property` 里手动同步。
**对策**:**划清界限** —— 组件内 UI 用 `@property`/`@state`;跨组件业务用 signal + `SignalWatcher`。
## 八、ESM 小文件多 → 请求多
**症状**:首屏依赖瀑布、加载慢。
**原因**:无打包 = 每个模块一次请求。
**对策**:Repose 开 **HTTP/2/3** 多路复用;合理目录深度(别过深);`Cache-Control` 长缓存版本化依赖。
## 九、老浏览器不支持 Import Map
**症状**:低版本浏览器无法解析裸标识符。
**原因**:Import Map 是现代浏览器原生特性。
**对策**:内部后台一般可接受现代浏览器基线;如需兼容,引入 import-map polyfill(es-module-shims)放在所有 module 之前。
## 十、循环依赖导致初始化时机问题
**症状**:某模块拿到 `undefined`(如 `http` 顶层解构了尚未初始化的 `authService`)。
**原因**:ESM 循环引用 + 顶层立即求值。
**对策**:跨模块引用**延迟到函数内**取值(别在模块顶层解构相互引用的单例)。
## 十一、无构建下的其他红线
| 坑 | 说明 |
|----|------|
| 引入需要打包器转译的依赖(CJS/TS) | 浏览器无法直接跑 CJS;只选 **原生 ESM** 依赖 |
| 用 `.ts` 源码直发 OBS | 浏览器不认 TS;无构建下**源码即 JS** |
| 直接改 `src/` 后忘记刷 CDN | 看到旧版本;发布流程要含 CDN 刷新 |
| `index.html` 被 CDN 长缓存 | 回退/更新拿到旧壳;`index.html` 设 `no-cache` |
| `index.html` 被 `sync` 跳过 | `sync` 按 size+mtime 判 Skip;**先删后传** |
| CDN 刷新「看似成功」 | `refresh_task` 是字符串 ID,读 `.id` 恒 undefined → 假成功;须非空断言 + 失败退出非 0 |
| CDN 刷新只刷了一个域名 | domain_id/凭证对号入座;未知域名直接报错 |
| 只 `curl` 首页算验收 | 首页 200 ≠ 能跑;须模块图爬取 + 真实浏览器 |
| 自定义元素标签忘带连字符 | `define` 抛错,如 `approot` 应 `app-root` |
| 在卡片内嵌链接未 `stopPropagation` | 点内层链接触发外层跳转;内层 `@click` 断冒泡 |
> 路由 / base / CDN 的完整肺坑见 [routing-best-practices.md](./routing-best-practices.md)。
## 十二、排查口诀
1. 看控制台 **MIME / CORS / 404** 三类报错——先分清是「依赖没托管好」还是「业务路径错」。
2. 打开 Network:模块请求的 **Content-Type** 与 **状态码**。
3. 深链接刷新 404 → 查 CDN 回退规则(是否 200)。
4. 相对资源 404 → 查 `` 与部署前缀是否一致。
5. 导航跳到域名根 / 前缀丢失 → 查是否用了 `new URL(path, baseURI)`。
6. 点内部链接整页刷新 / 路由跳错 → 查是否引入 `@lit-labs/router`(子路径部署)或裸 ``。
7. 线上还是旧版 → 查 CDN 刷新的 domain_id 与 `refresh_task` 返回值(别信「假成功」日志)。
8. 元素重复注册 → 查 lit 是否多版本。