1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
use rusty_v8 as v8;
use std::collections::HashMap;
pub struct Ssr {}
impl Ssr {
fn init_platform() {
lazy_static! {
static ref INIT_PLATFORM: () = {
//Initialize a new V8 platform
let platform = v8::new_default_platform().unwrap();
v8::V8::initialize_platform(platform);
v8::V8::initialize();
};
}
lazy_static::initialize(&INIT_PLATFORM);
}
/// Evaluates the javascript source code passed and runs the render functions.
/// Any initial params (if needed) must be passed as JSON using <a href="https://crates.io/crates/serde_json" target="_blank">serde_json</a>.
///
/// <a href="https://github.com/Valerioageno/ssr-rs/blob/main/examples/actix_with_initial_props.rs" target="_blank">Here</a> an useful example of how to use initial params with the actix framework.
///
/// "enrty_point" is the variable name set from the frontend bundler used. <a href="https://github.com/Valerioageno/ssr-rs/blob/main/client/webpack.ssr.js" target="_blank">Here</a> an example from webpack.
pub fn render_to_string(source: &str, entry_point: &str, params: Option<&str>) -> String {
Self::init_platform();
{
//The isolate rapresente an isolated instance of the v8 engine
//Object from one isolate must not be used in other isolates.
let isolate = &mut v8::Isolate::new(Default::default());
//A stack-allocated class that governs a number of local handles.
let handle_scope = &mut v8::HandleScope::new(isolate);
//A sandboxed execution context with its own set of built-in objects and functions.
let context = v8::Context::new(handle_scope);
//Stack-allocated class which sets the execution context for all operations executed within a local scope.
let scope = &mut v8::ContextScope::new(handle_scope, context);
let code = v8::String::new(scope, &[source, entry_point].concat())
.expect("Strings are needed");
let script =
v8::Script::compile(scope, code, None).expect("There aren't runnable scripts");
let exports = script.run(scope).expect("Nothing to export");
let exports = exports.to_object(scope).expect("There are no objects");
let mut fn_map: HashMap<String, v8::Local<v8::Function>> = HashMap::new();
if let Some(props) = exports.get_own_property_names(scope) {
for i in 0..props.length() {
let name = props.get_index(scope, i).unwrap();
//A HandleScope which first allocates a handle in the current scope which will be later filled with the escape value.
let mut scope = v8::EscapableHandleScope::new(scope);
let func = exports.get(&mut scope, name).unwrap();
let func = unsafe { v8::Local::<v8::Function>::cast(func) };
fn_map.insert(
name.to_string(&mut scope)
.unwrap()
.to_rust_string_lossy(&mut scope),
scope.escape(func),
);
}
}
let params: v8::Local<v8::Value> = match v8::String::new(scope, params.unwrap_or("")) {
Some(s) => s.into(),
None => v8::undefined(scope).into(),
};
let undef = v8::undefined(scope).into();
let mut rendered = String::new();
for key in fn_map.keys() {
let result = fn_map[key].call(scope, undef, &[params]).unwrap();
let result = result.to_string(scope).unwrap();
rendered = format!("{}{}", rendered, result.to_rust_string_lossy(scope));
}
rendered
}
}
}