umi打包编译优化

项目分析

痛点

  1. 项目打包后的体积很大,打包时间也很长,首次打包时长预计8min-20min,导致每次遇到紧急线上问题或者紧急需求时,开发两分钟,发布两小时。整个发布时长拉长,用户体验不好
  2. 项目启动非常慢,热更新非常慢,开发效率大大减少

分析

  1. 原项目总的打包体积为****146.83MB!! ,压缩后也****38.9MB!! ,体积非常大。
  1. 很多公共的模块出现在多个chunk 里面,比如antd相关等
  1. 启动时间非常慢,热更新速度非常慢,影响开发效率,启动速度大约164s/3min~ ,热更新大约16s

体积优化

step1:升级到umi3.5.0

v3.umijs.org/zh-CN/docs/...

umi版本较低,许多新特性无法使用,修改 umi 的版本为 ^3.5.0 或以上

json 复制代码
{
  "devDependencies": {
-    "umi": "^3.2.0-beta.6",
+   "umi": "^3.5.0",
  }
}

step2:提取公共依赖

调整 splitChunks 策略,增加公共依赖的提取,减少整体尺寸

php 复制代码
  // 如果开了 dynamicImport,然后产物特别大,每个出口文件都包含了相同的依赖,比如 antd,可尝试通过 splitChunks 配置调整公共依赖的提取策略。
  dynamicImport: {
    loading: '@/components/Loading',
  },
  chunks: ['vendors', 'umi'],
  chainWebpack: function(config) {
    config.merge({
      optimization: {
        splitChunks: {
          chunks: 'all',
          minSize: 30000,
          minChunks: 3,
          automaticNameDelimiter: '.',
          cacheGroups: {
            vendor: {
              name: 'vendors',
              test({ resource }) {
                return /[\/]node_modules[\/]/.test(resource);
              },
              priority: 10,
            },
          },
        },
      },
    });
  },
  • 首次见效

做完以上两步骤后可以看出,以上修改后包体积大大减少,提升率达到91%!!

step3:删除多余依赖

项目中一些依赖包没有用到,但仍然安装,可以删除这些不必要的依赖包

json 复制代码
{
  "dependencies": {
    -"add": "^2.0.6",
    -"behooks": "0.8.3",
  },
  "devDependencies": {
    -"webpack": "^4.43.0"
  },
}

step4:优化冗余依赖(暂时注释)

对于一些大尺寸依赖,比如图表库、antd 等,可尝试通过 externals 的配置引入相关 umd 文件,减少编译消耗

xlsx/react-dom/react

不再使用npm方式安装xlsx 通过externals 引入cdn链接

xml 复制代码
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.16.9/xlsx.mini.min.js"  integrity="sha512-bF+HLFD/vkMdiECsWsJfRUkaQObWj/BhCE4E9eDfZL2VN6HDh1JKMzRFs0uOPUwic71Zvodr5imoD7eT3eVdGA==" crossorigin="anonymous"></script>
rust 复制代码
  externals: {
    react: 'window.React',
    'react-dom': 'window.ReactDOM',
    xlsx: 'XLSX',
  },
  // 区分 development 和 production,使用不同的产物
  scripts:
    process.env.NODE_ENV === 'development'
      ? [
          'https://gw.alipayobjects.com/os/lib/react/17.0.0/umd/react.development.js',
          'https://gw.alipayobjects.com/os/lib/react-dom/17.0.0/umd/react-dom.development.js',
        ]
      : [
          'https://gw.alipayobjects.com/os/lib/react/17.0.0/umd/react.production.min.js',
          'https://gw.alipayobjects.com/os/lib/react-dom/17.0.0/umd/react-dom.production.min.js',
        ],

通过externals引入后,xlsx/react-dom/react依赖将不再占用包内存! 大约减少了5mb!

由于外部cdn可能挂掉,这项优化项暂时不做~

moment.js

忽略 moment 的 locale 文件,用于减少尺寸

yaml 复制代码
 ignoreMomentLocale: true,

优化前大小:1.05mb、压缩大小:271.68kb

优化前大小:118.41kb、压缩大小:37.73kb

可以看出moment体积大大减小!!

step5:开启gzip压缩

需要Nginx开启Gzip属性

npm install compression-webpack-plugin
javascript 复制代码
const CompressionWebpackPlugin = require('compression-webpack-plugin');

// 开启gzip压缩
config.plugin('CompressionPlugin').use(
  new CompressionWebpackPlugin({
    test: /.(js|css)$/,
    threshold: 10240, // 超过10kb的文件就压缩
    deleteOriginalAssets: false, // 不删除源文件
    minRatio: 0.8,
  }),
);

编译提速

webpack4升级webpack5

webpack.docschina.org/migrate/5/

webpack5的加载速度比webpack4加载更快

css 复制代码
  webpack5: {},

esbuild

编译慢中压缩时间占了大部分,所以如果编译时不压缩可节约大量的时间和内存消耗,但尺寸会增加不少。替换terser压缩器为esbuild,来提高编译速度。

css 复制代码
 esbuild: {},

fastRefresh

开发环境下,可以保持组件状态 ,同时编辑提供即时反馈

css 复制代码
  fastRefresh: {}, 

nodeModulesTransform

Umi 默认编译 node_modules 下的文件,带来一些收益的同时,也增加了额外的编译时间。如果不希望 node_modules 下的文件走 babel 编译,可通过以下配置减少 40% 到 60% 的编译时间

css 复制代码
 nodeModulesTransform: {
      type: 'none',
      exclude: [],
    },

mfsu

arduino 复制代码
  // 有缓存时启动 1s+,热更新平均 500ms 内
  mfsu: {},

分析对比

产物分布对比

未压缩体积对比

已压缩体积对比

首次启动速度对比

二次启动速度对比

热更新速度对比

tone首次发布时长对比

花费预计10分钟


花费预计5分钟

tone二次发布时长对比

花费预计4分钟


花费预计1分钟

总结

未压缩体积m 已压缩体积m 首次启动速度s 二次启动速度s 热更新速度s tone首次发布时min tone二次发布时长min
优化前 146.83 38.9 141 98.4 7 10 4
优化后 9.89 2.78 9 3.7 2 5 1
提升率% 93.26431928 92.85347044 93.61702128 96.2398374 71.42857143 50 75

可以看出数据呈现直线下降趋势,效率大大提高!或许一些数据只提升了几分钟甚至几秒钟的效率,但当我们在开发一个版本时,不停修改保存打包代码,次数不断累加时,就会发现效率产生质的飞跃!

过程问题

  • 开启了mfsu报错:Unhandled Rejection (ChunkLoadError): Loading chunk mf-dep_vendors failed.

将splitChunks策略放在生产环境执行

  • tone构建出现告警:(node:45214) [DEP_WEBPACK_CONFIGURATION_OPTIMIZATION_NO_EMIT_ON_ERRORS] DeprecationWarning: optimization.noEmitOnErrors,压缩css失败 optimization,optimize-css-assets-webpack-plugin插件与webpack5不兼容引起的警告,但不影响压缩

webpack5中同等功能的插件是css-minimizer-webpack-plugin,安装并修改配置

由于无法下载css-minimizer-webpack-plugin 暂未使用

csharp 复制代码
yarn add -D css-minimizer-webpack-plugin
yaml 复制代码
optimization: {
    minimize: true,
    minimizer: [
        new CssMinimizerPlugin(),
    ],
}
  • 时间组件全部变成英文格式

由于配置了 ignoreMomentLocale: true 忽略了moment.js所有语言包,需要单独在app.js导入中文语言包

javascript 复制代码
import moment from 'moment'
// 导入中文语言包
import 'moment/locale/zh-cn';
// 设置中文
moment.locale('zh-cn');

umirc.ts配置

javascript 复制代码
import { defineConfig, IConfig } from 'umi';
import routes from './routes';
import path from 'path';
const CompressionWebpackPlugin = require('compression-webpack-plugin')

// 生产环境配置
let configPro = {};
if (process.env.NODE_ENV !== 'development') {
  // 开启了mfsu报错:Unhandled Rejection (ChunkLoadError): Loading chunk mf-dep_vendors failed.
  // splitChunks 策略 只能放在生产环境
  configPro = {
    // Umi 默认编译 node_modules 下的文件,带来一些收益的同时,也增加了额外的编译时间。如果不希望 node_modules 下的文件走 babel 编译,可通过以下配置减少 40% 到 60% 的编译时间
    nodeModulesTransform: {
      type: 'none',
      exclude: [],
    },
    chunks: ['vendors', 'umi'],
    chainWebpack: function(config: any) {
      if (process.env.UMI_ENV !== 'development') {
        config.merge({
          optimization: {
            // 提取公共依赖,调整 splitChunks 策略,减少整体尺寸
            splitChunks: {
              chunks: 'all',
              minSize: 30000,
              minChunks: 3,
              automaticNameDelimiter: '.',
              cacheGroups: {
                vendor: {
                  name: 'vendors',
                  test({ resource }) {
                    return /[\/]node_modules[\/]/.test(resource);
                  },
                  priority: 10,
                },
              },
            },
          },
        });
        // 开启gzip压缩
        config.plugin('CompressionPlugin').use(
          new CompressionWebpackPlugin({
            test: /.(js|css)$/,
            threshold: 10240, // 超过10kb的文件就压缩
            deleteOriginalAssets: false, // 不删除源文件
            minRatio: 0.8
          })
        )
      }
    },
    // 替换压缩器为 esbuild
    esbuild: {},
  };
}

const config: IConfig = {
  title: '陆运通运营后台',
  antd: {},
  dva: {},
  hash: true,
  history: {
    type: 'hash',
  },
  publicPath: '/s/lytAdminSite/',
  theme: {
    'primary-color': '#2B82D8',
  },
  routes,
  alias: {
    '@': path.resolve(__dirname, 'src'),
    // TODO  本地开发使用的
    // 'tf-exportFile/libs': path.join(__dirname, 'dev-npm/lyt-components/tf-exportFile/src/components/index'),
    // 'tf-traceService/libs': path.join(__dirname, 'dev-npm/lyt-components/tf-traceService/src/components/index'),
    // 'tf-lookHd/libs': path.join(__dirname, 'dev-npm/lyt-components/tf-lookHd/src/components/index.js'),
  },
  devServer: {
    host: '0.0.0.0',
    port: 4000,
    proxy: {
      '/landTransAdmin': {
        target: 'https://optest.tf56.com',
        // target: 'http://optest.tf56.lo',  //容器云环境
        // target: 'http://10.50.9.154:8080',
        changeOrigin: true,
      },
    },
    https: {
      key: './public/mytest.tf56.com-key.pem',
      cert: './public/mytest.tf56.com.pem',
    },
  },

  /**-------------------------------------编译提速配置-------------------------------- */
  // 如果开了 dynamicImport,然后产物特别大,每个出口文件都包含了相同的依赖,比如 antd,可尝试通过 splitChunks 配置调整公共依赖的提取策略。
  dynamicImport: {
    loading: '@/components/Loading',
  },

  // 忽略 moment 的 locale 文件,用于减少尺寸。
  ignoreMomentLocale: true,

  // 快速刷新
  fastRefresh: {},
  // 对于一些大尺寸依赖,比如图表库、antd 等,可尝试通过 externals 的配置引入相关 umd 文件,减少编译消耗。
  // externals: {
  //   "react": 'window.React',
  //   'react-dom': 'window.ReactDOM',
  //   'xlsx': 'XLSX'
  // },
  // 区分 development 和 production,使用不同的产物
  // scripts:
  //   process.env.NODE_ENV === 'development'
  //     ? [
  //         'https://gw.alipayobjects.com/os/lib/react/17.0.0/umd/react.development.js',
  //         'https://gw.alipayobjects.com/os/lib/react-dom/17.0.0/umd/react-dom.development.js',
  //       ]
  //     : [
  //         'https://gw.alipayobjects.com/os/lib/react/17.0.0/umd/react.production.min.js',
  //         'https://gw.alipayobjects.com/os/lib/react-dom/17.0.0/umd/react-dom.production.min.js',
  //       ],

  // 有缓存时启动 1s+,热更新平均 500ms 内
  mfsu: {},
  webpack5: {},
  ...configPro,
};

export default defineConfig(config);
相关推荐
Мартин.2 小时前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
昨天;明天。今天。3 小时前
案例-表白墙简单实现
前端·javascript·css
数云界3 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd3 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
安冬的码畜日常3 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine
ChinaDragonDreamer3 小时前
Vite:为什么选 Vite
前端
小御姐@stella3 小时前
Vue 之组件插槽Slot用法(组件间通信一种方式)
前端·javascript·vue.js
GISer_Jing3 小时前
【React】增量传输与渲染
前端·javascript·面试
eHackyd3 小时前
前端知识汇总(持续更新)
前端
万叶学编程6 小时前
Day02-JavaScript-Vue
前端·javascript·vue.js