Skip to content

Instantly share code, notes, and snippets.

@zengxs
Created May 13, 2021 03:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zengxs/f35fe4f18bb65641c9ef12c937f45be7 to your computer and use it in GitHub Desktop.
Save zengxs/f35fe4f18bb65641c9ef12c937f45be7 to your computer and use it in GitHub Desktop.
rust server side render (ssr) bench
<template>
<div id="app">
<h1>Hello</h1>
</div>
</template>
<script>
export default {
name: "App",
};
</script>
use std::time::SystemTime;
static PREPARE_CODE: &str = r#"
var process = { env: { VUE_ENV: "server", NODE_ENV: "production" } };
this.global = { process: process };
"#;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
struct RenderRequest {
id: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
struct RenderResponse {
id: String,
body: String,
}
fn main() {
let context = quick_js::Context::new().unwrap();
context.eval(PREPARE_CODE).unwrap();
context.eval(include_str!("../../../actix-v8/dist/main.js")).unwrap();
let render = move |req| {
let req = serde_json::to_string::<RenderRequest>(&req).unwrap();
let req = quick_js::JsValue::from(req);
let resp = context.call_function("render", vec![req]).unwrap();
if let quick_js::JsValue::String(resp) = resp {
let resp = serde_json::from_str::<RenderResponse>(&resp).unwrap();
resp
} else {
panic!("")
}
};
let now = SystemTime::now();
let times = 1;
for _ in 0..times {
let req = RenderRequest::new();
let req_clone = req.clone();
let resp = render(req_clone);
assert_eq!(req.id, resp.id);
}
let cost = SystemTime::now().duration_since(now).unwrap();
let cost = cost.as_secs_f64() * 1000_f64;
println!("total cost {:.3} ms", cost);
println!("average cost {:.3} ms", cost / times as f64);
}
impl RenderRequest {
fn new() -> Self {
let u = uuid::Uuid::new_v4();
Self {
id: data_encoding::BASE32_NOPAD.encode(u.as_bytes()).to_ascii_lowercase()
}
}
}
use std::convert::TryFrom;
use std::time::SystemTime;
use data_encoding::BASE32_NOPAD;
use rusty_v8 as v8;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug)]
struct Renderer {
isolate: v8::OwnedIsolate,
global_context: v8::Global<v8::Context>,
}
#[derive(Debug, Serialize, Deserialize)]
struct RenderRequest {
id: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct RenderResponse {
id: String,
body: String,
}
fn main() {
let platform = v8::new_default_platform().unwrap();
v8::V8::initialize_platform(platform);
v8::V8::initialize();
v8::V8::set_flags_from_string("--jitless");
// 这里创建一个作用域的作用是: 在退出作用域时, 自动回收 renderer
// 否则 renderer 将会在 v8::V8::dispose 之后才被回收, 这将会产生错误
{
let mut renderer = Renderer::new();
// isolate 不是线程安全的,因此只能在单线程使用
// 考虑使用 mpsc 队列来实现并发
let now = SystemTime::now();
let times = 10000;
for _ in 0..times {
let req = RenderRequest::new();
let resp = renderer.render(&req);
assert_eq!(req.id, resp.id);
}
let cost = SystemTime::now().duration_since(now).unwrap();
let cost = cost.as_secs_f64() * 1000_f64;
println!("{} times render cost {:.2} ms", times, cost);
println!("average time cost is {:.2} ms", cost / times as f64);
}
unsafe {
v8::V8::dispose();
}
v8::V8::shutdown_platform();
}
impl RenderRequest {
fn new() -> Self {
let uuid = Uuid::new_v4();
RenderRequest {
id: BASE32_NOPAD.encode(uuid.as_bytes()).to_ascii_lowercase(),
}
}
}
impl Renderer {
pub fn new() -> Renderer {
let mut isolate = v8::Isolate::new(v8::CreateParams::default());
let global_context;
{
// 启动一个新作用域, 在这个作用域中才可以使用 isolate
// 因为作用域结束时, 生命周期完结, 被借用的 isolate 会自动归还
let scope = &mut v8::HandleScope::new(&mut isolate);
let context = v8::Context::new(scope);
let scope = &mut v8::ContextScope::new(scope, context);
global_context = v8::Global::new(scope, context);
}
let mut renderer = Renderer {
isolate,
global_context,
};
renderer.run(r#"
var process = { env: { VUE_ENV: "server", NODE_ENV: "production" } };
this.global = { process: process };
"#);
renderer.run(include_str!("../../dist/main.js"));
renderer
}
pub fn run(&mut self, script: &str) {
let scope = &mut v8::HandleScope::with_context(&mut self.isolate, &self.global_context);
let script = v8::String::new(scope, script).unwrap();
let script = v8::Script::compile(scope, script, None).unwrap();
script.run(scope).unwrap();
}
pub fn render(&mut self, req: &RenderRequest) -> RenderResponse {
// 从 isolate 中获取 scope
let scope = &mut v8::HandleScope::with_context(&mut self.isolate, &self.global_context);
// 从 global_context 中获取 context, 然后从 context 中获取 global 对象
let global = self.global_context.get(scope).global(scope);
// 从 v8 隔离实例中获取 render 函数
let render_name = v8::String::new(scope, "render").unwrap();
let render = global.get(scope, render_name.into()).unwrap();
let render = v8::Local::<v8::Function>::try_from(render).unwrap();
let recv = v8::String::empty(scope);
// 调用 render 函数并获取响应结果
let req = serde_json::to_string(req).unwrap();
let arg = v8::String::new(scope, &req).unwrap();
let promise = render.call(scope, recv.into(), &[arg.into()]).unwrap();
let promise = v8::Local::<v8::Promise>::try_from(promise).unwrap();
let result = promise.result(scope);
let result = result.to_rust_string_lossy(scope);
serde_json::from_str(&result).unwrap()
}
}
use std::convert::TryFrom;
use std::time::SystemTime;
use data_encoding::BASE32_NOPAD;
use rusty_v8 as v8;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug)]
struct Renderer {
isolate: v8::OwnedIsolate,
global_context: v8::Global<v8::Context>,
}
#[derive(Debug, Serialize, Deserialize)]
struct RenderRequest {
id: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct RenderResponse {
id: String,
body: String,
}
fn main() {
let platform = v8::new_default_platform().unwrap();
v8::V8::initialize_platform(platform);
v8::V8::initialize();
// 这里创建一个作用域的作用是: 在退出作用域时, 自动回收 renderer
// 否则 renderer 将会在 v8::V8::dispose 之后才被回收, 这将会产生错误
{
let mut renderer = Renderer::new();
// isolate 不是线程安全的,因此只能在单线程使用
// 考虑使用 mpsc 队列来实现并发
let now = SystemTime::now();
let times = 10000;
for _ in 0..times {
let req = RenderRequest::new();
let resp = renderer.render(&req);
assert_eq!(req.id, resp.id);
}
let cost = SystemTime::now().duration_since(now).unwrap();
let cost = cost.as_secs_f64() * 1000_f64;
println!("{} times render cost {:.2} ms", times, cost);
println!("average time cost is {:.2} ms", cost / times as f64);
}
unsafe {
v8::V8::dispose();
}
v8::V8::shutdown_platform();
}
impl RenderRequest {
fn new() -> Self {
let uuid = Uuid::new_v4();
RenderRequest {
id: BASE32_NOPAD.encode(uuid.as_bytes()).to_ascii_lowercase(),
}
}
}
impl Renderer {
pub fn new() -> Renderer {
let mut isolate = v8::Isolate::new(v8::CreateParams::default());
let global_context;
{
// 启动一个新作用域, 在这个作用域中才可以使用 isolate
// 因为作用域结束时, 生命周期完结, 被借用的 isolate 会自动归还
let scope = &mut v8::HandleScope::new(&mut isolate);
let context = v8::Context::new(scope);
let scope = &mut v8::ContextScope::new(scope, context);
global_context = v8::Global::new(scope, context);
}
let mut renderer = Renderer {
isolate,
global_context,
};
renderer.run(r#"
var process = { env: { VUE_ENV: "server", NODE_ENV: "production" } };
this.global = { process: process };
"#);
renderer.run(include_str!("../../dist/main.js"));
renderer
}
pub fn run(&mut self, script: &str) {
let scope = &mut v8::HandleScope::with_context(&mut self.isolate, &self.global_context);
let script = v8::String::new(scope, script).unwrap();
let script = v8::Script::compile(scope, script, None).unwrap();
script.run(scope).unwrap();
}
pub fn render(&mut self, req: &RenderRequest) -> RenderResponse {
// 从 isolate 中获取 scope
let scope = &mut v8::HandleScope::with_context(&mut self.isolate, &self.global_context);
// 从 global_context 中获取 context, 然后从 context 中获取 global 对象
let global = self.global_context.get(scope).global(scope);
// 从 v8 隔离实例中获取 render 函数
let render_name = v8::String::new(scope, "render").unwrap();
let render = global.get(scope, render_name.into()).unwrap();
let render = v8::Local::<v8::Function>::try_from(render).unwrap();
let recv = v8::String::empty(scope);
// 调用 render 函数并获取响应结果
let req = serde_json::to_string(req).unwrap();
let arg = v8::String::new(scope, &req).unwrap();
let promise = render.call(scope, recv.into(), &[arg.into()]).unwrap();
let promise = v8::Local::<v8::Promise>::try_from(promise).unwrap();
let result = promise.result(scope);
let result = result.to_rust_string_lossy(scope);
serde_json::from_str(&result).unwrap()
}
}
import Vue from "vue";
import renderVueComponentToString from "vue-server-renderer/basic"
import App from "./App.vue";
global.render = function (request) {
const req = JSON.parse(request);
const app = new Vue({
render: h => h(App),
});
return new Promise((resolve, reject) => {
renderVueComponentToString(app, (err, res) => {
const resp = {
id: req.id,
body: res,
}
resolve(JSON.stringify(resp));
});
});
}
const {resolve} = require('path');
const {VueLoaderPlugin} = require('vue-loader');
module.exports = {
mode: 'production',
entry: resolve(__dirname, 'main.js'),
output: {
path: resolve(__dirname, 'dist'),
filename: '[name].js'
},
module: {
rules: [
{test: /\.vue$/, exclude: /node_modules/, loader: 'vue-loader'},
{test: /\.m?js$/, exclude: /node_modules/, loader: 'babel-loader', options: {presets: [['@babel/preset-env']]}},
{test: /\.css$/, exclude: /node_modules/, use: ['css-loader']},
],
},
plugins: [
new VueLoaderPlugin(),
],
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment