将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 相关文章推荐
My Desktop :) 桌面式代码
Dec 29 Javascript
Javascript将string类型转换int类型
Dec 09 Javascript
js图片延迟加载的实现方法及思路
Jul 22 Javascript
Javascript 实现图片无缝滚动
Dec 19 Javascript
jQuery实现“扫码阅读”功能
Jan 21 Javascript
AngularJS表单详解及示例代码
Aug 17 Javascript
很棒的js Tab选项卡切换效果
Aug 30 Javascript
微信小程序开发之相册选择和拍照详解及实例代码
Feb 22 Javascript
微信小程序自定义prompt组件步骤详解
Jun 12 Javascript
vue.js 添加 fastclick的支持方法
Aug 28 Javascript
微信小程序pinker组件使用实现自动相减日期
May 07 Javascript
three.js 制作动态二维码的示例代码
Jul 31 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
JavaScript中的new的使用方法与注意事项
2007/05/16 Javascript
jquery 防止表单重复提交代码
2010/01/21 Javascript
通过js简单实现将一个文本内容转译成加密文本
2013/10/22 Javascript
jquery ajax属性async(同步异步)示例
2013/11/05 Javascript
Javascript实现返回上一页面并刷新的小例子
2013/12/11 Javascript
javascript消除window.close()的提示窗口
2015/05/20 Javascript
JavaScript中函数表达式和函数声明及函数声明与函数表达式的不同
2015/11/15 Javascript
JavaScript 函数的执行过程
2016/05/09 Javascript
基于ES6 Array.of的用法(实例讲解)
2017/09/05 Javascript
详解处理bootstrap4不支持远程静态框问题
2018/07/20 Javascript
Vue源码之关于vm.$delete()/Vue.use()内部原理详解
2019/05/01 Javascript
微信小程序页面上下滚动效果
2020/11/18 Javascript
vue 使用鼠标滚动加载数据的例子
2019/10/31 Javascript
Vue 的双向绑定原理与用法揭秘
2020/05/06 Javascript
[03:15]2014DOTA2国际邀请赛 专访国士无双信心满满
2014/07/12 DOTA
[01:29:46]DOTA2上海特级锦标赛C组资格赛#1 OG VS LGD第二局
2016/02/27 DOTA
python数据结构之二叉树的建立实例
2014/04/29 Python
基python实现多线程网页爬虫
2015/09/06 Python
Tensorflow之Saver的用法详解
2018/04/23 Python
浅谈django rest jwt vue 跨域问题
2018/10/26 Python
python判断文件是否存在,不存在就创建一个的实例
2019/02/18 Python
使用Python检测文章抄袭及去重算法原理解析
2019/06/14 Python
python求最大公约数和最小公倍数的简单方法
2020/02/13 Python
基于python的docx模块处理word和WPS的docx格式文件方式
2020/02/13 Python
HTML5 Canvas基本线条绘制的实例教程
2016/03/17 HTML / CSS
美国最大的珠宝商之一:Littman Jewelers
2016/11/13 全球购物
SheIn沙特阿拉伯:女装在线
2020/03/23 全球购物
最新大学职业规划书范文
2013/12/30 职场文书
学生实习介绍信
2014/01/15 职场文书
详细的大学生创业计划书模板
2014/01/27 职场文书
法制宣传教育方案
2014/05/09 职场文书
党在我心中的演讲稿
2014/09/13 职场文书
2014年四风个人对照检查及整改措施
2014/10/28 职场文书
写给父母的感谢信
2015/01/22 职场文书
2016校本研修培训心得体会
2016/01/08 职场文书
python 如何用map()函数创建多线程任务
2021/04/07 Python