Skip to content

Commit

Permalink
feat: add support for component and directive resolutions
Browse files Browse the repository at this point in the history
  • Loading branch information
phoenix-ru committed Nov 2, 2023
1 parent 7283833 commit e019232
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 135 deletions.
16 changes: 2 additions & 14 deletions crates/fervid/benches/fixtures/input.vue
Original file line number Diff line number Diff line change
Expand Up @@ -77,25 +77,13 @@
</abc-def>
</template>

<script>
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup() {
return {
inputModel: ref(''),
modelValue: ref(''),
list: [1, 2, 3]
}
},
})
</script>

<script setup>
import { ref } from 'vue'
const foo = '123'
const bar = ref(456)
const inputModel = ref('')
const list = [1, 2, 3]
defineProps({ baz: String })
defineEmits(['emit-change'])
Expand Down
8 changes: 2 additions & 6 deletions crates/fervid/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@
//! let transform_result = fervid_transform::transform_sfc(sfc, is_prod);
//!
//! // Create the context and generate the template block
//! let mut ctx = fervid_codegen::CodegenContext::default();
//! ctx.used_imports = transform_result.used_vue_imports;
//! let mut ctx = fervid_codegen::CodegenContext::with_bindings_helper(transform_result.bindings_helper);
//!
//! let template_expr: Option<Expr> = transform_result.template_block.map(|template_block| {
//! ctx.generate_sfc_template(&template_block)
Expand All @@ -34,7 +33,6 @@
//! transform_result.module,
//! transform_result.exported_obj,
//! transform_result.setup_fn,
//! transform_result.template_generation_mode
//! );
//!
//! // (Optional) Stringify the code
Expand Down Expand Up @@ -70,8 +68,7 @@ pub fn compile_sync_naive(source: &str, is_prod: bool) -> Result<String, String>
// Also `used_imports`? `vue_imports`? User imports?
let transform_result = transform_sfc(sfc, is_prod);

let mut ctx = CodegenContext::default();
ctx.used_imports = transform_result.used_vue_imports;
let mut ctx = CodegenContext::with_bindings_helper(transform_result.bindings_helper);

let template_expr: Option<Expr> = transform_result.template_block.map(|template_block| {
ctx.generate_sfc_template(&template_block)
Expand All @@ -82,7 +79,6 @@ pub fn compile_sync_naive(source: &str, is_prod: bool) -> Result<String, String>
transform_result.module,
transform_result.exported_obj,
transform_result.setup_fn,
transform_result.template_generation_mode
);

let compiled_code = CodegenContext::stringify(&source, &sfc_module, false);
Expand Down
81 changes: 50 additions & 31 deletions crates/fervid_codegen/src/components/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use fervid_core::{
ElementNode, FervidAtom, Node, PatchHints, StartingTag, StrOrExpr, VSlotDirective,
VueDirectives, VueImports,
ComponentBinding, ElementNode, FervidAtom, Node, PatchHints, StartingTag, StrOrExpr,
VSlotDirective, VueDirectives, VueImports,
};
use swc_core::{
common::{Span, DUMMY_SP},
Expand All @@ -26,11 +26,8 @@ impl CodegenContext {
) -> Expr {
let span = component_node.span;

let component_identifier = Expr::Ident(Ident {
span,
sym: self.get_component_identifier(&component_node.starting_tag.tag_name),
optional: false,
});
let component_identifier =
self.get_component_identifier(&component_node.starting_tag.tag_name, span);

let attributes_obj = self.generate_component_attributes(component_node);
// TODO Apply all the directives and modifications
Expand Down Expand Up @@ -191,33 +188,37 @@ impl CodegenContext {
pub fn generate_component_resolves(&mut self) -> Vec<VarDeclarator> {
let mut result = Vec::new();

if self.components.len() == 0 {
if self.bindings_helper.components.is_empty() {
return result;
}

let resolve_component_ident = self.get_and_add_import_ident(VueImports::ResolveComponent);

// We need sorted entries for stable output.
// Entries are sorted by Js identifier (second element of tuple in hashmap entry)
let mut sorted_components: Vec<(&FervidAtom, &FervidAtom)> = self
// Entries are sorted by a component name (first element of tuple in hashmap entry)
let mut sorted_components: Vec<(&FervidAtom, &Ident)> = self
.bindings_helper
.components
.iter()
.map(|(component_name, component_ident)| (component_name, component_ident))
.filter_map(
|(component_name, component_resolution)| match component_resolution {
ComponentBinding::RuntimeResolved(ident) => {
Some((component_name, ident.as_ref()))
}
_ => None,
},
)
.collect();

sorted_components.sort_by(|a, b| a.1.cmp(b.1));
sorted_components.sort_by(|a, b| a.0.cmp(b.0));

// Key is a component as used in template, value is the assigned Js identifier
for (component_name, identifier) in sorted_components.iter() {
for (component_name, component_identifier) in sorted_components.iter() {
// _component_ident_name = resolveComponent("component-name")
result.push(VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(BindingIdent {
id: Ident {
span: DUMMY_SP,
sym: (*identifier).to_owned(),
optional: false,
},
id: (*component_identifier).to_owned(),
type_ann: None,
}),
init: Some(Box::new(Expr::Call(CallExpr {
Expand Down Expand Up @@ -360,16 +361,17 @@ impl CodegenContext {
}

// Check if this is a `<template>` or not
let Node::Element(
ElementNode {
starting_tag: StartingTag {
let Node::Element(ElementNode {
starting_tag:
StartingTag {
tag_name: js_word!("template"),
directives: Some(directives),
..
},
children,
..
}) = node else {
children,
..
}) = node
else {
not_in_a_template_v_slot!();
};

Expand Down Expand Up @@ -509,11 +511,17 @@ impl CodegenContext {
}

/// Creates the SWC identifier from a tag name. Will fetch from cache if present
fn get_component_identifier(&mut self, tag_name: &FervidAtom) -> JsWord {
fn get_component_identifier(&mut self, tag_name: &FervidAtom, span: Span) -> Expr {
// Cached
let existing_component_name = self.components.get(tag_name);
if let Some(component_name) = existing_component_name {
return component_name.to_owned();
let existing_component_binding = self.bindings_helper.components.get(tag_name);
match existing_component_binding {
Some(ComponentBinding::Resolved(component_binding)) => {
return (**component_binding).to_owned()
}
Some(ComponentBinding::RuntimeResolved(component_identifier)) => {
return Expr::Ident((**component_identifier).to_owned())
}
_ => {}
}

// _component_ prefix plus tag name
Expand All @@ -523,10 +531,21 @@ impl CodegenContext {
// To create an identifier, we need to convert it to an SWC JsWord
let component_name = JsWord::from(component_name);

self.components
.insert(tag_name.to_owned(), component_name.to_owned());
// Directive will be resolved during runtime, this provides a variable name,
// e.g. `const _component_custom = resolveComponent('custom')`
// and later used in `createVNode(_component_custom)`
let resolve_identifier = Ident {
span,
sym: component_name,
optional: false,
};

self.bindings_helper.components.insert(
tag_name.to_owned(),
ComponentBinding::RuntimeResolved(Box::new(resolve_identifier.to_owned())),
);

return component_name;
Expr::Ident(resolve_identifier)
}

// Generates `withDirectives(expr, [directives])`
Expand Down
15 changes: 8 additions & 7 deletions crates/fervid_codegen/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use fervid_core::{VueImports, FervidAtom};
use flagset::FlagSet;
use fxhash::FxHashMap as HashMap;
use swc_core::ecma::atoms::JsWord;
use fervid_core::BindingsHelper;

#[derive(Debug, Default)]
pub struct CodegenContext {
pub components: HashMap<FervidAtom, JsWord>,
pub directives: HashMap<FervidAtom, JsWord>,
pub used_imports: FlagSet<VueImports>,
pub bindings_helper: BindingsHelper,
}

impl CodegenContext {
pub fn with_bindings_helper(bindings_helper: BindingsHelper) -> CodegenContext {
CodegenContext { bindings_helper }
}
}
11 changes: 6 additions & 5 deletions crates/fervid_codegen/src/control_flow/sfc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ impl CodegenContext {
// TODO Generation mode? Is it relevant?
// TODO Generating module? Or instead taking a module? Or generating an expression and merging?
pub fn generate_sfc_template(&mut self, sfc_template: &SfcTemplateBlock) -> Expr {
assert!(!sfc_template.roots.is_empty());
assert_eq!(sfc_template.roots.len(), 1);

// TODO Multi-root? Is it actually merged before into a Fragment?
let first_child = &sfc_template.roots[0];
Expand All @@ -30,16 +30,17 @@ impl CodegenContext {
template_expr: Option<Expr>,
mut script: Module,
mut sfc_export_obj: ObjectLit,
mut setup_fn: Option<Box<Function>>,
template_generation_mode: TemplateGenerationMode,
mut synthetic_setup_fn: Option<Box<Function>>,
) -> Module {
let template_generation_mode = &self.bindings_helper.template_generation_mode;

if let Some(template_expr) = template_expr {
match template_generation_mode {
// Generates the render expression and appends it to the end of the `setup` function.
TemplateGenerationMode::Inline => {
let render_arrow = self.generate_render_arrow(template_expr);

let setup_function = setup_fn.get_or_insert_with(|| {
let setup_function = synthetic_setup_fn.get_or_insert_with(|| {
Box::new(Function {
params: vec![],
decorators: vec![],
Expand Down Expand Up @@ -83,7 +84,7 @@ impl CodegenContext {
}

// Add the `setup` function to the exported object
if let Some(setup_fn) = setup_fn {
if let Some(setup_fn) = synthetic_setup_fn {
match setup_fn.body {
// Append only when function has a body and it is not empty
Some(ref b) if !b.stmts.is_empty() => {
Expand Down
Loading

0 comments on commit e019232

Please sign in to comment.