前言
众所周知,当我们在网上下载资源时希望这个文件内存小点或者下载速度快点就好了,这也决定了一个网站用户体验度的重要性,而带宽是决定此速度的重要因素,尤其是对于访问量较大的网站而言,那么怎么满足这个需求呢?这就涉及到接下来我们要聊到的文件压缩。
下面我们以node环境的demo为例
准备
- 先用
npm init -y初始化一下node,并在此下创建了相关文件夹。其中放了一张图片和一个大小为几乎2MB的js脚本(随便放什么,最好内存大点好测试文件压缩)。
- 创建一个HTML,只放了图片、js脚本和文字。
- 在根目录下创建
index.js脚本,在此进行主要的文件压缩操作。
正文
创建一个包含图片、文字和脚本的页面,并开启浏览器缓存策略,最后读取文件并返回。
const http = require('http')
const fs = require('fs')
const path = require('path')
const mime = require('mime')//用于处理响应头的类型
//创建服务
const server = http.createServer((req, res) => {
let filePath = path.resolve(__dirname, `www/${req.url}`)//获取文件路径,req.url为在浏览器输入的路径
if (fs.existsSync(filePath)) {//判断文件路径是否存在
const stats = fs.statSync(filePath)//读取文件的详细信息
if (stats.isDirectory()) {//前端请求的是目录
filePath = path.join(filePath, '/index.html')//是文件夹的话就手动在路径末尾添拼接'/index.html'
}
//缓存
const { ext } = path.parse(filePath) //文件后缀名 .html .jpg
const timeStamp = req.headers['if-modified-since']//前端请求头拿到的if-modified-since
let status = 200
if (timeStamp && Number(timeStamp) === stats.mtimeMs) {//前端拿到的if-modified-since和后端最后修改时间mtimeMs相同
status = 304
}
const mimeType=mime.getType(ext)//利用mime获取文件类型
res.writeHead(status,{
'Content-Type': mimeType,
'Cache-Control': 'max-age=86400',//强缓存 缓存一天
'Last-Modified': stats.mtimeMs,//协商缓存
})
//读取文件并返回
if (status == 200) {
const fileStream = fs.createReadStream(filePath)
fileStream.pipe(res);
}else {
res.end()
}
} else {
res.writeHead(404, { 'Content-Type': 'text/html' })
res.end('<h1>Not Found</h1>')
}
})
server.listen(3000, () => {
console.log('listening on port 3000');
})
初次在浏览器加载此页面,资源大小、加载时间如下图所示。
由于开启浏览器缓存策略后,再次刷新页面时,资源大小、加载时间如下图所示。
可以看到全部资源的大小和加载时间大大减小,并且图片和js脚本被缓存下来。以上还只是浏览器的缓存策略,下面来正式加入文件压缩(常用于压缩大型的js脚本)。
- 引入node中的zlib模块
const zlib = require('zlib') - 在响应头中设置
Content-Encoding字段告诉浏览器以何种方式压缩文件,而浏览器的请求头中会带有accept-encoding字段(一般都是gzip,deflate,br),判断要是文件类型是以text或者application开头的则设置压缩操作,在给该text/xxx或者application/xxx选择压缩类型时是根据请求头中的accept-encoding字段,只要符合其中一个即可(默认从左往右)。此外在读取文件并返回时还需要判断 要进行压缩的文件以对应的方式开始进行压缩,其他类型的文件照常操作即可。
完整代码如下:
const http = require('http')
const fs = require('fs')
const path = require('path')
const mime = require('mime')
const zlib = require('zlib')
//提高数据传输效率,减少带宽
const server = http.createServer((req, res) => {
let filePath = path.resolve(__dirname, `www/${req.url}`)
if (fs.existsSync(filePath)) {
const stats = fs.statSync(filePath)
if (stats.isDirectory()) {
filePath = path.join(filePath, '/index.html')
}
//缓存
const { ext } = path.parse(filePath)
const timeStamp = req.headers['if-modified-since']
let status = 200
if (timeStamp && Number(timeStamp) === stats.mtimeMs) {
status = 304
}
const mimeType=mime.getType(ext)
const acceptEncoding=req.headers['accept-encoding']//前端请求头中自带的'gzip, deflate, br'
//利用正则表达式判断文件类型以text或者application开头的
const compress=acceptEncoding&&/^(text|application)\//.test(mimeType)
//mimeType.startsWith('text')||mimeType.startsWith('application')
const responseHeaders ={
'Content-Type': mimeType,
'Cache-Control': 'max-age=86400',//缓存一天
'Last-Modified': stats.mtimeMs,
}
//text/xxx application/xxx 类型的文件才进行压缩
if(compress){//以' , '切割,形成数组,some从左往右遍历直到满足条件就为true
acceptEncoding.split(/\s*,\s*/).some((encoding)=>{
if(encoding==='gzip'){
responseHeaders['Content-Encoding']='gzip'
return true
}
if(encoding==='deflate'){
responseHeaders['Content-Encoding']='deflate'
return true
}
if(encoding==='br'){
responseHeaders['Content-Encoding']='br'
return true
}
return false
})
}
const compressEncoding=responseHeaders['Content-Encoding']
res.writeHead(status,responseHeaders)
//读取文件并返回
if (status == 200) {
const fileStream = fs.createReadStream(filePath)
if(compress&&compressEncoding){
let comp
if(compressEncoding=='gzip'){
comp=zlib.createGzip()
}else if(compressEncoding=='deflate'){
comp=zlib.createDeflate()
}else{
comp=zlib.createBrotliCompress()
}
fileStream.pipe(comp).pipe(res) //文件流先压缩再返回
}else{//不需要被压缩
fileStream.pipe(res);
}
} else {
res.end()
}
} else {
res.writeHead(404, { 'Content-Type': 'text/html' })
res.end('<h1>Not Found</h1>')
}
})
server.listen(3000, () => {
console.log('listening on port 3000');
})
压缩后初次加载时的资源大小如下所示:
JS脚本的大小跟之前还未被压缩的大小相比大大减少了!
可以看到只有index.html和spritejs.js的响应头才被压缩了。
以上就是文件的压缩处理!这样看来是不是觉得以后在大型文件中用到这个优化手段大大地节省带宽、提高了性能。
对于还对浏览器缓存存在疑问的,详细解答见于前端优化之浏览器的缓存 - 掘金 (juejin.cn)
拓展
以上案例都是在node环境下设置的缓存,下面我们来了解下在koa框架下如何设置缓存。
- 下载并引入
koa-compress(一般在主文件下引入)
const compress = require('koa-compress')
- 使用
compress中间件
app.use(compress({
filter (content_type) {
return /text|application/i.test(content_type)//选择需要压缩的文件类型
},
threshold: 4096, //超过4KB就压缩
gzip: {
flush: require('zlib').constants.Z_SYNC_FLUSH
},
deflate: {
flush: require('zlib').constants.Z_SYNC_FLUSH,
},
br: false // disable brotli
}))
总结
本篇文章就到此为止啦,由于本人经验水平有限,难免会有纰漏,对此欢迎指正。如觉得本文对你有帮助的话,欢迎点赞收藏❤❤❤,您的点赞是持续写作的动力,感谢支持!