Templating & Styling
Quokka comes with Handlebars as template engine, uses Sass/SCSS to make working with CSS a bit more comfortable and relies on JS modules to make working with JavaScript feel modern but without the modern (and mostly painful) JavaScript build stack.
To use the templating, styling, scripting and resources properly you have to load the quokka::templating::TemplatingPouch.
Templating
For templating there is the quokka::state::Templating substate. The usage of it is better documented in the
Template Helpers chapter.
To get your templates into Quokka in the first place though, you use the configure_state callback of the Pouch trait.
Here you can not only register your templates, but also set up custom handlers, blocks and partials.
#[derive(rust_embed::Embed)]
struct Templates;
struct TemplatingExample;
impl<S> Pouch<S> for TemplatingExample
where
S: Send + Sync + Clone + 'static,
S: ProvideStateRef<Scripting>,
S: ProvideStateRef<Styling>,
S: ProvideStateRef<Templating>, {
fn configure_state(&mut self, state: &mut S) -> quokka::pouch::Result<()> {
use quokka::templating::TemplatingStateExt;
state.register_templates::<Templates>()?;
// Or alternatively also this (description in the Template Helpers chapter)
// state.register_templates_aliased::<Templates>()?;
// Registering a helper
handlebars::handlebars_helper!(debug: |value: serde_json::Value| { println!("{value:#?}") });
templates.register_helper("debug", Box::new(debug));
Ok(())
}
}
Styling
To make styling even more comfortable Quokka renders all styling files using grass (a Sass compiler for Rust). By that your templates can
do the usual Sass things, like macros, variables and nesting (although variables and nesting are by now also natively supported in CSS).
But still, it gives you more power with macros when you have to do otherwise more tedious stuff (like loading fonts on your own).
Styling is done in the configure_styles callback:
#[derive(rust_embed::Embed)]
struct Styling;
struct StylingExample;
impl<S> Pouch<S> for StylingExample
where
S: Send + Sync + Clone + 'static,
S: ProvideStateRef<Scripting>,
S: ProvideStateRef<Styling>,
S: ProvideStateRef<Templating>, {
fn configure_state(&mut self, state: &mut S) -> quokka::pouch::Result<()> {
use quokka::templating::TemplatingStateExt;
state.register_styles::<Styling>()?;
state.add_merged_style_group("my-module", "my-module/style.scss");
Ok(())
}
}
In this example you can see the use of "merged styles". This functionallity is intended to be used to collect styles across bundles and load a stylesheet from one endpoint. Other bundles can merge their own styles into your style group to bring their own styling into your frontend so that you don't have to find a way for that.
Styles can be retrieved from the following endpoints:
/styling/my-module/style.scss- wheremy-module/style.scsslies withing yourStylespath and can be anything that is registered inside of it/styling/group/group_name- where "group_name" is the name of the registered group./styling/style.css- This returns the compiled style for the "default" merged style group
Scripting
For the scripting Quokka uses JavaScript Modules. These make it easy and quick to import and export JavaScript functionality between scripts.
Scripting is configured in the configure_scripts callback:
#[derive(rust_embed::Embed)]
struct JavaScript;
struct ScriptingExample;
impl<S> Pouch<S> for ScriptingExample
where
S: Send + Sync + Clone + 'static,
S: ProvideStateRef<Scripting>,
S: ProvideStateRef<Styling>,
S: ProvideStateRef<Templating>, {
fn configure_state(&mut self, state: &mut S) -> quokka::pouch::Result<()> {
use quokka::templating::TemplatingStateExt;
state.register_scripts::<JavaScript>()?;
state.add_merged_script_group("my-module", "my-module/style.scss");
Ok(())
}
}
The "merged script" here is the same functionality as with the Styling one. The only difference is, that you have to take care of the import path. The path either has to be an absolute path, or if it is a relative one, it has to start with "./". You can also sot up import maps, but Quokka does not bring them (yet) on it's own.
Here again are the import paths
/scripting/my-module/script.js- wheremy-module/script.jslies withing yourStylespath and can be anything that is registered inside of it/scripting/group/group_name- where "group_name" is the name of the registered group./scripting/style.js- This returns the script for the "default" merged script group
The last important part for the scripting is, that you have to include the type="module" attribute with your <script> include.