antd圖標庫按需加載的插件實現


前景概要

antd是阿里出品的一款基於antd的UI組件庫,使用簡單,功能豐富,被廣泛應用在中台項目開發中,雖然也出現了彩蛋事故,但不能否認antd本身的優秀,而我們公司在實際工作中也大量使用antd進行開發,使用的版本主要集中在3.x這個大版本中,在實際使用過程中發現了一個比較明顯的問題,那就是antd的圖標輸出打包體積過大,即便是使用了一個圖標,也會將所有圖標打包輸出,沒有按需加載(4.x版本已經實現了按需加載,但目前公司還沒做整體的升級),在這種情況下,就亟需一款能按需加載的插件來減小圖標輸出的體積。

解決思路

定位了問題,接下來就是想辦法解決了,前期在網上搜索到一種解決辦法,那就是在webpack中的配置圖標文件路徑,具體如下:

resolve: {
  alias: {
    '@ant-design/icons/lib/dist$': './youIcon.js',
  }
}

youIcon.js中導出使用到的圖標,通過這種方式能極大減小靜態資源輸出的體積,但是這一過程是手動配置,維護和使用不是很方便,借助這種解決方式,加上動態生成本地youIcon.js文件就可以了,確定了解決思路,接下來就動手設計插件。

插件設計

1.運行流程

圖片描述

2.功能設計

如上圖所示,我們需要的插件需要滿足兩個功能,第一,動態提取項目源代碼和使用到的antd組件中的圖標,第二,修改webpack的alias配置,接下來將分別講述設計過程:

1.動態提取圖標

提取圖標,需要對目標文件進行代碼分析,提取圖標代碼的相關特征,然后整理輸出到本地的目標文件下,過程如下圖:
圖片描述
1.) 特征匹配
利用babel將源代碼編譯輸出,然后得到icon的生成代碼,結合astexplorer分析節點屬性,確定出匹配方案,這里還需要注意一點的是,有些組件在生成圖標的過程中比較特殊,比如Button可以通過loading和icon屬性設置,在匹配的時候需要特殊處理;
2.) 屬性提取
在提取圖標的過程中,得到了圖標的屬性后,需要和本地node_modules里面的@ant-design/icon/lib/dist的所有官方導出庫匹配,找到目標圖標,就能確定圖標的有效性;
3.) 字符串拼接寫入
將圖標名稱和尋址路徑拼接起來,再、在寫入antd-icon-reduce.js文件之前,判斷是否已經存在相同的圖標名稱,如果存在則放棄寫入,經過上述步驟就可以將項目中用到的所有圖標全部收集到antd-icon-reduce.js文件中了。

2.動態修改配置

動態修改配置依賴於antd-icon-reduce-plugin插件來實現,其中的工作原理如下圖:
圖片描述
1.) 初始化
在插件初始化的時候,插件主要干了兩件事,第一,生成antd-icon-reduce.js文件,然后將這個文件路徑傳遞給antd-icon-reduce-loader,第二,添加專門匹配node_modules/antd目錄下的所有js文件,確保項目中使用到的組件都能被loader處理,經過初始化之后,項目就有了存放導出的圖標文件,更重要的是適配了所有可能生成圖標的源文件;
2.) 配置文件
當loader運行完成之后,我們就得到了一份完整的圖標導出文件(antd-icon-reduce.js),這個時候就需要修改webpack的alias了,這里需要在每次loader運行完成后都重新生成一份文件(內容拷貝至antd-icon-reduce.js文件),文件名需要動態更新,確保每次webpack內存加載的配置文件都是最新的;
3.) 文件刪除
在編譯輸出完成后,需要清除antd-icon-reduce.js和另一份配置文件,這里都是在webpack相應的hooks里面實現,具體可以參考插件源代碼。

3.插件實現

上面也介紹到,此次涉及到兩個插件,下面分別簡單講述一下具體的編碼實現:

1.antd-icon-reduce-loader

loader的主要實現如下:

module.exports = function(source) {
    parseOptions.call(this);
    ......
    var ast = parser.parse(source, { sourceType: "module", plugins: ['dynamicImport'] }); // 解析源代碼,獲取ast對象
    traverse(ast, {
        CallExpression: function(path) { // 匹配所有調用表達式
		    ......
                if (isCreateIcon(Identifier)) { // Icon組件
                        var iconProps = getIconProps(ObjectExpression.properties);
                       if (Object.keys(iconProps).length > 0) {
                            var type = iconProps.type;
                            var theme = iconProps.theme || 'outline';
                            if (isArray(type)) {  // 三元符情況下,type值不止一個
                                type.forEach(function(item) {
                                    searchIconByName(item, theme);
                                });
                            } else {
                                searchIconByName(type, theme);
                            }
                        }
                    } else if (isButton(Identifier)) { // Button組件
                        var btnProps = getBtnProps(ObjectExpression.properties);
                        Object.keys(btnProps).forEach(function(k) {
                            searchIconByName(k === 'loading' ? k : btnProps[k]);
                        });
                }
        },
    });
    return core.transformFromAstSync(ast).code;
};


2.antd-icon-reduce-plugin

AntdIconReducePlugin.prototype.apply = function(compiler) {
    ......
    const rules = compiler.options.module.rules;
    rules.forEach(function(ruleItem) {
        ......
                if (ruleItem.use[i] === 'ant-icon-reduce-loader') {
                    ruleItem.use[i] = {
                        loader: loaderName,
                        options: {
                            filePath: tempFilePath, // 給loader添加臨時路徑配置
                        },
                    };
                    ......
                }
       ......
    });
    // 添加專門匹配antd依賴包的loader配置
    rules.push({
        test: (filePath) => {
            if (filePath.indexOf(antdModulePath) >= 0 && path.extname(filePath) === '.js') {
                return true;
            }
            return false;
        },
        use: [{
            loader: "antd-icon-reduce-loader",
            options: {
                filePath: tempFilePath, 
            },
        }]
    });
 ......
};


插件使用

1.安裝依賴項

npm i antd-icon-reduce-loader antd-icon-reduce-plugin -D

2.webpack配置

1.添加antd-icon-reduce-loader

......
 module: {
    rules: [
        {
            test: /\.js(x)?$/,
            exclude: /node_modules/,
            use: ["antd-icon-reduce-loader", "babel-loader"],
        }
    ],
},
......

2.添加antd-icon-reduce-plugin插件

......
var AntdIconReducePlugin = require('antd-icon-reduce-plugin');
......
 plugins: [
    ......
    new AntdIconReducePlugin({
            icons: ['download', { type: 'up', theme: 'outline' }], // 自定義需要加入的圖標,在插件不能解析源代碼的情況下使用 
		development: true, // 開發模式下運行插件,默認true
    }),
    ......
]

插件效果展示

圖片描述
從上圖中可以看到使用了插件之后,main.js體積減小了差不多500kb左右,效果還是比較明顯(這只是演示插件效果,沒有做其他輸出優化)。

注意事項

  • 插件只能處理使用字符串字面量來定義Icon類型,使用變量或者其他賦值方式將會被忽略,只有如下兩種方式可以被識別:

1.字符串字面量直接定義

<Icon type="down" />

2.三元符

const isUp = true;
......
<Icon type={isUp ? 'up' : 'down'}

  • 在其他未識別的情況下,需要通過插件的icons屬性手動傳入圖標。

最后

該插件是基於react+antd+webpack的基礎上使用,其中antd適用於3.x大版本,webpack為4.x版本,如果在使用過程中有任何問題,歡迎聯系我,
github地址:https://github.com/fuluteam/antd-icon-reduce-plugin

福祿ICH·架構組 福袋


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM