webpack进阶——缓存与独立打包的用法


Posted in Javascript onAugust 02, 2017

本文介绍了webpack进阶——缓存与独立打包的用法,分享给大家,希望对大家有帮助

先来看看最基础的webpack配置:

var path = require('path');
 
module.exports = {
 entry: './src/index.js',
 output: {
  filename: 'bundle.js',
  path: path.resolve(__dirname, 'dist')
 }
}

在index.js中引入了lodash库:

src/index.js:

import _ from 'lodash';
 
 function component() {
  var element = document.createElement('div');
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');
 
  return element;
 }
 
 document.body.appendChild(component());

打包之后,只会生成一个bundle.js,这样的话,每次若要加载资源文件,浏览器都会加载根本不会改动的lodash库,这样很低效。

由于如果每次去访问浏览器,浏览器都重新下载资源,由于网络获取资源可能很慢,可能页面久久加载不出来,低效且不友好,故浏览器会缓存资源,以避免每次访问都通过网络去获取资源。

但是,由于浏览器缓存,又会出现新的问题,如果我们部署版本时不更改资源的文件名,浏览器可能认为它没有更新,就会使用它的缓存版本。

这样我们就需要解决两个问题:第一,分离打包文件。第二,解决缓存问题。

const path = require('path');
const webpack = require('webpack');
 
module.exports = {
 entry: {
  common: ['lodash'],
  app: './src/index.js'
 },
 output: {
  filename: '[name].[hash].js',
  path: path.resolve(__dirname, 'dist')
 },
 plugins: [
  new webpack.optimize.CommonsChunkPlugin({
   name: 'common' // 指代index.js引入的lodash库
  })
 ]
}

主要变动:

  • 添加插件:CommonsChunkPlugin,提取引入的库,并且更名,实现代码分离。
  • 输出上在名字上加了hash,每次打包后,hash值都不一样解决了浏览器缓存的问题。

结果:index.js打包为app.[hash].js,index.js引入的lodash打包为common.[hash].js。这样解决了浏览器缓存问题和实现了静态资源代码和源代码的分离,但是新的问题又出现了。

第一次打包后(注意Asset列下的名字):

webpack进阶——缓存与独立打包的用法

每次我们修改源代码时,再次打包,不仅仅index生成app.[hash].js的hash值发生了变化,

webpack进阶——缓存与独立打包的用法

而且common.[hash].js的hash值与app的hash值相同也发生了变化(可以自行测试一下,先webpack打包一次,修改index.js后再次打包一次)。

这并不是我们想要的结果,虽然源代码hash改变解决了浏览器使用缓存版本的问题,但是,如果common.js的hash值也一同发生了变化的话,那么浏览器也还需要每次都请求不会发生改变的静态代码common,这样还是浪费了网络资源,很低效。

注:本案例会多次打包,dist目录中会生成过多垃圾文件,在实际使用中都使用了CleanWebpackPlugin插件。

new CleanWebpackPlugin(['dist']) // 加入在插件数组中,用于在每次打包前,都清空打包文件夹下之前打包的文件。

如果修改了index,仅仅只是生成的app的hash值发生变化,而common的hash值不发生变化,那就能够达到我们的目的,既能缓存库又能识别源文件的更改。
我们进行如下配置: output中将 [name].[hash].js 改为[name].[chunkhash].js ,让每个文件生成唯一的hash值:

const path = require('path');
const webpack = require('webpack');
 
module.exports = {
 entry: {
  common: ['lodash'],
  app: './src/index.js'
 },
 output: {
  filename: '[name].[chunkhash].js',
  path: path.resolve(__dirname, 'dist')
 },
 plugins: [
  new CleanWebpackPlugin(['dist']),
  new webpack.optimize.CommonsChunkPlugin({
   name: 'common' // 指代index.js引入的lodash库
  })
 ]
}

(注意:不要在开发环境下使用 [chunkhash],因为这会增加编译时间。将开发和生产模式的配置分开,并在开发模式中使用 [name].js 的文件名,在生产模式中使用 [name].[chunkhash].js 文件名,所以如果这个时候使用了热替换插HotModuleReplacementPlugin,将会导致编译不成功!)

我们配置好之后,进行webpack打包:

webpack进阶——缓存与独立打包的用法

chunkhash是根据文件内容生成的hash,可见app与common生成的hash值不相同了(对比使用 [name].[hash].js打包)。

我们在index.js中随便进行修改,再次打包:

webpack进阶——缓存与独立打包的用法

奇怪的是,虽然common与app生成了单独的hash值,但是修改了index.js,common的hash值还是发生了变化。

原因是:为了最小化生成的文件大小,webpack使用标识符而不是模块名称,在编译期间生成标识符,并映射到块文件名,然后放入一个名为chunk manifest的JS对象中。重点就在于!!当我们使用CommonsChunkPlugin分离代码时,被分离出来的代码(本文中的lodash库,被打包为common。),会默认被移动到entry中最后一个入口进行打包(第一个入口是index.js)。重要的是,chunk manifest将随着这些被分离出来的代码共同打包!!!

由于我们更改源代码后,不但会更新app的hash值,还会生成新的映射,然后新的映射又会和资源代码一同打包,又由于chunkhash是根据内容生成hash的,那么加入了新的映射对象chunk manifest的资源代码被打包后,hash自然也会发生改变。这反过来,产生的新hash将使长效缓存失效。

那么接下来我们需要做的就是讲 manifest分离出来。这里我们利用一个CommonsChunkPlugin一个较少有人知道的功能,能够在每次修改后的构建中将manifest提取出来,通过指定entry中未用到的名称,此插件会自动将我们需要的内容提取到单独的包中。

故再额外配置一个CommonsChunkPlugin:

const path = require('path');
const webpack = require('webpack');
 
module.exports = {
 entry: {
  common: ['lodash'],
  app: './src/index.js'
 },
 output: {
  filename: '[name].[chunkhash].js',
  path: path.resolve(__dirname, 'dist')
 },
 plugins: [
  new CleanWebpackPlugin(['dist']),
  new webpack.optimize.CommonsChunkPlugin({
   name: 'common' // 指代index.js引入的lodash库
  }),
  new webpack.optimize.CommonsChunkPlugin({
   name: 'manifest' // 用于提取manifest
  })
 ]
}

webpack打包后:

webpack进阶——缓存与独立打包的用法

从这里可以证明之前所说的manifest被打包进了common!!!仔细看之前的图:common的Size都是547kb,到这里common大小是541kb 而manifest大小正好为5.85kb,加起来正好为547kb。

然后我们修改index.js再次打包:

webpack进阶——缓存与独立打包的用法

从这里可以发现!!我们修改了源代码,common的hash值已经不再发生改变了!到这里可以达到我们不缓存源代码缓存资源文件的目的了。

但是可别高兴得太早!!我们做了一个很小的修改,交换了entry中 app 和 common的顺序(对比上一个代码段):

const path = require('path');
const webpack = require('webpack');
 
module.exports = {
 entry: {
  app: './src/index.js',
  common: ['lodash']
 },
 output: {
  filename: '[name].[chunkhash].js',
  path: path.resolve(__dirname, 'dist')
 },
 plugins: [
  new CleanWebpackPlugin(['dist']),
  new webpack.optimize.CommonsChunkPlugin({
   name: 'common' // 指代index.js引入的lodash库
  }),
  new webpack.optimize.CommonsChunkPlugin({
   name: 'manifest' // 用于提取manifest
  })
 ]
}

打包后:

webpack进阶——缓存与独立打包的用法

这里发现对比上一张图片发现,common的hash值又发生改变了!!而且根本没有更改index.js的内容app的hash也变了,只是换了一下顺序而已!

大家注意看本张图与上一张图的模块解析顺序([1],[2],[3]...之后所对应的模块)。发现上一张图,lodash第一个解析,而现在lodash最后一个解析。

这就是hash更变的原因:这是因为每个module.id 会基于默认的解析顺序(resolve order)进行增量。也就是说,当解析顺序发生变化,ID 也会随之改变,所以hash值也会发生变化。

有人可能会决定,一般我们都不会更换webpack.config.js中entry的入口顺序,那么是否我就不会遇见这个问题了。答案是否定的,除否你能保证资源文件都写在entry的顶部。否则会出现这样的情况:

假如entry的顺序为: app -> common, 那么解析顺序为 index.js → lodash。 如果之后index.js引入了 print.js,那么解析顺序变为 index.js → print.js -> lodash。

以上,我们并没有在entry中更改入口顺序,解析的顺序还是会发生改变,common的hash还是会发生,不能缓存。

这里我们就引入一个新的组件:HashedModuleIdsPlugin:根据hash生成ID(NamedModulesPlugin也具有同样的效果,但是是根据路径名生成ID,可读性更高,也由此编译时间会相对长一些)。 这样module.id就不会使用数字标识符,而使用hash:

const path = require('path');
const webpack = require('webpack');
 
module.exports = {
 entry: {
  common: ['lodash'],
  app: './src/index.js'
 },
 output: {
  filename: '[name].[chunkhash].js',
  path: path.resolve(__dirname, 'dist')
 },
 plugins: [
  new CleanWebpackPlugin(['dist']),
  new webpack.HashedModuleIdsPlugin(), // 引入该插件
  new webpack.optimize.CommonsChunkPlugin({
   name: 'common' // 指代index.js引入的lodash库
  }),
  new webpack.optimize.CommonsChunkPlugin({
   name: 'manifest' // 用于提取manifest
  })
 ]
}

打包发现,之前[ ]里都是数字,现在都是一些字符,

webpack进阶——缓存与独立打包的用法

接下来,我们再把app和common的顺序调换一下,并且随意修改index.js,再次打包:

webpack进阶——缓存与独立打包的用法

现在大功告成,common的hash没有改变,而因为更变了内容app的hash改变了,这正是我们想要的结果。

参考资料:

webpack文档 -- 缓存

webpack独立打包与缓存处理

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

Javascript 相关文章推荐
Javascript 继承机制实例
Aug 12 Javascript
JS 类型转换常见方法小结
May 31 Javascript
js中符号转意问题示例探讨
Aug 19 Javascript
javascript实现很浪漫的气泡冒出特效
Sep 05 Javascript
JQuery Mobile 弹出式登录框的实现方法
May 28 Javascript
JavaScript中的冒泡排序法
Aug 03 Javascript
jQuery简单自定义图片轮播插件及用法示例
Nov 21 Javascript
jQuery插件HighCharts绘制2D半圆环图效果示例【附demo源码下载】
Mar 09 Javascript
vue 2.0组件与v-model详解
Mar 27 Javascript
基于rem的移动端响应式适配方案(详解)
Jul 07 Javascript
基于Vue中点击组件外关闭组件的实现方法
Mar 06 Javascript
在Webpack中用url-loader处理图片和字体的问题
Apr 28 Javascript
使用vue构建移动应用实战代码
Aug 02 #Javascript
React Native之ListView实现九宫格效果的示例
Aug 02 #Javascript
jQuery初级教程之网站品牌列表效果
Aug 02 #jQuery
ReactNative列表ListView的用法
Aug 02 #Javascript
ReactNative页面跳转Navigator实现的示例代码
Aug 02 #Javascript
利用JavaScript实现栈的数据结构示例代码
Aug 02 #Javascript
easyui-datagrid开发实践(总结)
Aug 02 #Javascript
You might like
世界第一个无线广播电台 KDKA
2021/03/01 无线电
php删除指定目录的方法
2015/04/03 PHP
PHP基于自定义类随机生成姓名的方法示例
2017/08/05 PHP
js 分栏效果实现代码
2009/08/29 Javascript
jsTree树控件(基于jQuery, 超强悍)[推荐]
2009/09/01 Javascript
js自执行函数的几种不同写法的比较
2012/08/16 Javascript
用js+iframe形成页面的一种遮罩效果的具体实现
2013/12/31 Javascript
JavaScript获取各大浏览器信息图示
2015/11/20 Javascript
JS中call/apply、arguments、undefined/null方法详解
2016/02/15 Javascript
JQuery ztree 异步加载实例讲解
2016/02/25 Javascript
jquery ztree异步搜索(搜叶子)实践
2016/02/25 Javascript
bootstrap-wysiwyg结合ajax实现图片上传实时刷新功能
2016/05/27 Javascript
Angularjs+bootstrap+table多选(全选)支持单击行选中实现编辑、删除功能
2017/03/27 Javascript
JavaScript实现实时更新系统时间的实例代码
2017/04/04 Javascript
Vue2仿淘宝实现省市区三级联动
2020/04/15 Javascript
vue+moment实现倒计时效果
2019/08/26 Javascript
[04:52]DOTA2亚洲邀请赛附加赛 TOP10精彩集锦
2015/01/29 DOTA
更改Python命令行交互提示符的方法
2015/01/14 Python
Python中AND、OR的一个使用小技巧
2015/02/18 Python
Python如何判断数独是否合法
2016/09/08 Python
python将字典内容存入mysql实例代码
2018/01/18 Python
python pandas中对Series数据进行轴向连接的实例
2018/06/08 Python
python获取指定字符串中重复模式最高的字符串方法
2018/06/29 Python
Django框架封装外部函数示例
2019/05/28 Python
如何运行带参数的python脚本
2019/11/15 Python
python推导式的使用方法实例
2021/02/28 Python
迪拜航空官方网站:flydubai
2017/04/20 全球购物
波兰最大的度假胜地和城市公寓租赁运营商:Sun & Snow
2018/10/18 全球购物
花店创业计划书范文
2014/02/07 职场文书
优秀语文教师事迹
2014/05/18 职场文书
2015年话务员工作总结
2015/04/29 职场文书
房地产项目合作意向书
2015/05/08 职场文书
常用的Python代码调试工具总结
2021/06/23 Python
解析python中的jsonpath 提取器
2022/01/18 Python
Nginx禁止ip访问或非法域名访问
2022/04/07 Servers
golang的文件创建及读写操作
2022/04/14 Golang