2.1 提升用户的开发体验
框架设计和开发过程中,提供有好的警告信息至关重要。
createApp(App).mount('#not-exist')
当我们创建一个Vue应用并试图将其挂载到一个不存在的DOM节点时,就会收到一条警告信息
【vue warn】: failed to mount target selector '#not-exist' return null
2.2 控制框架代码的体积
如果我们去看Vue.js的源码,就会发现每一个warn函数的调用都会配合_DEV_常量的检查,例如:
if(_DEV_ && !res) {
warn(`Failed to mount app:mount target selcetor '${container}' return null.`)
}
可以看到,打印警告信息的前期是:_DEV_常量一定为true,这里的_DEV_常量就是达到目的的关键。
Vue.js使用rollup.js对项目进行构建,这里的_DEV_常量实际上是通过rollup.js的插件配置来预定义的,其功能类似于webpack中的DefinePlugin插件。
Vue.js在输出资源的时候,会输出两个版本,其中一个用于开发环境,如vue.global.js,另一个用于生产环境
,如vue.global.prod.js,通过文件名来区分。
2.3 框架要做到良好的Tree-Shaking
简单地说,Tree-Sharking指的就是消除那些永远不会被执行的代码。
想要实现Tree-Shaking,必须满足一个条件,即模块必须是ESM(ES Module),因为Tree-Shaking依赖ESM的静态结构,我们以rollup.js为例看看Tree-Shaking如何工作,其目录结构如下:
| ---- demo
| ---- -------- package.json
| ---- -------- input.js
| ---- -------- utils.js
首先安装rollup.js:
npm i rollup -D
下面是input.js和utils.js文件的内容:
// input.js
import { foo } from './utils.js'
foo()
// utils.js
export function foo(obj) {
obj && obj.foo
}
export funtion bar(obj) {
obj && obj.bar
}
代码很简单,我们在utils.js文件中定义并导出两个函数,分别是foo函数和bar函数,然后在input.js中导入了foo函数并执行。注意,我们并没有导入bar函数。
npx rollup input.js -f esm -o bundle.js
这命令的意思是,以input.js文件为入口,输出ESM,输出的文件叫做bundle.js。命令执行成功后,我们打开bundle.js来查看一下它的内容:
// bundle.js
function foo(obj) {
obj && obj.foo
}
foo()
可以看到,其中并不包含bar函数,这说明Tree-Shaking起了作用。由于我们没有是有bar函数,所以这段dead code被删除了。但是仔细发现,foo函数的执行也并没有产生什么意义,仅仅读取了对象的值,所以似乎也没什么作用,那么rollup.js并没有把他删除呢。这就涉及Tree-Shaking中第二个关键点---副作用。如果一个函数传递函数产生副作用了,那么就不能被删除。
因为静态地分析JavaScript代码很困难,所以rollup.js这类工具都会提供一个机制,让我们明确地告诉rollup.js,这段代码没问题,不会产生副作用,你可以移除它。下面修改input.js:
import { foo } from './utils'
/*#__PURE__*/ foo()
注释代码/#PURE/,其作用就是告诉rollup.js,对于foo函数的调用不会产生副作用,你可以放心地对其进行Tree-Shaking,此时再次执行构建命令查看bundle.js文件,就会发生他的内容是空的,这说明Tree-Shaking成功了
2.4 框架应该输出怎样的构建产物
<script src="/path/to/vue.js"></script>
<script>
const { createApp } = Vue
</script>
vue.global.js文件就是IIFE形式的资源
var Vue = (function () {
// ...
export.createApp = createApp
// ...
return exports
})({})
除了使用script标签引用IIFE格式的资源外,还可以直接引入ESM格式的资源,Vue3可以直接使用
为了输出ESM格式的资源,rollup.js的输出格式需要配置为:format: "esm"
// rollup.config.js
const config = {
input: 'input.js',
output: {
file: 'output.js',
format: 'iife'
}
}
export default config
2.5 特性开关
- 对于用户关闭的特性,我们可以利用Tree-Shaking机制让其不包括含在最终的资源中。
- 未框架增加新的特性而不担心资源体积变大。可以兼容遗留的API。
{
__FEATURE_OPTIONS_API__: isBunderESMBuild ? `_VUE_OPTIONS_API__` : true
}
在Vue.js3的源码中搜索:
if(__FEATURE_OPTIONS_API__) {
currentInstance = instance
pauseTracking()
applyOptions(instance, Component)
resetTracking()
currentInstance = null
}
// webpack.DefinePlugin
new webpack.DefinePlugin({
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
__VUE_PROD_DEVTOOLS__: false,
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
__VUE_PROD_DEVTOOLS__: false,
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
__VUE_PROD_DEVTOOLS__: false,
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
__VUE_PROD_DEVTOOLS__: false,
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
__VUE_PROD_DEVTOOLS__: false,
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
__VUE_PROD_DEVTOOLS__: false,
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
__VUE_PROD_DEVTOOLS__: false,
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
__VUE_PROD_DEVTOOLS__: false,
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
__VUE_PROD_DEVTOOLS__: false
})
2.6、错误处理
错误处理事开发框架过程中非常重要的环节。框架错误处理机制的好坏直接决定了用户应用程序的健壮性,还决定了用户开发时处理错误的心智负担。
为了大家更加直观地感受错误处理的重要性,我们从一个小例子说起。假设我们开发了一个工具模块,代码如下:
export default {
foo(fn) {
fn && fn()
}
}
该模块导出一个对象,其中foo属性是一个函数,接收一个回调函数作为参数,调用foo函数时会执行该回调函数,在用户侧使用时:
import utils from 'utils.js'
utils.foo(() => {
// ...
})
大家思考一下,如果用户提供的回调函数在执行的时候出错了,怎么办?此时两个办法,第一个办法是让用户自行处理,这需要用户自己执行try...catch:
import utils from 'utils.js'
utils.foo(() => {
try {
// ...
}catch(e) {
// ...
}
})
但是这会增加用户的负担。需要在多个类似的函数中加异常try...catch处理
第二个办法是我们代替用户统一处理错误,如下代码所示:
export default {
foo(fn) {
try {
fn && fn()
}catch(e){/**/}
},
bar(fn) {
try {
fn && fn()
}catch(e){/**/}
}
}
在每个函数内都增加try...catch代码块,实际上,我们可以进一步将错误处理程序封装为一个函数,假设叫它callWicthErrorHandling:
export default {
foo(fn) {
callWicthErrorHandling(fn)
},
bar(fn) {
callWicthErrorHandling(fn)
}
}
function callWicthErrorHandling(fn) {
try {
fn && fn()
}catch(e){
console.log(e)
}
}
提供统一的额错误处理接口:
let handleError = null
export default {
foo(fn) {
callWicthErrorHandling(fn)
},
registerErrorHandler(fn) {
handlerError = fn
}
}
function callWicthErrorHandling(fn) {
try {
fn && fn()
}catch(e) {
handlerError(e)
}
}
我们提供了registerErrorHandler函数,用户可以使用它注册错误处理程序,然后在callWicthErrorHandling函数内部捕获错误后,把错误传递给用户注册的错误程序。
import utils form 'utils.js'
utils.registerErrorHandler((e) => {
console.log(e)
})
utils.foo(() => {/*...*/})
utils.bar(() => {/*...*/})
在Vue.js错误处理的原理,你可以在源码中搜索到callWithErrorHandling函数。另外,在Vue.js中,我们也可以注册统一的错误处理函数:
import App from 'App.vue'
const app = createApp(App)
app.config.errorHandler = () => {
// 错误处理程序
}
2.7、良好的TypeScript类型支持
TypeScript是由微软开源的编程语言,简称TS,它是JavaScript的超集,能够为JavaScript提供类型支持。