Skip to content

Commit

Permalink
feat: implement advanced transforms and v-model
Browse files Browse the repository at this point in the history
  • Loading branch information
phoenix-ru committed Nov 15, 2023
1 parent cda040b commit 2df878b
Show file tree
Hide file tree
Showing 10 changed files with 500 additions and 146 deletions.
3 changes: 2 additions & 1 deletion crates/fervid/src/parser/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,8 @@ fn parse_directive<'i>(
let directives = get_directives!();
directives.v_model.push(VModelDirective {
argument,
value: *model_binding,
value: model_binding,
update_handler: None,
modifiers,
span: DUMMY_SP, // TODO
});
Expand Down
6 changes: 1 addition & 5 deletions crates/fervid_codegen/src/components/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,11 +251,7 @@ impl CodegenContext {
if let Some(ref directives) = component_node.starting_tag.directives {
// `v-model`s
for v_model in directives.v_model.iter() {
self.generate_v_model_for_component(
v_model,
&mut result_props,
component_node.template_scope,
);
self.generate_v_model_for_component(v_model, &mut result_props);
}

// Process `v-text`
Expand Down
112 changes: 28 additions & 84 deletions crates/fervid_codegen/src/directives/v_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ use fervid_core::{FervidAtom, StrOrExpr, VModelDirective};
use swc_core::{
common::Span,
ecma::ast::{
ArrowExpr, AssignExpr, AssignOp, BinExpr, BinaryOp, BindingIdent, BlockStmtOrExpr, Bool,
ComputedPropName, Expr, Ident, KeyValueProp, Lit, ObjectLit, ParenExpr, Pat, PatOrExpr,
Prop, PropName, PropOrSpread, Str,
BinExpr, BinaryOp, Bool, ComputedPropName, Expr, KeyValueProp, Lit, ObjectLit, Prop,
PropName, PropOrSpread, Str,
},
};

Expand All @@ -19,7 +18,6 @@ impl CodegenContext {
&self,
v_model: &VModelDirective,
out: &mut Vec<PropOrSpread>,
scope_to_use: u32,
) -> bool {
let span = v_model.span;
let mut buf = String::new();
Expand All @@ -39,7 +37,7 @@ impl CodegenContext {
// e.g. `v-model="smth"` -> `modelValue: _ctx.smth`
out.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key: str_or_expr_to_propname(bound_attribute.to_owned(), span),
value: Box::new(v_model.value.to_owned()),
value: v_model.value.to_owned(),
}))));

// 3. Generate event name, e.g. `onUpdate:modelValue` or `onUpdate:usersArgument`
Expand Down Expand Up @@ -74,10 +72,12 @@ impl CodegenContext {
// 4. Push the update code,
// e.g. `v-model="smth"` -> `"onUpdate:modelValue": $event => ((_ctx.smth) = $event)`
// TODO Cache like so `_cache[1] || (_cache[1] = `
out.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key: event_listener_propname,
value: self.generate_v_model_update_fn(&v_model.value, scope_to_use, span),
}))));
if let Some(ref update_handler) = v_model.update_handler {
out.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key: event_listener_propname,
value: update_handler.to_owned(),
}))));
}

// 5. Optionally generate modifiers
if v_model.modifiers.len() == 0 {
Expand Down Expand Up @@ -134,71 +134,6 @@ impl CodegenContext {

has_js_bindings
}

/// Transforms the binding of `v-model`.
/// Because the rules of transformation differ a lot depending on the `BindingType`,
/// transformed expression may also differ a lot.
fn transform_v_model_value(
&self,
value: &Expr,
scope_to_use: u32,
_span: Span,
) -> (Box<Expr>, bool) {
// Polyfill

// TODO Maybe transform the `v-model` to an object in the `fervid_transform`
// and just unwrap the object to a props object?
// Or better yet, type VModelDirective value to have `Expr` and `Bindings` variants (VModelValue)
// `Expr` variant can be used in `withDirectives` call,
// while `Bindings` variant is used in component code

// TODO Implement the correct transformation based on BindingTypes
// let has_js = transform_scoped(&mut expr, &self.scope_helper, scope_to_use);
(Box::new(value.to_owned()), true)
}

/// Generates the update code for the `v-model`.
/// Same as [`transform_v_model_value`], logic may differ a lot.
fn generate_v_model_update_fn(&self, value: &Expr, scope_to_use: u32, span: Span) -> Box<Expr> {
// TODO Actual implementation

// todo maybe re-use the previously generated expression from generate_v_model_for_component?
let (transformed_v_model, _was_transformed) =
self.transform_v_model_value(value, scope_to_use, span);

// $event => ((_ctx.modelValue) = $event)
Box::new(Expr::Arrow(ArrowExpr {
span,
params: vec![Pat::Ident(BindingIdent {
id: Ident {
span,
sym: FervidAtom::from("$event"),
optional: false,
},
type_ann: None,
})],
body: Box::new(BlockStmtOrExpr::Expr(Box::new(Expr::Paren(ParenExpr {
span,
expr: Box::new(Expr::Assign(AssignExpr {
span,
op: AssignOp::Assign,
left: PatOrExpr::Expr(Box::new(Expr::Paren(ParenExpr {
span,
expr: transformed_v_model,
}))),
right: Box::new(Expr::Ident(Ident {
span,
sym: FervidAtom::from("$event"),
optional: false,
})),
})),
})))),
is_async: false,
is_generator: false,
type_params: None,
return_type: None,
}))
}
}

fn generate_v_model_modifiers(modifiers: &[FervidAtom], span: Span) -> ObjectLit {
Expand Down Expand Up @@ -230,7 +165,8 @@ mod tests {
test_out(
vec![VModelDirective {
argument: None,
value: *js("foo"),
value: js("foo"),
update_handler: js("$event=>((foo)=$event)").into(),
modifiers: Vec::new(),
span: DUMMY_SP,
}],
Expand All @@ -244,7 +180,8 @@ mod tests {
test_out(
vec![VModelDirective {
argument: Some("simple".into()),
value: *js("foo"),
value: js("foo"),
update_handler: js("$event=>((foo)=$event)").into(),
modifiers: Vec::new(),
span: DUMMY_SP,
}],
Expand All @@ -255,7 +192,8 @@ mod tests {
test_out(
vec![VModelDirective {
argument: Some("modelValue".into()),
value: *js("bar"),
value: js("bar"),
update_handler: js("$event=>((bar)=$event)").into(),
modifiers: Vec::new(),
span: DUMMY_SP,
}],
Expand All @@ -266,7 +204,8 @@ mod tests {
test_out(
vec![VModelDirective {
argument: Some("model-value".into()),
value: *js("baz"),
value: js("baz"),
update_handler: js("$event=>((baz)=$event)").into(),
modifiers: Vec::new(),
span: DUMMY_SP,
}],
Expand All @@ -280,7 +219,8 @@ mod tests {
test_out(
vec![VModelDirective {
argument: None,
value: *js("foo"),
value: js("foo"),
update_handler: js("$event=>((foo)=$event)").into(),
modifiers: vec!["lazy".into(), "trim".into()],
span: DUMMY_SP,
}],
Expand All @@ -291,7 +231,8 @@ mod tests {
test_out(
vec![VModelDirective {
argument: None,
value: *js("foo"),
value: js("foo"),
update_handler: js("$event=>((foo)=$event)").into(),
modifiers: vec!["custom-modifier".into()],
span: DUMMY_SP,
}],
Expand All @@ -302,7 +243,8 @@ mod tests {
test_out(
vec![VModelDirective {
argument: Some("foo-bar".into()),
value: *js("bazQux"),
value: js("bazQux"),
update_handler: js("$event=>((bazQux)=$event)").into(),
modifiers: vec!["custom-modifier".into()],
span: DUMMY_SP,
}],
Expand All @@ -316,7 +258,8 @@ mod tests {
test_out(
vec![VModelDirective {
argument: Some(StrOrExpr::Expr(js("foo"))),
value: *js("bar"),
value: js("bar"),
update_handler: js("$event=>((bar)=$event)").into(),
modifiers: Vec::new(),
span: DUMMY_SP,
}],
Expand All @@ -327,7 +270,8 @@ mod tests {
test_out(
vec![VModelDirective {
argument: Some(StrOrExpr::Expr(js("foo"))),
value: *js("bar"),
value: js("bar"),
update_handler: js("$event=>((bar)=$event)").into(),
modifiers: vec!["baz".into()],
span: DUMMY_SP,
}],
Expand All @@ -342,7 +286,7 @@ mod tests {
props: vec![],
};
for v_model in input.iter() {
ctx.generate_v_model_for_component(v_model, &mut out.props, 0);
ctx.generate_v_model_for_component(v_model, &mut out.props);
}
assert_eq!(crate::test_utils::to_str(out), expected)
}
Expand Down
2 changes: 1 addition & 1 deletion crates/fervid_core/src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,6 @@ pub struct SetupBinding(pub FervidAtom, pub BindingTypes);

#[derive(Debug)]
pub struct TemplateScope {
pub variables: SmallVec<[FervidAtom; 1]>,
pub variables: SmallVec<[FervidAtom; 2]>,
pub parent: u32,
}
4 changes: 3 additions & 1 deletion crates/fervid_core/src/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,9 @@ pub struct VModelDirective {
/// What to apply v-model to, e.g. `first-name` in `v-model:first-name="first"`
pub argument: Option<StrOrExpr>,
/// The binding of a `v-model`, e.g. `userInput` in `v-model="userInput"`
pub value: Expr,
pub value: Box<Expr>,
/// The handler to generate for the directive, e.g. `$event => (msg.value = $event)`
pub update_handler: Option<Box<Expr>>,
/// `lazy` and `trim` in `v-model.lazy.trim`
pub modifiers: Vec<FervidAtom>,
pub span: Span
Expand Down
2 changes: 2 additions & 0 deletions crates/fervid_core/src/vue_imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ flags! {
CreateTextVNode,
CreateVNode,
Fragment,
IsRef,
KeepAlive,
MergeModels,
NormalizeClass,
Expand Down Expand Up @@ -53,6 +54,7 @@ impl VueImports {
VueImports::CreateTextVNode => "_createTextVNode",
VueImports::CreateVNode => "_createVNode",
VueImports::Fragment => "_Fragment",
VueImports::IsRef => "_isRef",
VueImports::KeepAlive => "_KeepAlive",
VueImports::MergeModels => "_mergeModels",
VueImports::NormalizeClass => "_normalizeClass",
Expand Down
3 changes: 2 additions & 1 deletion crates/fervid_parser/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,8 @@ pub fn try_parse_directive(
let directives = get_directives!();
directives.v_model.push(VModelDirective {
argument,
value: *model_binding,
value: model_binding,
update_handler: None,
modifiers,
span,
});
Expand Down
2 changes: 1 addition & 1 deletion crates/fervid_transform/src/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use self::{
setup::{transform_and_record_script_setup, merge_sfc_helper},
};

mod common;
pub mod common;
mod options_api;
mod setup;
pub mod utils;
Expand Down
Loading

0 comments on commit 2df878b

Please sign in to comment.