最近无论是在公司还是在自己的研究项目中,都在探索H5页面的服务端渲染,所以本文讨论一下service端渲染的必要性和背后的原理。我们先来看几个问题。为什么ToC的H5适合做SSR?ToC类营销H5页面的典型特征是:流量大,交互相对简单(尤其是搭建平台搭建的活动页面),一般页面首屏的知名度都比较高。那么为什么此时作为传统的CSR渲染方式就不合适了呢?看完下一节,或许你就有了答案。为什么服务器端渲染比客户端渲染快?下面分别对比一下两者的DOM渲染过程。图片来源ServerSideRenderingoverClientSideRendering的好处ClientRenderingServerRenderingClientRendering,需要先得到一个空的HTML页面(此时页面已经进入白屏),然后需要经过:request并解析JavaScript和CSS请求后端服务器获取数据,并根据数据在几个过程中渲染页面,然后才能看到最终页面。尤其是在复杂的应用中,由于需要加载JavaScript脚本,应用越复杂,需要加载的JavaScript脚本越多越大,会导致应用首屏加载时间非常长,这将影响用户体验。与客户端渲染相比,服务器端渲染在用户发送页面url请求后,应用服务器返回的html字符串是一个完整的计算,可以交给浏览器直接渲染,这样渲染DOM不再受静态资源和ajax限制的影响。什么是服务器端渲染限制?但是服务器端渲染真的那么好吗?实际上,不。为了实现服务器端渲染,应用程序代码需要同时兼容服务器端和客户端操作。对第三方库的要求比较高。Node渲染时如果想直接调用第三方库,该库必须支持服务端。使成为。相应的代码复杂度增加了很多。由于服务器对渲染HTML的需求增加,原本只需要输出静态资源文件的nodejs服务增加了渲染HTML的数据获取IO和CPU占用。如果流量急剧增加,可能会导致服务器崩溃,因此需要使用适当的缓存策略,并为适当的服务器负载做好准备。对构建和部署也有更高的要求。之前的SPA应用可以直接部署在静态文件服务器上,而服务端渲染应用需要在Node.js服务端运行环境。说了这么多VueSSR的原理,大家可能还不是很清楚服务端渲染的原理。下面以Vue服务端渲染为例简单介绍一下它的原理:这张图来自VueSSR指导原理分析参考Howtobuilda高可用服务端渲染项目的源码是我们的源码区,那个是,项目代码。UniversalApplicationCode和我们通常的客户端渲染代码组织完全一样,因为渲染过程是在Node端,所以没有DOM和BOM对象,所以在beforeCreate和created中不要做涉及DOM和BOM的操作生命周期钩子。app.js、Serverentry、Cliententry比client渲染的主要功能是:app.js分别向Serverentry和Cliententry暴露createApp()方法,这样每次请求都会生成一个新的app实例和Server入口和Client入口会被webpack分别打包成vue-ssr-server-bundle.json和vue-ssr-client-manifest.json。Node会根据webpack打包好的vue-ssr-server-bundle.json调用createBundleRenderer生成一个renderer实例,然后调用renderer.renderToString生成一个完整的html字符串。Node端将渲染后的HTML字符串返回给Browser,同时Node端基于vue-ssr-client-manifest.json生成的js会和HTML字符串进行hydrate,完成HTML在on上的激活客户端,使页面具有交互性。写个demo实现SSR。我们知道目前市面上实现服务端渲染一般有以下几种方式:使用next的服务端渲染方案。也就是上面提到的)使用node+ReactrenderToStaticMarkup/renderToString实现react项目的服务端渲染使用模板引擎实现ssr(如ejs、jade、pug等)最近要改造的项目刚好是开发的Vue,目前正在考虑基于vue-server-renderer改造为服务端渲染。基于以上分析的原理,我从零开始一步步搭建了一个最小的vue-ssr。有需要的可以直接使用~这里贴几个注意事项:使用SSR时没有单例模式,我们知道Node.js服务器是一个长时间运行的进程。当我们的代码进入进程时,它会做一次fetch,并保存在内存中。这意味着如果创建了单例对象,它将在每个传入请求之间共享。所以每次用户请求都会创建一个新的Vue实例,这也是为了避免跨请求状态污染的发生。因此,与其直接创建应用实例,不如暴露一个可以重复执行的工厂函数,为每个请求创建一个新的应用实例://main.jsimportVuefrom"vue";importAppfrom"./App.vue”;从“./router”导入createRouter;从“./store”导入createStore;导出默认值()=>{constrouter=createRouter();conststore=createStore();constapp=newVue({router,store,render:(h)=>h(App),});返回{应用程序、路由器、商店};};服务端代码构建和客户端代码构建的区别在于:不需要编译CSS,服务端渲染会自动将内置的CSS目标设置到nodejs环境中,无需代码切割。Nodejs一次性加载所有代码到内存,更有利于运行效率//vue.config.js//两个插件分别负责封装客户端和服务端constVueSSRServerPlugin=require("vue-server-渲染器/服务器插件");constVueSSRClientPlugin=require("vue-server-renderer/client-plugin");constnodeExternals=require("webpack-node-externals");constmerge=require("lodash.merge");//根据传入的环境变量确定入口文件和对应的配置项constTARGET_NODE=process.env.WEBPACK_TARGET===“节点”;常量目标=TARGET_NODE?"server":"client";module.exports={css:{extract:false,},outputDir:"./dist/"+target,configureWebpack:()=>({//将入口指向应用程序的服务器/clientfileentry:`./src/${target}-entry.js`,//为bundlerenderer提供源映射支持处理动态导入的方式,//也告诉`vue-loader`在编译Vue组件时输出面向服务器的代码target:TARGET_NODE?"node":"web",//是否模拟节点全局变量node:TARGET_NODE?undefined:false,output:{//使用Node样式导出模块libraryTarget:TARGET_NODE?"commonjs2":undefined,},externals:TARGET_NODE?nodeExternals({allowlist:[/\.css$/],}):undefined,optimization:{splitChunks:undefined,},//这是一个将服务器的整个输出构建到单个JSON文件中的插件。//服务器的默认文件名是`vue-ssr-server-bundle.json`//客户端默认文件名为“vue-ssr-client-manifest.json”。插件:[TARGET_NODE?newVueSSRServerPlugin():newVueSSRClientPlugin(),],}),chainWebpack:(config)=>{//cli4项目添加if(TARGET_NODE){config.optimization.delete("splitChunks");}config.module.rule("vue").use("vue-loader").tap((options)=>{merge(options,{optimizeSSR:false,});});},};处理CSS对于正常的服务器端路由,我们可以这样写:router.get("/",async(ctx)=>{ctx.body=awaitrender.renderToString();});但是这样打包之后,启动服务器,你会发现样式并没有生效。我们需要通过promise来解决这个问题:pp.use(async(ctx)=>{try{ctx.body=awaitnewPromise((resolve,reject)=>{render.renderToString({url:ctx.url},(err,data)=>{console.log("data",data);如果(err)reject(err);resolve(data);});});}catch(error){ctx.body="404";}});事件不生效的原因是我们没有激活客户端,即将客户端打包的clientBundle.js挂载到html中。首先,我们需要在App.vue的根节点添加app的id:
