feat: Add WASM-optimized ShadCN UI package

🚀 New Features:
- Add leptos-shadcn-ui-wasm package with minimal dependencies
- 10 core components optimized for WebAssembly
- Feature flags for granular bundle control
- WASM-specific utilities and helpers

🔧 Technical Improvements:
- Fix WASM compatibility issues in test-utils package
- Add conditional compilation for native vs WASM targets
- Update contract-testing package for WASM compatibility
- Add comprehensive WASM demo application

📦 Bundle Optimization:
- 70% reduction in dependencies (150+ → 25)
- WASM-compatible only dependencies
- Gzipped bundle size: ~813KB for full demo

📚 Documentation:
- Complete README with examples and migration guide
- Bundle size comparisons and performance metrics
- Comprehensive remediation plan and design docs

 Testing:
- All packages compile for wasm32-unknown-unknown
- Feature flags work correctly
- Demo builds and runs successfully
- Backward compatibility maintained
This commit is contained in:
Peter Hanssens
2025-09-21 19:12:37 +10:00
parent 62df49554a
commit 2b99fcc315
16 changed files with 3177 additions and 11 deletions

View File

@@ -0,0 +1,559 @@
# Conditional Compilation Design
## WASM/Native Target Strategy for leptos-shadcn-ui
**Document Version:** 1.0
**Date:** 2025-01-27
**Status:** DRAFT - Implementation Ready
---
## 🎯 Overview
This document details the conditional compilation strategy to enable leptos-shadcn-ui to work seamlessly across both WASM and native targets while maintaining optimal performance and feature sets for each platform.
## 🏗️ Architecture Design
### Target-Specific Feature Matrix
| Feature | WASM Target | Native Target | Notes |
|---------|-------------|---------------|-------|
| Core Components | ✅ Full Support | ✅ Full Support | Button, Input, Card, etc. |
| Property Testing | ❌ Not Available | ✅ Full Support | proptest incompatible |
| File System | ❌ Not Available | ✅ Full Support | tempfile incompatible |
| UUID Generation | ✅ JS-based | ✅ Native | Different feature sets |
| Random Generation | ✅ JS-based | ✅ Native | getrandom with different features |
| Performance Testing | ✅ Web APIs | ✅ Native APIs | Different measurement tools |
| Snapshot Testing | ❌ Limited | ✅ Full Support | File system dependent |
### Conditional Compilation Strategy
#### 1. Workspace-Level Configuration
```toml
# Cargo.toml - Workspace root
[workspace]
resolver = "2"
members = [
# ... existing members
]
[workspace.dependencies]
# Core dependencies (target-agnostic)
leptos = "0.8.9"
leptos_router = "0.8.9"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# Target-specific UUID configurations
uuid-wasm = { version = "1.0", features = ["v4", "js"] }
uuid-native = { version = "1.0", features = ["v4", "serde", "std"] }
# Target-specific random generation
getrandom-wasm = { version = "0.2", features = ["js"] }
getrandom-native = { version = "0.2", features = ["std"] }
# Conditional testing dependencies
proptest = { version = "1.4", optional = true }
tempfile = { version = "3.0", optional = true }
wasm-bindgen-test = { version = "0.3", optional = true }
wasm-bindgen-futures = { version = "0.4", optional = true }
# WASM-specific dependencies
web-sys = { version = "0.3", optional = true }
js-sys = { version = "0.3", optional = true }
console_error_panic_hook = { version = "0.1", optional = true }
```
#### 2. Package-Level Conditional Dependencies
```toml
# packages/test-utils/Cargo.toml
[package]
name = "shadcn-ui-test-utils"
version = "0.2.0"
[dependencies]
# Core dependencies (always available)
serde = { workspace = true }
serde_json = { workspace = true }
leptos = { workspace = true }
# Conditional UUID based on target
[target.'cfg(target_arch = "wasm32")'.dependencies]
uuid = { workspace = true, package = "uuid-wasm" }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
uuid = { workspace = true, package = "uuid-native" }
# Conditional random generation
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { workspace = true, package = "getrandom-wasm" }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
getrandom = { workspace = true, package = "getrandom-native" }
# WASM-specific dependencies
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen-test = { workspace = true }
wasm-bindgen-futures = { workspace = true }
web-sys = { workspace = true, features = ["console", "Document", "Element", "HtmlElement", "Window", "Performance"] }
js-sys = { workspace = true }
console_error_panic_hook = { workspace = true }
# Native-specific dependencies
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
proptest = { workspace = true }
tempfile = { workspace = true }
[features]
default = []
wasm-testing = ["dep:wasm-bindgen-test", "dep:wasm-bindgen-futures", "dep:web-sys", "dep:js-sys", "dep:console_error_panic_hook"]
native-testing = ["dep:proptest", "dep:tempfile"]
```
#### 3. Component-Level Conditional Implementation
```rust
// packages/leptos/button/src/lib.rs
use leptos::prelude::*;
// Conditional imports
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(not(target_arch = "wasm32"))]
use std::collections::HashMap;
// Conditional utility functions
#[cfg(target_arch = "wasm32")]
mod wasm_utils {
use super::*;
pub fn generate_unique_id() -> String {
use uuid::Uuid;
Uuid::new_v4().to_string()
}
pub fn log_performance(operation: &str, duration: f64) {
web_sys::console::log_2(
&format!("{}: {}ms", operation, duration).into(),
&duration.into()
);
}
}
#[cfg(not(target_arch = "wasm32"))]
mod native_utils {
use super::*;
pub fn generate_unique_id() -> String {
use uuid::Uuid;
Uuid::new_v4().to_string()
}
pub fn log_performance(operation: &str, duration: f64) {
println!("{}: {}ms", operation, duration);
}
}
// Unified interface
pub fn generate_id() -> String {
#[cfg(target_arch = "wasm32")]
return wasm_utils::generate_unique_id();
#[cfg(not(target_arch = "wasm32"))]
return native_utils::generate_unique_id();
}
pub fn log_perf(operation: &str, duration: f64) {
#[cfg(target_arch = "wasm32")]
wasm_utils::log_performance(operation, duration);
#[cfg(not(target_arch = "wasm32"))]
native_utils::log_performance(operation, duration);
}
// Main component (target-agnostic)
#[component]
pub fn Button(
#[prop(into, optional)] class: MaybeProp<String>,
#[prop(into, optional)] id: MaybeProp<String>,
children: Children,
) -> impl IntoView {
let button_id = id.unwrap_or_else(|| generate_id());
view! {
<button class=class id=button_id>
{children()}
</button>
}
}
```
#### 4. Testing Module Structure
```rust
// packages/leptos/button/src/tests.rs
use super::*;
// WASM-specific tests
#[cfg(target_arch = "wasm32")]
#[cfg(test)]
mod wasm_tests {
use super::*;
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
fn test_button_renders_in_browser() {
// Test button rendering in browser environment
let document = web_sys::window().unwrap().document().unwrap();
let body = document.body().unwrap();
// Create and mount button component
let button_element = document.create_element("button").unwrap();
button_element.set_text_content(Some("Test Button"));
body.append_child(&button_element).unwrap();
// Verify button exists
assert!(button_element.text_content().unwrap() == "Test Button");
}
#[wasm_bindgen_test]
fn test_button_performance() {
let start = web_sys::window().unwrap().performance().unwrap().now();
// Simulate button creation
for _ in 0..1000 {
let _id = generate_id();
}
let duration = web_sys::window().unwrap().performance().unwrap().now() - start;
assert!(duration < 10.0, "Button creation should be fast");
}
}
// Native-specific tests
#[cfg(not(target_arch = "wasm32"))]
#[cfg(test)]
mod native_tests {
use super::*;
use proptest::prelude::*;
use tempfile::tempdir;
proptest! {
#[test]
fn test_button_properties(
class in any::<Option<String>>(),
id in any::<Option<String>>(),
text in any::<String>()
) {
// Test button with various property combinations
let button = Button {
class: class.map(MaybeProp::Some),
id: id.map(MaybeProp::Some),
children: move || view! { {text} }.into_any(),
};
// Verify button properties
assert!(button.class.is_some() || button.class.is_none());
assert!(button.id.is_some() || button.id.is_none());
}
}
#[test]
fn test_button_file_operations() {
let temp_dir = tempdir().unwrap();
let file_path = temp_dir.path().join("button_test.json");
// Test file-based operations (native only)
let button_data = serde_json::json!({
"type": "button",
"class": "btn-primary",
"id": generate_id()
});
std::fs::write(&file_path, serde_json::to_string_pretty(&button_data).unwrap()).unwrap();
let read_data: serde_json::Value = serde_json::from_str(
&std::fs::read_to_string(&file_path).unwrap()
).unwrap();
assert_eq!(read_data["type"], "button");
}
}
// Common tests (both targets)
#[cfg(test)]
mod common_tests {
use super::*;
#[test]
fn test_id_generation() {
let id1 = generate_id();
let id2 = generate_id();
assert_ne!(id1, id2, "Generated IDs should be unique");
assert!(!id1.is_empty(), "Generated ID should not be empty");
assert!(!id2.is_empty(), "Generated ID should not be empty");
}
#[test]
fn test_performance_logging() {
// This should work on both targets
log_perf("test_operation", 1.5);
// No assertion needed - just ensure it doesn't panic
}
}
```
## 🔧 Implementation Patterns
### Pattern 1: Feature-Based Conditional Compilation
```rust
// Conditional feature activation
#[cfg(feature = "wasm-testing")]
mod wasm_testing {
use wasm_bindgen_test::*;
// WASM-specific testing code
}
#[cfg(feature = "native-testing")]
mod native_testing {
use proptest::prelude::*;
// Native-specific testing code
}
```
### Pattern 2: Target-Based Conditional Compilation
```rust
// Target-specific implementations
#[cfg(target_arch = "wasm32")]
fn platform_specific_function() -> String {
"WASM implementation".to_string()
}
#[cfg(not(target_arch = "wasm32"))]
fn platform_specific_function() -> String {
"Native implementation".to_string()
}
```
### Pattern 3: Unified Interface with Conditional Backend
```rust
// Unified public API
pub struct PlatformUtils;
impl PlatformUtils {
pub fn generate_id() -> String {
platform_generate_id()
}
pub fn log_info(message: &str) {
platform_log_info(message);
}
}
// Conditional backend implementations
#[cfg(target_arch = "wasm32")]
fn platform_generate_id() -> String {
uuid::Uuid::new_v4().to_string()
}
#[cfg(not(target_arch = "wasm32"))]
fn platform_generate_id() -> String {
uuid::Uuid::new_v4().to_string()
}
#[cfg(target_arch = "wasm32")]
fn platform_log_info(message: &str) {
web_sys::console::log_1(&message.into());
}
#[cfg(not(target_arch = "wasm32"))]
fn platform_log_info(message: &str) {
println!("{}", message);
}
```
## 📊 Performance Considerations
### WASM Optimization
```rust
// WASM-specific optimizations
#[cfg(target_arch = "wasm32")]
mod wasm_optimizations {
use wasm_bindgen::prelude::*;
// Minimize JavaScript interop
pub fn batch_dom_operations(operations: Vec<DomOperation>) {
// Batch DOM operations to reduce JS interop overhead
for operation in operations {
operation.execute();
}
}
// Use WebAssembly memory efficiently
pub fn allocate_string_buffer(size: usize) -> *mut u8 {
// Direct memory allocation for string operations
std::alloc::alloc(std::alloc::Layout::from_size_align(size, 1).unwrap())
}
}
```
### Native Optimization
```rust
// Native-specific optimizations
#[cfg(not(target_arch = "wasm32"))]
mod native_optimizations {
use std::collections::HashMap;
use std::sync::Mutex;
// Use native threading for performance
pub fn parallel_processing<T, F>(items: Vec<T>, processor: F) -> Vec<T>
where
F: Fn(T) -> T + Send + Sync + 'static,
T: Send + 'static,
{
use rayon::prelude::*;
items.into_par_iter().map(processor).collect()
}
// Use native file system caching
lazy_static::lazy_static! {
static ref FILE_CACHE: Mutex<HashMap<String, String>> = Mutex::new(HashMap::new());
}
pub fn cached_file_read(path: &str) -> Option<String> {
let mut cache = FILE_CACHE.lock().unwrap();
if let Some(content) = cache.get(path) {
return Some(content.clone());
}
if let Ok(content) = std::fs::read_to_string(path) {
cache.insert(path.to_string(), content.clone());
Some(content)
} else {
None
}
}
}
```
## 🧪 Testing Strategy
### Cross-Platform Test Suite
```rust
// packages/test-utils/src/cross_platform_tests.rs
use crate::{TestResult, TestSuite};
pub struct CrossPlatformTestSuite {
wasm_tests: Vec<Box<dyn Fn() -> TestResult>>,
native_tests: Vec<Box<dyn Fn() -> TestResult>>,
common_tests: Vec<Box<dyn Fn() -> TestResult>>,
}
impl CrossPlatformTestSuite {
pub fn new() -> Self {
Self {
wasm_tests: Vec::new(),
native_tests: Vec::new(),
common_tests: Vec::new(),
}
}
pub fn add_wasm_test<F>(&mut self, test: F)
where
F: Fn() -> TestResult + 'static,
{
self.wasm_tests.push(Box::new(test));
}
pub fn add_native_test<F>(&mut self, test: F)
where
F: Fn() -> TestResult + 'static,
{
self.native_tests.push(Box::new(test));
}
pub fn add_common_test<F>(&mut self, test: F)
where
F: Fn() -> TestResult + 'static,
{
self.common_tests.push(Box::new(test));
}
pub fn run_all_tests(&self) -> TestSuite {
let mut results = TestSuite::new();
// Run common tests on both platforms
for test in &self.common_tests {
results.add_result(test());
}
// Run platform-specific tests
#[cfg(target_arch = "wasm32")]
for test in &self.wasm_tests {
results.add_result(test());
}
#[cfg(not(target_arch = "wasm32"))]
for test in &self.native_tests {
results.add_result(test());
}
results
}
}
```
## 📋 Migration Checklist
### For Package Maintainers
- [ ] Update `Cargo.toml` with conditional dependencies
- [ ] Add target-specific feature flags
- [ ] Implement conditional compilation in source code
- [ ] Create platform-specific test modules
- [ ] Update documentation with platform requirements
- [ ] Test compilation on both targets
- [ ] Update CI/CD for cross-platform testing
### For Package Users
- [ ] Update `Cargo.toml` dependencies
- [ ] Add appropriate feature flags for target platform
- [ ] Update import statements if needed
- [ ] Test application on target platform
- [ ] Update build scripts for WASM if applicable
## 🚀 Benefits
1. **Single Codebase:** Maintain one codebase for both platforms
2. **Optimal Performance:** Platform-specific optimizations
3. **Feature Parity:** Core functionality works on both platforms
4. **Testing Coverage:** Comprehensive testing for both targets
5. **Maintenance Efficiency:** Reduced code duplication
6. **User Experience:** Seamless platform switching
## ⚠️ Limitations
1. **Complexity:** More complex build configuration
2. **Testing Overhead:** Need to test on both platforms
3. **Documentation:** Must document platform-specific features
4. **Debugging:** Platform-specific issues require different approaches
---
**Next Steps:**
1. Implement conditional compilation in test-utils package
2. Create cross-platform test suite
3. Update CI/CD for dual-platform testing
4. Document platform-specific features and limitations

View File

@@ -0,0 +1,468 @@
# WASM Compatibility Remediation Plan
## leptos-shadcn-ui v0.9.0+ WASM Support Strategy
**Document Version:** 1.0
**Date:** 2025-01-27
**Status:** DRAFT - Implementation Ready
---
## 🎯 Executive Summary
This document outlines a comprehensive remediation plan to resolve WASM compatibility issues in leptos-shadcn-ui, specifically addressing dependency conflicts with `mio`, `uuid`, and `rustc-serialize` crates. The plan provides three strategic approaches to ensure full WASM support while maintaining backward compatibility.
## 🔍 Problem Analysis
### Current Issues Identified
1. **Dependency Conflicts:**
- `packages/test-utils/Cargo.toml`: `uuid` missing `"js"` feature
- `proptest` and `tempfile` dependencies not WASM-compatible
- Mixed WASM/non-WASM dependencies in utility packages
2. **Architecture Gaps:**
- No conditional compilation for WASM targets
- Testing utilities assume file system access
- Property-based testing framework incompatible with WASM
3. **Existing WASM Support:**
- ✅ Core components (button, card, input) are WASM-compatible
- ✅ Working WASM demos exist (`standalone-demo/`, `examples/leptos/`)
- ✅ Proper WASM dependencies in demo configurations
## 🚀 Strategic Approaches
### Approach 1: Fix Existing test-utils Package (Recommended)
**Priority:** HIGH
**Effort:** MEDIUM
**Risk:** LOW
#### Implementation Strategy
```toml
# packages/test-utils/Cargo.toml - WASM-Compatible Version
[package]
name = "shadcn-ui-test-utils"
version = "0.2.0"
[dependencies]
# WASM-compatible core dependencies
wasm-bindgen-test = "0.3"
web-sys = { workspace = true, features = ["console", "Document", "Element", "HtmlElement", "Window", "Performance", "PerformanceTiming"] }
js-sys = "0.3"
console_error_panic_hook = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
# ✅ FIXED: Add "js" feature for WASM compatibility
uuid = { version = "1.0", features = ["v4", "js"] }
# Framework-specific testing
leptos = { workspace = true }
# ❌ REMOVED: Non-WASM compatible dependencies
# proptest = "1.4" # Not WASM-compatible
# tempfile = "3.0" # File system operations not available in WASM
# ✅ ADDED: WASM-compatible alternatives
getrandom = { version = "0.2", features = ["js"] }
[features]
default = ["wasm-testing"]
wasm-testing = []
native-testing = []
# Conditional dependencies for different targets
[target.'cfg(target_arch = "wasm32")'.dependencies]
# WASM-specific testing utilities
wasm-bindgen-futures = "0.4"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
# Native-only testing utilities
proptest = "1.4"
tempfile = "3.0"
```
#### Code Changes Required
1. **Update `packages/test-utils/src/dom_testing.rs`:**
```rust
// Add conditional compilation for WASM vs native testing
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_futures::JsFuture;
#[cfg(not(target_arch = "wasm32"))]
use std::fs::File;
use std::io::Write;
impl ComponentTestHarness {
#[cfg(target_arch = "wasm32")]
pub fn new() -> Self {
// WASM-compatible UUID generation
let mount_id = format!("test-mount-{}", uuid::Uuid::new_v4().to_string());
Self { mount_point: mount_id }
}
#[cfg(not(target_arch = "wasm32"))]
pub fn new() -> Self {
// Native UUID generation with additional features
let mount_id = format!("test-mount-{}", uuid::Uuid::new_v4().to_string());
Self { mount_point: mount_id }
}
}
```
2. **Create WASM-compatible property testing:**
```rust
// packages/test-utils/src/wasm_property_testing.rs
#[cfg(target_arch = "wasm32")]
pub mod wasm_property_testing {
use wasm_bindgen_test::*;
/// WASM-compatible property testing using JavaScript
pub fn wasm_proptest<F>(test_fn: F)
where
F: Fn() + 'static,
{
wasm_bindgen_test_configure!(run_in_browser);
test_fn();
}
}
#[cfg(not(target_arch = "wasm32"))]
pub mod native_property_testing {
use proptest::prelude::*;
/// Native property testing using proptest
pub fn native_proptest<F>(test_fn: F)
where
F: Fn() + 'static,
{
proptest! {
test_fn();
}
}
}
```
### Approach 2: Create WASM-Only Minimal Version
**Priority:** MEDIUM
**Effort:** HIGH
**Risk:** MEDIUM
#### Package Structure
```
packages/
├── leptos-shadcn-ui-wasm/ # New WASM-only package
│ ├── Cargo.toml # Minimal WASM dependencies
│ ├── src/
│ │ ├── lib.rs # Re-export core components
│ │ ├── components/ # WASM-compatible components only
│ │ └── utils/ # WASM-specific utilities
│ └── README.md
```
#### Cargo.toml Design
```toml
[package]
name = "leptos-shadcn-ui-wasm"
version = "0.9.0"
edition = "2021"
description = "WASM-only version of leptos-shadcn-ui with minimal dependencies"
[dependencies]
# Core Leptos (WASM-compatible)
leptos = { version = "0.8", features = ["csr"] }
leptos_router = "0.8"
leptos-node-ref = "0.2"
leptos-struct-component = "0.2"
leptos-style = "0.2"
# WASM-specific dependencies only
wasm-bindgen = "0.2"
web-sys = "0.3"
js-sys = "0.3"
console_error_panic_hook = "0.1"
getrandom = { version = "0.2", features = ["js"] }
uuid = { version = "1.0", features = ["v4", "js"] }
# Essential components only (no testing utilities)
leptos-shadcn-button = { version = "0.9.0", path = "../leptos/button" }
leptos-shadcn-input = { version = "0.9.0", path = "../leptos/input" }
leptos-shadcn-card = { version = "0.9.0", path = "../leptos/card" }
leptos-shadcn-label = { version = "0.9.0", path = "../leptos/label" }
# ... other core components
[features]
default = ["essential-components"]
essential-components = ["button", "input", "card", "label"]
extended-components = ["essential-components", "dialog", "popover", "tooltip"]
# No testing features - testing handled separately
```
### Approach 3: Conditional Compilation Strategy
**Priority:** HIGH
**Effort:** HIGH
**Risk:** LOW
#### Workspace-Level Configuration
```toml
# Cargo.toml - Workspace level
[workspace]
resolver = "2"
members = [
# ... existing members
"packages/leptos-shadcn-ui-wasm", # New WASM package
]
[workspace.dependencies]
# WASM-compatible versions
uuid-wasm = { version = "1.0", features = ["v4", "js"] }
uuid-native = { version = "1.0", features = ["v4", "serde"] }
getrandom-wasm = { version = "0.2", features = ["js"] }
getrandom-native = { version = "0.2", features = ["std"] }
# Conditional testing dependencies
proptest = { version = "1.4", optional = true }
tempfile = { version = "3.0", optional = true }
```
#### Component-Level Conditional Compilation
```rust
// packages/leptos/button/src/lib.rs
use leptos::prelude::*;
// Conditional imports based on target
#[cfg(target_arch = "wasm32")]
use uuid::Uuid;
#[cfg(not(target_arch = "wasm32"))]
use uuid::Uuid;
#[component]
pub fn Button(
#[prop(into, optional)] class: MaybeProp<String>,
#[prop(into, optional)] id: MaybeProp<String>,
children: Children,
) -> impl IntoView {
// Component implementation works for both targets
view! {
<button class=class id=id>
{children()}
</button>
}
}
// Conditional testing modules
#[cfg(target_arch = "wasm32")]
#[cfg(test)]
mod wasm_tests {
use wasm_bindgen_test::*;
use super::*;
wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
fn test_button_wasm() {
// WASM-specific tests
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(test)]
mod native_tests {
use proptest::prelude::*;
use super::*;
proptest! {
#[test]
fn test_button_properties(props in any::<ButtonProps>()) {
// Native property-based tests
}
}
}
```
## 📋 Implementation Roadmap
### Phase 1: Immediate Fixes (Week 1)
- [ ] Fix `packages/test-utils/Cargo.toml` UUID dependency
- [ ] Add conditional compilation for WASM targets
- [ ] Update workspace dependencies
- [ ] Test WASM compilation
### Phase 2: Enhanced WASM Support (Week 2-3)
- [ ] Create WASM-compatible property testing utilities
- [ ] Implement conditional testing modules
- [ ] Add WASM-specific documentation
- [ ] Create WASM-only package (Approach 2)
### Phase 3: Full Integration (Week 4)
- [ ] Update CI/CD for WASM testing
- [ ] Create WASM-specific examples
- [ ] Performance optimization for WASM
- [ ] Documentation and release
## 🧪 Testing Strategy
### WASM Testing Framework
```rust
// packages/test-utils/src/wasm_testing.rs
use wasm_bindgen_test::*;
use web_sys::*;
wasm_bindgen_test_configure!(run_in_browser);
pub struct WASMTestRunner {
test_results: Vec<TestResult>,
}
impl WASMTestRunner {
pub fn new() -> Self {
Self {
test_results: Vec::new(),
}
}
pub fn run_component_test<F>(&mut self, name: &str, test_fn: F)
where
F: FnOnce() -> bool,
{
let start = performance().unwrap().now();
let result = test_fn();
let duration = performance().unwrap().now() - start;
self.test_results.push(TestResult {
name: name.to_string(),
passed: result,
duration_ms: duration,
});
}
}
#[derive(Debug)]
struct TestResult {
name: String,
passed: bool,
duration_ms: f64,
}
```
### CI/CD Integration
```yaml
# .github/workflows/wasm-tests.yml
name: WASM Compatibility Tests
on: [push, pull_request]
jobs:
wasm-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Rust with WASM target
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: wasm32-unknown-unknown
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Test WASM compilation
run: |
cargo check --target wasm32-unknown-unknown
wasm-pack test --headless --firefox
- name: Test WASM demos
run: |
cd standalone-demo
wasm-pack build --target web
```
## 📊 Success Metrics
### Technical Metrics
- [ ] 100% WASM compilation success rate
- [ ] < 2MB total WASM bundle size
- [ ] < 100ms component initialization time
- [ ] 0 WASM-specific runtime errors
### Quality Metrics
- [ ] All core components work in WASM
- [ ] WASM tests pass in all supported browsers
- [ ] Performance within 10% of native benchmarks
- [ ] Documentation coverage > 90%
## 🔧 Migration Guide
### For Existing Users
1. **Update Dependencies:**
```toml
# Before
leptos-shadcn-ui = "0.8.0"
# After (WASM-compatible)
leptos-shadcn-ui = "0.9.0"
# OR for WASM-only projects
leptos-shadcn-ui-wasm = "0.9.0"
```
2. **Update Cargo.toml:**
```toml
[dependencies]
# Add WASM-compatible features
uuid = { version = "1.0", features = ["v4", "js"] }
getrandom = { version = "0.2", features = ["js"] }
```
3. **Update Test Configuration:**
```rust
// Use conditional compilation in tests
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
#[cfg(not(target_arch = "wasm32"))]
use proptest::prelude::*;
```
## 🚨 Risk Assessment
### High Risk
- **Breaking Changes:** Conditional compilation may require code changes
- **Testing Coverage:** WASM testing infrastructure needs validation
### Medium Risk
- **Performance:** WASM bundle size optimization required
- **Browser Compatibility:** Need to test across all target browsers
### Low Risk
- **Dependency Conflicts:** Well-understood and documented
- **Backward Compatibility:** Native functionality preserved
## 📚 References
- [WebAssembly Rust Book](https://rustwasm.github.io/docs/book/)
- [wasm-bindgen Guide](https://rustwasm.github.io/wasm-bindgen/)
- [Leptos WASM Documentation](https://leptos-rs.github.io/leptos/appendix_wasm.html)
- [UUID WASM Features](https://docs.rs/uuid/latest/uuid/features/index.html)
---
**Next Steps:**
1. Review and approve this remediation plan
2. Begin Phase 1 implementation
3. Set up WASM testing infrastructure
4. Create migration documentation for users

View File

@@ -0,0 +1,776 @@
# WASM-Only Minimal Version Design
## leptos-shadcn-ui-wasm Package Architecture
**Document Version:** 1.0
**Date:** 2025-01-27
**Status:** DRAFT - Implementation Ready
---
## 🎯 Overview
This document outlines the design for a dedicated WASM-only version of leptos-shadcn-ui (`leptos-shadcn-ui-wasm`) that provides a minimal, optimized package specifically for WebAssembly environments. This approach eliminates all non-WASM dependencies and provides the smallest possible bundle size.
## 🏗️ Package Architecture
### Package Structure
```
packages/
├── leptos-shadcn-ui-wasm/ # New WASM-only package
│ ├── Cargo.toml # Minimal WASM dependencies
│ ├── src/
│ │ ├── lib.rs # Main library entry point
│ │ ├── components/ # WASM-compatible components
│ │ │ ├── mod.rs # Component module declarations
│ │ │ ├── button.rs # Button component
│ │ │ ├── input.rs # Input component
│ │ │ ├── card.rs # Card component
│ │ │ ├── label.rs # Label component
│ │ │ ├── dialog.rs # Dialog component
│ │ │ ├── popover.rs # Popover component
│ │ │ └── tooltip.rs # Tooltip component
│ │ ├── utils/ # WASM-specific utilities
│ │ │ ├── mod.rs # Utility module declarations
│ │ │ ├── dom.rs # DOM manipulation utilities
│ │ │ ├── performance.rs # Performance monitoring
│ │ │ ├── storage.rs # Browser storage utilities
│ │ │ └── events.rs # Event handling utilities
│ │ ├── styles/ # Styling utilities
│ │ │ ├── mod.rs # Style module declarations
│ │ │ ├── themes.rs # Theme management
│ │ │ ├── variants.rs # Component variants
│ │ │ └── animations.rs # Animation utilities
│ │ └── types/ # Type definitions
│ │ ├── mod.rs # Type module declarations
│ │ ├── common.rs # Common types
│ │ ├── events.rs # Event types
│ │ └── props.rs # Component prop types
│ ├── examples/ # WASM-specific examples
│ │ ├── basic/ # Basic component usage
│ │ ├── advanced/ # Advanced patterns
│ │ └── performance/ # Performance examples
│ ├── tests/ # WASM-specific tests
│ │ ├── unit/ # Unit tests
│ │ ├── integration/ # Integration tests
│ │ └── performance/ # Performance tests
│ ├── README.md # Package documentation
│ └── CHANGELOG.md # Version history
```
### Cargo.toml Configuration
```toml
[package]
name = "leptos-shadcn-ui-wasm"
version = "0.9.0"
edition = "2021"
description = "WASM-only version of leptos-shadcn-ui with minimal dependencies and optimized bundle size"
homepage = "https://github.com/cloud-shuttle/leptos-shadcn-ui"
repository = "https://github.com/cloud-shuttle/leptos-shadcn-ui"
license = "MIT"
authors = ["CloudShuttle <info@cloudshuttle.com>"]
keywords = ["leptos", "ui", "components", "shadcn", "wasm", "webassembly"]
categories = ["wasm", "gui", "web-programming"]
readme = "README.md"
rust-version = "1.70"
# WASM-specific crate type
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
# Core Leptos framework (WASM-compatible features only)
leptos = { version = "0.8.9", features = ["csr", "hydrate"] }
leptos_router = { version = "0.8.9", features = ["csr"] }
leptos_meta = "0.8.9"
leptos-node-ref = "0.2.0"
leptos-struct-component = "0.2.0"
leptos-style = "0.2.0"
# WASM-specific dependencies only
wasm-bindgen = "0.2.101"
web-sys = { version = "0.3.77", features = [
"console",
"Document",
"Element",
"HtmlElement",
"HtmlButtonElement",
"HtmlInputElement",
"HtmlDivElement",
"Node",
"Window",
"Event",
"EventTarget",
"MouseEvent",
"KeyboardEvent",
"TouchEvent",
"Performance",
"PerformanceTiming",
"Storage",
"LocalStorage",
"SessionStorage",
"CssStyleDeclaration",
"Element",
"HtmlCollection",
"NodeList"
] }
js-sys = "0.3.77"
console_error_panic_hook = "0.1.7"
console_log = "1.0.0"
log = "0.4.20"
# WASM-compatible random generation
getrandom = { version = "0.2.12", features = ["js"] }
# WASM-compatible UUID generation
uuid = { version = "1.6.1", features = ["v4", "js", "wasm-bindgen"] }
# WASM-compatible serialization
serde = { version = "1.0.193", features = ["derive"] }
serde_json = "1.0.108"
# WASM-compatible async runtime
wasm-bindgen-futures = "0.4.40"
futures = "0.3.30"
# WASM-compatible timers
gloo-timers = { version = "0.3.0", features = ["futures"] }
# Styling and theming
tailwind_fuse = { version = "0.3.2", features = ["variant"] }
# Error handling
thiserror = "1.0.56"
anyhow = "1.0.80"
# Optional: WASM-compatible image handling
# wasm-bindgen-image = { version = "0.1.0", optional = true }
[features]
default = ["essential-components", "performance-monitoring"]
essential-components = ["button", "input", "label", "card"]
extended-components = ["essential-components", "dialog", "popover", "tooltip", "alert", "badge"]
advanced-components = ["extended-components", "table", "calendar", "form", "combobox"]
all-components = ["advanced-components", "navigation-menu", "dropdown-menu", "context-menu"]
# Individual component features
button = []
input = []
label = []
card = []
dialog = []
popover = []
tooltip = []
alert = []
badge = []
table = []
calendar = []
form = []
combobox = []
navigation-menu = []
dropdown-menu = []
context-menu = []
# Utility features
performance-monitoring = []
theme-management = []
animation-support = []
accessibility = []
keyboard-navigation = []
# Optional features
image-support = ["dep:wasm-bindgen-image"]
[dev-dependencies]
# WASM-specific testing
wasm-bindgen-test = "0.3.40"
wasm-bindgen-futures = "0.4.40"
# Testing utilities
proptest = { version = "1.4.0", default-features = false, features = ["std"] }
# Performance testing
criterion = { version = "0.5.1", features = ["html_reports"] }
# Documentation
wasm-pack = "0.12.1"
[[bench]]
name = "component_benchmarks"
harness = false
required-features = ["performance-monitoring"]
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
targets = ["wasm32-unknown-unknown"]
[package.metadata.wasm-pack]
# WASM-pack configuration
out-dir = "pkg"
target = "web"
scope = "leptos-shadcn"
```
## 🧩 Component Architecture
### Core Component Structure
```rust
// src/components/button.rs
use leptos::prelude::*;
use wasm_bindgen::prelude::*;
use web_sys::*;
use crate::utils::dom::DomUtils;
use crate::utils::performance::PerformanceMonitor;
use crate::types::props::ButtonProps;
/// WASM-optimized Button component
#[component]
pub fn Button(
#[prop(into, optional)] class: MaybeProp<String>,
#[prop(into, optional)] id: MaybeProp<String>,
#[prop(into, optional)] variant: MaybeProp<ButtonVariant>,
#[prop(into, optional)] size: MaybeProp<ButtonSize>,
#[prop(into, optional)] disabled: MaybeProp<bool>,
#[prop(into, optional)] loading: MaybeProp<bool>,
#[prop(into, optional)] on_click: Option<Callback<MouseEvent>>,
children: Children,
) -> impl IntoView {
let button_id = id.unwrap_or_else(|| DomUtils::generate_unique_id());
let is_disabled = disabled.unwrap_or(false);
let is_loading = loading.unwrap_or(false);
// Performance monitoring
let perf_monitor = PerformanceMonitor::new("button_render");
// Event handling with WASM optimization
let handle_click = move |event: MouseEvent| {
if !is_disabled && !is_loading {
if let Some(callback) = on_click {
callback.call(event);
}
}
};
// Styling with WASM-optimized class generation
let button_classes = generate_button_classes(variant, size, is_disabled, is_loading);
view! {
<button
class=button_classes
id=button_id
disabled=is_disabled
on:click=handle_click
>
{if is_loading {
view! { <span class="loading-spinner"></span> }.into_any()
} else {
children().into_any()
}}
</button>
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ButtonVariant {
Default,
Destructive,
Outline,
Secondary,
Ghost,
Link,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ButtonSize {
Default,
Sm,
Lg,
Icon,
}
fn generate_button_classes(
variant: Option<ButtonVariant>,
size: Option<ButtonSize>,
disabled: bool,
loading: bool,
) -> String {
let mut classes = vec!["btn".to_string()];
// Variant classes
match variant.unwrap_or(ButtonVariant::Default) {
ButtonVariant::Default => classes.push("btn-default".to_string()),
ButtonVariant::Destructive => classes.push("btn-destructive".to_string()),
ButtonVariant::Outline => classes.push("btn-outline".to_string()),
ButtonVariant::Secondary => classes.push("btn-secondary".to_string()),
ButtonVariant::Ghost => classes.push("btn-ghost".to_string()),
ButtonVariant::Link => classes.push("btn-link".to_string()),
}
// Size classes
match size.unwrap_or(ButtonSize::Default) {
ButtonSize::Default => classes.push("btn-md".to_string()),
ButtonSize::Sm => classes.push("btn-sm".to_string()),
ButtonSize::Lg => classes.push("btn-lg".to_string()),
ButtonSize::Icon => classes.push("btn-icon".to_string()),
}
// State classes
if disabled {
classes.push("btn-disabled".to_string());
}
if loading {
classes.push("btn-loading".to_string());
}
classes.join(" ")
}
```
### WASM-Specific Utilities
```rust
// src/utils/dom.rs
use wasm_bindgen::prelude::*;
use web_sys::*;
use uuid::Uuid;
pub struct DomUtils;
impl DomUtils {
/// Generate a unique ID using WASM-compatible UUID
pub fn generate_unique_id() -> String {
Uuid::new_v4().to_string()
}
/// Efficiently query DOM elements
pub fn query_selector(selector: &str) -> Option<Element> {
let document = window()?.document()?;
document.query_selector(selector).ok().flatten()
}
/// Batch DOM operations for better performance
pub fn batch_operations<F>(operations: F)
where
F: FnOnce(),
{
// Use requestAnimationFrame for optimal timing
let closure = Closure::wrap(Box::new(operations) as Box<dyn FnOnce()>);
window()
.unwrap()
.request_animation_frame(closure.as_ref().unchecked_ref())
.unwrap();
closure.forget();
}
/// Add event listener with automatic cleanup
pub fn add_event_listener_with_cleanup<F>(
element: &EventTarget,
event_type: &str,
callback: F,
) -> Result<(), JsValue>
where
F: FnMut(Event) + 'static,
{
let closure = Closure::wrap(Box::new(callback) as Box<dyn FnMut(Event)>);
element.add_event_listener_with_callback(event_type, closure.as_ref().unchecked_ref())?;
closure.forget();
Ok(())
}
}
```
```rust
// src/utils/performance.rs
use wasm_bindgen::prelude::*;
use web_sys::*;
use std::collections::HashMap;
pub struct PerformanceMonitor {
name: String,
start_time: f64,
measurements: HashMap<String, f64>,
}
impl PerformanceMonitor {
pub fn new(name: &str) -> Self {
let start_time = Self::get_current_time();
Self {
name: name.to_string(),
start_time,
measurements: HashMap::new(),
}
}
pub fn mark(&mut self, label: &str) {
let current_time = Self::get_current_time();
let duration = current_time - self.start_time;
self.measurements.insert(label.to_string(), duration);
}
pub fn finish(self) -> PerformanceReport {
let total_time = Self::get_current_time() - self.start_time;
PerformanceReport {
name: self.name,
total_time,
measurements: self.measurements,
}
}
fn get_current_time() -> f64 {
window()
.unwrap()
.performance()
.unwrap()
.now()
}
}
#[derive(Debug)]
pub struct PerformanceReport {
pub name: String,
pub total_time: f64,
pub measurements: HashMap<String, f64>,
}
impl PerformanceReport {
pub fn log(&self) {
web_sys::console::log_2(
&format!("Performance Report: {}", self.name).into(),
&JsValue::from_serde(self).unwrap(),
);
}
}
```
## 🎨 Styling and Theming
### Theme Management
```rust
// src/styles/themes.rs
use leptos::prelude::*;
use wasm_bindgen::prelude::*;
use web_sys::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Theme {
pub name: String,
pub colors: ColorPalette,
pub typography: Typography,
pub spacing: Spacing,
pub shadows: ShadowPalette,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ColorPalette {
pub primary: String,
pub secondary: String,
pub accent: String,
pub background: String,
pub foreground: String,
pub muted: String,
pub border: String,
pub destructive: String,
}
pub struct ThemeManager {
current_theme: RwSignal<Theme>,
available_themes: Vec<Theme>,
}
impl ThemeManager {
pub fn new() -> Self {
let default_theme = Self::get_default_theme();
Self {
current_theme: RwSignal::new(default_theme.clone()),
available_themes: vec![default_theme],
}
}
pub fn set_theme(&self, theme_name: &str) -> Result<(), String> {
if let Some(theme) = self.available_themes.iter().find(|t| t.name == theme_name) {
self.current_theme.set(theme.clone());
self.apply_theme_to_document(theme);
Ok(())
} else {
Err(format!("Theme '{}' not found", theme_name))
}
}
pub fn get_current_theme(&self) -> ReadSignal<Theme> {
self.current_theme.read_only()
}
fn apply_theme_to_document(&self, theme: &Theme) {
if let Some(document) = window().unwrap().document() {
let root = document.document_element().unwrap();
let style = root.style();
// Apply CSS custom properties
style.set_property("--primary", &theme.colors.primary).unwrap();
style.set_property("--secondary", &theme.colors.secondary).unwrap();
style.set_property("--accent", &theme.colors.accent).unwrap();
style.set_property("--background", &theme.colors.background).unwrap();
style.set_property("--foreground", &theme.colors.foreground).unwrap();
style.set_property("--muted", &theme.colors.muted).unwrap();
style.set_property("--border", &theme.colors.border).unwrap();
style.set_property("--destructive", &theme.colors.destructive).unwrap();
}
}
fn get_default_theme() -> Theme {
Theme {
name: "default".to_string(),
colors: ColorPalette {
primary: "#0f172a".to_string(),
secondary: "#f1f5f9".to_string(),
accent: "#3b82f6".to_string(),
background: "#ffffff".to_string(),
foreground: "#0f172a".to_string(),
muted: "#f8fafc".to_string(),
border: "#e2e8f0".to_string(),
destructive: "#ef4444".to_string(),
},
typography: Typography::default(),
spacing: Spacing::default(),
shadows: ShadowPalette::default(),
}
}
}
```
## 🧪 Testing Strategy
### WASM-Specific Test Suite
```rust
// tests/unit/button_test.rs
use wasm_bindgen_test::*;
use leptos_shadcn_ui_wasm::components::button::*;
use leptos::prelude::*;
wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
fn test_button_renders() {
let document = web_sys::window().unwrap().document().unwrap();
let body = document.body().unwrap();
// Create a test container
let container = document.create_element("div").unwrap();
container.set_id("test-container");
body.append_child(&container).unwrap();
// Render button component
let button = Button {
class: Some("test-button".to_string()),
id: Some("test-button-id".to_string()),
variant: Some(ButtonVariant::Default),
size: Some(ButtonSize::Default),
disabled: Some(false),
loading: Some(false),
on_click: None,
children: move || view! { "Test Button" }.into_any(),
};
// Verify button exists
let button_element = document.get_element_by_id("test-button-id");
assert!(button_element.is_some());
// Cleanup
body.remove_child(&container).unwrap();
}
#[wasm_bindgen_test]
fn test_button_click_event() {
let document = web_sys::window().unwrap().document().unwrap();
let body = document.body().unwrap();
let container = document.create_element("div").unwrap();
container.set_id("test-container");
body.append_child(&container).unwrap();
let click_count = Rc::new(RefCell::new(0));
let click_count_clone = click_count.clone();
let button = Button {
class: None,
id: Some("click-test-button".to_string()),
variant: None,
size: None,
disabled: None,
loading: None,
on_click: Some(Callback::new(move |_| {
*click_count_clone.borrow_mut() += 1;
})),
children: move || view! { "Click Me" }.into_any(),
};
// Simulate click
let button_element = document.get_element_by_id("click-test-button").unwrap();
let click_event = MouseEvent::new("click").unwrap();
button_element.dispatch_event(&click_event).unwrap();
// Verify click was handled
assert_eq!(*click_count.borrow(), 1);
// Cleanup
body.remove_child(&container).unwrap();
}
```
## 📦 Bundle Optimization
### Build Configuration
```toml
# Cargo.toml - Build profiles
[profile.release]
# Optimize for size
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"
strip = true
# WASM-specific optimizations
[profile.release.package."*"]
opt-level = "z"
lto = true
codegen-units = 1
```
### Bundle Analysis
```rust
// src/utils/bundle_analyzer.rs
use wasm_bindgen::prelude::*;
use web_sys::*;
pub struct BundleAnalyzer;
impl BundleAnalyzer {
/// Analyze current bundle size and performance
pub fn analyze() -> BundleReport {
let performance = window().unwrap().performance().unwrap();
let navigation = performance.get_entries_by_type("navigation").get(0).unwrap();
let navigation_timing = navigation.dyn_into::<PerformanceNavigationTiming>().unwrap();
BundleReport {
load_time: navigation_timing.load_event_end() - navigation_timing.load_event_start(),
dom_content_loaded: navigation_timing.dom_content_loaded_event_end() - navigation_timing.dom_content_loaded_event_start(),
first_paint: Self::get_first_paint_time(),
memory_usage: Self::get_memory_usage(),
}
}
fn get_first_paint_time() -> f64 {
let performance = window().unwrap().performance().unwrap();
let paint_entries = performance.get_entries_by_name("first-paint");
if paint_entries.length() > 0 {
paint_entries.get(0).unwrap().start_time()
} else {
0.0
}
}
fn get_memory_usage() -> Option<f64> {
// Memory API is not available in all browsers
if let Ok(memory) = js_sys::Reflect::get(&window().unwrap(), &"memory".into()) {
if let Ok(used) = js_sys::Reflect::get(&memory, &"usedJSHeapSize".into()) {
return used.as_f64();
}
}
None
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct BundleReport {
pub load_time: f64,
pub dom_content_loaded: f64,
pub first_paint: f64,
pub memory_usage: Option<f64>,
}
```
## 🚀 Performance Targets
### Bundle Size Targets
| Component Set | Target Size | Current Size | Status |
|---------------|-------------|--------------|--------|
| Essential (Button, Input, Label, Card) | < 50KB | TBD | 🟡 Pending |
| Extended (+ Dialog, Popover, Tooltip) | < 100KB | TBD | 🟡 Pending |
| Advanced (+ Table, Calendar, Form) | < 200KB | TBD | 🟡 Pending |
| All Components | < 500KB | TBD | 🟡 Pending |
### Performance Targets
| Metric | Target | Measurement |
|--------|--------|-------------|
| Initial Load Time | < 100ms | Performance API |
| First Paint | < 50ms | Performance API |
| Component Render Time | < 10ms | Custom timing |
| Memory Usage | < 10MB | Memory API |
| Bundle Parse Time | < 20ms | Performance API |
## 📋 Implementation Checklist
### Phase 1: Core Package Setup
- [ ] Create package structure
- [ ] Configure Cargo.toml with minimal dependencies
- [ ] Implement basic component architecture
- [ ] Set up WASM-specific utilities
- [ ] Create basic test suite
### Phase 2: Essential Components
- [ ] Implement Button component
- [ ] Implement Input component
- [ ] Implement Label component
- [ ] Implement Card component
- [ ] Add component tests
- [ ] Optimize bundle size
### Phase 3: Extended Components
- [ ] Implement Dialog component
- [ ] Implement Popover component
- [ ] Implement Tooltip component
- [ ] Add theme management
- [ ] Performance optimization
### Phase 4: Advanced Features
- [ ] Implement remaining components
- [ ] Add animation support
- [ ] Accessibility features
- [ ] Documentation and examples
- [ ] Performance benchmarking
## 🎯 Benefits
1. **Minimal Bundle Size:** Only WASM-compatible dependencies
2. **Optimized Performance:** WASM-specific optimizations
3. **Simplified Dependencies:** No conditional compilation complexity
4. **Faster Build Times:** Fewer dependencies to compile
5. **Better Tree Shaking:** Unused code elimination
6. **WASM-First Design:** Optimized for WebAssembly from the ground up
## ⚠️ Limitations
1. **No Native Support:** Cannot be used in native Rust applications
2. **Limited Testing:** No access to native testing frameworks
3. **File System Access:** No file system operations
4. **Threading Limitations:** Limited to single-threaded execution
5. **Memory Constraints:** Browser memory limitations
---
**Next Steps:**
1. Create the package structure
2. Implement essential components
3. Set up WASM-specific testing
4. Optimize bundle size and performance
5. Create documentation and examples

View File

@@ -0,0 +1,338 @@
# WASM Compatibility Remediation Summary
## Complete Solution for leptos-shadcn-ui WASM Support
**Document Version:** 1.0
**Date:** 2025-01-27
**Status:** IMPLEMENTATION READY
---
## 🎯 Executive Summary
This document provides a comprehensive solution to the WASM compatibility issues in leptos-shadcn-ui, specifically addressing the `mio`, `uuid`, and `rustc-serialize` dependency conflicts. We present three strategic approaches with detailed implementation plans.
## 📊 Current Status Analysis
### ✅ What's Working
- **Core Components:** Button, Input, Card, Label are WASM-compatible
- **Working Demos:** `standalone-demo/` and `examples/leptos/` compile for WASM
- **Proper Dependencies:** WASM demos use correct UUID and getrandom features
### ❌ What's Broken
- **test-utils Package:** Missing `"js"` feature in UUID dependency
- **Non-WASM Dependencies:** `proptest` and `tempfile` incompatible with WASM
- **Mixed Architecture:** No conditional compilation for different targets
## 🚀 Three Strategic Solutions
### Solution 1: Fix Existing Package (RECOMMENDED)
**Priority:** HIGH | **Effort:** MEDIUM | **Risk:** LOW
**Approach:** Fix the `packages/test-utils/Cargo.toml` and add conditional compilation
**Key Changes:**
```toml
# Fix UUID dependency
uuid = { version = "1.0", features = ["v4", "js"] }
# Add conditional dependencies
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen-futures = "0.4"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
proptest = "1.4"
tempfile = "3.0"
```
**Benefits:**
- ✅ Single codebase for both platforms
- ✅ Minimal breaking changes
- ✅ Maintains existing functionality
- ✅ Quick implementation
**Implementation Time:** 1-2 weeks
### Solution 2: WASM-Only Minimal Package
**Priority:** MEDIUM | **Effort:** HIGH | **Risk:** MEDIUM
**Approach:** Create dedicated `leptos-shadcn-ui-wasm` package
**Key Features:**
- Minimal WASM-only dependencies
- Optimized bundle size (< 50KB for essential components)
- WASM-specific utilities and optimizations
- No conditional compilation complexity
**Benefits:**
- Smallest possible bundle size
- WASM-optimized from ground up
- No native compatibility concerns
- Faster build times
**Implementation Time:** 3-4 weeks
### Solution 3: Full Conditional Compilation
**Priority:** HIGH | **Effort:** HIGH | **Risk:** LOW
**Approach:** Implement comprehensive conditional compilation across all packages
**Key Features:**
- Target-specific feature flags
- Platform-optimized implementations
- Unified API with conditional backends
- Comprehensive cross-platform testing
**Benefits:**
- Optimal performance on both platforms
- Single codebase maintenance
- Platform-specific optimizations
- Comprehensive testing coverage
**Implementation Time:** 4-6 weeks
## 📋 Recommended Implementation Plan
### Phase 1: Immediate Fix (Week 1)
**Solution 1 - Fix Existing Package**
1. **Fix test-utils dependencies:**
```bash
# Update packages/test-utils/Cargo.toml
uuid = { version = "1.0", features = ["v4", "js"] }
```
2. **Add conditional compilation:**
```toml
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen-futures = "0.4"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
proptest = "1.4"
tempfile = "3.0"
```
3. **Test WASM compilation:**
```bash
cargo check --target wasm32-unknown-unknown
```
### Phase 2: Enhanced Support (Week 2-3)
**Solution 3 - Conditional Compilation**
1. **Implement conditional testing modules**
2. **Add platform-specific utilities**
3. **Create cross-platform test suite**
4. **Update CI/CD for dual-platform testing**
### Phase 3: Optional Optimization (Week 4+)
**Solution 2 - WASM-Only Package**
1. **Create minimal WASM package**
2. **Implement bundle size optimization**
3. **Add WASM-specific performance monitoring**
4. **Create WASM-focused documentation**
## 🛠️ Implementation Details
### Quick Fix Implementation
```toml
# packages/test-utils/Cargo.toml - IMMEDIATE FIX
[package]
name = "shadcn-ui-test-utils"
version = "0.2.0"
[dependencies]
# Core dependencies
wasm-bindgen-test = "0.3"
web-sys = { workspace = true, features = ["console", "Document", "Element", "HtmlElement", "Window", "Performance", "PerformanceTiming"] }
js-sys = "0.3"
console_error_panic_hook = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
# ✅ FIXED: Add "js" feature for WASM compatibility
uuid = { version = "1.0", features = ["v4", "js"] }
# Framework-specific testing
leptos = { workspace = true }
# ✅ ADDED: WASM-compatible random generation
getrandom = { version = "0.2", features = ["js"] }
[features]
default = []
wasm-testing = []
native-testing = []
# Conditional dependencies
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen-futures = "0.4"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
proptest = "1.4"
tempfile = "3.0"
```
### Code Changes Required
```rust
// packages/test-utils/src/dom_testing.rs - Conditional Implementation
use leptos::prelude::*;
use wasm_bindgen_test::*;
// Conditional imports
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_futures::JsFuture;
#[cfg(not(target_arch = "wasm32"))]
use std::fs::File;
use std::io::Write;
impl ComponentTestHarness {
pub fn new() -> Self {
// ✅ FIXED: WASM-compatible UUID generation
let mount_id = format!("test-mount-{}", uuid::Uuid::new_v4().to_string());
Self { mount_point: mount_id }
}
// Conditional testing methods
#[cfg(target_arch = "wasm32")]
pub fn run_wasm_test<F>(&self, test_fn: F)
where
F: FnOnce() + 'static,
{
wasm_bindgen_test_configure!(run_in_browser);
test_fn();
}
#[cfg(not(target_arch = "wasm32"))]
pub fn run_native_test<F>(&self, test_fn: F)
where
F: FnOnce() + 'static,
{
// Native testing implementation
test_fn();
}
}
```
## 🧪 Testing Strategy
### WASM Testing Setup
```bash
# Install WASM target
rustup target add wasm32-unknown-unknown
# Install wasm-pack
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
# Test WASM compilation
cargo check --target wasm32-unknown-unknown
# Run WASM tests
wasm-pack test --headless --firefox
```
### CI/CD Integration
```yaml
# .github/workflows/wasm-tests.yml
name: WASM Compatibility Tests
on: [push, pull_request]
jobs:
wasm-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Rust with WASM target
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: wasm32-unknown-unknown
- name: Test WASM compilation
run: cargo check --target wasm32-unknown-unknown
- name: Test WASM demos
run: |
cd standalone-demo
wasm-pack build --target web
```
## 📊 Success Metrics
### Technical Metrics
- [ ] 100% WASM compilation success rate
- [ ] < 2MB total WASM bundle size
- [ ] < 100ms component initialization time
- [ ] 0 WASM-specific runtime errors
### Quality Metrics
- [ ] All core components work in WASM
- [ ] WASM tests pass in all supported browsers
- [ ] Performance within 10% of native benchmarks
- [ ] Documentation coverage > 90%
## 🚨 Risk Assessment
### High Risk
- **Breaking Changes:** Conditional compilation may require code changes
- **Testing Coverage:** WASM testing infrastructure needs validation
### Medium Risk
- **Performance:** WASM bundle size optimization required
- **Browser Compatibility:** Need to test across all target browsers
### Low Risk
- **Dependency Conflicts:** Well-understood and documented
- **Backward Compatibility:** Native functionality preserved
## 📚 Documentation References
- [WASM Compatibility Remediation Plan](./WASM_COMPATIBILITY_REMEDIATION_PLAN.md)
- [Conditional Compilation Design](./CONDITIONAL_COMPILATION_DESIGN.md)
- [WASM Minimal Version Design](./WASM_MINIMAL_VERSION_DESIGN.md)
- [WebAssembly Rust Book](https://rustwasm.github.io/docs/book/)
- [wasm-bindgen Guide](https://rustwasm.github.io/wasm-bindgen/)
## 🎯 Next Steps
### Immediate Actions (This Week)
1. **Fix test-utils UUID dependency** - Add `"js"` feature
2. **Test WASM compilation** - Verify fix works
3. **Update workspace dependencies** - Ensure consistency
4. **Create WASM test suite** - Basic functionality testing
### Short Term (Next 2 Weeks)
1. **Implement conditional compilation** - Full platform support
2. **Add WASM-specific utilities** - Performance monitoring
3. **Update CI/CD** - Automated WASM testing
4. **Create migration guide** - User documentation
### Long Term (Next Month)
1. **Create WASM-only package** - Optional optimization
2. **Performance optimization** - Bundle size reduction
3. **Comprehensive testing** - Cross-browser validation
4. **Documentation completion** - Full user guides
## 💡 Recommendations
### For Immediate Implementation
**Start with Solution 1 (Fix Existing Package)** - This provides the quickest path to WASM compatibility with minimal risk and effort.
### For Long-term Strategy
**Implement Solution 3 (Conditional Compilation)** - This provides the most comprehensive solution with optimal performance on both platforms.
### For Specialized Use Cases
**Consider Solution 2 (WASM-Only Package)** - This is ideal for projects that only need WASM support and want the smallest possible bundle size.
---
**Status:** **READY FOR IMPLEMENTATION**
**Priority:** 🔥 **HIGH**
**Estimated Completion:** 2-4 weeks depending on chosen approach