当前位置: 首页 > 科技观察

SSR和前端编译在这一点上是一样的

时间:2023-03-12 11:07:06 科技观察

现在我们通过组件来开发前端页面。在浏览器中,组件在渲染时,会通过domapi对dom进行增删改查,以显示相应的内容。.但是服务端没有domapi,我们可以把组件渲染成html字符串,然后发送给浏览器渲染,因为已经有html了,可以直接渲染成dom,不需要执行js,所以它会非常快。第一种浏览器渲染方式称为CSR(clientsiderender),第二种服务器端渲染方式称为SSR(serversiderender)。很明显,SSR会非常快速地渲染屏幕,因为它不需要执行JS,而是直接解析html。所以app内嵌的页面基本都使用SSR,这样体验会更好。而且,低端机可能执行JS很慢。如果是CSR,页面可能会出现较长的白屏时间。另外,SSR直接返回html,这样搜索引擎爬虫就可以从中抓取特定的内容,并且会给予更高的搜索权重,更有利于SEO(搜索引擎优化)。App内嵌页面和搜索引擎排名优化这两个场景,我们都要做SSR。既然我们知道了SSR是什么以及为什么需要这样做,那么我们如何实施SSR?SSR实现原理我们知道vue是通过template来描述页面结构的,react是用jsx的,但是不管是template还是jsx,编译后都会生成render函数。然后执行生成vdom。在浏览器中,vdom会通过domapi对dom进行增删改查来完成CSR,而在server端则会通过拼接字符串来完成SSR。vdom是一个树结构,所以SSR就是遍历树,拼接字符串的过程。看到这张图,不知大家是否还记得编译的generate阶段也是拼接字符串的过程:是的,SSR中将vdom打印成字符串的逻辑确实和编译时将AST打印成字符串的逻辑是一样的。这么说是没有根据的,还是先看看两者的源码再下定论吧。VueSSR的渲染过程Vue为SSR提供了vue-server-renderer包,用于将Vue组件渲染成字符串。它提供了createBundleRenderer的api:constbundle=fs.readFileSync(resolve('./dist/server-bundle.js'),'utf-8');createBundleRenderer(bundle)这个bundle就是webpack编译生成的目标代码:你可能会问,为什么要等webpack把代码编译成bundle再渲染呢?因为像esm这样的模块语法,像ts、sass等语法是node不支持的,所以需要先把代码编译打包成bundle,这样才能在node中运行。这就是提供的api称为createBundleRenderer的原因。创建渲染器后,调用renderToStream方法开始渲染。当然你也可以调用renderToString。这两种API的区别在于,一种是在渲染的同时返回内容,另一种是渲染完成后才返回内容。渲染的第一步自然是执行传入的bundle:这里的runInVm就是执行bundle的字符串的代码,它是基于node提供的vm包的api:通过vm.runInContext可以执行一段上下文中的代码。执行后返回Vue实例:注意这是在node环境下创建的Vue实例,所以没有domapi,无法操作dom,但是可以打印成字符串:这里render的实现就是拼接字符串:这样遍历完vdom,拼接最后的html:把这个html返回给浏览器就行了。这样,我们就实现了Vue的SSR!总结一下Vue的SSR的过程:vue-server-renderer包提供了createBundleRendererapi,可以在编译打包的bundle代码中导入创建renderer。渲染器具有renderToString和renderToStreamAPI。在内部,bundle的代码会通过vm.runInContext执行,生成一个Vue实例,然后将Vue实例的vdom渲染成html字符串。返回此html字符串实现SSR。当然,实际做SSR的时候,我们不会直接使用vue-server-renderer,而是会使用nuxt.js,加上一层封装,因为它处理了路由等,对工具链也封装的很好,out的盒子。至此,我们可以说SSR就是遍历vdom拼接字符串的过程。接下来看一下编译中的generate阶段:编译过程中前端领域的编译,基本上就是源代码到源代码的转换,所以过程是类似的,parse、transform、generate三个步骤:parse阶段将源代码转换为AST(抽象语法树),然后transform阶段会对AST进行各种增删改查,generate阶段将修改后的AST递归打印成字符串。这里的generate阶段就像SSR的render,也是一个拼接字符串的过程:比如babel的generate的实现是这样的:printwhilenode:printconditionnode:递归遍历AST,打印每个节点,拼接字符串,可以生成目标代码。所以SSR的vdomrender和前端编译的ASTgenerate逻辑是一样的,都是拼接的字符串。当然,有很多不同之处。比如SSR的vdom是动态执行render函数生成的,而编译后的AST则是从源码静态编译而来。只是拼接字符串的逻辑是一样的。综上所述,SSR首屏渲染快,容易被搜索引擎抓取。所以我们会在页面嵌入APP和SEO这两个场景下做SSR。SSR的原理是将vdom打印成字符串,和前端编译中的generate阶段很像。我们查看了Vue的vue-server-render包的源码,它提供了createBundleRenderer的api,将编译打包的bundle代码传入,通过vm执行,然后将生成的Vue实例的vdom打印成html字符串.实施SSR。我们还看了babel生成器的源码,里面提供了每个节点的打印逻辑,递归遍历AST,拼接字符串生成目标代码。SSR和前端编译虽然过程和目的不同,但是在生成代码上是一样的,都是把树结构打印成字符串。