开坑,我又写了个前端框架,专注于下一代 Web Component UI 库
大家好我是 anuoua,我之前写了个前端框架叫 Unis,Github:anuoua/unis,在掘金发了几篇介绍的文章:
- 我终于登上了这个榜单!与 Vue 3 和 React 正面 PK!和全世界框架同台比武!
- 我终于可以不依赖 Vue 3 和 React 了!Unis 1.2.0 正式发布!支持 SSR
- 前端框架 Unis 带着它的生态来了!
骗了不少赞和评论互动,然而这里面有个老哥貌似不买账,他想要一个不需要构建的又好用的前端框架,需求很强烈。
评论截图
我当时的想法是,这年头前端框架基本和编译分不开,真的没啥必要排斥,就没有理他。
嗯?
在某个机缘巧合之下,我开始想这个问题,其实不需要编译的前端框架很简单,主要是需要接受全新的视图写法以代替 jsx ,首先想到的是直接在 react 中写 createElement 函数就行了,这确实是可以的,但是不美观。
长这样:
const h = createElement;
const App = () => {
return h("div", { class: "main" },
h("div", null,
h("span", null,
"hello world!"
)
)
);
};
可以优化,直到我看到 van 这个前端框架,受它的写法启发,是可以这么写的,干净不少:
const App = () =>
div({ class: "main" },
div(null,
span(null,
"hello world"
)
)
);
这样是不是好看多了?改造成这种形态可以直接生成对应的 ReactElement ,这样就可以在不使用编译的情况下使用 React 了,但是看着 null 还是有点膈应。
要不再改一下,利用高阶函数
const App = () =>
div({ class: "main" })(
div()(
span()(
"hello world"
)
)
);
这套工具包我顺手就做了,后来才发现,这个老哥并不是要这个东西。。
但是东西我已经写好了(没几行代码,哈哈),来都来了,感兴趣的可以点的 start 呗。
Github: github.com/anuoua/jsx-…
进入正题
前面只是导火索,刚才我有提到 van 这个框架,这个框架是无虚拟 DOM 的非编译型响应式框架,对应目前很火的 solid 则是无虚拟 DOM 编译型响应式框架。
我突然对无虚拟 DOM 这件事产生了兴趣,真实的 DOM vs 虚拟 DOM ,这里面有什么惊为天人的秘密吗?
所以我研究了一下 van 和 solid,稍微懂了点无虚拟 DOM 前端框架的门道。
思索过后我有点自己的想法,于是开始琢磨写一个这种类似的框架,给这个框架列了个简单的目标:
- 响应式
- 无虚拟 DOM ,不用编译
- 专注于 webcomponent ,专注于 ui 库
以上特点,solid 也不是不可以,但是它的主要目标不是不编译和专注 webcomponent 组件,真要那样用 DX 体验没有那么好,不了解的可以看它的文档。
技术方案
响应式 api
我看了下 van 的实现太简单了,van 本身为了小巧舍弃了不少 DX 体验,这不是我要的。当我看 solid 实现的时候,发现它的响应式 api 和 @vue/reactivity 很像,类似的还有 @preact/signals ,大差不差,只是做了点取舍。
这么看来,基于 proxy 的响应式大体都是这么回事,无非大家的考虑和取舍不太一样。
这个我不打算自己写了,@preact/signals @vue/reactivity 都是可以直接用的,但 @vue/reactivity 是这类响应式 api 的集大成者,功能是目前最全的,例如 @preact/signals 对深层数据的响应就不太行。
所以暂时用 @vue/reactivity 作为响应 api 。
无虚拟 DOM ,不用编译
@vue/reactivity 是可以直接引入浏览器用的,摆在面前的困难就是视图 dsl 怎么整比较优雅。
通过真实的 DOM 构建用户界面,如果不能借助 jsx 编译或者 html 模板编译那么挺难受的,但是上面提到的那套视图构建函数可以勉强接近 jsx 开发体验,而且类型友好,那套函数不仅可以产出虚拟 DOM 元素,同样可以直接创建 DOM 。
// 实现示例
const div =
(attrs) =>
(...children) => {
const el = document.createElement("div");
el.append(...children);
return el;
};
// 但是每个都要写就很麻烦,所以写个方便的 proxy,搭配 typescript 体验很是ok。
const tags = new Proxy(
{},
{
get(target, key) {
// ...
},
}
);
const { div, span } = tags;
// prettier-ignore
div({ class: "main" })(
div()(
span()(
str("hello world")
)
)
);
// <div><div><span>hello world</span></div></div>
至于核心逻辑,响应式数据如何绑定到 DOM 元素,以及视图突变怎么处理。这里我简要说一下,界面突变无非这三种 “增”,“删”,“改”。
“改”是最简单的,既监听数据变更,通过 effect 处理 DOM 的 attributes 更新。 “增”和“删”比较复杂,想想我们平时使用其他框架的时候,用于实现界面突变的方法,主要就两个
- 条件渲染:对应 vue 的 v-if else,react 的 xxx && ()、xxx ? () : ()
- 列表渲染:对应 vue 的 v-for,react 的 list.map()
然后再想想,条件逻辑貌似也是列表逻辑,既列表 [true] 和 列表 [false] 的相互切换,所以本质上只需要完成列表逻辑即可,至于列表更改后怎么处理 DOM ,建议直接看源码吧,挺简单的。
专注于 webcomponent
我预测下一代前端 ui 库一定会大规模使用 webcomponent ,ui 库全框架化将成为主要潮流。所以本框架将专注于 webcomponent ,专注于下一代 ui 库。
是的,后续应该和 stencil 目标一致,是成为下一代全框架 ui 库的编译器(编译最终还是要上的)!
掘金的老哥们给点力,点点赞,点点 start,给我力量,让我把它最终做出来,干掉 stencil ,国产框架雄起!😘
项目进度
借助于现成的 @vue/reactivity,很快就实现了 demo。
现在已经实现基本的组件逻辑,以及常见视图变更逻辑
- defineComponent
- If
- Switch
- For
可以做 demo 了。
成果展示
如下图,这个“精致”的 Todo list,意味着框架基本逻辑已经完成。
webcomponent 组件
最后
地址:anuoua/j20 ,老哥们点个 star 吧
项目中文名: 歼 20,是的,你没听错
项目状态:正在开发中 (WIP) 还没发包
代码质量:能跑就行
完
转载自:https://juejin.cn/post/7256397479646117947