Practice about strong cache/negotiation cache

foreword


This article mainly discusses the use of strong cache and negotiated cache, and the basic concepts will not be discussed. I have written about the basic concepts before. If you are interested, you can read what I wrote in my blog: web cache .

Mainly from nodejs practice strong cache and negotiation cache and update strategy of webpack project.

NodeJs practice strong cache negotiation cache

Which one should be used for strong cache and negotiation cache, and where to use it, is actually very easy to distinguish. Usually, when we visit a website, we first enter the html file, and the js, css, pictures, audio, etc. referenced in the html file are shown to us through these. of a website.

So does html use strong cache lines?

Don't even think about it, no, why, assuming that html uses strong caching, when I need to change the style of a certain place, I will re-launch the css file and html file after I change it (because css has changed, so html has to be re-introduced css file, so html should also be changed and re-launched), but html uses strong cache. During the cache period, the browser directly reads the html file from the strong cache, so that you can go online again but it is still the original html, and the content is still It turns out that the user must be forced to refresh before new content appears. Is this reasonable? This is unreasonable.

So html should use negotiated cache, and css, js, etc. should use strong cache.

Example reference cache-control-nodejs-demo

Code parsing

The code uses a server built by nodejs, and headersets the cache through settings. It uses etagand freshtwo npm packages to calculate the etag value of the negotiated cache and determine whether it has expired.

The following is the index.html file, which references js, css

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>node demo</title>
  <link rel="stylesheet" href="/index.css">
</head>

<body>
  <h1>hello world</h1>
  <script src="/index.js"></script>
</body>
复制代码

index.css

h1 {
  color: skyblue;
}
复制代码

js

console.log('node cache demo')
复制代码

The following is the service code of nodejs: first determine whether the url enters the root directory, and then return the index.html file path

const pathname = url.parse(req.url, true).pathname
//根据请求路径取文件绝对路径
    if (pathname === '/') {
    filePath = path.join(__dirname, '/index.html')
isHtml = true
} else {
    filePath = path.join(__dirname, 'static', pathname)
    isHtml = false
}
复制代码

然后通过fs.stat读取刚刚的路径,如果没有则返回404,文件存在就根据上面判断的是否是进入html的,然后给html设置协商缓存,也就是设置Last-ModifiedETag响应头,其中的etag()方法是Etag包的方法,fresh()是用来判断协商缓存是否过期。这篇文章讲解了这两个方法的源码,有兴趣的朋友可以阅读一下:前端缓存最佳实践

fs.stat(filePath, function (err, stat) {
    if (err) {
      res.writeHead(404, 'not found')
      res.end('<h1>404 Not Found</h1>')
    } else {
      if (isHtml) {
        // html文件使用协商缓存
        const lastModified = stat.mtime.toUTCString()
        const fileEtag = etag(stat)
        res.setHeader('Cache-Control', 'public, max-age=0')
        res.setHeader('Last-Modified', lastModified)
        res.setHeader('ETag', fileEtag)

        // 根据请求头判断缓存是否是最新的
        isFresh = fresh(req.headers, {
          etag: fileEtag,
          'last-modified': lastModified
        })
      } else {
        // 其他静态资源使用强缓存
        res.setHeader('Cache-Control', 'public, max-age=3600')
      }

      fs.readFile(filePath, 'utf-8', function (err, fileContent) {
        if (err) {
          res.writeHead(404, 'not found')
          res.end('<h1>404 Not Found</h1>')
        } else {
          if (isHtml && isFresh) {
            //如果缓存是最新的 则返回304状态码
            //由于其他资源使用了强缓存 所以不会出现304
            res.writeHead(304, 'Not Modified')
          } else {
            res.write(fileContent, 'utf-8')
          }

          res.end()
        }
      })
    }
  })
复制代码

效果图:可以看到下面第一次进来都是200状态码

1648533257(1).jpg

刷新一遍就可以看到设置的缓存生效了,响应头也有刚刚设置的:

1648533336(1).jpg

1648533410(1).jpg

缓存更新的问题

现在突然来一个需求,需要将h1的字体颜色修改为红色。我将index.css修改了,但是在访问网站时候并没有看到想看的效果,字体还是蓝色,因为除了html文件,其余资源使用的是强缓存,所以每次访问都是访问强缓存里面的资源(强缓存没过期期间),得强制刷新才能跳过缓存重新请求拿到最新的文件。

这样显然是不合理,那么该如何做呢?

之前html不是设置了协商缓存嘛,所以每次访问都会询问html文件是否是最新的,所以只要修改html文件里面的css引入地址就行了

比如:<link rel="stylesheet" href="/index.css?v=1.0.2">在后面加个版本号,因为每次html都会发送请求询问是否过期,所以就可以达到效果了。

这样确实可行,但是开发不可能就引用一个css文件,往往是多个的,我每次修改一个css文件,其余的版本也要跟着升级但其余的css根本没有改动啊,所以这种方式就不大行,只适用于这种简单的网站应用。

对于复杂一点的就可以用文件的摘要信息来对资源文件进行重命名实现一个精确的缓存控制,更多详细的可以阅读一下这篇文章,里面写的十分详细:前端静态资源缓存与更新

webpack实践

webpack搭建项目就可以使用文件的摘要信息来对资源文件进行重命名实现一个精确的缓存控制,下面就做一个小Demo来学习。源码地址

Here is a simple React project built:

webpack-demo
├── yarn.lock
├── package.json
├── public
│   └── index.html
├── src
│   ├── app.css
│   ├── index.js
│   └── app.jsx
└── webpack.config.js
复制代码

Mainly focus on webpack.config.jsconfiguration

Package output configuration: insert a hash value at the back to achieve renaming; then also split the css and use hash to rename to achieve precise control of the cache.

output: {
    path: path.join(__dirname, './dist'),
    filename: 'bundle.[chunkhash].js'
},
plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack cache demo',
      template: './public/index.html'
    }),
    new MiniCssExtractPlugin({
      filename: '[name].[hash].css'
    })
],
复制代码

Here I upload it to the server to see the effect. The first time it is a new request, there is no problem. The second visit is also a negotiation cache for html, and a strong cache for css, js and other resources.

1648535312(1).jpg

1648536147(1).jpg

The app.css file before modification

body {
  background-color: #f5f5f5;
}

h1 {
  color: skyblue;
}

p {
  color: pink;
}
复制代码

After modifying the app.css file, swap the color of h1 with the color of p

body {
  background-color: #f5f5f5;
}

h1 {
  color: pink;
}

p {
  color: skyblue;
}
复制代码

The packaged css file is shown in the figure, and the html file also changes the reference address

1648536326(1).jpg

In this way, a precise cache control can be achieved by renaming resource files.

Briefly talk about the [chunkhash] in the configuration

Webpack provides three hash value calculation methods, namely hash, chunkhash and contenthash.

[hash] Substitution can be used to include a build-specific hash in the filename, but a better way is to use  [chunkhash] substitution to include a chunk-specific hash in the filename. [contenthash] substitution (replaceable template string) will create a unique hash based on the content of the resource. When the content of the resource changes, so [contenthash] does the change.

The difference between these three methods can be known from the webpack documentation. The hash is related to the entire project. As long as the project is changed somewhere, the overall hash will change.

Chunkhash parses dependent files according to the entry file, builds the corresponding chunk, and generates the corresponding hash value. Therefore, if the CSS content is changed, js will also change the hash value when this CSS is referenced.

The contenthash is the hash generated according to the content of the file. If the content changes, the hash will change, and other references will not cause the hash to change.

Reference article

Front-end static resource caching and updating

Front-end caching best practices

Webpack cache

Guess you like

Origin juejin.im/post/7080417470414061582
Recommended