溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何將Vue組件庫更換為按需加載

發(fā)布時間:2020-07-28 15:38:23 來源:億速云 閱讀:131 作者:小豬 欄目:web開發(fā)

小編這次要給大家分享的是如何將Vue組件庫更換為按需加載,文章內容豐富,感興趣的小伙伴可以來了解一下,希望大家閱讀完這篇文章之后能夠有所收獲。

按需加載DEMO倉庫地址

背景

我司前端團隊擁有一套支撐公司業(yè)務系統(tǒng)的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));
  
  // 此處繼續(xù)完成一些服務的掛載
}

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

export default {
  install,
  ...components
}

組件庫并不導出編譯完成后的依賴文件,業(yè)務系統(tǒng)使用時,安裝依賴并導入,就能注冊組件。

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

組件庫的編譯是交由業(yè)務系統(tǒng)的編譯服務順帶編譯的。

即組件庫項目本身不會編譯,僅作為組件導出。node_module 就像一個免費的云盤,用于存儲組件庫代碼。

因為經業(yè)務系統(tǒng)編譯,在業(yè)務系統(tǒng)中。組件庫代碼能夠和本地文件一樣,直接調試。而且非常簡單粗暴,并不需要做一些依賴導出的額外配置。

但也存在缺點

  • 組件庫中無法使用更為特殊的代碼

vue-cli會靜態(tài)編譯在 node_module 引用的 .vue 文件,但不會編譯 node_module 中的其他文件,一旦組件庫代碼存在特殊的語法擴展(JSX),或者特殊的語言(TypeScript)。此時項目啟動會運行失敗。

  • 組件庫中使用 webpack 的特殊變量將不起效

組件庫中的 webpack 配置不會被業(yè)務系統(tǒng)去執(zhí)行,所以組件庫中的路徑別名等屬性無法使用

  • 組件庫依賴每次都是全量加載

index.js 本身就是全量的組件導入,所以即使業(yè)務系統(tǒng)只使用了部分組件, index.js 也會將所有的組件文件(圖片資源,依賴)都打包進去,依賴體積總是全量大小的。

業(yè)務系統(tǒng)并不存在只使用一兩個組件的情況,每個業(yè)務系統(tǒng)都需要絕大部分組件。
幾乎每個項目都會使用比如 按鈕,輸入框,下拉選項,表格 等常見基礎組件。
只有部分組件僅在少數(shù)特殊業(yè)務線使用,例如 富文本編輯器,音樂播放器。

組件分類

為了解決上述問題,及完成按需引入的效果。提供兩種組件導出方式,全量導出,基礎導出。
將組件導出分為兩種類型?;A組件,按需引入組件。
按需引入組件的評定標準為:

  • 較少業(yè)務系統(tǒng)使用
  • 組件中包含體積較大或資源文件較多的第三方依賴
  • 未被其他組件內部引用

全量導出模式導出全部組件,基礎導出僅導出基礎組件。在需要使用按需引入組件時,需要自行引入對應組件。

調整為按需引入

參考 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 命令,執(zhí)行的便是以上這段 webpack 配置。

配置中,會尋找組件目錄的所有入口文件。對每個入口文件根據(jù)設置進行編譯輸出到指定路徑。

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 在按需引入時,如組件名稱為駝峰命名,路徑會轉換為橫杠分隔。

例如業(yè)務系統(tǒng)引入

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

業(yè)務系統(tǒng)使用時

全量導出默認導出全部組件

// 全量導出
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

業(yè)務系統(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`;
    }
   }
  ]
 ]
}

基礎導出

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è)務系統(tǒng)中調試組件庫代碼

如果仍然想調試組件庫代碼,在引入組件時,直接引入組件庫依賴內的 components 下的組件導出文件并覆蓋安裝。就能調試目標組件。

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

優(yōu)化效果

在組件庫較大的情況下,優(yōu)化效果非常明顯。在使用基礎組件時,體積小了一兆。而且還減少了很多組件內不必要的第三方依賴文件資源。

如何將Vue組件庫更換為按需加載

看完這篇關于如何將Vue組件庫更換為按需加載的文章,如果覺得文章內容寫得不錯的話,可以把它分享出去給更多人看到。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經查實,將立刻刪除涉嫌侵權內容。

AI