将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的IE和Firefox兼容性汇编
Jul 01 Javascript
jquery 圆形旋转图片滚动切换效果
Jan 19 Javascript
javascript动态添加样式(行内式/嵌入式/外链式等规则)
Jun 24 Javascript
jquery单选框radio绑定click事件实现方法
Jan 14 Javascript
Javascript中的匿名函数与封装介绍
Mar 15 Javascript
Js制作点击输入框时默认文字消失的效果
Sep 05 Javascript
Angular2 Service实现简单音乐播放器服务
Feb 24 Javascript
Bootstrap标签页(Tab)插件使用方法
Mar 21 Javascript
jQuery之动画ajax事件(实例讲解)
Jul 18 jQuery
Vue封装Swiper实现图片轮播效果
Feb 06 Javascript
优雅的在React项目中使用Redux的方法
Nov 10 Javascript
详解ng-alain动态表单SF表单项设置必填和正则校验
Jun 11 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随机显示图片的简单示例
2014/02/15 PHP
PHP数组编码gbk与utf8互相转换的两种方法
2016/09/01 PHP
Yii2.0中使用js异步删除示例
2017/03/10 PHP
PHP实现的字符串匹配算法示例【sunday算法】
2017/12/19 PHP
php微信支付之公众号支付功能
2018/05/30 PHP
Laravel框架Request、Response及Session操作示例
2019/05/06 PHP
laravel异步监控定时调度器实例详解
2019/06/21 PHP
PHP实现本地图片转base64格式并上传
2020/05/29 PHP
用CSS+JS实现的进度条效果效果
2007/06/05 Javascript
JavaScript 字符串处理函数使用小结
2010/12/02 Javascript
js获取视频时长代码
2014/04/10 Javascript
Nodejs全栈框架StrongLoop推荐
2014/11/09 NodeJs
AngularJS的表单使用详解
2015/06/17 Javascript
微信小程序 在Chrome浏览器上运行以及WebStorm的使用
2016/09/27 Javascript
详解React-Native解决键盘遮挡问题(Keyboard遮挡问题)
2017/07/13 Javascript
react-native中ListView组件点击跳转的方法示例
2017/09/30 Javascript
vue自定义tap指令及tap事件的实现
2018/09/18 Javascript
解决vue与node模版引擎的渲染标记{{}}(双花括号)冲突问题
2020/09/11 Javascript
简单的python后台管理程序
2017/04/13 Python
老生常谈Python进阶之装饰器
2017/05/11 Python
TensorFlow如何实现反向传播
2018/02/06 Python
Python中pandas模块DataFrame创建方法示例
2018/06/20 Python
Python提取转移文件夹内所有.jpg文件并查看每一帧的方法
2019/06/27 Python
python logging设置level失败的解决方法
2020/02/19 Python
python 多线程死锁问题的解决方案
2020/08/25 Python
CSS3 重置iphone浏览器按钮input,select等表单元素的默认样式
2014/10/11 HTML / CSS
利用HTML5实现使用按钮控制背景音乐开关
2015/09/21 HTML / CSS
ET Mall东森购物网:东森严选
2017/03/06 全球购物
类和结构的区别
2012/08/15 面试题
自主实习接收函
2014/01/13 职场文书
诚信贷款承诺书
2014/05/30 职场文书
五一促销活动总结
2014/07/01 职场文书
个人对照检查材料思想汇报
2014/09/26 职场文书
医院领导班子四风对照检查材料
2014/09/27 职场文书
教师先进事迹材料
2014/12/16 职场文书
“学党章、守党纪、讲党规”学习心得体会
2016/01/14 职场文书