使用craco對cra專案進行構建優化

語言: CN / TW / HK

修改CRA專案的配置

使用create-react-app 建立的專案預設是無法修改其內部的webpack配置的,不像vue-cli那樣可以通過一個配置檔案修改。 雖然有一個eject 命令可以是將配置完全暴露出來,但這是一個不可逆的操作,同時也會失去CRA帶來的便利和後續升級。

如果想要無 eject 重寫 CRA 配置,目前成熟的是下面這幾種方式

  1. 通過 CRA 官方支援的 --scripts-version 引數,建立專案時使用自己重寫過的 react-scripts 包
  2. 使用 react-app-rewired + customize-cra 組合覆蓋配置
  3. 使用 craco 覆蓋配置

這裡我選擇的是craco

安裝

  1. 安裝依賴
yarn add @craco/craco
複製程式碼
  1. 修改pacage.json中的命令
{
  "scripts":{
    "start": "craco start",
    "build": "craco build",
    "test": "craco test"
  }
} 
複製程式碼

在根目錄建立craco.config.js配置檔案

/* craco.config.js */

module.exports = {
  // ...
  webpack: {},
  babel: {},
}
複製程式碼

基礎的配置到此完成了,接下來是處理各種配置的覆蓋,完整的 craco.config.js 配置檔案結構,可以在 craco 官方的文件中詳細查詢:Configuration File 。

注意! 目前的craco最新版本v6.4.3僅支援cra4建立的專案

構建體積分析

首先引入了webpack-bundle-analyzer 這個外掛來分析一下構建產物的組成

/* craco.config.js */
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); 
module.exports = {
    webpack: {
        plugins: [
            new BundleAnalyzerPlugin({
                analyzerMode: 'server',
                analyzerHost: '127.0.0.1',
                analyzerPort: 8888,
                openAnalyzer: true, // 構建完開啟瀏覽器
                reportFilename: path.resolve(__dirname, `analyzer/index.html`), 
            }),
        ],
    }
}
複製程式碼

在使用yarn build命令打包後,就可以得到一個分析圖,包含了每個chunk的組成部分。

可以看到這裡專案的包體積高達24M,有非常多的重複檔案被打包。

程式碼拆分,減少重複打包

由於使用了懶載入,每個頁面都對應一個獨立的chunk檔案。有些使用比較頻繁的庫,會被重複打包進每個chunk中,增加了很多體積。這裡使用 SplitChunksPlugin來將這些庫拆成一個單獨的chunk。

craco中可以通過configure屬性拿到webpack的配置物件,對其進行修改來配置,將重複的包拆分出去。

經過對圖的分析,發現jsoneditor,echarts,antv等庫對包體積的影響比較大,所以將他們拆分出去。 除了將重複打包的內容拆分之外,我們還可以將專案的基本框架也提取到一個單獨的檔案 base.js 中,該檔案包含了所有網頁的基礎執行環境。(為了長期快取 base.js 檔案)

webpack: {
    plugins: [/** */],
    configure: (webpackConfig, { env: webpackEnv, paths }) => {
            webpackConfig.optimization.splitChunks = {
                ...webpackConfig.optimization.splitChunks,
                cacheGroups: {
                    base: {
                        // 基本框架
                        chunks: 'all',
                        test: /(react|react-dom|react-dom-router)/,
                        name: 'base',
                        priority: 100,
                    },
                    jsoneditor: {
                        test: /jsoneditor/,
                        name: 'jsoneditor',
                        priority: 100,
                    },
                    echarts: {
                        test: /(echarts)/,
                        name: 'echarts',
                        priority: 100,
                    },
                    g2: {
                        test: /@antv/,
                        name: 'g2',
                        priority: 100,
                    },
                    commons: {
                        chunks: 'all',
                        // 將兩個以上的chunk所共享的模組打包至commons組。
                        minChunks: 2,
                        name: 'commons',
                        priority: 80,
                    },
                },
            };
            return webpackConfig;
        },
}
複製程式碼

將它拆分出去後,包體積直接減少到了7.61M,提升顯著。

按需載入大體積的庫

從優化後的分析圖中我發現了一個體積很大的庫BizCharts,而專案中這個庫實際上只使用過不多的幾個元件.

這種情況下,可以通過修改引入方式來進行按需引入。

import { Chart, Axis, Legend, Tooltip } from 'bizcharts';
// 改為手動按需引入
import Chart from 'bizcharts/lib/components/Chart'
import Axis from 'bizcharts/lib/components/Axis'
import ......
複製程式碼

手動修改所有的引入非常的麻煩,這時可以通過babel外掛來幫我們在構建時修改。

babel-plugin-import這個外掛原本是用來給antd按需引入的,但在這裡我們也能用於其他的庫

babel: {
    plugins: [
        [
        'import', 
        { libraryName: 'bizcharts', libraryDirectory: 'lib/components' },
        ],
    ],
}

複製程式碼

構建速度優化

HardSourceWebpackPlugin 外掛可以為模組提供中間快取。首次構建時間沒有太大變化,但是第二次開始,構建時間大約可以節約 80%。

在我的專案中,一開始的構建的速度為26s,配置完外掛生成快取後為15s,節約了60%多的時間。

附上配置

// craco.config.js

const path = require('path');
const CracoLessPlugin = require('craco-less');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const WebpackBar = require('webpackbar');
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
const env = process.env.REACT_APP_ENV;
module.exports = {
    webpack: {
        plugins: [
            new BundleAnalyzerPlugin({
                analyzerMode: env !== 'development' ? 'server' : 'disabled',
                analyzerHost: '127.0.0.1',
                analyzerPort: 8888,
                openAnalyzer: true,
                reportFilename: path.resolve(__dirname, `analyzer/index.html`),
            }),
            new WebpackBar({
                profile: true,
                color: '#fa8c16',
            }),
            new HardSourceWebpackPlugin(),
        ],
        alias: {
            layouts: path.resolve(__dirname, './src/app/layouts'),
            containers: path.resolve(__dirname, './src/app/containers'),
            components: path.resolve(__dirname, './src/app/components'),
            utils: path.resolve(__dirname, './src/utils'),
            routers: path.resolve(__dirname, './src/routers'),
        },
        configure: (webpackConfig, { env: webpackEnv, paths }) => {
            webpackConfig.externals = {
                'ckeditor5-custom-build': 'ClassicEditor',
            };
            webpackConfig.optimization.splitChunks = {
                ...webpackConfig.optimization.splitChunks,
                cacheGroups: {
                    base: {
                        // 基本框架
                        chunks: 'all',
                        test: /(react|react-dom|react-dom-router)/,
                        name: 'base',
                        priority: 100,
                    },
                    jsoneditor: {
                        test: /jsoneditor/,
                        name: 'jsoneditor',
                        priority: 100,
                    },
                    echarts: {
                        test: /(echarts)/,
                        name: 'echarts',
                        priority: 100,
                    },
                    g2: {
                        test: /@antv/,
                        name: 'g2',
                        priority: 100,
                    },
                    commons: {
                        chunks: 'all',
                        // 將兩個以上的chunk所共享的模組打包至commons組。
                        minChunks: 2,
                        name: 'commons',
                        priority: 80,
                    },
                },
            };
            return webpackConfig;
        },
    },
   
    babel: {
        plugins: [
            [   // antd 的按需載入用和自動引入樣式檔案
                'import',
                {
                    libraryName: 'antd',
                    libraryDirectory: 'es',
                    style: true,
                },
            ],
            // bizcharts的按需載入
            ['import', { libraryName: 'bizcharts', libraryDirectory: 'lib/components' }, 'biz'],
        ],
    },
    plugins: [
        {   // 修改antd主題
            plugin: CracoLessPlugin,
            options: {
                lessLoaderOptions: {
                    lessOptions: {
                        math: 'always',
                        modifyVars: {
                            '@primary-color': '#1890ff', //主題顏色
                        }, 
                        javascriptEnabled: true,
                    },
                },
            },
        },
    ],
};
複製程式碼

總結

這次的優化主要是針對減小構建產物體積的,體積從24M -> 6.8M左右,提升還是非常大的。

通過了程式碼分割的方式減少庫被重複打包,以及按需載入一些很大的庫,同時通過一些快取的外掛提升了構建速度。

最後

如果你覺得此文對你有一丁點幫助,點個贊。或者可以加入我的開發交流群:1025263163相互學習,我們會有專業的技術答疑解惑

如果你覺得這篇文章對你有點用的話,麻煩請給我們的開源專案點點star:http://github.crmeb.net/u/defu不勝感激 !

PHP學習手冊:http://doc.crmeb.com
技術交流論壇:http://q.crmeb.com