組件庫按需加載深入探討

為了降低首屏代碼大小,對于一些大的第三方庫或者團隊的基礎(chǔ)工具庫,需要按需導(dǎo)入模塊。如:

import Button from 'antd/lib/button';

但這在需要導(dǎo)入非常多的組件場景時,開發(fā)繁瑣,體驗不友好。在這些組件庫的官方文檔或者社區(qū)會推薦一些babel插件,幫助達(dá)到良好的開發(fā)體驗和性能優(yōu)化。

本文將詳細(xì)探究這些工具的原理。

antd等UI組件庫按需加載

在使用antd的老版本時,會推薦使用babel-plugin-import工具按需導(dǎo)入組件。工具可以做到如下的轉(zhuǎn)換:

import { Button } from 'antd';
ReactDOM.render(<Button>xxxx</Button>);

      ↓ ↓ ↓ ↓ ↓ ↓

var _button = require('antd/lib/button');
require('antd/lib/button/style');
ReactDOM.render(<_button>xxxx</_button>);

antd和element-ui都叫做按需加載,但我覺得叫做按需導(dǎo)入更加貼合意境。
eleemnt-ui配套按需導(dǎo)入工具為babel-plugin-component。

babel-plugin-import工具,會在編譯時會分析模塊導(dǎo)入語句。當(dāng)為需要導(dǎo)入的目標(biāo)庫組件時,會將原導(dǎo)入刪除,生成多個新的導(dǎo)入(如果你配置了style,會額外導(dǎo)入樣式)。由于重新生成的導(dǎo)入語句會導(dǎo)致模塊變量發(fā)生變化(如上文案例演示中Button會被轉(zhuǎn)換為_button),這導(dǎo)致插件程序還會分析當(dāng)前模塊的所有變量,對使用原變量的語句中,將變量名修復(fù)。

最新的antd已經(jīng)推薦使用webpack的tree shaking機制來按需加載。babel-plugin-import也基本沒什么更新。對于vue生態(tài)來說,很多組件庫為了支持全局注冊的方式,無法使用tree shaking。

雖然babel-plugin-import等工具支持一些配置定制,但還是存在下面缺點:

  1. 每個插件都是針對特定的組件庫,需要符合特定的目錄和文件維護(hù)規(guī)范。如babel-plugin-import導(dǎo)入的模塊需要支持目錄為:
|--component
|----index.js
|----*.js
|----style
|------index.js
|------*.css
  1. babel-plugin-import由于底層實現(xiàn)改變了導(dǎo)入的模塊變量,然后再全模塊枚舉語句類型中找到使用變量將其修復(fù),再某一些非常不常見的語句中,會出現(xiàn)沒有轉(zhuǎn)變模塊變量導(dǎo)致語法錯誤問題。

對任意庫支持按需導(dǎo)入

如果也想對項目中公共基礎(chǔ)模塊(公共組件,公共工具文件等)支持源碼中全量導(dǎo)入但實際按需加載的效果,除了可以fork babel-plugin-import等工具調(diào)整邏輯來實現(xiàn),還可以使用工具babel-plugin-transform-imports來支持。

babel-plugin-transform-imports是自己配置轉(zhuǎn)換格式,如可以達(dá)到下面效果:

import { Row, Grid as MyGrid } from 'react-bootstrap';
import { merge } from 'lodash';

      ↓ ↓ ↓ ↓ ↓ ↓
      
import Row from 'react-bootstrap/lib/Row';
import MyGrid from 'react-bootstrap/lib/Grid';
import merge from 'lodash/merge';

此時需要的配置為:

{
  "plugins": [
    ["transform-imports", {
      "react-bootstrap": {
        "transform": "react-bootstrap/lib/${member}",
        "preventFullImport": true
      },
    }]
  ]
}

transform是可以支持函數(shù)的,實現(xiàn)高級定制,擴展性非常高。

babel-plugin-transform-imports實現(xiàn)的方式是直接對導(dǎo)入語句進(jìn)行了修復(fù),比babel-plugin-import更加優(yōu)雅和適用性更廣,閱讀他的源碼也可以發(fā)現(xiàn)比babel-plugin-import簡潔的多。

babel-plugin-import底層是將原來導(dǎo)入刪除,然后生成新的目標(biāo)導(dǎo)入,導(dǎo)致了導(dǎo)入模塊的變量發(fā)生了變更。

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

可以fork babel-plugin-transform-imports擴展其邏輯,如這個庫babel-plugin-transform-module-imports。我參照他的實現(xiàn)原理,實現(xiàn)了一個工具babel-plugin-transform-import-module,可以支持更多的配置,基本可以使用它滿足所有的模塊在源碼中全量導(dǎo)入但實際按需導(dǎo)入的效果。

像lodash一樣根據(jù)調(diào)用按需導(dǎo)入

在使用lodash是,可以使用babel-plugin-lodash插件進(jìn)行導(dǎo)入優(yōu)化。能夠?qū)ⅲ?/p>

import _ from 'lodash'
import { add } from 'lodash/fp'
 
const addOne = add(1)
_.map([1, 2, 3], addOne)

在編譯時轉(zhuǎn)換為:

import _add from 'lodash/fp/add'
import _map from 'lodash/map'
 
const addOne = _add(1)
_map([1, 2, 3], addOne)

利用babel-plugin-lodash可以提升lodash使用時的開發(fā)體驗:代碼中是全量導(dǎo)入使用,無需關(guān)注其內(nèi)部結(jié)構(gòu),將按需導(dǎo)入對應(yīng)的工具函數(shù)模塊交給底層babel插件處理。使用者除了不需要操心是否導(dǎo)入額外的代碼外,還可以配合typescipt使用,擁有更好的代碼提示,從而降低認(rèn)知。

當(dāng)前這個插件只支持lodash庫使用。借鑒其理念,可以實現(xiàn):

import utils from 'utils@'
utils.downloadFile('path/to/file')

      ↓ ↓ ↓ ↓ ↓ ↓

import _downloadFile from 'utils@/downloadFile'
_downloadFile('path/to/file')

如果改造更加深入一點,可以對一些市面上流行庫的優(yōu)化,如antd庫的使用:

import * as Antd from 'antd';
ReactDOM.render(<Antd.Button>xxxx</Antd.Button>);

      ↓ ↓ ↓ ↓ ↓ ↓

import Button from 'antd/lib/button';
import('antd/lib/button');
ReactDOM.render(<Button>xxxx</Button>);

上面的效果在我實現(xiàn)的一個babel插件babel-plugin-module-call-import都可以支持。如果你需要完全像babel-plugin-lodash一樣根據(jù)文件目錄結(jié)構(gòu)自動轉(zhuǎn)換加載器,可以直接fork源碼上調(diào)整。

我的團隊里面多個項目基本使用這種方式。

在vue代碼中根據(jù)標(biāo)簽按需導(dǎo)入

在vue應(yīng)用注冊組件時,有兩種方式:全局注冊局部注冊。全局注冊開發(fā)體驗式最好的,但會導(dǎo)入UI組件庫的所有代碼,增加很多無用代碼從而首屏激增。如何做到像全局注冊組件那樣開發(fā)友好,又支持按需導(dǎo)入組件提高性能呢?

這里提供一個方案:在vue文件的模板在解析的時候,記錄模板文件使用的標(biāo)簽,然后在文件對應(yīng)的js代碼編譯的時候,根據(jù)標(biāo)簽自動導(dǎo)入UI組件,然后在組件配置對象的components屬性中注冊上去。

假如代碼:

<template>
  <div>
    <el-button>按鈕</el-button>
  </div>
</template>

<script>
  export default {
    created() {
    },
  };
</script>

在編譯時,將會轉(zhuǎn)換類似于:

<template>
  <div>
    <el-button>按鈕</el-button>
  </div>
</template>

<script>
  import ElButton from 'element-ui/lib/form-item';
  
  export default {
    components: {
      ElButton,
    },
    created() {
    },
  };
</script>

我開發(fā)了兩個工具實現(xiàn)持這個方案:

只支持與vue2版本,vue3可以參照方案自己實現(xiàn)下。

你有什么更好的想法,歡迎討論。

?著作權(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)容