基于vue-cli项目的webpack打包优化实践

前言

看了不少打包优化的文章,不少都是基于原生的webpack配置,直接在webpack.config.js文件中修改配置的。可是vue-cli建立的项目已经封装了基本的webpack配置,须要在vue.config.js文件中修改预置的webpack配置。不多看到这方面的文章,所以记录一下本身的实践过程和踩过的一些坑。javascript

本次使用技术的版本状况:html

  • vue:2.6.10
  • @vue/cli:4.0.5
  • webpack:4.31.0

vue-cli中的webpack

要优化项目,首先咱们得了解vue-cli已经替咱们作过了哪些优化,也就是须要查看webpack已经配置了哪些选项。
使用vue inpsect输出webpack配置,还能够指定输出的文件:vue inspect > output.jsvue

vue-cli提供了两种方式来更改webpack配置:
一、原生配置方式,配置的结果将会被 webpack-merge 合并入最终的 webpack 配置。java

// vue.config.js
module.exports = {
    configureWebpack: {
        // 在这里直接书写webpack配置项...
    }
}
复制代码

二、链式配置方式,vue-cli内部是使用webpack-chain这个插件来维护webpack配置的,由于能更细粒度的控制其内部配置,所以也是官方比较推荐的一个方式。node

// vue.config.js
module.exports = {
    chainWebpack: config => {
        config.resolve.alias.set('@assets', resolve(`src/assets`));
    },
}
复制代码

这两种方法能够配合使用。
为了简便,也为了少踩点儿坑,本次优化主要采用原生的webpack配置,也就是使用configureWebpack的方式。 优化过程分为打包体积优化和打包速度优化。webpack

优化打包体积

使用webpack-bundle-analyzer分析打包体积

webpack官方提供一些插件分析打包性能。
git

  • webpack-chart:webpack stats 可交互饼图。
  • webpack-visualizer:可视化并分析你的 bundle,检查哪些模块占用空间,哪些多是重复使用的。
  • webpack-bundle-analyzer:一个 plugin 和 CLI 工具,它将 bundle 内容展现为便捷的、交互式、可缩放的树状图形式。
  • webpack bundle optimize helper:此工具会分析你的bundle,并为你提供可操做的改进措施建议,以减小 bundle 体积大小。

咱们使用webpack-bundle-analyzer来分析打包体积。github

// yarn add analyze-webpack-plugin --dev

// vue.config.js
const AnalyzeWebpackPlugin = require('analyze-webpack-plugin')
module.exports = {
  configureWebpack: {
    plugins: [
      new AnalyzeWebpackPlugin({}),
    ],
  }
}
复制代码

运行打包命令:yarn build,会自动打开分析结果页面。web

webpack-bundle-analyzer使用三种指标衡量打包体积:
正则表达式

  • stat:输入的文件大小,还未通过例如压缩之类的转换。
  • parsed:输出的文件大小,代码通过丑化压缩后的大小。
  • gzip:开启了gzip压缩后的大小。
优化moment —— ContextReplacementPlugin

观察上图,能够发现moment占据了不小的比重,主要是一些本地化的语言包,默认都会打包进来。
对于普通应用来讲,咱们只须要中文语言包就够了。

优化前:

  • Stat: 540.76KB
  • Parsed: 234.36KB
  • Gzipped: 68.46KB

首先选择合适的语言包设置语言环境

import moment from 'moment';
import 'moment/locale/zh-cn';
moment.locale('zh-cn');
复制代码

ContextReplacementPlugin插件的做用是改变某个模块的打包上下文,经过修改正则,来让webpack只打包咱们想要的文件。

// yarn add webpack --dev

// vue.config.js
const webpack = require('webpack');
module.exports = {
  configureWebpack: {
    plugins: [
      new webpack.ContextReplacementPlugin(
        /moment[/\\]locale$/, // 这个参数代表了咱们要改变的打包上下文
        /zh-cn/ // 这个参数表示咱们只想打包这个正则匹配的文件
      )
    ]
  },
};
复制代码

优化后:

  • Stat: 150.79KB
  • Parsed: 54.61KB
  • Gzipped: 17.96KB

原来393.36KB的语言包只保留中文后变为仅有3.39KB。

关于moment打包,社区提供了不少方法,还有其余一些方案能够参考:github.com/jmblog/how-…

优化XLSX

咱们项目中使用了这个库来生成excel并下载。
原来直接引入import { utils, writeFile } from 'xlsx';,打包后体积很是庞大。

优化前:

  • Stat: 1.23MB
  • Parsed: 920.85KB
  • Gzipped: 327.65KB

后来在issue区查到了解决方案,改成只引入mini版本:

import { utils, writeFile } from 'xlsx/dist/xlsx.mini.min.js';

若是使用的是typescript会报错:

声明一下模块便可:

// modules.d.ts
declare module 'xlsx/dist/xlsx.mini.min.js';
复制代码

优化后几乎只剩了零头:

  • Stat: 236.73KB
  • Parsed: 189.66KB
  • Gzipped: 60.79KB

注意,官方解释这个xlsx这么大是有缘由的,由于涉及到读取文件,要支持一些比较老的格式。若是你的项目中只是用来生成excel,不涉及读取文件,就能够用这个mini版本;若是有涉及到读取excel文件的操做,仍是老老实实全量引入吧。官方将来或许会提供只支持现代文件格式的轻量级版本。

lodash打包体积 —— lodash专用plugin

优化前:

  • Stat: 540.17KB
  • Parsed: 73.29KB
  • Gzipped: 25.74KB

须要使用两个插件:

  • babel-plugin-lodash 用来精简Lodash模块的,只保留用到的方法。

  • lodash-webpack-plugin 这个插件经过用noop, identity, 或其余更简单的替代品来替换一些模块的特性,使得打包后的体积更小(翻译)。
    注意:这个插件默认会关闭一些lodash不经常使用的特性,能够给插件传递options来开启某些特性。

这两个插件配合使用来使效果最大化。只须要在Babel插件中添加lodash,并在webpack配置中添加一个插件:

// yarn add babel-plugin-lodash lodash-webpack-plugin --dev

// babel.config.js
modules.exports = {
  // 其余配置省略...
  plugins: ['lodash']
}

// vue.config.js
const LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
module.exports = {
  // ...
  configureWebpack: {
    plugins: [
      new LodashModuleReplacementPlugin()
    ]
  }
}
复制代码

优化后:

  • Stat: 56.42KB
  • Parsed: 11.34KB
  • Gzipped: 3.81KB

低调了许多,找了很久才找到 XD

抽取公共代码 —— splitChunks(webpack4以前使用commonChunkPlugin,webpack4以后使用splitChunks)

咱们项目中使用了西瓜播放器,发现xgplayer做为第三方库,并无被打包进chunk-vendors,而且还重复打包了两次。

关于这个xgplayer,引用状况是:有两个页面引用了一个公共的组件,这个组件引用了xgplayer。因此为何xgplayer没有打包进chunk-vendors?

看一下vue-cli预设的webpack配置:

// ...
optimization: {
    minimizer: [
      // ...
    ],
    splitChunks: {
      cacheGroups: {
        vendors: {
          name: 'chunk-vendors',
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          chunks: 'initial'
        },
        common: {
          name: 'chunk-common',
          minChunks: 1,
          priority: -20,
          chunks: 'initial',
          reuseExistingChunk: true
        }
      }
    }
  }
复制代码

vendors打包了node_modules里符合条件的第三方库,这个条件就是chunks: 'initial'
chunks表示要打包的这些chunks的类型,有三个值:

  • initial:初始的chunk,须要当即加载,其实就是main.ts里经过import同步引入的模块。
  • async:经过import()等动态引入的chunk,也就是按需引入的异步的模块。
  • all:包含同步和异步的模块。选了这个,将会打包全部test匹配到的模块,这里是node_modules,显然是不合适的,由于有些第三方库可能晚点才会用到,好比这里的xgplayer。

因此xgplayer虽然是经过import同步引入的,但引用它的两个页面组件在路由文件中是import()按需引入的,而且没有在main.ts中引入xgplayer,因此天然不会打包到chunk-vendors里。

因此应该按照异步模块async或all的类型来打包。

// vue.config.js
module.exports = {
  // ...
  configureWebpack: {
    optimization: {
      splitChunks: {
        cacheGroups: {
          xgplayer: {
            name: 'xgplayer',
            test: /[\\/]node_modules[\\/]xgplayer[\\/]/,
            minSize: 0,
            minChunks: 1,
            reuseExistingChunk: true,
            chunks: 'all'
          }
        }
      }
    }
  }
}
复制代码

优化后只打包了一份:

优化echarts —— IgnorePlugin

优化echarts的难点在于,项目前期使用了两种方式:

  1. 引入了第三方的vue-echarts,参考这个组件写了一个本身的公共组件base-chart,结果并无用到这个库。 页面的图表有的是基于base-chart的同时按需引入相关组件如echarts/lib/line。
  2. 有的是直接引用原生的echart从0开始写的组件。

这就致使了echarts全量引入,而且处处打包的问题。

解决方案:因为咱们的首页是登陆页,没有用到echarts,不须要第一次就加载echarts,所以要作两件事来优化:

  1. 这个第三方vue-echarts在main.ts中全局注册组件了,但其实并无使用,须要删除全局引用,避免打包进chunk-vendors。
  2. 抽离重复打包的部分,合并进一个chunk。
// main.ts的组件注册代码也要注释掉
// import ECharts from 'vue-echarts';
// Vue.component('echart', ECharts);

optimization: {
  splitChunks: {
    cacheGroups: {
      echarts: {
        name: 'echarts',
        test: /[\\/]node_modules[\\/]echarts[\\/]/,
        minSize: 0,
        minChunks: 1,
        reuseExistingChunk: true,
        chunks: 'all',
      },
    },
  },
},
复制代码

通过优化后已经从chunk-vendor里抽离出来,并把多处存在的echarts引用合并进了一个bundle。可是能够看到体积仍是很大的。

github上有人就打包体积太大提了issue,做者建议使用在线builder,根据项目使用状况按需打包。并说5.0版本可能会考虑减少打包体积。

可是实际使用过程当中打包到中途某些资源504网关超时了,重试了几回都失败,只好另寻他法。

使用webpack内置的IgnorePlugin插件来忽略项目中用不到的文件。能够对照在线builder的网址。
分别从node_modules/echarts/lib目录下的component、chart、coord三个目录进行排除。

IgnorePlugin插件配置项中,须要先使用contextRegExp来肯定即将要排除的文件的上下文,这里是echarts目录。
而后使用resourceRegExp来指定要排除的资源的正则表达式。
实际上,这里只排除了这些目录,还有一些跟目录同级的文件,可能跟要排除的这些图表/组件相关,可是为了不误判,就作不到那么精细了。

plugins: [
  new webpack.IgnorePlugin({
    resourceRegExp:
      /^\.\/lib\/(component\/visualMap|toolbox|timeline|geo|brush|calendar)|(chart\/effectScatter|candlestick|heatmap|tree|treemap|sunburst|map|graph|boxplot|parallel|gauge|funnel|sankey|themeRiver|pictorialBar)|(coord\/polar|geo|singleAxis|calendar)$/,
    contextRegExp: /echarts$/
  })
]
复制代码

优化后立马小了很多:

优化ant-design-vue

优化前:

发现icons占据很大的位置,可是实际使用的时候极少使用icons。
GitHub上面有人提了issue 做者解释说button会自动引用icon,设计如此。ant-design已经在优化了,目前暂时使用了做者推荐的方法来按需引入icon:
增长一个别名,让webpack解析的时候使用咱们提供的icons.js文件中的路径,只打包使用过的icon。

resolve: {
  alias: {
    '@ant-design/icons/lib/dist$': resolve('./src/core/antd/icons.js')
  }
},
复制代码

而后在src目录下添加相应的文件,见github

export {
  default as SettingOutline
} from '@ant-design/icons/lib/outline/SettingOutline'
export {
  default as GithubOutline
} from '@ant-design/icons/lib/outline/GithubOutline'
export {
  default as CopyrightOutline
} from '@ant-design/icons/lib/outline/CopyrightOutline'

/* MultiTab begin */
export {
  default as CloseOutline
} from '@ant-design/icons/lib/outline/CloseOutline'
export {
  default as ReloadOutline
} from '@ant-design/icons/lib/outline/ReloadOutline'
export {
  default as DownOutline
} from '@ant-design/icons/lib/outline/DownOutline'
export {
  default as AlignLeftOutline
} from '@ant-design/icons/lib/outline/AlignLeftOutline'
/* MultiTab end */

/* Layout begin */
export {
  default as LeftOutline
} from '@ant-design/icons/lib/outline/LeftOutline'
export {
  default as RightOutline
} from '@ant-design/icons/lib/outline/RightOutline'
export {
  default as MenuFoldOutline
} from '@ant-design/icons/lib/outline/MenuFoldOutline'
export {
  default as MenuUnfoldOutline
} from '@ant-design/icons/lib/outline/MenuUnfoldOutline'
export {
  default as DashboardOutline
} from '@ant-design/icons/lib/outline/DashboardOutline'
export {
  default as VideoCameraOutline
} from '@ant-design/icons/lib/outline/VideoCameraOutline'
export {
  default as LoadingOutline
} from '@ant-design/icons/lib/outline/LoadingOutline'
export {
  default as GlobalOutline
} from '@ant-design/icons/lib/outline/GlobalOutline'
export {
  default as UserOutline
} from '@ant-design/icons/lib/outline/UserOutline'
export {
  default as LogoutOutline
} from '@ant-design/icons/lib/outline/LogoutOutline'
/* Layout end */
复制代码

优化后已经低调了许多:

至此,项目打包已经获得了很大程度的优化,对比优化前,打包的整体积减少了约1/3,压缩后减少了约一半的体积,终于降到了KB级,可喜可贺:

优化前:

  • Stat: 10.54MB
  • Parsed: 4.94MB
  • Gzipped: 1.55MB

优化后:

  • Stat: 7.57MB
  • Parsed: 3.2MB
  • Gzipped: 1003.36KB
总结
  1. 对于首页不须要的模块,尽可能不要使用同步引用(import XX from '...')的方式引入到入口文件中,避免打包到一块儿,以减小首次请求的时间,加快首页的渲染速度;
  2. 使用第三方库的时候尽可能按需引入,若是有须要,可使用IgnorePlugin或ContextReplacementPlugin告诉webpack咱们须要/不须要打包的文件;
  3. 使用splitChunks提取公共模块,注意chunks这个属性的值,若是是在按需引入(import())的vue组件中使用同步引入的模块,chunks设置成initial是没用的。这也是为何vue-cli预设的splitChunks没有帮咱们把某些重复代码抽离出来,它只会帮咱们处理同步的模块:
// ...
optimization: {
    minimizer: [
      // ...
    ],
    splitChunks: {
      cacheGroups: {
        vendors: {
          name: 'chunk-vendors',
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          chunks: 'initial'
        },
        common: {
          name: 'chunk-common',
          minChunks: 1,
          priority: -20,
          chunks: 'initial',
          reuseExistingChunk: true
        }
      }
    }
}
复制代码

优化打包速度

使用speed-measure-webpack-plugin插件测量打包各环节耗费时间

vue-cli中的使用方法

// yarn add speed-measure-webpack-plugin --dev

// vue.config.js
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();

module.exports = {
  // 这里没法使用链式写法chainWebpack,会报错
  configureWebpack: smp.wrap({
    // ... webpack config goes here ...
  }
}
复制代码

运行打包指令:yarn build

使用dll提取不常更新的公共库

更新中...

相关文章
相关标签/搜索