解析 ui组件库的 按需引入 原理
背景
由于自己也写了 公司内部的组件库,然后希望实现 按需引入 的功能。达到缩小文件体积的目的。提升项目性能。
此文章作为一个整理,分享思考的过程和思路给大家
首先寻找实现案例
先看element-ui的按需引入
查看官网
- 依赖一个babel插件 babel-plugin-component
- 亲测不使用这个插件,按需引入无效(查看run build后的文件大小)
结论 - 看不出什么特别之处,核心可能跟 babel-plugin-component 插件有关系
在看lodash的按需引入
// 按需引入
import cloneDeep from 'lodash/cloneDeep'; // 注意看路径'lodash/cloneDeep'
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = cloneDeep(objects);
console.log(deep[0] === objects[0]); // false
// 非按需引入(全部引入)
import _ from 'lodash';
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]); // false
分2次打包,一次按需引入,一次全部引入。最终webpack打包后的结果:(确实是生效的)
翻看lodash源码,目录结构如下:
lodash
├── ...
├── cloneDeep.js
├── ...
合理猜想:按需引入的原理 和 按文件路径引入有关
- 在进一步猜想,lodash模块的导出应该是ES6 Module(查看源码,确实如此)
- 为什么一定要是ES6 Module。因为只有ES6 Module可以做tree shaking,因为ES6 Module是静态的,在编译时可以分析出依赖关系。(详细版 可以看我的另一篇https://juejin.cn/post/6959360326299025445)
验证猜想:按需引入的原理 是 按文件路径引入
正好拿element-ui尝试,我不引入他的babel插件(babel-plugin-component)
我用文件路径的引入的方式写
// 按需引入(文件路径的引入)
import Button from "element-ui/lib/button" // 把node_modules的源码翻出来,找到对应的目录结构
// 也可以写成 import Button from 'element-ui/packages/button'; 区别就是,上面的是打包过的。这个是没打包的文件,有更好的source map方便调试
import 'element-ui/lib/theme-chalk/button.css' // 样式文件也可以按需引入
Vue.component(Button.name, Button);
// 全局引入
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
分2次打包,一次按需引入,一次全部引入。打包后,对比: 确实生效了! js和css体积都减少了很多
- js:从809kb 减小到 101kb
- css:从236kb 减小到 11kb
最后猜想
element-ui 的 babel-plugin-component 插件的作用
element-ui官网的按需加载写法:
// main.js
import { Button } from 'element-ui';
Vue.component(Button.name, Button);
// 安装npm install babel-plugin-component -D
// .babelrc 修改为: (摘自官网)
{
"presets": [["es2015", { "modules": false }]], // 此处有坑,如果用了babel 7版本以上。 此次要写成 [["@babel/preset-env", { "modules": false }]],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
猜想:babel-plugin-component的作用是 把简单语法import { Button } from 'element-ui';
转成按文件路径引入import Button from 'element-ui/lib/button';
验证猜想:写一个loader,放在babel-loader的前面
{
test: /.js$/,
loader: './my-loader', // 写一个自己的loader,放在babel-loader的前面,可以得到babel解析之后的结果(因为loader的解析顺序是从下到上,从后到前的)
},
{
test: /.js$/,
loader: 'babel-loader',
include: /src/,
options: {
cacheDirectory: true
}
},
my-loader.js(和webpack.config.js 同目录下)
module.exports = function (source) {
console.log(source)
debugger
return source
}
可以得到 babel-plugin-component 转换后的 js代码如下:
// 转换前
import Vue from 'vue';
import App from './App.vue';
import { Button } from 'element-ui';
Vue.component(Button.name, Button);
new Vue({
el: '#app',
render: h => h(App)
});
// babel-plugin-component 转换后
import Vue from 'vue';
import App from './App.vue';
import _Button2 from "element-ui/lib/theme-chalk/button.css"; // 此处和猜想是一致
import "element-ui/lib/theme-chalk/base.css"; // 多增加了一个base配置,翻了一下源码,是一些icon的class和动画配置。个人觉得看需求,可以不引入
import _Button from "element-ui/lib/button"; // 此处和猜想是一致
Vue.component(_Button.name, _Button);
new Vue({
el: '#app',
render: function render(h) {
return h(App);
}
});
猜想是对的!
结论:
按需引入的原理:就是 按 资源的路径引入,前提是要用ES6 Module
结论是否适用所有组件库?是否适用于其他的第三方资源?
合理猜想,只要使用ES6 Module,并且把子模块都拆出来,应该是适用的
- 比如ui组件库(el-ui,iview),函数工具库(lodash),都是适用的
码字不易,点赞鼓励