水歌 —— 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 { Component } from 'react';
export class MyComponent extends 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>
<template shadowrootmode="open">
<button><slot></slot></button>
</template>
按钮
</my-button>
渐进增强,优雅降级
<!-- 良 -->
<my-button>
<template shadowrootmode="open">
<slot></slot>
</template>
<button>按钮</button>
</my-button>
<!-- 优 -->
<button is="my-button">
<template shadowrootmode="open">
<slot></slot>
</template>
按钮
</button>
兼容性
原生
polyfill
IE 11 +
工程问题
数据绑定
资源打包
路由定义
状态管理
WebCell
优雅、轻量的 Web 组件引擎
声明式组件
官方适配 MobX
import { observer } from 'web-cell';
import { app } from '../model';
export const PageIndex = observer(() => (
<div onClick={app.increase}>count: {app.count}</div>
));
import { component, observer } from 'web-cell';
import { app } from '../model';
@component({ tagName: 'page-index' })
@observer
export class PageIndex extends HTMLElement {
render() {
return <div onClick={app.increase}>count: {app.count}</div>;
}
}
极简路由 —— Cell Router
路径即状态,容器即组件
import { DOMRenderer } from 'dom-renderer';
import { FC } from 'web-cell';
import { createRouter, PageProps } from 'cell-router';
const { Route, Link } = createRouter();
const TestPage: FC<PageProps> = ({
className,
style,
path,
history,
...data
}) => (
<ul {...{ className, style }}>
<li>Path: {path}</li>
<li>Data: {JSON.stringify(data)}</li>
</ul>
);
new DOMRenderer().render(
<>
<nav>
<Link to="test?a=1">Test</Link>
<Link to="example/2">Example</Link>
</nav>
<main className="router">
<Route
path=""
component={props => <div {...props}>Home Page</div>}
/>
<Route path="test" component={TestPage} />
<Route path="example/:id" component={TestPage} />
</main>
</>
);
异步加载页面
tsconfig.json
{
"compilerOptions": {
"module": "ESNext"
}
}
source/page/index.ts
import { lazy } from 'web-cell';
export default [
{
path: '',
component: lazy(() => import('./Home'))
},
{
path: 'list',
component: lazy(() => import('./List'))
}
];
开箱即用
npm init -y
npm i dom-renderer web-cell mobx cell-router web-utility boot-cell
npm i parcel -D
官方组件库 —— BootCell
推荐组件库 —— MDUI
竞争对手
Google Polymer/Lit
Ionic Stencil
Tencent Omi