diff --git a/README.md b/README.md
index 7d7b403..f45b393 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ The project aims to enable server side rendering on rust servers in the simplest
It use an embedded version of the v8 javascript engine (rusty_v8) to parse and evaluate a built bundle file and return a string with the rendered html.
-Currently it works with Webpack bundler v5.65.0; check it out here a full project who use this crate.
+Currently it works with Webpack bundler v5.65.0.
## Getting started
@@ -15,7 +15,7 @@ Add this to your `Cargo.toml`:
```toml
[dependencies]
-ssr_rs = "0.2.3"
+ssr_rs = "0.3.0"
```
## Example
@@ -32,9 +32,9 @@ fn main() {
let source = read_to_string("./path/to/build.js").unwrap();
- let mut js = Ssr::new(&source, "entryPoint");
+ let mut js = Ssr::new(&source, "entryPoint").unwrap();
- let html = js.render_to_string(None);
+ let html = js.render_to_string(None).unwrap();
assert_eq!(html, "...".to_string());
}
@@ -60,14 +60,65 @@ fn main() {
let source = read_to_string("./path/to/build.js").unwrap();
- let mut js = Ssr::new(&source, "entryPoint");
+ let mut js = Ssr::new(&source, "entryPoint").unwrap();
- let html = js.render_to_string(Some(&props));
+ let html = js.render_to_string(Some(&props)).unwrap();
assert_eq!(html, "...".to_string());
}
```
+## Example with actix-web
+
+> Examples with different web frameworks are available in the examples folder.
+
+Even though the V8 engine allows accessing the same `isolate` from different threads that is forbidden by this crate for two reasons:
+
+1. rusty_v8 library have not implemented yet the V8 Locker API. Accessing Ssr struct from a different thread will make the V8 engine to panic.
+2. Rendering HTML does not need shared state across threads.
+
+For this reason parallel computation is a better choice. Following actix-web setup:
+
+```rust
+use actix_web::{get, http::StatusCode, App, HttpResponse, HttpServer};
+use std::cell::RefCell;
+use std::fs::read_to_string;
+
+use ssr_rs::Ssr;
+
+thread_local! {
+ static SSR: RefCell> = RefCell::new(
+ Ssr::from(
+ read_to_string("./client/dist/ssr/index.js").unwrap(),
+ "SSR"
+ ).unwrap()
+ )
+}
+
+#[actix_web::main]
+async fn main() -> std::io::Result<()> {
+
+ Ssr::create_platform();
+
+ HttpServer::new(|| {
+ App::new()
+ .service(index)
+ })
+ .bind("127.0.0.1:8080")?
+ .run()
+ .await
+}
+
+#[get("/")]
+async fn index() -> HttpResponse {
+ let result = SSR.with(|ssr| ssr.borrow_mut().render_to_string(None).unwrap());
+
+ HttpResponse::build(StatusCode::OK)
+ .content_type("text/html; charset=utf-8")
+ .body(result)
+}
+```
+
## Contributing
Any helps or suggestions will be appreciated.
diff --git a/examples/actix.rs b/examples/actix.rs
index 7155fca..e83a31b 100644
--- a/examples/actix.rs
+++ b/examples/actix.rs
@@ -11,7 +11,7 @@ thread_local! {
Ssr::from(
read_to_string("./client/dist/ssr/index.js").unwrap(),
"SSR"
- )
+ ).unwrap()
)
}
@@ -37,9 +37,9 @@ async fn main() -> std::io::Result<()> {
#[get("/")]
async fn index() -> HttpResponse {
let start = Instant::now();
- let result = SSR.with(|ssr| ssr.borrow_mut().render_to_string(None));
+ let result = SSR.with(|ssr| ssr.borrow_mut().render_to_string(None).unwrap());
println!("Elapsed: {:?}", start.elapsed());
- // This is a benchmark example. Please refer to examples/shared_ssr.rs for a better solution.
+
HttpResponse::build(StatusCode::OK)
.content_type("text/html; charset=utf-8")
.body(result)
diff --git a/examples/actix_with_initial_props.rs b/examples/actix_with_initial_props.rs
index 5cc6c4a..4387229 100644
--- a/examples/actix_with_initial_props.rs
+++ b/examples/actix_with_initial_props.rs
@@ -11,7 +11,7 @@ thread_local! {
Ssr::from(
read_to_string("./client/dist/ssr/index.js").unwrap(),
"SSR"
- )
+ ).unwrap()
)
}
@@ -42,5 +42,5 @@ async fn index() -> HttpResponse {
HttpResponse::build(StatusCode::OK)
.content_type("text/html; charset=utf-8")
- .body(SSR.with(|ssr| ssr.borrow_mut().render_to_string(Some(mock_props))))
+ .body(SSR.with(|ssr| ssr.borrow_mut().render_to_string(Some(mock_props)).unwrap()))
}
diff --git a/examples/axum.rs b/examples/axum.rs
index aebb4ae..fe115f6 100644
--- a/examples/axum.rs
+++ b/examples/axum.rs
@@ -9,7 +9,7 @@ thread_local! {
Ssr::from(
read_to_string("./client/dist/ssr/index.js").unwrap(),
"SSR"
- )
+ ).unwrap()
)
}
@@ -29,5 +29,5 @@ async fn root() -> Html {
let start = Instant::now();
let result = SSR.with(|ssr| ssr.borrow_mut().render_to_string(None));
println!("Elapsed: {:?}", start.elapsed());
- Html(result)
+ Html(result.unwrap())
}
diff --git a/examples/multi-thread.rs b/examples/multi-thread.rs
index 4b38d48..71f6d3e 100644
--- a/examples/multi-thread.rs
+++ b/examples/multi-thread.rs
@@ -9,7 +9,7 @@ thread_local! {
Ssr::from(
read_to_string("./client/dist/ssr/index.js").unwrap(),
"SSR"
- )
+ ).unwrap()
)
}
@@ -23,7 +23,7 @@ fn main() {
let start = Instant::now();
println!(
"result: {}",
- SSR.with(|ssr| ssr.borrow_mut().render_to_string(None))
+ SSR.with(|ssr| ssr.borrow_mut().render_to_string(None).unwrap())
);
println!(
"Thread #{i} finished! - Elapsed time: {:?}",
diff --git a/examples/rocket.rs b/examples/rocket.rs
index 45bc4ca..7834e88 100644
--- a/examples/rocket.rs
+++ b/examples/rocket.rs
@@ -12,7 +12,7 @@ thread_local! {
Ssr::from(
read_to_string("./client/dist/ssr/index.js").unwrap(),
"SSR"
- )
+ ).unwrap()
)
}
@@ -21,7 +21,7 @@ fn index() -> content::RawHtml {
let start = Instant::now();
let result = SSR.with(|ssr| ssr.borrow_mut().render_to_string(None));
println!("Elapsed: {:?}", start.elapsed());
- content::RawHtml(result)
+ content::RawHtml(result.unwrap())
}
#[launch]
diff --git a/examples/run.rs b/examples/run.rs
index 719259a..57d2daa 100644
--- a/examples/run.rs
+++ b/examples/run.rs
@@ -8,9 +8,9 @@ fn main() {
Ssr::create_platform();
// This takes roughly 40ms
- let mut ssr = Ssr::from(source, "SSR");
+ let mut ssr = Ssr::from(source, "SSR").unwrap();
// This takes roughly 0.5ms
- println!("{}", ssr.render_to_string(None));
- println!("{}", ssr.render_to_string(None));
+ println!("{}", ssr.render_to_string(None).unwrap());
+ println!("{}", ssr.render_to_string(None).unwrap());
}
diff --git a/examples/tide.rs b/examples/tide.rs
index 0ccc0f3..23c2635 100644
--- a/examples/tide.rs
+++ b/examples/tide.rs
@@ -9,7 +9,7 @@ thread_local! {
Ssr::from(
read_to_string("./client/dist/ssr/index.js").unwrap(),
"SSR"
- )
+ ).unwrap()
)
}
@@ -31,7 +31,7 @@ async fn return_html(_req: Request<()>) -> tide::Result {
println!("Elapsed: {:?}", start.elapsed());
let response = Response::builder(200)
- .body(html)
+ .body(html.unwrap())
.content_type(tide::http::mime::HTML)
.build();
diff --git a/examples/warp.rs b/examples/warp.rs
index a55fe45..8520ede 100644
--- a/examples/warp.rs
+++ b/examples/warp.rs
@@ -10,7 +10,7 @@ thread_local! {
Ssr::from(
read_to_string("./client/dist/ssr/index.js").unwrap(),
"SSR"
- )
+ ).unwrap()
)
}
@@ -22,7 +22,7 @@ async fn main() {
let start = Instant::now();
let result = SSR.with(|ssr| ssr.borrow_mut().render_to_string(None));
println!("Elapsed: {:?}", start.elapsed());
- Response::builder().body(result)
+ Response::builder().body(result.unwrap())
});
let css = warp::path("styles").and(warp::fs::dir("./client/dist/ssr/styles/"));
diff --git a/src/lib.rs b/src/lib.rs
index c13a0f1..3aa642a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -5,9 +5,9 @@
//!
//! It use an embedded version of the v8 javascript engine (rusty_v8) to parse and evaluate a built bundle file and return a string with the rendered html.
//!
-//! Currently it works with Webpack bundler v4.44.2; check it out here a full project who use this crate.
+//! Currently it works with Webpack bundler v4.44.2.
//!
-//! # Gettin started
+//! # Getting started
//! ```toml
//! [dependencies]
//! ssr_rs = "0.3.0"
@@ -28,9 +28,9 @@
//!
//! let source = read_to_string("./path/to/build.js").unwrap();
//!
-//! let mut js = Ssr::from(source, "entryPoint");
+//! let mut js = Ssr::from(source, "entryPoint").unwrap();
//!
-//! let html = js.render_to_string(None);
+//! let html = js.render_to_string(None).unwrap();
//!
//! assert_eq!(html, "...".to_string());
//! }
@@ -56,14 +56,62 @@
//!
//! let source = read_to_string("./path/to/build.js").unwrap();
//!
-//! let mut js = Ssr::from(source, "entryPoint");
+//! let mut js = Ssr::from(source, "entryPoint").unwrap();
//!
-//! let html = js.render_to_string(Some(&props));
+//! let html = js.render_to_string(Some(&props)).unwrap();
//!
//! assert_eq!(html, "...".to_string());
//! }
//!```
-
+//!
+//! # Example with actix-web
+//!
+//! > Examples with different web frameworks are available in the examples folder.
+//!
+//! Even though the V8 engine allows accessing the same `isolate` from different threads that is forbidden by this crate for two reasons:
+//! 1. rusty_v8 library have not implemented yet the V8 Locker API. Accessing Ssr struct from a different thread will make the V8 engine to panic.
+//! 2. Rendering HTML does not need shared state across threads.
+//!
+//! For this reason parallel computation is a better choice. Following actix-web setup:
+//!
+//! ```no_run
+//! use actix_web::{get, http::StatusCode, App, HttpResponse, HttpServer};
+//! use std::cell::RefCell;
+//! use std::fs::read_to_string;
+//!
+//! use ssr_rs::Ssr;
+//!
+//! thread_local! {
+//! static SSR: RefCell> = RefCell::new(
+//! Ssr::from(
+//! read_to_string("./client/dist/ssr/index.js").unwrap(),
+//! "SSR"
+//! ).unwrap()
+//! )
+//!}
+//!
+//! #[actix_web::main]
+//!async fn main() -> std::io::Result<()> {
+//! Ssr::create_platform();
+//!
+//! HttpServer::new(|| {
+//! App::new()
+//! .service(index)
+//! })
+//! .bind("127.0.0.1:8080")?
+//! .run()
+//! .await
+//! }
+//!
+//! #[get("/")]
+//! async fn index() -> HttpResponse {
+//! let result = SSR.with(|ssr| ssr.borrow_mut().render_to_string(None).unwrap());
+//!
+//! HttpResponse::build(StatusCode::OK)
+//! .content_type("text/html; charset=utf-8")
+//! .body(result)
+//! }
+//!```
mod ssr;
pub use ssr::Ssr;
diff --git a/src/ssr.rs b/src/ssr.rs
index 08968bd..61943f5 100644
--- a/src/ssr.rs
+++ b/src/ssr.rs
@@ -1,6 +1,7 @@
// TODO: replace hashmap with more performant https://nnethercote.github.io/perf-book/hashing.html
use std::collections::HashMap;
+#[derive(Debug)]
pub struct Ssr<'s, 'i> {
isolate: *mut v8::OwnedIsolate,
handle_scope: *mut v8::HandleScope<'s, ()>,
@@ -43,7 +44,7 @@ where
///
/// See the examples folder for more about using multiple parallel instances for multi-threaded
/// execution.
- pub fn from(source: String, entry_point: &str) -> Self {
+ pub fn from(source: String, entry_point: &str) -> Result {
let isolate = Box::into_raw(Box::new(v8::Isolate::new(v8::CreateParams::default())));
let handle_scope = unsafe { Box::into_raw(Box::new(v8::HandleScope::new(&mut *isolate))) };
@@ -55,60 +56,78 @@ where
let scope = unsafe { &mut *scope_ptr };
- let code = v8::String::new(scope, &format!("{source};{entry_point}"))
- .expect("Invalid JS: Strings are needed");
+ let code = match v8::String::new(scope, &format!("{source};{entry_point}")) {
+ Some(val) => val,
+ None => return Err("Invalid JS: Strings are needed"),
+ };
- let script = v8::Script::compile(scope, code, None)
- .expect("Invalid JS: There aren't runnable scripts");
+ let script = match v8::Script::compile(scope, code, None) {
+ Some(val) => val,
+ None => return Err("Invalid JS: There aren't runnable scripts"),
+ };
- let exports = script
- .run(scope)
- .expect("Invalid JS: Missing entry point. Is the bundle exported as a variable?");
+ let exports = match script.run(scope) {
+ Some(val) => val,
+ None => {
+ return Err(
+ "Invalid JS: Missing entry point. Is the bundle exported as a variable?",
+ )
+ }
+ };
- let object = exports
- .to_object(scope)
- .expect("Invalid JS: There are no objects");
+ let object = match exports.to_object(scope) {
+ Some(val) => val,
+ None => return Err("Invalid JS: There are no objects"),
+ };
let mut fn_map: HashMap> = HashMap::new();
if let Some(props) = object.get_own_property_names(scope, Default::default()) {
- fn_map = Some(props)
+ fn_map = match Some(props)
.iter()
.enumerate()
- .map(|(i, &p)| {
- let name = p
- .get_index(scope, i as u32)
- .expect("Failed to get function name");
-
- let mut scope = v8::EscapableHandleScope::new(scope);
-
- let func = object
- .get(&mut scope, name)
- .expect("Failed to get function from obj");
-
- let func = unsafe { v8::Local::::cast(func) };
-
- (
- name.to_string(&mut scope)
- .unwrap()
- .to_rust_string_lossy(&mut scope),
- scope.escape(func),
- )
- })
+ .map(
+ |(i, &p)| -> Result<(String, v8::Local), &'static str> {
+ let name = match p.get_index(scope, i as u32) {
+ Some(val) => val,
+ None => return Err("Failed to get function name"),
+ };
+
+ let mut scope = v8::EscapableHandleScope::new(scope);
+
+ let func = match object.get(&mut scope, name) {
+ Some(val) => val,
+ None => return Err("Failed to get function from obj"),
+ };
+
+ let func = unsafe { v8::Local::::cast(func) };
+
+ let fn_name = match name.to_string(&mut scope) {
+ Some(val) => val.to_rust_string_lossy(&mut scope),
+ None => return Err("Failed to find function name"),
+ };
+
+ Ok((fn_name, scope.escape(func)))
+ },
+ )
// TODO: collect directly the values into a map
- .collect();
+ .collect()
+ {
+ Ok(val) => val,
+ Err(err) => return Err(err),
+ }
}
- Ssr {
+ Ok(Ssr {
isolate,
handle_scope,
fn_map,
scope: scope_ptr,
- }
+ })
}
/// Execute the Javascript functions and return the result as string.
- pub fn render_to_string(&mut self, params: Option<&str>) -> String {
+ pub fn render_to_string(&mut self, params: Option<&str>) -> Result {
let scope = unsafe { &mut *self.scope };
let params: v8::Local = match v8::String::new(scope, params.unwrap_or("")) {
@@ -122,18 +141,20 @@ where
// TODO: transform this into an iterator
for key in self.fn_map.keys() {
- let result = self.fn_map[key]
- .call(scope, undef, &[params])
- .expect("Failed to call function");
+ let result = match self.fn_map[key].call(scope, undef, &[params]) {
+ Some(val) => val,
+ None => return Err("Failed to call function"),
+ };
- let result = result
- .to_string(scope)
- .expect("Failed to parse the result to string");
+ let result = match result.to_string(scope) {
+ Some(val) => val,
+ None => return Err("Failed to parse the result to string"),
+ };
rendered = format!("{}{}", rendered, result.to_rust_string_lossy(scope));
}
- rendered
+ Ok(rendered)
}
}
@@ -151,21 +172,28 @@ mod tests {
}
#[test]
- #[should_panic]
fn wrong_entry_point() {
init_test();
let source = r##"var entryPoint = {x: () => ""};"##;
- let _ = Ssr::from(source.to_owned(), "IncorrectEntryPoint");
+ let res = Ssr::from(source.to_owned(), "IncorrectEntryPoint");
+
+ assert_eq!(
+ res.unwrap_err(),
+ "Invalid JS: Missing entry point. Is the bundle exported as a variable?"
+ );
}
#[test]
- #[should_panic]
fn empty_code() {
init_test();
let source = r##""##;
- let _ = Ssr::from(source.to_owned(), "SSR");
+ let res = Ssr::from(source.to_owned(), "SSR");
+ assert_eq!(
+ res.unwrap_err(),
+ "Invalid JS: Missing entry point. Is the bundle exported as a variable?"
+ );
}
#[test]
@@ -177,20 +205,20 @@ mod tests {
let accept_params_source =
r##"var SSR = {x: (params) => "These are our parameters: " + params};"##.to_string();
- let mut js = Ssr::from(accept_params_source, "SSR");
+ let mut js = Ssr::from(accept_params_source, "SSR").unwrap();
println!("Before render_to_string");
- let result = js.render_to_string(Some(&props));
+ let result = js.render_to_string(Some(&props)).unwrap();
assert_eq!(result, "These are our parameters: {\"Hello world\"}");
let no_params_source = r##"var SSR = {x: () => "I don't accept params"};"##.to_string();
- let mut js2 = Ssr::from(no_params_source, "SSR");
- let result2 = js2.render_to_string(Some(&props));
+ let mut js2 = Ssr::from(no_params_source, "SSR").unwrap();
+ let result2 = js2.render_to_string(Some(&props)).unwrap();
assert_eq!(result2, "I don't accept params");
- let result3 = js.render_to_string(None);
+ let result3 = js.render_to_string(None).unwrap();
assert_eq!(result3, "These are our parameters: ");
}
@@ -201,16 +229,16 @@ mod tests {
let source = r##"var SSR = {x: () => ""};"##.to_string();
- let mut js = Ssr::from(source, "SSR");
- let html = js.render_to_string(None);
+ let mut js = Ssr::from(source, "SSR").unwrap();
+ let html = js.render_to_string(None).unwrap();
assert_eq!(html, "");
//Prevent missing semicolon
let source2 = r##"var SSR = {x: () => ""}"##.to_string();
- let mut js2 = Ssr::from(source2, "SSR");
- let html2 = js2.render_to_string(None);
+ let mut js2 = Ssr::from(source2, "SSR").unwrap();
+ let html2 = js2.render_to_string(None).unwrap();
assert_eq!(html2, "");
}
@@ -222,19 +250,21 @@ mod tests {
let mut js = Ssr::from(
r##"var SSR = {x: () => ""};"##.to_string(),
"SSR",
- );
+ )
+ .unwrap();
- assert_eq!(js.render_to_string(None), "");
+ assert_eq!(js.render_to_string(None).unwrap(), "");
assert_eq!(
- js.render_to_string(Some(r#"{"Hello world"}"#)),
+ js.render_to_string(Some(r#"{"Hello world"}"#)).unwrap(),
""
);
let mut js2 = Ssr::from(
r##"var SSR = {x: () => "I don't accept params"};"##.to_string(),
"SSR",
- );
+ )
+ .unwrap();
- assert_eq!(js2.render_to_string(None), "I don't accept params");
+ assert_eq!(js2.render_to_string(None).unwrap(), "I don't accept params");
}
}