fervid_core/structs.rs
1use swc_core::{ecma::{ast::{Expr, Pat}, atoms::{JsWord, Atom}}, common::Span};
2
3pub type FervidAtom = Atom;
4
5// TODO Add some known atoms here with lazy evaluation?
6#[macro_export]
7macro_rules! fervid_atom {
8 ($lit: literal) => {
9 fervid_core::FervidAtom::from($lit)
10 };
11}
12
13/// A Node represents a part of the Abstract Syntax Tree (AST).
14#[derive(Debug, Clone)]
15pub enum Node {
16 /// `Element` means that the node is a basic HTML tag node.
17 ///
18 /// `Element` has a starting `<tag>` with attributes,
19 /// zero or more children and a closing `</tag>` unless this node is self-closed `<tag />`.
20 /// The parser does not add any meaning to the discovered tag name,
21 /// as this logic is application-specific.
22 Element(ElementNode),
23
24 /// These nodes are the basic HTML text leaf nodes
25 /// which can only contain static text.
26 Text(FervidAtom, Span),
27
28 /// Interpolation is a special syntax for Vue templates.
29 ///
30 /// It looks like this: `{{ some + js - expression }}`,
31 /// where the content inside `{{` and `}}` delimiters is arbitrary.
32 Interpolation(Interpolation),
33
34 /// `Comment` is the vanilla HTML comment, which looks like this: `<-- this is comment -->`
35 Comment(FervidAtom, Span),
36
37 /// `ConditionalSeq` is a representation of `v-if`/`v-else-if`/`v-else` node sequence.
38 /// Its children are the other `Node`s, this node is just a wrapper.
39 ConditionalSeq(ConditionalNodeSequence),
40
41 // /// `ForFragment` is a representation of a `v-for` node.
42 // /// This type is for ergonomics,
43 // /// i.e. to separate patch flags and `key` of the repeater from the repeatable.
44 // ForFragment(ForFragment<'a>)
45}
46
47/// Element node is a classic HTML node with some added functionality:
48/// 1. Its starting tag can have Vue directives as attributes;
49/// 2. It may have [`Node::Interpolation`] as a child;
50/// 3. It has a `template_scope` assigned, which is responsible
51/// for the correct compilation of dynamic bindings and expressions.
52#[derive(Debug, Clone)]
53pub struct ElementNode {
54 /// Marks the node as either an Element (HTML tag), Builtin (Vue) or Component
55 pub kind: ElementKind,
56 pub starting_tag: StartingTag,
57 pub children: Vec<Node>,
58 pub template_scope: u32,
59 pub patch_hints: PatchHints,
60 pub span: Span
61}
62
63#[derive(Debug, Clone, Copy, Default)]
64pub enum ElementKind {
65 Builtin(BuiltinType),
66 #[default]
67 Element,
68 Component,
69}
70
71#[derive(Debug, Clone, Copy)]
72pub enum BuiltinType {
73 Component,
74 KeepAlive,
75 Slot,
76 Suspense,
77 Teleport,
78 Transition,
79 TransitionGroup,
80}
81
82/// This is a synthetic node type only available after AST optimizations.
83/// Its purpose is to make conditional code generation trivial.
84///
85/// The `ConditionalNodeSequence` consists of:
86/// - exactly one `v-if` `ElementNode`;
87/// - 0 or more `v-else-if` `ElementNode`s;
88/// - 0 or 1 `v-else` `ElementNode`.
89#[derive(Debug, Clone)]
90pub struct ConditionalNodeSequence {
91 pub if_node: Box<Conditional>,
92 pub else_if_nodes: Vec<Conditional>,
93 pub else_node: Option<Box<ElementNode>>,
94}
95
96/// A wrapper around an `ElementNode` with a condition attached to it.
97/// This is used in `v-if` and `v-else-if` nodes.
98#[derive(Debug, Clone)]
99pub struct Conditional {
100 pub condition: Expr,
101 pub node: ElementNode,
102}
103
104/// A special Vue `{{ expression }}`,
105/// which would be rendered as a stringified value of executing said expression.
106#[derive(Debug, Clone)]
107pub struct Interpolation {
108 pub value: Box<Expr>,
109 pub template_scope: u32,
110 pub patch_flag: bool,
111 pub span: Span
112}
113
114/// Starting tag represents [`ElementNode`]'s tag name and attributes
115#[derive(Debug, Clone)]
116pub struct StartingTag {
117 pub tag_name: FervidAtom,
118 pub attributes: Vec<AttributeOrBinding>,
119 pub directives: Option<Box<VueDirectives>>,
120}
121
122/// Denotes the basic attributes or bindings of a DOM element
123/// As of directives, this only covers `v-bind` and `v-on`,
124/// because they bind something to DOM.
125/// `v-model` is not covered here because its code generation is not as trivial.
126#[derive(Debug, Clone)]
127pub enum AttributeOrBinding {
128 /// `RegularAttribute` is a plain HTML attribute without any associated logic
129 RegularAttribute { name: FervidAtom, value: FervidAtom, span: Span },
130 /// `v-bind` directive
131 VBind(VBindDirective),
132 /// `v-on` directive
133 VOn(VOnDirective),
134}
135
136/// Describes a type which can be either a static &str or a js Expr.
137/// This is mostly usable for dynamic binding scenarios.
138/// ## Example
139/// - `:foo="bar"` yields `StrOrExpr::Str("foo")`;
140/// - `:[baz]="qux"` yields `StrOrExpr::Expr(Box::new(Expr::Lit(Lit::Str(Str { value: "baz".into(), .. }))))`
141#[derive(Debug, Clone)]
142pub enum StrOrExpr {
143 Str(FervidAtom),
144 Expr(Box<Expr>),
145}
146
147impl<'s> From<&'s str> for StrOrExpr {
148 fn from(value: &'s str) -> StrOrExpr {
149 StrOrExpr::Str(FervidAtom::from(value))
150 }
151}
152
153/// A helper structure attached to `ElementNode`s to handle Patch Flags
154/// and contain the list of dynamic props.
155#[derive(Debug, Default, Clone)]
156pub struct PatchHints {
157 /// Patch flags
158 pub flags: PatchFlagsSet,
159 /// Dynamic props
160 pub props: Vec<JsWord>,
161 /// Whether the node codegen needs to be surrounded by `(openBlock(),`
162 pub should_use_block: bool
163}
164
165flagset::flags! {
166 /// From https://github.com/vuejs/core/blob/b8fc18c0b23be9a77b05dc41ed452a87a0becf82/packages/shared/src/patchFlags.ts
167 #[derive(Default)]
168 pub enum PatchFlags: i32 {
169 /**
170 * Indicates an element with dynamic textContent (children fast path)
171 */
172 Text = 1,
173
174 /**
175 * Indicates an element with dynamic class binding.
176 */
177 Class = 1 << 1,
178
179 /**
180 * Indicates an element with dynamic style
181 * The compiler pre-compiles static string styles into static objects
182 * + detects and hoists inline static objects
183 * e.g. `style="color: red"` and `:style="{ color: 'red' }"` both get hoisted
184 * as:
185 * ```js
186 * const style = { color: 'red' }
187 * render() { return e('div', { style }) }
188 * ```
189 */
190 Style = 1 << 2,
191
192 /**
193 * Indicates an element that has non-class/style dynamic props.
194 * Can also be on a component that has any dynamic props (includes
195 * class/style). when this flag is present, the vnode also has a dynamicProps
196 * array that contains the keys of the props that may change so the runtime
197 * can diff them faster (without having to worry about removed props)
198 */
199 Props = 1 << 3,
200
201 /**
202 * Indicates an element with props with dynamic keys. When keys change, a full
203 * diff is always needed to remove the old key. This flag is mutually
204 * exclusive with CLASS, STYLE and PROPS.
205 */
206 FullProps = 1 << 4,
207
208 /**
209 * Indicates an element that requires props hydration
210 * (but not necessarily patching)
211 * e.g. event listeners & v-bind with prop modifier
212 */
213 NeedHydration = 1 << 5,
214
215 /**
216 * Indicates a fragment whose children order doesn't change.
217 */
218 StableFragment = 1 << 6,
219
220 /**
221 * Indicates a fragment with keyed or partially keyed children
222 */
223 KeyedFragment = 1 << 7,
224
225 /**
226 * Indicates a fragment with unkeyed children.
227 */
228 UnkeyedFragment = 1 << 8,
229
230 /**
231 * Indicates an element that only needs non-props patching, e.g. ref or
232 * directives (onVnodeXXX hooks). since every patched vnode checks for refs
233 * and onVnodeXXX hooks, it simply marks the vnode so that a parent block
234 * will track it.
235 */
236 #[default]
237 NeedPatch = 1 << 9,
238
239 /**
240 * Indicates a component with dynamic slots (e.g. slot that references a v-for
241 * iterated value, or dynamic slot names).
242 * Components with this flag are always force updated.
243 */
244 DynamicSlots = 1 << 10,
245
246 /**
247 * Indicates a fragment that was created only because the user has placed
248 * comments at the root level of a template. This is a dev-only flag since
249 * comments are stripped in production.
250 */
251 DevRootFragment = 1 << 11,
252
253 /**
254 * SPECIAL FLAGS -------------------------------------------------------------
255 * Special flags are negative integers. They are never matched against using
256 * bitwise operators (bitwise matching should only happen in branches where
257 * patchFlag > 0), and are mutually exclusive. When checking for a special
258 * flag, simply check patchFlag === FLAG.
259 */
260
261 /**
262 * Indicates a hoisted static vnode. This is a hint for hydration to skip
263 * the entire sub tree since static content never needs to be updated.
264 */
265 Hoisted = -1,
266 /**
267 * A special flag that indicates that the diffing algorithm should bail out
268 * of optimized mode. For example, on block fragments created by renderSlot()
269 * when encountering non-compiler generated slots (i.e. manually written
270 * render functions, which should always be fully diffed)
271 * OR manually cloneVNodes
272 */
273 Bail = -2,
274 }
275}
276
277pub type PatchFlagsSet = flagset::FlagSet<PatchFlags>;
278
279/// A structure which stores all the Vue directives of an `ElementNode`.
280#[derive(Clone, Debug, Default)]
281pub struct VueDirectives {
282 pub custom: Vec<VCustomDirective>,
283 pub v_cloak: Option<()>,
284 pub v_else: Option<()>,
285 pub v_else_if: Option<Box<Expr>>,
286 pub v_for: Option<VForDirective>,
287 pub v_html: Option<Box<Expr>>,
288 pub v_if: Option<Box<Expr>>,
289 pub v_memo: Option<Box<Expr>>,
290 pub v_model: Vec<VModelDirective>,
291 pub v_once: Option<()>,
292 pub v_pre: Option<()>,
293 pub v_show: Option<Box<Expr>>,
294 pub v_slot: Option<VSlotDirective>,
295 pub v_text: Option<Box<Expr>>,
296}
297
298/// `v-for`
299#[derive(Clone, Debug)]
300pub struct VForDirective {
301 /// `bar` in `v-for="foo in bar"`
302 pub iterable: Box<Expr>,
303 /// `foo` in `v-for="foo in bar"`
304 pub itervar: Box<Expr>,
305 pub patch_flags: PatchFlagsSet,
306 pub span: Span
307}
308
309/// `v-on` and its shorthand `@`
310#[derive(Clone, Debug)]
311pub struct VOnDirective {
312 /// What event to listen to. If None, it is equivalent to `v-on="..."`.
313 pub event: Option<StrOrExpr>,
314 /// What is the handler to use. If None, `modifiers` must not be empty.
315 pub handler: Option<Box<Expr>>,
316 /// A list of modifiers after the dot, e.g. `stop` and `prevent` in `@click.stop.prevent="handleClick"`
317 pub modifiers: Vec<FervidAtom>,
318 /// Byte location in source
319 pub span: Span
320}
321
322/// `v-bind` and its shorthand `:`
323#[derive(Clone, Debug)]
324pub struct VBindDirective {
325 /// Attribute name to bind. If None, it is equivalent to `v-bind="..."`.
326 pub argument: Option<StrOrExpr>,
327 /// Attribute value, e.g. `smth` in `:attr="smth"`
328 pub value: Box<Expr>,
329 /// .camel modifier
330 pub is_camel: bool,
331 /// .prop modifier
332 pub is_prop: bool,
333 /// .attr modifier
334 pub is_attr: bool,
335 /// Byte location in source
336 pub span: Span
337
338 // TODO Add constant type attribute to allow hoisting
339}
340
341/// `v-model`
342#[derive(Clone, Debug)]
343pub struct VModelDirective {
344 /// What to apply v-model to, e.g. `first-name` in `v-model:first-name="first"`
345 pub argument: Option<StrOrExpr>,
346 /// The binding of a `v-model`, e.g. `userInput` in `v-model="userInput"`
347 pub value: Box<Expr>,
348 /// The handler to generate for the directive, e.g. `$event => (msg.value = $event)`
349 pub update_handler: Option<Box<Expr>>,
350 /// `lazy` and `trim` in `v-model.lazy.trim`
351 pub modifiers: Vec<FervidAtom>,
352 pub span: Span
353}
354
355/// `v-slot`
356#[derive(Clone, Debug)]
357pub struct VSlotDirective {
358 pub slot_name: Option<StrOrExpr>,
359 /// What bindings are provided to slot children, e.g. `value` in `v-slot="{ value }"`
360 pub value: Option<Box<Pat>>,
361}
362
363/// A custom directive defined by a user.
364#[derive(Debug, Default, Clone)]
365pub struct VCustomDirective {
366 /// `foo` in `v-foo`
367 pub name: FervidAtom,
368 /// `bar` in `v-foo:bar`
369 pub argument: Option<StrOrExpr>,
370 /// `baz` and `qux` in `v-foo:bar.baz.qux`
371 pub modifiers: Vec<FervidAtom>,
372 /// `loremIpsum` in `v-foo="loremIpsum"`
373 pub value: Option<Box<Expr>>,
374}
375
376/// The type of a binding (or identifier) which is used to show where this binding came from,
377/// e.g. `Data` is for Options API `data()`, `SetupRef` if for `ref`s and `computed`s in Composition API.
378///
379/// <https://github.com/vuejs/core/blob/020851e57d9a9f727c6ea07e9c1575430af02b73/packages/compiler-core/src/options.ts#L76>
380#[derive(Clone, Copy, Debug, PartialEq)]
381pub enum BindingTypes {
382 /// returned from data()
383 Data,
384 /// declared as a prop
385 Props,
386 /// a local alias of a `<script setup>` destructured prop.
387 /// the original is stored in __propsAliases of the bindingMetadata object.
388 PropsAliased,
389 /// a let binding (may or may not be a ref)
390 SetupLet,
391 /// a const binding that can never be a ref.
392 /// these bindings don't need `unref()` calls when processed in inlined
393 /// template expressions.
394 SetupConst,
395 /// a const binding that does not need `unref()`, but may be mutated.
396 SetupReactiveConst,
397 /// a const binding that may be a ref
398 SetupMaybeRef,
399 /// bindings that are guaranteed to be refs
400 SetupRef,
401 /// declared by other options, e.g. computed, inject
402 Options,
403 /// a literal constant, e.g. 'foo', 1, true
404 LiteralConst,
405
406 // Introduced by fervid:
407
408 /// a `.vue` import or `defineComponent` call
409 Component,
410 /// an import which is not a `.vue` or `from 'vue'`
411 Imported,
412 /// a variable from the template
413 TemplateLocal,
414 /// a variable in the global Javascript context, e.g. `Array` or `undefined`
415 JsGlobal,
416 /// a non-resolved variable, presumably from the global Vue context
417 Unresolved,
418}
419
420/// Mode with which the template is attached to the exported SFC object.
421#[derive(Debug, Default)]
422pub enum TemplateGenerationMode {
423 /// Applies the transformation as if the template is rendered inline
424 /// and variables are directly accessible in the function scope.
425 /// For example, if there is `const foo = ref(0)`, then `foo` will be transformed to `foo.value`.
426 /// Non-ref bindings and literal constants will remain untouched.
427 Inline,
428
429 /// Applies the transformation as if the template is inside a
430 /// `function render(_ctx, _cache, $props, $setup, $data, $options)`.\
431 /// Variable access will be translated to object property access,
432 /// e.g. `const foo = ref(0)` and `foo.bar` -> `$setup.foo.bar`.
433 #[default]
434 RenderFn
435}