Vue根據(jù)標(biāo)簽實現(xiàn)按需加載

實際開發(fā)中發(fā)現(xiàn)一個項目中會使用到多個組件庫,對于這些組件庫有一些使用的是按需加載,但是也有一些使用的是全局注冊。
對于一些大的第三方組件庫需要按需導(dǎo)入。

如:import ElButton from 'element-ui/lib/button;
但是這種按需導(dǎo)入在頁面使用組件非常多的場景時,開發(fā)繁瑣,體驗不友好。
當(dāng)然這些組件庫也會推薦一些babel插件來提升開發(fā)體驗和性能優(yōu)化。
babel-plugin-component、babel-plugin-import、babel-plugin-transform-imports等。
babel-plugin-transform-imports可以通過自己的配置來實現(xiàn)以下效果。

import { Row, Grid as MyGrid } from react-bootstrap'; 
import { merge } from 'lodash';
↓ ↓ ↓ ↓ ↓ ↓
import Row from 'reactbootstrap/lib/Row';
import MyGrid from 'react-bootstrap/lib/Grid';
import merge from 'lodash/merge';`

babel-plugin-transform-imports同樣有一些缺陷:它無法由一個導(dǎo)入生成多個導(dǎo)入。

這也就意味著使用babel-plugin-transform-imports無法對antd,element-ui這樣的UI組件庫進(jìn)行模塊按需導(dǎo)入:這些UI組件庫,除了js模塊的導(dǎo)入外,往往還有一個樣式模塊。

詳情請參考https://bitbucket.org/amctheatres/babel-transform-imports/src/master/

因此參考babel-plugin-transform-imports實現(xiàn)一個vue項目的組件按需導(dǎo)入。

1. 獲取頁面所有標(biāo)簽。

Vue中的每個頁面其實都是一個.vue的文件,這種文件,Vue稱之為組件頁面,其使用vue-loader來進(jìn)行解析。

當(dāng)解析某個vue頁面時通過node-html-parser來獲取到當(dāng)前頁面的所有標(biāo)簽。使用sync-disk-cache來將獲取到的標(biāo)簽保存在本地。

image.png

image.png

頁面源碼會被vue-loader解析成上圖所示,其中包含四種類型的代碼塊。

一、因此根據(jù)loaderresourceQuery屬性是否含有type=template來獲取template塊。

二、使用node-html-parsertemplate代碼塊進(jìn)行解析(深層遍歷,去重)

三、創(chuàng)建sync-disk-cache對象,暴露get、set、clear方法

2. 使用babel在代碼編譯時將目標(biāo)標(biāo)簽按照特定規(guī)則進(jìn)行引入、并將組件進(jìn)行注冊。

一、babel插件暴露lib(配置組件導(dǎo)入地址)、style(配置樣式導(dǎo)入地址)方法。

二、在visitor指定 Program節(jié)點,并根據(jù)路徑獲取sync-disk-cache保存的本地標(biāo)簽。

三、在export default語句中完成剩下的邏輯, 也就是表達(dá)式ExportDefaultDeclaration的訪問器中:

因為組件最終導(dǎo)入的形式為:

import Comp1 from 'path/to/comp1';
import Comp2 from 'path/to/comp2';

vue文件中,script里面一定會導(dǎo)出一個對象

export default {
   data() {
       return {};
   },
 };

此時要將Comp1、Comp2注冊則需要寫成以下這種形式。

export default {
   components: {
       Comp1: Comp1,
       Comp2: Comp2,
   },
   data() { 
       return {};
   },
};
   

理論來說需要這樣,但是這樣去改太復(fù)雜了,雖然有babel的幫助,至少要考慮:

  1. 當(dāng)前組件的配置對象里面是否有components這個選項,沒有還需要特殊的復(fù)雜處理
  2. 當(dāng)前組件的配置對象里面有components這個選項,低于注冊的那個組件,他是不是已經(jīng)注冊過了,如果是,還有可能語法報錯。
  3. 他是不是復(fù)雜的組件配置對象的寫法,比如
const config = {
   components: {},
   data: ...
   // ....
};
export default config;

還有可能是這樣的:

const compontns = { ... };
export default {
   components: compontns,
   data: ...
   // ....
};

要處理的情況太多了,從另一個角度想,它最終導(dǎo)出的一定是一個組件的配置對象,因此可以把它解出來。
比如 export default ....
不管他這個....是一個字面量還是一個變量都轉(zhuǎn)換成

const _thisCpnponentConfig = ...
export _thisCpnponentConfig;

在這種情況下如果要給他注冊組件只需擴展這個_thisCpnponentConfig里面的components選項就好了。
因此上面的代碼可以寫成

const _thisCpnponentConfig = ...;
// 先初始化一下,避免他開始沒有
thisCpnponentConfig.components = thisCpnponentConfig.components || {};
thisCpnponentConfig.components['Comp1'] = Comp1;
export _thisCpnponentConfig;

3. 如何使用

npm i vue-template-label-loader;
npm i babel-plugin-vue-auto-import;

3.1.1. 在webpack配置中配置該loader:

const { clear } = require('vue-template-label-loader/lib/store');
// 在每次構(gòu)建時, 都清空上一次存儲信息。
clear();
module.exports = {
   module: {
       rules: [{
           test: /\.vue$/,
           use: [{
               loader: 'vue-template-label-loader',
               options: {}
               exclude: {}
           },{
               loader: 'vue-loader',
               options: {
               //...
               },
           }]
       },
   ]}
}; 

3.1.2. 在vue.config.js中

const { clear } = require('vue-template-label-loader/lib/store');
// 在每次構(gòu)建時, 都清空上一次存儲信息。
clear();
module.exports = {
   chainWebpack: (config) => {
       config.module
           .rule('vue')
           .set('exclude', [/node_modules/])
           .use('vue-template-label-loader')
           .loader('vue-template-label-loader')
           .end()
       },
};

3.2. 配置babelrc.js

該工具需要配合 vue-template-label-loader 使用

function isCapitalStart(string) {
  if (!string) {
    return false
  }
  const first = string[0];
  const reg = new RegExp(/[A-Z]/);
  return reg.test(first);
}


function toLine(string) {
  return string.replace(/([A-Z][a-z]*)([A-Z][a-z]*)/g,"$1-$2").toLowerCase();
}

function kebabCase(str) {
  var hyphenateRE = /([^-])([A-Z])/g;
  return str
    .replace(hyphenateRE, '$1-$2')
    .replace(hyphenateRE, '$1-$2')
    .toLowerCase();
}

module.exports = {
  presets: ['@vue/cli-plugin-babel/preset'],
  plugins: [
    [
      'babel-plugin-vue-auto-import',
      {
        // excludeTags: ['List', 'HelloWorld'],
        lib(tag) {
          // 如果某個標(biāo)簽需要自動導(dǎo)入,請返回導(dǎo)入路徑, 不需要則返回null
          if (tag.startsWith('el-')) {
            return `element-ui/lib/${tag.replace('el-', '')}`;
          }
          if (tag.startsWith('a-')) {
            return `ant-design-vue/lib/${tag.replace('a-', '')}`;
          }
          if(isCapitalStart(tag) || tag.startsWith('i-')) {
            const tagName = tag.startsWith('i-') ? tag.replace('i-', '') : toLine(tag)
            return `view-design/src/components/${toLine(tagName)}/index.js`
          }
          return null
        },
        style(tag) {
          // 如果某個標(biāo)簽需要自動樣式文件,請返回導(dǎo)入路徑,無則返回null
          if (tag.startsWith('el-')) {
            const label = tag.replace('el-', '');
            return `element-ui/lib/theme-chalk/${label}.css`;
          }
          if (tag.startsWith('a-')) {
            const tagName = tag.replace('a-', '');
            return `ant-design-vue/lib/${tagName}/style`;
          }
          return null;
        },
      },
    ],
  ],
};

4. 注意事項

因為element-ui部分組件有使用到icon但是組件不會去自動導(dǎo)入icon,因此icon需要手動進(jìn)行全局導(dǎo)入。

import Vue from 'vue'
import { Icon } from 'element-ui'
import 'element-ui/lib/theme-chalk/icon.css';
Vue.component(Icon.name, Icon)

ant-desgin-vuemodel組件使用時會報錯,具體原因是,按需引入的常用寫法中沒有調(diào)用到Vue.use所執(zhí)行的自定義指令。解決方案如下

// main.js
import { Modal }from 'ant-design-vue';
Modal.install(Vue)` 

view-desgin 根據(jù)官方文檔,需要在main.js將樣式全部導(dǎo)入

import 'view-design/dist/styles/iview.css';

具體使用可以參考https://github.com/dexterBo/vue-auto-tag

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容