将Vue组件库更换为按需加载的方法步骤


Posted in Javascript onMay 06, 2020

本文介绍了将Vue组件库更换为按需加载的方法步骤,分享给大家,具体如下:

按需加载DEMO仓库地址

背景

我司前端团队拥有一套支撑公司业务系统的UI组件库,经过多次迭代后,组件库体积非常庞大。

组件库依赖在npm上管理,组件库以项目根目录的 index.js 作为出口导出,文件中导入了项目中所有的组件,并提供组件安装方法。

index.js

import Button from "./button";
import Table from "./table";
import MusicPlayer from "./musicPlayer";

import utils from "../utils"
import * as directive from "../directive";
import * as filters from "../filters";

const components = {
  Button,
  Table,
  MusicPlayer
}

const install = (Vue) => {
  Object.keys(components).forEach(component => Vue.use(component));
  
  // 此处继续完成一些服务的挂载
}

if (typeof window !== 'undefined' && window.Vue) {
 install(Vue, true);
}

export default {
  install,
  ...components
}

组件库并不导出编译完成后的依赖文件,业务系统使用时,安装依赖并导入,就能注册组件。

import JRUI from 'jr-ui';
Vue.use(JRUI);

组件库的编译是交由业务系统的编译服务顺带编译的。

即组件库项目本身不会编译,仅作为组件导出。node_module 就像一个免费的云盘,用于存储组件库代码。

因为经业务系统编译,在业务系统中。组件库代码能够和本地文件一样,直接调试。而且非常简单粗暴,并不需要做一些依赖导出的额外配置。

但也存在缺点

  • 组件库中无法使用更为特殊的代码

vue-cli会静态编译在 node_module 引用的 .vue 文件,但不会编译 node_module 中的其他文件,一旦组件库代码存在特殊的语法扩展(JSX),或者特殊的语言(TypeScript)。此时项目启动会运行失败。

  • 组件库中使用 webpack 的特殊变量将不起效

组件库中的 webpack 配置不会被业务系统去执行,所以组件库中的路径别名等属性无法使用

  • 组件库依赖每次都是全量加载

index.js 本身就是全量的组件导入,所以即使业务系统只使用了部分组件, index.js 也会将所有的组件文件(图片资源,依赖)都打包进去,依赖体积总是全量大小的。

业务系统并不存在只使用一两个组件的情况,每个业务系统都需要绝大部分组件。
几乎每个项目都会使用比如 按钮,输入框,下拉选项,表格 等常见基础组件。
只有部分组件仅在少数特殊业务线使用,例如 富文本编辑器,音乐播放器。

组件分类

为了解决上述问题,及完成按需引入的效果。提供两种组件导出方式,全量导出,基础导出。
将组件导出分为两种类型。基础组件,按需引入组件。
按需引入组件的评定标准为:

  • 较少业务系统使用
  • 组件中包含体积较大或资源文件较多的第三方依赖
  • 未被其他组件内部引用

全量导出模式导出全部组件,基础导出仅导出基础组件。在需要使用按需引入组件时,需要自行引入对应组件。

调整为按需引入

参考 element-ui 的导出方案,组件库导出的组件依赖,要提供每个组件单独打包的依赖文件。

将Vue组件库更换为按需加载的方法步骤

全量导出 index.js 文件无需改动,在 index.js 同级目录增加新文件 base.js,用于导出基础组件。

base.js

import Button from "./Button";
import Table from "./table";

const components = {
  Button,
  Table
}

const install = (Vue) => {
  Object.keys(components).forEach(component => Vue.use(component));
}

export default {
  install,
  ...components
}

修改组件库脚手架工具,增加额外打包配置。用于编译组件文件,输出编译后的依赖。

vue.config.js

const devConfig = require('./build/config.dev');
const buildConfig = require('./build/config.build');

module.exports = process.env.NODE_ENV === 'development' ? devConfig : buildConfig;

config.build.js

const fs = require('fs');
const path = require('path');
const join = path.join;
// 获取基于当前路径的目标文件
const resolve = (dir) => path.join(__dirname, '../', dir);

/**
 * @desc 大写转横杠
 * @param {*} str
 */
function upperCasetoLine(str) {
 let temp = str.replace(/[A-Z]/g, function (match) {
  return "-" + match.toLowerCase();
 });
 if (temp.slice(0, 1) === '-') {
  temp = temp.slice(1);
 }
 return temp;
}

/**
* @desc 获取组件入口
* @param {String} path
*/
function getComponentEntries(path) {
  let files = fs.readdirSync(resolve(path));

  const componentEntries = files.reduce((fileObj, item) => {
   // 文件路径
   const itemPath = join(path, item);
   // 在文件夹中
   const isDir = fs.statSync(itemPath).isDirectory();
   const [name, suffix] = item.split('.');
  
   // 文件中的入口文件
   if (isDir) {
    fileObj[upperCasetoLine(item)] = resolve(join(itemPath, 'index.js'))
   }
   // 文件夹外的入口文件
   else if (suffix === "js") {
    fileObj[name] = resolve(`${itemPath}`);
   }
   return fileObj
  }, {});
  
  return componentEntries;
}

const buildConfig = {
 // 输出文件目录
 outputDir: resolve('lib'),
 // webpack配置
 configureWebpack: {
  // 入口文件
  entry: getComponentEntries('src/components'),
  // 输出配置
  output: {
   // 文件名称
   filename: '[name]/index.js',
   // 构建依赖类型
   libraryTarget: 'umd',
   // 库中被导出的项
   libraryExport: 'default',
   // 引用时的依赖名
   library: 'jr-ui',
  }
 },
 css: {
  sourceMap: true,
  extract: {
   filename: '[name]/style.css'
  }
 },
 chainWebpack: config => {
  config.resolve.alias
   .set("@", resolve("src"))
   .set("@assets", resolve("src/assets"))
   .set("@images", resolve("src/assets/images"))
   .set("@themes", resolve("src/themes"))
   .set("@views", resolve("src/views"))
   .set("@utils", resolve("src/utils"))
   .set("@mixins", resolve("src/mixins"))
   .set("jr-ui", resolve("src/components/index.js"));
 }
}

module.exports = buildConfig;

此时我们的 npm run build 命令,执行的便是以上这段 webpack 配置。

配置中,会寻找组件目录的所有入口文件。对每个入口文件根据设置进行编译输出到指定路径。

configureWebpack: {
  // 入口文件
  entry: getComponentEntries('src/components'),
  // 输出配置
  output: {
   // 文件名称
   filename: '[name]/index.js',
   // 输出依赖类型
   libraryTarget: 'umd',
   // 库中被导出的项
   libraryExport: 'default',
   // 引用时的依赖名
   library: 'jr-ui',
  }
},
css: {
  sourceMap: true,
  extract: {
   filename: '[name]/style.css'
  }
}
function getComponentEntries(path) {
  let files = fs.readdirSync(resolve(path));

  const componentEntries = files.reduce((fileObj, item) => {
   // 文件路径
   const itemPath = join(path, item);
   // 在文件夹中
   const isDir = fs.statSync(itemPath).isDirectory();
   const [name, suffix] = item.split('.');
  
   // 文件中的入口文件
   if (isDir) {
    fileObj[upperCasetoLine(item)] = resolve(join(itemPath, 'index.js'))
   }
   // 文件夹外的入口文件
   else if (suffix === "js") {
    fileObj[name] = resolve(`${itemPath}`);
   }
   return fileObj;
  }, {});
  
  return componentEntries;
}

项目中的组件目录为如下,配置将会将每个组件打包编译导出到 lib 中

components             组件文件目录
│             
│— button             
│  │— button.vue         button组件
│  └─ index.js          button组件导出文件
│
│— input             
│  │— input.vue          input组件
│  └─ index.js          input组件导出文件
│
│— musicPlayer
│  │— musicPlayer.vue       musicPlayer组件
│  └─ index.js          musicPlayer组件导出文件
│
│ base.js             基础组件的导出文件
└─ index.js            所有组件的导出文件

lib                编译后的文件目录
│             
│— button             
│  │— style.css          button组件依赖样式
│  └─ index.js          button组件依赖文件
│
│— input             
│  │— style.css          input组件依赖样式
│  └─ index.js          input组件依赖文件
│
│— music-player
│  │— style.css          musicPlayer组件依赖样式
│  └─ index.js          musicPlayer组件依赖文件
│
│— base             
│  │— style.css          基础组件依赖样式
│  └─ index.js          基础组件依赖文件
│
└─ index             
  │— style.css          所有组件依赖样式
  └─ index.js          所有组件依赖文件

获取组件全部入口时,对入口名称做驼峰转横杠处理 upperCasetoLine,是因为 babel-plugin-import 在按需引入时,如组件名称为驼峰命名,路径会转换为横杠分隔。

例如业务系统引入

import { MusicPlayer } from "jr-ui"

// 转化为
var MusicPlayer = require('jr-ui/lib/music-player');
require('jr-ui/lib/music-player/style.css');

因为组件库命名约定,组件文件夹命名大小写并不以横杠隔开。但为了让 babel-plugin-import 正确运行,所以此处对每个文件的入口文件名称做了转换处理。

如不经过方法转换名称,也可以配置 babel.config.js 中的plugin-import配置 camel2DashComponentName 为 false,来禁用名称转换。

babel-plugin-import路径命名issue

业务系统使用时

全量导出默认导出全部组件

// 全量导出
import JRUI from "jr-ui";
import "jr-ui/lib/index/index.css";

Vue.use(JRUI);

基础导出仅导出基础组件,如需要使用额外组件,需要安装 babel-plugin-import 插件且配置 babel.config.js 来完成导入语句的转换

npm i babel-plugin-import -D

业务系统——babel.config.js配置

module.exports = {
 presets: ["@vue/app", ["@babel/preset-env", { "modules": false }]],
 plugins: [
  [
   "import",
   {
    "libraryName": "jr-ui",
    "style": (name) => {
      return `${name}/style.css`;
    }
   }
  ]
 ]
}

基础导出

import JRUI_base from "jr-ui/lib/base";
import "jr-ui/lib/base/index.css";
Vue.use(JRUI_base);

// 按需使用额外引入的组件
import { MusicPlayer } from "jr-ui";
Vue.use(MusicPlayer);

业务系统中调试组件库代码

如果仍然想调试组件库代码,在引入组件时,直接引入组件库依赖内的 components 下的组件导出文件并覆盖安装。就能调试目标组件。

import button from "jr-ui/src/components/button";
Vue.use(button);

优化效果

在组件库较大的情况下,优化效果非常明显。在使用基础组件时,体积小了一兆。而且还减少了很多组件内不必要的第三方依赖文件资源。

将Vue组件库更换为按需加载的方法步骤

案例仓库地址,如有疑问和错误的地方,欢迎大家提问或指出。
祝你有个快乐的劳动节假期 :)
Have a nice day.

参考资料

vue-cli执行解析
babel-plugin-import

 到此这篇关于将Vue组件库更换为按需加载的方法步骤的文章就介绍到这了,更多相关Vue组件库更换为按需加载内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
JavaScript的单例模式 (singleton in Javascript)
Jun 11 Javascript
JS获取URL中的参数数据
Dec 05 Javascript
javascript数组快速打乱重排的方法
Jan 02 Javascript
基于jQuery实现下拉框
Nov 24 Javascript
JavaScript中的定时器之Item23的合理使用
Oct 30 Javascript
JS实现密码框的显示密码和隐藏密码功能示例
Dec 26 Javascript
JS实现非首屏图片延迟加载的示例
Jan 06 Javascript
微信小程序实现左侧滑动导航栏
Apr 08 Javascript
分享JS表单验证源码(带错误提示及密码等级)
Jan 05 Javascript
echarts实现获取datazoom的起始值(包括x轴和y轴)
Jul 20 Javascript
js异步接口并发数量控制的方法示例
Nov 22 Javascript
低门槛开发iOS、Android、小程序应用的前端框架详解
Oct 16 Javascript
让IDE识别webpack的别名alias的实现方法
May 06 #Javascript
JS 设计模式之:工厂模式定义与实现方法浅析
May 06 #Javascript
JS 设计模式之:单例模式定义与实现方法浅析
May 06 #Javascript
基于vue3.0.1beta搭建仿京东的电商H5项目
May 06 #Javascript
JavaScript布尔运算符原理使用解析
May 06 #Javascript
ES5 模拟 ES6 的 Symbol 实现私有成员功能示例
May 06 #Javascript
Vue 的双向绑定原理与用法揭秘
May 06 #Javascript
You might like
php daodb插入、更新与删除数据
2009/03/19 PHP
PHP遍历目录并返回统计目录大小
2014/06/09 PHP
PHP实现根据设备类型自动跳转相应页面的方法
2014/07/24 PHP
PHP实现多级分类生成树的方法示例
2017/02/07 PHP
菜鸟javascript基础资料整理3 正则
2010/12/06 Javascript
javascript实现div浮动在网页最顶上并带关闭按钮效果实例
2013/08/13 Javascript
吐槽一下我所了解的Node.js
2014/10/08 Javascript
node.js中的http.createServer方法使用说明
2014/12/14 Javascript
avalon js实现仿google plus图片多张拖动排序附源码下载
2015/09/24 Javascript
在JavaScript中如何解决用execCommand(
2015/10/19 Javascript
Javascript BOM学习小结(六)
2015/11/26 Javascript
JQuery日历插件My97DatePicker日期范围限制
2016/01/20 Javascript
javascript每日必学之运算符
2016/02/16 Javascript
微信jssdk在iframe页面失效问题的解决措施
2016/03/03 Javascript
js移动焦点到最后位置的简单方法
2016/11/25 Javascript
微信小程序之蓝牙的链接
2017/09/26 Javascript
微信小程序录音与播放录音功能
2017/12/25 Javascript
angular 数据绑定之[]和{{}}的区别
2018/09/25 Javascript
Vue生命周期activated之返回上一页不重新请求数据操作
2020/07/26 Javascript
[26:40]DOTA2上海特级锦标赛A组资格赛#1 Secret VS MVP.Phx第一局
2016/02/25 DOTA
在Python的Django框架中simple-todo工具的简单使用
2015/05/30 Python
Python使用urllib2模块实现断点续传下载的方法
2015/06/17 Python
python如何实现int函数的方法示例
2018/02/19 Python
Pycharm设置界面全黑的方法
2018/05/23 Python
利用Pandas读取文件路径或文件名称包含中文的csv文件方法
2018/07/04 Python
python中退出多层循环的方法
2018/11/27 Python
Python爬取数据保存为Json格式的代码示例
2019/04/09 Python
python实现控制COM口的示例
2019/07/03 Python
日本高岛屋百货购物网站:TAKASHIMAYA
2019/03/24 全球购物
创意爱尔兰礼物:Creative Irish Gifts
2020/01/29 全球购物
short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?
2014/09/26 面试题
超市重阳节活动方案
2014/02/10 职场文书
院领导写的就业推荐信
2014/03/09 职场文书
团队拓展活动方案
2014/08/28 职场文书
小学生优秀作文范文(六篇)
2019/07/10 职场文书
javascript中Set、Map、WeakSet、WeakMap区别
2022/12/24 Javascript