前几天看了社区翻译的一篇文章《使用 PHP 来做 Vue.js 的 SSR 服务端渲染》,文章讲述的是现在很火的 JavaScript 服务端渲染(SSR),恰巧我对这方面略有研究,所以就趁着热闹写上一篇。
得益于 Google V8 引擎,服务端运行 JavaScript,除了正统的 Node.js 以外,还有其他语言各自实现的 V8 扩展库,这是解决 JavaScript 服务端运行最有效的办法。不过 V8 扩展和 Node 还是有许多不同,除了缺少 Node API,还包括一些优化(如事件驱动、异步 I/O)。
你可能看不懂上面这段描述,不妨,我们接着科普几个概念。
V8 是什么?
V8 是 Google 开源的 JavaScript 引擎(C++ 编写),用于 Chrome 系列浏览器。JavaScript 引擎是用于运行 JavaScript 脚本的虚拟机(解析器),实现了核心的 ECMAScript 语法解析功能,但不包括 WEB API(DOM/CSSOM 等)和 Node API 等。
V8 和 Node.js 的关系
Node.js 是基于 V8 引擎实现的可用于服务端 JavaScript 的运行环境,为了方便服务端应用,提供了一系列 API。除了 V8 之外,Node.js 还引进了 libuv 来处理事件驱动、异步 I/O。
V8 和 ext-v8js 扩展库的关系
ext-v8js 是基于 V8 实现的 PHP 扩展库,可用于 PHP 服务端执行 JavaScript 脚本。JavaScript 运行于安全沙盒中,可以通过注入和导出实现上下文数据通信。ext-v8js 只实现了核心的 ECMAScript 语法解析功能,不包含 Node API 和事件驱动、异步 I/O 处理,如果需要这方面特性,可以结合 ext-ev(libev)/ext-event(libevent)扩展库实现,如 reactphp、amphp 等。PHP 提供的事件驱动相关的扩展库有很多(libev、libuv、libevent ),推荐使用基于 libevent 实现的 ext-event。
概念性的就说到这里,接下来简单介绍如何安装 v8js 扩展库。v8js 是我见过最难安装的扩展库,安装扩展前需要安装 v8 引擎,但是因为各种原因(此处省略 800 字),建议想尝鲜的朋友使用 Docker 环境。分享个早先我编译好的 docker-v8,可以用于 Debian Jessie。另外,Ubuntu 还可以用 pinepain 的 libv8 ,Mac OS 也可以用 brew 安装。
业务需要,折腾 v8js 也有段时间了,但是 v8js 太原始,所以我写了 chenos/v8js-module-loader 和 chenos/execjs,感兴趣的可以 star 一下。
好了,我们进入正题。先来看看 Node.js 是怎么做的,以 vue.js 为例。
// hello.js
const Vue = require('vue')
const renderer = require('vue-server-renderer')
const renderVueComponentToString = require('vue-server-renderer/basic')
const app = new Vue({
template: `<div>Hello Vue!</div>`
})
renderVueComponentToString(app, (err, html) => {
console.log(html)
})
运行结果如下:
$ node hello.js
# <div data-server-rendered="true">Hello Vue!</div>
那在 PHP 里应该怎么做呢?以 chenos/execjs 为例:
<?php
// hello.php
// 初始化 JS 安全沙盒运行环境
$context = new Chenos\ExecJs\Context();
// 指定 module loader 的路径
$context->getLoader()
->setEntryDir(__DIR__)
->addVendorDir(__DIR__.'/node_modules')
->addOverride('vue', 'vue/dist/vue.runtime.common.js')
;
// 声明全局变量 & 函数
$context->eval("
global.process = {env: {VUE_ENV: 'server'}}
global.console = {log: print}
");
// 必须加上 ./ 表示相对路径
$context->load('./hello.js');
echo PHP_EOL;
运行结果如下:
$ php hello.php
# <div data-server-rendered="true">Hello Vue!</div>
这个例子太简单了,增加一下难度吧。我们声明一个 Vue 类用来处理相关操作。
class Vue
{
public function __construct()
{
$context = new Chenos\ExecJs\Context();
// 指定 module loader 的路径
$context->getLoader()
->setEntryDir(__DIR__)
->addVendorDir(__DIR__.'/node_modules')
->addOverride('vue', 'vue/dist/vue.runtime.common.js')
;
// 声明全局变量 & 函数
$context->eval("
global.process = {env: {VUE_ENV: 'server'}}
global.console = {log: print}
");
$this->context = $context;
}
public function render($component, $propsData = [])
{
$this->context->component = $component;
$this->context->propsData = $propsData;
$this->context->eval("
var renderVueComponentToString = require('vue-server-renderer/basic')
var Component = require(PHP.component)
var component = new Component({ propsData: PHP.propsData })
renderVueComponentToString(component, (err, html) => {
console.log(html)
})
");
echo PHP_EOL;
}
}
添加一个简单的 hello.js
组件,需要注意的是如果是 vue 或者 jsx 模板需要通过 webpack/rollup 打包再运行编译过的 JS 文件。
const Vue = require('vue')
module.exports = Vue.extend({
template: `<div class="hello">
<h1 class="hello__title">Hello {{ msg }}!</h1>
</div>`,
props: ['msg'],
})
调用:
// vue.php
$vue = new Vue();
$vue->render('./hello.js', ['msg' => 'Vuejs']);
运行结果:
$ php vue.php
# <div data-server-rendered="true" class="hello"><h1 class="hello__title">Hello Vuejs!</h1></div>
引申到框架层面,只要实现各自框架的 View 接口,我们就可以在任意框架里实现 Vue/React 组件的服务端渲染了,而并非只能用 twig/php/html 之类的模板引擎了。
PHP 部分的核心思想也就这些了,更多的可能与 Node 有关,如果有相关的 SSR 经历,会很容易理解上述代码。不知道我讲清楚了没有,大家看懂代码了没有,更多例子 点击这里查看。
接下来说个大家比较关注的性能问题,业务需要,我们的一些项目前端用了 markdown-it/js-yaml 这些非常好用的 JS 库,最关键的是它们可以满足我们自定义方面的需求,但是后端苦苦找不到合适的第三方库,直到我们尝试了 v8js 方案,解析效率比 PHP 版本好太多,最关键的是前后代码同构,我们只需要维护前端库就可以了。
性能方面,我用了几个常用的 npm package 做了简单的 time 测试,直接放结论:
ext-v8js > node.js >> php
对细节感兴趣的可以 点击这里查看。
在 PHP 端执行 JavaScript 代码,不是个常规需求,但是合理使用能够解决一些痛点需求,尤其在如今越来越多的业务逻辑前移到前端之后,当你眼馋 NPM 上大量 JS 库不能跨平台使用的时候,我们并不需要另立一个 Node 服务器,使用 ext-v8js 扩展也能够解决这类问题。
Original url: Access
Created at: 2018-10-10 18:19:09
Category: default
Tags: none
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
java windows火焰图_mob64ca12ec8020的技术博客_51CTO博客 - 在windows下不可行,不知道作者是怎样搞的 监听SpringBoot 服务启动成功事件并打印信息_监听springboot启动完毕-CSDN博客 SpringBoot中就绪探针和存活探针_management.endpoint.health.probes.enabled-CSDN博客 u2u转换板 - 嘉立创EDA开源硬件平台 Spring Boot 项目的轻量级 HTTP 客户端 retrofit 框架,快来试试它!_Java精选-CSDN博客 手把手教你打造一套最牛的知识笔记管理系统! - 知乎 - 想法有重合-理论可参考 安宇雨 闲鱼 机械键盘 客制化 开贴记录 文本 linux 使用find命令查找包含某字符串的文件_beijihukk的博客-CSDN博客_find 查找字符串 ---- mac 也适用 安宇雨 打字音 记录集合 B站 bilibili 自行搭建 开坑 真正的客制化 安宇雨 黑苹果开坑 查找工具包maven pom 引用地 工具网站 Dantelis 介绍的玩轴入坑攻略 --- 关于轴的一些说法 --- 非官方 ---- 心得而已 --- 长期开坑更新 [本人问题][新开坑位]关于自动化测试的工具与平台应用 机械键盘 开团 网站记录 -- 能做一个收集的程序就好了 不过现在没时间 -- 信息大多是在群里发的 - 你要让垃圾佬 都去一个地方看难度也是很大的 精神支柱 [超级前台]sprinbboot maven superdesk-app 记录 [信息有用] [环境准备] [基本完成] [sebp/elk] 给已创建的Docker容器增加新的端口映射 - qq_30599553的博客 - CSDN博客 [正在研究] Elasticsearch, Logstash, Kibana (ELK) Docker image documentation elasticsearch centos 安装记录 及 启动手记 正式服务器 39 elasticsearch 问题合集 不断更新 6.1.1 | 6.5.1 两个版本 博客程序 - 测试 - bug记录 等等问题 laravel的启动过程解析 - lpfuture - 博客园 OAuth2 Server PHP 用 Laravel 搭建带 OAuth2 验证的 RESTful 服务 | Laravel China 社区 - 高品质的 Laravel 和 PHP 开发者社区 利用Laravel 搭建oauth2 API接口 附 Unauthenticated 解决办法 - 煮茶的博客 - SegmentFault 思否 使用 OAuth2-Server-php 搭建 OAuth2 Server - 午时的海 - 博客园 基于PHP构建OAuth 2.0 服务端 认证平台 - Endv - 博客园 Laravel 的 Artisan 命令行工具 Laravel 的文件系统和云存储功能集成 浅谈Chromium中的设计模式--终--Observer模式 浅谈Chromium中的设计模式--二--pre/post和Delegate模式 浅谈Chromium中的设计模式--一--Chromium中模块分层和进程模型 DeepMind 4 Hacking Yourself README.md update 20211011
Laravel China 简书 知乎 博客园 CSDN博客 开源中国 Go Further Ryan是菜鸟 | LNMP技术栈笔记 云栖社区-阿里云 Netflix技术博客 Techie Delight Linkedin技术博客 Dropbox技术博客 Facebook技术博客 淘宝中间件团队 美团技术博客 360技术博客 古巷博客 - 一个专注于分享的不正常博客 软件测试知识传播 - 测试窝 有赞技术团队 阮一峰 语雀 静觅丨崔庆才的个人博客 软件测试从业者综合能力提升 - isTester IBM Java 开发 使用开放 Java 生态系统开发现代应用程序 pengdai 一个强大的博主 HTML5资源教程 | 分享HTML5开发资源和开发教程 蘑菇博客 - 专注于技术分享的博客平台 个人博客-leapMie 流星007 CSDN博客 - 舍其小伙伴 稀土掘金 Go 技术论坛 | Golang / Go 语言中国知识社区
最新评论