如何开发了一个前端库 or 框架?
前端发展速度已经远远超出了我们的预计范围,前端基于 JS 的框架/库更是层出不穷。
那么框架与库有什么区别呢?库更多是一个封装好的特定的集合,提供给开发者使用,而且是特定于某一方面的集合(方法和函数),库没有控制权,控制权在使用者手中,在库中查询需要的功能在自己的应用中使用,我们可以从封装的角度理解库;框架顾名思义就是一套架构,会基于自身的特点向用户提供一套相当于叫完整的解决方案,而且控制权的在框架本身,使用者要找框架所规定的某种规范进行开发。
我们尝试举个例子:你想烹饪一条鱼,这时你需要一些原料,比如油、盐、醋、葱、香料以及烹饪工具等,同时鱼也是你需要的主要材料,当你聚齐所有材料时,再经过烹饪就得到成品;现在我们进行对比,其中油、盐、醋、葱、香料以及烹饪工具和鱼其实就是库,组合到一起就成了框架,成品就是最终开发好的应用,或者这样能是你更好里的理解框架与库之间的区别。
我们熟知的 React,它就是一个库,官网称是一个用于构建用户界面的 JavaScript 库。刚接触 React 的时候,在 JS 文件 中写 HTML 语法 觉得很奇妙,后来就觉得自己可不可以也搞一个库来锻炼下自己的能力呢!于是在业余时间自己通过各种摸索搞了一个迷你版 JSX,但是在数据驱动层面上遇到了一些问题,几天的辗转反侧,自己终于想明白了。我为什么先要做一个仿 React 的库呢?我能不能换一种方式来实现这种类 JSX 语法呢!我们知道 ES6 语法中有模板字符串,你可以在里面写 HTML 标签,并且在字符串中可以嵌入变量。找到了开发方向,我就开始搜索各种资料,正式进入开发库的过程。这个过程是非常痛苦的,但庆幸的是库的开发都是我一个人参与的,我可以按照我的想法去做,去完善它。
我将这个库命名为 Strve.js,为什么会这样命名呢?它其实是字符串(String)与 视图(View)的名称缩写拼接而成。它实现了在 JS 字符串中写 HTML 标签,并且可以嵌入变量,达到了数据驱动视图的效果。并且你可以灵活地分离代码块,提高工作效率。到这里你会觉得这有什么难的,使用 innerHTML
也可以实现类似的效果。效果是一样的,但是在一定场景下 DOM 性能是不同的。Strve.js 另一个定位就是一个轻量级的 MVVM 框架,你只需要关心数据操作,其他事情由 Strve.js 内部的 Virtual DOM 来处理,这也是目前优化 DOM 性能的一种常用手段。
最终,功夫不负有心人。在 2021 年 11 月 3 日发布了第一个版本 1.0.4,然后接下来就是不断地更新迭代,让它的生态更加完善,更加符合一个库或者框架的要求。中途也遇到过架构重新搭建等等情况,到目前为止,Strve.js 最新版本是 5.1.1,发布时间为 2023 年 1 月 1 日。
下面我来详细介绍下 Strve.js。
文档
Strve.js 文档是基于 VitePress 搭建,分别部署到 Github 和 Gitee 上。
Github 中文网址:https://maomincoding.github.io/strve-doc/zh
Gitee 中文网址:https://maomincoding.gitee.io/strve-doc/zh
下面,将根据文档内容分成几大模块,分享我设计这个库时的心得体会。
开始
首先,我们进入开始页。尝试学习一样新的技术,最简单的就是做一个小的案例。通过这些简单的案例来进一步学习它的用法,因为往往这些最简单之处最能体现出它的核心用途。
你可以通过两种方式去使用它,一种是 ES Modules,另一种是UMD。我们先分别列举两种方式,然后统一讲一下代码结构。
现代浏览器大多都已原生支持 ES 模块。因此我们可以像这样通过 CDN 以及原生 ES 模块使用 Strve.js:
html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Strve.jstitle>
head>
<body>
<script type="module">
import {
h,
setData,
createApp,
} from 'https://cdn.jsdelivr.net/npm/strve-js@5.1.1/dist/strve.full-esm.js';
const state = {
count: 0,
};
function add() {
setData(() => {
state.count++;
});
}
function App() {
return h`
${state.count}
>Add
`;
}
const app = createApp(App);
app.mount('#app');
script>
body>
html>
UMD 叫做通用模块定义规范(Universal Module Definition)。也是随着大前端的趋势所诞生,它可以通过运行时或者编译时让同一个代码模块在使用 CommonJs、CMD 甚至是 AMD 的项目中运行。未来同一个 JavaScript 包运行在浏览器端、服务区端甚至是 APP 端都只需要遵守同一个写法就行了。
你也可以选择使用 标签直接引入,这样就可以直接在浏览器中打开了。
html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Strve.jstitle>
head>
<body>
<script src="https://cdn.jsdelivr.net/npm/strve-js@5.1.1/dist/strve.full.prod.js">script>
<script>
const { h, setData, createApp } = Strve;
const state = {
count: 0,
};
function add() {
setData(() => {
state.count++;
});
}
function App() {
return h`
${state.count}
>Add
`;
}
const app = createApp(App);
app.mount('#app');
script>
body>
html>
两种不同的方式其中相同的代码无非是这几行,通过数据与视图页面的绑定,操作业务逻辑,你会发现Strve.js更加关注结果,至于如何实现这个结果的,我们并不关心。换句话说,Strve.js帮我们封装了过程。
const state = {
count: 0,
};
function add() {
setData(() => {
state.count++;
});
}
function App() {
return h`
${state.count}
>Add
`;
}
const app = createApp(App);
app.mount('#app');
通过上面的案例你会发现一切代码逻辑都是在JS中,可以说离 All in JS 更近了一步。
安装
我们简单快速地了解 Strve.js 的使用,那么我们在这一篇详细说明下 Strve.js 有哪些安装方法。
CDN
使用CDN在上面也提到了,一种是ES Modules,另一种是UMD。因为篇幅的原因,这里就不再复述了。
包管理器
当你构建大型应用时,推荐使用 包管理器 安装。
> npm install strve-js
> yarn add strve-js
命令行工具
当你构建大型应用时,推荐使用 Strve.js 提供的官方项目脚手架来搭建项目。为单页面应用 (SPA) 快速搭建繁杂的脚手架。它为现代前端工作流提供了开箱即用的构建设置。
目前有两款工具:
CreateStrveApp CreateStrve
下面会再详细介绍。
对不同构建版本的解释
在 NPM 包的 dist/
目录你将会找到很多不同的 Strve.js 构建版本。这里列出了它们之间的差别:
ES Module (基于构建工具使用) | ES Module (直接用于浏览器) | UMD | |
---|---|---|---|
完整版 | - | strve.full-esm.js | strve.full.js |
完整版(生产环境) | - | strve.full-esm.prod.js | strve.full.prod.js |
运行时版 | strve.runtime-esm.js | - | - |
运行时版(生产环境) | strve.runtime-esm.prod.js | - | - |
不同的版本:
完整版本:包括编译器(用于将模板字符串编译为 JavaScript 呈现函数的代码)和运行时版本; 运行时版:用于创建实例、渲染和处理虚拟 DOM 的代码。基本上,它是从编译器中删除所有其他内容;
在Strve.js@5.1.1发布之前,用户可以提供 HTML 字符串,我们将其编译为数据对象后再交给运行时处理。准确地说,上面的代码其实是运行时编译,意思是代码运行的时候才开始编译,而这会产生一定的性能开销,因此我们也可以在构建的时候就执行编译程序将用户提供的内容编译好,等到运行时就无须编译了,这对性能是非常友好的。 这也是为什么我们发布了不同的构建版本,以适用于不同场景。
API
API目前总共有10个。
createApp
、h
、setData
这三个为基本API,主要用于渲染页面;onMounted
、onUnmounted
这两个API为生命周期钩子函数;nextTick
、domInfo
这两个API则为与DOM相关的API;version
则为辅助API,用于获取Strve.js版本号;propsData
、defineCustomElement
则为特性API,为适配一些特性而设计。
其中,值得一提的是defineCustomElement
。这个API用于支持 Web Components 的引入,它可以传两个参数。
第一个参数是对象类型,对象属性如下:
属性 | 类型 | 必选 | 含义 |
---|---|---|---|
id | String | 是 | 原生自定义组件ID,应保持其唯一性 |
template | Function | 是 | 返回一个模版字符串函数 |
styles | Array | 否 | 原生自定义组件样式集合 |
attributeChanged | Array | 否 | 原生自定义组件监听属性集合 |
immediateProps | Boolean | 否 | 原生自定义组件是否开启立即监听属性变化 |
lifetimes | Object | 否 | 原生自定义组件生命周期,与Web Components生命周期一致 |
第二个参数是字符串类型,原生自定义组件的名称,名称中必须含有-
字段。
示例1:
const data = {
count1: 1
}
const myCom1 = {
id: "myCom1",
template: () => {
return h`
${data.count1}
`
},
styles: [`.msg { color: red; }`],
}
defineCustomElement(myCom1, 'my-com1')
function App() {
return h`
`
}
示例2:
const myCom1 = {
id: "myCom1",
template: (props) => {
return h`
${props.value}
${props.msg}
`
},
styles: [`.msg { color: red; }`],
attributeChanged: ['value', 'msg'],
immediateProps: true,
lifetimes: {
attributeChangedCallback(v) {
console.log(v);
}
}
}
defineCustomElement(myCom1, 'my-com1');
const data = {
count1: 1,
count2: '1',
}
function add() {
setData(() => {
data.count1++;
})
}
function App() {
return h`
">btn
${data.count1} msg="${data.count2}" $key>
`
}
下面,我发布了一个Web Components的案例。
预览链接:
https://codepen.io/maomincoding/pen/RwBBZpr
用法
目前共有11种用法,有些用法是对一些API的补充。
其中数据绑定、属性绑定、条件渲染、列表渲染、事件处理 这五种用法为基本用法。详细用法可以去文档中查阅,这里就不做赘述。
其余6种则为拓展用法,比如里面提到的静态标签中$key
,比如我们在更改数据时,Strve.js内部需要用到Diff算法进行差异对比,但是也不是所有的节点都需要进行差异对比,像一些静态文本,前后变化都一样,就不需要再参与对比。那么用户在一些需要变化的动态节点上提供$key
静态标记,标识这需要动态变化。
还有我们也可以自己封装自定义组件,我将其命名为命名功能组件,比如像下面这样。另外,你需要父子传值,也可以通过搭配$props
实现。
const state1 = {
count: 0,
};
function add1() {
setData(
() => {
state1.count++;
},
{
name: Component1,
}
);
}
function Component1() {
return h`
Component1
${state1.count}
>add1
`;
}
function App() {
return h`
txt1
txt2
${Component1.name}>
${Component1()}
`;
}
我们也内置了很多组件,比如fragment
标签,它创建一个文档片段标签。它不是真实 DOM 树的一部分,它的变化不会触发 DOM 树的重新渲染,且不会对性能产生影响。
const state = {
x: 0,
y: 0,
};
function App() {
return h`
Mouse position is at: ${state.x}, ${state.y}
`;
}
最后,我们在上文中提到支持Web Components,这里做一下补充。
自定义元素的主要好处是,它们可以在使用任何框架,甚至是在不使用框架的场景下使用。当你面向的最终用户可能使用了不同的前端技术栈,或是当你希望将最终的应用与它使用的组件实现细节解耦时,它们会是理想的选择。
工具
到目前为止,Strve.js不仅仅是一个渲染页面的库,它可以搭配其他工具为你生成一个项目搭建框架。
现在有4款工具,分别是CreateStrveApp、CreateStrve、StrveRouter、BabelPluginStrve。
CreateStrveApp与 CreateStrve 都是快速构建 Strve.js 项目的命令行工具,与早期的脚手架 CreateStrve 相比,CreateStrveApp 更好,可以直接输入命令快速创建 Strve 项目。CreateStrveApp 是使用 Vite 构建的,这是一个新的前端构建工具,可以显着提升前端开发体验。
当我们使用Strve.js构建单页面应用时,一旦页面加载完毕,整个页面就不会因为用户的操作而重新加载或者页面跳转,没有页面的跳转,是如何实现页面跳转的效果呢?使用路由机制,实现内容的切换,实现不同内容的展示。
StrveRouter 是 Strve.js 的官方路由管理器。它与 Strve.js 的核心深度集成,轻松构建单页应用程序。你可以通过下面代码快速了解StrveRouter的使用。
html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>strve-routertitle>
head>
<body>
<div id="app">div>
<script type="module">
import {
h,
createApp,
setData,
} from 'https://cdn.jsdelivr.net/npm/strve-js@5.1.1/dist/strve.full-esm.prod.js';
import {
initRouter,
linkTo,
} from 'https://cdn.jsdelivr.net/npm/strve-router@2.3.1/dist/strve-router.esm.js';
class Home {
constructor() {
this.state = {
count: 0,
};
}
useAdd = () => {
setData(() => {
this.state.count++;
});
};
goAbout = () => {
linkTo('/about');
};
render = () => {
return h`
>goAbout
${this.useAdd}
$key>${this.state.count}
`;
};
}
class About {
constructor() {
this.state = {
msg: 'About',
};
}
useChange = () => {
setData(() => {
this.state.msg = 'Changed';
});
};
goHome = () => {
linkTo('/');
};
render = () => {
return h`
>goHome
${this.useChange} $key>${this.state.msg}
`;
};
}
const router = initRouter(
[
{
path: '/',
template: [Home, 'render'],
},
{
path: '/about',
template: [About, 'render'],
},
],
setData
);
function App() {
return h`
${router.view()}
`;
}
const app = createApp(App);
app.mount('#app');
script>
body>
html>
我们在上文中的提到的编译器就是指的是BabelPluginStrve,BabelPluginStrve是一款babel 插件,将 HTML 模板字符串转化为 Virtual Dom。从之前的运行时转移到编译时,大幅度提高渲染性能。
// input:
const state = {
count: 0,
};
h`${state.count}
`;
// output:
{
children: [0],
props: {"$key": true},
tag: "h1"
}
相关安装与使用方式可以参照文档。
其他
目前总共分为5个模块,分别为更新日志、IDE支持、UI 框架、浏览器兼容性、关于。
其中值得一提的是,在使用编辑器时,如何使HTML标签在模版字符串中高亮显示。比如这里我们使用Visual Studio Code时,下载es6-string-html插件后,在h``中间添加 /* html */。
function App() {
return h/* html */ `
${state.msg}
`;
}
另外,UI框架除了支持Bootstrap5、Tailwindcss、还支持Quark,Quark 是一款基于 Web Components 的跨框架 UI 组件库。它可以同时在任意框架或无框架中使用。
这是基于Quark的预览地址:
https://codepen.io/maomincoding/pen/MWOmyLW
然后,Strve.js也可以跟Three.js搭配使用,以下是预览链接:
https://codepen.io/maomincoding/pen/GRBYzzM
最后,以下是我的个人社区信息,希望大家多多支持。
昵称:Vam ID:maomincoding
Github: https://github.com/maomincoding
Twitter: https://twitter.com/maomincoding
掘金:https://juejin.cn/user/2506542244168909
知乎:https://www.zhihu.com/people/maomincoding
CSDN:https://maomin.blog.csdn.net
微信公众号:前端历劫之路
结语
至此,关于Strve.js的介绍到这里就结束了,感谢阅读。如果大家想进一步了解Strve.js,可以到官方文档查阅。Strve.js我也会继续维护下去,虽然路很长,很难走,但是我也不会放弃。
Strve.js 源码仓库:https://github.com/maomincoding/strve
Strve.js 中文文档:https://maomincoding.gitee.io/strve-doc/zh/