babel-plugin-import
在之前如果想要做按需加载那肯定不会陌生这个插件 babel-plugin-import
或者是 babel-plugin-component
。他们出现在 element-ui
和 VantUI
按需加载章节
他们的原理都是在 babel
解析 JS
文件时候利用解析后 AST
树去匹配关键字
import { Button } from "element-ui";
使用插件后大概会变成这样
import Button from "element-ui/lib/button/index.js";
import "element-ui/lib/theme/button.css";
为了适应这样的结构,你会发现 element-ui
会吧每个组件都打包成一个单独的 .js
和 .css
。用的打包工具分别是 webpack
和 gulp
webpack
在打包的时候需要设置多入口打包
module.exports = {
entry: {
button: path.resolve(__dirname, "src/button/index.js"),
// ...其他组件
},
};
而处理css
的时候就更方便了不难发现大多数组件库的 css
都不会向我们平时写 SFC
一样直接写 .vue
文件中,而是直接写在单独的 scss
文件中。这样的好处是可以直接让 gulp
进行单文件编译。
这是以前 webpack
的通用解决方案,但是现在我们的工程逐步像 vite
靠拢且 vite
目标是现代浏览器,对于大多数语法已经无需进行降级,也就没有了 babel
生成 AST
这一核心工作,上面的插件自然而然就用不了了。
unplugin-vue-components
unplugin-vue-components
可以不需要手动引入组件,能够让开发者就像全局组件那样进行开发,但实际上又是按需引入,且不限制打包工具,不需要使用 babel
。
使用效果以 element-plus
为例
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
export default {
plugins: [
Components({
resolvers: [ElementPlusResolver()],
}),
],
};
这样就能自动引入 element-plus
的组件,甚至不需要手动 import
组件以及组件样式,使用起来就像全局组件一样,但这是按需自动引入,可以减少产物大小。
<template>
<el-input />
</template>
解析器
对于这个插件来说它不可能兼容各种库的写法然后做一个汇总。它是一个插件但也是一个核心,而解析各种库的工作则是交给插件的 resolver
用户可以自定义这个 resolver
解析器的规则
resolver
需要下面这个格式
function createElementPlusResolver() {
return {
type: "component" | "directive",
resolve: function (name) {
return {
from: "", // 真实引入的文件路径
sideEffects: "", // 如果有css的话则加载这里
};
},
};
}
当页面中使用 <el-input />
的时候就会将 ElInput
传入函数。这时候就需要 resolve
去解析这个参数返回具体的路径以及可能有的 css
文件。
实现原理
我们先来看看 Vue3
在生成 render
函数的时候遇到组件时的 编译结果
可以看到,全局组件编译后会用 resolveComponent
包裹,该函数在 Vue
官网有说明,就是按名称找到已注册的全局组件。
unplugin-vue-components
只需要正则匹配 _resolveComponent
,拿到入参 el-button
,然后根据将名称标准化为 ElButton
,然后交给解析器,解析器会返回如下对象:
{
from:'element-plus/es',
sideEffects:'element-plus/es/components/button/style/index'
}
上面对象转换成以下链接注入到文件头部
import { ElButton as __unplugin_components_0 } from "element-plus/es";
import "element-plus/es/components/button/style/index";
最后将const _component_el_button = _resolveComponent('a-button')
替换成 const _component_el_button = __unplugin_components_0
就这样完成按需引入
局限性
- 不能作用于
JSX
,因为插件只处理template
模板的代码。不过JSX
则需要配合unplugin-auto-import
(用于自动引入变量) - 在
Vite
中CSS 无法 tree shaking
,不过这个是Vite
的问题。但JS
仍然是可以tree shaking
,相对于全局注册组件,对构建产物体积的优化还是非常可观的