Merge branch 'upstream' into fix-hot-reloading
This commit is contained in:
commit
555b192436
6 files changed
+77
-99
No files matched your search
@@ -1,5 +1,6 @@
|
|||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
|
use rsx::RenderCallBody;
|
||||||
use syn::parse_macro_input;
|
use syn::parse_macro_input;
|
||||||
|
|
||||||
mod inlineprops;
|
mod inlineprops;
|
||||||
@@ -41,10 +42,7 @@ pub fn rsx(s: TokenStream) -> TokenStream {
|
|||||||
pub fn render(s: TokenStream) -> TokenStream {
|
pub fn render(s: TokenStream) -> TokenStream {
|
||||||
match syn::parse::<rsx::CallBody>(s) {
|
match syn::parse::<rsx::CallBody>(s) {
|
||||||
Err(err) => err.to_compile_error().into(),
|
Err(err) => err.to_compile_error().into(),
|
||||||
Ok(mut body) => {
|
Ok(body) => RenderCallBody(body).into_token_stream().into(),
|
||||||
body.inline_cx = true;
|
|
||||||
body.into_token_stream().into()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use serde_json::Value;
|
|||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::future::IntoFuture;
|
use std::future::IntoFuture;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
use wry::application::dpi::LogicalSize;
|
||||||
use wry::application::event_loop::ControlFlow;
|
use wry::application::event_loop::ControlFlow;
|
||||||
use wry::application::event_loop::EventLoopProxy;
|
use wry::application::event_loop::EventLoopProxy;
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
@@ -136,6 +137,11 @@ impl DesktopContext {
|
|||||||
let _ = self.proxy.send_event(SetZoomLevel(scale_factor));
|
let _ = self.proxy.send_event(SetZoomLevel(scale_factor));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// modifies the inner size of the window
|
||||||
|
pub fn set_inner_size(&self, logical_size: LogicalSize<f64>) {
|
||||||
|
let _ = self.proxy.send_event(SetInnerSize(logical_size));
|
||||||
|
}
|
||||||
|
|
||||||
/// launch print modal
|
/// launch print modal
|
||||||
pub fn print(&self) {
|
pub fn print(&self) {
|
||||||
let _ = self.proxy.send_event(Print);
|
let _ = self.proxy.send_event(Print);
|
||||||
@@ -188,6 +194,7 @@ pub enum UserWindowEvent {
|
|||||||
SetDecorations(bool),
|
SetDecorations(bool),
|
||||||
|
|
||||||
SetZoomLevel(f64),
|
SetZoomLevel(f64),
|
||||||
|
SetInnerSize(LogicalSize<f64>),
|
||||||
|
|
||||||
Print,
|
Print,
|
||||||
DevTool,
|
DevTool,
|
||||||
@@ -259,6 +266,7 @@ impl DesktopController {
|
|||||||
SetDecorations(state) => window.set_decorations(state),
|
SetDecorations(state) => window.set_decorations(state),
|
||||||
|
|
||||||
SetZoomLevel(scale_factor) => webview.zoom(scale_factor),
|
SetZoomLevel(scale_factor) => webview.zoom(scale_factor),
|
||||||
|
SetInnerSize(logical_size) => window.set_inner_size(logical_size),
|
||||||
|
|
||||||
Print => {
|
Print => {
|
||||||
if let Err(e) = webview.print() {
|
if let Err(e) = webview.print() {
|
||||||
|
|||||||
@@ -11,8 +11,6 @@
|
|||||||
//! - [ ] Keys
|
//! - [ ] Keys
|
||||||
//! - [ ] Properties spreading with with `..` syntax
|
//! - [ ] Properties spreading with with `..` syntax
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
@@ -169,7 +167,6 @@ impl ToTokens for Component {
|
|||||||
if !self.children.is_empty() {
|
if !self.children.is_empty() {
|
||||||
let renderer: TemplateRenderer = TemplateRenderer {
|
let renderer: TemplateRenderer = TemplateRenderer {
|
||||||
roots: &self.children,
|
roots: &self.children,
|
||||||
phantom: PhantomData,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
toks.append_all(quote! {
|
toks.append_all(quote! {
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ impl<Ctx: HotReloadingContext> FileMap<Ctx> {
|
|||||||
let old_start = old.span().start();
|
let old_start = old.span().start();
|
||||||
|
|
||||||
if let (Ok(old_call_body), Ok(new_call_body)) = (
|
if let (Ok(old_call_body), Ok(new_call_body)) = (
|
||||||
syn::parse2::<CallBody<Ctx>>(old.tokens),
|
syn::parse2::<CallBody>(old.tokens),
|
||||||
syn::parse2::<CallBody<Ctx>>(new),
|
syn::parse2::<CallBody>(new),
|
||||||
) {
|
) {
|
||||||
if let Ok(file) = file_path.strip_prefix(crate_dir) {
|
if let Ok(file) = file_path.strip_prefix(crate_dir) {
|
||||||
let line = old_start.line;
|
let line = old_start.line;
|
||||||
@@ -83,10 +83,12 @@ impl<Ctx: HotReloadingContext> FileMap<Ctx> {
|
|||||||
// the byte index doesn't matter, but dioxus needs it
|
// the byte index doesn't matter, but dioxus needs it
|
||||||
+ ":0";
|
+ ":0";
|
||||||
|
|
||||||
if let Some(template) = new_call_body.update_template(
|
if let Some(template) = new_call_body
|
||||||
Some(old_call_body),
|
.update_template::<Ctx>(
|
||||||
Box::leak(location.into_boxed_str()),
|
Some(old_call_body),
|
||||||
) {
|
Box::leak(location.into_boxed_str()),
|
||||||
|
)
|
||||||
|
{
|
||||||
// dioxus cannot handle empty templates
|
// dioxus cannot handle empty templates
|
||||||
if template.roots.is_empty() {
|
if template.roots.is_empty() {
|
||||||
return UpdateResult::NeedsRebuild;
|
return UpdateResult::NeedsRebuild;
|
||||||
|
|||||||
+58
-81
@@ -25,7 +25,6 @@ use std::{collections::HashMap, fmt::Debug, hash::Hash};
|
|||||||
pub use component::*;
|
pub use component::*;
|
||||||
use dioxus_core::{Template, TemplateAttribute, TemplateNode};
|
use dioxus_core::{Template, TemplateAttribute, TemplateNode};
|
||||||
pub use element::*;
|
pub use element::*;
|
||||||
use hot_reload::Empty;
|
|
||||||
pub use hot_reload::HotReloadingContext;
|
pub use hot_reload::HotReloadingContext;
|
||||||
pub use ifmt::*;
|
pub use ifmt::*;
|
||||||
use internment::Intern;
|
use internment::Intern;
|
||||||
@@ -46,34 +45,26 @@ fn intern<T: Eq + Hash + Send + Sync + ?Sized + 'static>(s: impl Into<Intern<T>>
|
|||||||
|
|
||||||
/// Fundametnally, every CallBody is a template
|
/// Fundametnally, every CallBody is a template
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct CallBody<Ctx: HotReloadingContext = Empty> {
|
pub struct CallBody {
|
||||||
pub roots: Vec<BodyNode>,
|
pub roots: Vec<BodyNode>,
|
||||||
|
|
||||||
// set this after
|
|
||||||
pub inline_cx: bool,
|
|
||||||
|
|
||||||
phantom: std::marker::PhantomData<Ctx>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: HotReloadingContext> CallBody<Ctx> {
|
impl CallBody {
|
||||||
/// This will try to create a new template from the current body and the previous body. This will return None if the rsx has some dynamic part that has changed.
|
/// This will try to create a new template from the current body and the previous body. This will return None if the rsx has some dynamic part that has changed.
|
||||||
/// This function intentionally leaks memory to create a static template.
|
/// This function intentionally leaks memory to create a static template.
|
||||||
/// Keeping the template static allows us to simplify the core of dioxus and leaking memory in dev mode is less of an issue.
|
/// Keeping the template static allows us to simplify the core of dioxus and leaking memory in dev mode is less of an issue.
|
||||||
/// the previous_location is the location of the previous template at the time the template was originally compiled.
|
/// the previous_location is the location of the previous template at the time the template was originally compiled.
|
||||||
pub fn update_template(
|
pub fn update_template<Ctx: HotReloadingContext>(
|
||||||
&self,
|
&self,
|
||||||
template: Option<CallBody<Ctx>>,
|
template: Option<CallBody>,
|
||||||
location: &'static str,
|
location: &'static str,
|
||||||
) -> Option<Template<'static>> {
|
) -> Option<Template<'static>> {
|
||||||
let mut renderer: TemplateRenderer<Ctx> = TemplateRenderer {
|
let mut renderer: TemplateRenderer = TemplateRenderer { roots: &self.roots };
|
||||||
roots: &self.roots,
|
renderer.update_template::<Ctx>(template, location)
|
||||||
phantom: std::marker::PhantomData,
|
|
||||||
};
|
|
||||||
renderer.update_template(template, location)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: HotReloadingContext> Parse for CallBody<Ctx> {
|
impl Parse for CallBody {
|
||||||
fn parse(input: ParseStream) -> Result<Self> {
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
let mut roots = Vec::new();
|
let mut roots = Vec::new();
|
||||||
|
|
||||||
@@ -87,58 +78,59 @@ impl<Ctx: HotReloadingContext> Parse for CallBody<Ctx> {
|
|||||||
roots.push(node);
|
roots.push(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self { roots })
|
||||||
roots,
|
|
||||||
inline_cx: false,
|
|
||||||
phantom: std::marker::PhantomData,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serialize the same way, regardless of flavor
|
/// Serialize the same way, regardless of flavor
|
||||||
impl<Ctx: HotReloadingContext> ToTokens for CallBody<Ctx> {
|
impl ToTokens for CallBody {
|
||||||
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
|
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
|
||||||
let body: TemplateRenderer<Ctx> = TemplateRenderer {
|
let body = TemplateRenderer { roots: &self.roots };
|
||||||
roots: &self.roots,
|
|
||||||
phantom: std::marker::PhantomData,
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.inline_cx {
|
out_tokens.append_all(quote! {
|
||||||
out_tokens.append_all(quote! {
|
::dioxus::core::LazyNodes::new( move | __cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode {
|
||||||
Some({
|
#body
|
||||||
let __cx = cx;
|
|
||||||
#body
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
} else {
|
})
|
||||||
out_tokens.append_all(quote! {
|
|
||||||
::dioxus::core::LazyNodes::new( move | __cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode {
|
|
||||||
#body
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TemplateRenderer<'a, Ctx: HotReloadingContext = Empty> {
|
#[derive(Default, Debug)]
|
||||||
pub roots: &'a [BodyNode],
|
pub struct RenderCallBody(pub CallBody);
|
||||||
phantom: std::marker::PhantomData<Ctx>,
|
|
||||||
|
impl ToTokens for RenderCallBody {
|
||||||
|
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
|
||||||
|
let body: TemplateRenderer = TemplateRenderer {
|
||||||
|
roots: &self.0.roots,
|
||||||
|
};
|
||||||
|
|
||||||
|
out_tokens.append_all(quote! {
|
||||||
|
Some({
|
||||||
|
let __cx = cx;
|
||||||
|
#body
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Ctx: HotReloadingContext> TemplateRenderer<'a, Ctx> {
|
pub struct TemplateRenderer<'a> {
|
||||||
fn update_template(
|
pub roots: &'a [BodyNode],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TemplateRenderer<'a> {
|
||||||
|
fn update_template<Ctx: HotReloadingContext>(
|
||||||
&mut self,
|
&mut self,
|
||||||
previous_call: Option<CallBody<Ctx>>,
|
previous_call: Option<CallBody>,
|
||||||
location: &'static str,
|
location: &'static str,
|
||||||
) -> Option<Template<'static>> {
|
) -> Option<Template<'static>> {
|
||||||
let mut mapping = previous_call.map(|call| DynamicMapping::from(call.roots));
|
let mut mapping = previous_call.map(|call| DynamicMapping::from(call.roots));
|
||||||
|
|
||||||
let mut context: DynamicContext<Ctx> = DynamicContext::default();
|
let mut context = DynamicContext::default();
|
||||||
|
|
||||||
let mut roots = Vec::new();
|
let mut roots = Vec::new();
|
||||||
for (idx, root) in self.roots.iter().enumerate() {
|
for (idx, root) in self.roots.iter().enumerate() {
|
||||||
context.current_path.push(idx as u8);
|
context.current_path.push(idx as u8);
|
||||||
roots.push(context.update_node(root, &mut mapping)?);
|
roots.push(context.update_node::<Ctx>(root, &mut mapping)?);
|
||||||
context.current_path.pop();
|
context.current_path.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,9 +157,9 @@ impl<'a, Ctx: HotReloadingContext> TemplateRenderer<'a, Ctx> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Ctx: HotReloadingContext> ToTokens for TemplateRenderer<'a, Ctx> {
|
impl<'a> ToTokens for TemplateRenderer<'a> {
|
||||||
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
|
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
|
||||||
let mut context: DynamicContext<Ctx> = DynamicContext::default();
|
let mut context = DynamicContext::default();
|
||||||
|
|
||||||
let key = match self.roots.get(0) {
|
let key = match self.roots.get(0) {
|
||||||
Some(BodyNode::Element(el)) if self.roots.len() == 1 => el.key.clone(),
|
Some(BodyNode::Element(el)) if self.roots.len() == 1 => el.key.clone(),
|
||||||
@@ -180,6 +172,12 @@ impl<'a, Ctx: HotReloadingContext> ToTokens for TemplateRenderer<'a, Ctx> {
|
|||||||
None => quote! { None },
|
None => quote! { None },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let spndbg = format!("{:?}", self.roots[0].span());
|
||||||
|
let root_col = spndbg
|
||||||
|
.rsplit_once("..")
|
||||||
|
.and_then(|(_, after)| after.split_once(')').map(|(before, _)| before))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let root_printer = self.roots.iter().enumerate().map(|(idx, root)| {
|
let root_printer = self.roots.iter().enumerate().map(|(idx, root)| {
|
||||||
context.current_path.push(idx as u8);
|
context.current_path.push(idx as u8);
|
||||||
let out = context.render_static_node(root);
|
let out = context.render_static_node(root);
|
||||||
@@ -187,12 +185,6 @@ impl<'a, Ctx: HotReloadingContext> ToTokens for TemplateRenderer<'a, Ctx> {
|
|||||||
out
|
out
|
||||||
});
|
});
|
||||||
|
|
||||||
let spndbg = format!("{:?}", self.roots[0].span());
|
|
||||||
let root_col = spndbg
|
|
||||||
.rsplit_once("..")
|
|
||||||
.and_then(|(_, after)| after.split_once(')').map(|(before, _)| before))
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
// Render and release the mutable borrow on context
|
// Render and release the mutable borrow on context
|
||||||
let roots = quote! { #( #root_printer ),* };
|
let roots = quote! { #( #root_printer ),* };
|
||||||
let node_printer = &context.dynamic_nodes;
|
let node_printer = &context.dynamic_nodes;
|
||||||
@@ -220,7 +212,6 @@ impl<'a, Ctx: HotReloadingContext> ToTokens for TemplateRenderer<'a, Ctx> {
|
|||||||
key: #key_tokens,
|
key: #key_tokens,
|
||||||
template: std::cell::Cell::new(TEMPLATE),
|
template: std::cell::Cell::new(TEMPLATE),
|
||||||
root_ids: Default::default(),
|
root_ids: Default::default(),
|
||||||
// root_ids: std::cell::Cell::from_mut( __cx.bump().alloc([None; #num_roots]) as &mut [::dioxus::core::ElementId]).as_slice_of_cells(),
|
|
||||||
dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
|
dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
|
||||||
dynamic_attrs: __cx.bump().alloc([ #( #dyn_attr_printer ),* ]),
|
dynamic_attrs: __cx.bump().alloc([ #( #dyn_attr_printer ),* ]),
|
||||||
}
|
}
|
||||||
@@ -318,32 +309,18 @@ impl DynamicMapping {
|
|||||||
|
|
||||||
// As we create the dynamic nodes, we want to keep track of them in a linear fashion
|
// As we create the dynamic nodes, we want to keep track of them in a linear fashion
|
||||||
// We'll use the size of the vecs to determine the index of the dynamic node in the final output
|
// We'll use the size of the vecs to determine the index of the dynamic node in the final output
|
||||||
pub struct DynamicContext<'a, Ctx: HotReloadingContext> {
|
#[derive(Default, Debug)]
|
||||||
|
pub struct DynamicContext<'a> {
|
||||||
dynamic_nodes: Vec<&'a BodyNode>,
|
dynamic_nodes: Vec<&'a BodyNode>,
|
||||||
dynamic_attributes: Vec<&'a ElementAttrNamed>,
|
dynamic_attributes: Vec<&'a ElementAttrNamed>,
|
||||||
current_path: Vec<u8>,
|
current_path: Vec<u8>,
|
||||||
|
|
||||||
node_paths: Vec<Vec<u8>>,
|
node_paths: Vec<Vec<u8>>,
|
||||||
attr_paths: Vec<Vec<u8>>,
|
attr_paths: Vec<Vec<u8>>,
|
||||||
|
|
||||||
phantom: std::marker::PhantomData<Ctx>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Ctx: HotReloadingContext> Default for DynamicContext<'a, Ctx> {
|
impl<'a> DynamicContext<'a> {
|
||||||
fn default() -> Self {
|
fn update_node<Ctx: HotReloadingContext>(
|
||||||
Self {
|
|
||||||
dynamic_nodes: Vec::new(),
|
|
||||||
dynamic_attributes: Vec::new(),
|
|
||||||
current_path: Vec::new(),
|
|
||||||
node_paths: Vec::new(),
|
|
||||||
attr_paths: Vec::new(),
|
|
||||||
phantom: std::marker::PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Ctx: HotReloadingContext> DynamicContext<'a, Ctx> {
|
|
||||||
fn update_node(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
root: &'a BodyNode,
|
root: &'a BodyNode,
|
||||||
mapping: &mut Option<DynamicMapping>,
|
mapping: &mut Option<DynamicMapping>,
|
||||||
@@ -400,7 +377,7 @@ impl<'a, Ctx: HotReloadingContext> DynamicContext<'a, Ctx> {
|
|||||||
let mut children = Vec::new();
|
let mut children = Vec::new();
|
||||||
for (idx, root) in el.children.iter().enumerate() {
|
for (idx, root) in el.children.iter().enumerate() {
|
||||||
self.current_path.push(idx as u8);
|
self.current_path.push(idx as u8);
|
||||||
children.push(self.update_node(root, mapping)?);
|
children.push(self.update_node::<Ctx>(root, mapping)?);
|
||||||
self.current_path.pop();
|
self.current_path.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,9 +554,9 @@ fn create_template() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let call_body: CallBody<Mock> = syn::parse2(input).unwrap();
|
let call_body: CallBody = syn::parse2(input).unwrap();
|
||||||
|
|
||||||
let template = call_body.update_template(None, "testing").unwrap();
|
let template = call_body.update_template::<Mock>(None, "testing").unwrap();
|
||||||
|
|
||||||
dbg!(template);
|
dbg!(template);
|
||||||
|
|
||||||
@@ -672,9 +649,9 @@ fn diff_template() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let call_body1: CallBody<Mock> = syn::parse2(input).unwrap();
|
let call_body1: CallBody = syn::parse2(input).unwrap();
|
||||||
|
|
||||||
let template = call_body1.update_template(None, "testing").unwrap();
|
let template = call_body1.update_template::<Mock>(None, "testing").unwrap();
|
||||||
dbg!(template);
|
dbg!(template);
|
||||||
|
|
||||||
// scrambling the attributes should not cause a full rebuild
|
// scrambling the attributes should not cause a full rebuild
|
||||||
@@ -694,10 +671,10 @@ fn diff_template() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let call_body2: CallBody<Mock> = syn::parse2(input).unwrap();
|
let call_body2: CallBody = syn::parse2(input).unwrap();
|
||||||
|
|
||||||
let template = call_body2
|
let template = call_body2
|
||||||
.update_template(Some(call_body1), "testing")
|
.update_template::<Mock>(Some(call_body1), "testing")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
dbg!(template);
|
dbg!(template);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@@ -143,10 +142,7 @@ impl ToTokens for BodyNode {
|
|||||||
pat, expr, body, ..
|
pat, expr, body, ..
|
||||||
} = exp;
|
} = exp;
|
||||||
|
|
||||||
let renderer: TemplateRenderer = TemplateRenderer {
|
let renderer: TemplateRenderer = TemplateRenderer { roots: body };
|
||||||
roots: body,
|
|
||||||
phantom: PhantomData,
|
|
||||||
};
|
|
||||||
|
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
__cx.make_node(
|
__cx.make_node(
|
||||||
|
|||||||
Reference in new issue
Block a user