组件可以使用 ShadowDOM 封装。App 只使用 LightDOM,slot 用 property 替代,part 使用 class 代替,这样可以使用 TailwindCSS,并且能 SSR(或者 Prerender)。
Building UI components in DevTools with Custom Elements (goo.gle/building-ui-devtools)
目标:
- 原生 js,简单 // https://ionicframework.com/blog/the-end-of-framework-churn/
- 功能全,自定性能高
- 速度快,
https://music.xianqiao.wang/ MV* 模式
概念的核心是 Web Component,但 Web Component + Shadow DOM 是不适用于 APP,因为:
- 不能使用 svg 符号 // https://github.com/WICG/webcomponents/issues/772#issuecomment-438834060
- URL Hash 没用了 // 找不到具有该 id 的内置元素, :target 伪类也没用了,但 ::target-text 能用
- document.activeElement 没用了 // 好像可以模拟
- document.addEventListener 小心用,有些事件默认不冒出 ShadowDOM // Event.target 不准确
- 不方便集成 React/Vue 组件 // slot、gem-frame
- 样式不能穿透(自定义样式);选择不到或者不能继承的属性不方便共享;
- 不能重复定义元素(作为外部库时影响大)
- // 命名范围 // 名称可能会很长
// ShadowDOM 中使用 <link> 可以解决
// ::slotted // 注意一个空格就能占用插槽
// 单层穿透 ::part,多层穿透 ::theme // 类似原生元素的伪类选择
// Constructable Stylesheet Only actually parse the stylesheet when the first instance is connected.
slot 的问题:
- 总是挂载
- slot 未分配时可以占位,但分配时不能添加额外元素
https://forum.palemoon.org/viewtopic.php?f=1&t=24004 // 但是保存并不是那么重要
https://dev.to/richharris/why-i-don-t-use-web-components-2cia
https://dev.to/shihn/why-i-use-web-components-my-use-cases-1nip
模块化使用 ES6 Modules
- HTTP2 Server Push 提前加载深层依赖 // 安装(skypack/jspm/npm 内容)依赖到本地
- 无法单独为一个模块使用强制缓存
- 如何使用依赖的打包/压缩版本?
- 慢
JS 类型标记 // 使用 jsdoc/ts
ICON 使用自定义 ShadowDOM 元素(不能使用 SVG <use> // 片段标识不能越边界,讨论)
全局布局 CSS 阻塞渲染
组件使用 Web Component(包含组件样式)
// 每个 ShadowDOM 都有 <style> 将影响性能 https://bugs.chromium.org/p/chromium/issues/detail?id=824684
// 可以用 link 元素,现在有 blocking 属性
// 构造函数 XXXElement,名称 应用名-系统名-组件名
// ES 字段是 [[Define]] 语义,不要使用 js 字段覆盖已有定义
// MutationObserver 元素时检测不到 ShadowDOM 内的更改
ShadowDOM 组件一定存在一层嵌套,解决:display: contents; // 不影响语义
组件懒/异步加载:Custom Element when defined import(<depend>)
跨平台
- 组件支持多平台 // 媒体查询使用 <style media>, StyleSheet.media
- 不同入口
App 数据管理使用订阅模式,数据更新时通知订阅该数据的组件 // 不是订阅单个值而是数据模块
- 网络请求或者用户操作才会修改数据,本身是需要花费长时间
- 只是修改当前页面,内容不会太多
跨组件通信通过全局数据 // 只有组件状态才使用组件数据
UI 跟数据绝对分离 // 复杂项目方便维护
Router,Modal 使用 History API // 只有一次 onload
- 返回时通过 popstate event 响应
- 重写 pushState,为导航添加响应 // 在 push 前响应会造成和 pop 行为偏差
单元测试,UI 测试使用 puppeteer 进行 // 类似 wpt wct
组件订阅数据,数据更新时通知组件更新 // 使用 Set 保证多次同步修改组件只更新一次
服务端渲染使用声明式 ShadowDOM // 水合 DSD ?
// 构造样式不能直接使用 getInnerHTML 读取到,但有重复样式代码的问题
如何更新引用数据的 DOM/Node?
- 根据占位符使用查询建立数据和 DOM/Node 的绑定 -> diff(数据) -> 修改 DOM/Node
- vdom/ast 建立数据和 DOM/Node 的绑定 -> diff(数据) -> 修改 DOM/Node
- Parser 建立数据和 DOM/Node 的绑定
window.__ENV__DNS__HOST__ 格式区别环境 // Server 读取配置进行字符替换
键盘访问,语义化标签,Accessibility 支持
SEO 支持 扁平化 ShadowDOM,结构化页面可以使用 josn-ld
Tips:
- 防止和原生属性冲突,如 `title`, `id`
- 少使用组件/元素
- 自定义链接组件需要包装 a 标签 // a 有些特性用户代理支持
- 不要用 index 命名,不然在 devtools 和编辑器中全是同名文件
- 严格进行错误处理,在元素上发出 error 事件
Note:
- closest/matches 都是在 shadowRoot 内工作
Node Server:
- live load // webpack
- hot load(所有组件保存所有状态刷新在恢复) // 自定义元素不能重新定义,upgrade 没用,需要新元素替代(替换 -> attr/props 赋值 -> update)
- 发布 // release