Created
May 13, 2021 03:20
-
-
Save zengxs/f35fe4f18bb65641c9ef12c937f45be7 to your computer and use it in GitHub Desktop.
rust server side render (ssr) bench
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div id="app"> | |
<h1>Hello</h1> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: "App", | |
}; | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
}); | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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