前面有篇文章已经写了一遍, 就是在 Respo 站点上有的优化方案,
https://segmentfault.com/a/11...
这种方案当中, 页面的静态内容是提前编译好的, 但同时前端也做渲染,
大致的流程是
编译阶段, 调用 SSR API 对页面进行一次渲染
页面加载到前端, HTML 先于 js 在页面上展示出来
js 启动后, 生成和 HTML 几乎一样的 DOM, 做一次初始化
之后按照正常的更新步骤做界面的更新
这个做法其实近似于单页面技术流行之前, jQuery 加模板引擎的做法,
服务端首先会用模板引擎渲染好带数据的页面, 前端再动态更新,
但单纯用模板引擎做渲染存在的一些局限性:
对后端依赖严重, 必需有一个独立的渲染服务器
调试不方便, 比如没有热替换技术, 而且与模板引擎相关
开发方式, 单页面技术有完整的前端路由等等方案, 用模板引擎增加复杂度
所以更好的办法是既是前端渲染单页面, 又能原样拿到后端运行,
也就是最近出现频率更高的服务端渲染, 简称 SSR,
然而 SSR 实现起来需要处理一些配套的技术细节, 否则麻烦挺多:
数据请求用户验证相关的问题, 需要往两边转发 Cookies
首屏可能需要不止一个甚至不同时发出的请求, 需要额外的抽象
服务端的性能不太好控制, 除非能很好利用缓存, 或者对性能要求不高
因此在 SSR 周边的方案成熟之前, 我认为提供过渡的方案比较可行,
也就是, 在编译阶段先渲染静态内容, 而动态内容仍然采用纯客户端渲染.
这也是 Addy Osmani 所以的 App Shell 渲染的问题,
这个方案能带来一些基础的好处:
用户能更快地看 App Shell, 页面也能缓存, 因而用户以为页面更快了
编译阶段渲染, 因而不存在服务器开销, 暂时也不会涉及数据请求的麻烦
前端开发的方案更加一致, 也就是目前熟悉的单页面应用的手法
同时相比前面最为理想化的 SSR 的效果存在一些差距:
毕竟首屏是没有数据的, 用户看到的是框框, 甚至只是色块
首屏也对应加载动画的页面, 这相比之前需要更额外的一些代码来完成
对于 SEO 来说, 还是差一些, 只有头部和导航, 没有具体数据
如果后面需要再填充数据以应对 SEO, 迁移方案仍然不乐观,
从单纯前端渲染, 到完全 SSR, 这之间有不少的台阶需要迈过,
而且即便现在基于已有的方案来讲, 还是有一些不成熟的地方:
前端路由和编译阶段路由要保持一致, 甚至对于 Nginx 也要做到一致.
路由能不能完全做到支持, 其实是不确定的代码拆分, 整个站的路由分割, 实际场景当中会更复杂
当然, 我还是觉得基于目前大量纯前端渲染的单页面, 这是一个加分项,
只要框架层面对 SSR API 做了实现, 用不大的成本就能完成这个优化,
然后对首屏加载的效果做一点点提升.
React 和 Respo 我前面已经做了演示, 感兴趣往前面翻一翻,
这边加上前面几天用 Vue 2 仿制的一个 Demo, 几乎同样的功能:
Demo http://vue-coffee-workflow.co...
Repo https://github.com/coffee-js/...
大致有一些要点, 类似的项目可以参考:
代码在到 Node 运行, 可以用 Webpack 预处理, 或者想办法直接载入
用户可能从多个路径访问, 所以需要编译出多个 HTML 文件作为入口
路由建议使用
.html
作为后缀, 方便 Nginx 直接命中文件组件当中不建议直接引入资源文件, 不方便处理, 建议在 CSS 当中进行打包
最主要的因素还是框架本身对 SSR 的支持尽量做到简单,
现在 React, Respo, Vue 2 的 Virtual DOM 都较好地支持 SSR 渲染了,
至于 Angular, 前面收集到的资料不够, 我等等看别人的进展.
我用 Vue 2 试验的例子, 直接放在了 GitHub 上, 打包方式有注明.
中间为了加载代码的方便, 我直接用 js 来写 vue render 方法,
实际开发当中几乎没人这么做, 但是对于 Node 环境这样更友好,
不过大概还是 Vue 早起那种 template 的写法更加实在一点...
核心的渲染代码是 gulpfile 当中的这一段, 编译路由, 生成 HTML 文件:
https://github.com/coffee-js/...
# this is the initial address
entries = [
'index.html'
'page/a.html'
'page/b.html'
]
entries.forEach (address) ->
app = new Vue
router: router
store: store
components:
container: Container
render: (h) ->
h 'container'
router.push address
renderer.renderToString app, (err, appHtml) ->
if err?
throw err
else
html = template.render appHtml, store.state, settings
htmlPath = path.join 'build', address
console.log 'render entry:', htmlPath
mkpath.sync path.dirname(htmlPath)
fs.writeFileSync htmlPath, html
细节不展开了, 毕竟是试验的代码, 实际项目并不是我这样写的.
如果有 SSR 相关的想法, 可以一起交流下, 我这边方案还比较粗浅.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。