feat(tera_factory): Implement hot reload example for Tera templates with Rhai
This commit adds a comprehensive hot reload example that demonstrates how to use the rhai_system for dynamic template rendering with Tera. Key improvements include: - Refactor the example to use external script files instead of hardcoded Rhai code - Implement proper module imports using the BasePathModuleResolver approach - Fix template rendering by using keyword arguments in Tera function calls - Add support for hot reloading both main and utility scripts - Remove unnecessary output file generation to keep the example clean - Fix compatibility issues with Rhai functions (avoiding to_string with parameters) This example showcases how changes to Rhai scripts are automatically detected and applied to rendered templates without restarting the application, providing a smooth development experience.
This commit is contained in:
194
tera_factory/examples/hot_reload/main.rs
Normal file
194
tera_factory/examples/hot_reload/main.rs
Normal file
@@ -0,0 +1,194 @@
|
||||
use std::path::PathBuf;
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
|
||||
use tera::{Context};
|
||||
use rhai_system::{create_hot_reloadable_system};
|
||||
use tera_factory::TeraFactory;
|
||||
|
||||
/// Function to modify a script file with content from another file
|
||||
fn modify_script(target_path: &PathBuf, source_path: &PathBuf, delay_secs: u64, message: &str) {
|
||||
println!("\n🔄 {}", message);
|
||||
|
||||
// Read the source script content
|
||||
let source_content = fs::read_to_string(&source_path)
|
||||
.expect(&format!("Failed to read source script file: {}", source_path.display()));
|
||||
|
||||
// Write the content to the target file
|
||||
let mut file = File::create(target_path)
|
||||
.expect("Failed to open target script file for writing");
|
||||
file.write_all(source_content.as_bytes())
|
||||
.expect("Failed to write to target script file");
|
||||
|
||||
println!("✅ Script modified successfully!");
|
||||
|
||||
// Wait before the next modification if delay is specified
|
||||
if delay_secs > 0 {
|
||||
thread::sleep(Duration::from_secs(delay_secs));
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Set up the script paths
|
||||
let script_dir = PathBuf::from("examples/hot_reload/scripts");
|
||||
|
||||
// Main script paths
|
||||
let main_script_path = script_dir.join("main.rhai");
|
||||
let utils_script_path = script_dir.join("utils.rhai");
|
||||
|
||||
// Initial script paths
|
||||
let initial_main_path = script_dir.join("initial_main.rhai");
|
||||
let initial_utils_path = script_dir.join("initial_utils.rhai");
|
||||
|
||||
// Modified script paths
|
||||
let modified_main_path = script_dir.join("modified_main.rhai");
|
||||
let modified_utils_path = script_dir.join("modified_utils.rhai");
|
||||
|
||||
println!("Main script path: {:?}", main_script_path);
|
||||
println!("Utils script path: {:?}", utils_script_path);
|
||||
|
||||
// Initialize main.rhai with the content from initial_main.rhai
|
||||
let initial_main_content = fs::read_to_string(&initial_main_path)
|
||||
.expect("Failed to read initial main script file");
|
||||
|
||||
let mut file = File::create(&main_script_path)
|
||||
.expect("Failed to open main script file for writing");
|
||||
file.write_all(initial_main_content.as_bytes())
|
||||
.expect("Failed to write to main script file");
|
||||
|
||||
// Initialize utils.rhai with the content from initial_utils.rhai
|
||||
let initial_utils_content = fs::read_to_string(&initial_utils_path)
|
||||
.expect("Failed to read initial utils script file");
|
||||
|
||||
let mut file = File::create(&utils_script_path)
|
||||
.expect("Failed to open utils script file for writing");
|
||||
file.write_all(initial_utils_content.as_bytes())
|
||||
.expect("Failed to write to utils script file");
|
||||
|
||||
// Set up the template path
|
||||
let template_dir = PathBuf::from("examples/hot_reload/templates");
|
||||
let template_path = template_dir.join("index.html.tera");
|
||||
|
||||
println!("Template path: {:?}", template_path);
|
||||
println!("Template exists: {}", template_path.exists());
|
||||
|
||||
// Create a hot reloadable Rhai system
|
||||
let script_paths = vec![main_script_path.clone(), utils_script_path.clone()];
|
||||
|
||||
// Use the first script as the main script
|
||||
let main_script_index = Some(0);
|
||||
println!("Using main script index: {}", main_script_index.unwrap());
|
||||
|
||||
let system = Arc::new(create_hot_reloadable_system(&script_paths, main_script_index)?);
|
||||
|
||||
// Create a TeraFactory instance
|
||||
let factory = TeraFactory::new();
|
||||
|
||||
// Create a Tera instance with the hot reloadable Rhai system
|
||||
println!("Creating Tera with template directory: {}", template_dir.display());
|
||||
let mut tera = factory.create_tera_with_hot_rhai(
|
||||
&[template_dir.to_str().unwrap()],
|
||||
Arc::clone(&system)
|
||||
)?;
|
||||
|
||||
// Manually add the template to Tera
|
||||
println!("Manually adding template: {}", template_path.display());
|
||||
let template_content = fs::read_to_string(&template_path)?;
|
||||
tera.add_raw_template("index.html.tera", &template_content)?;
|
||||
|
||||
// List available templates
|
||||
println!("Available templates: {:?}", tera.get_template_names().collect::<Vec<_>>());
|
||||
|
||||
// Create a thread to modify the scripts after a delay
|
||||
let main_script_path_clone = main_script_path.clone();
|
||||
let utils_script_path_clone = utils_script_path.clone();
|
||||
let modified_main_path_clone = modified_main_path.clone();
|
||||
let modified_utils_path_clone = modified_utils_path.clone();
|
||||
|
||||
let _modification_thread = thread::spawn(move || {
|
||||
// Modify the main script after 5 seconds
|
||||
modify_script(
|
||||
&main_script_path_clone,
|
||||
&modified_main_path_clone,
|
||||
5,
|
||||
"Modifying the main script with updated functions..."
|
||||
);
|
||||
|
||||
// Modify the utils script after another 5 seconds
|
||||
modify_script(
|
||||
&utils_script_path_clone,
|
||||
&modified_utils_path_clone,
|
||||
5,
|
||||
"Modifying the utils script with updated functions..."
|
||||
);
|
||||
});
|
||||
|
||||
// Main rendering loop
|
||||
for i in 1..30 {
|
||||
// Create a context with some data
|
||||
let mut context = Context::new();
|
||||
context.insert("name", "User");
|
||||
context.insert("current_date", "2025-05-02");
|
||||
context.insert("current_time", &format!("{:02}:{:02}:{:02}",
|
||||
(12 + i % 12) % 12, i % 60, i % 60));
|
||||
|
||||
// Add some products
|
||||
let products = vec![
|
||||
tera::to_value(serde_json::json!({
|
||||
"name": "Deluxe Widget",
|
||||
"price": 99.99,
|
||||
"discount": 15.0
|
||||
})).unwrap(),
|
||||
tera::to_value(serde_json::json!({
|
||||
"name": "Premium Gadget",
|
||||
"price": 149.95,
|
||||
"discount": 20.0
|
||||
})).unwrap(),
|
||||
tera::to_value(serde_json::json!({
|
||||
"name": "Basic Tool",
|
||||
"price": 29.99,
|
||||
"discount": 5.0
|
||||
})).unwrap(),
|
||||
];
|
||||
context.insert("products", &products);
|
||||
|
||||
// Add some categories
|
||||
let categories = vec![
|
||||
"Electronics", "Home & Garden", "Clothing", "Sports"
|
||||
];
|
||||
context.insert("categories", &categories);
|
||||
|
||||
// Render the template
|
||||
match tera.render("index.html.tera", &context) {
|
||||
Ok(result) => {
|
||||
// Print the first 500 characters of the result to avoid flooding the console
|
||||
let preview = if result.len() > 500 {
|
||||
format!("{}... (truncated)", &result[..500])
|
||||
} else {
|
||||
result.clone()
|
||||
};
|
||||
|
||||
println!("\n--- Render #{} ---\n{}", i, preview);
|
||||
},
|
||||
Err(e) => {
|
||||
println!("Error rendering template: {}", e);
|
||||
// Print more detailed error information
|
||||
if let Some(source) = e.source() {
|
||||
println!("Error source: {}", source);
|
||||
if let Some(next_source) = source.source() {
|
||||
println!("Next error source: {}", next_source);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for a second before rendering again
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
Reference in New Issue
Block a user