Subtree memorization / reactive templates (#488)
This commit adds subtree memoization to Dioxus. Subtree memoization is basically a compile-time step that drastically reduces the amount of work the diffing engine needs to do at runtime by extracting non-changing nodes out into a static "template." Templates are then understood by the various renderers in the ecosystem as a faster way of rendering the same items. For example, in the web, templates are simply a set of DOM Nodes created once and then cloned later. This is the same pattern frameworks like Lithtml and SolidJS use to achieve near-perfect performance. Subtree memoization adds an additional level of complexity to Dioxus. The RSX macro needs to be much smarter to identify changing/nonchanging nodes and generate a mapping between the Template and its runtime counterparts. This commit represents a working starter point for this work, adding support for templates for the web, desktop, liveview, ssr, and native-core renderers. In the future we will try to shrink code generation, generally improve performance, and simplify our implementation.
This commit is contained in:
1 parent
b32fd2d2cd
commit
047ed1e553
80 files changed
+6774
-2552
No files matched your search
@@ -0,0 +1,892 @@
|
||||
use crate::elements::*;
|
||||
|
||||
// map the rsx name of the attribute to the html name of the attribute, the namespace that contains it, and if the attribute is volitile
|
||||
pub fn attrbute_to_static_str(
|
||||
attr: &str,
|
||||
element: &'static str,
|
||||
namespace: Option<&'static str>,
|
||||
) -> Option<(&'static str, Option<&'static str>, bool)> {
|
||||
if namespace == Some("http://www.w3.org/2000/svg") {
|
||||
svg::MAPPED_ATTRIBUTES
|
||||
.iter()
|
||||
.find(|(a, _)| *a == attr)
|
||||
.map(|(_, b)| (*b, None, false))
|
||||
} else {
|
||||
NO_NAMESPACE_ATTRIBUTES
|
||||
.iter()
|
||||
.find(|&a| *a == attr)
|
||||
.map(|a| (*a, None, false))
|
||||
.or_else(|| {
|
||||
STYLE_ATTRIBUTES
|
||||
.iter()
|
||||
.find(|(a, _)| *a == attr)
|
||||
.map(|(_, b)| (*b, Some("style"), false))
|
||||
})
|
||||
.or_else(|| {
|
||||
MAPPED_ATTRIBUTES
|
||||
.iter()
|
||||
.find(|(a, _)| *a == attr)
|
||||
.map(|(_, b)| (*b, None, false))
|
||||
})
|
||||
}
|
||||
.or_else(|| {
|
||||
ELEMENTS_WITH_MAPPED_ATTRIBUTES
|
||||
.iter()
|
||||
.find_map(|(el, attrs)| {
|
||||
(element == *el)
|
||||
.then(|| {
|
||||
attrs
|
||||
.iter()
|
||||
.find(|(a, _, _)| *a == attr)
|
||||
.map(|(_, b, volitile)| (*b, None, *volitile))
|
||||
})
|
||||
.flatten()
|
||||
})
|
||||
})
|
||||
.or_else(|| {
|
||||
ELEMENTS_WITH_NAMESPACE.iter().find_map(|(el, ns, attrs)| {
|
||||
(element == *el && namespace == Some(*ns))
|
||||
.then(|| {
|
||||
attrs
|
||||
.iter()
|
||||
.find(|a| **a == attr)
|
||||
.map(|a| (*a, None, false))
|
||||
})
|
||||
.flatten()
|
||||
})
|
||||
})
|
||||
.or_else(|| {
|
||||
ELEMENTS_WITHOUT_NAMESPACE.iter().find_map(|(el, attrs)| {
|
||||
(element == *el)
|
||||
.then(|| {
|
||||
attrs
|
||||
.iter()
|
||||
.find(|a| **a == attr)
|
||||
.map(|a| (*a, None, false))
|
||||
})
|
||||
.flatten()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! no_namespace_trait_methods {
|
||||
(
|
||||
$(
|
||||
$(#[$attr:meta])*
|
||||
$name:ident;
|
||||
)*
|
||||
) => {
|
||||
pub const NO_NAMESPACE_ATTRIBUTES: &'static [&'static str] = &[
|
||||
$(
|
||||
stringify!($name),
|
||||
)*
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! style_trait_methods {
|
||||
(
|
||||
$(
|
||||
$(#[$attr:meta])*
|
||||
$name:ident: $lit:literal,
|
||||
)*
|
||||
) => {
|
||||
pub const STYLE_ATTRIBUTES: &'static [(&'static str, &'static str)] = &[
|
||||
$(
|
||||
(stringify!($name), $lit),
|
||||
)*
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! mapped_trait_methods {
|
||||
(
|
||||
$(
|
||||
$(#[$attr:meta])*
|
||||
$name:ident: $lit:literal,
|
||||
)*
|
||||
) => {
|
||||
pub const MAPPED_ATTRIBUTES: &'static [(&'static str, &'static str)] = &[
|
||||
$(
|
||||
(stringify!($name), $lit),
|
||||
)*
|
||||
("prevent_default", "dioxus-prevent-default"),
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
no_namespace_trait_methods! {
|
||||
accesskey;
|
||||
|
||||
/// The HTML class attribute is used to specify a class for an HTML element.
|
||||
///
|
||||
/// ## Details
|
||||
/// Multiple HTML elements can share the same class.
|
||||
///
|
||||
/// The class global attribute is a space-separated list of the case-sensitive classes of the element.
|
||||
/// Classes allow CSS and Javascript to select and access specific elements via the class selectors or
|
||||
/// functions like the DOM method document.getElementsByClassName.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ### HTML:
|
||||
/// ```html
|
||||
/// <p class="note editorial">Above point sounds a bit obvious. Remove/rewrite?</p>
|
||||
/// ```
|
||||
///
|
||||
/// ### CSS:
|
||||
/// ```css
|
||||
/// .note {
|
||||
/// font-style: italic;
|
||||
/// font-weight: bold;
|
||||
/// }
|
||||
///
|
||||
/// .editorial {
|
||||
/// background: rgb(255, 0, 0, .25);
|
||||
/// padding: 10px;
|
||||
/// }
|
||||
/// ```
|
||||
class;
|
||||
contenteditable;
|
||||
data;
|
||||
dir;
|
||||
draggable;
|
||||
hidden;
|
||||
id;
|
||||
lang;
|
||||
spellcheck;
|
||||
style;
|
||||
tabindex;
|
||||
title;
|
||||
translate;
|
||||
|
||||
role;
|
||||
|
||||
/// dangerous_inner_html is Dioxus's replacement for using innerHTML in the browser DOM. In general, setting
|
||||
/// HTML from code is risky because it’s easy to inadvertently expose your users to a cross-site scripting (XSS)
|
||||
/// attack. So, you can set HTML directly from Dioxus, but you have to type out dangerous_inner_html to remind
|
||||
/// yourself that it’s dangerous
|
||||
dangerous_inner_html;
|
||||
}
|
||||
|
||||
// This macro creates an explicit method call for each of the style attributes.
|
||||
//
|
||||
// The left token specifies the name of the attribute in the rsx! macro, and the right string literal specifies the
|
||||
// actual name of the attribute generated.
|
||||
//
|
||||
// This roughly follows the html spec
|
||||
style_trait_methods! {
|
||||
align_content: "align-content",
|
||||
align_items: "align-items",
|
||||
align_self: "align-self",
|
||||
alignment_adjust: "alignment-adjust",
|
||||
alignment_baseline: "alignment-baseline",
|
||||
all: "all",
|
||||
alt: "alt",
|
||||
animation: "animation",
|
||||
animation_delay: "animation-delay",
|
||||
animation_direction: "animation-direction",
|
||||
animation_duration: "animation-duration",
|
||||
animation_fill_mode: "animation-fill-mode",
|
||||
animation_iteration_count: "animation-iteration-count",
|
||||
animation_name: "animation-name",
|
||||
animation_play_state: "animation-play-state",
|
||||
animation_timing_function: "animation-timing-function",
|
||||
azimuth: "azimuth",
|
||||
backface_visibility: "backface-visibility",
|
||||
background: "background",
|
||||
background_attachment: "background-attachment",
|
||||
background_clip: "background-clip",
|
||||
background_color: "background-color",
|
||||
background_image: "background-image",
|
||||
background_origin: "background-origin",
|
||||
background_position: "background-position",
|
||||
background_repeat: "background-repeat",
|
||||
background_size: "background-size",
|
||||
background_blend_mode: "background-blend-mode",
|
||||
baseline_shift: "baseline-shift",
|
||||
bleed: "bleed",
|
||||
bookmark_label: "bookmark-label",
|
||||
bookmark_level: "bookmark-level",
|
||||
bookmark_state: "bookmark-state",
|
||||
border: "border",
|
||||
border_color: "border-color",
|
||||
border_style: "border-style",
|
||||
border_width: "border-width",
|
||||
border_bottom: "border-bottom",
|
||||
border_bottom_color: "border-bottom-color",
|
||||
border_bottom_style: "border-bottom-style",
|
||||
border_bottom_width: "border-bottom-width",
|
||||
border_left: "border-left",
|
||||
border_left_color: "border-left-color",
|
||||
border_left_style: "border-left-style",
|
||||
border_left_width: "border-left-width",
|
||||
border_right: "border-right",
|
||||
border_right_color: "border-right-color",
|
||||
border_right_style: "border-right-style",
|
||||
border_right_width: "border-right-width",
|
||||
border_top: "border-top",
|
||||
border_top_color: "border-top-color",
|
||||
border_top_style: "border-top-style",
|
||||
border_top_width: "border-top-width",
|
||||
border_collapse: "border-collapse",
|
||||
border_image: "border-image",
|
||||
border_image_outset: "border-image-outset",
|
||||
border_image_repeat: "border-image-repeat",
|
||||
border_image_slice: "border-image-slice",
|
||||
border_image_source: "border-image-source",
|
||||
border_image_width: "border-image-width",
|
||||
border_radius: "border-radius",
|
||||
border_bottom_left_radius: "border-bottom-left-radius",
|
||||
border_bottom_right_radius: "border-bottom-right-radius",
|
||||
border_top_left_radius: "border-top-left-radius",
|
||||
border_top_right_radius: "border-top-right-radius",
|
||||
border_spacing: "border-spacing",
|
||||
bottom: "bottom",
|
||||
box_decoration_break: "box-decoration-break",
|
||||
box_shadow: "box-shadow",
|
||||
box_sizing: "box-sizing",
|
||||
box_snap: "box-snap",
|
||||
break_after: "break-after",
|
||||
break_before: "break-before",
|
||||
break_inside: "break-inside",
|
||||
buffered_rendering: "buffered-rendering",
|
||||
caption_side: "caption-side",
|
||||
clear: "clear",
|
||||
clear_side: "clear-side",
|
||||
clip: "clip",
|
||||
clip_path: "clip-path",
|
||||
clip_rule: "clip-rule",
|
||||
color: "color",
|
||||
color_adjust: "color-adjust",
|
||||
color_correction: "color-correction",
|
||||
color_interpolation: "color-interpolation",
|
||||
color_interpolation_filters: "color-interpolation-filters",
|
||||
color_profile: "color-profile",
|
||||
color_rendering: "color-rendering",
|
||||
column_fill: "column-fill",
|
||||
column_gap: "column-gap",
|
||||
column_rule: "column-rule",
|
||||
column_rule_color: "column-rule-color",
|
||||
column_rule_style: "column-rule-style",
|
||||
column_rule_width: "column-rule-width",
|
||||
column_span: "column-span",
|
||||
columns: "columns",
|
||||
column_count: "column-count",
|
||||
column_width: "column-width",
|
||||
contain: "contain",
|
||||
content: "content",
|
||||
counter_increment: "counter-increment",
|
||||
counter_reset: "counter-reset",
|
||||
counter_set: "counter-set",
|
||||
cue: "cue",
|
||||
cue_after: "cue-after",
|
||||
cue_before: "cue-before",
|
||||
cursor: "cursor",
|
||||
direction: "direction",
|
||||
display: "display",
|
||||
display_inside: "display-inside",
|
||||
display_outside: "display-outside",
|
||||
display_extras: "display-extras",
|
||||
display_box: "display-box",
|
||||
dominant_baseline: "dominant-baseline",
|
||||
elevation: "elevation",
|
||||
empty_cells: "empty-cells",
|
||||
enable_background: "enable-background",
|
||||
fill: "fill",
|
||||
fill_opacity: "fill-opacity",
|
||||
fill_rule: "fill-rule",
|
||||
filter: "filter",
|
||||
float: "float",
|
||||
float_defer_column: "float-defer-column",
|
||||
float_defer_page: "float-defer-page",
|
||||
float_offset: "float-offset",
|
||||
float_wrap: "float-wrap",
|
||||
flow_into: "flow-into",
|
||||
flow_from: "flow-from",
|
||||
flex: "flex",
|
||||
flex_basis: "flex-basis",
|
||||
flex_grow: "flex-grow",
|
||||
flex_shrink: "flex-shrink",
|
||||
flex_flow: "flex-flow",
|
||||
flex_direction: "flex-direction",
|
||||
flex_wrap: "flex-wrap",
|
||||
flood_color: "flood-color",
|
||||
flood_opacity: "flood-opacity",
|
||||
font: "font",
|
||||
font_family: "font-family",
|
||||
font_size: "font-size",
|
||||
font_stretch: "font-stretch",
|
||||
font_style: "font-style",
|
||||
font_weight: "font-weight",
|
||||
font_feature_settings: "font-feature-settings",
|
||||
font_kerning: "font-kerning",
|
||||
font_language_override: "font-language-override",
|
||||
font_size_adjust: "font-size-adjust",
|
||||
font_synthesis: "font-synthesis",
|
||||
font_variant: "font-variant",
|
||||
font_variant_alternates: "font-variant-alternates",
|
||||
font_variant_caps: "font-variant-caps",
|
||||
font_variant_east_asian: "font-variant-east-asian",
|
||||
font_variant_ligatures: "font-variant-ligatures",
|
||||
font_variant_numeric: "font-variant-numeric",
|
||||
font_variant_position: "font-variant-position",
|
||||
footnote_policy: "footnote-policy",
|
||||
glyph_orientation_horizontal: "glyph-orientation-horizontal",
|
||||
glyph_orientation_vertical: "glyph-orientation-vertical",
|
||||
grid: "grid",
|
||||
grid_auto_flow: "grid-auto-flow",
|
||||
grid_auto_columns: "grid-auto-columns",
|
||||
grid_auto_rows: "grid-auto-rows",
|
||||
grid_template: "grid-template",
|
||||
grid_template_areas: "grid-template-areas",
|
||||
grid_template_columns: "grid-template-columns",
|
||||
grid_template_rows: "grid-template-rows",
|
||||
grid_area: "grid-area",
|
||||
grid_column: "grid-column",
|
||||
grid_column_start: "grid-column-start",
|
||||
grid_column_end: "grid-column-end",
|
||||
grid_row: "grid-row",
|
||||
grid_row_start: "grid-row-start",
|
||||
grid_row_end: "grid-row-end",
|
||||
hanging_punctuation: "hanging-punctuation",
|
||||
height: "height",
|
||||
hyphenate_character: "hyphenate-character",
|
||||
hyphenate_limit_chars: "hyphenate-limit-chars",
|
||||
hyphenate_limit_last: "hyphenate-limit-last",
|
||||
hyphenate_limit_lines: "hyphenate-limit-lines",
|
||||
hyphenate_limit_zone: "hyphenate-limit-zone",
|
||||
hyphens: "hyphens",
|
||||
icon: "icon",
|
||||
image_orientation: "image-orientation",
|
||||
image_resolution: "image-resolution",
|
||||
image_rendering: "image-rendering",
|
||||
ime: "ime",
|
||||
ime_align: "ime-align",
|
||||
ime_mode: "ime-mode",
|
||||
ime_offset: "ime-offset",
|
||||
ime_width: "ime-width",
|
||||
initial_letters: "initial-letters",
|
||||
inline_box_align: "inline-box-align",
|
||||
isolation: "isolation",
|
||||
justify_content: "justify-content",
|
||||
justify_items: "justify-items",
|
||||
justify_self: "justify-self",
|
||||
kerning: "kerning",
|
||||
left: "left",
|
||||
letter_spacing: "letter-spacing",
|
||||
lighting_color: "lighting-color",
|
||||
line_box_contain: "line-box-contain",
|
||||
line_break: "line-break",
|
||||
line_grid: "line-grid",
|
||||
line_height: "line-height",
|
||||
line_slack: "line-slack",
|
||||
line_snap: "line-snap",
|
||||
list_style: "list-style",
|
||||
list_style_image: "list-style-image",
|
||||
list_style_position: "list-style-position",
|
||||
list_style_type: "list-style-type",
|
||||
margin: "margin",
|
||||
margin_bottom: "margin-bottom",
|
||||
margin_left: "margin-left",
|
||||
margin_right: "margin-right",
|
||||
margin_top: "margin-top",
|
||||
marker: "marker",
|
||||
marker_end: "marker-end",
|
||||
marker_mid: "marker-mid",
|
||||
marker_pattern: "marker-pattern",
|
||||
marker_segment: "marker-segment",
|
||||
marker_start: "marker-start",
|
||||
marker_knockout_left: "marker-knockout-left",
|
||||
marker_knockout_right: "marker-knockout-right",
|
||||
marker_side: "marker-side",
|
||||
marks: "marks",
|
||||
marquee_direction: "marquee-direction",
|
||||
marquee_play_count: "marquee-play-count",
|
||||
marquee_speed: "marquee-speed",
|
||||
marquee_style: "marquee-style",
|
||||
mask: "mask",
|
||||
mask_image: "mask-image",
|
||||
mask_repeat: "mask-repeat",
|
||||
mask_position: "mask-position",
|
||||
mask_clip: "mask-clip",
|
||||
mask_origin: "mask-origin",
|
||||
mask_size: "mask-size",
|
||||
mask_box: "mask-box",
|
||||
mask_box_outset: "mask-box-outset",
|
||||
mask_box_repeat: "mask-box-repeat",
|
||||
mask_box_slice: "mask-box-slice",
|
||||
mask_box_source: "mask-box-source",
|
||||
mask_box_width: "mask-box-width",
|
||||
mask_type: "mask-type",
|
||||
max_height: "max-height",
|
||||
max_lines: "max-lines",
|
||||
max_width: "max-width",
|
||||
min_height: "min-height",
|
||||
min_width: "min-width",
|
||||
mix_blend_mode: "mix-blend-mode",
|
||||
nav_down: "nav-down",
|
||||
nav_index: "nav-index",
|
||||
nav_left: "nav-left",
|
||||
nav_right: "nav-right",
|
||||
nav_up: "nav-up",
|
||||
object_fit: "object-fit",
|
||||
object_position: "object-position",
|
||||
offset_after: "offset-after",
|
||||
offset_before: "offset-before",
|
||||
offset_end: "offset-end",
|
||||
offset_start: "offset-start",
|
||||
opacity: "opacity",
|
||||
order: "order",
|
||||
orphans: "orphans",
|
||||
outline: "outline",
|
||||
outline_color: "outline-color",
|
||||
outline_style: "outline-style",
|
||||
outline_width: "outline-width",
|
||||
outline_offset: "outline-offset",
|
||||
overflow: "overflow",
|
||||
overflow_x: "overflow-x",
|
||||
overflow_y: "overflow-y",
|
||||
overflow_style: "overflow-style",
|
||||
overflow_wrap: "overflow-wrap",
|
||||
padding: "padding",
|
||||
padding_bottom: "padding-bottom",
|
||||
padding_left: "padding-left",
|
||||
padding_right: "padding-right",
|
||||
padding_top: "padding-top",
|
||||
page: "page",
|
||||
page_break_after: "page-break-after",
|
||||
page_break_before: "page-break-before",
|
||||
page_break_inside: "page-break-inside",
|
||||
paint_order: "paint-order",
|
||||
pause: "pause",
|
||||
pause_after: "pause-after",
|
||||
pause_before: "pause-before",
|
||||
perspective: "perspective",
|
||||
perspective_origin: "perspective-origin",
|
||||
pitch: "pitch",
|
||||
pitch_range: "pitch-range",
|
||||
play_during: "play-during",
|
||||
pointer_events: "pointer-events",
|
||||
position: "position",
|
||||
quotes: "quotes",
|
||||
region_fragment: "region-fragment",
|
||||
resize: "resize",
|
||||
rest: "rest",
|
||||
rest_after: "rest-after",
|
||||
rest_before: "rest-before",
|
||||
richness: "richness",
|
||||
right: "right",
|
||||
ruby_align: "ruby-align",
|
||||
ruby_merge: "ruby-merge",
|
||||
ruby_position: "ruby-position",
|
||||
scroll_behavior: "scroll-behavior",
|
||||
scroll_snap_coordinate: "scroll-snap-coordinate",
|
||||
scroll_snap_destination: "scroll-snap-destination",
|
||||
scroll_snap_points_x: "scroll-snap-points-x",
|
||||
scroll_snap_points_y: "scroll-snap-points-y",
|
||||
scroll_snap_type: "scroll-snap-type",
|
||||
shape_image_threshold: "shape-image-threshold",
|
||||
shape_inside: "shape-inside",
|
||||
shape_margin: "shape-margin",
|
||||
shape_outside: "shape-outside",
|
||||
shape_padding: "shape-padding",
|
||||
shape_rendering: "shape-rendering",
|
||||
size: "size",
|
||||
speak: "speak",
|
||||
speak_as: "speak-as",
|
||||
speak_header: "speak-header",
|
||||
speak_numeral: "speak-numeral",
|
||||
speak_punctuation: "speak-punctuation",
|
||||
speech_rate: "speech-rate",
|
||||
stop_color: "stop-color",
|
||||
stop_opacity: "stop-opacity",
|
||||
stress: "stress",
|
||||
string_set: "string-set",
|
||||
stroke: "stroke",
|
||||
stroke_dasharray: "stroke-dasharray",
|
||||
stroke_dashoffset: "stroke-dashoffset",
|
||||
stroke_linecap: "stroke-linecap",
|
||||
stroke_linejoin: "stroke-linejoin",
|
||||
stroke_miterlimit: "stroke-miterlimit",
|
||||
stroke_opacity: "stroke-opacity",
|
||||
stroke_width: "stroke-width",
|
||||
tab_size: "tab-size",
|
||||
table_layout: "table-layout",
|
||||
text_align: "text-align",
|
||||
text_align_all: "text-align-all",
|
||||
text_align_last: "text-align-last",
|
||||
text_anchor: "text-anchor",
|
||||
text_combine_upright: "text-combine-upright",
|
||||
text_decoration: "text-decoration",
|
||||
text_decoration_color: "text-decoration-color",
|
||||
text_decoration_line: "text-decoration-line",
|
||||
text_decoration_style: "text-decoration-style",
|
||||
text_decoration_skip: "text-decoration-skip",
|
||||
text_emphasis: "text-emphasis",
|
||||
text_emphasis_color: "text-emphasis-color",
|
||||
text_emphasis_style: "text-emphasis-style",
|
||||
text_emphasis_position: "text-emphasis-position",
|
||||
text_emphasis_skip: "text-emphasis-skip",
|
||||
text_height: "text-height",
|
||||
text_indent: "text-indent",
|
||||
text_justify: "text-justify",
|
||||
text_orientation: "text-orientation",
|
||||
text_overflow: "text-overflow",
|
||||
text_rendering: "text-rendering",
|
||||
text_shadow: "text-shadow",
|
||||
text_size_adjust: "text-size-adjust",
|
||||
text_space_collapse: "text-space-collapse",
|
||||
text_spacing: "text-spacing",
|
||||
text_transform: "text-transform",
|
||||
text_underline_position: "text-underline-position",
|
||||
text_wrap: "text-wrap",
|
||||
top: "top",
|
||||
touch_action: "touch-action",
|
||||
transform: "transform",
|
||||
transform_box: "transform-box",
|
||||
transform_origin: "transform-origin",
|
||||
transform_style: "transform-style",
|
||||
transition: "transition",
|
||||
transition_delay: "transition-delay",
|
||||
transition_duration: "transition-duration",
|
||||
transition_property: "transition-property",
|
||||
unicode_bidi: "unicode-bidi",
|
||||
vector_effect: "vector-effect",
|
||||
vertical_align: "vertical-align",
|
||||
visibility: "visibility",
|
||||
voice_balance: "voice-balance",
|
||||
voice_duration: "voice-duration",
|
||||
voice_family: "voice-family",
|
||||
voice_pitch: "voice-pitch",
|
||||
voice_range: "voice-range",
|
||||
voice_rate: "voice-rate",
|
||||
voice_stress: "voice-stress",
|
||||
voice_volumn: "voice-volumn",
|
||||
volume: "volume",
|
||||
white_space: "white-space",
|
||||
widows: "widows",
|
||||
width: "width",
|
||||
will_change: "will-change",
|
||||
word_break: "word-break",
|
||||
word_spacing: "word-spacing",
|
||||
word_wrap: "word-wrap",
|
||||
wrap_flow: "wrap-flow",
|
||||
wrap_through: "wrap-through",
|
||||
writing_mode: "writing-mode",
|
||||
gap: "gap",
|
||||
list_styler_type: "list-style-type",
|
||||
row_gap: "row-gap",
|
||||
transition_timing_function: "transition-timing-function",
|
||||
user_select: "user-select",
|
||||
webkit_user_select: "-webkit-user-select",
|
||||
z_index : "z-index",
|
||||
}
|
||||
mapped_trait_methods! {
|
||||
aria_current: "aria-current",
|
||||
aria_details: "aria-details",
|
||||
aria_disabled: "aria-disabled",
|
||||
aria_hidden: "aria-hidden",
|
||||
aria_invalid: "aria-invalid",
|
||||
aria_keyshortcuts: "aria-keyshortcuts",
|
||||
aria_label: "aria-label",
|
||||
aria_roledescription: "aria-roledescription",
|
||||
// Widget Attributes
|
||||
aria_autocomplete: "aria-autocomplete",
|
||||
aria_checked: "aria-checked",
|
||||
aria_expanded: "aria-expanded",
|
||||
aria_haspopup: "aria-haspopup",
|
||||
aria_level: "aria-level",
|
||||
aria_modal: "aria-modal",
|
||||
aria_multiline: "aria-multiline",
|
||||
aria_multiselectable: "aria-multiselectable",
|
||||
aria_orientation: "aria-orientation",
|
||||
aria_placeholder: "aria-placeholder",
|
||||
aria_pressed: "aria-pressed",
|
||||
aria_readonly: "aria-readonly",
|
||||
aria_required: "aria-required",
|
||||
aria_selected: "aria-selected",
|
||||
aria_sort: "aria-sort",
|
||||
aria_valuemax: "aria-valuemax",
|
||||
aria_valuemin: "aria-valuemin",
|
||||
aria_valuenow: "aria-valuenow",
|
||||
aria_valuetext: "aria-valuetext",
|
||||
// Live Region Attributes
|
||||
aria_atomic: "aria-atomic",
|
||||
aria_busy: "aria-busy",
|
||||
aria_live: "aria-live",
|
||||
aria_relevant: "aria-relevant",
|
||||
|
||||
aria_dropeffect: "aria-dropeffect",
|
||||
aria_grabbed: "aria-grabbed",
|
||||
// Relationship Attributes
|
||||
aria_activedescendant: "aria-activedescendant",
|
||||
aria_colcount: "aria-colcount",
|
||||
aria_colindex: "aria-colindex",
|
||||
aria_colspan: "aria-colspan",
|
||||
aria_controls: "aria-controls",
|
||||
aria_describedby: "aria-describedby",
|
||||
aria_errormessage: "aria-errormessage",
|
||||
aria_flowto: "aria-flowto",
|
||||
aria_labelledby: "aria-labelledby",
|
||||
aria_owns: "aria-owns",
|
||||
aria_posinset: "aria-posinset",
|
||||
aria_rowcount: "aria-rowcount",
|
||||
aria_rowindex: "aria-rowindex",
|
||||
aria_rowspan: "aria-rowspan",
|
||||
aria_setsize: "aria-setsize",
|
||||
}
|
||||
|
||||
pub mod svg {
|
||||
mapped_trait_methods! {
|
||||
accent_height: "accent-height",
|
||||
accumulate: "accumulate",
|
||||
additive: "additive",
|
||||
alignment_baseline: "alignment-baseline",
|
||||
alphabetic: "alphabetic",
|
||||
amplitude: "amplitude",
|
||||
arabic_form: "arabic-form",
|
||||
ascent: "ascent",
|
||||
attributeName: "attributeName",
|
||||
attributeType: "attributeType",
|
||||
azimuth: "azimuth",
|
||||
baseFrequency: "baseFrequency",
|
||||
baseline_shift: "baseline-shift",
|
||||
baseProfile: "baseProfile",
|
||||
bbox: "bbox",
|
||||
begin: "begin",
|
||||
bias: "bias",
|
||||
by: "by",
|
||||
calcMode: "calcMode",
|
||||
cap_height: "cap-height",
|
||||
class: "class",
|
||||
clip: "clip",
|
||||
clipPathUnits: "clipPathUnits",
|
||||
clip_path: "clip-path",
|
||||
clip_rule: "clip-rule",
|
||||
color: "color",
|
||||
color_interpolation: "color-interpolation",
|
||||
color_interpolation_filters: "color-interpolation-filters",
|
||||
color_profile: "color-profile",
|
||||
color_rendering: "color-rendering",
|
||||
contentScriptType: "contentScriptType",
|
||||
contentStyleType: "contentStyleType",
|
||||
crossorigin: "crossorigin",
|
||||
cursor: "cursor",
|
||||
cx: "cx",
|
||||
cy: "cy",
|
||||
d: "d",
|
||||
decelerate: "decelerate",
|
||||
descent: "descent",
|
||||
diffuseConstant: "diffuseConstant",
|
||||
direction: "direction",
|
||||
display: "display",
|
||||
divisor: "divisor",
|
||||
dominant_baseline: "dominant-baseline",
|
||||
dur: "dur",
|
||||
dx: "dx",
|
||||
dy: "dy",
|
||||
edgeMode: "edgeMode",
|
||||
elevation: "elevation",
|
||||
enable_background: "enable-background",
|
||||
end: "end",
|
||||
exponent: "exponent",
|
||||
fill: "fill",
|
||||
fill_opacity: "fill-opacity",
|
||||
fill_rule: "fill-rule",
|
||||
filter: "filter",
|
||||
filterRes: "filterRes",
|
||||
filterUnits: "filterUnits",
|
||||
flood_color: "flood-color",
|
||||
flood_opacity: "flood-opacity",
|
||||
font_family: "font-family",
|
||||
font_size: "font-size",
|
||||
font_size_adjust: "font-size-adjust",
|
||||
font_stretch: "font-stretch",
|
||||
font_style: "font-style",
|
||||
font_variant: "font-variant",
|
||||
font_weight: "font-weight",
|
||||
format: "format",
|
||||
from: "from",
|
||||
fr: "fr",
|
||||
fx: "fx",
|
||||
fy: "fy",
|
||||
g1: "g1",
|
||||
g2: "g2",
|
||||
glyph_name: "glyph-name",
|
||||
glyph_orientation_horizontal: "glyph-orientation-horizontal",
|
||||
glyph_orientation_vertical: "glyph-orientation-vertical",
|
||||
glyphRef: "glyphRef",
|
||||
gradientTransform: "gradientTransform",
|
||||
gradientUnits: "gradientUnits",
|
||||
hanging: "hanging",
|
||||
height: "height",
|
||||
href: "href",
|
||||
hreflang: "hreflang",
|
||||
horiz_adv_x: "horiz-adv-x",
|
||||
horiz_origin_x: "horiz-origin-x",
|
||||
id: "id",
|
||||
ideographic: "ideographic",
|
||||
image_rendering: "image-rendering",
|
||||
_in: "_in",
|
||||
in2: "in2",
|
||||
intercept: "intercept",
|
||||
k: "k",
|
||||
k1: "k1",
|
||||
k2: "k2",
|
||||
k3: "k3",
|
||||
k4: "k4",
|
||||
kernelMatrix: "kernelMatrix",
|
||||
kernelUnitLength: "kernelUnitLength",
|
||||
kerning: "kerning",
|
||||
keyPoints: "keyPoints",
|
||||
keySplines: "keySplines",
|
||||
keyTimes: "keyTimes",
|
||||
lang: "lang",
|
||||
lengthAdjust: "lengthAdjust",
|
||||
letter_spacing: "letter-spacing",
|
||||
lighting_color: "lighting-color",
|
||||
limitingConeAngle: "limitingConeAngle",
|
||||
local: "local",
|
||||
marker_end: "marker-end",
|
||||
marker_mid: "marker-mid",
|
||||
marker_start: "marker_start",
|
||||
markerHeight: "markerHeight",
|
||||
markerUnits: "markerUnits",
|
||||
markerWidth: "markerWidth",
|
||||
mask: "mask",
|
||||
maskContentUnits: "maskContentUnits",
|
||||
maskUnits: "maskUnits",
|
||||
mathematical: "mathematical",
|
||||
max: "max",
|
||||
media: "media",
|
||||
method: "method",
|
||||
min: "min",
|
||||
mode: "mode",
|
||||
name: "name",
|
||||
numOctaves: "numOctaves",
|
||||
offset: "offset",
|
||||
opacity: "opacity",
|
||||
operator: "operator",
|
||||
order: "order",
|
||||
orient: "orient",
|
||||
orientation: "orientation",
|
||||
origin: "origin",
|
||||
overflow: "overflow",
|
||||
overline_position: "overline-position",
|
||||
overline_thickness: "overline-thickness",
|
||||
panose_1: "panose-1",
|
||||
paint_order: "paint-order",
|
||||
path: "path",
|
||||
pathLength: "pathLength",
|
||||
patternContentUnits: "patternContentUnits",
|
||||
patternTransform: "patternTransform",
|
||||
patternUnits: "patternUnits",
|
||||
ping: "ping",
|
||||
pointer_events: "pointer-events",
|
||||
points: "points",
|
||||
pointsAtX: "pointsAtX",
|
||||
pointsAtY: "pointsAtY",
|
||||
pointsAtZ: "pointsAtZ",
|
||||
preserveAlpha: "preserveAlpha",
|
||||
preserveAspectRatio: "preserveAspectRatio",
|
||||
primitiveUnits: "primitiveUnits",
|
||||
r: "r",
|
||||
radius: "radius",
|
||||
referrerPolicy: "referrerPolicy",
|
||||
refX: "refX",
|
||||
refY: "refY",
|
||||
rel: "rel",
|
||||
rendering_intent: "rendering-intent",
|
||||
repeatCount: "repeatCount",
|
||||
repeatDur: "repeatDur",
|
||||
requiredExtensions: "requiredExtensions",
|
||||
requiredFeatures: "requiredFeatures",
|
||||
restart: "restart",
|
||||
result: "result",
|
||||
role: "role",
|
||||
rotate: "rotate",
|
||||
rx: "rx",
|
||||
ry: "ry",
|
||||
scale: "scale",
|
||||
seed: "seed",
|
||||
shape_rendering: "shape-rendering",
|
||||
slope: "slope",
|
||||
spacing: "spacing",
|
||||
specularConstant: "specularConstant",
|
||||
specularExponent: "specularExponent",
|
||||
speed: "speed",
|
||||
spreadMethod: "spreadMethod",
|
||||
startOffset: "startOffset",
|
||||
stdDeviation: "stdDeviation",
|
||||
stemh: "stemh",
|
||||
stemv: "stemv",
|
||||
stitchTiles: "stitchTiles",
|
||||
stop_color: "stop_color",
|
||||
stop_opacity: "stop_opacity",
|
||||
strikethrough_position: "strikethrough-position",
|
||||
strikethrough_thickness: "strikethrough-thickness",
|
||||
string: "string",
|
||||
stroke: "stroke",
|
||||
stroke_dasharray: "stroke-dasharray",
|
||||
stroke_dashoffset: "stroke-dashoffset",
|
||||
stroke_linecap: "stroke-linecap",
|
||||
stroke_linejoin: "stroke-linejoin",
|
||||
stroke_miterlimit: "stroke-miterlimit",
|
||||
stroke_opacity: "stroke-opacity",
|
||||
stroke_width: "stroke-width",
|
||||
style: "style",
|
||||
surfaceScale: "surfaceScale",
|
||||
systemLanguage: "systemLanguage",
|
||||
tabindex: "tabindex",
|
||||
tableValues: "tableValues",
|
||||
target: "target",
|
||||
targetX: "targetX",
|
||||
targetY: "targetY",
|
||||
text_anchor: "text-anchor",
|
||||
text_decoration: "text-decoration",
|
||||
text_rendering: "text-rendering",
|
||||
textLength: "textLength",
|
||||
to: "to",
|
||||
transform: "transform",
|
||||
transform_origin: "transform-origin",
|
||||
r#type: "_type",
|
||||
u1: "u1",
|
||||
u2: "u2",
|
||||
underline_position: "underline-position",
|
||||
underline_thickness: "underline-thickness",
|
||||
unicode: "unicode",
|
||||
unicode_bidi: "unicode-bidi",
|
||||
unicode_range: "unicode-range",
|
||||
units_per_em: "units-per-em",
|
||||
v_alphabetic: "v-alphabetic",
|
||||
v_hanging: "v-hanging",
|
||||
v_ideographic: "v-ideographic",
|
||||
v_mathematical: "v-mathematical",
|
||||
values: "values",
|
||||
vector_effect: "vector-effect",
|
||||
version: "version",
|
||||
vert_adv_y: "vert-adv-y",
|
||||
vert_origin_x: "vert-origin-x",
|
||||
vert_origin_y: "vert-origin-y",
|
||||
view_box: "viewBox",
|
||||
view_target: "viewTarget",
|
||||
visibility: "visibility",
|
||||
width: "width",
|
||||
widths: "widths",
|
||||
word_spacing: "word-spacing",
|
||||
writing_mode: "writing-mode",
|
||||
x: "x",
|
||||
x_height: "x-height",
|
||||
x1: "x1",
|
||||
x2: "x2",
|
||||
xmlns: "xmlns",
|
||||
x_channel_selector: "xChannelSelector",
|
||||
y: "y",
|
||||
y1: "y1",
|
||||
y2: "y2",
|
||||
y_channel_selector: "yChannelSelector",
|
||||
z: "z",
|
||||
zoomAndPan: "zoomAndPan",
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ use syn::{
|
||||
Token,
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
pub struct Component {
|
||||
pub name: syn::Path,
|
||||
pub prop_gen_args: Option<AngleBracketedGenericArguments>,
|
||||
@@ -174,7 +174,11 @@ impl ToTokens for Component {
|
||||
let key_token = match has_key {
|
||||
Some(field) => {
|
||||
let inners = &field.content;
|
||||
quote! { Some(format_args_f!(#inners)) }
|
||||
if let ContentField::Formatted(ifmt) = inners {
|
||||
quote! { Some(#ifmt) }
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
None => quote! { None },
|
||||
};
|
||||
@@ -193,16 +197,16 @@ impl ToTokens for Component {
|
||||
}
|
||||
|
||||
// the struct's fields info
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
pub struct ComponentField {
|
||||
pub name: Ident,
|
||||
pub content: ContentField,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
pub enum ContentField {
|
||||
ManExpr(Expr),
|
||||
Formatted(LitStr),
|
||||
Formatted(IfmtInput),
|
||||
OnHandlerRaw(Expr),
|
||||
}
|
||||
|
||||
@@ -211,7 +215,7 @@ impl ToTokens for ContentField {
|
||||
match self {
|
||||
ContentField::ManExpr(e) => e.to_tokens(tokens),
|
||||
ContentField::Formatted(s) => tokens.append_all(quote! {
|
||||
__cx.raw_text(format_args_f!(#s)).0
|
||||
__cx.raw_text(#s).0
|
||||
}),
|
||||
ContentField::OnHandlerRaw(e) => tokens.append_all(quote! {
|
||||
__cx.event_handler(#e)
|
||||
@@ -231,7 +235,7 @@ impl Parse for ComponentField {
|
||||
}
|
||||
|
||||
if name == "key" {
|
||||
let content = ContentField::ManExpr(input.parse()?);
|
||||
let content = ContentField::Formatted(input.parse()?);
|
||||
return Ok(Self { name, content });
|
||||
}
|
||||
if input.peek(LitStr) {
|
||||
|
||||
+12
-17
@@ -10,10 +10,10 @@ use syn::{
|
||||
// =======================================
|
||||
// Parse the VNode::Element type
|
||||
// =======================================
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
pub struct Element {
|
||||
pub name: Ident,
|
||||
pub key: Option<LitStr>,
|
||||
pub key: Option<IfmtInput>,
|
||||
pub attributes: Vec<ElementAttrNamed>,
|
||||
pub children: Vec<BodyNode>,
|
||||
pub _is_static: bool,
|
||||
@@ -46,7 +46,7 @@ impl Parse for Element {
|
||||
content.parse::<Token![:]>()?;
|
||||
|
||||
if content.peek(LitStr) && content.peek2(Token![,]) {
|
||||
let value = content.parse::<LitStr>()?;
|
||||
let value = content.parse()?;
|
||||
attributes.push(ElementAttrNamed {
|
||||
el_name: el_name.clone(),
|
||||
attr: ElementAttr::CustomAttrText { name, value },
|
||||
@@ -164,7 +164,7 @@ impl ToTokens for Element {
|
||||
let children = &self.children;
|
||||
|
||||
let key = match &self.key {
|
||||
Some(ty) => quote! { Some(format_args_f!(#ty)) },
|
||||
Some(ty) => quote! { Some(#ty) },
|
||||
None => quote! { None },
|
||||
};
|
||||
|
||||
@@ -190,16 +190,16 @@ impl ToTokens for Element {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
pub enum ElementAttr {
|
||||
/// attribute: "valuee {}"
|
||||
AttrText { name: Ident, value: LitStr },
|
||||
AttrText { name: Ident, value: IfmtInput },
|
||||
|
||||
/// attribute: true,
|
||||
AttrExpression { name: Ident, value: Expr },
|
||||
|
||||
/// "attribute": "value {}"
|
||||
CustomAttrText { name: LitStr, value: LitStr },
|
||||
CustomAttrText { name: LitStr, value: IfmtInput },
|
||||
|
||||
/// "attribute": true,
|
||||
CustomAttrExpression { name: LitStr, value: Expr },
|
||||
@@ -231,7 +231,7 @@ impl ElementAttr {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
pub struct ElementAttrNamed {
|
||||
pub el_name: Ident,
|
||||
pub attr: ElementAttr,
|
||||
@@ -244,29 +244,24 @@ impl ToTokens for ElementAttrNamed {
|
||||
tokens.append_all(match attr {
|
||||
ElementAttr::AttrText { name, value } => {
|
||||
quote! {
|
||||
dioxus_elements::#el_name.#name(__cx, format_args_f!(#value))
|
||||
__cx.attr_disciption( dioxus_elements::#el_name::#name, #value)
|
||||
}
|
||||
}
|
||||
ElementAttr::AttrExpression { name, value } => {
|
||||
quote! {
|
||||
dioxus_elements::#el_name.#name(__cx, #value)
|
||||
__cx.attr_disciption( dioxus_elements::#el_name::#name, #value)
|
||||
}
|
||||
}
|
||||
ElementAttr::CustomAttrText { name, value } => {
|
||||
quote! {
|
||||
__cx.attr( #name, format_args_f!(#value), None, false )
|
||||
__cx.attr( #name, #value, None, false )
|
||||
}
|
||||
}
|
||||
ElementAttr::CustomAttrExpression { name, value } => {
|
||||
quote! {
|
||||
__cx.attr( #name, format_args_f!(#value), None, false )
|
||||
__cx.attr( #name, #value, None, false )
|
||||
}
|
||||
}
|
||||
// ElementAttr::EventClosure { name, closure } => {
|
||||
// quote! {
|
||||
// dioxus_elements::on::#name(__cx, #closure)
|
||||
// }
|
||||
// }
|
||||
ElementAttr::EventTokens { name, tokens } => {
|
||||
quote! {
|
||||
dioxus_elements::on::#name(__cx, #tokens)
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,56 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use dioxus_core::OwnedCodeLocation;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// An error produced when interperting the rsx
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum Error {
|
||||
ParseError(ParseError),
|
||||
RecompileRequiredError(RecompileReason),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum RecompileReason {
|
||||
CapturedVariable(String),
|
||||
CapturedExpression(String),
|
||||
CapturedComponent(String),
|
||||
CapturedListener(String),
|
||||
CapturedAttribute(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ParseError {
|
||||
pub message: String,
|
||||
pub location: OwnedCodeLocation,
|
||||
}
|
||||
|
||||
impl ParseError {
|
||||
pub fn new(error: syn::Error, mut location: OwnedCodeLocation) -> Self {
|
||||
let message = error.to_string();
|
||||
let syn_call_site = error.span().start();
|
||||
location.line += syn_call_site.line as u32;
|
||||
if syn_call_site.line == 0 {
|
||||
location.column += syn_call_site.column as u32;
|
||||
} else {
|
||||
location.column = syn_call_site.column as u32;
|
||||
}
|
||||
location.column += 1;
|
||||
ParseError { message, location }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Error::ParseError(error) => writeln!(
|
||||
f,
|
||||
"parse error:\n--> at {}:{}:{}\n\t{:?}",
|
||||
error.location.file_path, error.location.line, error.location.column, error.message
|
||||
),
|
||||
Error::RecompileRequiredError(reason) => {
|
||||
writeln!(f, "recompile required: {:?}", reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+133
-89
@@ -2,87 +2,37 @@ use std::{collections::HashSet, str::FromStr};
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
|
||||
use quote::{quote, ToTokens};
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
*,
|
||||
};
|
||||
|
||||
pub fn format_args_f_impl(input: IfmtInput) -> Result<TokenStream> {
|
||||
// build format_literal
|
||||
let mut format_literal = String::new();
|
||||
let mut expr_counter = 0;
|
||||
for segment in input.segments.iter() {
|
||||
match segment {
|
||||
Segment::Literal(s) => format_literal += &s.replace('{', "{{").replace('}', "}}"),
|
||||
Segment::Formatted {
|
||||
format_args,
|
||||
segment,
|
||||
} => {
|
||||
format_literal += "{";
|
||||
match segment {
|
||||
FormattedSegment::Expr(_) => {
|
||||
format_literal += &expr_counter.to_string();
|
||||
expr_counter += 1;
|
||||
}
|
||||
FormattedSegment::Ident(ident) => {
|
||||
format_literal += &ident.to_string();
|
||||
}
|
||||
}
|
||||
format_literal += ":";
|
||||
format_literal += format_args;
|
||||
format_literal += "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let positional_args = input.segments.iter().filter_map(|seg| {
|
||||
if let Segment::Formatted {
|
||||
segment: FormattedSegment::Expr(expr),
|
||||
..
|
||||
} = seg
|
||||
{
|
||||
Some(expr)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
// remove duplicate idents
|
||||
let named_args_idents: HashSet<_> = input
|
||||
.segments
|
||||
.iter()
|
||||
.filter_map(|seg| {
|
||||
if let Segment::Formatted {
|
||||
segment: FormattedSegment::Ident(ident),
|
||||
..
|
||||
} = seg
|
||||
{
|
||||
Some(ident)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let named_args = named_args_idents
|
||||
.iter()
|
||||
.map(|ident| quote!(#ident = #ident));
|
||||
|
||||
Ok(quote! {
|
||||
format_args!(
|
||||
#format_literal
|
||||
#(, #positional_args)*
|
||||
#(, #named_args)*
|
||||
)
|
||||
})
|
||||
Ok(input.into_token_stream())
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // dumb compiler does not see the struct being used...
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
pub struct IfmtInput {
|
||||
pub source: Option<LitStr>,
|
||||
pub segments: Vec<Segment>,
|
||||
}
|
||||
|
||||
impl IfmtInput {
|
||||
pub fn to_static(&self) -> Option<String> {
|
||||
self.segments
|
||||
.iter()
|
||||
.try_fold(String::new(), |acc, segment| {
|
||||
if let Segment::Literal(seg) = segment {
|
||||
Some(acc + seg)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for IfmtInput {
|
||||
type Err = syn::Error;
|
||||
|
||||
@@ -96,7 +46,9 @@ impl FromStr for IfmtInput {
|
||||
current_literal.push(c);
|
||||
continue;
|
||||
}
|
||||
segments.push(Segment::Literal(current_literal));
|
||||
if !current_literal.is_empty() {
|
||||
segments.push(Segment::Literal(current_literal));
|
||||
}
|
||||
current_literal = String::new();
|
||||
let mut current_captured = String::new();
|
||||
while let Some(c) = chars.next() {
|
||||
@@ -104,10 +56,10 @@ impl FromStr for IfmtInput {
|
||||
let mut current_format_args = String::new();
|
||||
for c in chars.by_ref() {
|
||||
if c == '}' {
|
||||
segments.push(Segment::Formatted {
|
||||
segments.push(Segment::Formatted(FormattedSegment {
|
||||
format_args: current_format_args,
|
||||
segment: FormattedSegment::parse(¤t_captured)?,
|
||||
});
|
||||
segment: FormattedSegmentType::parse(¤t_captured)?,
|
||||
}));
|
||||
break;
|
||||
}
|
||||
current_format_args.push(c);
|
||||
@@ -115,10 +67,10 @@ impl FromStr for IfmtInput {
|
||||
break;
|
||||
}
|
||||
if c == '}' {
|
||||
segments.push(Segment::Formatted {
|
||||
segments.push(Segment::Formatted(FormattedSegment {
|
||||
format_args: String::new(),
|
||||
segment: FormattedSegment::parse(¤t_captured)?,
|
||||
});
|
||||
segment: FormattedSegmentType::parse(¤t_captured)?,
|
||||
}));
|
||||
break;
|
||||
}
|
||||
current_captured.push(c);
|
||||
@@ -138,27 +90,117 @@ impl FromStr for IfmtInput {
|
||||
current_literal.push(c);
|
||||
}
|
||||
}
|
||||
segments.push(Segment::Literal(current_literal));
|
||||
Ok(Self { segments })
|
||||
if !current_literal.is_empty() {
|
||||
segments.push(Segment::Literal(current_literal));
|
||||
}
|
||||
Ok(Self {
|
||||
segments,
|
||||
source: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Segment {
|
||||
Literal(String),
|
||||
Formatted {
|
||||
format_args: String,
|
||||
segment: FormattedSegment,
|
||||
},
|
||||
impl ToTokens for IfmtInput {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
// build format_literal
|
||||
let mut format_literal = String::new();
|
||||
let mut expr_counter = 0;
|
||||
for segment in self.segments.iter() {
|
||||
match segment {
|
||||
Segment::Literal(s) => format_literal += &s.replace('{', "{{").replace('}', "}}"),
|
||||
Segment::Formatted(FormattedSegment {
|
||||
format_args,
|
||||
segment,
|
||||
}) => {
|
||||
format_literal += "{";
|
||||
match segment {
|
||||
FormattedSegmentType::Expr(_) => {
|
||||
format_literal += &expr_counter.to_string();
|
||||
expr_counter += 1;
|
||||
}
|
||||
FormattedSegmentType::Ident(ident) => {
|
||||
format_literal += &ident.to_string();
|
||||
}
|
||||
}
|
||||
format_literal += ":";
|
||||
format_literal += format_args;
|
||||
format_literal += "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let positional_args = self.segments.iter().filter_map(|seg| {
|
||||
if let Segment::Formatted(FormattedSegment {
|
||||
segment: FormattedSegmentType::Expr(expr),
|
||||
..
|
||||
}) = seg
|
||||
{
|
||||
Some(expr)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
// remove duplicate idents
|
||||
let named_args_idents: HashSet<_> = self
|
||||
.segments
|
||||
.iter()
|
||||
.filter_map(|seg| {
|
||||
if let Segment::Formatted(FormattedSegment {
|
||||
segment: FormattedSegmentType::Ident(ident),
|
||||
..
|
||||
}) = seg
|
||||
{
|
||||
Some(ident)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let named_args = named_args_idents
|
||||
.iter()
|
||||
.map(|ident| quote!(#ident = #ident));
|
||||
|
||||
quote! {
|
||||
format_args!(
|
||||
#format_literal
|
||||
#(, #positional_args)*
|
||||
#(, #named_args)*
|
||||
)
|
||||
}
|
||||
.to_tokens(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FormattedSegment {
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
pub enum Segment {
|
||||
Literal(String),
|
||||
Formatted(FormattedSegment),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
pub struct FormattedSegment {
|
||||
format_args: String,
|
||||
segment: FormattedSegmentType,
|
||||
}
|
||||
|
||||
impl ToTokens for FormattedSegment {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let (fmt, seg) = (&self.format_args, &self.segment);
|
||||
let fmt = format!("{{0:{}}}", fmt);
|
||||
tokens.append_all(quote! {
|
||||
format_args!(#fmt, #seg)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
pub enum FormattedSegmentType {
|
||||
Expr(Box<Expr>),
|
||||
Ident(Ident),
|
||||
}
|
||||
|
||||
impl FormattedSegment {
|
||||
impl FormattedSegmentType {
|
||||
fn parse(input: &str) -> Result<Self> {
|
||||
if let Ok(ident) = parse_str::<Ident>(input) {
|
||||
if ident == input {
|
||||
@@ -176,7 +218,7 @@ impl FormattedSegment {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for FormattedSegment {
|
||||
impl ToTokens for FormattedSegmentType {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match self {
|
||||
Self::Expr(expr) => expr.to_tokens(tokens),
|
||||
@@ -189,6 +231,8 @@ impl Parse for IfmtInput {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let input: LitStr = input.parse()?;
|
||||
let input_str = input.value();
|
||||
IfmtInput::from_str(&input_str)
|
||||
let mut ifmt = IfmtInput::from_str(&input_str)?;
|
||||
ifmt.source = Some(input);
|
||||
Ok(ifmt)
|
||||
}
|
||||
}
|
||||
+39
-6
@@ -13,17 +13,25 @@
|
||||
|
||||
#[macro_use]
|
||||
mod errors;
|
||||
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
mod attributes;
|
||||
mod component;
|
||||
mod element;
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
mod elements;
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
mod error;
|
||||
mod ifmt;
|
||||
mod node;
|
||||
mod template;
|
||||
|
||||
// Re-export the namespaces into each other
|
||||
pub use component::*;
|
||||
pub use element::*;
|
||||
pub use ifmt::*;
|
||||
pub use node::*;
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
pub use template::{try_parse_template, DynamicTemplateContextBuilder};
|
||||
|
||||
// imports
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
@@ -32,6 +40,7 @@ use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
Result, Token,
|
||||
};
|
||||
use template::TemplateBuilder;
|
||||
|
||||
pub struct CallBody {
|
||||
pub roots: Vec<BodyNode>,
|
||||
@@ -58,12 +67,17 @@ impl Parse for CallBody {
|
||||
/// Serialize the same way, regardless of flavor
|
||||
impl ToTokens for CallBody {
|
||||
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
|
||||
let inner = if self.roots.len() == 1 {
|
||||
let inner = &self.roots[0];
|
||||
quote! { #inner }
|
||||
let template = TemplateBuilder::from_roots(self.roots.clone());
|
||||
let inner = if let Some(template) = template {
|
||||
quote! { #template }
|
||||
} else {
|
||||
let childs = &self.roots;
|
||||
quote! { __cx.fragment_root([ #(#childs),* ]) }
|
||||
let children = &self.roots;
|
||||
if children.len() == 1 {
|
||||
let inner = &self.roots[0];
|
||||
quote! { #inner }
|
||||
} else {
|
||||
quote! { __cx.fragment_root([ #(#children),* ]) }
|
||||
}
|
||||
};
|
||||
|
||||
// Otherwise we just build the LazyNode wrapper
|
||||
@@ -75,3 +89,22 @@ impl ToTokens for CallBody {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl CallBody {
|
||||
pub fn to_tokens_without_template(&self, out_tokens: &mut TokenStream2) {
|
||||
let children = &self.roots;
|
||||
let inner = if children.len() == 1 {
|
||||
let inner = &self.roots[0];
|
||||
quote! { #inner }
|
||||
} else {
|
||||
quote! { __cx.fragment_root([ #(#children),* ]) }
|
||||
};
|
||||
|
||||
out_tokens.append_all(quote! {
|
||||
LazyNodes::new(move |__cx: NodeFactory| -> VNode {
|
||||
use dioxus_elements::{GlobalAttributes, SvgAttributes};
|
||||
#inner
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,11 @@ Parse
|
||||
-> "text {with_args}"
|
||||
-> (0..10).map(|f| rsx!("asd")), // <--- notice the comma - must be a complete expr
|
||||
*/
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
pub enum BodyNode {
|
||||
Element(Element),
|
||||
Component(Component),
|
||||
Text(LitStr),
|
||||
Text(IfmtInput),
|
||||
RawExpr(Expr),
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ impl BodyNode {
|
||||
match self {
|
||||
BodyNode::Element(el) => el.name.span(),
|
||||
BodyNode::Component(component) => component.name.span(),
|
||||
BodyNode::Text(text) => text.span(),
|
||||
BodyNode::Text(text) => text.source.span(),
|
||||
BodyNode::RawExpr(exp) => exp.span(),
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ impl ToTokens for BodyNode {
|
||||
BodyNode::Element(el) => el.to_tokens(tokens),
|
||||
BodyNode::Component(comp) => comp.to_tokens(tokens),
|
||||
BodyNode::Text(txt) => tokens.append_all(quote! {
|
||||
__cx.text(format_args_f!(#txt))
|
||||
__cx.text(#txt)
|
||||
}),
|
||||
BodyNode::RawExpr(exp) => tokens.append_all(quote! {
|
||||
__cx.fragment_from_iter(#exp)
|
||||
|
||||
@@ -0,0 +1,917 @@
|
||||
use dioxus_core::{
|
||||
OwnedAttributeValue, TemplateAttributeValue, TemplateNodeId, TextTemplate, TextTemplateSegment,
|
||||
};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::TokenStreamExt;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{Expr, Ident, LitStr};
|
||||
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
pub fn try_parse_template(
|
||||
rsx: &str,
|
||||
location: OwnedCodeLocation,
|
||||
previous_template: Option<DynamicTemplateContextBuilder>,
|
||||
) -> Result<(OwnedTemplate, DynamicTemplateContextBuilder), Error> {
|
||||
use crate::CallBody;
|
||||
|
||||
let call_body: CallBody =
|
||||
syn::parse_str(rsx).map_err(|e| Error::ParseError(ParseError::new(e, location.clone())))?;
|
||||
let mut template_builder = TemplateBuilder::from_roots_always(call_body.roots);
|
||||
if let Some(prev) = previous_template {
|
||||
template_builder = template_builder
|
||||
.try_switch_dynamic_context(prev)
|
||||
.ok_or_else(|| {
|
||||
Error::RecompileRequiredError(RecompileReason::CapturedVariable(
|
||||
"dynamic context updated".to_string(),
|
||||
))
|
||||
})?;
|
||||
}
|
||||
let dyn_ctx = template_builder.dynamic_context.clone();
|
||||
Ok((template_builder.try_into_owned(&location)?, dyn_ctx))
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
use hot_reload_imports::*;
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
mod hot_reload_imports {
|
||||
pub use crate::{
|
||||
attributes::attrbute_to_static_str,
|
||||
elements::element_to_static_str,
|
||||
error::{Error, ParseError, RecompileReason},
|
||||
};
|
||||
pub use dioxus_core::prelude::OwnedTemplate;
|
||||
pub use dioxus_core::{
|
||||
AttributeDiscription, OwnedAttributeValue, OwnedCodeLocation, OwnedDynamicNodeMapping,
|
||||
OwnedTemplateNode, Template, TemplateAttribute, TemplateAttributeValue, TemplateElement,
|
||||
TemplateNodeId, TemplateNodeType, TextTemplate, TextTemplateSegment,
|
||||
};
|
||||
pub use std::collections::HashMap;
|
||||
}
|
||||
use crate::{BodyNode, ElementAttr, FormattedSegment, Segment};
|
||||
|
||||
struct TemplateElementBuilder {
|
||||
tag: Ident,
|
||||
attributes: Vec<TemplateAttributeBuilder>,
|
||||
children: Vec<TemplateNodeId>,
|
||||
listeners: Vec<usize>,
|
||||
parent: Option<TemplateNodeId>,
|
||||
}
|
||||
|
||||
impl TemplateElementBuilder {
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
fn try_into_owned(
|
||||
self,
|
||||
location: &OwnedCodeLocation,
|
||||
) -> Result<
|
||||
TemplateElement<
|
||||
Vec<TemplateAttribute<OwnedAttributeValue>>,
|
||||
OwnedAttributeValue,
|
||||
Vec<TemplateNodeId>,
|
||||
Vec<usize>,
|
||||
>,
|
||||
Error,
|
||||
> {
|
||||
let Self {
|
||||
tag,
|
||||
attributes,
|
||||
children,
|
||||
listeners,
|
||||
parent,
|
||||
} = self;
|
||||
let (element_tag, element_ns) =
|
||||
element_to_static_str(&tag.to_string()).ok_or_else(|| {
|
||||
Error::ParseError(ParseError::new(
|
||||
syn::Error::new(tag.span(), format!("unknown element: {}", tag)),
|
||||
location.clone(),
|
||||
))
|
||||
})?;
|
||||
|
||||
let mut owned_attributes = Vec::new();
|
||||
for a in attributes {
|
||||
owned_attributes.push(a.try_into_owned(location, element_tag, element_ns)?);
|
||||
}
|
||||
|
||||
Ok(TemplateElement::new(
|
||||
element_tag,
|
||||
element_ns,
|
||||
owned_attributes,
|
||||
children,
|
||||
listeners,
|
||||
parent,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for TemplateElementBuilder {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let Self {
|
||||
tag,
|
||||
attributes,
|
||||
children,
|
||||
listeners,
|
||||
parent,
|
||||
} = self;
|
||||
let children = children.iter().map(|id| {
|
||||
let raw = id.0;
|
||||
quote! {TemplateNodeId(#raw)}
|
||||
});
|
||||
let parent = match parent {
|
||||
Some(id) => {
|
||||
let raw = id.0;
|
||||
quote! {Some(TemplateNodeId(#raw))}
|
||||
}
|
||||
None => quote! {None},
|
||||
};
|
||||
tokens.append_all(quote! {
|
||||
TemplateElement::new(
|
||||
dioxus_elements::#tag::TAG_NAME,
|
||||
dioxus_elements::#tag::NAME_SPACE,
|
||||
&[#(#attributes),*],
|
||||
&[#(#children),*],
|
||||
&[#(#listeners),*],
|
||||
#parent,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
enum AttributeName {
|
||||
Ident(Ident),
|
||||
Str(LitStr),
|
||||
}
|
||||
|
||||
struct TemplateAttributeBuilder {
|
||||
element_tag: Ident,
|
||||
name: AttributeName,
|
||||
value: TemplateAttributeValue<OwnedAttributeValue>,
|
||||
}
|
||||
|
||||
impl TemplateAttributeBuilder {
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
fn try_into_owned(
|
||||
self,
|
||||
location: &OwnedCodeLocation,
|
||||
element_tag: &'static str,
|
||||
element_ns: Option<&'static str>,
|
||||
) -> Result<TemplateAttribute<OwnedAttributeValue>, Error> {
|
||||
let Self { name, value, .. } = self;
|
||||
let (name, span, literal) = match name {
|
||||
AttributeName::Ident(name) => (name.to_string(), name.span(), false),
|
||||
AttributeName::Str(name) => (name.value(), name.span(), true),
|
||||
};
|
||||
let (name, namespace, volatile) = attrbute_to_static_str(&name, element_tag, element_ns)
|
||||
.ok_or_else(|| {
|
||||
if literal {
|
||||
// literals will be captured when a full recompile is triggered
|
||||
Error::RecompileRequiredError(RecompileReason::CapturedAttribute(
|
||||
name.to_string(),
|
||||
))
|
||||
} else {
|
||||
Error::ParseError(ParseError::new(
|
||||
syn::Error::new(span, format!("unknown attribute: {}", name)),
|
||||
location.clone(),
|
||||
))
|
||||
}
|
||||
})?;
|
||||
let attribute = AttributeDiscription {
|
||||
name,
|
||||
namespace,
|
||||
volatile,
|
||||
};
|
||||
Ok(TemplateAttribute { value, attribute })
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for TemplateAttributeBuilder {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let Self {
|
||||
element_tag,
|
||||
name,
|
||||
value,
|
||||
} = self;
|
||||
let value = match value {
|
||||
TemplateAttributeValue::Static(val) => {
|
||||
let val = match val {
|
||||
OwnedAttributeValue::Text(txt) => quote! {StaticAttributeValue::Text(#txt)},
|
||||
OwnedAttributeValue::Float32(f) => quote! {StaticAttributeValue::Float32(#f)},
|
||||
OwnedAttributeValue::Float64(f) => quote! {StaticAttributeValue::Float64(#f)},
|
||||
OwnedAttributeValue::Int32(i) => quote! {StaticAttributeValue::Int32(#i)},
|
||||
OwnedAttributeValue::Int64(i) => quote! {StaticAttributeValue::Int64(#i)},
|
||||
OwnedAttributeValue::Uint32(u) => quote! {StaticAttributeValue::Uint32(#u)},
|
||||
OwnedAttributeValue::Uint64(u) => quote! {StaticAttributeValue::Uint64(#u)},
|
||||
OwnedAttributeValue::Bool(b) => quote! {StaticAttributeValue::Bool(#b)},
|
||||
OwnedAttributeValue::Vec3Float(f1, f2, f3) => {
|
||||
quote! {StaticAttributeValue::Vec3Float(#f1, #f2, #f3)}
|
||||
}
|
||||
OwnedAttributeValue::Vec3Int(f1, f2, f3) => {
|
||||
quote! {StaticAttributeValue::Vec3Int(#f1, #f2, #f3)}
|
||||
}
|
||||
OwnedAttributeValue::Vec3Uint(f1, f2, f3) => {
|
||||
quote! {StaticAttributeValue::Vec3Uint(#f1, #f2, #f3)}
|
||||
}
|
||||
OwnedAttributeValue::Vec4Float(f1, f2, f3, f4) => {
|
||||
quote! {StaticAttributeValue::Vec4Float(#f1, #f2, #f3, #f4)}
|
||||
}
|
||||
OwnedAttributeValue::Vec4Int(f1, f2, f3, f4) => {
|
||||
quote! {StaticAttributeValue::Vec4Int(#f1, #f2, #f3, #f4)}
|
||||
}
|
||||
OwnedAttributeValue::Vec4Uint(f1, f2, f3, f4) => {
|
||||
quote! {StaticAttributeValue::Vec4Uint(#f1, #f2, #f3, #f4)}
|
||||
}
|
||||
OwnedAttributeValue::Bytes(b) => {
|
||||
quote! {StaticAttributeValue::Bytes(&[#(#b),*])}
|
||||
}
|
||||
};
|
||||
quote! {TemplateAttributeValue::Static(#val)}
|
||||
}
|
||||
TemplateAttributeValue::Dynamic(idx) => quote! {TemplateAttributeValue::Dynamic(#idx)},
|
||||
};
|
||||
match name {
|
||||
AttributeName::Ident(name) => tokens.append_all(quote! {
|
||||
TemplateAttribute{
|
||||
attribute: dioxus_elements::#element_tag::#name,
|
||||
value: #value,
|
||||
}
|
||||
}),
|
||||
AttributeName::Str(lit) => tokens.append_all(quote! {
|
||||
TemplateAttribute{
|
||||
attribute: dioxus::prelude::AttributeDiscription{
|
||||
name: #lit,
|
||||
namespace: None,
|
||||
volatile: false
|
||||
},
|
||||
value: #value,
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum TemplateNodeTypeBuilder {
|
||||
Element(TemplateElementBuilder),
|
||||
Text(TextTemplate<Vec<TextTemplateSegment<String>>, String>),
|
||||
DynamicNode(usize),
|
||||
}
|
||||
|
||||
impl TemplateNodeTypeBuilder {
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
fn try_into_owned(
|
||||
self,
|
||||
location: &OwnedCodeLocation,
|
||||
) -> Result<
|
||||
TemplateNodeType<
|
||||
Vec<TemplateAttribute<OwnedAttributeValue>>,
|
||||
OwnedAttributeValue,
|
||||
Vec<TemplateNodeId>,
|
||||
Vec<usize>,
|
||||
Vec<TextTemplateSegment<String>>,
|
||||
String,
|
||||
>,
|
||||
Error,
|
||||
> {
|
||||
match self {
|
||||
TemplateNodeTypeBuilder::Element(el) => {
|
||||
Ok(TemplateNodeType::Element(el.try_into_owned(location)?))
|
||||
}
|
||||
TemplateNodeTypeBuilder::Text(txt) => Ok(TemplateNodeType::Text(txt)),
|
||||
TemplateNodeTypeBuilder::DynamicNode(idx) => Ok(TemplateNodeType::DynamicNode(idx)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for TemplateNodeTypeBuilder {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match self {
|
||||
TemplateNodeTypeBuilder::Element(el) => tokens.append_all(quote! {
|
||||
TemplateNodeType::Element(#el)
|
||||
}),
|
||||
TemplateNodeTypeBuilder::Text(txt) => {
|
||||
let segments = txt.segments.iter().map(|seg| match seg {
|
||||
TextTemplateSegment::Static(s) => quote!(TextTemplateSegment::Static(#s)),
|
||||
TextTemplateSegment::Dynamic(idx) => quote!(TextTemplateSegment::Dynamic(#idx)),
|
||||
});
|
||||
tokens.append_all(quote! {
|
||||
TemplateNodeType::Text(TextTemplate::new(&[#(#segments),*]))
|
||||
});
|
||||
}
|
||||
TemplateNodeTypeBuilder::DynamicNode(idx) => tokens.append_all(quote! {
|
||||
TemplateNodeType::DynamicNode(#idx)
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TemplateNodeBuilder {
|
||||
id: TemplateNodeId,
|
||||
node_type: TemplateNodeTypeBuilder,
|
||||
}
|
||||
|
||||
impl TemplateNodeBuilder {
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
fn try_into_owned(self, location: &OwnedCodeLocation) -> Result<OwnedTemplateNode, Error> {
|
||||
let TemplateNodeBuilder { id, node_type } = self;
|
||||
let node_type = node_type.try_into_owned(location)?;
|
||||
Ok(OwnedTemplateNode {
|
||||
id,
|
||||
node_type,
|
||||
locally_static: false,
|
||||
fully_static: false,
|
||||
})
|
||||
}
|
||||
|
||||
fn is_fully_static(&self, nodes: &Vec<TemplateNodeBuilder>) -> bool {
|
||||
self.is_locally_static()
|
||||
&& match &self.node_type {
|
||||
TemplateNodeTypeBuilder::Element(el) => el
|
||||
.children
|
||||
.iter()
|
||||
.all(|child| nodes[child.0].is_fully_static(nodes)),
|
||||
TemplateNodeTypeBuilder::Text(_) => true,
|
||||
TemplateNodeTypeBuilder::DynamicNode(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_locally_static(&self) -> bool {
|
||||
match &self.node_type {
|
||||
TemplateNodeTypeBuilder::Element(el) => {
|
||||
el.attributes.iter().all(|attr| match &attr.value {
|
||||
TemplateAttributeValue::Static(_) => true,
|
||||
TemplateAttributeValue::Dynamic(_) => false,
|
||||
}) && el.listeners.is_empty()
|
||||
}
|
||||
TemplateNodeTypeBuilder::Text(txt) => txt.segments.iter().all(|seg| match seg {
|
||||
TextTemplateSegment::Static(_) => true,
|
||||
TextTemplateSegment::Dynamic(_) => false,
|
||||
}),
|
||||
TemplateNodeTypeBuilder::DynamicNode(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_tokens(&self, tokens: &mut TokenStream, nodes: &Vec<TemplateNodeBuilder>) {
|
||||
let Self { id, node_type } = self;
|
||||
let raw_id = id.0;
|
||||
let fully_static = self.is_fully_static(nodes);
|
||||
let locally_static = self.is_locally_static();
|
||||
|
||||
tokens.append_all(quote! {
|
||||
TemplateNode {
|
||||
id: TemplateNodeId(#raw_id),
|
||||
node_type: #node_type,
|
||||
locally_static: #locally_static,
|
||||
fully_static: #fully_static,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TemplateBuilder {
|
||||
nodes: Vec<TemplateNodeBuilder>,
|
||||
root_nodes: Vec<TemplateNodeId>,
|
||||
dynamic_context: DynamicTemplateContextBuilder,
|
||||
}
|
||||
|
||||
impl TemplateBuilder {
|
||||
/// Create a template builder from nodes if it would improve performance to do so.
|
||||
pub fn from_roots(roots: Vec<BodyNode>) -> Option<Self> {
|
||||
let mut builder = Self::default();
|
||||
|
||||
for root in roots {
|
||||
let id = builder.build_node(root, None);
|
||||
builder.root_nodes.push(id);
|
||||
}
|
||||
|
||||
// only build a template if there is at least one static node
|
||||
if builder.nodes.iter().all(|r| {
|
||||
if let TemplateNodeTypeBuilder::DynamicNode(_) = &r.node_type {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
None
|
||||
} else {
|
||||
Some(builder)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a template builder from nodes regardless of performance.
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
fn from_roots_always(roots: Vec<BodyNode>) -> Self {
|
||||
let mut builder = Self::default();
|
||||
|
||||
for root in roots {
|
||||
let id = builder.build_node(root, None);
|
||||
builder.root_nodes.push(id);
|
||||
}
|
||||
|
||||
builder
|
||||
}
|
||||
|
||||
fn build_node(&mut self, node: BodyNode, parent: Option<TemplateNodeId>) -> TemplateNodeId {
|
||||
let id = TemplateNodeId(self.nodes.len());
|
||||
match node {
|
||||
BodyNode::Element(el) => {
|
||||
let mut attributes = Vec::new();
|
||||
let mut listeners = Vec::new();
|
||||
for attr in el.attributes {
|
||||
match attr.attr {
|
||||
ElementAttr::AttrText { name, value } => {
|
||||
if let Some(static_value) = value.to_static() {
|
||||
attributes.push(TemplateAttributeBuilder {
|
||||
element_tag: el.name.clone(),
|
||||
name: AttributeName::Ident(name),
|
||||
value: TemplateAttributeValue::Static(
|
||||
OwnedAttributeValue::Text(static_value),
|
||||
),
|
||||
})
|
||||
} else {
|
||||
attributes.push(TemplateAttributeBuilder {
|
||||
element_tag: el.name.clone(),
|
||||
name: AttributeName::Ident(name),
|
||||
value: TemplateAttributeValue::Dynamic(
|
||||
self.dynamic_context.add_attr(quote!(AttributeValue::Text(
|
||||
dioxus::core::exports::bumpalo::format!(in __bump, "{}", #value)
|
||||
.into_bump_str()
|
||||
))),
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
ElementAttr::CustomAttrText { name, value } => {
|
||||
if let Some(static_value) = value.to_static() {
|
||||
attributes.push(TemplateAttributeBuilder {
|
||||
element_tag: el.name.clone(),
|
||||
name: AttributeName::Str(name),
|
||||
value: TemplateAttributeValue::Static(
|
||||
OwnedAttributeValue::Text(static_value),
|
||||
),
|
||||
})
|
||||
} else {
|
||||
attributes.push(TemplateAttributeBuilder {
|
||||
element_tag: el.name.clone(),
|
||||
name: AttributeName::Str(name),
|
||||
value: TemplateAttributeValue::Dynamic(
|
||||
self.dynamic_context.add_attr(quote!(AttributeValue::Text(
|
||||
dioxus::core::exports::bumpalo::format!(in __bump, "{}", #value)
|
||||
.into_bump_str()
|
||||
))),
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
ElementAttr::AttrExpression { name, value } => {
|
||||
attributes.push(TemplateAttributeBuilder {
|
||||
element_tag: el.name.clone(),
|
||||
name: AttributeName::Ident(name),
|
||||
value: TemplateAttributeValue::Dynamic(
|
||||
self.dynamic_context.add_attr(quote!(AttributeValue::Text(
|
||||
dioxus::core::exports::bumpalo::format!(in __bump, "{}", #value)
|
||||
.into_bump_str()
|
||||
))),
|
||||
),
|
||||
})
|
||||
}
|
||||
ElementAttr::CustomAttrExpression { name, value } => {
|
||||
attributes.push(TemplateAttributeBuilder {
|
||||
element_tag: el.name.clone(),
|
||||
name: AttributeName::Str(name),
|
||||
value: TemplateAttributeValue::Dynamic(
|
||||
self.dynamic_context.add_attr(quote!(AttributeValue::Text(
|
||||
dioxus::core::exports::bumpalo::format!(in __bump, "{}", #value)
|
||||
.into_bump_str()
|
||||
))),
|
||||
),
|
||||
})
|
||||
}
|
||||
ElementAttr::EventTokens { name, tokens } => {
|
||||
listeners.push(self.dynamic_context.add_listener(name, tokens))
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(key) = el.key {
|
||||
self.dynamic_context.add_key(quote!(
|
||||
dioxus::core::exports::bumpalo::format!(in __bump, "{}", #key)
|
||||
.into_bump_str()
|
||||
));
|
||||
}
|
||||
self.nodes.push(TemplateNodeBuilder {
|
||||
id,
|
||||
node_type: TemplateNodeTypeBuilder::Element(TemplateElementBuilder {
|
||||
tag: el.name,
|
||||
attributes,
|
||||
children: Vec::new(),
|
||||
listeners,
|
||||
parent,
|
||||
}),
|
||||
});
|
||||
|
||||
let children: Vec<_> = el
|
||||
.children
|
||||
.into_iter()
|
||||
.map(|child| self.build_node(child, Some(id)))
|
||||
.collect();
|
||||
let parent = &mut self.nodes[id.0];
|
||||
if let TemplateNodeTypeBuilder::Element(element) = &mut parent.node_type {
|
||||
element.children = children;
|
||||
}
|
||||
}
|
||||
|
||||
BodyNode::Component(comp) => {
|
||||
self.nodes.push(TemplateNodeBuilder {
|
||||
id,
|
||||
node_type: TemplateNodeTypeBuilder::DynamicNode(
|
||||
self.dynamic_context.add_node(BodyNode::Component(comp)),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
BodyNode::Text(txt) => {
|
||||
let mut segments = Vec::new();
|
||||
|
||||
for segment in txt.segments {
|
||||
segments.push(match segment {
|
||||
Segment::Literal(lit) => TextTemplateSegment::Static(lit),
|
||||
Segment::Formatted(fmted) => {
|
||||
TextTemplateSegment::Dynamic(self.dynamic_context.add_text(fmted))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
self.nodes.push(TemplateNodeBuilder {
|
||||
id,
|
||||
node_type: TemplateNodeTypeBuilder::Text(TextTemplate::new(segments)),
|
||||
});
|
||||
}
|
||||
|
||||
BodyNode::RawExpr(expr) => {
|
||||
self.nodes.push(TemplateNodeBuilder {
|
||||
id,
|
||||
node_type: TemplateNodeTypeBuilder::DynamicNode(
|
||||
self.dynamic_context.add_node(BodyNode::RawExpr(expr)),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
id
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
pub fn try_switch_dynamic_context(
|
||||
mut self,
|
||||
dynamic_context: DynamicTemplateContextBuilder,
|
||||
) -> Option<Self> {
|
||||
let attribute_mapping: HashMap<String, usize> = dynamic_context
|
||||
.attributes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, ts)| (ts.to_string(), i))
|
||||
.collect();
|
||||
let text_mapping: HashMap<String, usize> = dynamic_context
|
||||
.text
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, ts)| (ts.to_token_stream().to_string(), i))
|
||||
.collect();
|
||||
let listener_mapping: HashMap<(String, Expr), usize> = dynamic_context
|
||||
.listeners
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, ts)| (ts.clone(), i))
|
||||
.collect();
|
||||
let node_mapping: HashMap<String, usize> = dynamic_context
|
||||
.nodes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, ts)| (ts.to_token_stream().to_string(), i))
|
||||
.collect();
|
||||
|
||||
for node in &mut self.nodes {
|
||||
match &mut node.node_type {
|
||||
TemplateNodeTypeBuilder::Element(element) => {
|
||||
for listener in &mut element.listeners {
|
||||
*listener =
|
||||
*listener_mapping.get(&self.dynamic_context.listeners[*listener])?;
|
||||
}
|
||||
for attribute in &mut element.attributes {
|
||||
if let TemplateAttributeValue::Dynamic(idx) = &mut attribute.value {
|
||||
*idx = *attribute_mapping
|
||||
.get(&self.dynamic_context.attributes[*idx].to_string())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
TemplateNodeTypeBuilder::Text(txt) => {
|
||||
for seg in &mut txt.segments {
|
||||
if let TextTemplateSegment::Dynamic(idx) = seg {
|
||||
*idx = *text_mapping.get(
|
||||
&self.dynamic_context.text[*idx]
|
||||
.to_token_stream()
|
||||
.to_string(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
TemplateNodeTypeBuilder::DynamicNode(idx) => {
|
||||
*idx = *node_mapping.get(
|
||||
&self.dynamic_context.nodes[*idx]
|
||||
.to_token_stream()
|
||||
.to_string(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.dynamic_context = dynamic_context;
|
||||
|
||||
Some(self)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
pub fn try_into_owned(self, location: &OwnedCodeLocation) -> Result<OwnedTemplate, Error> {
|
||||
let mut nodes = Vec::new();
|
||||
let dynamic_mapping = self.dynamic_mapping(&nodes);
|
||||
for node in self.nodes {
|
||||
nodes.push(node.try_into_owned(location)?);
|
||||
}
|
||||
|
||||
Ok(OwnedTemplate {
|
||||
nodes,
|
||||
root_nodes: self.root_nodes,
|
||||
dynamic_mapping,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
pub fn dynamic_mapping(
|
||||
&self,
|
||||
resolved_nodes: &Vec<OwnedTemplateNode>,
|
||||
) -> OwnedDynamicNodeMapping {
|
||||
let dynamic_context = &self.dynamic_context;
|
||||
let mut node_mapping = vec![None; dynamic_context.nodes.len()];
|
||||
let nodes = &self.nodes;
|
||||
for n in nodes {
|
||||
match &n.node_type {
|
||||
TemplateNodeTypeBuilder::DynamicNode(idx) => node_mapping[*idx] = Some(n.id),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
let mut text_mapping = vec![Vec::new(); dynamic_context.text.len()];
|
||||
for n in nodes {
|
||||
match &n.node_type {
|
||||
TemplateNodeTypeBuilder::Text(txt) => {
|
||||
for seg in &txt.segments {
|
||||
match seg {
|
||||
TextTemplateSegment::Static(_) => (),
|
||||
TextTemplateSegment::Dynamic(idx) => text_mapping[*idx].push(n.id),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
let mut attribute_mapping = vec![Vec::new(); dynamic_context.attributes.len()];
|
||||
for n in nodes {
|
||||
match &n.node_type {
|
||||
TemplateNodeTypeBuilder::Element(el) => {
|
||||
for (i, attr) in el.attributes.iter().enumerate() {
|
||||
match attr.value {
|
||||
TemplateAttributeValue::Static(_) => (),
|
||||
TemplateAttributeValue::Dynamic(idx) => {
|
||||
attribute_mapping[idx].push((n.id, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
let mut listener_mapping = Vec::new();
|
||||
for n in nodes {
|
||||
match &n.node_type {
|
||||
TemplateNodeTypeBuilder::Element(el) => {
|
||||
if !el.listeners.is_empty() {
|
||||
listener_mapping.push(n.id);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
let mut volatile_mapping = Vec::new();
|
||||
for n in resolved_nodes {
|
||||
if let TemplateNodeType::Element(el) = &n.node_type {
|
||||
for (i, attr) in el.attributes.iter().enumerate() {
|
||||
if attr.attribute.volatile {
|
||||
volatile_mapping.push((n.id, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OwnedDynamicNodeMapping::new(
|
||||
node_mapping,
|
||||
text_mapping,
|
||||
attribute_mapping,
|
||||
volatile_mapping,
|
||||
listener_mapping,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for TemplateBuilder {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let Self {
|
||||
nodes,
|
||||
root_nodes,
|
||||
dynamic_context,
|
||||
} = self;
|
||||
|
||||
let mut node_mapping = vec![None; dynamic_context.nodes.len()];
|
||||
for n in nodes {
|
||||
match &n.node_type {
|
||||
TemplateNodeTypeBuilder::DynamicNode(idx) => node_mapping[*idx] = Some(n.id),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
let mut text_mapping = vec![Vec::new(); dynamic_context.text.len()];
|
||||
for n in nodes {
|
||||
match &n.node_type {
|
||||
TemplateNodeTypeBuilder::Text(txt) => {
|
||||
for seg in &txt.segments {
|
||||
match seg {
|
||||
TextTemplateSegment::Static(_) => (),
|
||||
TextTemplateSegment::Dynamic(idx) => text_mapping[*idx].push(n.id),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
let mut attribute_mapping = vec![Vec::new(); dynamic_context.attributes.len()];
|
||||
for n in nodes {
|
||||
match &n.node_type {
|
||||
TemplateNodeTypeBuilder::Element(el) => {
|
||||
for (i, attr) in el.attributes.iter().enumerate() {
|
||||
match attr.value {
|
||||
TemplateAttributeValue::Static(_) => (),
|
||||
TemplateAttributeValue::Dynamic(idx) => {
|
||||
attribute_mapping[idx].push((n.id, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
let mut listener_mapping = Vec::new();
|
||||
for n in nodes {
|
||||
match &n.node_type {
|
||||
TemplateNodeTypeBuilder::Element(el) => {
|
||||
if !el.listeners.is_empty() {
|
||||
listener_mapping.push(n.id);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
let root_nodes = root_nodes.iter().map(|id| {
|
||||
let raw = id.0;
|
||||
quote! { TemplateNodeId(#raw) }
|
||||
});
|
||||
let node_mapping_quoted = node_mapping.iter().map(|op| match op {
|
||||
Some(id) => {
|
||||
let raw_id = id.0;
|
||||
quote! {Some(TemplateNodeId(#raw_id))}
|
||||
}
|
||||
None => quote! {None},
|
||||
});
|
||||
let text_mapping_quoted = text_mapping.iter().map(|inner| {
|
||||
let raw = inner.iter().map(|id| id.0);
|
||||
quote! {&[#(TemplateNodeId(#raw)),*]}
|
||||
});
|
||||
let attribute_mapping_quoted = attribute_mapping.iter().map(|inner| {
|
||||
let raw = inner.iter().map(|(id, _)| id.0);
|
||||
let indecies = inner.iter().map(|(_, idx)| idx);
|
||||
quote! {&[#((TemplateNodeId(#raw), #indecies)),*]}
|
||||
});
|
||||
let listener_mapping_quoted = listener_mapping.iter().map(|id| {
|
||||
let raw = id.0;
|
||||
quote! {TemplateNodeId(#raw)}
|
||||
});
|
||||
let mut nodes_quoted = TokenStream::new();
|
||||
for n in nodes {
|
||||
n.to_tokens(&mut nodes_quoted, nodes);
|
||||
quote! {,}.to_tokens(&mut nodes_quoted);
|
||||
}
|
||||
|
||||
let quoted = quote! {
|
||||
{
|
||||
const __NODES: dioxus::prelude::StaticTemplateNodes = &[#nodes_quoted];
|
||||
const __TEXT_MAPPING: &'static [&'static [dioxus::prelude::TemplateNodeId]] = &[#(#text_mapping_quoted),*];
|
||||
const __ATTRIBUTE_MAPPING: &'static [&'static [(dioxus::prelude::TemplateNodeId, usize)]] = &[#(#attribute_mapping_quoted),*];
|
||||
const __ROOT_NODES: &'static [dioxus::prelude::TemplateNodeId] = &[#(#root_nodes),*];
|
||||
const __NODE_MAPPING: &'static [Option<dioxus::prelude::TemplateNodeId>] = &[#(#node_mapping_quoted),*];
|
||||
const __NODES_WITH_LISTENERS: &'static [dioxus::prelude::TemplateNodeId] = &[#(#listener_mapping_quoted),*];
|
||||
static __VOLITALE_MAPPING_INNER: dioxus::core::exports::once_cell::sync::Lazy<Vec<(dioxus::prelude::TemplateNodeId, usize)>> = dioxus::core::exports::once_cell::sync::Lazy::new(||{
|
||||
// check each property to see if it is volatile
|
||||
let mut volatile = Vec::new();
|
||||
for n in __NODES {
|
||||
if let TemplateNodeType::Element(el) = &n.node_type {
|
||||
for (i, attr) in el.attributes.iter().enumerate() {
|
||||
if attr.attribute.volatile {
|
||||
volatile.push((n.id, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
volatile
|
||||
});
|
||||
static __VOLITALE_MAPPING: &'static dioxus::core::exports::once_cell::sync::Lazy<Vec<(dioxus::prelude::TemplateNodeId, usize)>> = &__VOLITALE_MAPPING_INNER;
|
||||
static __STATIC_VOLITALE_MAPPING: dioxus::prelude::LazyStaticVec<(dioxus::prelude::TemplateNodeId, usize)> = LazyStaticVec(__VOLITALE_MAPPING);
|
||||
static __TEMPLATE: dioxus::prelude::Template = Template::Static(&StaticTemplate {
|
||||
nodes: __NODES,
|
||||
root_nodes: __ROOT_NODES,
|
||||
dynamic_mapping: StaticDynamicNodeMapping::new(__NODE_MAPPING, __TEXT_MAPPING, __ATTRIBUTE_MAPPING, __STATIC_VOLITALE_MAPPING, __NODES_WITH_LISTENERS),
|
||||
});
|
||||
|
||||
let __bump = __cx.bump();
|
||||
__cx.template_ref(dioxus::prelude::TemplateId(get_line_num!()), __TEMPLATE.clone(), #dynamic_context)
|
||||
}
|
||||
};
|
||||
|
||||
tokens.append_all(quoted)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct DynamicTemplateContextBuilder {
|
||||
nodes: Vec<BodyNode>,
|
||||
text: Vec<FormattedSegment>,
|
||||
attributes: Vec<TokenStream>,
|
||||
listeners: Vec<(String, Expr)>,
|
||||
key: Option<TokenStream>,
|
||||
}
|
||||
|
||||
impl DynamicTemplateContextBuilder {
|
||||
fn add_node(&mut self, node: BodyNode) -> usize {
|
||||
let node_id = self.nodes.len();
|
||||
|
||||
self.nodes.push(node);
|
||||
|
||||
node_id
|
||||
}
|
||||
|
||||
fn add_text(&mut self, text: FormattedSegment) -> usize {
|
||||
let text_id = self.text.len();
|
||||
|
||||
self.text.push(text);
|
||||
|
||||
text_id
|
||||
}
|
||||
|
||||
fn add_attr(&mut self, attr: TokenStream) -> usize {
|
||||
let attr_id = self.attributes.len();
|
||||
|
||||
self.attributes.push(attr);
|
||||
|
||||
attr_id
|
||||
}
|
||||
|
||||
fn add_listener(&mut self, name: Ident, listener: Expr) -> usize {
|
||||
let listener_id = self.listeners.len();
|
||||
|
||||
self.listeners.push((name.to_string(), listener));
|
||||
|
||||
listener_id
|
||||
}
|
||||
|
||||
fn add_key(&mut self, key: TokenStream) {
|
||||
self.key = Some(key);
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for DynamicTemplateContextBuilder {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let nodes = &self.nodes;
|
||||
let text = &self.text;
|
||||
let attributes = &self.attributes;
|
||||
let listeners_names = self
|
||||
.listeners
|
||||
.iter()
|
||||
.map(|(n, _)| syn::parse_str::<Ident>(n).expect(n));
|
||||
let listeners_exprs = self.listeners.iter().map(|(_, e)| e);
|
||||
let key = match &self.key {
|
||||
Some(k) => quote!(Some(#k)),
|
||||
None => quote!(None),
|
||||
};
|
||||
tokens.append_all(quote! {
|
||||
TemplateContext {
|
||||
nodes: __cx.bump().alloc([#(#nodes),*]),
|
||||
text_segments: __cx.bump().alloc([#(&*dioxus::core::exports::bumpalo::format!(in __bump, "{}", #text).into_bump_str()),*]),
|
||||
attributes: __cx.bump().alloc([#(#attributes),*]),
|
||||
listeners: __cx.bump().alloc([#(dioxus_elements::on::#listeners_names(__cx, #listeners_exprs)),*]),
|
||||
key: #key,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user