Razzle: 原生 HTTP2 支持

创建于 2018-12-24  ·  10评论  ·  资料来源: jaredpalmer/razzle

你好,

这是一个功能请求/提案。

前几天我发现了HTTP2 。 所以,当我用拉扎勒创建after.js应用,我认为这可能是这个框架有很大的改进。 简而言之,HTTP2 支持服务器推送“命令”,允许网络服务器推送静态资产,例如样式表和捆绑包以及index.html而无需额外的请求周期。 这可以显着提高站点性能,并且可能会非常有价值。

至于实现,这将需要我们将spdy模块与express结合使用并在开发中创建假证书,因为浏览器仅支持h2 (官方协议缩写) 通过 HTTPS。 在生产中,这可能无关紧要,因为大多数人无论如何都部署在符合h2反向代理之后。 如果我没记错的话,实现应该是相当直接的,因为资产路径是作为环境变量公开的。

还需要注意的是,如果浏览器不支持, h2将回退到 HTTP 1.1,因此它不会强加任何破坏性的更改。

discussion

最有用的评论

这个问题是从大约一年前开始的,但我今晚读到了。
我不知道我应该如何开始:)

问题是什么

After.js 只发送来自服务器响应的主要 CSS 和 js 文件,然后在浏览器上使用 ensureReady 方法尝试为当前代码溢出页面获取 javascript 和 CSS 文件,但速度太慢

期望的行为

从服务器发送当前路由所需的所有 CSS 和 JS。

为了解决这个问题,我有一个想法:)

快进:

这是结果#1178

{
  "client": {
    "css": [
      "/static/css/bundle.36d04d42.css"
    ],
    "js": [
      "/static/js/bundle.d3433edb.js",
      "/static/js/bundle.d3433edb.js.map"
    ]
  },
  "home": {
    "css": [
      "/static/css/home.2f97ec2d.chunk.css"
    ],
    "js": [
      "/static/js/home.82beecb0.chunk.js",
      "/static/js/home.82beecb0.chunk.js.map"
    ]
  },
  "about": {
    "css": [],
    "js": [
      "/static/js/about.1e639ac9.chunk.js",
      "/static/js/about.1e639ac9.chunk.js.map"
    ]
  }
}

这正是@nicklasfrahm提到的

当前清单不包含有关资产属于哪些路线的任何信息,因此可能需要调整或完全排除这种方法。 如果我们修改资产清单的形式,我们可能应该坚持您提到的来自 Google 的参考。

它是如何运作的?

1) 我们需要将 webpackChunkName 添加到我们的异步组件中,我们还应该将 chunkName 存储在一个变量中,以便我们可以在接下来的步骤中找到该块。

// routes.js

import Home from './Home';

export default [
  {
    path: '/',
    exact: true,
    component: Home,
  },
  {
    path: '/about',
    exact: true,
    component: () => import(/* webpackChunkName: "whatever" */ './About'),
    chunkName: 'whatever',
  },
  {
    path: '/contact-us',
    exact: true,
    component: () => import(/* webpackChunkName: "ContactUs" */ './Contact'),
    chunkName: 'ContactUs',
  },
];

_你也可以开发一个 babel 插件来为你做这件事,就像这个插件一样。_

2)找出当前请求是针对哪个组件的。 我使用了 react-router 所以我会调用matchPath方法。

const matched = matchPath(req.url, routes)

所以matched变量将包含一个这样的对象:

{
  path: '/contact-us',
  exact: true,
  component: () => import(/* webpackChunkName: "ContactUs" */ './Contact'),
  chunkName: 'ContactUs',
}

3) getAssets()这是一个简单的函数,它从数组中返回一个属性

const { scripts, styles } = getAssets(matched.chunkName, chunks)

function getAssets(chunkName, chunks) {
  const scripts = chunks[chunkName].js || [];
  const styles = chunks[chunkName].css || [];
  return { scripts, styles };
}

就是这么多EZ :)

您可以在https://github.com/jaredpalmer/after.js/pull/237找到上述代码的实现版本。

http2 推送!

https://github.com/jaredpalmer/after.js/pull/237#issuecomment -536197799

我们也可以做更多很棒的事情 :D

在 ReactDOM.renderToString 完成它的工作后, react-loadableloadable-components 都会为当前请求找到脚本和 css 文件。

所以我们的TTFB(第一个字节的时间)将是当前路由的 getInitialProps 时间(大约 400 毫秒)+ ReactDOM.renderToString(大约 60 毫秒)

但是有了这个 PR,服务器在调用 getInitialProps 和 renderToString 之前就知道当前请求的 CSS 和 JS 文件。
after.js 在内部调用 renderToString 两次,第一次渲染当前路由的请求,第二次渲染 Document.js...

如果我们使用renderToNodeStream来渲染 Document.js,我们可以使用预加载的 CSS 和 JS 文件尽快向客户端发送<head>的 HTML,这样我们的 TTFB 就会减少到 0(实际上不是 0,但它太低了)并且服务器处理 getInitialProps和当前路由的 renderToString ,浏览器可以获取 CSS 和 JS 文件(还有一个叫做 h2 push 的东西,但很多人说完全使用 h2 push 不是一个好主意)。

<html>
  <head>
    <meta charset="utf-8" />
    <link
      rel="preload"
      href="/static/media/bundle.css"
    />
    <link
      rel="preload"
      href="/static/ccs/homepage.chunk.css"
    />
    <link
      rel="preload"
      href="/static/media/homepage.chunk.js"
    />  
    <link
      rel="preload"
      href="/static/media/bundle.js"
    />  

当前请求的渲染完成后,我们可以从 react-helmet 获取元标记并将它们发送到浏览器

    <title>Hii</title>
    <meta name="description" content="A page's description, usually one or two sentences."/>

然后我们将页面的其余部分发送到最后

  </head>
  <body>
    <div id="root">
      WHAT EVER REACT RENDER TO STRING RETURNED
    </div>
    <div>window.__SERVER_APP_STATE__ = { ... }</div>
  </body>
</html>

那太复杂了!

我们可以简单地使用HTTP Link Header来实现相同的功能。

// server.js
import { getAssets } from "@jaredpalmer/after"

const server = express()
server.get("/*", async (req, res) => {
  // before call render
  const { scripts, styles } = getAssets({ url: req.url, routes })
  res.links({
    preload: scripts,
    preload: styles,
  })
  const html = await render()

上面的代码 TTFB 没有改变(仍然大约 500 毫秒),但浏览器更快地获取 CSS 和 JS 文件。 (不确定这是否像我解释的那样有效)

下一步是什么?

https://github.com/jaredpalmer/after.js/issues/281

渲染异步 (ReactDOM.renderToNodeStream) 🏎

所以我们使用renderToNodeStream()来实现我之前所说的

所有10条评论

我建议将 nginx 服务器放在 razle 服务器前面。 您可以立即使用 http2,我猜您会享受到一些安全方面的好处。

@mschipperheyn我只是部分同意你的看法。 是的,这是更安全的设置,并且在大多数情况下,如果您在单个 Docker 主机上使用带有nginx-ingressnginx-proxy 之类的东西的 Kubernetes,无论如何您都会拥有它。

至于开箱即用的 HTTP/2,我不同意你的观点,如果我错了,请纠正我。 是的,您可以获得二进制数据传输和其他一些不错的功能,但我正在谈论的功能是HTTP/2 服务器推送,据我所知,这需要实现和支持直到应用程序服务器,这将是razzle在这种情况下。 所以我认为我们不应该关闭它,直到我们完全评估它的有用性。

@nicklasfrahm你是对的。 我查看了https://webapplog.com/http2-server-push-node-express/坦率地说,这是一个糟糕的实现方式,因为它很难在没有手动实现的情况下提供开箱即用的服务每一次。

相反,实现某种清单(例如 https://github.com/GoogleChromeLabs/http2-push-manifest)可能是有意义的,前端网络服务器 (nginx) 可以使用它。 此清单的静态资产部分已存在,即razzle已生成的assets.json 。 它必须被重写为清单格式。 最重要的是,您可能需要能够解析 react-router 路由来推送页面。

嗯,Nginx 似乎无法处理推送清单。

因此,替代方案 3 是使用链接标头,这是预加载而不是推送。 这似乎应该更符合开箱即用的处理方式,并且不需要在 razzle 端设置 SSL。 您可以实施诸如https://github.com/cloudflare/netjet 之类的内容,以获得尽可能快的响应。

@mschipperheyn我认为我们可以使用方法一,因为我们可以实现一个中间件,它接受我们目前拥有的资产清单,然后基于它进行推送。 然后可以在示例的server.js中设置此中间件,如果您使用create-razzle-app ,它将默认

但我不确定这是否也适用于after.js ,因为它会根据您的路线进行代码拆分。 当前清单不包含有关资产属于哪些路线的任何信息,因此可能需要调整或完全排除这种方法。 如果我们修改资产清单的形式,我们可能应该坚持您提到的来自 Google 的参考。

在发布版本 3 之前考虑此更改可能是有意义的,因为它_可能_需要对资产清单的格式进行潜在的破坏性更改,如果它应该包含资产的路由信息​​。

也许我没有理解,但是如果 HTTP2 可以作为快速中间件实现,是否需要将其烘焙到 Razzle 中?

许多人(包括我自己)一直在等待 Razzle v3 达到“稳定”里程碑一段时间。 HTTP2 支持作为潜在的 v4 功能更有意义,而不是推动 v3 进一步发展(并延迟对当前 v3 alpha 更改的生产使用反馈/验证)。

@mattlubner目前,我们仍在尝试为此评估一个好的方法,所以我不能说单独的 express 中间件是否可以做到。

至于版本 3。我最近才发现这个 gem (razzle),我的意图绝对不是阻止任何人。 因此,我完全同意您将其移至第 4 版。我之前从未做过开源,所以我不太了解生产使用反馈和验证的相关性。 但事后看来,我同意这是至关重要的。

@nicklasfrahm @mschipperheyn @mattlubner
这个线程很棒,它充满了经验:)
我爱它❤️

这个问题是从大约一年前开始的,但我今晚读到了。
我不知道我应该如何开始:)

问题是什么

After.js 只发送来自服务器响应的主要 CSS 和 js 文件,然后在浏览器上使用 ensureReady 方法尝试为当前代码溢出页面获取 javascript 和 CSS 文件,但速度太慢

期望的行为

从服务器发送当前路由所需的所有 CSS 和 JS。

为了解决这个问题,我有一个想法:)

快进:

这是结果#1178

{
  "client": {
    "css": [
      "/static/css/bundle.36d04d42.css"
    ],
    "js": [
      "/static/js/bundle.d3433edb.js",
      "/static/js/bundle.d3433edb.js.map"
    ]
  },
  "home": {
    "css": [
      "/static/css/home.2f97ec2d.chunk.css"
    ],
    "js": [
      "/static/js/home.82beecb0.chunk.js",
      "/static/js/home.82beecb0.chunk.js.map"
    ]
  },
  "about": {
    "css": [],
    "js": [
      "/static/js/about.1e639ac9.chunk.js",
      "/static/js/about.1e639ac9.chunk.js.map"
    ]
  }
}

这正是@nicklasfrahm提到的

当前清单不包含有关资产属于哪些路线的任何信息,因此可能需要调整或完全排除这种方法。 如果我们修改资产清单的形式,我们可能应该坚持您提到的来自 Google 的参考。

它是如何运作的?

1) 我们需要将 webpackChunkName 添加到我们的异步组件中,我们还应该将 chunkName 存储在一个变量中,以便我们可以在接下来的步骤中找到该块。

// routes.js

import Home from './Home';

export default [
  {
    path: '/',
    exact: true,
    component: Home,
  },
  {
    path: '/about',
    exact: true,
    component: () => import(/* webpackChunkName: "whatever" */ './About'),
    chunkName: 'whatever',
  },
  {
    path: '/contact-us',
    exact: true,
    component: () => import(/* webpackChunkName: "ContactUs" */ './Contact'),
    chunkName: 'ContactUs',
  },
];

_你也可以开发一个 babel 插件来为你做这件事,就像这个插件一样。_

2)找出当前请求是针对哪个组件的。 我使用了 react-router 所以我会调用matchPath方法。

const matched = matchPath(req.url, routes)

所以matched变量将包含一个这样的对象:

{
  path: '/contact-us',
  exact: true,
  component: () => import(/* webpackChunkName: "ContactUs" */ './Contact'),
  chunkName: 'ContactUs',
}

3) getAssets()这是一个简单的函数,它从数组中返回一个属性

const { scripts, styles } = getAssets(matched.chunkName, chunks)

function getAssets(chunkName, chunks) {
  const scripts = chunks[chunkName].js || [];
  const styles = chunks[chunkName].css || [];
  return { scripts, styles };
}

就是这么多EZ :)

您可以在https://github.com/jaredpalmer/after.js/pull/237找到上述代码的实现版本。

http2 推送!

https://github.com/jaredpalmer/after.js/pull/237#issuecomment -536197799

我们也可以做更多很棒的事情 :D

在 ReactDOM.renderToString 完成它的工作后, react-loadableloadable-components 都会为当前请求找到脚本和 css 文件。

所以我们的TTFB(第一个字节的时间)将是当前路由的 getInitialProps 时间(大约 400 毫秒)+ ReactDOM.renderToString(大约 60 毫秒)

但是有了这个 PR,服务器在调用 getInitialProps 和 renderToString 之前就知道当前请求的 CSS 和 JS 文件。
after.js 在内部调用 renderToString 两次,第一次渲染当前路由的请求,第二次渲染 Document.js...

如果我们使用renderToNodeStream来渲染 Document.js,我们可以使用预加载的 CSS 和 JS 文件尽快向客户端发送<head>的 HTML,这样我们的 TTFB 就会减少到 0(实际上不是 0,但它太低了)并且服务器处理 getInitialProps和当前路由的 renderToString ,浏览器可以获取 CSS 和 JS 文件(还有一个叫做 h2 push 的东西,但很多人说完全使用 h2 push 不是一个好主意)。

<html>
  <head>
    <meta charset="utf-8" />
    <link
      rel="preload"
      href="/static/media/bundle.css"
    />
    <link
      rel="preload"
      href="/static/ccs/homepage.chunk.css"
    />
    <link
      rel="preload"
      href="/static/media/homepage.chunk.js"
    />  
    <link
      rel="preload"
      href="/static/media/bundle.js"
    />  

当前请求的渲染完成后,我们可以从 react-helmet 获取元标记并将它们发送到浏览器

    <title>Hii</title>
    <meta name="description" content="A page's description, usually one or two sentences."/>

然后我们将页面的其余部分发送到最后

  </head>
  <body>
    <div id="root">
      WHAT EVER REACT RENDER TO STRING RETURNED
    </div>
    <div>window.__SERVER_APP_STATE__ = { ... }</div>
  </body>
</html>

那太复杂了!

我们可以简单地使用HTTP Link Header来实现相同的功能。

// server.js
import { getAssets } from "@jaredpalmer/after"

const server = express()
server.get("/*", async (req, res) => {
  // before call render
  const { scripts, styles } = getAssets({ url: req.url, routes })
  res.links({
    preload: scripts,
    preload: styles,
  })
  const html = await render()

上面的代码 TTFB 没有改变(仍然大约 500 毫秒),但浏览器更快地获取 CSS 和 JS 文件。 (不确定这是否像我解释的那样有效)

下一步是什么?

https://github.com/jaredpalmer/after.js/issues/281

渲染异步 (ReactDOM.renderToNodeStream) 🏎

所以我们使用renderToNodeStream()来实现我之前所说的

我很想知道你对此的看法
@nicklasfrahm @mschipperheyn @mattlubner

@nimaa77这听起来很棒。 因此,如果我理解正确的话,您没有使用任何 HTTP/2 功能,而是通过使用已通过 HTTP/1.1 可用的功能来改善加载时间。 我喜欢它,因为它不需要用户提供额外的脚手架。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

sebmor picture sebmor  ·  4评论

JacopKane picture JacopKane  ·  3评论

ewolfe picture ewolfe  ·  4评论

dizzyn picture dizzyn  ·  3评论

mhuggins picture mhuggins  ·  3评论