导读:本期聚焦于小伙伴创作的《Webpack里如何配置http2推送?Push Manifest生成与使用方法详解》,敬请观看详情。把静态资源提前推给浏览器,能省掉关键请求往返时间,可不少人在Webpack里配http2推送时连清单都生成不对。Push Manifest本质是一份描述路径与依赖对应关系的JSON,构建阶段由插件扫描入口资源后产出。若缺少正确的manifest中间件,服务端无法识别哪些文件该主动推。本文说明如何用webpack插件生成清单、如何在Node服务中读取并触发推送,以及多页面下按路由拆分清单的注意点,帮你避开推送变成多余下载的坑。

在Webpack构建流程中接入http2服务器推送,核心并不是改打包逻辑,而是让构建产物能告诉服务端“当用户访问某个路由时,请顺手把这几个文件推过去”。这套机制依赖一份被称为Push Manifest的映射文件,它记录了路由路径与待推送资源之间的对应关系。很多项目之所以推送不生效,是因为只开了http2却没有生成或读取这份清单。

Webpack里如何配置http2推送?Push Manifest生成与使用方法详解

Push Manifest 是什么以及为什么需要它

http2的服务器推送允许服务端在响应主文档时,主动将客户端可能需要的资源(如CSS、JS、字体)通过同一个连接推送给浏览器,减少往返延迟。但服务端本身并不知道Webpack打包后哪些chunk属于哪个页面,这就需要一种声明式配置文件来描述这种归属关系,它就是Push Manifest。

从原理上看,Push Manifest通常是一个JSON结构,键是请求路径或路由标识,值是该路径下应被推送的资源数组。例如访问首页时推送首页的运行时、vendor和样式文件。没有它,服务端只能盲推或者完全不推;有了它,推送变得精准且可维护。在Webpack生态中,这类清单一般由特定插件在emit阶段扫描资源依赖后写入磁盘。

需要注意的是,Push Manifest只负责“描述”,真正执行推送的是http2服务端(如Node的http2模块、Nginx等)。因此生成清单只是第一步,还必须配合服务端逻辑读取并调用stream.pushStream才能完成推送。如果两端格式约定不一致,就会出现推送失效或推送了错误文件的情况。

使用 webpack 插件生成 Push Manifest

社区中可用http2-push-manifest这类Webpack插件来自动生成清单。它在编译完成、资源即将写入时,根据入口和子资源引用关系输出一个push-manifest.json。下面展示一个基础的Webpack配置示例,演示如何引入并配置该插件。

const Http2PushManifest = require('http2-push-manifest');

module.exports = {
  entry: {
    home: './src/home.js',
    about: './src/about.js'
  },
  output: {
    filename: '[name].[contenthash].js',
    path: __dirname + '/dist'
  },
  plugins: [
    new Http2PushManifest({
      // 指定输出的清单文件名
      fileName: 'push-manifest.json',
      // 只推送特定后缀资源,避免推送sourcemap
      include: ['**.js', '**.css'],
      exclude: ['**.map']
    })
  ]
};

上述配置在构建后会在dist目录生成清单,内容大致形如{"/home":["/home.abc123.js","/home.def456.css"]}。插件通过解析资源图谱,把每个入口对应的同步依赖收集进来。如果你用了代码分割,动态import的chunk默认不会进入推送列表,因为它们不是首屏必需,盲目推送反而浪费带宽。

在多页面项目中,建议按入口名自然映射路由。比如home入口对应/home路径。若你的路由是带参数的(如/user/:id),则需要在服务端做前缀匹配,而不是完全相等匹配。生成阶段也可以通过插件的transform钩子自定义键名,使其贴合实际路由表。

在 Node http2 服务端读取并配置推送

生成清单后,需要在http2服务中加载它,并在收到请求时查找对应资源进行推送。下面给出一个简化的Node服务代码,展示如何读取JSON并调用推送接口。这里使用原生http2模块,避免引入过重框架。

const http2 = require('http2');
const fs = require('fs');
const path = require('path');

const manifest = JSON.parse(fs.readFileSync('./dist/push-manifest.json', 'utf8'));
const server = http2.createSecureServer({
  key: fs.readFileSync('./key.pem'),
  cert: fs.readFileSync('./cert.pem')
});

server.on('stream', (stream, headers) => {
  const pathname = headers[':path'];
  // 查找当前路径对应的推送资源
  const pushes = manifest[pathname] || [];
  pushes.forEach(function (asset) {
    const filePath = path.join(__dirname, 'dist', asset);
    const content = fs.readFileSync(filePath);
    stream.pushStream({ ':path': asset }, function (err, pushStream) {
      if (err) return;
      pushStream.respond({ 'content-type': 'application/javascript' });
      pushStream.end(content);
    });
  });
  stream.respond({ 'content-type': 'text/html' });
  stream.end('<html><body>home page</body></html>');
});

server.listen(8443);

这段代码在收到流时先查清单,再为每个资源开一个pushStream。需要特别留意内容类型的设置:JS应标application/javascript,CSS为text/css,否则浏览器可能拒绝执行。推送的文件内容必须真实存在且未被 gzip 二次封装,因为http2层有自身压缩,服务端推送原始字节即可。

另一个常见坑是缓存对齐。如果浏览器已经缓存了某资源,服务端仍推送就会造成浪费,甚至触发浏览器取消推送流。高级做法是在请求头中检查if-none-match或利用RFC中定义的缓存摘要帧,但多数中小型项目可简单通过只推送首屏关键小体积文件来规避。同时,Nginx等反向代理若开启http2,也需要配置http2_push_preload并配合Link头,此时Webpack清单可转为Link头输出,而非上面Node直推方式。

多页面与路由拆分的进阶处理

当应用包含数十个页面时,单一清单文件会变得庞大,且服务端每次请求都要遍历匹配。更好的方案是按路由拆分清单,或在清单中使用通配前缀。比如将/admin/*统一指向后台公共chunk,避免每个子页都写一遍。

{
  "/home": ["/home.a1.js", "/base.css"],
  "/admin/*": ["/admin-common.b2.js", "/admin.css"],
  "/about": ["/about.c3.js"]
}

服务端匹配时可用Object.keys(manifest).find(k => k.endsWith('*') && pathname.startsWith(k.slice(0,-1)))来捕获通配项。这样新增后台页面无需改清单,只要静态资源命名规律一致即可。对于使用HTML模板插件(如html-webpack-plugin)的项目,还可让插件把清单内联进HTML的Link rel=preload头,作为推送的降级方案,保证不支持推送的环境也能预加载。

最后要强调,推送不是越多越好。Webpack生成的清单若包含了过大图片或字体,推送会阻塞关键渲染。应通过插件include严格限制类型,并结合真实网络环境用Chrome的http2水族馆面板观察推送流是否被立即使用。只有被立即使用的推送才真正起到优化作用,其余都应从清单移除。

webpackhttp2_server_pushpush_manifest修改时间:2026-08-14 21:12:35

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。