webpack学习笔记之优化缓存、合并、懒加载


Posted in Javascript onAugust 24, 2017

除了的webpack基本配置,还可以进一步添加配置,优化合并文件,加快编译速度。下面是生产环境配置文件webpack.production.js,与wenbpack.config.js相比其不需要一些dev-tools,dev-server和jshint校验等,将与开发相关的东西删掉。下面的介绍均以此代码配置作参考。

/*生成环境配置文件:不需要一些dev-tools,dev-server和jshint校验等。和开发有关的东西删掉*/
var webpack = require('webpack');
var path = require('path');
var node_modules = path.resolve(__dirname, 'node_modules');
var pathToReact = path.resolve(node_modules, 'react/dist/react.min.js');
var pathToReactDOM = path.resolve(node_modules, 'react-dom/dist/react-dom.min.js');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
//具体作用及缺点见plugins中的描述
//var WebpackMd5Hash = require('webpack-md5-hash');
// 该插件是对“webpack-md5-hash”的改进:在主文件中获取到各异步模块的hash值,然后将这些hash值与主文件的代码内容一同作为计算hash的参数,这样就能保证主文件的hash值会跟随异步模块的修改而修改。
var WebpackSplitHash = require('webpack-split-hash');

var config = {
  entry:{
    app:path.resolve(__dirname, 'src/js/entry.js'),
    mobile: path.resolve(__dirname, 'src/js/mobile.js'),
    //添加要打包在vendors.js里面的库
    vendors:['react','react-dom']
  },
  resolve:{
    alias: {
      'react.js': pathToReact, //alias:别名,
      'react-dom.js': pathToReactDOM
    },
    fallback: path.join(__dirname, "node_modules")
  },
  resolveLoader: { 
    fallback: path.join(__dirname, "node_modules") 
  },
  output:{
    path:path.resolve(__dirname, 'dist'),
    publicPath:'../',//生成的html里的引用路径用 publicPath
    //以文件内容的MD5生成Hash名称的script来防止缓存
    filename: 'js/[name].[chunkhash:8].js',
    //异步加载的模块是要以文件形式加载,生成的文件名是以chunkFilename配置的
    chunkFilename: 'js/[name].[chunkhash:8].js'
  },
  module:{
    loaders:[{
      test: /\.jsx?$/,
      //这里(node_modules文件夹)再也不需通过任何第三方来加载
      exclude:path.resolve(__dirname, 'node_modules'),
      loader: 'babel',
      query:{
        presets:['es2015', 'react']
      }
    },
    {
      test:/\.css$/,
      //loader:'style!css'
      loader: ExtractTextPlugin.extract("style", "css")
    },
    {
      test:/\.scss$/,
      loader:'style!css!sass'
    },
    //url-loader:图片、字体图标加载器,是对file-loader的上层封装,支持base64编码。传入的size(也有的写limit) 参数是告诉它图片如果不大于 25KB 的话要自动在它从属的 css 文件中转成 BASE64 字符串。
    {
      test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$/,
      loader: 'url?limit=25000&name=[name].[ext]'
    }]
  },
  plugins:[
    //提取公共代码的插件,用于提取多个入口文件的公共脚本部分,然后生成一个vendors.js。注意HTML代码中要加载这个公共文件
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendors',
      filename: 'js/vendors.js'
    }),
    //在文件开头插入banner
    new webpack.BannerPlugin("The file is creted by yangmin.--"+ new Date()),
    //压缩js文件
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    }),
    /*插件:单独使用style标签加载css文件.contenthash代表的是文本文件内容的hash值,也就是只有style文件的hash值*/
    new ExtractTextPlugin("css/[name].[contenthash:8].css"),//设置其路径(路径相对于path)
    /*插件:动态生成html,在webpack完成前端资源打包以后,自动将打包后的资源路径和版本号写入HTML中,达到自动化的效果。*/
    new HtmlWebpackPlugin({
      filename:'view/index.html',  //生成的html存放路径,相对于 path
      template:'src/view/index.html',  //html模板路径
      inject:true,  //允许插件修改哪些内容,true/'head'/'body'/false,
      chunks:['vendors','app'],//加载指定模块中的文件,否则页面会加载所有文件
      hash:false,  //为静态资源生成hash值
      minify:{  //压缩HTML文件
        removeComments:false,  //移除HTML中的注释
        collapseWhitespace:false  //删除空白符与换行符
      }    
    }),
    new HtmlWebpackPlugin({
      filename:'view/mobile.html',  //生成的html存放路径,相对于 path
      template:'src/view/mobile.html',  //html模板路径
      inject:true,  //允许插件修改哪些内容,true/'head'/'body'/false,
      chunks:['vendors','mobile'],//加载指定模块中的文件,否则页面会加载所欲文件
      hash:false,  //为静态资源生成hash值
      minify:{  //压缩HTML文件
        removeComments:false,  //移除HTML中的注释
        collapseWhitespace:false  //删除空白符与换行符
      }
    }),
    /*作用:生成具有独立hash值的css和js文件,即css和js文件hash值解耦.
     *缺点:webpack-md5-hash插件对chunk-hash钩子进行捕获并重新计算chunkhash,它的计算方法是只计算模块本身的当前内容(包括同步模块)。这种计算方式把异步模块的内容忽略掉了,会造成一个问题:异步模块的修改并未影响主文件的hash值。
     */
    //new WebpackMd5Hash()
    new WebpackSplitHash()
  ]
}
 module.exports = config;

一、在开发环境中使用压缩文件

例如ReactJS项目中为了不让 Webpack 去遍历 React JS 及其所有依赖,你可以在webpack.config.js中重写它的行为。

config.alias: 每当 "react" 在代码中被引入,它会使用压缩后的 React JS 文件。

noParse: 阻止Webpack 去解析那个压缩后的文件。

当加载多个压缩文件时,下述方法更优雅简便,webpack.production.js:

var webpack = require("webpack");
...
var HtmlWebpackPlugin = require('html-webpack-plugin');

var deps = [
  'react/dist/react.min.js',
  'react-dom/dist/react-dom.min.js'
];
var config = {
  ...
  resolve:{
    alias:{},
    fallback:path.join(__dirname, "node_modules")
  },
  ...
  module:{
     ...
     noParse:[]  
   }  
}
/*当加载多个压缩文件时,下述方法更优雅简便*/
deps.forEach(function(dep){  
  var depPath = path.resolve(node_modules, dep);
  //path.dep是路径分隔符。
  config.resolve.alias[dep.split(path.dep)[0]] = depPath;  
  config.module.noParse.push(depPath);

});

module.exports = config;

二、分离应用和第三方文件

当你的应用依赖其他库尤其是像 React JS 这种大型库的时候,需要考虑把这些依赖分离出去,这样就能够让用户在你更新应用之后不需要再次下载第三方文件。上述配置文件中的entry里添加了第三方包vendors,其值为要分离打包的文件。运行配置后会在dist/js下生成三个独立文件:app.js、mobile.js、vendors.js。注意在页面中药引入vendors.js

<script src="../dist/vendors.js"></script>
<script src="../dist/app.js"></script>

三、多重入口

当应用有多个页面, 页面之间虽然有共享代码,但是不想在页面中加载所有代码时可以定义多重入口。例如配置文件中的app.js针对pc端页面,mobile.js仅针对移动端页面,output的filename:'js/[name].[chunkhash:8].js',采用了文件名变量,这样在dist/js中可生成与源文件同名的文件。

四、优化缓存及懒加载

在生产环境中,将输出文件名添加hash值,目的是在文件更改时强制客户端重新加载这个文件,而未改变的文件继续使用缓存文件。常用的有hash和chunkhash。配置文件中的[chunkhash:8]即截取8位chunkhash值。

webpack的编译理念:webpack将style视为js的一部分,所以在计算chunkhash时,会把所有的js代码和style代码混合在一起计算。比如entry.js引用了main.css:

import 'main.css'; 
alert('I am main.js');

webpack计算chunkhash时,以entry.js文件为编译入口,整个chunk的内容会将main.css的内容也计算在内。所以,不论是修改了js代码还是css代码,整个chunk的内容都改变了,计算所得的chunkhash随之改变。但理想情况下是想css或js内容改变时仅影响自身文件的chunkhash,这样客户端只需更新一部分文件。解决此问题首先要将css单独编译输出文件,因为正常情况下webpack会把js文件中引入的css文件编译输出到html页面的<style></style>标签中。

1.使用extract-text-webpack-plugin单独编译输出css文件

安装extract-text-webpack-plugin,

npm install extract-text-webpack-plugin --save-dev

然后在配置文件中引入插件,

//webpack.production.config.js
var ExtractTextPlugin = require('extract-text-webpack-plugin');

该插件除了chunkhash还提供了另外一种hash值contenthash。顾名思义,contenthash代表的是文本文件内容的hash值,也就是只有style文件的hash值。此hash是可解决上述问题的关键所在。上述配置文件使用了contenthash:

//webpack.production.config.js
new ExtractTextPlugin("css/[name].[contenthash:8].css"),//设置其路径(路径相对于path)

2.使用使用webpack-md5-hash解耦css和js文件hash值

再考虑以下情况,只修改了main.css文件,未修改entry.js文件,编译输出的js文件hash是否改变?答案是改变了,因为上文提到的webpack的编译理念,webpack将style视为js的一部分,所以在计算chunkhash时,会把所有的js代码和style代码混合在一起计算。解决办法是使用webpack-md5-hash插件:

//webpack.production.config.js

var WebpackMd5Hash = require('webpack-md5-hash');
...
new WebpackMd5Hash();

它的作用是生成具有独立hash值的css和js文件,即css和js文件hash值解耦。webpack-md5-hash插件对chunk-hash钩子进行捕获并重新计算chunkhash,它的计算方法是只计算模块本身的当前内容(包括同步模块)。

3.主文件使用hash代替chunkhash解决异步加载模块改变时主文件hash不改变

假如文件中引入了异步模块,异步模块修改后会影响编译输出的js文件的chunkhash吗?现在入口文件中引入异步模块a.js,a.js文件又异步引入b.js,b.js同步引入c模块

//entry.js

'use strict';

import './saveCarInfo.js';

window.onload = function(){//懒加载
  require.ensure(['./a.js'],function(require){
    var moduleA = require('./a.js');
  },'a');
};
//a.js

'use strict'

console.log("a");

setTimeout(function(){
  require.ensure([],function(require){
    require('./b.js');
  },'b');
},10000);

module.exports = "moduleA";
//b.js
import fn_c from './c.js';

console.log('b');
module.exports = 'moduleB';
//c.js

console.log("c");
module.exports = "moduleC";

运行npm run deploy,编译输出如下,我们看到除了入口文件、css文件、html文件被输出外,异步加载的模块a.js、b.js也被当做独立模块输出。

webpack学习笔记之优化缓存、合并、懒加载

此时修改a.js文件中的代码,经编译后,a.[chunkhash].js的chunkhash会改变,而生成的主文件app.[chunkhash].js的chunkhash值并没有改变。原因是webpack-md5-hash的这种计算方式把异步模块的内容忽略掉了,这会造成一个问题:异步模块的修改并未影响主文件的chunkhash值。解决办法是将输出的主文件采用[hash],而非[chunkhash]

output:{
  path:path.resolve(__dirname, 'dist'),
  publicPath:'../',//生成的html里的引用路径用 publicPath
  filename: 'js/[name].[hash:8].js',
  //异步加载的模块是要以文件形式加载,生成的文件名是以chunkFilename配置的
  chunkFilename: 'js/[name].[chunkhash:8].js'
},

这种做法也存在缺陷,如果项目中存在不止一个主js文件,修改任意js代码会影响所有最终主文件的[hash]值。例如上面的项目配置中会生成两个带[hash]的主文件:app.[hash].js, mobile.[hash].js。无论是修改entry.js代码还是异步模块a.js,或b.js的代码,app.[hash].js和mobile.[hash].js的[hash]都会改变。

补充:npm提供了webpack-split-hash插件代替webpack-md5-hash,该插件可以获取到各异步模块的hash值,然后将这些hash值与主文件的代码内容一同作为计算hash的参数,这样就能保证主文件的hash值会跟随异步模块的修改而修改。但我验证后没有实现。。。

4.使用html-webpack-plugin动态生成html

配置文件中的输出文件都带了[chunkhash]作为版本号,在style或js文件改变时,其值都会随之改变。利用html-webpack-plugin在webpack完成前端资源打包以后,自动将打包后的资源路径和版本号写入HTML中,达到自动化的效果。

//webpack.production.config.js

var HtmlWebpackPlugin = require('html-webpack-plugin');
...
var config = {
...
plugins:[
... 
  new HtmlWebpackPlugin({
    filename:'view/index.html',  //生成的html存放路径,相对于 path
    template:'src/view/index.html',  //html模板路径
    inject:true,  //允许插件修改哪些内容,true/'head'/'body'/false,
    chunks:['vendors','app'],//加载指定模块中的文件,否则页面会加载所有文件
    hash:false,  //为静态资源生成hash值
    minify:{  //压缩HTML文件
      removeComments:false,  //移除HTML中的注释
      collapseWhitespace:false  //删除空白符与换行符
     }    
  }),
  new HtmlWebpackPlugin({
    filename:'view/mobile.html',  //生成的html存放路径,相对于 path
    template:'src/view/mobile.html',  //html模板路径
    inject:true,  //允许插件修改哪些内容,true/'head'/'body'/false,
    chunks:['vendors','mobile'],//加载指定模块中的文件,否则页面会加载所有文件
    hash:false,  //为静态资源生成hash值
    minify:{  //压缩HTML文件
      removeComments:false,  //移除HTML中的注释
      collapseWhitespace:false  //删除空白符与换行符
     }    
  })
]}

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

Javascript 相关文章推荐
jQuery News Ticker 基于jQuery的即时新闻行情展示插件
Nov 05 Javascript
做好七件事帮你提升jQuery的性能
Feb 06 Javascript
js获取元素相对窗口位置的实现代码
Sep 28 Javascript
快速解决Canvas.toDataURL 图片跨域的问题
May 10 Javascript
DIV+CSS+jQ实现省市联动可扩展
Jun 22 Javascript
js友好的时间返回函数
Aug 24 Javascript
浅谈原生JS实现jQuery的animate()动画示例
Mar 08 Javascript
加载 vue 远程代码的组件实例详解
Nov 20 Javascript
React如何避免重渲染
Apr 10 Javascript
vue项目中,main.js,App.vue,index.html的调用方法
Sep 20 Javascript
JQuery实现折叠式菜单的详细代码
Jun 03 jQuery
VUE中的v-if与v-show区别介绍
Mar 13 Vue.js
基于JavaScript实现新增内容滚动播放效果附完整代码
Aug 24 #Javascript
快速理解 JavaScript 中的 LHS 和 RHS 查询的用法
Aug 24 #Javascript
vue不通过路由直接获取url中参数的方法示例
Aug 24 #Javascript
VueJS 集成 Medium Editor的示例代码 (自定义编辑器按钮)
Aug 24 #Javascript
JS实现电商放大镜效果
Aug 24 #Javascript
weui框架实现上传、预览和删除图片功能代码
Aug 24 #Javascript
jQuery Datatable 多个查询条件自定义提交事件(推荐)
Aug 24 #jQuery
You might like
异世界新番又来了,同样是从零开始,男主的年龄降到5岁
2020/04/09 日漫
PHP 七大优势分析
2009/06/23 PHP
Laravel框架源码解析之模型Model原理与用法解析
2020/05/14 PHP
如何实现JS函数的重载
2006/09/22 Javascript
JavaScript中的new的使用方法与注意事项
2007/05/16 Javascript
通过修改referer下载文件的方法
2008/05/11 Javascript
Jquery 1.42 checkbox 全选和反选代码
2010/03/27 Javascript
javascript 防止刷新,后退,关闭
2010/08/07 Javascript
node.js实现逐行读取文件内容的代码
2014/06/27 Javascript
JavaScript中获取样式的原生方法小结
2014/10/08 Javascript
node.js中的socket.io的广播消息
2014/12/15 Javascript
JavaScript基本的输出和嵌入式写法教程
2015/10/20 Javascript
js 判断数据类型的几种方法
2017/01/13 Javascript
微信扫码支付零云插件版实例详解
2017/04/26 Javascript
npm国内镜像 安装失败的几种解决方案
2017/06/04 Javascript
跟混乱的页面弹窗说再见
2019/04/11 Javascript
浅谈layer弹出层按钮颜色修改方法
2019/09/11 Javascript
vue中的双向数据绑定原理与常见操作技巧详解
2020/03/16 Javascript
浅谈Python中的闭包
2015/07/08 Python
python基础教程项目二之画幅好画
2018/04/02 Python
用python实现k近邻算法的示例代码
2018/09/06 Python
Python数据可视化:顶级绘图库plotly详解
2019/12/07 Python
python计算Content-MD5并获取文件的Content-MD5值方式
2020/04/03 Python
Python自动创建Excel并获取内容
2020/09/16 Python
CSS3美化表单控件全集
2016/06/29 HTML / CSS
英国排名第一的冲浪店:Ann’s Cottage
2020/06/21 全球购物
params有什么用
2016/03/01 面试题
人力资源职位说明书
2014/07/29 职场文书
建筑横幅标语
2014/10/09 职场文书
2015年政府采购工作总结
2015/05/21 职场文书
Python基础之元类详解
2021/04/29 Python
浅谈Python numpy创建空数组的问题
2021/05/25 Python
python单元测试之pytest的使用
2021/06/07 Python
SQL模糊查询报:ORA-00909:参数个数无效问题的解决
2021/06/21 Oracle
MySQL图形化管理工具Navicat安装步骤
2021/12/04 MySQL
Docker容器harbor私有仓库部署和管理
2022/08/05 Servers