浅谈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 相关文章推荐
使用jQuery简化Ajax开发 Ajax开发入门
Oct 14 Javascript
jQuery创建自己的插件(自定义插件)的方法
Jun 10 Javascript
Raphael一个用于在网页中绘制矢量图形的Javascript库
Jan 08 Javascript
使用js实现关闭js弹出层的窗口
Feb 10 Javascript
JS 在指定数组中随机取出N个不重复的数据
Jun 10 Javascript
JavaScript使表单中的内容显示在屏幕上的方法
Jun 29 Javascript
JS常用算法实现代码
Nov 14 Javascript
React-router 4 按需加载的实现方式及原理详解
May 25 Javascript
Angular4的输入属性与输出属性实例详解
Nov 29 Javascript
通过jQuery学习js类型判断的技巧
May 27 jQuery
js实现九宫格抽奖
Mar 19 Javascript
Vue之封装公用变量以及实现方式
Jul 31 Javascript
关于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访问查询mysql数据的三种方法
2006/10/09 PHP
PHP 实现判断用户是否手机访问
2015/01/21 PHP
PHP Imagick完美实现图片裁切、生成缩略图、添加水印
2016/02/22 PHP
ThinkPHP3.2框架使用addAll()批量插入数据的方法
2017/03/16 PHP
你需要知道的JavsScript可以做什么?
2007/06/29 Javascript
javascript获取checkbox复选框获取选中的选项
2014/08/12 Javascript
浅谈javascript的调试
2015/01/28 Javascript
纯JS前端实现分页代码
2016/06/21 Javascript
快速解决js动态改变dom元素属性后页面及时渲染的问题
2016/07/06 Javascript
Js动态设置rem来实现移动端字体的自适应代码
2016/10/14 Javascript
微信小程序入门教程
2016/11/18 Javascript
nodejs进阶(6)—连接MySQL数据库示例
2017/01/07 NodeJs
利用Query+bootstrap和js两种方式实现日期选择器
2017/01/10 Javascript
vue2.0数据双向绑定与表单bootstrap+vue组件
2017/02/27 Javascript
Vue2.0组件间数据传递示例
2017/03/07 Javascript
详解Node中导入模块require和import的区别
2017/08/11 Javascript
基于Axios 常用的请求方法别名(详解)
2018/03/13 Javascript
个人小程序接入支付解决方案
2019/05/23 Javascript
JS数组方法join()用法实例分析
2020/01/18 Javascript
JavaScript, select标签元素左右移动功能实现
2020/05/14 Javascript
[00:59]DOTA2英雄背景故事——上古巨神
2020/06/28 DOTA
使用Python抓取豆瓣影评数据的方法
2018/10/17 Python
深入了解python中元类的相关知识
2019/08/29 Python
python实现将json多行数据传入到mysql中使用
2019/12/31 Python
python使用布隆过滤器的实现示例
2020/08/20 Python
python空元组在all中返回结果详解
2020/12/15 Python
Python读取pdf表格写入excel的方法
2021/01/22 Python
Schecker荷兰:狗狗用品和配件
2019/06/06 全球购物
Mamaearth官方网站:印度母婴护理产品公司
2019/10/06 全球购物
英语系毕业生自荐信
2013/10/31 职场文书
质检员的岗位职责
2013/11/15 职场文书
优秀教师事迹简介
2014/02/02 职场文书
先进教育工作者事迹材料
2014/12/23 职场文书
新郎婚礼致辞
2015/07/27 职场文书
PostgreSQL13基于流复制搭建后备服务器的方法
2022/01/18 PostgreSQL
oracle delete误删除表数据后如何恢复
2022/06/28 Oracle