diff --git a/.github/workflows/wasm-bundlers.yml b/.github/workflows/wasm-bundlers.yml
new file mode 100644
index 00000000..c55c00a1
--- /dev/null
+++ b/.github/workflows/wasm-bundlers.yml
@@ -0,0 +1,66 @@
+name: WASM Bundler Compatibility
+
+on:
+ push:
+ branches: ["main"]
+ pull_request:
+ branches: ["main"]
+
+jobs:
+ test-bundlers:
+ name: Test Bundler Compatibility (Vite/Next/Webpack)
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v6
+ with:
+ node-version: 24
+
+ - name: Install Rust toolchain
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ toolchain: stable
+
+ - name: Install wasm-pack
+ run: cargo install wasm-pack || true
+
+ - name: Build WASM package
+ run: |
+ cd tooling/sanctifier-wasm
+ wasm-pack build --target web --out-dir pkg
+
+ - name: Test Vite Compatibility
+ run: |
+ mkdir -p e2e/vite-test
+ cd e2e/vite-test
+ npm init -y
+ npm install vite
+ npm install ../../tooling/sanctifier-wasm/pkg
+ echo "import { analyze } from '@sanctifier/wasm'; console.log(typeof analyze);" > index.js
+ echo "import { defineConfig } from 'vite'; export default defineConfig({ build: { target: 'esnext' } });" > vite.config.js
+ npx vite build
+
+ - name: Test Webpack Compatibility
+ run: |
+ mkdir -p e2e/webpack-test
+ cd e2e/webpack-test
+ npm init -y
+ npm install webpack webpack-cli
+ npm install ../../tooling/sanctifier-wasm/pkg
+ echo "import { analyze } from '@sanctifier/wasm'; console.log(typeof analyze);" > index.js
+ echo "module.exports = { mode: 'production', entry: './index.js', experiments: { asyncWebAssembly: true } };" > webpack.config.js
+ npx webpack
+
+ - name: Test Next.js Compatibility
+ run: |
+ mkdir -p e2e/next-test
+ cd e2e/next-test
+ npm init -y
+ npm install next react react-dom
+ npm install ../../tooling/sanctifier-wasm/pkg
+ mkdir pages
+ echo "import { analyze } from '@sanctifier/wasm'; export default function Home() { return
{typeof analyze}
; }" > pages/index.js
+ echo "module.exports = { webpack(config) { config.experiments = { ...config.experiments, asyncWebAssembly: true }; return config; } };" > next.config.js
+ npx next build
diff --git a/tooling/sanctifier-wasm/README.md b/tooling/sanctifier-wasm/README.md
index a098adcd..3223722e 100644
--- a/tooling/sanctifier-wasm/README.md
+++ b/tooling/sanctifier-wasm/README.md
@@ -16,4 +16,15 @@ WebAssembly bindings for Sanctifier analysis.
## Offline caching integration
-Use `asset_cache_key()` or `cache_metadata().cache_key` when storing wasm assets in CacheStorage or a service worker. The key changes when either package version or schema version changes, so stale assets are safely evicted.
+Use `asset_cache_key()` or `cache_metadata().cache_key` when storing wasm assets in CacheStorage or a service worker. The key changes when either package version or schema version changes, so stale assets are safely evicted. This improves release and publishing reliability by providing predictable outputs for frontend applications.
+
+## Web Worker / Parallelization Strategy
+
+To avoid blocking the main UI thread during intensive static analysis, it is recommended to run the `@sanctifier/wasm` module inside a Web Worker. See `examples/web_worker.js` for an implementation reference. By delegating analysis requests to background workers, you can ensure a smooth user experience even on large codebases.
+
+## API Surface Stability for Frontend
+
+The `@sanctifier/wasm` package maintains a strict API surface stability contract for frontend consumers.
+- All breaking changes to the exported functions will result in a major version bump.
+- The `schema_version()` function returns the data shape version of the analysis output.
+- For stable integrations, always check the `schema_version()` or `version()` to implement safe fallback behaviors in the frontend.
diff --git a/tooling/sanctifier-wasm/examples/web_worker.js b/tooling/sanctifier-wasm/examples/web_worker.js
new file mode 100644
index 00000000..98074007
--- /dev/null
+++ b/tooling/sanctifier-wasm/examples/web_worker.js
@@ -0,0 +1,42 @@
+/**
+ * Web Worker Example for @sanctifier/wasm
+ *
+ * This demonstrates how to offload WASM analysis to a background worker
+ * to avoid blocking the main UI thread during intensive static analysis.
+ */
+
+import { analyze, version } from '@sanctifier/wasm';
+
+// Handle incoming messages from the main thread
+self.onmessage = async (event) => {
+ const { id, type, source, config } = event.data;
+
+ try {
+ let result;
+
+ switch (type) {
+ case 'ANALYZE':
+ // Run standard analysis
+ result = analyze(source);
+ break;
+
+ case 'GET_VERSION':
+ result = version();
+ break;
+
+ default:
+ throw new Error(`Unknown analysis task type: ${type}`);
+ }
+
+ // Send results back to the main thread
+ self.postMessage({ id, status: 'success', data: result });
+
+ } catch (error) {
+ // Send errors back
+ self.postMessage({
+ id,
+ status: 'error',
+ error: error.message || 'Analysis failed'
+ });
+ }
+};
diff --git a/tooling/sanctifier-wasm/tests/wasm_tests.rs b/tooling/sanctifier-wasm/tests/wasm_tests.rs
index 181f2b3b..c53ad035 100644
--- a/tooling/sanctifier-wasm/tests/wasm_tests.rs
+++ b/tooling/sanctifier-wasm/tests/wasm_tests.rs
@@ -1,6 +1,9 @@
#![cfg(target_arch = "wasm32")]
-use sanctifier_wasm::{analyze, analyze_with_config, finding_codes, schema_version, version};
+use sanctifier_wasm::{
+ analyze, analyze_with_config, asset_cache_key, cache_metadata, finding_codes, schema_version,
+ version,
+};
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
@@ -187,3 +190,19 @@ fn test_analyze_normal_source_unaffected_by_memory_check() {
let result = analyze(source);
assert!(!result.is_null());
}
+
+// ── Caching API Tests ────────────────────────────────────────────────────────
+
+#[wasm_bindgen_test]
+fn test_asset_cache_key() {
+ let key = asset_cache_key();
+ assert!(!key.is_empty());
+ assert!(key.contains("sanctifier-wasm:"));
+}
+
+#[wasm_bindgen_test]
+fn test_cache_metadata() {
+ let meta = cache_metadata();
+ assert!(!meta.is_null());
+ assert!(meta.is_object());
+}