Vue.js设计与实现-框架设计的核心要素

开发 前端
在本文中主要介绍了开发体验、tree-shaking以及错误处理等对框架设计的重要性,提供良好的警告信息可以有助于开发者快速定位问题,框架提供错误处理API能够提升用户应用程序的健壮性。

1、写在前面

在前面文章中,了解到框架的设计是一种权衡的艺术,是需要宏观把握各方面因素从而尽可能完善。其实,框架设计比想象中的复杂,并不是只有功能实现就完事,需要考虑如何提供给使用者构建产物、如何让其快速定位问题,需要有良好的使用交互体验。

2、提升用户体验

衡量框架是否优秀的重要指标,就是看它的开发体验是否达到预期,需要为用户提供良好直观的交互指引和错误警示,帮助用户快速理解和错误定位。

3、控制代码体积

在进行项目开发中,在实现相同功能代码越简洁、越少,在进行打包压缩体积后,在浏览器渲染加载资源的时间更少,就能够换取提升用户的使用体验。在前面提到要给用户完善丰富的警告信息,那么也就意味着编写更多的代码,这就违背了控制代码体积的初衷。

对此,Vue3框架在设计源码时,采用_DEV_进行判断是否为开发环境,从而让一些代码只在开发时进行调用,也会构建对应的开发资源。在Vue.js中采用的打包工具是rollup,_DEV_ 是使用插件进行预定义的,在进行资源输出时用于区分生产环境还是开发环境。

if(_DEV_ && !res){
warn(`Failed to mount app: mount target selector "${container}" returned null.`)
}

当在构建生产环境的资源时,_DEV_的值为false,这时候上面的这段代码将永远不会进行执行,这样就成为了dead code,在构建资源时就会被打包工具所移除(Tree-shaking)。

4、Tree-shaking

事实上,在我们实际项目开发中,有些Vue.js内置的组件压根没有用不上,那么在构建生产环境的资源时就不能让它出现,也就是Tree-shaking。Tree-shaking概念是由rollup推广普及的,指的是消除那些永远不会被执行的代码,即排除dead code。

实现Tree-shaking功能,前提是依赖于文件的模块必须是ESM(ES Module),即文件的静态结构是ESM。

在rollup中Tree-shaking是如何工作的呢?

demo文件结构:

|-- home
| |__ package.json
| |__ ping.js
| |__ chuan.js

安装rollup:

yarn add rollup -D

ping.js和chuan.js文件内容:

// ping.js
export function foo(obj){
obj && obj.foo
}
export function bar(obj){
obj && obj.bar
}
export function fun(obj){
obj && obj.fun
}

注意,我们在chuan.js文件中并没有导入ping.js中的bar函数。

import {foo, fun} from "./ping"
// 告知rollup这是个纯函数,不会产生任何副作用,可以放心tree-shaking
/*#__PURE__*/ foo()
fun()

使用命令进行构建,以chuan.js文件作为打包的入口,输出ESM的文件模块,打包后的输出文件是bundle.js。

npx roolup chuan.js -f esm -o bundle.js

在执行命令打包成功过后,我们可以输出的bundle.js文件中只包含fun函数的相关代码。而bar函数因为入口文件没有导入,不会打包进去,而foo函数前面加了/*#__PURE__*/代码,即告知rollup这是个纯函数,不会产生任何副作用,可以放心tree-shaking。这样,打包后的文件就只剩fun函数了。

//bundle.js
function fun(obj){
obj && obj.fun;
}
fun();

什么是tree-shaking的副作用呢?

经常提到的副作用其实就是:当调用函数时,会对外部产生影响,在rollup中如果函数调用时产生了副作用,就不能将其移除,因为会潜在的影响其他代码。

打包工具是怎么知道哪些代码可以放心移除呢?

rollup提供/*#__PURE__*/代码,可以告知rollup这是个纯函数,不会产生任何副作用,可以放心tree-shaking。上面代码片段中,在foo函数前添加/*#__PURE__*/,就可以做个标记告知rollup可以对其tree-shaking,打包后的bundle.js文件也对应将代码进行移除。其实/*#__PURE__*/可以用于任何代码,不单单是函数。

在Vue.js框架的源码中,我们可以发现大量的/*#__PURE__*/,但其实这并不会对使用者产生心智负担,因为通常产生副作用的代码都是模块内顶级调用的,而没有被顶级调用的代码是不会产生副作用的。

什么是顶级调用?

fun();//顶级调用
function fun(obj){
obj && obj.fun;
}
function foo(){
fun();//函数中调用
}

5、构建产物

Vue.js会不同的环境输出不同的包,vue.global.js用于开发环境,vue.global.prod.js用于生产环境,在构建产物时,会根据不同的需求输出不同的构建产物。

<body>
<script src="./vue.js"></script>
<script>
const {createApp} = Vue;
//...
</script>
</body>

首先用户直接可以在html页面中使用script引入框架使用,根据不同需求构建不同产物,需要输出一种IIFE(立即调用的函数表达式)格式的资源。

const Vue = (function(){
//...
exports.createApp = createApp;
//...
return exports
})()

这样,在使用<script>标签可以直接引入vue.global.js文件后,可以在html文件中进行全局使用了。在使用打包工具rollup时,我们需要配置format:"iife"来允许输出立即执行函数。

//rollup.config.js
const config = {
input:"input.js",
output:{
file:"output.js",
format:"iife"//指定模块形式
}
}
export default config

时至今日,主流浏览器不仅支持在<script>标签引用IIFE格式的资源,还能够直接引入ESM格式的资源,通过rollup配置format:"esm"即可输出vue.esm-browser.js文件。

<script type="module" src="./vue.esm-browser.js"></script>

_DEV_根据不同的值构建不同的产物,当_DEV_设置为true时,可用于生产环境从而被Tree-Shaking移除代码。但是,当我们构建提供给打包工具的ESM格式的资源时,不能直接设置_DEV_的值,要使用process.env.NODE_ENV1=="production"进行替换。在带有-bundler后缀的资源文件会变成:

if(process.env.NODE_ENV1=="production"){
warn(`useCssModule() is not supported in the global build.`)
}

6、特性开关

在设计框架时会为用户提供诸多特性功能,用户可以根据自己的需要开启或关闭对应的特性。

  • 对于用户关闭的特性,可以使用Tree-Shaking机制将代码从资源中清除。
  • 开关特性可以提升框架的灵活性,可以通过特性开关在框架任意添加新特性,在框架升级时可以开启使用上版本API的支持,从而减少代码打包资源的体积。

在Vue.js3框架设计中,为了减少框架升级对于之前版本的兼容性,为用户提供了__VUE_OPTIONS_API__开关来判断是否要开启对options API的兼容。

7、错误处理

错误处理是框架设计最重要的环节,可以决定用户应用程序的健壮性,降低开发者在处理报错时的心智负担。在utils.js的模块可以导出一个包含foo函数,接收一个回调函数作为参数,调用foo函数时会执行回调函数。

//utils.js
export default {
foo(fn){
fn && fn();
}
}

使用进行开发时,直接导入utils.js文件调用foo函数。

import utils from "utils.js";
utils.foo(()=>{
//...
})

如果用户提供了回调函数在执行时报错,那么就需要用户不断地使用try...catch...捕获和抛出错误,毫无疑问这会增加用户的心智负担。如果Vue.js框架已经在内部封装了许多try...catch...的错误处理函数callWithErrorHandling,那么用户在使用的时候代码就简洁很多,而且能够为用户提供统一处理的接口。

//utils.js
let handlerError = null;
export default {
foo(fn){
callWithErrorHandling(fn)
},
//用户可以调用该函数注册统一的错误处理函数
registerErrorHandling(fn){
handlError = fn
}
}
function registerErrorHandling(fn){
try{
fn && fn()
}catch(e){
//将捕获到的错误抛出给用户进行处理
handlerError(e)
}
}

此外,Vue.js提供了registerErrorHandler函数注册错误处理程序,callWithErrorHandling函数捕获错误后会抛出给用户注册的错误处理程序。

import utils from "utils.js";
//注册错误处理程序
utils.registerErrorHandler(e=>{
console.log(e)
})
utils.foo(()=>{//...})
utils.bar(()=>{//...})

此时用户的代码非常简洁健壮,错误的处理能力就完全由用户控制,也可以在Vue.js文件中注册统一的错误处理函数。

import App from "App.vue"
const app = createApp(App);
app.config.errHandler = ()=>{
//错误处理程序
}

8、写在最后

在本文中主要介绍了开发体验、tree-shaking以及错误处理等对框架设计的重要性,提供良好的警告信息可以有助于开发者快速定位问题,框架提供错误处理API能够提升用户应用程序的健壮性。tree-shaking可以用于打包文件的时候移除dead code,根据不同的环境构建不同的资源产物,从而减少代码打包的体积。此外还给用户提供了各种特性开关,__VUE_OPTIONS_API__可以用于设置Vue.js3对于option API的兼容性。

责任编辑:姜华 来源: 前端一码平川
相关推荐

2022-04-04 16:53:56

Vue.js设计框架

2022-04-01 08:08:27

Vue.js框架命令式

2022-04-25 07:36:21

组件数据函数

2022-04-12 08:08:57

watch函数options封装

2022-04-14 09:35:03

Vue.js设计Reflect

2022-04-18 08:09:44

渲染器DOM挂载Vue.js

2022-05-03 21:18:38

Vue.js组件KeepAlive

2019-05-21 21:15:32

架构架构设计性能优化

2022-04-26 05:55:06

Vue.js异步组件

2022-04-11 08:03:30

Vue.jscomputed计算属性

2022-04-05 16:44:59

系统Vue.js响应式

2022-04-09 17:53:56

Vue.js分支切换嵌套的effect

2022-04-20 09:07:04

Vue.js的事件处理

2022-04-19 23:01:54

Vue.jsDOM节点DOM树

2022-04-17 09:18:11

响应式数据Vue.js

2022-04-16 13:59:34

Vue.jsJavascript

2012-01-10 10:04:43

Node.js

2012-06-25 12:43:26

.NET框架

2019-01-23 08:59:00

大数据大数据治理数据管理

2012-01-18 10:20:42

框架设计
点赞
收藏

51CTO技术栈公众号