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}