浏览器缓存分为两种类型:css
浏览器缓存机制的过程以下:html
强缓存是最完全的缓存,无需向服务器发送请求,一般用于css、js、图片等静态资源。浏览器发送请求后会先判断本地是否有缓存。若是无缓存,则直接向服务器发送请求;若是有缓存,则判断缓存是否命中强缓存,若是命中则直接使用本地缓存,若是没命中则向服务器发送请求。判断是否命中本地缓存的方法有两种:Expires和Cache-Control。webpack
Expires是http1.0的响应头,表明的含义是资源本地缓存的过时时间,由服务器设定。服务器返回给浏览器的响应头中若是包含Expires字段,浏览器发送请求时拿当前时间和Expires字段值进行比较,判断资源缓存是否失效。以下图所示:web
Date表明请求资源的时间,Expires表明资源缓存的过时时间,能够看到服务器设置资源的缓存时间为5分钟。2017-02-10 07:53:19以前,请求这个资源就是命中本地缓存。超过这个时间再去请求则不命中。浏览器
Cache-Control是http1.0中新增的字段。因为Expires设置的是资源的具体过时时间,若是服务器时间和客户端时间不同,就会形成缓存错乱,好比认为调节了客户端的时间,因此设置资源有效期的时长更合理。http1.1添加了Cache-Control的max-age字段。max-age表明的含义是资源有效期的时长,是一个相对时长,单位为s。缓存
Cache-Control: max-age = 300设置资源的过时时间为5分钟。浏览器再次发送请求时,会把第一次请求的时间和max-age字段值相加和当前时间比较,以此判断是否命中本地缓存。max-age使用的都是客户端时间,比Expires更可靠。若是max-age和Expires同时出现,max-age的优先级更高。Cache-Control提供了更多的字段来控制缓存:服务器
协商缓存的判断在服务器端进行,判断是否命中的依据就是此次请求和上次请求之间资源是否发生改变。未发生改变命中,发生改变则未命中。判断文件是否发生改变的方法有两个:Last-Modified、If-Modified-Since和Etag、If-None-Match。app
Last-Modified是http1.0中的响应头字段,表明请求的资源最后一次的改变时间。If-Modified-Since是http1.0的请求头,If-Modified-Since的值是上次请求服务器返回的Last-Modified的值。浏览器第一次请求资源时,服务器返回Last-Modified,浏览器缓存该值。浏览器第二次请求资源时,用于缓存的Last-Modified赋值给If-Modified-Since,发送给服务器。服务器判断If-Modified-Since和服务器本地的Last-Modified是否相等。若是相等,说明资源未发生改变,命中协商缓存;若是不相等,说明资源发生改变,未命中协商缓存。webpack-dev-server
能够看到该请求返回的是304状态码,说明资源的Last-Modified未改变,因此此次请求的Last-Modified和If-Modified-Since是一致的。测试
Last-Modified、If-Modified-Since使用的都是服务器提供的时间,因此相对来讲仍是很可靠的。可是因为修改时间的精确级别或者按期生成文件这种状况,会形成必定的错误。因此http1.1添加Etag、If-None-Match字段,完善协商缓存的判断。Etag是根据资源文件内容生成的资源惟一标识符,一旦资源内容发生改变,Etag就会发生改变。基于内容的标识符比基于修改时间的更可靠。If-None-Match的值是上次请求服务器返回的Etag的值。Etag、If-None-Match的判断过程和Last-Modified、If-Modified-Since一致,Etag、If-None-Match的优先级更高。
强缓存的优点很明显,无需向服务器发送请求,节省了大量的时间和带宽。可是有一个问题,缓存有效期内想更新资源怎么办?我在工程中还遇到另一个问题,一个项目有四个环境,测试环境、开发环境、在线确认环境、在线环境,四个环境的域名相同,这样就会形成四个环境的缓存共用问题。好比先访问了测试环境,index.js被换成到浏览器中,再切换到在线环境,在线环境会请求index.js,此时浏览器就会使用本地缓存中测试环境的index.js,形成代码错乱。
如何使强缓存失效,是问题的关键。一般的解决方法是更新文件名,文件名不同的话,浏览器就会从新请求资源。咱们要保证新发布版本和不一样环境中的文件名是不同的。其中一种方法在文件名后加版本号:
index.js?version=1
index.css?version=1
复制代码
webpack提供了很简单的方法能够配置缓存。
// webpack.config.js
module.exports = {
entry: "main.js",
output: {
path: "/build",
filename: "main.[hash].js"
}
};
复制代码
经过hash占位符,在每次生成打包文件时,都会经过文件内容生成惟一的hash,并添加到输出的文件名中。若是有多个入口文件,能够使用name占位符设置输出:
// webpack.config.js
module.exports = {
entry: {
main:"main.js",
sub:"sub.js"
},
output: {
path: "/dist",
filename: "[name].[hash].js"
}
};
复制代码
这时候有一个问题是,此时的hash是根据两个文件的内容来生成的,两个文件名使用的hash是一致的。若是main.js和sub.js只有一个改变,两个文件名都会改变,两个文件都会从新请求,形成资源浪费。webpack提供了chunkhash来代替hash在多入口状况下使用。chunkhash是根据每一个入口文件单独生成的哈希值,避免上述状况。
webpack打包动态生成文件名,咱们须要动态地把文件引用插入到html启动文件中。html-webpack-plugin能够帮我很好地解决这个问题。html-webpack-plugin
能够动态地生成一个html文件,并在html文件中动态插入webpack打包生成的资源文件。
var HtmlWebpackPlugin = require('html-webpack-plugin');
var webpackConfig = {
entry: 'main.js',
output: {
path: '/dist',
publicPath: '/dist',
filename: 'main.[hash].js'
},
plugins: [new HtmlWebpackPlugin()]
};
复制代码
默认在webpackConfig.output.path
路径下生成index.html
,生成的html文件以下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Webpack App</title>
</head>
<body>
<script src="main.2a6c1fee4b5b0d2c9285.js"></script>
</body>
</html>
复制代码
一般html启动文件都有自定义的内容,因此html-webpack-plugin
提供了模板功能,template字段设置模板的路径,html-webpack-plugin
以template为模板,动态添加webpack打包生成的资源路径。
var HtmlWebpackPlugin = require('html-webpack-plugin');
var webpackConfig = {
entry: 'main.js',
output: {
path: '/dist',
publicPath: '/dist',
filename: 'main.[hash].js'
},
plugins: [new HtmlWebpackPlugin(
{
template:'index.html'
}
)]
};
复制代码
原index.html内容(\index.html):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>stat-front</title>
<link rel="stylesheet" href="//at.alicdn.com/t/font_ejl5slgdvtg74x6r.css">
</head>
<body>
<div id="app" class="app-root">
<router-view></router-view>
</div>
<!-- built files will be auto injected -->
</body>
</html>
复制代码
生成的index.html内容(\dist\index.html):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>stat-front</title>
<link rel="stylesheet" href="//at.alicdn.com/t/font_ejl5slgdvtg74x6r.css">
</head>
<body>
<div id="app" class="app-root">
<router-view></router-view>
</div>
<!-- built files will be auto injected -->
<script src="main.2a6c1fee4b5b0d2c9285.js"></script>
</body>
</html>
复制代码
最开始的时候静态的index.html在根目录下,webpack-dev-server
设置的启动路径就是根目录下的index.html,若是要启动生成的index.html,还须要设置webpackConfig.output.publicPath
:
var HtmlWebpackPlugin = require('html-webpack-plugin');
var webpackConfig = {
entry: 'main.js',
output: {
path: '/dist',
publicPath: '/',
filename: 'main.[hash].js'
},
plugins: [new HtmlWebpackPlugin(
{
template:'index.html'
}
)]
};
复制代码
这样webpack-dev-server在内存中生成的资源都存放在根目录下,生成的index.html会代替原index.html启动。