5.8 KiB
5.8 KiB
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
// 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:
// 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
[workspace]
members = ["main-app", "about-chunk", "contact-chunk"]
[workspace.dependencies]
yew = "0.21"
wasm-bindgen = "0.2"
Step 3: Build Each Crate Separately
# 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
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)
// 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
// 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:
- Complete UX Pattern: All loading states, error handling, and user feedback
- Correct Architecture: Ready for true chunk splitting when tooling improves
- Development Efficiency: No complex build setup required
- Learning Value: Understand lazy loading patterns without tooling complexity
Migration to True Chunk Splitting
When you're ready for production with true chunk splitting:
- Choose a build strategy (separate crates, Webpack, or Vite)
- Replace simulation with real imports:
// 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?;
- Configure build tools for WASM chunk generation
- Test network behavior to verify chunks load separately
Why This Is Complex
WASM chunk splitting is challenging because:
- Rust Compilation Model: Rust compiles to a single WASM binary by default
- WASM Limitations: WASM modules can't dynamically import other WASM modules natively
- Build Tool Maturity: Most Rust WASM tools don't support chunk splitting yet
- 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.