* create the dynamic value pool * assign ids to dynamic formatted segments * separate the rendering and literal pools * rsx output compiles again * more examples compiling with new rsx expansion * update template body explanation * all workspace examples compile * fix formatted segments in keys * start hot reload diffing * fix component literal hot reloading * start integrate new hot reloading with the CLI * simple hot reloads working * Fix hot reloading blocks with components * implement hot reloading for if chains * Fix hot reloading after a template requires a full rebuild * Fix hot reloading any attribute values * remove unsafe from hot reload utils * Fix hot reloading empty rsx * add more hot reloading tests * reorganize hot reload module * fix web hydration * fix empty rsx nodes in autoformatting * fix tests * remove path sorting logic from core * make template names more consistent in debug mode * fix quote_as_hot_reload_literal for explicitly typed literals * fix can_be_shorthand for string literals * fix formatted single dynamic expression * Fix usize component properties and playwright tests * remove default implementation for TemplateBody * add a bunch more comments for diffing, scoring and why this scoring system is optimal
125 lines
4.5 KiB
Rust
125 lines
4.5 KiB
Rust
//! The actual rsx! macro implementation.
|
|
//!
|
|
//! This mostly just defers to the root TemplateBody with some additional tooling to provide better errors.
|
|
//! Currently the additional tooling doesn't do much.
|
|
|
|
use proc_macro2::TokenStream as TokenStream2;
|
|
use quote::ToTokens;
|
|
use std::{cell::Cell, fmt::Debug};
|
|
use syn::{
|
|
parse::{Parse, ParseStream},
|
|
Result,
|
|
};
|
|
|
|
use crate::{BodyNode, TemplateBody};
|
|
|
|
/// The Callbody is the contents of the rsx! macro
|
|
///
|
|
/// It is a list of BodyNodes, which are the different parts of the template.
|
|
/// The Callbody contains no information about how the template will be rendered, only information about the parsed tokens.
|
|
///
|
|
/// Every callbody should be valid, so you can use it to build a template.
|
|
/// To generate the code used to render the template, use the ToTokens impl on the Callbody, or with the `render_with_location` method.
|
|
///
|
|
/// Ideally we don't need the metadata here and can bake the idx-es into the templates themselves but I haven't figured out how to do that yet.
|
|
#[derive(Debug, Clone)]
|
|
pub struct CallBody {
|
|
pub body: TemplateBody,
|
|
pub template_idx: Cell<usize>,
|
|
}
|
|
|
|
impl Parse for CallBody {
|
|
fn parse(input: ParseStream) -> Result<Self> {
|
|
// Defer to the `new` method such that we can wire up hotreload information
|
|
Ok(CallBody::new(input.parse()?))
|
|
}
|
|
}
|
|
|
|
impl ToTokens for CallBody {
|
|
fn to_tokens(&self, out: &mut TokenStream2) {
|
|
self.body.to_tokens(out)
|
|
}
|
|
}
|
|
|
|
impl CallBody {
|
|
/// Create a new CallBody from a TemplateBody
|
|
///
|
|
/// This will overwrite all internal metadata regarding hotreloading.
|
|
pub fn new(body: TemplateBody) -> Self {
|
|
let body = CallBody {
|
|
body,
|
|
template_idx: Cell::new(0),
|
|
};
|
|
|
|
body.body.template_idx.set(body.next_template_idx());
|
|
|
|
body.cascade_hotreload_info(&body.body.roots);
|
|
|
|
body
|
|
}
|
|
|
|
/// Parse a stream into a CallBody. Return all error immediately instead of trying to partially expand the macro
|
|
///
|
|
/// This should be preferred over `parse` if you are outside of a macro
|
|
pub fn parse_strict(input: ParseStream) -> Result<Self> {
|
|
// todo: actually throw warnings if there are any
|
|
Self::parse(input)
|
|
}
|
|
|
|
/// With the entire knowledge of the macro call, wire up location information for anything hotreloading
|
|
/// specific. It's a little bit simpler just to have a global id per callbody than to try and track it
|
|
/// relative to each template, though we could do that if we wanted to.
|
|
///
|
|
/// For now this is just information for ifmts and templates so that when they generate, they can be
|
|
/// tracked back to the original location in the source code, to support formatted string hotreloading.
|
|
///
|
|
/// Note that there are some more complex cases we could in theory support, but have bigger plans
|
|
/// to enable just pure rust hotreloading that would make those tricks moot. So, manage more of
|
|
/// the simple cases until that proper stuff ships.
|
|
///
|
|
/// We need to make sure to wire up:
|
|
/// - subtemplate IDs
|
|
/// - ifmt IDs
|
|
/// - dynamic node IDs
|
|
/// - dynamic attribute IDs
|
|
/// - paths for dynamic nodes and attributes
|
|
///
|
|
/// Lots of wiring!
|
|
///
|
|
/// However, here, we only need to wire up template IDs since TemplateBody will handle the rest.
|
|
///
|
|
/// This is better though since we can save the relevant data on the structures themselves.
|
|
fn cascade_hotreload_info(&self, nodes: &[BodyNode]) {
|
|
for node in nodes.iter() {
|
|
match node {
|
|
BodyNode::Element(el) => {
|
|
self.cascade_hotreload_info(&el.children);
|
|
}
|
|
|
|
BodyNode::Component(comp) => {
|
|
comp.children.template_idx.set(self.next_template_idx());
|
|
self.cascade_hotreload_info(&comp.children.roots);
|
|
}
|
|
|
|
BodyNode::ForLoop(floop) => {
|
|
floop.body.template_idx.set(self.next_template_idx());
|
|
self.cascade_hotreload_info(&floop.body.roots);
|
|
}
|
|
|
|
BodyNode::IfChain(chain) => chain.for_each_branch(&mut |body| {
|
|
body.template_idx.set(self.next_template_idx());
|
|
self.cascade_hotreload_info(&body.roots)
|
|
}),
|
|
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn next_template_idx(&self) -> usize {
|
|
let idx = self.template_idx.get();
|
|
self.template_idx.set(idx + 1);
|
|
idx
|
|
}
|
|
}
|