zhangyuang / ssr

A most advanced ssr framework support React17/React18/Vue2/Vue3 on Earth that implemented serverless-side render specification.
http://doc.ssr-fc.com/
MIT License
2.61k stars 284 forks source link

feat: support html head chunked output #282

Closed MervynFang closed 1 year ago

MervynFang commented 1 year ago

需求点:支持 html 标签内容分块提前返回 背景:ssr 相比 cdn 静态文件有较长的服务端处理耗时,白屏时间相对较长,可以提前返回 html 分块内容,达到提前加载静态资源或者增加 loading/骨架屏的目的

修改点: 增加 htmlHeadChunked 配置项; ssr-core 增加 headRender 方法; runtime 增加 generateHeadHtml 方法;

支持 vue2/3,react 因为 18 有 Suspense SSR feature,故无须支持

zhangyuang commented 1 year ago

提大feature类型的pr前请先提issue讨论并列出完整的实现方案细节,不要直接提pr

zhangyuang commented 1 year ago

另外我觉得如果你只是想实现自定义切割html控制返回结果的话完全可以用如下更简单的方案实现。例如 layout的结构如下

<template>
  <!-- 注:Layout 只会在服务端被渲染,不要在此运行客户端有关逻辑,不要删除 rem 初始化以外的任何初始设置 -->
  <html>
    <head>
      <meta charSet="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
      <meta name="theme-color" content="#000000">
      <title>Serverless Side Render for Vue3</title>
      <!-- 初始化移动端 rem 设置,如不需要可自行删除 -->
      <slot name="remInitial" />
      <slot name="injectHeader" />
    </head>
    <body>
      <slot name="content" />
    </body>
  </html>
</template>

此时content代表的是当前path对应的页面级组件的内容,你的需求是希望先返回

<template>
  <!-- 注:Layout 只会在服务端被渲染,不要在此运行客户端有关逻辑,不要删除 rem 初始化以外的任何初始设置 -->
  <html>
    <head>
      <meta charSet="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
      <meta name="theme-color" content="#000000">
      <title>Serverless Side Render for Vue3</title>
      <!-- 初始化移动端 rem 设置,如不需要可自行删除 -->
      <slot name="remInitial" />
      <slot name="injectHeader" />
    </head>
    <body>
</template>

这一块的内容,完全可以调用两次 render来实现组装

第一次调用的时候只渲染layout不渲染content,通过配置文件或业务代码判断(待确认)来设置content的内容为空,此时拿到的就是外层的layout的完整内容。 再把layout的内容进行 split 分割。

第二次调用的时候只渲染content不渲染layout。再自行拼接组装即可。

zhangyuang commented 1 year ago

实现我上面所说的功能应该只需要修改 server-entry一个文件就可以了

MervynFang commented 1 year ago

已新建 issue #283

MervynFang commented 1 year ago

实现我上面所说的功能应该只需要修改 server-entry一个文件就可以了

实现的时候也有考虑说用 layout 来分割,但是考虑直接拼接字符串会相对简单一点性能也略好一点,且layout 需要传入完整的闭合标签,所以考虑不清楚怎么去分割,在哪里分割 layout 比较合适。我试一下 body 内设置为空的实现

MervynFang commented 1 year ago

@zhangyuang 已更新方案 通过 htmlHeadChunkedisHead 两个配置来控制,htmlHeadChunked 控制 setHeaderisHead 控制 内容是否为空,可见 config.md 本地打点数据

Snipaste_2023-05-10_20-27-00

静态文件提前加载:

Snipaste_2023-05-10_19-55-59
zhangyuang commented 1 year ago

看起来 isHeadhtmlHeadChunked 应该只需要保留一个配置,只要开启了就认为是需要pipe返回

zhangyuang commented 1 year ago

另外这里对于 isHead 开启的时候,app实例渲染为空节点的有点多了。我认为这里只需要不调用fetch以及不渲染页面组件就行了。底部的 jsInject 等无需让它不渲染。这一块不影响性能,也不会影响最终的split产物结果

zhangyuang commented 1 year ago
image

看起来你应该在

const children = h(App, { ctx, config, asyncData, fetchData: combineAysncData, reactiveFetchData: { value: combineAysncData }, ssrApp: app })

这一步来判断,而不是下方。否则会导致render.vue组件仍然会被调用,此时没有fetch的数据导致运行错误

zhangyuang commented 1 year ago

稍后我会更新此pr的代码,请在我更新后同步再继续commit

MervynFang commented 1 year ago

看起来 isHeadhtmlHeadChunked 应该只需要保留一个配置,只要开启了就认为是需要pipe返回

两个配置是为了 render 方法里面在 pipe 情况下不设置 header htmlHeadChunked,这里有更好的解决方法么?

zhangyuang commented 1 year ago

请fork的本地仓库切换一个分支名,不要直接用fork仓库的dev分支来提交pr

MervynFang commented 1 year ago

已切换分支,我看你已经新建了 feat/pipe 了,感谢后续跟进处理,有需要我再另提 pr