水歌 —— WebCell 引擎作者
水歌其人
- Web/JavaScript 全栈开发者
- WebCell 等多个开源项目的作者
- jQuery、Babel 等多个国际开源项目的贡献者
- freeCodeCamp 成都社区负责人
- 开源社官网开发组长、执委会成员
- 微软 MVP
Web 组件的概念
将网页中某块界面的 HTML 结构、CSS 样式、JS 逻辑,封装成一个可移植的模块
Web 组件技术简史
上古时代
W3C:
<frameset />
、<iframe />
IE: HTML Component (HTC)
中古时代
function MyComponent(options) {
this.options = options;
}
$.extend(MyComponent.prototype, {
method: function () {}
});
$.fn.myComponent = function (options) {
this.each(function () {
$.data(this, 'instance', new MyComponent(options));
});
};
近代
import React from 'react';
export default class MyComponent extends React.Component {
render() {
return <h1>Hello, World!</h1>;
}
}
历史问题
隔离性好的,运行时过重
易用性好的,工程化不佳
实用性强的,工具链过重
Web 组件标准
https://www.webcomponents.org/
标准集
- HTML 5.3
- DOM 4.1
- CSS Variables level 1
- ECMAScript 6+
主要目标
运行时 轻量级 隔离环境
框架无关的 DOM 接口
原生写法
样式定制
my-component {
--my-color: blue;
}
:host {
}
:host(.class) {
}
:host-context(.class) {
}
::slotted(*) {
color: var(--my-color);
}
生命周期
class MyComponent extends HTMLElement {
static get observedAttributes() {
return ['title'];
}
attributeChangedCallback(attrName, oldVal, newVal) {}
connectedCallback() {}
disconnectedCallback() {}
adoptedCallback() {}
}
扩展原生
customElements.define('my-button', class extends HTMLButtonElement {}, {
extends: 'button'
});
<button is="my-button">按钮</button>
规范模式
不要接管一切
<my-button>
<!-- shadow-root -->
<button><slot></slot></button>
<!-- shadow-root -->
按钮
</my-button>
渐进增强,优雅降级
<!-- 良 -->
<my-button>
<!-- shadow-root -->
<slot></slot>
<!-- shadow-root -->
<button>按钮</button>
</my-button>
<!-- 优 -->
<button is="my-button">
<!-- shadow-root -->
<slot></slot>
<!-- shadow-root -->
按钮
</button>
兼容性
原生
polyfill
IE 11 +
工程问题
数据绑定
资源打包
路由定义
状态管理
WebCell
优雅、轻量的 Web 组件引擎
声明式组件
官方适配 MobX
import { createCell } from 'web-cell';
import { observer } from 'mobx-web-cell';
import { app } from '../model';
export default observer(function PageIndex() {
return <div onClick={app.increase}>count: {app.count}</div>;
});
import { createCell, component, mixin } from 'web-cell';
import { observer } from 'mobx-web-cell';
import { app } from '../model';
@observer
@component({
tagName: 'page-index'
})
export default class PageIndex extends mixin() {
render() {
return <div onClick={app.increase}>count: {app.count}</div>;
}
}
极简路由 —— Cell Router
路径即状态,容器即组件
import { documentReady, render, createCell, Fragment } from 'web-cell';
import { History, PageProps, CellRouter } from 'cell-router/source';
const history = new History();
function TestPage({ path, history, defaultSlot, ...data }: PageProps) {
return (
<ul>
<li>Path: {path}</li>
<li>Data: {JSON.stringify(data)}</li>
</ul>
);
}
documentReady.then(() =>
render(
<>
<nav>
<a href="test?a=1">Test</a>
<a href="example?b=2">Example</a>
</nav>
<CellRouter
className="router"
history={history}
routes={[{ paths: ['test', /^example/], component: TestPage }]}
/>
</>
)
);
异步加载页面
tsconfig.json
{
"compilerOptions": {
"module": "ESNext"
}
}
source/page/index.ts
export default [
{
paths: ['', 'home'],
component: async () => (await import('./Home.tsx')).default
},
{
paths: ['list'],
component: async () => (await import('./List.tsx')).default
}
];
开箱即用
npm init -y
npm install web-cell mobx mobx-web-cell cell-router web-utility boot-cell
npm install parcel-bundler -D
官方组件库 —— BootCell
官方组件库 —— Material Cell
竞争对手
Google Polymer
Ionic Stencil
Tencent Omi