使用 craco 对 cra 项目进行构建优化

2022-09-29 16:26:07 浏览数 (1)

修改 CRA 项目的配置使用 create-react-app 创建的项目默认是无法修改其内部的 webpack 配置的,不像 vue-cli 那样可以通过一个配置文件修改。 虽然有一个 eject 命令可以是将配置完全暴露出来,但这是一个不可逆的操作,同时也会失去 CRA 带来的便利和后续升级。

如果想要无 eject 重写 CRA 配置,目前成熟的是下面这几种方式

通过 CRA 官方支持的 --scripts-version 参数,创建项目时使用自己重写过的 react-scripts 包使用 react-app-rewired customize-cra 组合覆盖配置使用 craco 覆盖配置这里我选择的是 craco

安装

安装依赖

代码语言:javascript复制
yarn add @craco/craco

修改 pacage.json 中的命令

代码语言:javascript复制
{
  "scripts":{
    "start": "craco start",
    "build": "craco build",
    "test": "craco test"
  }
} 

在根目录创建 craco.config.js 配置文件

代码语言:javascript复制
/* craco.config.js */

module.exports = {
  // ...
  webpack: {},
  babel: {},
}

基础的配置到此完成了,接下来是处理各种配置的覆盖,完整的 craco.config.js 配置文件结构,可以在 craco 官方的文档中详细查询:Configuration File 。

注意! 目前的 craco 最新版本 v6.4.3 仅支持 cra4 创建的项目

构建体积分析首先引入了 webpack-bundle-analyzer 这个插件来分析一下构建产物的组成

代码语言:javascript复制
/* 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 文件)

代码语言:javascript复制
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,而项目中这个库实际上只使用过不多的几个组件.

这种情况下,可以通过修改引入方式来进行按需引入。

代码语言:javascript复制
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 按需引入的,但在这里我们也能用于其他的库

代码语言:javascript复制
babel: {
    plugins: [
        [
        'import', 
        { libraryName: 'bizcharts', libraryDirectory: 'lib/components' },
        ],
    ],
}


构建速度优化 HardSourceWebpackPlugin 插件可以为模块提供中间缓存。首次构建时间没有太大变化,但是第二次开始,构建时间大约可以节约 80%。

在我的项目中,一开始的构建的速度为 26s,配置完插件生成缓存后为 15s,节约了 60%多的时间。

附上配置

代码语言:javascript复制
// 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 左右,提升还是非常大的。

通过了代码分割的方式减少库被重复打包,以及按需加载一些很大的库,同时通过一些缓存的插件提升了构建速度。

最后

如果你觉得此文对你有一丁点帮助,点个赞。

如果你觉得这篇文章对你有点用的话,麻烦请给我们的开源项目点点 star:http://github.crmeb.net/u/lsq不胜感激 !

PHP 学习手册:https://doc.crmeb.com

技术交流论坛:https://q.crmeb.com

0 人点赞