vize_atelier_sfc 0.12.0

Atelier SFC - The Single File Component workshop for Vize
Documentation
//! Vue Single File Component (.vue) compiler.
//!
//! This module provides parsing and compilation of Vue SFCs, following the
//! Vue.js core structure:
//!
//! - `parse` - SFC parsing into descriptor blocks
//! - `compile_script` - Script/script setup compilation
//! - `compile_template` - Template block compilation (DOM and Vapor)
//! - `compile` - Main SFC compilation orchestration
//! - `style` - Style block compilation with scoped CSS
//! - `css` - Low-level CSS compilation with LightningCSS
//!
//! # Example
//!
//! ```ignore
//! use vize_atelier_sfc::{parse_sfc, compile_sfc, SfcParseOptions, SfcCompileOptions};
//!
//! let source = r#"
//! <script setup>
//! import { ref } from 'vue'
//! const count = ref(0)
//! </script>
//! <template>
//!   <button @click="count++">{{ count }}</button>
//! </template>
//! "#;
//!
//! let descriptor = parse_sfc(source, SfcParseOptions::default()).unwrap();
//! let result = compile_sfc(&descriptor, SfcCompileOptions::default()).unwrap();
//! println!("{}", result.code);
//! ```

#![allow(clippy::collapsible_match)]
#![allow(clippy::type_complexity)]
#![allow(clippy::redundant_field_names)]
#![allow(clippy::unnecessary_lazy_evaluations)]
#![allow(clippy::field_reassign_with_default)]
#![allow(clippy::only_used_in_recursion)]

// Core modules - following Vue.js compiler-sfc structure
pub mod compile;
pub mod compile_script;
pub mod compile_template;
pub mod css;
pub mod parse;
pub mod rewrite_default;
pub mod script;
pub mod style;
pub mod types;

// Re-exports for public API
pub use compile::{compile_sfc, ScriptCompileResult};
pub use css::{compile_css, compile_style_block, CssCompileOptions, CssCompileResult, CssTargets};
pub use parse::parse_sfc;
pub use types::{
    BindingMetadata, BindingType, BlockLocation, PadOption, PropsDestructure, ScriptCompileOptions,
    SfcCompileOptions, SfcCompileResult, SfcCustomBlock, SfcDescriptor, SfcError, SfcParseOptions,
    SfcScriptBlock, SfcStyleBlock, SfcTemplateBlock, StyleCompileOptions, TemplateCompileOptions,
};

// Re-export key types from dependencies
pub use vize_atelier_core::CompilerError;
pub use vize_atelier_dom::compile_template;

#[cfg(test)]
mod snapshot_tests;

#[cfg(test)]
mod tests {
    use super::{compile_sfc, parse_sfc, SfcCompileOptions};

    #[test]
    fn test_parse_simple_sfc() {
        let source = r#"
<template>
  <div>Hello World</div>
</template>

<script>
export default {
  name: 'HelloWorld'
}
</script>

<style>
.hello { color: red; }
</style>
"#;
        let descriptor = parse_sfc(source, Default::default()).unwrap();

        assert!(descriptor.template.is_some());
        assert!(descriptor.script.is_some());
        assert_eq!(descriptor.styles.len(), 1);
    }

    #[test]
    fn test_parse_script_setup() {
        let source = r#"
<template>
  <div>{{ msg }}</div>
</template>

<script setup>
import { ref } from 'vue'
const msg = ref('Hello')
</script>
"#;
        let descriptor = parse_sfc(source, Default::default()).unwrap();

        assert!(descriptor.template.is_some());
        assert!(descriptor.script_setup.is_some());
    }

    #[test]
    fn test_parse_scoped_style() {
        let source = r#"
<template>
  <div class="container">Scoped</div>
</template>

<style scoped>
.container { background: blue; }
</style>
"#;
        let descriptor = parse_sfc(source, Default::default()).unwrap();

        assert_eq!(descriptor.styles.len(), 1);
        assert!(descriptor.styles[0].scoped);
    }

    #[test]
    fn test_compile_sfc_with_define_emits() {
        let source = r#"
<template>
  <button @click="onClick">{{ count }}</button>
</template>

<script setup>
import { ref } from 'vue'
const emit = defineEmits(['update'])
const count = ref(0)
function onClick() {
    emit('update', count.value)
}
</script>
"#;
        let descriptor = parse_sfc(source, Default::default()).unwrap();
        let result = compile_sfc(&descriptor, SfcCompileOptions::default()).unwrap();

        println!("Full SFC output:\n{}", result.code);

        // defineEmits should NOT be in the output
        assert!(
            !result.code.contains("defineEmits"),
            "defineEmits should be removed"
        );
        // emits should be defined at component level
        assert!(
            result.code.contains("emits:"),
            "emits should be at component level"
        );
        // emit should be bound to __emit
        assert!(
            result.code.contains("const emit = __emit"),
            "emit should be bound to __emit"
        );
    }

    #[test]
    fn test_compile_sfc_define_model_with_type_args_preserves_body() {
        // Regression test: defineModel<Type>('name', { opts }); was wrongly detected
        // as a multi-line macro call because the line ends with `;` not `)`.
        // This caused all subsequent setup code to be swallowed by the macro tracker.
        let source = r#"
<template>
  <div>{{ fx }}</div>
</template>

<script setup lang="ts">
interface Layer { fxId: string }
const layer = defineModel<Layer>('layer', { required: true });

const fx = layer.value.fxId;
if (fx == null) {
  throw new Error('not found');
}
</script>
"#;
        let descriptor = parse_sfc(source, Default::default()).unwrap();
        let result = compile_sfc(&descriptor, SfcCompileOptions::default()).unwrap();

        println!("defineModel output:\n{}", result.code);

        // defineModel should be transformed to _useModel
        assert!(
            result.code.contains("_useModel(__props,"),
            "defineModel should become _useModel"
        );
        // Code after defineModel must be preserved
        assert!(
            result.code.contains("const fx = layer.value.fxId"),
            "setup body after defineModel must be preserved"
        );
        assert!(
            result.code.contains("throw new Error"),
            "throw statement after defineModel must be preserved"
        );
    }
}