From cf0d09a7f08a805ba9d410979e0bebe574a0e34c Mon Sep 17 00:00:00 2001 From: Marsel Shaikhin Date: Tue, 26 Sep 2023 23:26:04 +0200 Subject: [PATCH] feat: add spans and patch flags --- README.md | 6 +- crates/fervid/src/parser/attributes.rs | 2 + crates/fervid/src/parser/core.rs | 6 + .../fervid_codegen/src/builtins/component.rs | 34 +++-- .../fervid_codegen/src/builtins/keepalive.rs | 30 +++- crates/fervid_codegen/src/builtins/slot.rs | 34 +++-- .../fervid_codegen/src/builtins/suspense.rs | 20 ++- .../fervid_codegen/src/builtins/teleport.rs | 20 ++- .../fervid_codegen/src/builtins/transition.rs | 20 ++- .../src/builtins/transition_group.rs | 22 +-- crates/fervid_codegen/src/components/mod.rs | 109 ++++++++++++-- .../src/control_flow/conditional_seq.rs | 40 +++-- .../src/control_flow/slotted_iterator.rs | 50 +++++-- .../fervid_codegen/src/directives/v_html.rs | 4 + .../fervid_codegen/src/directives/v_model.rs | 14 +- .../fervid_codegen/src/directives/v_text.rs | 21 +-- crates/fervid_codegen/src/elements/mod.rs | 73 +++++++-- crates/fervid_core/src/sfc.rs | 3 +- crates/fervid_core/src/structs.rs | 141 +++++++++++++++++- .../src/template/ast_transform.rs | 84 ++++++++++- 20 files changed, 581 insertions(+), 152 deletions(-) diff --git a/README.md b/README.md index 8d31aef..20952be 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ All-In-One Vue compiler written in Rust. Currently in early development, and the closest goal is to reach feature-parity with the current [Vue SFC compiler](https://sfc.vuejs.org). -## Progress till MVP ![](https://geps.dev/progress/68) +## Progress till MVP ![](https://geps.dev/progress/70) A minimal target of this project includes: - Vue 3 code generation; - [unplugin](https://github.com/unjs/unplugin) integration; @@ -20,7 +20,7 @@ A minimal target of this project includes: This project uses [Vue SFC playground](https://sfc.vuejs.org) as its reference to compare the output. As of April 2023, fervid is capable of producing the DEV code almost identical to the official compiler, except for: - [WIP] Context variables. This includes usages like `{{ foo + bar.buzz }}` or `
`. These usages require a JavaScript parser and transformer like SWC and support for them in fervid is currently ongoing. -- Patch flags. These are used to help Vue runtime when diffing the VNodes. If a VNode only has one prop which is dynamic, and all the other props and text are static, this needs to be conveyed to Vue for fast updates. I am currently researching how they are originally implemented. +- [WIP] Patch flags. These are used to help Vue runtime when diffing the VNodes. If a VNode only has one prop which is dynamic, and all the other props and text are static, this needs to be conveyed to Vue for fast updates. I am currently researching how they are originally implemented. To check correctness of fervid, you can compare the [run log](run.log) to the output of playground. For doing so, go to https://sfc.vuejs.org and paste in the contents of [crates/fervid/benches/fixtures/input.vue](crates/fervid/benches/fixtures/input.vue). @@ -133,7 +133,7 @@ Code generator - [ ] Eager pre-compilation of Vue imports (avoid unneccessary bundler->compiler calls) Integrations -- [ ] WASM binary (with/without WASI) +- [x] WASM binary (unpublished) - [x] NAPI binary (unpublished) - [ ] [unplugin](https://github.com/unjs/unplugin) - [ ] [Turbopack](https://github.com/vercel/turbo) plugin (when plugin system is defined) diff --git a/crates/fervid/src/parser/attributes.rs b/crates/fervid/src/parser/attributes.rs index d2366a7..7499d97 100644 --- a/crates/fervid/src/parser/attributes.rs +++ b/crates/fervid/src/parser/attributes.rs @@ -12,6 +12,7 @@ use nom::{ sequence::{delimited, preceded}, Err, IResult, }; +use swc_core::common::DUMMY_SP; use crate::parser::{ ecma::{parse_js, parse_js_pat}, @@ -359,6 +360,7 @@ fn parse_directive<'i>( argument, value: *model_binding, modifiers, + span: DUMMY_SP, // TODO }); } Result::Err(_) => {} diff --git a/crates/fervid/src/parser/core.rs b/crates/fervid/src/parser/core.rs index 7c5c5d0..1bf6623 100644 --- a/crates/fervid/src/parser/core.rs +++ b/crates/fervid/src/parser/core.rs @@ -122,6 +122,7 @@ fn parse_root_block<'a>( out.template = Some(SfcTemplateBlock { lang, roots: Vec::new(), + span: DUMMY_SP, // TODO }); return Ok(input); @@ -137,6 +138,7 @@ fn parse_root_block<'a>( out.template = Some(SfcTemplateBlock { lang, roots: children, + span: DUMMY_SP, // TODO }); return Ok(input); @@ -301,6 +303,8 @@ pub fn parse_element_node(input: &str) -> IResult<&str, Node> { children: vec![], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, // TODO }), )); } @@ -326,6 +330,8 @@ pub fn parse_element_node(input: &str) -> IResult<&str, Node> { children, template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, // TODO }), )) } diff --git a/crates/fervid_codegen/src/builtins/component.rs b/crates/fervid_codegen/src/builtins/component.rs index 61464dd..d057cf7 100644 --- a/crates/fervid_codegen/src/builtins/component.rs +++ b/crates/fervid_codegen/src/builtins/component.rs @@ -2,12 +2,9 @@ //! Please do not confuse with the user components. use fervid_core::{AttributeOrBinding, ElementNode, StrOrExpr, VBindDirective, VueImports}; -use swc_core::{ - common::DUMMY_SP, - ecma::{ - ast::{CallExpr, Callee, Expr, ExprOrSpread, Ident, Lit, ObjectLit, PropOrSpread, Str}, - atoms::JsWord, - }, +use swc_core::ecma::{ + ast::{CallExpr, Callee, Expr, ExprOrSpread, Ident, Lit, ObjectLit, PropOrSpread, Str}, + atoms::JsWord, }; use crate::CodegenContext; @@ -15,7 +12,7 @@ use crate::CodegenContext; impl CodegenContext { /// Generates the `` builtin pub fn generate_component_builtin(&mut self, element_node: &ElementNode) -> Expr { - let span = DUMMY_SP; // TODO + let span = element_node.span; // Shortcut let attributes = &element_node.starting_tag.attributes; @@ -94,13 +91,11 @@ impl CodegenContext { let component_builtin_slots = self.generate_builtin_slots(element_node); - let patch_flag = 0; // TODO This comes from the attributes - self.generate_componentlike( identifier, component_builtin_attrs, component_builtin_slots, - patch_flag, + &element_node.patch_hints, true, span, ) @@ -115,6 +110,7 @@ mod tests { AttributeOrBinding, BuiltinType, ElementKind, Node, StartingTag, VSlotDirective, VueDirectives, }; + use swc_core::common::DUMMY_SP; use crate::test_utils::js; @@ -135,6 +131,8 @@ mod tests { }, children: vec![], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#""#, ); @@ -159,6 +157,8 @@ mod tests { }, children: vec![], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"(_openBlock(),_createBlock(_resolveDynamicComponent("div")))"#, ); @@ -183,6 +183,8 @@ mod tests { }, children: vec![], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"(_openBlock(),_createBlock(_resolveDynamicComponent(foo)))"#, ); @@ -217,6 +219,8 @@ mod tests { }, children: vec![], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"(_openBlock(),_createBlock(_resolveDynamicComponent("div"),{foo:"bar",baz:qux}))"#, ) @@ -238,6 +242,8 @@ mod tests { }, children: vec![Node::Text("foobar")], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"(_openBlock(),_createBlock(_resolveDynamicComponent("div"),null,{"default":_withCtx(()=>[_createTextVNode("foobar")]),_:1}))"#, ) @@ -275,8 +281,12 @@ mod tests { }, children: vec![Node::Text("foobar")], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, })], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"(_openBlock(),_createBlock(_resolveDynamicComponent("div"),null,{named:_withCtx(()=>[_createTextVNode("foobar")]),_:1}))"#, ) @@ -332,9 +342,13 @@ mod tests { }, children: vec![Node::Text("bazqux")], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }), ], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"(_openBlock(),_createBlock(_resolveDynamicComponent("div"),{foo:"bar",baz:qux},{named:_withCtx(()=>[_createTextVNode("bazqux")]),"default":_withCtx(()=>[_createTextVNode("foobar")]),_:1}))"#, ) diff --git a/crates/fervid_codegen/src/builtins/keepalive.rs b/crates/fervid_codegen/src/builtins/keepalive.rs index 9175ecb..dc2be99 100644 --- a/crates/fervid_codegen/src/builtins/keepalive.rs +++ b/crates/fervid_codegen/src/builtins/keepalive.rs @@ -1,15 +1,12 @@ -use fervid_core::{ElementNode, VueImports}; -use swc_core::{ - common::DUMMY_SP, - ecma::ast::{ArrayLit, Expr, ExprOrSpread, Ident}, -}; +use fervid_core::{ElementNode, PatchFlags, PatchFlagsSet, PatchHints, VueImports}; +use swc_core::ecma::ast::{ArrayLit, Expr, ExprOrSpread, Ident}; use crate::CodegenContext; impl CodegenContext { /// Generates `(_openBlock(), _createBlock(_KeepAlive, null, [keepalive_children], 1024))` pub fn generate_keepalive(&mut self, element_node: &ElementNode) -> Expr { - let span = DUMMY_SP; // TODO + let span = element_node.span; // _KeepAlive let keepalive_identifier = Expr::Ident(Ident { @@ -41,13 +38,21 @@ impl CodegenContext { }; let should_wrap_in_block = keepalive_children.is_some(); - let patch_flag = if should_wrap_in_block { 1024 } else { 0 }; + + let patch_hints = PatchHints { + flags: if should_wrap_in_block { + PatchFlagsSet::from(PatchFlags::DynamicSlots) + } else { + PatchFlagsSet::default() + }, + props: vec![], + }; self.generate_componentlike( keepalive_identifier, keepalive_attrs, keepalive_children, - patch_flag, + &patch_hints, should_wrap_in_block, span, ) @@ -57,6 +62,7 @@ impl CodegenContext { #[cfg(test)] mod tests { use fervid_core::{AttributeOrBinding, BuiltinType, ElementKind, Node, StartingTag}; + use swc_core::common::DUMMY_SP; use crate::test_utils::js; @@ -75,6 +81,8 @@ mod tests { }, children: vec![], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createVNode(_KeepAlive)"#, ) @@ -105,6 +113,8 @@ mod tests { }, children: vec![], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createVNode(_KeepAlive,{foo:"bar",baz:qux})"#, ) @@ -123,6 +133,8 @@ mod tests { }, children: vec![Node::Text("foobar")], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"(_openBlock(),_createBlock(_KeepAlive,null,[_createTextVNode("foobar")],1024))"#, ) @@ -153,6 +165,8 @@ mod tests { }, children: vec![Node::Text("foobar")], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"(_openBlock(),_createBlock(_KeepAlive,{foo:"bar",baz:qux},[_createTextVNode("foobar")],1024))"#, ) diff --git a/crates/fervid_codegen/src/builtins/slot.rs b/crates/fervid_codegen/src/builtins/slot.rs index e7580ea..4e4c68b 100644 --- a/crates/fervid_codegen/src/builtins/slot.rs +++ b/crates/fervid_codegen/src/builtins/slot.rs @@ -1,13 +1,10 @@ use fervid_core::{AttributeOrBinding, ElementNode, StrOrExpr, VBindDirective, VueImports}; -use swc_core::{ - common::DUMMY_SP, - ecma::{ - ast::{ - ArrayLit, CallExpr, Callee, Expr, ExprOrSpread, Ident, Lit, MemberExpr, MemberProp, - ObjectLit, Str, - }, - atoms::{js_word, JsWord}, +use swc_core::ecma::{ + ast::{ + ArrayLit, CallExpr, Callee, Expr, ExprOrSpread, Ident, Lit, MemberExpr, MemberProp, + ObjectLit, Str, }, + atoms::{js_word, JsWord}, }; use crate::CodegenContext; @@ -20,7 +17,7 @@ impl CodegenContext { /// renderSlot(_ctx.$slots, "slot-name", /*optional*/ { slot: attributes }, /*optional*/ [slot, children]) /// ``` pub fn generate_slot(&mut self, element_node: &ElementNode) -> Expr { - let span = DUMMY_SP; // TODO + let span = element_node.span; // The `name` attribute should NOT be generated, // therefore we split attributes generation to two slices, like so: @@ -184,6 +181,7 @@ impl CodegenContext { #[cfg(test)] mod tests { use fervid_core::{BuiltinType, ElementKind, Node, StartingTag}; + use swc_core::common::DUMMY_SP; use crate::test_utils::js; @@ -200,6 +198,8 @@ mod tests { }, children: $children, template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, } }; } @@ -375,7 +375,9 @@ mod tests { directives: None }, children: vec![Node::Text("Placeholder")], - template_scope: 0 + template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }), Node::Element(ElementNode { kind: ElementKind::Component, @@ -385,7 +387,9 @@ mod tests { directives: None }, children: vec![], - template_scope: 0 + template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }) ] ), @@ -427,7 +431,9 @@ mod tests { directives: None }, children: vec![Node::Text("Placeholder")], - template_scope: 0 + template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }), Node::Element(ElementNode { kind: ElementKind::Component, @@ -437,7 +443,9 @@ mod tests { directives: None }, children: vec![], - template_scope: 0 + template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }) ] ), diff --git a/crates/fervid_codegen/src/builtins/suspense.rs b/crates/fervid_codegen/src/builtins/suspense.rs index c6ebb65..79e33ce 100644 --- a/crates/fervid_codegen/src/builtins/suspense.rs +++ b/crates/fervid_codegen/src/builtins/suspense.rs @@ -1,15 +1,12 @@ use fervid_core::{ElementNode, VueImports}; -use swc_core::{ - common::DUMMY_SP, - ecma::ast::{Expr, Ident}, -}; +use swc_core::ecma::ast::{Expr, Ident}; use crate::CodegenContext; impl CodegenContext { /// yeah, function name sounds funny pub fn generate_suspense(&mut self, element_node: &ElementNode) -> Expr { - let span = DUMMY_SP; // TODO + let span = element_node.span; // _Suspense let suspense_identifier = Expr::Ident(Ident { @@ -23,13 +20,11 @@ impl CodegenContext { let suspense_slots = self.generate_builtin_slots(element_node); - let patch_flag = 0; // TODO This comes from the attributes - self.generate_componentlike( suspense_identifier, suspense_attrs, suspense_slots, - patch_flag, + &element_node.patch_hints, true, span, ) @@ -39,6 +34,7 @@ impl CodegenContext { #[cfg(test)] mod tests { use fervid_core::{BuiltinType, ElementKind, StartingTag, Node, AttributeOrBinding}; + use swc_core::common::DUMMY_SP; use crate::test_utils::js; @@ -57,6 +53,8 @@ mod tests { }, children: vec![], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"(_openBlock(),_createBlock(_Suspense))"#, ) @@ -87,6 +85,8 @@ mod tests { }, children: vec![], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"(_openBlock(),_createBlock(_Suspense,{foo:"bar",baz:qux}))"#, ) @@ -105,6 +105,8 @@ mod tests { }, children: vec![Node::Text("foobar")], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"(_openBlock(),_createBlock(_Suspense,null,{"default":_withCtx(()=>[_createTextVNode("foobar")]),_:1}))"#, ) @@ -135,6 +137,8 @@ mod tests { }, children: vec![Node::Text("foobar")], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"(_openBlock(),_createBlock(_Suspense,{foo:"bar",baz:qux},{"default":_withCtx(()=>[_createTextVNode("foobar")]),_:1}))"#, ) diff --git a/crates/fervid_codegen/src/builtins/teleport.rs b/crates/fervid_codegen/src/builtins/teleport.rs index 743e550..d4a069c 100644 --- a/crates/fervid_codegen/src/builtins/teleport.rs +++ b/crates/fervid_codegen/src/builtins/teleport.rs @@ -1,15 +1,12 @@ use fervid_core::{ElementNode, VueImports}; -use swc_core::{ - common::DUMMY_SP, - ecma::ast::{ArrayLit, Expr, ExprOrSpread, Ident}, -}; +use swc_core::ecma::ast::{ArrayLit, Expr, ExprOrSpread, Ident}; use crate::CodegenContext; impl CodegenContext { /// Generates `(_openBlock(), _createBlock(_Teleport, null, [teleport_children]))` pub fn generate_teleport(&mut self, element_node: &ElementNode) -> Expr { - let span = DUMMY_SP; // TODO + let span = element_node.span; // _Teleport let teleport_identifier = Expr::Ident(Ident { @@ -40,13 +37,11 @@ impl CodegenContext { None }; - let patch_flag = 0; // TODO Patch flag? - self.generate_componentlike( teleport_identifier, teleport_attrs, teleport_children, - patch_flag, + &element_node.patch_hints, true, span, ) @@ -56,6 +51,7 @@ impl CodegenContext { #[cfg(test)] mod tests { use fervid_core::{AttributeOrBinding, BuiltinType, ElementKind, Node, StartingTag}; + use swc_core::common::DUMMY_SP; use crate::test_utils::js; @@ -74,6 +70,8 @@ mod tests { }, children: vec![], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"(_openBlock(),_createBlock(_Teleport))"#, ) @@ -104,6 +102,8 @@ mod tests { }, children: vec![], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"(_openBlock(),_createBlock(_Teleport,{foo:"bar",baz:qux}))"#, ) @@ -122,6 +122,8 @@ mod tests { }, children: vec![Node::Text("foobar")], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"(_openBlock(),_createBlock(_Teleport,null,[_createTextVNode("foobar")]))"#, ) @@ -152,6 +154,8 @@ mod tests { }, children: vec![Node::Text("foobar")], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"(_openBlock(),_createBlock(_Teleport,{foo:"bar",baz:qux},[_createTextVNode("foobar")]))"#, ) diff --git a/crates/fervid_codegen/src/builtins/transition.rs b/crates/fervid_codegen/src/builtins/transition.rs index d59971e..7ad666b 100644 --- a/crates/fervid_codegen/src/builtins/transition.rs +++ b/crates/fervid_codegen/src/builtins/transition.rs @@ -1,14 +1,11 @@ use fervid_core::{ElementNode, VueImports}; -use swc_core::{ - common::DUMMY_SP, - ecma::ast::{Expr, Ident}, -}; +use swc_core::ecma::ast::{Expr, Ident}; use crate::CodegenContext; impl CodegenContext { pub fn generate_transition(&mut self, element_node: &ElementNode) -> Expr { - let span = DUMMY_SP; // TODO + let span = element_node.span; // _Transition let transition_identifier = Expr::Ident(Ident { @@ -22,13 +19,11 @@ impl CodegenContext { let transition_slots = self.generate_builtin_slots(element_node); - let patch_flag = 0; // TODO This comes from the attributes - self.generate_componentlike( transition_identifier, transition_attrs, transition_slots, - patch_flag, + &element_node.patch_hints, false, span, ) @@ -38,6 +33,7 @@ impl CodegenContext { #[cfg(test)] mod tests { use fervid_core::{BuiltinType, ElementKind, StartingTag, Node, AttributeOrBinding}; + use swc_core::common::DUMMY_SP; use crate::test_utils::js; @@ -56,6 +52,8 @@ mod tests { }, children: vec![], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP }, r#"_createVNode(_Transition)"#, ) @@ -86,6 +84,8 @@ mod tests { }, children: vec![], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP }, r#"_createVNode(_Transition,{foo:"bar",baz:qux})"#, ) @@ -104,6 +104,8 @@ mod tests { }, children: vec![Node::Text("foobar")], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP }, r#"_createVNode(_Transition,null,{"default":_withCtx(()=>[_createTextVNode("foobar")]),_:1})"#, ) @@ -134,6 +136,8 @@ mod tests { }, children: vec![Node::Text("foobar")], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP }, r#"_createVNode(_Transition,{foo:"bar",baz:qux},{"default":_withCtx(()=>[_createTextVNode("foobar")]),_:1})"#, ) diff --git a/crates/fervid_codegen/src/builtins/transition_group.rs b/crates/fervid_codegen/src/builtins/transition_group.rs index 4483f10..5da815c 100644 --- a/crates/fervid_codegen/src/builtins/transition_group.rs +++ b/crates/fervid_codegen/src/builtins/transition_group.rs @@ -1,14 +1,11 @@ use fervid_core::{ElementNode, VueImports}; -use swc_core::{ - common::DUMMY_SP, - ecma::ast::{Expr, Ident}, -}; +use swc_core::ecma::ast::{Expr, Ident}; use crate::CodegenContext; impl CodegenContext { pub fn generate_transition_group(&mut self, element_node: &ElementNode) -> Expr { - let span = DUMMY_SP; // TODO + let span = element_node.span; // _TransitionGroup let transition_group_identifier = Expr::Ident(Ident { @@ -22,13 +19,11 @@ impl CodegenContext { let transition_group_slots = self.generate_builtin_slots(element_node); - let patch_flag = 0; // TODO This comes from the attributes - self.generate_componentlike( transition_group_identifier, transition_group_attrs, transition_group_slots, - patch_flag, + &element_node.patch_hints, false, span, ) @@ -37,7 +32,8 @@ impl CodegenContext { #[cfg(test)] mod tests { - use fervid_core::{BuiltinType, ElementKind, StartingTag, Node, AttributeOrBinding}; + use fervid_core::{AttributeOrBinding, BuiltinType, ElementKind, Node, StartingTag}; + use swc_core::common::DUMMY_SP; use crate::test_utils::js; @@ -56,6 +52,8 @@ mod tests { }, children: vec![], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createVNode(_TransitionGroup)"#, ) @@ -86,6 +84,8 @@ mod tests { }, children: vec![], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createVNode(_TransitionGroup,{foo:"bar",baz:qux})"#, ) @@ -104,6 +104,8 @@ mod tests { }, children: vec![Node::Text("foobar")], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createVNode(_TransitionGroup,null,{"default":_withCtx(()=>[_createTextVNode("foobar")]),_:1})"#, ) @@ -134,6 +136,8 @@ mod tests { }, children: vec![Node::Text("foobar")], template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createVNode(_TransitionGroup,{foo:"bar",baz:qux},{"default":_withCtx(()=>[_createTextVNode("foobar")]),_:1})"#, ) diff --git a/crates/fervid_codegen/src/components/mod.rs b/crates/fervid_codegen/src/components/mod.rs index 1e9d572..90f1732 100644 --- a/crates/fervid_codegen/src/components/mod.rs +++ b/crates/fervid_codegen/src/components/mod.rs @@ -1,4 +1,4 @@ -use fervid_core::{ElementNode, Node, StartingTag, VSlotDirective, VueDirectives, VueImports}; +use fervid_core::{ElementNode, Node, StartingTag, VSlotDirective, VueDirectives, VueImports, PatchHints}; use swc_core::{ common::{Span, DUMMY_SP}, ecma::{ @@ -22,8 +22,7 @@ impl CodegenContext { component_node: &ElementNode, wrap_in_block: bool, ) -> Expr { - // todo should it be span of the whole component or only of its starting tag? - let span = DUMMY_SP; + let span = component_node.span; let component_identifier = Expr::Ident(Ident { span, @@ -41,15 +40,12 @@ impl CodegenContext { let children_slots = self.generate_component_children(component_node); - // TODO Use the correct patch flag - let patch_flags = 0; - // Call the general constructor let mut create_component_expr = self.generate_componentlike( component_identifier, attributes_expr, children_slots, - patch_flags, + &component_node.patch_hints, wrap_in_block, span, ); @@ -67,7 +63,7 @@ impl CodegenContext { identifier: Expr, attributes: Option, children_or_slots: Option, - patch_flag: i32, + patch_hints: &PatchHints, wrap_in_block: bool, span: Span, ) -> Expr { @@ -75,8 +71,11 @@ impl CodegenContext { // 1st - component identifier; // 2nd (optional) - component attributes & directives object; // 3rd (optional) - component slots; - // 4th (optional) - component patch flag. - let expected_component_args_count = if patch_flag != 0 { + // 4th (optional) - component patch flag; + // 5th (optional) - props array (for PROPS patch flag). + let expected_component_args_count = if !patch_hints.props.is_empty() { + 5 + } else if !patch_hints.flags.is_empty() { 4 } else if children_or_slots.is_some() { 3 @@ -124,10 +123,36 @@ impl CodegenContext { spread: None, expr: Box::new(Expr::Lit(Lit::Num(Number { span, - value: patch_flag as f64, + value: patch_hints.flags.bits().into(), raw: None, }))), - }) + }); + + // Props array + if !patch_hints.props.is_empty() { + let props_array = patch_hints + .props + .iter() + .map(|prop| { + Some(ExprOrSpread { + spread: None, + expr: Box::new(Expr::Lit(Lit::Str(Str { + span: DUMMY_SP, + value: prop.to_owned(), + raw: None, + }))), + }) + }) + .collect(); + + create_component_args.push(ExprOrSpread { + spread: None, + expr: Box::new(Expr::Array(ArrayLit { + span: DUMMY_SP, + elems: props_array, + })), + }); + } } // When wrapping in block, `createBlock` is used, otherwise `createVNode` @@ -605,6 +630,8 @@ mod tests { children: vec![], template_scope: 0, kind: ElementKind::Component, + patch_hints: Default::default(), + span: DUMMY_SP, }, r"_createVNode(_component_test_component)", false, @@ -621,6 +648,8 @@ mod tests { children: vec![], template_scope: 0, kind: ElementKind::Component, + patch_hints: Default::default(), + span: DUMMY_SP, }, r"_createVNode(_component_test_component)", false, @@ -652,6 +681,8 @@ mod tests { children: vec![], template_scope: 0, kind: ElementKind::Component, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createVNode(_component_test_component,{foo:"bar","some-baz":qux})"#, false, @@ -679,10 +710,14 @@ mod tests { children: vec![Node::Text("hello from div")], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }), ], template_scope: 0, kind: ElementKind::Component, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createVNode(_component_test_component,null,{"default":_withCtx(()=>[_createTextVNode("hello from component"),_createElementVNode("div",null,"hello from div")])})"#, false, @@ -722,13 +757,19 @@ mod tests { children: vec![Node::Text("hello from div")], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }), ], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, })], template_scope: 0, kind: ElementKind::Component, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createVNode(_component_test_component,null,{"default":_withCtx(()=>[_createTextVNode("hello from component"),_createElementVNode("div",null,"hello from div")])})"#, false, @@ -771,13 +812,19 @@ mod tests { children: vec![Node::Text("hello from div")], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }), ], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, })], template_scope: 0, kind: ElementKind::Component, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createVNode(_component_test_component,null,{"foo-bar":_withCtx(()=>[_createTextVNode("hello from component"),_createElementVNode("div",null,"hello from div")])})"#, false, @@ -821,6 +868,8 @@ mod tests { ], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }), Node::Element(ElementNode { starting_tag: StartingTag { @@ -846,14 +895,20 @@ mod tests { children: vec![Node::Text("two")], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }), ], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }), ], template_scope: 0, kind: ElementKind::Component, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createVNode(_component_test_component,null,{"foo-bar":_withCtx(()=>[_createTextVNode("hello from slot "+_toDisplayString(one),1)]),baz:_withCtx(()=>[_createTextVNode("hello from slot "),_createElementVNode("b",null,"two")])})"#, false, @@ -884,6 +939,8 @@ mod tests { children: vec![Node::Text("hello from div")], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }), Node::Element(ElementNode { starting_tag: StartingTag { @@ -901,10 +958,14 @@ mod tests { children: vec![Node::Text("hello from slot")], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }), ], template_scope: 0, kind: ElementKind::Component, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createVNode(_component_test_component,null,{"foo-bar":_withCtx(()=>[_createTextVNode("hello from slot")]),"default":_withCtx(()=>[_createTextVNode("hello from component"),_createElementVNode("div",null,"hello from div")])})"#, false, @@ -946,10 +1007,14 @@ mod tests { children: vec![Node::Text("hello from div")], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }), ], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }), Node::Element(ElementNode { starting_tag: StartingTag { @@ -967,10 +1032,14 @@ mod tests { children: vec![Node::Text("hello from slot")], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }), ], template_scope: 0, kind: ElementKind::Component, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createVNode(_component_test_component,null,{"default":_withCtx(()=>[_createTextVNode("hello from default"),_createElementVNode("div",null,"hello from div")]),"foo-bar":_withCtx(()=>[_createTextVNode("hello from slot")])})"#, false, @@ -1004,6 +1073,8 @@ mod tests { children: vec![Node::Text("hello from slot")], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }), Node::Text("hello from component"), Node::Element(ElementNode { @@ -1015,10 +1086,14 @@ mod tests { children: vec![Node::Text("hello from div")], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }), ], template_scope: 0, kind: ElementKind::Component, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createVNode(_component_test_component,null,{"foo-bar":_withCtx(()=>[_createTextVNode("hello from slot")]),"default":_withCtx(()=>[_createTextVNode("hello from component"),_createElementVNode("div",null,"hello from div")])})"#, false, @@ -1056,6 +1131,8 @@ mod tests { children: vec![Node::Text("hello from slot")], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }), Node::Element(ElementNode { starting_tag: StartingTag { @@ -1081,10 +1158,14 @@ mod tests { children: vec![Node::Text("hello from div")], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }), ], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }), Node::Element(ElementNode { starting_tag: StartingTag { @@ -1102,10 +1183,14 @@ mod tests { children: vec![Node::Text("hello from baz")], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }), ], template_scope: 0, kind: ElementKind::Component, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createVNode(_component_test_component,null,{"foo-bar":_withCtx(()=>[_createTextVNode("hello from slot")]),"default":_withCtx(()=>[_createTextVNode("hello from default"),_createElementVNode("div",null,"hello from div")]),baz:_withCtx(()=>[_createTextVNode("hello from baz")])})"#, false, diff --git a/crates/fervid_codegen/src/control_flow/conditional_seq.rs b/crates/fervid_codegen/src/control_flow/conditional_seq.rs index 0468883..7289439 100644 --- a/crates/fervid_codegen/src/control_flow/conditional_seq.rs +++ b/crates/fervid_codegen/src/control_flow/conditional_seq.rs @@ -93,7 +93,9 @@ mod tests { }, children: vec![Node::Text("hello")], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }, }), else_if_nodes: vec![], @@ -119,7 +121,9 @@ mod tests { }, children: vec![Node::Text("hello")], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }, }), else_if_nodes: vec![], @@ -131,7 +135,9 @@ mod tests { }, children: vec![Node::Text("bye")], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, })), }, r#"foo||true?(_openBlock(),_createElementBlock("h1",null,"hello")):(_openBlock(),_createElementBlock("h2",null,"bye"))"#, @@ -155,7 +161,9 @@ mod tests { }, children: vec![Node::Text("hello")], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }, }), else_if_nodes: vec![ @@ -169,7 +177,9 @@ mod tests { }, children: vec![Node::Text("hi")], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }, }, Conditional { @@ -182,7 +192,9 @@ mod tests { }, children: vec![Node::Text("bye")], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }, }, ], @@ -210,7 +222,9 @@ mod tests { }, children: vec![Node::Text("hello")], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }, }), else_if_nodes: vec![ @@ -224,7 +238,9 @@ mod tests { }, children: vec![Node::Text("hi")], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }, }, Conditional { @@ -237,7 +253,9 @@ mod tests { }, children: vec![Node::Text("good morning")], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }, }, ], @@ -249,7 +267,9 @@ mod tests { }, children: vec![Node::Text("bye")], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, })), }, r#"foo?(_openBlock(),_createElementBlock("h1",null,"hello")):true?(_openBlock(),_createElementBlock("h2",null,"hi")):undefined?(_openBlock(),_createElementBlock("h3",null,"good morning")):(_openBlock(),_createElementBlock("h4",null,"bye"))"#, diff --git a/crates/fervid_codegen/src/control_flow/slotted_iterator.rs b/crates/fervid_codegen/src/control_flow/slotted_iterator.rs index d53d08b..d0e22b5 100644 --- a/crates/fervid_codegen/src/control_flow/slotted_iterator.rs +++ b/crates/fervid_codegen/src/control_flow/slotted_iterator.rs @@ -114,8 +114,12 @@ fn is_from_default_slot(node: &Node) -> bool { // `v-slot:default` is default // `v-slot:custom` is not default // `v-slot:[default]` is not default - let Some(ref directives) = starting_tag.directives else { return true; }; - let Some(ref v_slot) = directives.v_slot else { return true; }; + let Some(ref directives) = starting_tag.directives else { + return true; + }; + let Some(ref v_slot) = directives.v_slot else { + return true; + }; if v_slot.is_dynamic_slot { return false; } @@ -137,7 +141,11 @@ fn is_from_default_slot(node: &Node) -> bool { #[cfg(test)] mod tests { - use fervid_core::{StartingTag, VBindDirective, VOnDirective, VSlotDirective, VueDirectives, AttributeOrBinding, ElementKind}; + use fervid_core::{ + AttributeOrBinding, ElementKind, StartingTag, VBindDirective, VOnDirective, VSlotDirective, + VueDirectives, + }; + use swc_core::common::DUMMY_SP; use crate::test_utils::js; @@ -235,11 +243,13 @@ mod tests { starting_tag: StartingTag { tag_name: "h1", attributes: vec![], - directives: None + directives: None, }, children: vec![Node::Text("This is an h1")], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }) } @@ -261,11 +271,13 @@ mod tests { is_attr: false, }), ], - directives: None + directives: None, }, children: vec![], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }) } @@ -288,11 +300,13 @@ mod tests { modifiers: vec![], }), ], - directives: None + directives: None, }, children: vec![Node::Text("This is a component")], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }) } @@ -302,11 +316,13 @@ mod tests { starting_tag: StartingTag { tag_name: "template", attributes: vec![], - directives: None + directives: None, }, children: vec![Node::Text("This is just a template")], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }) } @@ -323,11 +339,13 @@ mod tests { is_dynamic_slot: false, }), ..Default::default() - })) + })), }, children: vec![Node::Text("This is a default template")], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }) } @@ -344,11 +362,13 @@ mod tests { is_dynamic_slot: false, }), ..Default::default() - })) + })), }, children: vec![Node::Text("This is a named template")], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }) } } diff --git a/crates/fervid_codegen/src/directives/v_html.rs b/crates/fervid_codegen/src/directives/v_html.rs index a5284b2..069d039 100644 --- a/crates/fervid_codegen/src/directives/v_html.rs +++ b/crates/fervid_codegen/src/directives/v_html.rs @@ -58,6 +58,8 @@ mod tests { })), }, template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createVNode(_component_test_component,{innerHTML:foo+bar})"#, false, @@ -93,6 +95,8 @@ mod tests { })), }, template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createElementVNode("h1",{innerHTML:foo+bar})"#, false, diff --git a/crates/fervid_codegen/src/directives/v_model.rs b/crates/fervid_codegen/src/directives/v_model.rs index dff559b..c49a87a 100644 --- a/crates/fervid_codegen/src/directives/v_model.rs +++ b/crates/fervid_codegen/src/directives/v_model.rs @@ -1,6 +1,6 @@ use fervid_core::VModelDirective; use swc_core::{ - common::{Span, DUMMY_SP}, + common::Span, ecma::{ ast::{ ArrowExpr, AssignExpr, AssignOp, BindingIdent, BlockStmtOrExpr, Bool, Expr, Ident, @@ -23,8 +23,7 @@ impl CodegenContext { out: &mut Vec, scope_to_use: u32, ) -> bool { - // TODO Spans - let span = DUMMY_SP; + let span = v_model.span; // `v-model="smth"` is same as `v-model:modelValue="smth"` let bound_attribute = v_model.argument.unwrap_or("modelValue"); @@ -167,7 +166,7 @@ fn generate_v_model_modifiers(modifiers: &[&str], span: Span) -> ObjectLit { #[cfg(test)] mod tests { - use swc_core::ecma::ast::ObjectLit; + use swc_core::{common::DUMMY_SP, ecma::ast::ObjectLit}; use crate::test_utils::js; @@ -181,6 +180,7 @@ mod tests { argument: None, value: *js("foo"), modifiers: Vec::new(), + span: DUMMY_SP, }], r#"{modelValue:foo,"onUpdate:modelValue":$event=>((foo)=$event)}"#, ); @@ -194,6 +194,7 @@ mod tests { argument: Some("simple"), value: *js("foo"), modifiers: Vec::new(), + span: DUMMY_SP, }], r#"{simple:foo,"onUpdate:simple":$event=>((foo)=$event)}"#, ); @@ -204,6 +205,7 @@ mod tests { argument: Some("modelValue"), value: *js("bar"), modifiers: Vec::new(), + span: DUMMY_SP, }], r#"{modelValue:bar,"onUpdate:modelValue":$event=>((bar)=$event)}"#, ); @@ -214,6 +216,7 @@ mod tests { argument: Some("model-value"), value: *js("baz"), modifiers: Vec::new(), + span: DUMMY_SP, }], r#"{"model-value":baz,"onUpdate:modelValue":$event=>((baz)=$event)}"#, ); @@ -227,6 +230,7 @@ mod tests { argument: None, value: *js("foo"), modifiers: vec!["lazy", "trim"], + span: DUMMY_SP, }], r#"{modelValue:foo,"onUpdate:modelValue":$event=>((foo)=$event),modelModifiers:{lazy:true,trim:true}}"#, ); @@ -237,6 +241,7 @@ mod tests { argument: None, value: *js("foo"), modifiers: vec!["custom-modifier"], + span: DUMMY_SP, }], r#"{modelValue:foo,"onUpdate:modelValue":$event=>((foo)=$event),modelModifiers:{"custom-modifier":true}}"#, ); @@ -247,6 +252,7 @@ mod tests { argument: Some("foo-bar"), value: *js("bazQux"), modifiers: vec!["custom-modifier"], + span: DUMMY_SP, }], r#"{"foo-bar":bazQux,"onUpdate:fooBar":$event=>((bazQux)=$event),"foo-barModifiers":{"custom-modifier":true}}"#, ); diff --git a/crates/fervid_codegen/src/directives/v_text.rs b/crates/fervid_codegen/src/directives/v_text.rs index c790b41..1d3fd34 100644 --- a/crates/fervid_codegen/src/directives/v_text.rs +++ b/crates/fervid_codegen/src/directives/v_text.rs @@ -30,6 +30,8 @@ mod tests { use fervid_core::{ElementKind, ElementNode, StartingTag, VueDirectives}; use swc_core::ecma::ast::BinExpr; + use crate::test_utils::js; + use super::*; #[test] @@ -61,6 +63,8 @@ mod tests { })), }, template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createVNode(_component_test_component,{textContent:foo+bar})"#, false, @@ -78,24 +82,13 @@ mod tests { tag_name: "h1", attributes: vec![], directives: Some(Box::new(VueDirectives { - v_text: Some(Box::new(Expr::Bin(BinExpr { - span: DUMMY_SP, - op: swc_core::ecma::ast::BinaryOp::Add, - left: Box::new(Expr::Ident(Ident { - span: DUMMY_SP, - sym: JsWord::from("foo"), - optional: false, - })), - right: Box::new(Expr::Ident(Ident { - span: DUMMY_SP, - sym: JsWord::from("bar"), - optional: false, - })), - }))), + v_text: Some(js("foo + bar")), ..Default::default() })), }, template_scope: 0, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createElementVNode("h1",{textContent:foo+bar})"#, false, diff --git a/crates/fervid_codegen/src/elements/mod.rs b/crates/fervid_codegen/src/elements/mod.rs index c5e8d58..0cf8c8b 100644 --- a/crates/fervid_codegen/src/elements/mod.rs +++ b/crates/fervid_codegen/src/elements/mod.rs @@ -20,8 +20,6 @@ impl CodegenContext { element_node: &ElementNode, wrap_in_block: bool, ) -> Expr { - // TODO - let needs_patch_flags = false; let span = DUMMY_SP; let starting_tag = &element_node.starting_tag; @@ -50,8 +48,11 @@ impl CodegenContext { // 1st - element name or Fragment; // 2nd (optional) - element attributes & directives object; // 3rd (optional) - element children; - // 4th (optional) - element patch flag. - let expected_element_args_count = if needs_patch_flags { + // 4th (optional) - element patch flag; + // 5th (optional) - props array (for PROPS patch flag). + let expected_element_args_count = if !element_node.patch_hints.props.is_empty() { + 5 + } else if !element_node.patch_hints.flags.is_empty() { 4 } else if children.len() != 0 { 3 @@ -141,15 +142,42 @@ impl CodegenContext { // Arg 4 (optional): patch flags (default to nothing) if expected_element_args_count >= 4 { - // TODO Actual patch flag value + let patch_flag_value = element_node.patch_hints.flags.bits(); + create_element_args.push(ExprOrSpread { spread: None, expr: Box::new(Expr::Lit(Lit::Num(Number { span, - value: 512.0, // TODO + value: patch_flag_value.into(), raw: None, }))), - }) + }); + + if !element_node.patch_hints.props.is_empty() { + let props_array = element_node + .patch_hints + .props + .iter() + .map(|prop| { + Some(ExprOrSpread { + spread: None, + expr: Box::new(Expr::Lit(Lit::Str(Str { + span: DUMMY_SP, + value: prop.to_owned(), + raw: None, + }))), + }) + }) + .collect(); + + create_element_args.push(ExprOrSpread { + spread: None, + expr: Box::new(Expr::Array(ArrayLit { + span: DUMMY_SP, + elems: props_array, + })), + }); + } } // When wrapping in block, `createElementBlock` is used, otherwise `createElementVNode` @@ -337,7 +365,8 @@ impl CodegenContext { #[cfg(test)] mod tests { use fervid_core::{ - AttributeOrBinding, Interpolation, Node, StartingTag, VBindDirective, VOnDirective, ElementKind, + AttributeOrBinding, ElementKind, Interpolation, Node, StartingTag, VBindDirective, + VOnDirective, }; use super::*; @@ -384,7 +413,9 @@ mod tests { }, children: vec![Node::Text("hello from div")], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createElementVNode("div",{foo:"bar",baz:qux,readonly:true,onClick:handleClick},"hello from div")"#, false, @@ -403,7 +434,9 @@ mod tests { }, children: vec![Node::Text("hello from div")], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createElementVNode("div",null,"hello from div")"#, false, @@ -434,7 +467,9 @@ mod tests { }, children: vec![], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createElementVNode("div",{foo:"bar","some-baz":qux})"#, false, @@ -462,7 +497,9 @@ mod tests { }, children: vec![], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createElementVNode("div",{foo:"bar","some-baz":qux})"#, false, @@ -489,7 +526,9 @@ mod tests { Node::Text(" bye!"), ], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createElementVNode("div",null,"hello from div "+_toDisplayString(true)+" bye!")"#, false, @@ -521,11 +560,15 @@ mod tests { }, children: vec![Node::Text("bye!")], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }), ], template_scope: 0, - kind: ElementKind::Element + kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }, r#"_createElementVNode("div",null,[_createTextVNode("hello from div "+_toDisplayString(true)),_createElementVNode("span",null,"bye!")])"#, false, diff --git a/crates/fervid_core/src/sfc.rs b/crates/fervid_core/src/sfc.rs index d53b4c5..62d76b7 100644 --- a/crates/fervid_core/src/sfc.rs +++ b/crates/fervid_core/src/sfc.rs @@ -1,4 +1,4 @@ -use swc_core::ecma::ast::Module; +use swc_core::{ecma::ast::Module, common::Span}; use crate::{Node, StartingTag}; @@ -15,6 +15,7 @@ pub struct SfcDescriptor<'a> { pub struct SfcTemplateBlock<'a> { pub lang: &'a str, pub roots: Vec>, + pub span: Span } #[derive(Clone, Debug)] diff --git a/crates/fervid_core/src/structs.rs b/crates/fervid_core/src/structs.rs index a1ed9f4..97dd82a 100644 --- a/crates/fervid_core/src/structs.rs +++ b/crates/fervid_core/src/structs.rs @@ -1,4 +1,4 @@ -use swc_core::ecma::ast::{Expr, Pat}; +use swc_core::{ecma::{ast::{Expr, Pat}, atoms::JsWord}, common::Span}; /// A Node represents a part of the Abstract Syntax Tree (AST). #[derive(Debug, Clone)] @@ -27,6 +27,11 @@ pub enum Node<'a> { /// `ConditionalSeq` is a representation of `v-if`/`v-else-if`/`v-else` node sequence. /// Its children are the other `Node`s, this node is just a wrapper. ConditionalSeq(ConditionalNodeSequence<'a>), + + // /// `ForFragment` is a representation of a `v-for` node. + // /// This type is for ergonomics, + // /// i.e. to separate patch flags and `key` of the repeater from the repeatable. + // ForFragment(ForFragment<'a>) } /// Element node is a classic HTML node with some added functionality: @@ -41,6 +46,8 @@ pub struct ElementNode<'a> { pub starting_tag: StartingTag<'a>, pub children: Vec>, pub template_scope: u32, + pub patch_hints: PatchHints, + pub span: Span } #[derive(Debug, Clone, Copy, Default)] @@ -48,7 +55,7 @@ pub enum ElementKind { Builtin(BuiltinType), #[default] Element, - Component + Component, } #[derive(Debug, Clone, Copy)] @@ -78,14 +85,14 @@ pub struct ConditionalNodeSequence<'a> { #[derive(Debug, Clone)] pub struct Conditional<'e> { pub condition: Expr, - pub node: ElementNode<'e> + pub node: ElementNode<'e>, } #[derive(Debug, Clone)] pub struct Interpolation { pub value: Box, pub template_scope: u32, - pub patch_flag: bool + pub patch_flag: bool, } /// Starting tag represents [`ElementNode`]'s tag name and attributes @@ -118,15 +125,136 @@ pub enum AttributeOrBinding<'a> { #[derive(Debug, Clone)] pub enum StrOrExpr<'s> { Str(&'s str), - Expr(Box) + Expr(Box), } -impl <'s> From<&'s str> for StrOrExpr<'s> { +impl<'s> From<&'s str> for StrOrExpr<'s> { fn from(value: &'s str) -> StrOrExpr<'s> { StrOrExpr::Str(value) } } +#[derive(Debug, Default, Clone)] +pub struct PatchHints { + /// Patch flags + pub flags: PatchFlagsSet, + /// Dynamic props + pub props: Vec +} + +flagset::flags! { + /// From https://github.com/vuejs/core/blob/b8fc18c0b23be9a77b05dc41ed452a87a0becf82/packages/shared/src/patchFlags.ts + #[derive(Default)] + pub enum PatchFlags: i32 { + /** + * Indicates an element with dynamic textContent (children fast path) + */ + Text = 1, + + /** + * Indicates an element with dynamic class binding. + */ + Class = 1 << 1, + + /** + * Indicates an element with dynamic style + * The compiler pre-compiles static string styles into static objects + * + detects and hoists inline static objects + * e.g. `style="color: red"` and `:style="{ color: 'red' }"` both get hoisted + * as: + * ```js + * const style = { color: 'red' } + * render() { return e('div', { style }) } + * ``` + */ + Style = 1 << 2, + + /** + * Indicates an element that has non-class/style dynamic props. + * Can also be on a component that has any dynamic props (includes + * class/style). when this flag is present, the vnode also has a dynamicProps + * array that contains the keys of the props that may change so the runtime + * can diff them faster (without having to worry about removed props) + */ + Props = 1 << 3, + + /** + * Indicates an element with props with dynamic keys. When keys change, a full + * diff is always needed to remove the old key. This flag is mutually + * exclusive with CLASS, STYLE and PROPS. + */ + FullProps = 1 << 4, + + /** + * Indicates an element with event listeners (which need to be attached + * during hydration) + */ + HydrateEvents = 1 << 5, + + /** + * Indicates a fragment whose children order doesn't change. + */ + StableFragment = 1 << 6, + + /** + * Indicates a fragment with keyed or partially keyed children + */ + KeyedFragment = 1 << 7, + + /** + * Indicates a fragment with unkeyed children. + */ + UnkeyedFragment = 1 << 8, + + /** + * Indicates an element that only needs non-props patching, e.g. ref or + * directives (onVnodeXXX hooks). since every patched vnode checks for refs + * and onVnodeXXX hooks, it simply marks the vnode so that a parent block + * will track it. + */ + #[default] + NeedPatch = 1 << 9, + + /** + * Indicates a component with dynamic slots (e.g. slot that references a v-for + * iterated value, or dynamic slot names). + * Components with this flag are always force updated. + */ + DynamicSlots = 1 << 10, + + /** + * Indicates a fragment that was created only because the user has placed + * comments at the root level of a template. This is a dev-only flag since + * comments are stripped in production. + */ + DevRootFragment = 1 << 11, + + /** + * SPECIAL FLAGS ------------------------------------------------------------- + * Special flags are negative integers. They are never matched against using + * bitwise operators (bitwise matching should only happen in branches where + * patchFlag > 0), and are mutually exclusive. When checking for a special + * flag, simply check patchFlag === FLAG. + */ + + /** + * Indicates a hoisted static vnode. This is a hint for hydration to skip + * the entire sub tree since static content never needs to be updated. + */ + Hoisted = -1, + /** + * A special flag that indicates that the diffing algorithm should bail out + * of optimized mode. For example, on block fragments created by renderSlot() + * when encountering non-compiler generated slots (i.e. manually written + * render functions, which should always be fully diffed) + * OR manually cloneVNodes + */ + Bail = -2, + } +} + +pub type PatchFlagsSet = flagset::FlagSet; + #[derive(Clone, Debug, Default)] pub struct VueDirectives<'d> { pub custom: Vec>, @@ -185,6 +313,7 @@ pub struct VModelDirective<'a> { pub value: Expr, /// `lazy` and `trim` in `v-model.lazy.trim` pub modifiers: Vec<&'a str>, + pub span: Span } #[derive(Clone, Debug)] diff --git a/crates/fervid_transform/src/template/ast_transform.rs b/crates/fervid_transform/src/template/ast_transform.rs index e876a95..fa39ade 100644 --- a/crates/fervid_transform/src/template/ast_transform.rs +++ b/crates/fervid_transform/src/template/ast_transform.rs @@ -1,9 +1,10 @@ use fervid_core::{ is_from_default_slot, is_html_tag, AttributeOrBinding, Conditional, ConditionalNodeSequence, - ElementKind, ElementNode, Interpolation, Node, SfcTemplateBlock, StartingTag, StrOrExpr, - VBindDirective, VOnDirective, VSlotDirective, VUE_BUILTINS, + ElementKind, ElementNode, Interpolation, Node, PatchFlags, SfcTemplateBlock, StartingTag, + StrOrExpr, VBindDirective, VOnDirective, VSlotDirective, VUE_BUILTINS, }; use smallvec::SmallVec; +use swc_core::ecma::atoms::JsWord; use crate::structs::{ScopeHelper, TemplateScope}; @@ -44,6 +45,8 @@ pub fn transform_and_record_template( }, children: all_roots, template_scope: 0, + patch_hints: Default::default(), + span: template.span, }); template.roots.push(new_root); } @@ -228,6 +231,11 @@ impl<'a> Visitor for TemplateVisitor<'_> { let parent_scope = self.current_scope; let mut scope_to_use = parent_scope; + // Mark the node with a correct type (element, component or built-in) + let element_kind = self.recognize_element_kind(&element_node.starting_tag); + let is_component = matches!(element_kind, ElementKind::Component); + element_node.kind = element_kind; + // Check if there is a scoping directive // Finds a `v-for` or `v-slot` directive when in ElementNode // and collects their variables into the new template scope @@ -274,16 +282,59 @@ impl<'a> Visitor for TemplateVisitor<'_> { // Transform the VBind and VOn attributes for attr in element_node.starting_tag.attributes.iter_mut() { match attr { + // The logic for the patch flags: + // 1. Check if the attribute name is dynamic (`:foo` vs `:[foo]`) or ; + // If it is, clear the previous prop hints and set FULL_PROPS, then continue loop; + // 2. Check if there is a Js variable in the value; + // If there is, check if it is a component + // 2. Check if AttributeOrBinding::VBind(v_bind) => { - self.scope_helper + let has_bindings = self + .scope_helper .transform_expr(&mut v_bind.value, scope_to_use); + let patch_hints = &mut element_node.patch_hints; + + let Some(StrOrExpr::Str(argument)) = v_bind.argument else { + // This is dynamic + // From docs: [FULL_PROPS is] exclusive with CLASS, STYLE and PROPS. + patch_hints.flags &= + !(PatchFlags::Props | PatchFlags::Class | PatchFlags::Style); + patch_hints.flags |= PatchFlags::FullProps; + patch_hints.props.clear(); + continue; + }; + + // Again, if we are FULL_PROPS already, do not add other props/class/style. + // Or if we do not need to add. + if !has_bindings || patch_hints.flags.contains(PatchFlags::FullProps) { + continue; + } + + // Adding `class` and `style` bindings depends on `is_component` + // They are added to PROPS for the components. + if is_component { + patch_hints.flags |= PatchFlags::Props; + patch_hints.props.push(JsWord::from(argument)); + continue; + } + + if argument == "class" { + patch_hints.flags |= PatchFlags::Class; + } else if argument == "style" { + patch_hints.flags |= PatchFlags::Style; + } else { + patch_hints.flags |= PatchFlags::Props; + patch_hints.props.push(JsWord::from(argument)); + } } + AttributeOrBinding::VOn(VOnDirective { handler: Some(ref mut handler), .. }) => { self.scope_helper.transform_expr(handler, scope_to_use); } + _ => {} } } @@ -304,10 +355,6 @@ impl<'a> Visitor for TemplateVisitor<'_> { maybe_transform!(v_text); } - // Mark the node with a correct type (element, component or built-in) - let element_kind = self.recognize_element_kind(&element_node.starting_tag); - element_node.kind = element_kind; - // Merge conditional nodes and clean up whitespace optimize_children(&mut element_node.children, element_kind); @@ -403,7 +450,7 @@ impl VisitMut for Node<'_> { #[cfg(test)] mod tests { use fervid_core::{ElementKind, Node, VueDirectives}; - use swc_core::ecma::ast::Expr; + use swc_core::{common::DUMMY_SP, ecma::ast::Expr}; use crate::test_utils::{parser::parse_javascript_expr, to_str}; @@ -448,7 +495,10 @@ mod tests { children: vec![text_node(), if_node(), else_if_node(), else_node()], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, })], + span: DUMMY_SP, }; transform_and_record_template(&mut sfc_template, &mut Default::default()); @@ -487,6 +537,7 @@ mod tests { let mut sfc_template = SfcTemplateBlock { lang: "html", roots: vec![if_node(), else_if_node(), else_node()], + span: DUMMY_SP, }; transform_and_record_template(&mut sfc_template, &mut Default::default()); @@ -517,6 +568,7 @@ mod tests { let mut sfc_template = SfcTemplateBlock { lang: "html", roots: vec![if_node(), if_node()], + span: DUMMY_SP, }; transform_and_record_template(&mut sfc_template, &mut Default::default()); @@ -550,6 +602,7 @@ mod tests { let mut sfc_template = SfcTemplateBlock { lang: "html", roots: vec![if_node(), else_if_node(), if_node(), else_if_node()], + span: DUMMY_SP, }; transform_and_record_template(&mut sfc_template, &mut Default::default()); @@ -581,6 +634,7 @@ mod tests { let mut sfc_template = SfcTemplateBlock { lang: "html", roots: vec![else_if_node(), else_node()], + span: DUMMY_SP, }; transform_and_record_template(&mut sfc_template, &mut Default::default()); @@ -626,7 +680,10 @@ mod tests { ], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, })], + span: DUMMY_SP, }; transform_and_record_template(&mut sfc_template, &mut Default::default()); @@ -660,6 +717,8 @@ mod tests { children: vec![], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }); let no_directives2 = Node::Element(ElementNode { @@ -673,11 +732,14 @@ mod tests { children: vec![Node::Text("hello")], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }); let mut sfc_template = SfcTemplateBlock { lang: "html", roots: vec![no_directives1, no_directives2], + span: DUMMY_SP, }; transform_and_record_template(&mut sfc_template, &mut Default::default()); @@ -713,6 +775,8 @@ mod tests { children: vec![Node::Text("if")], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }) } @@ -741,6 +805,8 @@ mod tests { children: vec![Node::Text("else-if")], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }) } @@ -770,6 +836,8 @@ mod tests { children: vec![Node::Text("else")], template_scope: 0, kind: ElementKind::Element, + patch_hints: Default::default(), + span: DUMMY_SP, }) }