记一次vue2多页应用打包优化

1,332 阅读2分钟

项目为 9 个入口的多页应用, @vue/cli-service版本为 4.4.4webpack 版本为 4.46.0。部署的每一个应用加载都巨慢无比,抓包发现每个应用的首页都需要加载十几 M 的 js 文件。

优化前:

image.png

image.png

优化后:

image.png

image.png

通过命令./node_modules/.bin/vue-cli-service inspect --mode prod > webpack-output.txt 可以看到默认的分包策略:

splitChunks: {
      cacheGroups: {
        vendors: {
          name: 'chunk-vendors',
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          chunks: 'initial'
        },
        common: {
          name: 'chunk-common',
          minChunks: 2,
          priority: -20,
          chunks: 'initial',
          reuseExistingChunk: true
        }
      }
    },

这样会导致 node_modules 中使用的包全都打包进了 chunk-vendors.js 中,而每个应用都使用了同一个 chunk-vendors.js 文件

调整打包策略,将引用的次数达到 5 次的 npm 包打包进 chunk-vendors 中,引用次数达到 2 次的资源(业务代码,图片等)打包进 chunk-common 中:

config.optimization.splitChunks = {
      cacheGroups: {
        vendors: {
          name: 'chunk-vendors',
          test: /[\\/]node_modules[\\/]/,
          minChunks: 5,
          priority: -9,
          chunks: 'all',
          // maxSize: 1000000, // chunk-vendors 再分包(生成的 chunk 没有插入 html 中,先关闭)
          reuseExistingChunk: true,
        },
        common: {
          name: 'chunk-common',
          test(module) {
            return module.resource && !module.resource.includes('node_modules');
          },
          minChunks: 2,
          priority: -10,
          chunks: 'all',
          // maxSize: 1000000, // chunk-common 再分包(生成的 chunk 没有插入 html 中,先关闭)
          reuseExistingChunk: true,
        },
      },
    };

minChunks 不固定,通过不断调整并观察打包后的情况,达到一个比较平衡的状态。

配置 webpack externals,将 chunk-vendors 中的包以 cdn 引入:

const envCdn = {
  PROD: {},
  DEV: {
    js: [
      'https: //公司cdn镜像服务/fastly.jsdelivr.net/npm/vconsole@3.12.0/dist/vconsole.min.js',
        ],
    },
};
const {
  externals = {},
  js = [],
  css = [],
} = process.env.VUE_APP_CLIENT_MODE === 'PROD' ? envCdn['PROD'
] : envCdn['DEV'
];
 
 
const cdn = {
  externals: {
    vue: 'Vue',
    'vue-router': 'VueRouter',
    vuex: 'Vuex',
    axios: 'axios',
    vconsole: 'VConsole',
    echarts: 'echarts',
    jsrsasign: 'jsrsasign',
    swiper: 'Swiper',
    ...externals,
    },
  js: [
    'https: //公司cdn镜像服务/cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js',
    'https: //公司cdn镜像服务/cdn.jsdelivr.net/npm/vue-router@3.3.4/dist/vue-router.min.js',
    'https: //公司cdn镜像服务/cdn.jsdelivr.net/npm/vuex@3.4.0/dist/vuex.min.js',
    'https: //公司cdn镜像服务/cdn.jsdelivr.net/npm/axios@0.21.1/dist/axios.min.js',
    'https: //公司cdn镜像服务/cdn.jsdelivr.net/npm/jsrsasign@8.0.24/lib/jsrsasign-all-min.js',
    'https: //公司cdn镜像服务/cdn.jsdelivr.net/npm/swiper@5.4.5/dist/js/swiper.min.js',
    ...js,
    ],
  css: [...css
    ],
};
...
pages: {
    index: {
      entry: 'src/main.ts',
      template: 'public/index.html',
      filename: 'index.html',
      title: '',
      cdn,
    },
    client: {
      entry: 'client/main.ts',
      template: 'public/client.html',
      filename: 'client.html',
      title: '',
      cdn,
    },
    echart: {
      entry: 'plugin/echart/main.ts',
      template: 'plugin/echart/echart.html',
      filename: 'echart.html',
      title: '',
      cdn: {
        ...cdn,
        js: [
          ...cdn.js,
          'https://公司cdn镜像服务/cdn.jsdelivr.net/npm/echarts@5.0.1/dist/echarts.min.js',
        ],
      },
    },
}
...

调整 html 模板:

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0" />
  <meta name="format-detection" content="telephone=no" />
  <title></title>
  <!-- 使用CDN的CSS文件 -->
  <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
    <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="reload" as="style" />
    <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" />
  <% } %>
</head>

<body>
  <div id="app"></div>
  <!-- 使用CDN的JS文件 -->
  <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
    <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
  <% } %>
  <% if (VUE_APP_CLIENT_MODE !== 'PROD') { %>
    <script>
      if(location.host.indexOf('localhost') === -1) {
        const vConsole = new VConsole();
      }
    </script>
  <% } %>
</body>

</html>

另外,生产打包的时候, NODE_ENV 环境变量设置为 prod,而不是 production,导致部分包 tree shaking 未生效,引入了整个包。