How to Bundle Ammo.js with Vite or Webpack
Bundling Ammo.js—the WebAssembly (WASM) port of the Bullet physics engine—often presents challenges for modern build tools due to its large file size and asynchronous WASM loading requirements. This article provides a clear, step-by-step guide to successfully configuring both Vite and Webpack to bundle Ammo.js, ensuring smooth integration and optimal runtime loading in your web applications.
Understanding the Ammo.js Challenge
Ammo.js consists of two main parts: a JavaScript wrapper file
(ammo.js or ammo.wasm.js) and a binary
WebAssembly file (ammo.wasm.wasm). Because the WASM file
must be loaded asynchronously at runtime, standard module bundlers will
fail if they try to bundle the binary directly into the JavaScript chunk
without proper configuration.
To solve this, you must configure your bundler to serve the
.wasm file as a static asset and tell the JavaScript
wrapper where to find it.
Bundling Ammo.js with Vite
Vite is highly efficient but requires explicit instructions to handle Ammo’s WASM binary and dependency optimization.
Step 1: Install or Copy the Files
The most reliable way to use Ammo.js in Vite is to place the build
files directly in your project’s public/ folder.
- Download
ammo.wasm.jsandammo.wasm.wasmfrom the official Ammo.js repository. - Place both files inside your project’s
/publicdirectory (e.g.,/public/js/ammo.wasm.jsand/public/js/ammo.wasm.wasm).
Step 2: Configure Vite
Since the files are in the public folder, they will be
served as static assets. You need to prevent Vite from trying to
optimize or bundle the wrapper. Add this to your
vite.config.js:
import { defineConfig } from 'vite';
export default defineConfig({
optimizeDeps: {
exclude: ['ammo.js']
}
});Step 3: Initialize Ammo in Your Code
To load Ammo, dynamically inject the script or use a global loading helper. In your main JavaScript/TypeScript file:
async function initPhysics() {
return new Promise((resolve) => {
const script = document.createElement('script');
script.src = '/js/ammo.wasm.js';
script.onload = () => {
// Ammo is loaded globally. Now initialize the WASM binary.
window.Ammo().then((AmmoLib) => {
resolve(AmmoLib);
});
};
document.head.appendChild(script);
});
}
initPhysics().then((Ammo) => {
const collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
console.log("Ammo.js initialized successfully!", collisionConfiguration);
});Bundling Ammo.js with Webpack 5
Webpack 5 supports WebAssembly natively, but because Ammo.js uses a custom loading mechanism, you must configure asset modules and Webpack experiments.
Step 1: Install the Webpack Plugins
You need the copy-webpack-plugin to ensure the
WebAssembly binary is copied to your output build folder.
npm install copy-webpack-plugin --save-devStep 2: Configure Webpack
Modify your webpack.config.js to enable WebAssembly
experiments and copy the WASM file to your output directory.
const path = require('path');
const CopyPlugin = require('copy-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
experiments: {
asyncWebAssembly: true,
topLevelAwait: true,
},
plugins: [
new CopyPlugin({
patterns: [
{
from: 'node_modules/ammo.js/builds/ammo.wasm.wasm',
to: '[name][ext]'
},
{
from: 'node_modules/ammo.js/builds/ammo.wasm.js',
to: '[name][ext]'
}
],
}),
],
resolve: {
fallback: {
fs: false,
path: false,
},
},
};Step 3: Initialize Ammo in Webpack
When loading Ammo in a Webpack environment, you must manually point
the Ammo wrapper to the location of the copied .wasm
file.
import AmmoFactory from '../dist/ammo.wasm.js';
async function init() {
const Ammo = await AmmoFactory({
locateFile: (path) => {
if (path.endsWith('.wasm')) {
return './ammo.wasm.wasm'; // Points to the copied file in your dist folder
}
return path;
}
});
const gravity = new Ammo.btVector3(0, -9.81, 0);
console.log("Ammo.js loaded in Webpack. Gravity vector created:", gravity.y());
}
init();