fervid_codegen/directives/
mod.rs1use fervid_core::{CustomDirectiveBinding, FervidAtom, StrOrExpr, VueDirectives, VueImports};
2use swc_core::{
3 common::{Span, DUMMY_SP},
4 ecma::ast::{
5 ArrayLit, BindingIdent, Bool, CallExpr, Callee, Expr, ExprOrSpread, Ident, KeyValueProp,
6 Lit, Number, ObjectLit, Pat, Prop, PropOrSpread, Str, UnaryExpr, UnaryOp, VarDeclarator,
7 },
8};
9
10use crate::{utils::str_to_propname, CodegenContext};
11
12mod v_for;
13mod v_html;
14mod v_memo;
15mod v_model;
16mod v_once;
17mod v_text;
18
19impl CodegenContext {
20 pub fn generate_directives_to_array(
21 &mut self,
22 directives: &VueDirectives,
23 out: &mut Vec<Option<ExprOrSpread>>,
24 ) {
25 macro_rules! has {
27 ($key: ident) => {
28 directives.$key.is_some() as usize
29 };
30 }
31 let total_work = directives.custom.len() + has!(v_show) + has!(v_memo);
32 if total_work == 0 {
33 return;
34 }
35
36 out.reserve(total_work);
38
39 if let Some(ref v_show) = directives.v_show {
41 let span = DUMMY_SP; let v_show_identifier = Expr::Ident(Ident {
43 span,
44 sym: self.get_and_add_import_ident(VueImports::VShow),
45 optional: false,
46 });
47
48 out.push(Some(ExprOrSpread {
49 spread: None,
50 expr: Box::new(self.generate_directive_from_parts(
51 v_show_identifier,
52 Some(v_show),
53 None,
54 &[],
55 span,
56 )),
57 }))
58 }
59
60 for custom_directive in directives.custom.iter() {
62 let span = DUMMY_SP; let directive_ident = self.get_custom_directive_ident(&custom_directive.name, span);
64
65 out.push(Some(ExprOrSpread {
66 spread: None,
67 expr: Box::new(self.generate_directive_from_parts(
68 directive_ident,
69 custom_directive.value.as_deref(),
70 custom_directive.argument.as_ref(),
71 &custom_directive.modifiers,
72 span,
73 )),
74 }));
75 }
76 }
77
78 pub fn maybe_generate_with_directives(
80 &mut self,
81 expr: Expr,
82 directives_arr: Vec<Option<ExprOrSpread>>,
83 ) -> Expr {
84 if directives_arr.is_empty() {
85 return expr;
86 }
87
88 Expr::Call(CallExpr {
89 span: DUMMY_SP, callee: Callee::Expr(Box::new(Expr::Ident(Ident {
91 span: DUMMY_SP,
92 sym: self.get_and_add_import_ident(VueImports::WithDirectives),
93 optional: false,
94 }))),
95 args: vec![
96 ExprOrSpread {
97 spread: None,
98 expr: Box::new(expr),
99 },
100 ExprOrSpread {
101 spread: None,
102 expr: Box::new(Expr::Array(ArrayLit {
103 span: DUMMY_SP,
104 elems: directives_arr,
105 })),
106 },
107 ],
108 type_args: None,
109 })
110 }
111
112 pub fn generate_directive_from_parts(
122 &mut self,
123 identifier: Expr,
124 value: Option<&Expr>,
125 argument: Option<&StrOrExpr>,
126 modifiers: &[FervidAtom],
127 span: Span,
128 ) -> Expr {
129 let has_argument = argument.is_some();
130 let has_modifiers = modifiers.len() > 0;
131
132 let directive_arr_len_hint = if has_modifiers {
134 4
135 } else if has_argument {
136 3
137 } else if value.is_some() {
138 2
139 } else {
140 1
141 };
142 let mut directive_arr = ArrayLit {
143 span,
144 elems: Vec::with_capacity(directive_arr_len_hint),
145 };
146
147 directive_arr.elems.push(Some(ExprOrSpread {
150 spread: None,
151 expr: Box::new(identifier),
152 }));
153
154 macro_rules! early_exit {
156 ($desired: expr) => {
157 if directive_arr_len_hint == $desired {
158 return Expr::Array(directive_arr);
159 }
160 };
161 }
162
163 early_exit!(1);
164
165 directive_arr.elems.push(Some(ExprOrSpread {
167 spread: None,
168 expr: if let Some(directive_value) = value {
169 Box::new(directive_value.to_owned())
170 } else {
171 Box::new(void0())
172 },
173 }));
174
175 early_exit!(2);
176
177 let directive_arg_expr = match argument {
179 Some(StrOrExpr::Str(s)) => Box::new(Expr::Lit(Lit::Str(Str {
180 span: DUMMY_SP,
181 value: s.to_owned(),
182 raw: None,
183 }))),
184 Some(StrOrExpr::Expr(expr)) => expr.to_owned(),
185 None => Box::new(void0()),
186 };
187 directive_arr.elems.push(Some(ExprOrSpread {
188 spread: None,
189 expr: directive_arg_expr,
190 }));
191
192 early_exit!(3);
193
194 let mut modifiers_obj = ObjectLit {
196 span: DUMMY_SP,
197 props: Vec::with_capacity(modifiers.len()),
198 };
199 for modifier in modifiers.iter() {
200 modifiers_obj
201 .props
202 .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
203 key: str_to_propname(&modifier, DUMMY_SP),
204 value: Box::new(Expr::Lit(Lit::Bool(Bool {
205 span: DUMMY_SP,
206 value: true,
207 }))),
208 }))))
209 }
210 directive_arr.elems.push(Some(ExprOrSpread {
211 spread: None,
212 expr: Box::new(Expr::Object(modifiers_obj)),
213 }));
214
215 Expr::Array(directive_arr)
216 }
217
218 fn get_custom_directive_ident(&mut self, directive_name: &FervidAtom, span: Span) -> Expr {
219 let existing_directive_binding = self.bindings_helper.custom_directives.get(directive_name);
221 match existing_directive_binding {
222 Some(CustomDirectiveBinding::Resolved(directive_binding)) => {
223 return (**directive_binding).to_owned()
224 }
225 Some(CustomDirectiveBinding::RuntimeResolved(directive_ident)) => {
226 return Expr::Ident((**directive_ident).to_owned())
227 }
228 _ => {}
229 }
230
231 let mut directive_ident_raw = directive_name.replace('-', "_");
233 directive_ident_raw.insert_str(0, "_directive_");
234 let directive_ident_atom = FervidAtom::from(directive_ident_raw);
235
236 let resolve_identifier = Ident {
240 span,
241 sym: directive_ident_atom,
242 optional: false,
243 };
244
245 self.bindings_helper.custom_directives.insert(
247 directive_name.to_owned(),
248 CustomDirectiveBinding::RuntimeResolved(Box::new(resolve_identifier.to_owned())),
249 );
250
251 Expr::Ident(resolve_identifier)
252 }
253
254 pub fn generate_directive_resolves(&mut self) -> Vec<VarDeclarator> {
255 let mut result = Vec::new();
256
257 if self.bindings_helper.custom_directives.len() == 0 {
258 return result;
259 }
260
261 let resolve_directive_ident = self.get_and_add_import_ident(VueImports::ResolveDirective);
262
263 let mut sorted_directives: Vec<(&FervidAtom, &Ident)> = self
266 .bindings_helper
267 .custom_directives
268 .iter()
269 .filter_map(
270 |(directive_name, directive_resolution)| match directive_resolution {
271 CustomDirectiveBinding::RuntimeResolved(ident) => {
272 Some((directive_name, ident.as_ref()))
273 }
274 _ => None,
275 },
276 )
277 .collect();
278
279 sorted_directives.sort_by(|a, b| a.0.cmp(b.0));
280
281 for (directive_name, directive_identifier) in sorted_directives.iter() {
283 result.push(VarDeclarator {
285 span: DUMMY_SP,
286 name: Pat::Ident(BindingIdent {
287 id: (*directive_identifier).to_owned(),
288 type_ann: None,
289 }),
290 init: Some(Box::new(Expr::Call(CallExpr {
291 span: DUMMY_SP,
292 callee: Callee::Expr(Box::new(Expr::Ident(Ident {
293 span: DUMMY_SP,
294 sym: resolve_directive_ident.to_owned(),
295 optional: false,
296 }))),
297 args: vec![ExprOrSpread {
298 spread: None,
299 expr: Box::new(Expr::Lit(Lit::Str(Str {
300 span: DUMMY_SP,
301 value: (**directive_name).to_owned(),
302 raw: None,
303 }))),
304 }],
305 type_args: None,
306 }))),
307 definite: false,
308 });
309 }
310
311 result
312 }
313}
314
315fn void0() -> Expr {
317 Expr::Unary(UnaryExpr {
318 span: DUMMY_SP,
319 op: UnaryOp::Void,
320 arg: Box::new(Expr::Lit(Lit::Num(Number {
321 raw: None,
322 span: DUMMY_SP,
323 value: 0.0,
324 }))),
325 })
326}