mirror of
https://github.com/cloud-shuttle/leptos-shadcn-ui.git
synced 2025-12-22 22:00:00 +00:00
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:
559
docs/remediation/CONDITIONAL_COMPILATION_DESIGN.md
Normal file
559
docs/remediation/CONDITIONAL_COMPILATION_DESIGN.md
Normal 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
|
||||
468
docs/remediation/WASM_COMPATIBILITY_REMEDIATION_PLAN.md
Normal file
468
docs/remediation/WASM_COMPATIBILITY_REMEDIATION_PLAN.md
Normal 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
|
||||
776
docs/remediation/WASM_MINIMAL_VERSION_DESIGN.md
Normal file
776
docs/remediation/WASM_MINIMAL_VERSION_DESIGN.md
Normal 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
|
||||
338
docs/remediation/WASM_REMEDIATION_SUMMARY.md
Normal file
338
docs/remediation/WASM_REMEDIATION_SUMMARY.md
Normal 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
|
||||
Reference in New Issue
Block a user