当前位置: 首页 > 后端技术 > PHP

用PHP做Vue.js的SSR服务端渲染

时间:2023-03-29 14:20:26 PHP

对于客户端应用来说,服务端渲染是一个热门话题。不幸的是,这不是一件容易的事,特别是对于那些不在Node.js环境中开发的人来说。我发布了两个库来使PHP服务器端渲染成为可能。laravel应用程序的spatie/server-side-rendering和spatie/laravel-server-side-rendering。让我们仔细看看一些服务器端渲染的概念,权衡利弊,并遵循首要原则使用PHP构建服务器端渲染。什么是服务器端渲染单页应用程序(通常也称为SPA)是客户端渲染的应用程序。这是一个仅限浏览器的应用程序。如果您使用的是React、Vue.js或AngularJS等框架,客户端将从头开始呈现您的应用程序。浏览器如何工作在SPA启动并准备好使用之前,浏览器会经历几个步骤。下载JavaScript脚本解析JavaScript脚本运行JavaScript脚本检索数据(可选,但很常见)在空容器中渲染应用程序(第一次有意义的渲染)准备完成!(现在是交互式的)在浏览器完全呈现应用程序(这需要一点时间)之前,用户看不到任何有意义的东西。这会在第一次有意义的渲染完成之前造成明显的延迟,从而影响用户体验。这就是服务器端渲染(通常称为SSR)发挥作用的原因。SSR在服务器上预呈现初始应用程序状态。以下是浏览器在使用服务器端呈现后所经历的步骤:从服务器呈现HTML(第一个有意义的呈现)下载JavaScript脚本解析JavaScript脚本运行JavaScript脚本以检索数据使现有HTML页面具有交互性准备就绪!(现在交互式)由于服务器提供了预呈现的HTML块,用户不必等到一切都完成才能看到有意义的内容。请注意,虽然交互时间仍处于末尾,但感知性能已大大提高。服务端渲染的优势服务端渲染的主要优势在于可以提升用户体验。此外,如果您的网站需要处理无法执行JavaScript的旧爬虫,SSR将是必要的,以便爬虫可以索引服务器呈现的页面而不是空文档。如何在服务器端渲染?重要的是要记住服务器端渲染并非微不足道。当您的Web应用程序同时在浏览器和服务器上运行时,并且您的Web应用程序依赖于DOM访问,那么您需要确保这些调用不会在服务器端触发,因为没有可用的DOMAPI。基础设施复杂性假设您决定在服务器端呈现您的应用程序,如果您正在阅读本文,您可能正在用PHP构建您的大部分应用程序。但是,服务器渲染的SPA需要在Node.js环境中运行,因此需要维护第二个程序。您需要在两个应用程序之间建立一座桥梁,以便它们可以通信和共享数据:您需要一个API。构建无状态API比构建有状态API更难。您需要熟悉一些新概念,例如基于JWT或OAUTH的身份验证、CORS、REST,将这些添加到现有应用程序中很重要。得到的就是失去的,我们构建SSR是为了增加web应用的用户体验,但是SSR是有成本的。服务器端渲染权衡是在服务器上进行的额外操作。一是服务器增加了负载压力,二是页面响应时间也会稍微变长。但是因为服务器现在正在返回有效内容,所以从用户的角度来看,第二个问题并不重要。大多数时候,您将使用Node.js来呈现您的SPA代码。如果您的后端代码不是用Javascript编写的,添加Node.js堆栈会使您的应用程序架构复杂化。为了简化基础架构的复杂性,我们需要找到一种方法,将现有的PHP环境用作服务器来呈现客户端应用程序。在PHP中渲染JavaScript在服务器上渲染SPA需要以下三样东西:一个可以执行JavaScript的引擎,一个可以在服务器上渲染应用程序的脚本,以及一个可以在客户端渲染和运行应用程序的脚本。SSR脚本101以下示例使用Vue.js。如果你习惯使用其他框架(比如React),不用担心,它们的核心思想是相似的,一切看起来都是那么的相似。为简单起见,我们使用经典的“HelloWorld”示例。这是程序代码(没有SSR)://app.jsimportVuefrom'vue'newVue({template:`

Hello,world!
`,el:'#app'})Thisshort该代码实例化了一个Vue组件并将其呈现在一个容器中(一个id值为app的空div)。如果你在服务器端运行这个脚本,它会抛出一个错误,因为没有DOM可以访问,而Vue正试图在一个不存在的元素中渲染应用程序。重构此脚本,使其可以在服务器上运行。//app.jsimportVuefrom'vue'exportdefault()=>newVue({template:`
Hello,world!
`})//entry-client.jsimportcreateAppfrom'./app'constapp=createApp()app.$mount('#app')我们把前面的代码分成两部分。app.js充当创建应用程序实例的工厂,第二部分entry-client.js将运行在浏览器中,它使用工厂创建应用程序实例并将它们挂载到DOM上。现在我们可以创建一个没有DOM依赖项的应用程序,我们可以为服务器端编写第二个脚本。//entry-server.jsimportcreateAppfrom'./app'importrenderToStringfrom'vue-server-renderer/basic'constapp=createApp()renderToString(app,(err,html)=>{if(err){thrownewError(err)}//DispatchtheHTMLstringtotheclient...})我们导入相同的应用程序工厂,但我们使用服务器端呈现来呈现一个纯HTML字符串,它将包含应用程序的初始状态.我们已经有了三个关键元素中的两个:服务器端脚本和客户端脚本。现在,让我们在PHP上运行它!执行JavaScript在PHP中运行JavaScript,第一个想到的选项是V8Js。V8Js是嵌入在PHP扩展中的V8引擎,它允许我们执行JavaScript。使用V8Js执行脚本非常简单。我们可以在PHP中使用输出缓冲捕获结果并在JavaScript中打印。$v8=newV8Js();ob_start();//$script包含我们要执行的脚本内容$v8->executeString($script);echoob_get_contents();print('
Hello,world!
')这种方法的缺点是它需要第三方PHP扩展,这可能很难或不可能安装在您的系统上,因此如果有其他(不需要安装扩展)方法,它将是更好的选择。不同的方法是使用Node.js来运行JavaScript。我们可以启动一个运行脚本并捕获输出的节点进程。Symfony的Process组件正是我们想要的。useSymfony\Component\Process\Process;//$nodePath是可执行的Node.js的路径//$scriptPath是你要执行的JavaScript脚本的路径newProcess([$nodePath,$scriptPath]);echo$process->mustRun()->getOutput();console.log('
Hello,world!
')请注意,(打印)调用console.log而不是Node中的打印。让我们一起实现它!spatie/server-side-rendering包的关键思想之一是引擎接口。引擎是上述JavaScript执行的抽象。namespaceSpatie\Ssr;/***创建引擎接口。*/interfaceEngine{publicfunctionrun(string$script):string;publicfunctiongetDispatchHandler():string;}run方法需要脚本输入(脚本内容,不是路径),并返回执行结果。getDispatchHandler允许引擎声明它期望脚本如何显示调度。例如V8中的print方法,或Node中的console.log。V8Js引擎实现起来不是很花哨。它更类似于我们上面想法的验证,多了一些错误处理机制。namespaceSpatie\Ssr\Engines;useV8Js;useV8JsException;useSpatie\Ssr\Engine;useSpatie\Ssr\Exceptions\EngineError;/***创建一个V8类来实现引擎接口类Engine。*/classV8实现引擎。{/**@var\V8Js*/protected$v8;公共函数__construct(V8Js$v8){$this->v8=$v8;}/***打开缓冲区。*返回存储v8脚本处理结果的缓冲区。*/publicfunctionrun(string$script):string{try{ob_start();$this->v8->executeString($script);返回ob_get_contents();}catch(V8JsException$exception){抛出EngineError::withException($exception);}最后{ob_end_clean();}}publicfunctiongetDispatchHandler():string{return'print';请注意,这里我们将V8JsException作为我们的EngineError重新抛出。这样我们就可以在任何引擎视线范围内捕获相同的异常。Node引擎有点复杂。与V8Js不同,Node需要文件来执行,而不是脚本内容。在执行服务器端脚本之前,需要将其保存到一个临时目录中。namespaceSpatie\Ssr\Engines;useSpatie\Ssr\Engine;useSpatie\Ssr\Exceptions\EngineError;useSymfony\Component\Process\Process;useSymfony\Component\Process\Exception\ProcessFailedException;/***创建节点类实现引擎接口类Engine。*/classNodeimplementsEngine{/**@varstring*/protected$nodePath;/**@varstring*/protected$tempPath;公共函数__construct(string$nodePath,string$tempPath){$this->nodePath=$nodePath;$this->tempPath=$tempPath;}publicfunctionrun(string$script):string{//生成一个随机的、唯一的临时文件路径。$tempFilePath=$this->createTempFilePath();//在临时文件中写入脚本内容。file_put_contents($tempFilePath,$script);//为流程执行创建一个临时文件。$process=newProcess([$this->nodePath,$tempFilePath]);尝试{returnsubstr($process->mustRun()->getOutput(),0,-1);}catch(ProcessFailedException$exception){抛出EngineError::withException($exception);}最后{取消链接($tempFilePath);}}publicfunctiongetDispatchHandler():string{return'console.log';}protectedfunctioncreateTempFilePath():string{return$this->tempPath.'/'.md5(time()).'.js';}}除了临时路径步骤之外,实现看起来也相当简单。我们已经创建了Engine接口,接下来我们需要编写渲染类。下面的渲染类来自spatie/server-side-rendering扩展包,是最基本的渲染类结构。渲染类的唯一依赖是Engine接口的实现:classRenderer{publicfunction__construct(Engine$engine){$this->engine=$engine;}}渲染方法render会处理渲染部分的逻辑,想要执行一个JavaScript脚本文件需要以下两个要素:我们的应用程序脚本文件;获取解析后的HTML的分发方法;一个简单的渲染如下:$条目),]);返回$this->engine->run($serverScript);}}此方法接受entry-server.js文件路径作为参数。我们需要将未解析的HTML从脚本分发到PHP环境。dispatch方法返回Engine类中的getDispatchHandler方法,dispatch需要在服务器脚本加载前运行。还记得我们的服务器端入口脚本吗?接下来我们在这个脚本中调用我们的调度方法://entry-server.jsimportappfrom'./app'importrenderToStringfrom'vue-server-renderer/basic'renderToString(app,(err,html)=>{if(err){thrownewError(err)}dispatch(html)})Vue的应用脚本不需要特殊处理,直接使用file_get_contents方法读取文件即可。我们已经成功创建了一个PHPSSR。spatie/server-side-rendering中完整的渲染器Renderer和我们的实现有点不一样。它们具有更高的容错性和更丰富的功能,例如在PHP和JavaScript之间共享数据的机制。如果您有兴趣,我建议您阅读源服务器端渲染代码库。三思而后行我们了解了服务器端渲染的优缺点,知道SSR会增加应用程序架构和基础架构的复杂性。如果服务器端渲染没有为您的业务提供任何价值,那么您可能一开始就不应该考虑它。如果你真的想开始使用服务器端渲染,请先阅读应用程序的架构。大多数JavaScript框架都有关于SSR的深入指南。Vue.js甚至有一个专门的SSR文档站点来解释诸如数据获取和管理应用程序以进行服务器端渲染等陷阱。如果可能,使用经过实战检验的解决方案有许多经过实战检验的解决方案可以提供出色的SSR开发体验。例如,如果您正在构建React应用程序,则使用Next.js;如果您更喜欢Vue,则使用Nuxt.js,这些都是引人注目的项目。不够?尝试使用PHP服务器端呈现,您将拥有有限的资源来管理基础架构的复杂性。您希望将服务器端呈现作为大型PHP应用程序的一部分。您不想构建和维护无状态API。如果这些原因适用于您的情况,那么使用PHP进行服务器端渲染将是一个很好的解决方案。我已经发布了两个库来支持PHP中的服务器端JavaScript渲染:用于Laravel应用程序的spatie/server-side-rendering和spatie/laravel-server-side-rendering。Laravel定制版在Laravel应用中几乎零配置即可投入使用,通用版则需要根据运行环境进行调整。当然,你可以参考包的readme文件了解详情。如果你只是想试验一下,从spatie/laravel-server-side-rendering-examples查看项目并按照指南安装它。如果您正在考虑服务器端渲染,我希望这种类型的包对您有所帮助,并期待通过Github进一步提问和反馈!如需更多现代PHP知识,请访问Laravel/PHP知识社区