关于ssr:SSR-和前端编译在这点上是一样的

4次阅读

共计 2724 个字符,预计需要花费 7 分钟才能阅读完成。

当初咱们都是通过组件的形式来开发前端页面,在浏览器外面,组件渲染时会通过 dom api 对 dom 做增删改来显示相应的内容。但在服务端并没有 dom api,咱们能够把组件渲染成 html 字符串,而后下发到浏览器渲染,因为曾经有了 html 了,就能够间接渲染成 dom,不再须要执行 JS,所以很快。

第一种浏览器渲染的形式叫做 CSR(client side render),第二种服务端渲染的形式叫做 SSR(server side render)。

很显著,SSR 渲染出画面的速度会很快,因为不须要执行 JS,而是间接解析 html。因而,app 里嵌的页面根本都用 SSR,这样体验会更好。而且低端机执行 JS 是可能很慢的,要是 CSR,那页面可能会有很长一段白屏工夫。

此外,SSR 是间接返回了 html,这样搜索引擎的爬虫就能从中抓取到具体的内容,就会给更高的搜寻权重,也就是更有利于 SEO(search engine optimize)。

在 app 里嵌的页面、搜索引擎排名优化这两种场景下,咱们都要做 SSR。

晓得了 SSR 是什么和为什么要做 SSR,那如何实现 SSR 呢?

SSR 实现原理
咱们晓得 vue 是通过 template 形容页面构造,而 react 是通过 jsx,但不论是 template 还是 jsx,编译后都会产生 render function,而后执行产生 vdom。

vdom 在浏览器里会通过 dom api 增删改 dom 来实现 CSR,在服务端会通过拼接字符串来实现 SSR。

vdom 是一个树形构造,那么 SSR 就是遍历这棵树,拼接字符串的过程。

看到这张图,不知你有没有想起编译的 generate 阶段也是这样的拼接字符串的过程:

没错,SSR 中 vdom 打印成字符串,和编译中 AST 打印成字符串的逻辑的确是一样的。

口说无凭,咱们来看下两者的源码再下结论。

Vue SSR 的渲染流程
vue 提供了 vue-server-renderer 这个包用于 SSR,它的作用就是把 Vue 组件渲染成字符串。

它提供了 createBundleRenderer 的 api:

const bundle = fs.readFileSync(resolve('./dist/server-bundle.js'), 'utf-8');

createBundleRenderer(bundle)
复制代码

这个 bundle 就是 webpack 编译产生的指标代码:

可能你会问,为啥要等 webpack 把代码编译成 bundle 才去渲染啊?

因为像 esm 的模块语法、像 ts、sass 等语法都不是 node 反对的呀,要先把代码编译打包成 bundle,这样能力在 node 外面跑。

这也是为啥提供的 api 叫做 createBundleRenderer。

创立好 renderer 之后,调用 renderToStream 办法,就开始执行渲染了。

当然,也能够调用 renderToString,这俩 api 的区别是一个是边渲染边返回内容,一个是齐全渲染完再返回内容。

渲染第一步,天然是要把传入的 bundle 给执行了:

这里 runInVm 就是执行 bundle 那段字符串的代码,这是基于 node 提供的 vm 包的 api 实现的:

通过 vm.runInContext 能够在某个上下文中执行一段代码。

执行之后,返回的就是 Vue 的实例:

留神,这里是在 node 环境里创立的 Vue 实例,所以没有 dom api,不能操作 dom,但能够打印成字符串:

这里 render 的实现就是拼接字符串:

这样遍历完一遍 vdom,就拼接好了最终的 html:

把这段 html 返回给浏览器即可。这样咱们就实现了 Vue 的 SSR!

小结一下 Vue SSR 的流程:

vue-server-renderer 包提供了 createBundleRenderer 的 api,能够传入编译打包后的 bundle 代码来创立一个 renderer。renderer 有 renderToString 和 renderToStream 的 api。外部会通过 vm.runInContext 来执行 bundle 的代码,产生 Vue 实例,之后把 Vue 实例的 vdom 渲染成 html 字符串。返回这个 html 字符串就实现了 SSR。

当然,理论做 SSR 的时候,咱们不会间接用 vue-server-renderer,而是会用封装了一层的 nuxt.js,因为它对路由等做了解决,并且对工具链的封装也很好,开箱即用。

到了这里,咱们能够说 SSR 就是遍历 vdom 拼接字符串的过程了。

接下来再看下编译中的 generate 阶段:

编译流程
前端畛域的编译根本都是源码转源码,所以流程都差不多,都是 parse、transform、generate 这三步:

parse 阶段把源码转为 AST(形象语法树),而后 transform 阶段会对 AST 做各种增删改,generate 阶段会把批改后的 AST 递归打印成字符串。

这里的 generate 阶段就像 SSR 的 render 一样,也是个拼接字符串的过程:

比方 babel 的 generate 的实现是这样的:

打印 while 节点:

打印 condition 节点:

递归遍历 AST,打印每个节点,拼接字符串,就能产生指标代码。

所以说,SSR 的 vdom render 和前端编译的 AST generate 是一样的逻辑,都是拼接字符串。

当然,也是有很多不同的中央的,比方 SSR 的 vdom 是动静执行 render function 产生的,而编译中的 AST 是从源码中动态编译产生的。只是拼接字符串的逻辑一样。

总结
SSR 渲染首屏画面速度快,而且利于搜索引擎的抓取,所以在 app 里嵌的页面、SEO 这两种场景下,咱们都会做 SSR。

SSR 的原理就是把 vdom 打印成 字符串,这和前端编译中的 generate 阶段很相似。

咱们看了 Vue 的 vue-server-render 包的源码,它提供了 createBundleRenderer 的 api,传入编译打包后的 bundle 代码,通过 vm 执行它,而后把产生的 Vue 实例的 vdom 打印成 html 字符串,就实现了 SSR。

咱们也看了 babel generator 的源码,它提供了每种节点的打印逻辑,递归遍历 AST,拼接字符串,就能产生指标代码。

尽管 SSR 和前端编译在流程上和目标上都不同,然而在生成代码这一点上是一样的,都是把树形构造打印成字符串。

最初
如果你感觉此文对你有一丁点帮忙,点个赞。或者能够退出我的开发交换群:1025263163 互相学习,咱们会有业余的技术答疑解惑

如果你感觉这篇文章对你有点用的话,麻烦请给咱们的开源我的项目点点 star:http://github.crmeb.net/u/defu 不胜感激!

PHP 学习手册:https://doc.crmeb.com
技术交换论坛:https://q.crmeb.com

正文完
 0