推理 ui组件库的 按需引入 原理

1,429 阅读3分钟

推理 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.png

翻看lodash源码,目录结构如下:

lodash
├── ...
├── cloneDeep.js
├── ...

合理猜想:按需引入的原理 和 按文件路径引入有关

  • 在进一步猜想,lodash模块的导出应该是ES6 Module(查看源码,确实如此)
    • 为什么一定要是ES6 Module。因为只有ES6 Module可以做tree shaking,因为ES6 Module是静态的,在编译时可以分析出依赖关系。(详细版 可以看我的另一篇juejin.cn/post/695936…

验证猜想:按需引入的原理 是 按文件路径引入

正好拿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 eleme.png

最后猜想: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),都是适用的

码字不易,点赞鼓励