浅谈Webpack打包优化技巧


Posted in Javascript onJune 12, 2018

前端的打包工具从之前的browserify、grunt、gulp到现如今的rollup、webpack,涌现出了很多优秀的打包工具,而目前最火的无疑是webpack,无论是当前热门的框架还是工具库很多都选择了它作为打包工具,因此在开发中webpack作为打包工具是一个很好的选择。在最近的项目开发中我也用到了webpack,其中也碰到了不少优化方面的问题,这里总结一下webpack打包优化的一些细节和方法。

首先,这次项目用到的是vue的全家桶,在webpack的配置方面直接用的是 vue-cli 生成的默认配置,项目打包完成后发现生成的 vendor.js 文件体积特别大,其次打包过程相当缓慢,因此想尝试各种方式对其进行优化。

定位体积大的模块

要想对打包体积进行优化,首先得找到体积大的模块,在这里我们可以使用webpack插件 webpack-bundle-analyzer 来查看整个项目的体积结构对比,它是以treemap的形式展现出来,很形象直观,还有一些具体的交互形式。既可以查看你项目中用到的所有依赖,也可以直观看到各个模块体积在整个项目中的占比。

浅谈Webpack打包优化技巧

该插件的使用方法可以直接通过 npm install webpack-bundle-analyzer --save-dev 安装,并在webpack的配置信息中的 plugins: [new BundleAnalyzerPlugin()] 中添加即可。对于 vue-cli 中的配置方式,默认是安装了该插件,但是没有启用,找到 config/index.js 文件在 build 下面会有 bundleAnalyzerReport 的配置,默认是 process.env.npm_config_report ,这里建议在 package.json 的 scripts 中添加一行 "analyz": "npm_config_report=true npm run build" ,这样每次想启用该插件时只需要 npm run analyze 即可。

提取公共模块

对于webpack,它在模块化打包上有两点是其核心功能,一是它支持大量的模块类型,无论是 TypeScript 、 CoffeeScript 还是 sass 、 stylus 等语言它都支持,二是它可以通过配置来控制打包文件的粒度,这个下面会讲到。

在开发中我们往往会将所有的依赖库单独提取出来,而不与我们的项目代码混在一起,这时我们会用到一个webpack自带的插件 CommonsChunkPlugin ,从名字上就可以看出它是一个提取公共模块的插件。从它的文档中可以看出可以传入一个对象最为参数,在使用中常用的三个参数分别为:

  1. name(names)
  2. minChunks
  3. chunks

name 好理解,指的就是最后打包文件的名字,而如果使用的是 names 的话,传入的必须是一个字符串数组。 minChunks 如果传入的是一个数字的话,指的是如果该模块被其他模块的引用次数达到了这个数值的话该模块就会被打包。如果传入的是一个函数的话,其返回值必须是布尔类型来指明这个模块是否应该被打包进公共模块。而 chunks 则会指定一个字符串数组,如果设置了该参数,则打包的时候只会从其中指定的模块中提取公共子模块。

下面通过几个实例来说明这个插件是如何工作的。

假设有两个模块 chunk1.js 和 chunk2.js 以及两个项目文件 a.js 和 b.js ,结构大致如下:

// a.js
require('./chunk1');
require('./chunk2');
require('jquery');
// b.js
require('./chunk1');
require('./chunk2');
require('vue');
// webpack配置如下
module.exports = {
 entry: {
  main: './main.js', 
  main1: './main1.js',    
  jquery:["jquery"],   
  vue:["vue"]  
 },  
 output: {   
  path: __dirname + '/dist',  
  filename: '[name].js' 
 },  
 plugins: [ 
  new CommonsChunkPlugin({  
   name: ["common","jquery","vue","load"],  
   minChunks:2 
  })  
 ] };

最终的打包结果是: jquery 被打包到 jquery.js , vue 被打包到 vue.js , common.js 打包的是公共模块(chunk1和chunk2)。使用该插件打包时,会将满足 minChunks 的模块打包到 name 数组的第一个块里,然后数组后面的依次打包,首先从 entry 中找,如果没有则产生一个空块。 name 数组中最后一个块打包的是webpack的runtime代码,在使用的时候必须先加载该块。

现在看一看 vue-cli 对于该插件的配置文件:

new webpack.optimize.CommonsChunkPlugin({
 name: 'vendor',
 minChunks: function (module, count) {
  // 将node_modules中的依赖模块全部提取到vendor文件中
  return (
   module.resource &&
   /\.js$/.test(module.resource) &&
   module.resource.indexOf(
    path.join(__dirname, '../node_modules')
   ) === 0
  )
 }
}),
// webpack在使用CommonsChunkPlugin时会生成一段runtime代码,并且打包进vendor中。
// 这样即使不改变vendor代码,每次打包时runtime会变化导致vendor的hash变化,这里
// 把独立的runtime代码抽离出来来解决这个问题
new webpack.optimize.CommonsChunkPlugin({
 name: 'manifest',
 chunks: ['vendor']
}),

移除不必要的文件

在项目中我通过方法一定位到几处体积占用较大的库,其中一个便是 moment.js 这个日期处理库。对于一个日期处理的功能,为何这个库会占用如此大的体积,仔细查看发现当引用这个库的时候,所有的 locale 文件都被引入,而这些文件甚至在整个库的体积中占了大部分,因此当webpack打包时移除这部分内容会让打包文件的体积有所减小。

webpack自带的两个库可以实现这个功能:

  1. IgnorePlugin
  2. ContextReplacementPlugin

IgnorePlugin 的使用方法如下:

// 插件配置
plugins: [
 // 忽略moment.js中所有的locale文件
 new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
],
// 使用方式
const moment = require('moment');
// 引入zh-cn locale文件
require('moment/locale/zh-cn');
moment.locale('zh-cn');

ContextReplacementPlugin 的使用方法如下:

// 插件配置
plugins: [
 // 只加载locale zh-cn文件
 new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn/),
],
// 使用方式
const moment = require('moment');
moment.locale('zh-cn');

通过以上两种方式, moment.js 的体积大致能缩减为原来的四分之一。

模块化引入

在项目中我使用了 lodash 这个很常用的工具库,然而在代码定位的时候发现这个库也占了不少的体积。仔细想想,我们在使用这类工具库的时候往往只使用到了其中的很少的一部分功能,但却把整个库都引入了。因此这里也可以进一步优化,只引用需要的部分。

import {chain, cloneDeep} from 'lodash';
// 可以改写为
import chain from 'lodash/chain';
import cloneDeep from 'lodash/cloneDeep';

这样就可以只打包我们需要的部分功能。

通过CDN引用

对于一些必要的库,但又无法对该库进行更好的体积优化的话,可以尝试通过外部引入的方式来减小打包文件的体积。采用该方法只需要在cdn站点找到需要引用的库的外部链接,以及对webpack进行简单配置即可。

// 在html中添加script引用
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
// 这里externals的key指的是使用时需要require的包名,value指的是该库通过script引入后在全局注册的变量名
externals: {
 jquery: 'jQuery'
}
// 使用方法
require('jquery')

通过 DLLPlugin 和 DLLReferencePlugin 拆分文件

如果项目过大,打包的时间会相当的长,如果频繁更新上线则会不断对代码进行编译打包,浪费很多时间。这时我们便可以将那些不常更新的框架和库(如vue.js等)进行单独的编译打包,这样每次开发上线就只需要对我们的开发文件进行编译打包,这样可以极大地省去不必要的打包时间。而这种方法需要 DLLPlugin DLLReferencePlugin 两个插件的配合来完成。

DLLPlugin

在使用这个插件时,我们需要单独创建一个配置文件,这里命名为 webpack.dll.config.js ,配置如下:

module.exports = {
 entry: {
  lib: ['vue', 'vuex', 'vue-resource', 'vue-router']
 },
 output: {
  path: path.resolve(__dirname, '../dist', 'dll'),
  filename: '[name].js',
  publicPath: process.env.NODE_ENV === 'production'
   ? config.build.assetsPublicPath
   : config.dev.assetsPublicPath,
  library: '[name]_library'
 },
 plugins: [
  new webpack.DefinePlugin({
   'process.env': '"production"'
  }),
  /**
   * path: manifest.json输出文件路径
   * name: dll对象名,跟output.library保持一致
   */ 
  new webpack.DllPlugin({
   context: __dirname,
   path: path.resolve(__dirname, '../dist/dll', 'lib.manifest.json'),
   name: '[name]_library'
  })
 ]
}

这里要注意几点:

  1. entry 中写明所有要单独打包的模块
  2. output 的 library 属性可以将dll包暴露出来
  3. DLLPlugin 的配置中, path 指明 manifest.json 文件的生成路径, name 暴露出dll的函数名

运行该配置文件便可生成打包文件和 manifest.json 文件。

DLLReferencePlugin

对于该插件的配置,不需要像上面一样单独写配置文件,只需要在生产配置文件中添加如下代码:

new webpack.DllReferencePlugin({    
 context: __dirname,         // 同dll配置的路径保持一致
 manifest: require('../dist/dll/lib.manifest.json') // manifest的位置
}),

然后运行webpack,发现打包的速度得到了极大地提升,也不用每次更新代码的时候重复编译打包这些依赖库了。

其他

对于webpack的打包优化我大致就总结了上面的一些方法,而为了让页面更快的加载,有更好的用户体验,我们并不只是从打包上优化,也可以有其他方面的优化,这里我也简单提一下我使用过的方法。

开启Gzip压缩

开启gzip压缩可以减少HTTP传输的数据量和时间,从而减少客户端请求的响应时间,由于降低了请求时间,页面的加载速度也会得到提升,会有更快的渲染速度,极大地改善了用户体验。由于现在基本上所有的主流浏览器都支持Gzip的压缩方式,只需要对服务器进行相关设置即可,这里就不具体讲如何配置服务器。

压缩混淆代码

我们平常也会对代码进行压缩混淆,可以通过 UglifyJS 等工具来对js代码进行压缩,同时可以去掉不必要的空格、注释、console信息等,也可以有效的减小代码体积。

总结

本文到这里就结束了,主要是对webpack的打包优化部分做了些讲解,当然能力和时间有限,只研究了部分方法,可能会有其他更多的优化方法,无论是从编译打包的体积还是速度上都能有更好的优化。接触了一段时间的webpack发现作为一个打包工具实在是过于复杂,无论从开始的官方文档还是到新的高级特性,都很难去完全掌握,还得需要自己不断去实践去深入研究才行。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript中的prototype属性实例分析说明
Aug 09 Javascript
修改jQuery Validation里默认的验证方法
Feb 14 Javascript
使用jQuery同时控制四张图片的伸缩实现代码
Apr 19 Javascript
js实现同一页面可多次调用的图片幻灯切换效果
Feb 28 Javascript
jQuery给元素添加样式的方法详解
Dec 30 Javascript
JavaScript对象_动力节点Java学院整理
Jun 23 Javascript
详解vue axios中文文档
Sep 12 Javascript
微信小程序支付之c#后台实现方法
Oct 19 Javascript
vue实现nav导航栏的方法
Dec 13 Javascript
vue 中 elment-ui table合并上下两行相同数据单元格
Dec 26 Javascript
Vue中this.$nextTick的作用及用法
Feb 04 Javascript
如何在vue 中使用柱状图 并自修改配置
Jan 21 Vue.js
关于TypeScript模块导入的那些事
Jun 12 #Javascript
JS实现前端页面的搜索功能
Jun 12 #Javascript
微信小程序实现弹出菜单功能
Jun 12 #Javascript
微信小程序实现折叠与展开文章功能
Jun 12 #Javascript
微信小程序收藏功能的实现代码
Jun 12 #Javascript
记一次webpack3升级webpack4的踩坑经历
Jun 12 #Javascript
webpack4之SplitChunksPlugin使用指南
Jun 12 #Javascript
You might like
php做下载文件的实现代码及文件名中乱码解决方法
2011/02/03 PHP
windows下配置php5.5开发环境及开发扩展
2014/12/25 PHP
PHP输出一个等腰三角形的方法
2015/05/12 PHP
windows8.1下Apache+Php+MySQL配置步骤
2015/10/30 PHP
javascript之函数直接量(function(){})()
2007/06/29 Javascript
jquery删除指定的html标签并保留标签内文本内容的方法
2014/04/02 Javascript
删除Javascript Object中间的key
2014/11/18 Javascript
基于zepto.js实现仿手机QQ空间的大图查看组件ImageView.js详解
2015/03/05 Javascript
基于jQuery实现文本框只能输入数字(小数、整数)
2016/01/14 Javascript
完美JQuery图片切换效果的简单实现
2016/07/21 Javascript
js实现3d悬浮效果
2017/02/16 Javascript
利用node.js写一个爬取知乎妹纸图的小爬虫
2017/05/03 Javascript
关于javascript作用域的常见面试题分享
2017/06/18 Javascript
Vue.js实现按钮的动态绑定效果及实现代码
2017/08/21 Javascript
javascript获取指定区间范围随机数的方法
2017/09/08 Javascript
vue2.0 自定义 饼状图 (Echarts)组件的方法
2018/03/02 Javascript
vue使用vue-i18n实现国际化的实现代码
2018/04/08 Javascript
自定义vue组件发布到npm的方法
2018/05/09 Javascript
vuex actions传递多参数的处理方法
2018/09/18 Javascript
vue实现随机验证码功能(完整代码)
2019/12/10 Javascript
js实现贪吃蛇小游戏(加墙)
2020/07/31 Javascript
基于postman获取动态数据过程详解
2020/09/08 Javascript
jQuery使用hide()、toggle()函数实现相机品牌展示隐藏功能
2021/01/29 jQuery
Python读取stdin方法实例
2019/05/24 Python
Python Tornado之跨域请求与Options请求方式
2020/03/28 Python
python pymysql链接数据库查询结果转为Dataframe实例
2020/06/05 Python
python获取linux系统信息的三种方法
2020/10/14 Python
Python利用myqr库创建自己的二维码
2020/11/24 Python
阿里巴巴Oracle DBA笔试题答案-备份恢复类
2013/11/20 面试题
什么是GWT的Entry Point
2013/08/16 面试题
费用会计岗位职责
2014/01/01 职场文书
面试自我评价范文
2014/09/17 职场文书
2014年大学生党员自我评议
2014/09/22 职场文书
解除同居协议书
2015/01/29 职场文书
订货会主持词
2015/07/01 职场文书
anaconda python3.8安装后降级
2021/06/11 Python