Skip to content

Commit

Permalink
feat(#23): support scope-aware type resolutions
Browse files Browse the repository at this point in the history
  • Loading branch information
phoenix-ru committed Sep 2, 2024
1 parent c7586ec commit dd57009
Show file tree
Hide file tree
Showing 8 changed files with 1,120 additions and 447 deletions.
2 changes: 2 additions & 0 deletions crates/fervid_transform/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ pub enum ScriptErrorKind {
DuplicateImport,
/// Could not resolve array element type
ResolveTypeElementType,
/// "Failed to resolve extends base type"
ResolveTypeExtendsBaseType,
/// A type param was not provided,
/// e.g. `ExtractPropTypes<>`
ResolveTypeMissingTypeParam,
Expand Down
63 changes: 40 additions & 23 deletions crates/fervid_transform/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::{cell::RefCell, rc::Rc};

use error::TransformError;
use fervid_core::{SfcDescriptor, SfcScriptBlock, SfcScriptLang};
use fervid_core::{SfcDescriptor, SfcScriptBlock, SfcScriptLang, TemplateGenerationMode};
use misc::infer_name;
use script::transform_and_record_scripts;
use style::{attach_scope_id, create_style_scope, transform_style_blocks};
Expand Down Expand Up @@ -29,40 +31,21 @@ pub fn transform_sfc<'o>(
options: TransformSfcOptions<'o>,
errors: &mut Vec<TransformError>,
) -> TransformSfcResult {
// Create the bindings helper
let mut bindings_helper = BindingsHelper::default();
bindings_helper.is_prod = options.is_prod;

// Create the context
let mut ctx = TransformSfcContext {
filename: options.filename.to_string()
};

// TS if any of scripts is TS.
// Unlike the official compiler, we don't care if languages are mixed, because nothing changes.
let recognize_lang = |script: &SfcScriptBlock| matches!(script.lang, SfcScriptLang::Typescript);
bindings_helper.is_ts = sfc_descriptor
.script_setup
.as_ref()
.map_or(false, recognize_lang)
|| sfc_descriptor
.script_legacy
.as_ref()
.map_or(false, recognize_lang);
let mut ctx = TransformSfcContext::new(&sfc_descriptor, &options);

// Transform the scripts
let mut transform_result = transform_and_record_scripts(
&mut ctx,
sfc_descriptor.script_setup,
sfc_descriptor.script_legacy,
&mut bindings_helper,
errors,
);

// Transform the template if it is present
let mut template_block = None;
if let Some(mut template) = sfc_descriptor.template {
transform_and_record_template(&mut template, &mut bindings_helper);
transform_and_record_template(&mut template, &mut ctx.bindings_helper);
if !template.roots.is_empty() {
template_block = Some(template);
}
Expand All @@ -81,7 +64,7 @@ pub fn transform_sfc<'o>(
infer_name(&mut exported_obj, &options.filename);

TransformSfcResult {
bindings_helper,
bindings_helper: ctx.bindings_helper,
exported_obj,
module: transform_result.module,
setup_fn: transform_result.setup_fn,
Expand All @@ -90,3 +73,37 @@ pub fn transform_sfc<'o>(
custom_blocks: sfc_descriptor.custom_blocks,
}
}

impl TransformSfcContext {
pub fn new(sfc_descriptor: &SfcDescriptor, options: &TransformSfcOptions) -> TransformSfcContext {
// Create the bindings helper
let mut bindings_helper = BindingsHelper::default();
bindings_helper.is_prod = options.is_prod;

// TS if any of scripts is TS.
// Unlike the official compiler, we don't care if languages are mixed, because nothing changes.
let recognize_lang = |script: &SfcScriptBlock| matches!(script.lang, SfcScriptLang::Typescript);
bindings_helper.is_ts = sfc_descriptor
.script_setup
.as_ref()
.map_or(false, recognize_lang)
|| sfc_descriptor
.script_legacy
.as_ref()
.map_or(false, recognize_lang);

// Set inline flag in `BindingsHelper`
if bindings_helper.is_prod && sfc_descriptor.script_setup.is_some() {
bindings_helper.template_generation_mode = TemplateGenerationMode::Inline;
}

let scope = Rc::new(RefCell::from(TypeScope::new(options.filename.to_string())));

TransformSfcContext {
filename: options.filename.to_string(),
is_ce: false, // todo
bindings_helper,
scope,
}
}
}
77 changes: 54 additions & 23 deletions crates/fervid_transform/src/script.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
//! Responsible for `<script>` and `<script setup>` transformations and analysis.
use fervid_core::{SfcScriptBlock, TemplateGenerationMode};
use fervid_core::SfcScriptBlock;
use resolve_type::record_types;
use swc_core::{
common::DUMMY_SP,
ecma::ast::{Function, Module, ObjectLit},
};

use crate::{
error::TransformError, structs::TransformScriptsResult, BindingsHelper, TransformSfcContext,
error::TransformError, structs::TransformScriptsResult, TransformSfcContext,
};

use self::{
Expand All @@ -34,19 +35,56 @@ pub mod utils;
/// - (TODO) Imported `.vue` component bindings;
pub fn transform_and_record_scripts(
ctx: &mut TransformSfcContext,
script_setup: Option<SfcScriptBlock>,
mut script_legacy: Option<SfcScriptBlock>,
bindings_helper: &mut BindingsHelper,
mut script_setup: Option<SfcScriptBlock>,
mut script_options: Option<SfcScriptBlock>,
errors: &mut Vec<TransformError>,
) -> TransformScriptsResult {
// Set inline flag in `BindingsHelper`
if bindings_helper.is_prod && script_setup.is_some() {
bindings_helper.template_generation_mode = TemplateGenerationMode::Inline;
}
//
// STEP 1: Imports and type collection.
//
// Imports are collected early because ES6 imports are hoisted and usage like this is valid:
// ```ts
// const bar = x(1)
// import { reactive as x } from 'vue'
// ```
//
// Official compiler does lazy type recording using the source AST,
// but we are modifying the source AST and thus cannot use it at a later stage.
// Therefore, types are eagerly recorded.

// 1.1. Imports in `<script>`
if let Some(ref mut script_options) = script_legacy {
process_imports(&mut script_options.content, bindings_helper, false, errors);
if let Some(ref mut script_options) = script_options {
process_imports(
&mut script_options.content,
&mut ctx.bindings_helper,
false,
errors,
);
}

// 1.2. Imports in `<script setup>`
if let Some(ref mut script_setup) = script_setup {
process_imports(
&mut script_setup.content,
&mut ctx.bindings_helper,
true,
errors,
);
}

// 1.3. Record types for to support type-only `defineProps` and `defineEmits`
if ctx.bindings_helper.is_ts {
let scope = ctx.scope.clone();
let mut scope = (*scope).borrow_mut();
scope.imports = ctx.bindings_helper.user_imports.clone();

record_types(
ctx,
script_setup.as_ref(),
script_options.as_ref(),
&mut scope,
false,
);
}

//
Expand All @@ -55,7 +93,7 @@ pub fn transform_and_record_scripts(
let mut script_module: Option<Box<Module>> = None;
let mut script_default_export: Option<ObjectLit> = None;

if let Some(script_options_block) = script_legacy {
if let Some(script_options_block) = script_options {
let mut module = script_options_block.content;

let transform_result = transform_and_record_script_options_api(
Expand All @@ -64,7 +102,7 @@ pub fn transform_and_record_scripts(
collect_top_level_stmts: script_setup.is_some(),
..Default::default()
},
bindings_helper,
&mut ctx.bindings_helper,
errors,
);

Expand Down Expand Up @@ -94,8 +132,7 @@ pub fn transform_and_record_scripts(

let mut setup_fn: Option<Box<Function>> = None;
if let Some(script_setup) = script_setup {
let setup_transform_result =
transform_and_record_script_setup(ctx, script_setup, bindings_helper, errors);
let setup_transform_result = transform_and_record_script_setup(ctx, script_setup, errors);

// TODO Push imports at module top or bottom? Or smart merge?
// TODO Merge Vue imports produced by module transformation
Expand Down Expand Up @@ -191,15 +228,9 @@ mod tests {
let mut ctx = TransformSfcContext::anonymous();

// Do work
let mut bindings_helper = BindingsHelper::default();
let mut errors = Vec::new();
let res = transform_and_record_scripts(
&mut ctx,
Some(script_setup),
Some(script),
&mut bindings_helper,
&mut errors,
);
let res =
transform_and_record_scripts(&mut ctx, Some(script_setup), Some(script), &mut errors);

// Emitting the result requires some setup with SWC
let cm: Lrc<SourceMap> = Default::default();
Expand Down
Loading

0 comments on commit dd57009

Please sign in to comment.