乐趣区

vuecli3多页应用构建优化

接续前篇多页应用改造,优化改造后的项目打包效果。

效果对比

阶段 磁盘占用空间 打包时间 备注
原始 9.9M 5.20 版
架构改造(SPA->MPA) 7.1M 5.27 版
样式整合 7.0M 5.30 版
公共组件 & 工具类整合 6.9M 6.4 版
图片压缩 6.0M 20.767s
UglifyJs 5.9M 23.391s
合并小文件 5.3M 55.469s
代码分割 splitChunks 5.1M 73.730s
gzip 6.3M 89.204s

优化前

优化后

优化过程

压缩图片

chainWebpack: config => {
  config.module
    .rule("image-webpack-loader")
    .test(/\.(gif|png|jpe?g|svg)$/i)
    .use("file-loader")
    .loader("image-webpack-loader")
    .tap(() => ({disable: process.env.NODE_ENV !== "production"}))
    .end()};

添加 UglifyJs,移除 console

const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
configureWebpack: config => {if (process.env.NODE_ENV === 'production') {const plugins = []
      plugins.push(
        new UglifyJsPlugin({
          uglifyOptions: {
            compress: {
              drop_console: true,
              drop_debugger: true
            }
          },
          cache: true, // 启用文件缓存
          parallel: true // 使用多进程并行运行来提高构建速度
          // sourceMap: false // 映射错误信息到模块
        })
      )
      config.plugins = [
        ...config.plugins,
        ...plugins
      ]
    }
  }

动态导入合并小文件 webpackChunkName

原来打包出来的文件,快上百个了,还有好些 0.29KB 的,需要合并下。此处使用webpackChunkName

首先按照业务含义合并,以 service 为例

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

const routes = [
  {
    path: '/service',
    component: () => import(/* webpackChunkName: "service-index" */ '@service/components/page-layout/TwoColLayout.vue'),
    redirect: {name: 'Detail'},
    children: [
      {
        path: 'detail',
        name: 'Detail',
        component: () => import(/* webpackChunkName: "service-index" */ '@service/views/detail/index.vue')
      },
      /**
       * 接口申请(指标)*/
      {
        path: 'api-apply',
        name: 'ApiApply',
        component: () => import(/* webpackChunkName: "service-api-apply" */ '@service/views/detail/views/apply-api/index.vue')
      },
      {
        path: 'api-apply-form',
        name: 'ApiApplyForm',
        component: () => import(/* webpackChunkName: "service-api-apply" */ '@service/views/detail/views/apply-api/api-apply-form.vue')
      },
      /**
       * 接口管理
       */
      {
        path: 'manage',
        name: 'Manage',
        component: () => import(/* webpackChunkName: "service-manage" */ '@service/views/manage/index.vue')
      },
      { // 接口管理 - 新建 / 修改
        path: 'manage/upsert',
        name: 'ManageUpsert',
        component: () => import(/* webpackChunkName: "service-manage" */ '@service/views/manage/module/upsert/index.vue')
      }]
  },
  {
    path: '*',
    redirect: '/service'
  }
]

const router = new Router({mode: 'history', routes})

export default router


看起来都比较小,太小的独立文件单占连接数不是很合适。那就 service-manage 保留(业务上此处只有管理员能访问的管理页会调用,一般人看不到,不用和其他逻辑一起加载,还是保持独立),
其他的再统一合并成 service-main。这里合并的宗旨是文件不超过 250K。


此处省略处理各个产品打包合并过程。

代码分割 splitChunks


从打包目录看,chunk-vendors-xxx.js已达 1.7M,需要拆分。

参考文章:一步一步的了解 webpack4 的 splitChunk 插件

这里根据实际情况优化,具体大家可通过 vue-cli-service build --report 生成 report.html 分析包内容

首先拆出公用的 element-ui

  configureWebpack: config => {if (IS_PROD) {
      config.optimization = {
        splitChunks: {
          cacheGroups: {
            element: {
              name: 'vendors-element-ui',
              test: /[\\/]node_modules[\\/]element-ui[\\/]/,
              chunks: 'initial',
              reuseExistingChunk: true,
              enforce: true,
              priority: 3
            },
          }
        }
      }
    }
  }

然后把所有 node_modlues 下的引用合并到 vendors

...
cacheGroups: {
        vendors: {
          name: 'vendors',
          test: /[\\/]node_modules[\\/]/,
          chunks: 'all',
          priority: 2
        }
}
...

再将所有模块中被不同的 chunk 引入超过 1 次的抽取为 common

...
cacheGroups: {
        common: {
          name: 'common',
          chunks: 'initial',
          minChunks: 2,
          maxInitialRequests: 5,
          priority: 1
        }
}
...

启用 gzip

const CompressionWebpackPlugin = require('compression-webpack-plugin')
  configureWebpack: config => {if (IS_PROD) {const plugins = []
      plugins.push(
        new CompressionWebpackPlugin({filename: '[path].gz[query]',
          algorithm: 'gzip',
          test: /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i,
          threshold: 10240,
          minRatio: 0.8
        })
      )
      config.plugins = [
        ...config.plugins,
        ...plugins
      ]
    }
  }

css 根据入口文件打包


到这里,打包出来的 css 文件都很小,单独放到一个资源文件去请求挺浪费连接数的,所以尝试合并 css。

参考:Extracting CSS based on entry

目前暂未成功,可能与上述 webpackChunkName 方式冲突,合并后的 css 正常,但根据 webpackChunkName 之后的 css 依旧会打包出来,暂时没找到更好的解决办法。待更新~

警告处理

[mini-css-extract-plugin] Conflicting

参考 issue reported on Github

两种解决方法:

  1. 调整 import 顺序
  2. 配置忽略警告

选用方法 1 解决。

退出移动版