framework/examples/website/TRUE_CHUNK_SPLITTING.md
2025-07-21 00:17:46 +02:00

203 lines
5.8 KiB
Markdown

# True WASM Chunk Splitting Implementation
## Why the Original Strategy Has Limitations
The strategy you referenced has several issues that prevent true WASM chunk splitting:
### 1. **Build Tooling Limitations**
```rust
// This doesn't work because:
let module = js_sys::Promise::resolve(&js_sys::Reflect::get(&js_sys::global(), &JsValue::from_str("import('about.rs')")).unwrap()).await.unwrap();
```
**Problems:**
- `import('about.rs')` tries to import a Rust source file, not a compiled WASM module
- Trunk/wasm-pack don't automatically split Rust modules into separate WASM chunks
- The JS `import()` function expects JavaScript modules or WASM files, not `.rs` files
### 2. **Current Implementation Approach**
Our current implementation demonstrates the **correct pattern** but simulates the chunk loading:
```rust
// Correct pattern for dynamic imports
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = "import")]
fn dynamic_import(module: &str) -> js_sys::Promise;
}
async fn load_about_chunk() -> Result<JsValue, JsValue> {
// This would work if we had separate WASM chunks:
// let promise = dynamic_import("./about_chunk.wasm");
// wasm_bindgen_futures::JsFuture::from(promise).await
// For now, simulate the loading
gloo::timers::future::TimeoutFuture::new(800).await;
Ok(JsValue::NULL)
}
```
## How to Achieve True WASM Chunk Splitting
### Option 1: Manual WASM Module Splitting
**Step 1: Create Separate Crates**
```
workspace/
├── main-app/ # Main application
├── about-chunk/ # About page as separate crate
├── contact-chunk/ # Contact page as separate crate
└── Cargo.toml # Workspace configuration
```
**Step 2: Workspace Cargo.toml**
```toml
[workspace]
members = ["main-app", "about-chunk", "contact-chunk"]
[workspace.dependencies]
yew = "0.21"
wasm-bindgen = "0.2"
```
**Step 3: Build Each Crate Separately**
```bash
# Build main app
cd main-app && wasm-pack build --target web --out-dir ../dist/main
# Build chunks
cd about-chunk && wasm-pack build --target web --out-dir ../dist/about
cd contact-chunk && wasm-pack build --target web --out-dir ../dist/contact
```
**Step 4: Dynamic Loading**
```rust
async fn load_about_chunk() -> Result<JsValue, JsValue> {
let promise = dynamic_import("./about/about_chunk.js");
let module = wasm_bindgen_futures::JsFuture::from(promise).await?;
// Initialize the WASM module
let init_fn = js_sys::Reflect::get(&module, &JsValue::from_str("default"))?;
let init_promise = js_sys::Function::from(init_fn).call0(&JsValue::NULL)?;
wasm_bindgen_futures::JsFuture::from(js_sys::Promise::from(init_promise)).await?;
Ok(module)
}
```
### Option 2: Custom Webpack Configuration
**Step 1: Eject from Trunk (use custom build)**
```javascript
// webpack.config.js
module.exports = {
entry: {
main: './src/main.rs',
about: './src/pages/about.rs',
contact: './src/pages/contact.rs',
},
experiments: {
asyncWebAssembly: true,
},
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
about: {
name: 'about-chunk',
test: /about/,
chunks: 'all',
},
contact: {
name: 'contact-chunk',
test: /contact/,
chunks: 'all',
},
},
},
},
};
```
### Option 3: Vite with WASM Support
**Step 1: Vite Configuration**
```javascript
// vite.config.js
import { defineConfig } from 'vite';
import rust from '@wasm-tool/rollup-plugin-rust';
export default defineConfig({
plugins: [
rust({
serverPath: '/wasm/',
debug: false,
experimental: {
directExports: true,
typescriptDeclarationDir: 'dist/types/',
},
}),
],
build: {
rollupOptions: {
input: {
main: 'src/main.rs',
about: 'src/pages/about.rs',
contact: 'src/pages/contact.rs',
},
},
},
});
```
## Current Implementation Benefits
Our current approach provides:
1. **Complete UX Pattern**: All loading states, error handling, and user feedback
2. **Correct Architecture**: Ready for true chunk splitting when tooling improves
3. **Development Efficiency**: No complex build setup required
4. **Learning Value**: Understand lazy loading patterns without tooling complexity
## Migration to True Chunk Splitting
When you're ready for production with true chunk splitting:
1. **Choose a build strategy** (separate crates, Webpack, or Vite)
2. **Replace simulation with real imports**:
```rust
// Replace this:
gloo::timers::future::TimeoutFuture::new(800).await;
// With this:
let promise = dynamic_import("./about_chunk.wasm");
wasm_bindgen_futures::JsFuture::from(promise).await?;
```
3. **Configure build tools** for WASM chunk generation
4. **Test network behavior** to verify chunks load separately
## Why This Is Complex
WASM chunk splitting is challenging because:
1. **Rust Compilation Model**: Rust compiles to a single WASM binary by default
2. **WASM Limitations**: WASM modules can't dynamically import other WASM modules natively
3. **Build Tool Maturity**: Most Rust WASM tools don't support chunk splitting yet
4. **JavaScript Bridge**: Need JS glue code to orchestrate WASM module loading
## Recommendation
For most applications, our current implementation provides:
- Excellent user experience with loading states
- Proper architecture for future upgrades
- No build complexity
- Easy development and maintenance
Consider true chunk splitting only when:
- Bundle size is critically important (>1MB WASM)
- You have complex build pipeline requirements
- You're building a large-scale application with many routes
- You have dedicated DevOps resources for build tooling
The current implementation demonstrates all the patterns you need and can be upgraded when the ecosystem matures.