将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 相关文章推荐
jQuery中文入门指南,翻译加实例,jQuery的起点教程
Jan 13 Javascript
JavaScript 编程引入命名空间的方法
Jun 29 Javascript
DIV外区域Click后关闭DIV的实现代码
Dec 21 Javascript
JS加jquery简单实现标签元素的显示或隐藏
Sep 23 Javascript
JS window对象的top、parent、opener含义介绍
Dec 03 Javascript
中止javascript执行的方法
Feb 14 Javascript
JS不能跨域借助jquery获取IP地址的方法
Aug 20 Javascript
JQuery调用绑定click事件的3种写法
Mar 28 Javascript
angular实现IM聊天图片发送实例
May 08 Javascript
javascript中undefined的本质解析
Jul 31 Javascript
element-ui 文件上传修改文件名的方法示例
Nov 05 Javascript
基于vue的tab-list类目切换商品列表组件的示例代码
Feb 14 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
SONY SRF-40W电路分析
2021/03/02 无线电
如何把php5.3版本升级到php5.4或者php5.5
2015/07/31 PHP
js form action动态修改方法
2008/11/04 Javascript
javascript 全等号运算符使用说明
2010/05/31 Javascript
纯JS实现根据CSS的class选择DOM
2014/03/22 Javascript
JS获得浏览器版本和操作系统版本的例子
2014/05/13 Javascript
Javascript中3种实现继承的方法和代码实例
2014/08/12 Javascript
详解JavaScript中|单竖杠运算符的使用方法
2016/05/23 Javascript
JS实现的多张图片轮流播放幻灯片效果
2016/07/22 Javascript
js replace()去除代码中空格的实例
2017/02/14 Javascript
JS设计模式之观察者模式实现实时改变页面中金额数的方法
2018/02/05 Javascript
JS实现的base64加密解密操作示例
2018/04/18 Javascript
微信小程序功能之全屏滚动效果的实现代码
2018/11/22 Javascript
iphone刘海屏页面适配方法
2019/05/07 Javascript
layerui代码控制tab选项卡,添加,关闭的实例
2019/09/04 Javascript
Vue中通过属性绑定为元素绑定style行内样式的实例代码
2020/04/30 Javascript
vue实现简单全选和反选功能
2020/09/15 Javascript
解决vue 使用axios.all()方法发起多个请求控制台报错的问题
2020/11/09 Javascript
python安装以及IDE的配置教程
2015/04/29 Python
django 删除数据库表后重新同步的方法
2018/05/27 Python
Python通过for循环理解迭代器和生成器实例详解
2019/02/16 Python
Python 爬取必应壁纸的实例讲解
2020/02/24 Python
Python爬虫爬取微信朋友圈
2020/08/06 Python
女士鞋子、包包和服装在线,第一款10美元:ShoeDazzle
2019/07/26 全球购物
求∏的近似值,直到最后一项的绝对值小于指定的数
2016/02/12 面试题
JAVA高级程序员面试题
2013/09/06 面试题
主持人演讲稿范文
2013/12/28 职场文书
小学生竞选班干部演讲稿(5篇)
2014/09/12 职场文书
反四风个人对照检查材料
2014/09/26 职场文书
2014年房产销售工作总结
2014/12/08 职场文书
2014年企业团支部工作总结
2014/12/10 职场文书
新闻稿怎么写
2015/07/18 职场文书
导游词之徐州-云龙山
2019/09/29 职场文书
Python爬虫实战之爬取京东商品数据并实实现数据可视化
2021/06/07 Python
Python 中面向接口编程
2022/05/20 Python
TaiShan 200服务器安装Ubuntu 18.04的图文教程
2022/06/28 Servers