將Vue組件庫更換為按需加載的方法步驟
本文介紹了將Vue組件庫更換為按需加載的方法步驟,分享給大家,具體如下:
背景
我司前端團(tuán)隊(duì)擁有一套支撐公司業(yè)務(wù)系統(tǒng)的UI組件庫,經(jīng)過多次迭代后,組件庫體積非常龐大。
組件庫依賴在npm上管理,組件庫以項(xiàng)目根目錄的 index.js 作為出口導(dǎo)出,文件中導(dǎo)入了項(xiàng)目中所有的組件,并提供組件安裝方法。
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));
// 此處繼續(xù)完成一些服務(wù)的掛載
}
if (typeof window !== 'undefined' && window.Vue) {
install(Vue, true);
}
export default {
install,
...components
}
組件庫并不導(dǎo)出編譯完成后的依賴文件,業(yè)務(wù)系統(tǒng)使用時,安裝依賴并導(dǎo)入,就能注冊組件。
import JRUI from 'jr-ui'; Vue.use(JRUI);
組件庫的編譯是交由業(yè)務(wù)系統(tǒng)的編譯服務(wù)順帶編譯的。
即組件庫項(xiàng)目本身不會編譯,僅作為組件導(dǎo)出。node_module 就像一個免費(fèi)的云盤,用于存儲組件庫代碼。
因?yàn)榻?jīng)業(yè)務(wù)系統(tǒng)編譯,在業(yè)務(wù)系統(tǒng)中。組件庫代碼能夠和本地文件一樣,直接調(diào)試。而且非常簡單粗暴,并不需要做一些依賴導(dǎo)出的額外配置。
但也存在缺點(diǎn)
- 組件庫中無法使用更為特殊的代碼
vue-cli會靜態(tài)編譯在 node_module 引用的 .vue 文件,但不會編譯 node_module 中的其他文件,一旦組件庫代碼存在特殊的語法擴(kuò)展(JSX),或者特殊的語言(TypeScript)。此時項(xiàng)目啟動會運(yùn)行失敗。
- 組件庫中使用 webpack 的特殊變量將不起效
組件庫中的 webpack 配置不會被業(yè)務(wù)系統(tǒng)去執(zhí)行,所以組件庫中的路徑別名等屬性無法使用
- 組件庫依賴每次都是全量加載
index.js 本身就是全量的組件導(dǎo)入,所以即使業(yè)務(wù)系統(tǒng)只使用了部分組件, index.js 也會將所有的組件文件(圖片資源,依賴)都打包進(jìn)去,依賴體積總是全量大小的。
業(yè)務(wù)系統(tǒng)并不存在只使用一兩個組件的情況,每個業(yè)務(wù)系統(tǒng)都需要絕大部分組件。
幾乎每個項(xiàng)目都會使用比如 按鈕,輸入框,下拉選項(xiàng),表格 等常見基礎(chǔ)組件。
只有部分組件僅在少數(shù)特殊業(yè)務(wù)線使用,例如 富文本編輯器,音樂播放器。
組件分類
為了解決上述問題,及完成按需引入的效果。提供兩種組件導(dǎo)出方式,全量導(dǎo)出,基礎(chǔ)導(dǎo)出。
將組件導(dǎo)出分為兩種類型?;A(chǔ)組件,按需引入組件。
按需引入組件的評定標(biāo)準(zhǔn)為:
- 較少業(yè)務(wù)系統(tǒng)使用
- 組件中包含體積較大或資源文件較多的第三方依賴
- 未被其他組件內(nèi)部引用
全量導(dǎo)出模式導(dǎo)出全部組件,基礎(chǔ)導(dǎo)出僅導(dǎo)出基礎(chǔ)組件。在需要使用按需引入組件時,需要自行引入對應(yīng)組件。
調(diào)整為按需引入
參考 element-ui 的導(dǎo)出方案,組件庫導(dǎo)出的組件依賴,要提供每個組件單獨(dú)打包的依賴文件。

全量導(dǎo)出 index.js 文件無需改動,在 index.js 同級目錄增加新文件 base.js,用于導(dǎo)出基礎(chǔ)組件。
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;
// 獲取基于當(dāng)前路徑的目標(biāo)文件
const resolve = (dir) => path.join(__dirname, '../', dir);
/**
* @desc 大寫轉(zhuǎn)橫杠
* @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',
// 構(gòu)建依賴類型
libraryTarget: 'umd',
// 庫中被導(dǎo)出的項(xiàng)
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 命令,執(zhí)行的便是以上這段 webpack 配置。
配置中,會尋找組件目錄的所有入口文件。對每個入口文件根據(jù)設(shè)置進(jìn)行編譯輸出到指定路徑。
configureWebpack: {
// 入口文件
entry: getComponentEntries('src/components'),
// 輸出配置
output: {
// 文件名稱
filename: '[name]/index.js',
// 輸出依賴類型
libraryTarget: 'umd',
// 庫中被導(dǎo)出的項(xiàng)
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;
}
項(xiàng)目中的組件目錄為如下,配置將會將每個組件打包編譯導(dǎo)出到 lib 中
components 組件文件目錄 │ │— button │ │— button.vue button組件 │ └─ index.js button組件導(dǎo)出文件 │ │— input │ │— input.vue input組件 │ └─ index.js input組件導(dǎo)出文件 │ │— musicPlayer │ │— musicPlayer.vue musicPlayer組件 │ └─ index.js musicPlayer組件導(dǎo)出文件 │ │ base.js 基礎(chǔ)組件的導(dǎo)出文件 └─ index.js 所有組件的導(dǎo)出文件 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 基礎(chǔ)組件依賴樣式 │ └─ index.js 基礎(chǔ)組件依賴文件 │ └─ index │— style.css 所有組件依賴樣式 └─ index.js 所有組件依賴文件
獲取組件全部入口時,對入口名稱做駝峰轉(zhuǎn)橫杠處理 upperCasetoLine,是因?yàn)?babel-plugin-import 在按需引入時,如組件名稱為駝峰命名,路徑會轉(zhuǎn)換為橫杠分隔。
例如業(yè)務(wù)系統(tǒng)引入
import { MusicPlayer } from "jr-ui"
// 轉(zhuǎn)化為
var MusicPlayer = require('jr-ui/lib/music-player');
require('jr-ui/lib/music-player/style.css');
因?yàn)榻M件庫命名約定,組件文件夾命名大小寫并不以橫杠隔開。但為了讓 babel-plugin-import 正確運(yùn)行,所以此處對每個文件的入口文件名稱做了轉(zhuǎn)換處理。
如不經(jīng)過方法轉(zhuǎn)換名稱,也可以配置 babel.config.js 中的plugin-import配置 camel2DashComponentName 為 false,來禁用名稱轉(zhuǎn)換。
業(yè)務(wù)系統(tǒng)使用時
全量導(dǎo)出默認(rèn)導(dǎo)出全部組件
// 全量導(dǎo)出 import JRUI from "jr-ui"; import "jr-ui/lib/index/index.css"; Vue.use(JRUI);
基礎(chǔ)導(dǎo)出僅導(dǎo)出基礎(chǔ)組件,如需要使用額外組件,需要安裝 babel-plugin-import 插件且配置 babel.config.js 來完成導(dǎo)入語句的轉(zhuǎn)換
npm i babel-plugin-import -D
業(yè)務(wù)系統(tǒng)——babel.config.js配置
module.exports = {
presets: ["@vue/app", ["@babel/preset-env", { "modules": false }]],
plugins: [
[
"import",
{
"libraryName": "jr-ui",
"style": (name) => {
return `${name}/style.css`;
}
}
]
]
}
基礎(chǔ)導(dǎo)出
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);
業(yè)務(wù)系統(tǒng)中調(diào)試組件庫代碼
如果仍然想調(diào)試組件庫代碼,在引入組件時,直接引入組件庫依賴內(nèi)的 components 下的組件導(dǎo)出文件并覆蓋安裝。就能調(diào)試目標(biāo)組件。
import button from "jr-ui/src/components/button"; Vue.use(button);
優(yōu)化效果
在組件庫較大的情況下,優(yōu)化效果非常明顯。在使用基礎(chǔ)組件時,體積小了一兆。而且還減少了很多組件內(nèi)不必要的第三方依賴文件資源。

案例倉庫地址,如有疑問和錯誤的地方,歡迎大家提問或指出。
祝你有個快樂的勞動節(jié)假期 :)
Have a nice day.
參考資料
vue-cli執(zhí)行解析
babel-plugin-import
到此這篇關(guān)于將Vue組件庫更換為按需加載的方法步驟的文章就介紹到這了,更多相關(guān)Vue組件庫更換為按需加載內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在Vue3中實(shí)現(xiàn)子組件向父組件傳遞數(shù)據(jù)的代碼示例
Vue3作為目前最熱門的前端框架之一,以其輕量化、易用性及性能優(yōu)勢吸引了大量開發(fā)者,在開發(fā)過程中,不可避免地需要在組件之間傳遞數(shù)據(jù),本文將詳細(xì)講解在Vue3中如何實(shí)現(xiàn)子組件向父組件傳遞數(shù)據(jù),并通過具體示例代碼使概念更加清晰2024-07-07
vue組件中傳值EventBus的使用及注意事項(xiàng)說明
這篇文章主要介紹了vue組件中傳值EventBus的使用及注意事項(xiàng)說明,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11
Vue-Router如何動態(tài)更改當(dāng)前頁url query
這篇文章主要介紹了Vue-Router如何動態(tài)更改當(dāng)前頁url query問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08
vue3實(shí)現(xiàn)動態(tài)側(cè)邊菜單欄的幾種方式簡單總結(jié)
在做開發(fā)中都會遇到的需求,每個用戶的權(quán)限是不一樣的,那他可以訪問的頁面(路由)可以操作的菜單選項(xiàng)是不一樣的,如果由后端控制,我們前端需要去實(shí)現(xiàn)動態(tài)路由,動態(tài)渲染側(cè)邊菜單欄,這篇文章主要給大家介紹了關(guān)于vue3實(shí)現(xiàn)動態(tài)側(cè)邊菜單欄的幾種方式,需要的朋友可以參考下2024-02-02
unplugin-auto-import與unplugin-vue-components安裝問題解析
這篇文章主要為大家介紹了unplugin-auto-import與unplugin-vue-components問題解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
vue watch監(jiān)聽對象及對應(yīng)值的變化詳解
下面小編就為大家分享一篇vue watch監(jiān)聽對象及對應(yīng)值的變化詳解,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-02-02
Vue中@click.stop與@click.prevent解讀
Vue中,`@click.stop`用于阻止事件冒泡,而`@click.prevent`用于阻止事件的默認(rèn)行為,這兩個方法在處理事件時非常有用2025-02-02

