看了不少打包优化的文章,不少都是基于原生的webpack配置,直接在webpack.config.js文件中修改配置的。可是vue-cli建立的项目已经封装了基本的webpack配置,须要在vue.config.js文件中修改预置的webpack配置。不多看到这方面的文章,所以记录一下本身的实践过程和踩过的一些坑。javascript
本次使用技术的版本状况:html
要优化项目,首先咱们得了解vue-cli已经替咱们作过了哪些优化,也就是须要查看webpack已经配置了哪些选项。
使用vue inpsect
输出webpack配置,还能够指定输出的文件:vue inspect > output.js
。vue
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官方提供一些插件分析打包性能。
git
咱们使用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使用三种指标衡量打包体积:
正则表达式
观察上图,能够发现moment占据了不小的比重,主要是一些本地化的语言包,默认都会打包进来。
对于普通应用来讲,咱们只须要中文语言包就够了。
优化前:
首先选择合适的语言包设置语言环境:
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/ // 这个参数表示咱们只想打包这个正则匹配的文件 ) ] }, }; 复制代码
优化后:
原来393.36KB的语言包只保留中文后变为仅有3.39KB。
关于moment打包,社区提供了不少方法,还有其余一些方案能够参考:github.com/jmblog/how-…
咱们项目中使用了这个库来生成excel并下载。
原来直接引入import { utils, writeFile } from 'xlsx';
,打包后体积很是庞大。
优化前:
后来在issue区查到了解决方案,改成只引入mini版本:
import { utils, writeFile } from 'xlsx/dist/xlsx.mini.min.js';
若是使用的是typescript会报错:
声明一下模块便可:
// modules.d.ts declare module 'xlsx/dist/xlsx.mini.min.js'; 复制代码
优化后几乎只剩了零头:
注意,官方解释这个xlsx这么大是有缘由的,由于涉及到读取文件,要支持一些比较老的格式。若是你的项目中只是用来生成excel,不涉及读取文件,就能够用这个mini版本;若是有涉及到读取excel文件的操做,仍是老老实实全量引入吧。官方将来或许会提供只支持现代文件格式的轻量级版本。
优化前:
须要使用两个插件:
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() ] } } 复制代码
优化后:
低调了许多,找了很久才找到 XD
咱们项目中使用了西瓜播放器,发现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的类型,有三个值:
因此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的难点在于,项目前期使用了两种方式:
这就致使了echarts全量引入,而且处处打包的问题。
解决方案:因为咱们的首页是登陆页,没有用到echarts,不须要第一次就加载echarts,所以要作两件事来优化:
// 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$/ }) ] 复制代码
优化后立马小了很多:
优化前:
发现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级,可喜可贺:
优化前:
优化后:
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 } } } } 复制代码
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
更新中...