Vue-Cli4+webpack4项目构建打包优化

lxf2023-04-16 17:46:02

开启AdminJS成长之旅!这是我参与「AdminJS · 12 月更文挑战」的第9天,点击查看活动详情

前言

这个项目是三年前的产品了,一个业务办理系统,集成了很多的模块,日积月累之下,项目变得臃肿,构建时间也来到了120秒;所以就开始了优化。那看完这片文章,我将告诉你,哪些方法可以提高构建速度?哪些不行?哪些是vue-cli4和webpack4自带的,哪些需要你下载额外的插件。

减少打包体积

代码压缩

uglifyjs-webpack-plugin

代码压缩可以减少构建打包后的体积,但是并不能减少构建的时间,甚至增加了构建的时间,因为压缩也是耗费资源和时间的。

npm i -D uglifyjs-webpack-plugin

// 代码压缩
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
​
  //在configureWebpack中plugins加入
    // 代码压缩
      new UglifyJsPlugin({
        uglifyOptions: {
          output: {
            comments: false // 去掉注释
          },
          //生产环境自动删除console
          compress: {
            drop_debugger: true,
            drop_console: true, //注释console
            pure_funcs: ["console.log"] // 移除console
          }
        },
        sourceMap: false, //是否启用文件缓存
        parallel: true  //使用多进程并行运行来提高构建速度
      }),

terser-webpack-plugin

webpack4 默认内置使用 terser-webpack-plugin 插件压缩优化代码,而该插件使用 terser 来缩小 JavaScript 。所以你可以不下载UglifyJsPlugin直接使用 terser-webpack-plugin

const TerserPlugin = require('terser-webpack-plugin')
​
    config
      .when(process.env.NODE_ENV === 'development',
        config => config.optimization.minimizer([new TerserPlugin({
          terserOptions: {
            mangle: true, // 混淆,默认也是开的,mangle也是可以配置很多选项的,具体看后面的链接  
            compress: {
              drop_console: true,//传true就是干掉所有的console.*这些函数的调用.   
              drop_debugger: true, //干掉那些debugger;   
              pure_funcs: ['console.log'] // 如果你要干掉特定的函数比如console.info ,又想删掉后保留其参数中的副作用,那用pure_funcs来处理  
            }
          }
        })])
      )

[terser-webpack-plugin]  github.com/webpack-con…

gzip压缩

gzip可以减少构建打包后的体积,但是并不能减少构建的时间,甚至增加了构建的时间,因为压缩也是耗费资源和时间的。如果是使用nginx进行发布的,记得nginx配置支持gzip

npm install compression-webpack-plugin@6.1.1 --save-dev

const CompressionWebpackPlugin = require('compression-webpack-plugin');
​
  //在configureWebpack中plugins加入
  new CompressionWebpackPlugin({
        // [file] 会被替换成原始资源。[path] 会被替换成原始资源的路径,[query] 会被替换成查询字符串
        filename: '[path][base].gz',
        // 压缩成gzip
        algorithm: 'gzip',
        // 使用正则给匹配到的文件做压缩,这里是给html、css、js以及字体做压缩
        test: /.js$|.css$|.html$|.ttf$|.eot$|.woff$/,
        // 只有大小大于该值的资源会被处理。单位是 bytes。默认值是 0。
        threshold: 10240,
        // 只有压缩率小于这个值的资源才会被处理。默认值是 0.8。
        minRatio: 0.8
      })

如果你按照上面优化一遍,启动时间直接接近翻倍。因为都是为了减少打包体积;

减少构建时间

HardSourceWebpackPlugin

HardSourceWebpackPlugin是一个webpack插件,为模块提供中间缓存步骤。第一次构建将花费正常的时间。之后的建设将大大加快。就算重启,只要缓存还有效,都会使用。

npm install --save-dev hard-source-webpack-plugin

 plugins: [      
      // 缓存 加速二次构建速度
      new HardSourceWebpackPlugin({
        //设置缓存目录的路径      相对路径或者绝对路径
        //  cacheDirectory是在高速缓存写入 ,设置缓存在磁盘中存放的路径
        cacheDirectory: './../disk/.cache/hard-source/[confighash]',
        // 也就是cacheDirectory中的[confighash]值
        recordsPath: './../disk/.cache/hard-source/[confighash]/records.json',
        //configHash在启动webpack实例时转换webpack配置,并用于cacheDirectory为不同的webpack配置构建不同的缓存
        configHash: function (webpackConfig) {
          // node-object-hash on npm can be used to build this.
          return require('node-object-hash')({ sort: false }).hash(webpackConfig);
        },
        // Either false, a string, an object, or a project hashing function.
        environmentHash: {
          root: process.cwd(),
          directories: [],
          files: ['package-lock.json']
          // files: ['./package-lock.json', './yarn.lock'],
        },
        //自动清除缓存
        cachePrune: {
          //缓存最长时间(默认2天),设置了30天
          maxAge: 30 * 24 * 60 * 60 * 1000,
          //所有的缓存大小超过size值将会被清除 (默认50MB) 设置了200MB
          sizeThreshold: 200 * 1024 * 1024
        },
      }),
    ],

配置 externals 引入 cdn 资源

如果要用CDN引入一些资源,为了防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖,可以配置 externals 引入CDN资源;就是改变包引用路径,开发和生产环境统统改变有效。 比如 import Vue from 'vue' 默认来说 引用‘vue’默认从node_modules里面找vue,但是vue.config.js配置了externals,就不去引用node_modules,转而去找public/index.html里面的cdn有关vue的链接

vue.config.js

/**
 * 生产环境上cdn,本地还是走node_modules,但是2版本号保持一致,版本号看package.json
 * https://unpkg.com/ unpkg如何找cdn链接
 * cdn  https://unpkg.com/jquery/  留下最后一个/
 * -> https://unpkg.com/browse/jquery@3.6.1/dist/jquery.slim.min.js  去掉browse/
 * -> https://unpkg.com/https://unpkg.com/jquery@3.6.1/dist/jquery.slim.min.js
 */
const CDN = {
  css: ["https://unpkg.com/vant@2.12.53/lib/index.css"],
  js: [
    "https://unpkg.com/vue@2.6.11/dist/vue.min.js",
    "https://unpkg.com/vue-router@3.2.0/dist/vue-router.min.js",
    "https://unpkg.com/vant@2.12.53/lib/vant.min.js",
    "https://unpkg.com/axios@1.1.3/dist/axios.min.js",
    "https://unpkg.com/vconsole@3.14.7/dist/vconsole.min.js",
    "https://unpkg.com/moment@2.29.4/min/moment.min.js",
    "https://unpkg.com/lodash@4.17.21/lodash.min.js",
  ],
};
module.exports = {
 
  configureWebpack: (config) => {
    if (process.env.NODE_ENV === "development") {
        // ......
    } else {
      //生产环境
      /**
       * externals:externals配置的对象统统走cdn.(不管生产还是本地开发)
       * 解释一下:import A from 'a' 去使用小a的时候不走node_modules了,统统走public/index.html里面配置的cdn链接。前提是要往html里面配置cdn各种链接.到这里就结束了 externals用法就完了
       * 但是有的时候本地开发走node_modules,生产环境走cdn,于是需要区分环境
       */
      config.externals = {
        vue: "Vue", //解释一下:import XX from 'vue' 这里的'vue' 就是externals的key vue. "Vue"是cdn vue链接全局挂载window上的Vue
        "vue-router": "VueRouter",
        moment: "moment",
        vant: "vant",
        axios: "axios",
        vconsole: "VConsole",
        lodash: "_",
      };
    }
  },
  //修改已有插件用chainWebpack vuecli官网明确说过。
  //这里就解释了为啥不在configureWebpack配置define,其实也可以配,不过很麻烦。
  chainWebpack: (config) => {
    config.plugin("define").tap((args) => {
      //这样是搞不出来的  args[0]['process_env'].isProd=JSON.stringify(process.env.NODE_ENV !== "development");
      args[0].isProd = JSON.stringify(process.env.NODE_ENV !== "development"); //都需要JSON.stringify
      args[0].CDN = JSON.stringify(CDN);
      return args;
    });
  },
};

public/index.html

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
    />
    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
    <!-- 生产环境生效 配合externals一起用
 直接就是isProd CDN使用 而不是htmlWebpackPlugin.options.isProd htmlWebpackPlugin.options.CDN使用.⚠️  -->
    <% if(isProd){ %> 
      <% for(var i in CDN.css) {%> 
        <link rel="stylesheet" href="<%= CDN.css[i]%>">
      <% } %>  
    <% } %>
​
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong
        >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
        properly without JavaScript enabled. Please enable it to
        continue.</strong
      >
    </noscript>
    <div id="app"></div>
​
    <!-- built files will be auto injected -->
    <!-- 生产环境生效  配合externals一起用-->
​
    <% if(isProd){ %>
      <% for(var i in CDN.js) {%> 
        <script  src="<%= CDN.js[i]%>"> </script>
      <% } %>  
    <% } %>
  </body>
</html>

splitChunks分包

webpack 4 两个新的配置项optimization.splitChunksoptimization.runtimeChunk,webpack通用模式现在已经做了一些通用性优化,适用于多数使用者。

常用参数:

  • minSize(默认是30000):最小包体积,这里的单位是byte,超过这个大小的包会被splitChunks优化
  • minChunks(默认是1):模块的最小引用次数,如果引用次数低于这个值,将不会被优化
  • maxInitialRequests(默认是3):设置initial chunks的最大并行请求数
  • maxAsyncRequests(默认是5):按需加载时候最大的并行请求数,设置async chunks的最大并行请求数
  • chunks (默认是async) :initial、async和all
  • test: 用于控制哪些模块被这个缓存组匹配到。原封不动传递出去的话,它默认会选择所有的模块。可以传递的值类型:RegExp、String和Function
  • name(打包的chunks的名字):字符串或者函数(函数可以根据条件自定义名字)
  • priority :缓存组打包的先后优先级。
  config
            .optimization.splitChunks({
              chunks: 'all',
              cacheGroups: {
                libs: {
                  name: 'chunk-libs',
                  test: /[\/]node_modules[\/]/,
                  priority: 10,
                  chunks: 'initial' // 仅打包最初依赖的第三方
                },
                elementUI: {
                  name: 'chunk-elementUI', // 将elementUI拆分为单个包
                  priority: 20, // 重量需要大于libs和app,否则将打包成libs或app
                  test: /[\/]node_modules[\/]_?element-ui(.*)/ // 为了适应cnpm
                },
                commons: {
                  name: 'chunk-commons',
                  test: resolve('src/components'), // 可以自定义规则
                  minChunks: 3, //  最小公共数
                  priority: 5,
                  reuseExistingChunk: true
                }
              }
            })
          config.optimization.runtimeChunk('single')

详情查看:www.webpackjs.com/plugins/spl…

其他操作

  1. 如果使用了moment这个包,可以使用dayjs替代,或者如果没有国际化要求,可以只导入中文包
  2. 清理无用的依赖,可能存在安装了,却没有使用的依赖
  3. 清理工程垃圾文件,由于多人协助开发,可能存在一些无用又占内存的文件
  4. 启用webpack使用Vite构建工具