From a9edc5dd899b1e38a80aaba7c186f82da2cea229 Mon Sep 17 00:00:00 2001 From: MatiasManevi Date: Thu, 19 Feb 2026 09:40:29 -0300 Subject: [PATCH 01/10] Add publish workflow and minimal npm package for WASM --- .github/workflows/publish-wasm.yml | 69 ++++++++++ wasm/README.npm.md | 209 +++++++++++++++++++++++++++++ wasm/package.json | 3 + wasm/package.npm.json | 38 ++++++ 4 files changed, 319 insertions(+) create mode 100644 .github/workflows/publish-wasm.yml create mode 100644 wasm/README.npm.md create mode 100644 wasm/package.npm.json diff --git a/.github/workflows/publish-wasm.yml b/.github/workflows/publish-wasm.yml new file mode 100644 index 00000000..5ca7e78d --- /dev/null +++ b/.github/workflows/publish-wasm.yml @@ -0,0 +1,69 @@ +name: publish-wasm + +on: + push: + tags: + - '[0-9]*.[0-9]*.[0-9]*' + +jobs: + publish: + name: Publish MuJoCo WASM package + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js for WASM bindings + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Prepare Linux + run: bash ./.github/workflows/build_steps.sh prepare_linux + + - name: Install NPM Dependencies for WASM bindings + run: bash ./.github/workflows/build_steps.sh npm_ci + + - name: Setup Emscripten for WASM bindings + run: bash ./.github/workflows/build_steps.sh setup_emsdk + + - name: Build WASM bindings + run: bash ./.github/workflows/build_steps.sh build_test_wasm + + - name: Prepare clean package manifest in dist + run: | + if [ -f wasm/package.npm.json ]; then + mkdir -p wasm/dist + cp wasm/package.npm.json wasm/dist/package.json + echo "Copied wasm/package.npm.json -> wasm/dist/package.json" + else + echo "No wasm/package.npm.json found; aborting" + exit 1 + fi + + - name: Prepare README for dist + run: | + if [ -f wasm/README.npm.md ]; then + mkdir -p wasm/dist + cp wasm/README.npm.md wasm/dist/README.md + echo "Copied wasm/README.npm.md -> wasm/dist/README.md" + else + echo "No wasm/README.npm.md found; skipping README copy" + fi + + - name: Sync version with tag (dist) + run: | + VERSION=${GITHUB_REF#refs/tags/} + echo "Setting package version to: ${VERSION}" + npm --prefix wasm/dist version "${VERSION}" --no-git-tag-version + + - name: Configure npm auth + env: + NPM_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }} + run: | + echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc + + - name: Preview package (dist) + run: npm pack --dry-run ./wasm/dist + + - name: Publish package (dist) + run: npm publish ./wasm/dist --access public diff --git a/wasm/README.npm.md b/wasm/README.npm.md new file mode 100644 index 00000000..2bb96153 --- /dev/null +++ b/wasm/README.npm.md @@ -0,0 +1,209 @@ +# MuJoCo WASM bindings + +Official WebAssembly (WASM) bindings for the MuJoCo physics engine, compiled from the MuJoCo C/C++ sources into WASM with JS glue (Emscripten + Embind). The package ships prebuilt ESM-ready JS/WASM artifacts and TypeScript types for immediate use. + +**Important**:_These bindings are still a WIP_. + +## Install +```sh +npm install mujoco_wasm +``` + +## Quick start (ESM / TypeScript) + +```ts +import loadMujoco from 'mujoco_wasm'; + +const mujoco = await loadMujoco(); + +const model = mujoco.MjModel.fromXMLString(` + + + + + +`); + +const data = new mujoco.MjData(model); +mujoco.mj_step(model, data); +``` + +## Usage Guide +When interacting with MuJoCo objects through the WASM bindings, it's important to understand how data is accessed. Properties on objects like `MjModel` and `MjData` can expose data in two ways: by copy or by reference. + +### Copy vs. Reference + +#### 1. By Copy (Value-based access) + +Some properties return a copy of the data at the time of access. This is common for complex data structures that need to be marshalled from C++ to JavaScript. + +A key example is `MjData.contact`. When you access `data.contact`, you get a new array containing the contacts at that specific moment in the simulation. If you step the simulation forward, this array will not be updated. You must access `data.contact` again to get the new contact information. + +Example: +```typescript +// Gets contacts at the current time. +const contacts = data.contact; + +// Step the simulation +mujoco.mj_step(model, data); + +// `contacts` is now stale. To get the new contacts, you must access the property again: +const newContacts = data.contact; +``` + +#### 2. By Reference (View-based access) +Many properties, especially large numerical arrays, return a live view directly into the WebAssembly memory. This is highly efficient as it avoids copying large amounts of data. + +A key example is `MjData.qpos` (joint positions). When you get a reference to this array, it points directly to the simulation's state data. Any changes in the simulation (e.g., after a call to `mj_step`) will be immediately reflected in this array. + +```typescript +// `qpos` is a live view into the simulation state. +const qpos = data.qpos; + +console.log(qpos[0]); // Print initial position + +// Step the simulation +mujoco.mj_step(model, data); + +// `qpos` is automatically updated. +console.log(qpos[0]); // Print new position +``` + +### Data Layout: Row-Major Matrices +When a function from the MuJoCo C API returns a matrix (or needs a matrix as input), these are represented in the JavaScript bindings as flat, one-dimensional `TypedArray`'s. The elements are stored in row-major order. + +For example, a 3x10 matrix will be returned as a flat array with 30 elements. The first 10 elements represent the first row, the next 10 represent the second row, and so on. + +Example: Accessing an element at `(row, col)` +```typescript +// A 3x10 matrix stored as a flat array. +const matrix: Float64Array = ...; +const nRows = 3; +const nCols = 10; + +// To access the element at row `i` and column `j`: +const element = matrix[i * nCols + j]; +``` + +### Working with Out Parameters +Many functions in the MuJoCo C API use "out parameters" to return data. This means instead of returning a value, they write the result into one of the arguments passed to them by reference (using pointers). In our JavaScript bindings, you'll need to handle these cases specifically. + +There are two main scenarios you'll encounter: + +#### 1. Array-like Out Parameters +When a function expects a pointer to a primitive type (like `mjtNum*` or `int*`) to write an array of values, you need to pre-allocate memory for the result on the JavaScript side. We provide helper classes for this: `mujoco.Uint8Buffer`, `mujoco.DoubleBuffer`, `mujoco.FloatBuffer`, and `mujoco.IntBuffer`. + +Here's how to use them: + +1. Create a buffer: Instantiate the appropriate buffer class with an initial array of the correct size (e.g., an array of zeros). +2. Call the function: Pass the buffer instance to the function as the out parameter. +3. Access the result: Use the `.getView()` method on the buffer to get a `TypedArray` view of the data written by the C++ function. +4. Free the memory: When you are done with the buffer, you must call the `.delete()` method to free the underlying memory and prevent memory leaks. + +Example: Rotating a vector + +The function `mju_rotVecQuat` rotates a vector `vec` by a quaternion `quat` and stores the result in the `res` out parameter. + +```typescript +// Create a buffer to hold the 3D vector result. +const res = new mujoco.DoubleBuffer([0, 0, 0]); + +const vec = [1, 0, 0]; +const quat = [0.707, 0, 0, 0.707]; // 90-degree rotation around z-axis + +try { + // Call the function with the buffer as the out parameter. + mujoco.mju_rotVecQuat(res, vec, quat); + + // Get the result as a Float64Array. + const resultView = res.getView(); + console.log(resultView); // Expected: approximately [0, 1, 0] + +} finally { + // IMPORTANT: Free the memory allocated for the buffer. + res.delete(); +} +``` + +#### 2. Struct Out Parameters (e.g., mjvCamera*, mjvScene*) +When a function modifies a struct passed by pointer, you should pass an instance of the corresponding JavaScript wrapper class. The underlying C++ struct will be modified in place. + +Example: Updating a scene + +The function `mjv_updateScene` populates an `mjvScene` object with information from `mjModel` and `mjData`. +```typescript +// Create instances of the necessary structs. +const model = mujoco.MjModel.loadFromXML(xmlContent); +const data = new mujoco.MjData(model); +const scene = new mujoco.MjvScene(model, 1000); +const option = new mujoco.MjvOption(); +const perturb = new mujoco.MjvPerturb(); +const camera = new mujoco.MjvCamera(); + +// ... (step simulation, etc.) + +// Update the scene. The 'scene' object is modified by the function. +mujoco.mjv_updateScene( + model, + data, + option, + perturb, + camera, + mujoco.mjtCatBit.mjCAT_ALL.value, + scene +); + +console.log('Number of geoms in scene:', scene.ngeom); + +// Remember to delete all created objects when they are no longer needed. +scene.delete(); +camera.delete(); +perturb.delete(); +option.delete(); +data.delete(); +model.delete(); +``` + +As with buffers, you are responsible for managing the memory of these struct instances and must call `.delete()` on them when you are finished. + +### Enums +Access via `.value`: +```javascript +mujoco.mjtDisableBit.mjDSBL_CLAMPCTRL.value +``` + +### Constants +Scalar constants will be accessed the same way they are on python, simply: + +```javascript +mujoco.mjNEQDATA +``` + +Due to Embind limitations, more complex constants that are not scalar, but are represented in more dimensions are exposed as functions. E.g to use `mujoco.mjFRAMESTRING` you will need to call a function: + +```javascript +mujoco.get_mjFRAMESTRING() +``` + +This will return a javascript array representation of the values in MuJoCo `mjFRAMESTRING`. + +## Notes +The package is ESM (`type: module`) and ships TypeScript types. + +Ensure your bundler or dev server serves the `.wasm` asset from +`node_modules/mujoco_wasm/` at runtime. + +### Development +For detailed build instructions see the repository [README](https://github.com/google-deepmind/mujoco/tree/main/wasm#mujoco-javascript-bindings) (Emscripten toolchain, Embind bindings, producing the .wasm artifact, and targets for Node and browser). + +## Versioning +Package versions follow the official MuJoCo release versions. +For example: + +| npm version | MuJoCo version | +|-------------|----------------| +| 3.5.0 | 3.5.0 | + +--- + +For full engine documentation, see: https://mujoco.readthedocs.io diff --git a/wasm/package.json b/wasm/package.json index 6384da1b..609a5178 100644 --- a/wasm/package.json +++ b/wasm/package.json @@ -7,6 +7,9 @@ "lib": "lib", "test": "tests" }, + "files": [ + "dist" + ], "scripts": { "test": "node --loader ts-node/esm --trace-warnings tests/run_tests.mjs", "benchmark": "node --loader ts-node/esm --trace-warnings tests/run_benchmarks.mjs", diff --git a/wasm/package.npm.json b/wasm/package.npm.json new file mode 100644 index 00000000..75f8d9a0 --- /dev/null +++ b/wasm/package.npm.json @@ -0,0 +1,38 @@ +{ + "name": "mujoco_wasm", + "version": "0.0.0", + "description": "MuJoCo WASM bindings", + "repository": { + "type": "git", + "url": "https://github.com/google-deepmind/mujoco.git", + "directory": "wasm" + }, + "homepage": "https://github.com/google-deepmind/mujoco/tree/main/wasm", + "bugs": { + "url": "https://github.com/google-deepmind/mujoco/issues" + }, + "keywords": [ + "mujoco", + "physics", + "wasm", + "webassembly", + "bindings", + "simulation", + "robotics", + "emscripten", + "javascript", + "typescript" + ], + "main": "mujoco_wasm.js", + "types": "mujoco_wasm.d.ts", + "files": [ + "mujoco_wasm.js", + "mujoco_wasm.d.ts", + "mujoco_wasm.wasm", + "mujoco_wasm.wasm.map", + "README.md" + ], + "author": "Google DeepMind", + "license": "Apache-2.0", + "type": "module" +} From c6564b1f792452016ee753d13336aa3f0cd63962 Mon Sep 17 00:00:00 2001 From: MatiasManevi Date: Mon, 23 Feb 2026 17:32:44 -0300 Subject: [PATCH 02/10] Change package name and artefacts --- wasm/CMakeLists.txt | 10 ++++++++-- wasm/README.npm.md | 7 +++---- wasm/codegen/tests/enums_test_generator.py | 4 ++-- wasm/demo_app/app.ts | 2 +- wasm/package-lock.json | 4 ++-- wasm/package.json | 2 +- wasm/package.npm.json | 14 +++++++------- wasm/tests/bindings_test.ts | 4 ++-- wasm/tests/enums_test.ts | 4 ++-- wasm/tests/sandbox/main.ts | 4 ++-- 10 files changed, 30 insertions(+), 25 deletions(-) diff --git a/wasm/CMakeLists.txt b/wasm/CMakeLists.txt index 2f2c77a8..0adcf7a0 100644 --- a/wasm/CMakeLists.txt +++ b/wasm/CMakeLists.txt @@ -46,13 +46,19 @@ set(EMCC_LINKER_FLAGS "-s DISABLE_EXCEPTION_CATCHING=0" "-gsource-map" "-g" - "--emit-tsd mujoco_wasm.d.ts" + "--emit-tsd mujoco.d.ts" ) string (REPLACE ";" " " EMCC_LINKER_FLAGS_STR "${EMCC_LINKER_FLAGS}") add_executable(mujoco_wasm ${MUJOCO_WASM_FILES}) -set_target_properties(mujoco_wasm PROPERTIES LINK_FLAGS "${EMCC_LINKER_FLAGS_STR}") +# Keep the internal target name distinct to avoid colliding with the native +# `mujoco` library target, but emit artifacts named `mujoco.*` by setting the +# output name. Also apply the emscripten linker flags to the wasm target. +set_target_properties(mujoco_wasm PROPERTIES + LINK_FLAGS "${EMCC_LINKER_FLAGS_STR}" + OUTPUT_NAME "mujoco" +) target_link_libraries(mujoco_wasm ccd lodepng mujoco tinyxml2 qhullstatic_r) diff --git a/wasm/README.npm.md b/wasm/README.npm.md index 2bb96153..ce9bb70c 100644 --- a/wasm/README.npm.md +++ b/wasm/README.npm.md @@ -6,13 +6,13 @@ Official WebAssembly (WASM) bindings for the MuJoCo physics engine, compiled fro ## Install ```sh -npm install mujoco_wasm +npm install mujoco ``` ## Quick start (ESM / TypeScript) ```ts -import loadMujoco from 'mujoco_wasm'; +import loadMujoco from 'mujoco'; const mujoco = await loadMujoco(); @@ -190,8 +190,7 @@ This will return a javascript array representation of the values in MuJoCo `mjFR ## Notes The package is ESM (`type: module`) and ships TypeScript types. -Ensure your bundler or dev server serves the `.wasm` asset from -`node_modules/mujoco_wasm/` at runtime. +Ensure your bundler or dev server serves the `.wasm` asset at runtime. ### Development For detailed build instructions see the repository [README](https://github.com/google-deepmind/mujoco/tree/main/wasm#mujoco-javascript-bindings) (Emscripten toolchain, Embind bindings, producing the .wasm artifact, and targets for Node and browser). diff --git a/wasm/codegen/tests/enums_test_generator.py b/wasm/codegen/tests/enums_test_generator.py index f380aabc..862b15e3 100644 --- a/wasm/codegen/tests/enums_test_generator.py +++ b/wasm/codegen/tests/enums_test_generator.py @@ -26,8 +26,8 @@ def generate_typescript_enum_tests(): output = textwrap.dedent("""\ import 'jasmine'; - import { MainModule } from "../dist/mujoco_wasm" - import loadMujoco from "../dist/mujoco_wasm.js" + import { MainModule } from "../dist/mujoco" + import loadMujoco from "../dist/mujoco.js" let mujoco: MainModule; diff --git a/wasm/demo_app/app.ts b/wasm/demo_app/app.ts index 3e04f42c..9e5a1a9f 100644 --- a/wasm/demo_app/app.ts +++ b/wasm/demo_app/app.ts @@ -14,7 +14,7 @@ import * as THREE from "three" import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js" -import loadMujoco from "../dist/mujoco_wasm.js" +import loadMujoco from "../dist/mujoco.js" declare function loadMujoco(): Promise; diff --git a/wasm/package-lock.json b/wasm/package-lock.json index 859f393d..a21344d4 100644 --- a/wasm/package-lock.json +++ b/wasm/package-lock.json @@ -1,11 +1,11 @@ { - "name": "mujoco_wasm", + "name": "mujoco", "version": "1.0.0-alpha.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "mujoco_wasm", + "name": "mujoco", "version": "1.0.0-alpha.1", "license": "Apache-2.0", "devDependencies": { diff --git a/wasm/package.json b/wasm/package.json index 609a5178..37087d22 100644 --- a/wasm/package.json +++ b/wasm/package.json @@ -1,5 +1,5 @@ { - "name": "mujoco_wasm", + "name": "mujoco", "version": "1.0.0-alpha.1", "description": "MuJoCo JavaScript Bindings", "directories": { diff --git a/wasm/package.npm.json b/wasm/package.npm.json index 75f8d9a0..82b8dba7 100644 --- a/wasm/package.npm.json +++ b/wasm/package.npm.json @@ -1,5 +1,5 @@ { - "name": "mujoco_wasm", + "name": "mujoco", "version": "0.0.0", "description": "MuJoCo WASM bindings", "repository": { @@ -23,13 +23,13 @@ "javascript", "typescript" ], - "main": "mujoco_wasm.js", - "types": "mujoco_wasm.d.ts", + "main": "mujoco.js", + "types": "mujoco.d.ts", "files": [ - "mujoco_wasm.js", - "mujoco_wasm.d.ts", - "mujoco_wasm.wasm", - "mujoco_wasm.wasm.map", + "mujoco.js", + "mujoco.d.ts", + "mujoco.wasm", + "mujoco.wasm.map", "README.md" ], "author": "Google DeepMind", diff --git a/wasm/tests/bindings_test.ts b/wasm/tests/bindings_test.ts index 8fe9afe0..a357637c 100644 --- a/wasm/tests/bindings_test.ts +++ b/wasm/tests/bindings_test.ts @@ -17,9 +17,9 @@ import 'jasmine'; import {MainModule, MjContact, MjContactVec, MjData, MjLROpt, MjModel, MjOption, MjsGeom, MjSolverStat, MjSpec, MjStatistic, MjTimerStat, MjvCamera, MjvFigure, MjvGeom, MjvGLCamera, MjvLight, MjvOption, MjvPerturb, MjvScene, -MjWarningStat, MjVFS, Uint8Buffer} from '../dist/mujoco_wasm.js'; +MjWarningStat, MjVFS, Uint8Buffer} from '../dist/mujoco.js'; -import loadMujoco from '../dist/mujoco_wasm.js' +import loadMujoco from '../dist/mujoco.js' function assertExists(value: T | null | undefined, message?: string): asserts value is T { diff --git a/wasm/tests/enums_test.ts b/wasm/tests/enums_test.ts index 7c966e92..96300469 100644 --- a/wasm/tests/enums_test.ts +++ b/wasm/tests/enums_test.ts @@ -14,8 +14,8 @@ import 'jasmine'; -import { MainModule } from "../dist/mujoco_wasm" -import loadMujoco from "../dist/mujoco_wasm.js" +import { MainModule } from "../dist/mujoco" +import loadMujoco from "../dist/mujoco.js" let mujoco: MainModule; diff --git a/wasm/tests/sandbox/main.ts b/wasm/tests/sandbox/main.ts index 79702e5f..60fc5e95 100644 --- a/wasm/tests/sandbox/main.ts +++ b/wasm/tests/sandbox/main.ts @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { MainModule, MjData, MjModel } from "../../dist/mujoco_wasm" -import loadMujoco from "../../dist/mujoco_wasm.js" +import { MainModule, MjData, MjModel } from "../../dist/mujoco" +import loadMujoco from "../../dist/mujoco.js" declare function loadMujoco(): Promise; From 12c2e98a6114ded9c6d83a6055bb116b05e8dc8a Mon Sep 17 00:00:00 2001 From: MatiasManevi Date: Mon, 23 Feb 2026 17:42:44 -0300 Subject: [PATCH 03/10] Modify npm package keywords --- wasm/package.npm.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wasm/package.npm.json b/wasm/package.npm.json index 82b8dba7..4554b2cc 100644 --- a/wasm/package.npm.json +++ b/wasm/package.npm.json @@ -15,8 +15,9 @@ "mujoco", "physics", "wasm", + "google", + "deepmind", "webassembly", - "bindings", "simulation", "robotics", "emscripten", From f989d40d8943df416848dd44c36ac7cadcca94a6 Mon Sep 17 00:00:00 2001 From: MatiasManevi Date: Mon, 23 Feb 2026 17:55:15 -0300 Subject: [PATCH 04/10] Merge wasm readme for package --- .github/workflows/publish-wasm.yml | 10 +- wasm/README.md | 194 +++++++++++++++++++++++++++ wasm/README.npm.md | 208 ----------------------------- 3 files changed, 197 insertions(+), 215 deletions(-) delete mode 100644 wasm/README.npm.md diff --git a/.github/workflows/publish-wasm.yml b/.github/workflows/publish-wasm.yml index 5ca7e78d..0e481ead 100644 --- a/.github/workflows/publish-wasm.yml +++ b/.github/workflows/publish-wasm.yml @@ -42,13 +42,9 @@ jobs: - name: Prepare README for dist run: | - if [ -f wasm/README.npm.md ]; then - mkdir -p wasm/dist - cp wasm/README.npm.md wasm/dist/README.md - echo "Copied wasm/README.npm.md -> wasm/dist/README.md" - else - echo "No wasm/README.npm.md found; skipping README copy" - fi + mkdir -p wasm/dist + cp wasm/README.md wasm/dist/README.md + - name: Sync version with tag (dist) run: | diff --git a/wasm/README.md b/wasm/README.md index cdda7b23..651deaf9 100644 --- a/wasm/README.md +++ b/wasm/README.md @@ -119,6 +119,192 @@ joint using `data.jnt('myjoint')`. For more details and examples of how to use named access, please refer to the [named access tests](tests/bindings_test.ts#L1876-L2378) and [documentation](https://mujoco.readthedocs.io/en/stable/python.html#named-access). +## Usage Guide +When interacting with MuJoCo objects through the WASM bindings, it's important to understand how data is accessed. Properties on objects like `MjModel` and `MjData` can expose data in two ways: by copy or by reference. + +### Install +```sh +npm install mujoco +``` + +```ts +import loadMujoco from 'mujoco'; + +const mujoco = await loadMujoco(); + +const model = mujoco.MjModel.fromXMLString(` + + + + + +`); + +const data = new mujoco.MjData(model); +mujoco.mj_step(model, data); +``` + +### Copy vs. Reference + +#### 1. By Copy (Value-based access) + +Some properties return a copy of the data at the time of access. This is common for complex data structures that need to be marshalled from C++ to JavaScript. + +A key example is `MjData.contact`. When you access `data.contact`, you get a new array containing the contacts at that specific moment in the simulation. If you step the simulation forward, this array will not be updated. You must access `data.contact` again to get the new contact information. + +Example: +```typescript +// Gets contacts at the current time. +const contacts = data.contact; + +// Step the simulation +mujoco.mj_step(model, data); + +// `contacts` is now stale. To get the new contacts, you must access the property again: +const newContacts = data.contact; +``` + +#### 2. By Reference (View-based access) +Many properties, especially large numerical arrays, return a live view directly into the WebAssembly memory. This is highly efficient as it avoids copying large amounts of data. + +A key example is `MjData.qpos` (joint positions). When you get a reference to this array, it points directly to the simulation's state data. Any changes in the simulation (e.g., after a call to `mj_step`) will be immediately reflected in this array. + +```typescript +// `qpos` is a live view into the simulation state. +const qpos = data.qpos; + +console.log(qpos[0]); // Print initial position + +// Step the simulation +mujoco.mj_step(model, data); + +// `qpos` is automatically updated. +console.log(qpos[0]); // Print new position +``` + +### Data Layout: Row-Major Matrices +When a function from the MuJoCo C API returns a matrix (or needs a matrix as input), these are represented in the JavaScript bindings as flat, one-dimensional `TypedArray`'s. The elements are stored in row-major order. + +For example, a 3x10 matrix will be returned as a flat array with 30 elements. The first 10 elements represent the first row, the next 10 represent the second row, and so on. + +Example: Accessing an element at `(row, col)` +```typescript +// A 3x10 matrix stored as a flat array. +const matrix: Float64Array = ...; +const nRows = 3; +const nCols = 10; + +// To access the element at row `i` and column `j`: +const element = matrix[i * nCols + j]; +``` + +### Working with Out Parameters +Many functions in the MuJoCo C API use "out parameters" to return data. This means instead of returning a value, they write the result into one of the arguments passed to them by reference (using pointers). In our JavaScript bindings, you'll need to handle these cases specifically. + +There are two main scenarios you'll encounter: + +#### 1. Array-like Out Parameters +When a function expects a pointer to a primitive type (like `mjtNum*` or `int*`) to write an array of values, you need to pre-allocate memory for the result on the JavaScript side. We provide helper classes for this: `mujoco.Uint8Buffer`, `mujoco.DoubleBuffer`, `mujoco.FloatBuffer`, and `mujoco.IntBuffer`. + +Here's how to use them: + +1. Create a buffer: Instantiate the appropriate buffer class with an initial array of the correct size (e.g., an array of zeros). +2. Call the function: Pass the buffer instance to the function as the out parameter. +3. Access the result: Use the `.getView()` method on the buffer to get a `TypedArray` view of the data written by the C++ function. +4. Free the memory: When you are done with the buffer, you must call the `.delete()` method to free the underlying memory and prevent memory leaks. + +Example: Rotating a vector + +The function `mju_rotVecQuat` rotates a vector `vec` by a quaternion `quat` and stores the result in the `res` out parameter. + +```typescript +// Create a buffer to hold the 3D vector result. +const res = new mujoco.DoubleBuffer([0, 0, 0]); + +const vec = [1, 0, 0]; +const quat = [0.707, 0, 0, 0.707]; // 90-degree rotation around z-axis + +try { + // Call the function with the buffer as the out parameter. + mujoco.mju_rotVecQuat(res, vec, quat); + + // Get the result as a Float64Array. + const resultView = res.getView(); + console.log(resultView); // Expected: approximately [0, 1, 0] + +} finally { + // IMPORTANT: Free the memory allocated for the buffer. + res.delete(); +} +``` + +#### 2. Struct Out Parameters (e.g., mjvCamera*, mjvScene*) +When a function modifies a struct passed by pointer, you should pass an instance of the corresponding JavaScript wrapper class. The underlying C++ struct will be modified in place. + +Example: Updating a scene + +The function `mjv_updateScene` populates an `mjvScene` object with information from `mjModel` and `mjData`. +```typescript +// Create instances of the necessary structs. +const model = mujoco.MjModel.loadFromXML(xmlContent); +const data = new mujoco.MjData(model); +const scene = new mujoco.MjvScene(model, 1000); +const option = new mujoco.MjvOption(); +const perturb = new mujoco.MjvPerturb(); +const camera = new mujoco.MjvCamera(); + +// ... (step simulation, etc.) + +// Update the scene. The 'scene' object is modified by the function. +mujoco.mjv_updateScene( + model, + data, + option, + perturb, + camera, + mujoco.mjtCatBit.mjCAT_ALL.value, + scene +); + +console.log('Number of geoms in scene:', scene.ngeom); + +// Remember to delete all created objects when they are no longer needed. +scene.delete(); +camera.delete(); +perturb.delete(); +option.delete(); +data.delete(); +model.delete(); +``` + +As with buffers, you are responsible for managing the memory of these struct instances and must call `.delete()` on them when you are finished. + +### Enums +Access via `.value`: +```javascript +mujoco.mjtDisableBit.mjDSBL_CLAMPCTRL.value +``` + +### Constants +Scalar constants will be accessed the same way they are on python, simply: + +```javascript +mujoco.mjNEQDATA +``` + +Due to Embind limitations, more complex constants that are not scalar, but are represented in more dimensions are exposed as functions. E.g to use `mujoco.mjFRAMESTRING` you will need to call a function: + +```javascript +mujoco.get_mjFRAMESTRING() +``` + +This will return a javascript array representation of the values in MuJoCo `mjFRAMESTRING`. + +### About assets +The package is ESM (`type: module`) and ships TypeScript types. + +Ensure your bundler or dev server serves the `.wasm` asset at runtime. + ## Development In order to change the bindings you will need to change the [`bindings.cc`](codegen/generated/bindings.cc) @@ -183,6 +369,14 @@ method to do this only works internally at Google, but it should be possible to replicate the experience with open-source tooling — community suggestions are welcome! +## Versioning +Package versions follow the official MuJoCo release versions. +For example: + +| npm version | MuJoCo version | +|-------------|----------------| +| 3.5.0 | 3.5.0 | + ## Future Work 1. **Bind all useful APIs.** diff --git a/wasm/README.npm.md b/wasm/README.npm.md deleted file mode 100644 index ce9bb70c..00000000 --- a/wasm/README.npm.md +++ /dev/null @@ -1,208 +0,0 @@ -# MuJoCo WASM bindings - -Official WebAssembly (WASM) bindings for the MuJoCo physics engine, compiled from the MuJoCo C/C++ sources into WASM with JS glue (Emscripten + Embind). The package ships prebuilt ESM-ready JS/WASM artifacts and TypeScript types for immediate use. - -**Important**:_These bindings are still a WIP_. - -## Install -```sh -npm install mujoco -``` - -## Quick start (ESM / TypeScript) - -```ts -import loadMujoco from 'mujoco'; - -const mujoco = await loadMujoco(); - -const model = mujoco.MjModel.fromXMLString(` - - - - - -`); - -const data = new mujoco.MjData(model); -mujoco.mj_step(model, data); -``` - -## Usage Guide -When interacting with MuJoCo objects through the WASM bindings, it's important to understand how data is accessed. Properties on objects like `MjModel` and `MjData` can expose data in two ways: by copy or by reference. - -### Copy vs. Reference - -#### 1. By Copy (Value-based access) - -Some properties return a copy of the data at the time of access. This is common for complex data structures that need to be marshalled from C++ to JavaScript. - -A key example is `MjData.contact`. When you access `data.contact`, you get a new array containing the contacts at that specific moment in the simulation. If you step the simulation forward, this array will not be updated. You must access `data.contact` again to get the new contact information. - -Example: -```typescript -// Gets contacts at the current time. -const contacts = data.contact; - -// Step the simulation -mujoco.mj_step(model, data); - -// `contacts` is now stale. To get the new contacts, you must access the property again: -const newContacts = data.contact; -``` - -#### 2. By Reference (View-based access) -Many properties, especially large numerical arrays, return a live view directly into the WebAssembly memory. This is highly efficient as it avoids copying large amounts of data. - -A key example is `MjData.qpos` (joint positions). When you get a reference to this array, it points directly to the simulation's state data. Any changes in the simulation (e.g., after a call to `mj_step`) will be immediately reflected in this array. - -```typescript -// `qpos` is a live view into the simulation state. -const qpos = data.qpos; - -console.log(qpos[0]); // Print initial position - -// Step the simulation -mujoco.mj_step(model, data); - -// `qpos` is automatically updated. -console.log(qpos[0]); // Print new position -``` - -### Data Layout: Row-Major Matrices -When a function from the MuJoCo C API returns a matrix (or needs a matrix as input), these are represented in the JavaScript bindings as flat, one-dimensional `TypedArray`'s. The elements are stored in row-major order. - -For example, a 3x10 matrix will be returned as a flat array with 30 elements. The first 10 elements represent the first row, the next 10 represent the second row, and so on. - -Example: Accessing an element at `(row, col)` -```typescript -// A 3x10 matrix stored as a flat array. -const matrix: Float64Array = ...; -const nRows = 3; -const nCols = 10; - -// To access the element at row `i` and column `j`: -const element = matrix[i * nCols + j]; -``` - -### Working with Out Parameters -Many functions in the MuJoCo C API use "out parameters" to return data. This means instead of returning a value, they write the result into one of the arguments passed to them by reference (using pointers). In our JavaScript bindings, you'll need to handle these cases specifically. - -There are two main scenarios you'll encounter: - -#### 1. Array-like Out Parameters -When a function expects a pointer to a primitive type (like `mjtNum*` or `int*`) to write an array of values, you need to pre-allocate memory for the result on the JavaScript side. We provide helper classes for this: `mujoco.Uint8Buffer`, `mujoco.DoubleBuffer`, `mujoco.FloatBuffer`, and `mujoco.IntBuffer`. - -Here's how to use them: - -1. Create a buffer: Instantiate the appropriate buffer class with an initial array of the correct size (e.g., an array of zeros). -2. Call the function: Pass the buffer instance to the function as the out parameter. -3. Access the result: Use the `.getView()` method on the buffer to get a `TypedArray` view of the data written by the C++ function. -4. Free the memory: When you are done with the buffer, you must call the `.delete()` method to free the underlying memory and prevent memory leaks. - -Example: Rotating a vector - -The function `mju_rotVecQuat` rotates a vector `vec` by a quaternion `quat` and stores the result in the `res` out parameter. - -```typescript -// Create a buffer to hold the 3D vector result. -const res = new mujoco.DoubleBuffer([0, 0, 0]); - -const vec = [1, 0, 0]; -const quat = [0.707, 0, 0, 0.707]; // 90-degree rotation around z-axis - -try { - // Call the function with the buffer as the out parameter. - mujoco.mju_rotVecQuat(res, vec, quat); - - // Get the result as a Float64Array. - const resultView = res.getView(); - console.log(resultView); // Expected: approximately [0, 1, 0] - -} finally { - // IMPORTANT: Free the memory allocated for the buffer. - res.delete(); -} -``` - -#### 2. Struct Out Parameters (e.g., mjvCamera*, mjvScene*) -When a function modifies a struct passed by pointer, you should pass an instance of the corresponding JavaScript wrapper class. The underlying C++ struct will be modified in place. - -Example: Updating a scene - -The function `mjv_updateScene` populates an `mjvScene` object with information from `mjModel` and `mjData`. -```typescript -// Create instances of the necessary structs. -const model = mujoco.MjModel.loadFromXML(xmlContent); -const data = new mujoco.MjData(model); -const scene = new mujoco.MjvScene(model, 1000); -const option = new mujoco.MjvOption(); -const perturb = new mujoco.MjvPerturb(); -const camera = new mujoco.MjvCamera(); - -// ... (step simulation, etc.) - -// Update the scene. The 'scene' object is modified by the function. -mujoco.mjv_updateScene( - model, - data, - option, - perturb, - camera, - mujoco.mjtCatBit.mjCAT_ALL.value, - scene -); - -console.log('Number of geoms in scene:', scene.ngeom); - -// Remember to delete all created objects when they are no longer needed. -scene.delete(); -camera.delete(); -perturb.delete(); -option.delete(); -data.delete(); -model.delete(); -``` - -As with buffers, you are responsible for managing the memory of these struct instances and must call `.delete()` on them when you are finished. - -### Enums -Access via `.value`: -```javascript -mujoco.mjtDisableBit.mjDSBL_CLAMPCTRL.value -``` - -### Constants -Scalar constants will be accessed the same way they are on python, simply: - -```javascript -mujoco.mjNEQDATA -``` - -Due to Embind limitations, more complex constants that are not scalar, but are represented in more dimensions are exposed as functions. E.g to use `mujoco.mjFRAMESTRING` you will need to call a function: - -```javascript -mujoco.get_mjFRAMESTRING() -``` - -This will return a javascript array representation of the values in MuJoCo `mjFRAMESTRING`. - -## Notes -The package is ESM (`type: module`) and ships TypeScript types. - -Ensure your bundler or dev server serves the `.wasm` asset at runtime. - -### Development -For detailed build instructions see the repository [README](https://github.com/google-deepmind/mujoco/tree/main/wasm#mujoco-javascript-bindings) (Emscripten toolchain, Embind bindings, producing the .wasm artifact, and targets for Node and browser). - -## Versioning -Package versions follow the official MuJoCo release versions. -For example: - -| npm version | MuJoCo version | -|-------------|----------------| -| 3.5.0 | 3.5.0 | - ---- - -For full engine documentation, see: https://mujoco.readthedocs.io From f27b8d3d7011871e98aab7ee566414a6afed8717 Mon Sep 17 00:00:00 2001 From: MatiasManevi Date: Mon, 23 Feb 2026 18:13:44 -0300 Subject: [PATCH 05/10] Move notes into example application section --- wasm/README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/wasm/README.md b/wasm/README.md index 651deaf9..32293a4b 100644 --- a/wasm/README.md +++ b/wasm/README.md @@ -110,6 +110,9 @@ write your application in C++ and compile it using Emscripten, you may want to copy a subset of the `EMSCRIPTEN_BINDINGS` from `bindings.cc` into your application’s source file. +The package is ESM (`type: module`) and ships TypeScript types. +Ensure your bundler or dev server serves the `.wasm` asset at runtime. + ### Named Access The bindings support named access methods, similar to the Python bindings, @@ -300,11 +303,6 @@ mujoco.get_mjFRAMESTRING() This will return a javascript array representation of the values in MuJoCo `mjFRAMESTRING`. -### About assets -The package is ESM (`type: module`) and ships TypeScript types. - -Ensure your bundler or dev server serves the `.wasm` asset at runtime. - ## Development In order to change the bindings you will need to change the [`bindings.cc`](codegen/generated/bindings.cc) From 3bbb5489910769f225baaed618263ce3ca2cb645 Mon Sep 17 00:00:00 2001 From: MatiasManevi Date: Tue, 24 Feb 2026 15:04:18 -0300 Subject: [PATCH 06/10] Add memory management section to readme --- wasm/README.md | 78 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 21 deletions(-) diff --git a/wasm/README.md b/wasm/README.md index 32293a4b..1572e141 100644 --- a/wasm/README.md +++ b/wasm/README.md @@ -122,38 +122,62 @@ joint using `data.jnt('myjoint')`. For more details and examples of how to use named access, please refer to the [named access tests](tests/bindings_test.ts#L1876-L2378) and [documentation](https://mujoco.readthedocs.io/en/stable/python.html#named-access). -## Usage Guide -When interacting with MuJoCo objects through the WASM bindings, it's important to understand how data is accessed. Properties on objects like `MjModel` and `MjData` can expose data in two ways: by copy or by reference. +### Memory Management +Embind-wrapped C++ object handles created or returned into JavaScript live on the +WebAssembly heap and are **not** garbage-collected by the JS runtime. -### Install -```sh -npm install mujoco +Any heap-allocated C++ object exposed to JS (e.g. via `new Module.MyClass(...)` +or returned as a pointer/reference from a binding) must be explicitly freed +when no longer needed to avoid memory leaks. + +Use the generated `.delete()` method on wrapped instances to destroy the +underlying C++ object: + +```typescript + const obj = new Module.MyClass(...); + // ... use obj ... + obj.delete(); // free the C++ memory ``` -```ts -import loadMujoco from 'mujoco'; +Be careful to call `.delete()` exactly once per created object (double-delete +is an error). In JS code paths that may throw or return early, ensure +deletion happens in finally blocks or wrap lifetime management to avoid leaks. -const mujoco = await loadMujoco(); - -const model = mujoco.MjModel.fromXMLString(` - - - - - -`); - -const data = new mujoco.MjData(model); -mujoco.mj_step(model, data); -``` +> [!IMPORTANT] +> _Embind's documentation strongly recommends that JavaScript code explicitly deletes any C++ object handles it has received._ ### Copy vs. Reference +When interacting with MuJoCo objects through the WASM bindings, it's important to understand how data is accessed. Properties on objects like `MjModel` and `MjData` can expose data in two ways: by copy or by reference. + #### 1. By Copy (Value-based access) Some properties return a copy of the data at the time of access. This is common for complex data structures that need to be marshalled from C++ to JavaScript. -A key example is `MjData.contact`. When you access `data.contact`, you get a new array containing the contacts at that specific moment in the simulation. If you step the simulation forward, this array will not be updated. You must access `data.contact` again to get the new contact information. +A key example is `MjData.contact`. When you access `data.contact`, you get an object containing a copy of the contacts at that specific moment in the simulation. + +If you step the simulation forward, they will not be updated. You must access `data.contact` again to get the new contact information. + +The object you get is a JavaScript proxy interface generated by [Emscripten’s Embind library](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#built-in-type-conversions) when you expose a `std::vector` using `register_vector`. It is essentially a "bridge" object. + +```typescript +export interface MjContactVec extends ClassHandle { + /** Appends a new element to the end of the vector, increasing its length by one. */ + push_back(_0: MjContact): void; + + /** Resizes the vector to contain the specified number of elements, filling new slots with the provided value. */ + resize(_0: number, _1: MjContact): void; + + /** Returns the total number of elements currently stored in the vector. */ + size(): number; + + /** Retrieves the element at the specified index, or returns undefined if the index is out of bounds. */ + get(_0: number): MjContact | undefined; + + /** Overwrites the element at the specified index; returns true if successful or false if the index is invalid. */ + set(_0: number, _1: MjContact): boolean; +} +``` Example: ```typescript @@ -165,9 +189,14 @@ mujoco.mj_step(model, data); // `contacts` is now stale. To get the new contacts, you must access the property again: const newContacts = data.contact; + +// Remember to delete all created objects when they are no longer needed. +contacts.delete(); +newContacts.delete(); ``` #### 2. By Reference (View-based access) + Many properties, especially large numerical arrays, return a live view directly into the WebAssembly memory. This is highly efficient as it avoids copying large amounts of data. A key example is `MjData.qpos` (joint positions). When you get a reference to this array, it points directly to the simulation's state data. Any changes in the simulation (e.g., after a call to `mj_step`) will be immediately reflected in this array. @@ -183,9 +212,13 @@ mujoco.mj_step(model, data); // `qpos` is automatically updated. console.log(qpos[0]); // Print new position + +// Remember to delete all created objects when they are no longer needed. +data.delete(); ``` ### Data Layout: Row-Major Matrices + When a function from the MuJoCo C API returns a matrix (or needs a matrix as input), these are represented in the JavaScript bindings as flat, one-dimensional `TypedArray`'s. The elements are stored in row-major order. For example, a 3x10 matrix will be returned as a flat array with 30 elements. The first 10 elements represent the first row, the next 10 represent the second row, and so on. @@ -202,11 +235,13 @@ const element = matrix[i * nCols + j]; ``` ### Working with Out Parameters + Many functions in the MuJoCo C API use "out parameters" to return data. This means instead of returning a value, they write the result into one of the arguments passed to them by reference (using pointers). In our JavaScript bindings, you'll need to handle these cases specifically. There are two main scenarios you'll encounter: #### 1. Array-like Out Parameters + When a function expects a pointer to a primitive type (like `mjtNum*` or `int*`) to write an array of values, you need to pre-allocate memory for the result on the JavaScript side. We provide helper classes for this: `mujoco.Uint8Buffer`, `mujoco.DoubleBuffer`, `mujoco.FloatBuffer`, and `mujoco.IntBuffer`. Here's how to use them: @@ -242,6 +277,7 @@ try { ``` #### 2. Struct Out Parameters (e.g., mjvCamera*, mjvScene*) + When a function modifies a struct passed by pointer, you should pass an instance of the corresponding JavaScript wrapper class. The underlying C++ struct will be modified in place. Example: Updating a scene From 1db1dfe6a0d2c5910b51f1df1b078a21b3dc4d98 Mon Sep 17 00:00:00 2001 From: MatiasManevi Date: Tue, 24 Feb 2026 15:08:57 -0300 Subject: [PATCH 07/10] Remove empty lines --- wasm/README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/wasm/README.md b/wasm/README.md index 1572e141..efc6d396 100644 --- a/wasm/README.md +++ b/wasm/README.md @@ -164,16 +164,12 @@ The object you get is a JavaScript proxy interface generated by [Emscripten’s export interface MjContactVec extends ClassHandle { /** Appends a new element to the end of the vector, increasing its length by one. */ push_back(_0: MjContact): void; - /** Resizes the vector to contain the specified number of elements, filling new slots with the provided value. */ resize(_0: number, _1: MjContact): void; - /** Returns the total number of elements currently stored in the vector. */ size(): number; - /** Retrieves the element at the specified index, or returns undefined if the index is out of bounds. */ get(_0: number): MjContact | undefined; - /** Overwrites the element at the specified index; returns true if successful or false if the index is invalid. */ set(_0: number, _1: MjContact): boolean; } From c17eb431cb81321189f0a4130af7a227a19d7fce Mon Sep 17 00:00:00 2001 From: MatiasManevi Date: Tue, 24 Feb 2026 16:58:32 -0300 Subject: [PATCH 08/10] Move publish step to sh file --- .github/workflows/build_steps.sh | 19 ++++++++++++++++ .github/workflows/publish-wasm.yml | 36 +++--------------------------- 2 files changed, 22 insertions(+), 33 deletions(-) diff --git a/.github/workflows/build_steps.sh b/.github/workflows/build_steps.sh index 6a6889bc..fc290633 100755 --- a/.github/workflows/build_steps.sh +++ b/.github/workflows/build_steps.sh @@ -230,6 +230,25 @@ build_test_wasm() { npm run test --prefix ./wasm } +package_wasm() { + mkdir -p wasm/dist + cp wasm/package.npm.json wasm/dist/package.json + cp wasm/README.md wasm/dist/README.md || true + + VERSION=${GITHUB_REF#refs/tags/} + npm --prefix wasm/dist version "${VERSION}" --no-git-tag-version + + if [ -z "${NPM_TOKEN}" ]; then + echo "NPM_TOKEN is not set or is invalid; aborting publish" + exit 1 + fi + echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc + + npm pack --dry-run ./wasm/dist + + npm publish ./wasm/dist --access public +} + package_mjx() { echo "Packaging MJX..." diff --git a/.github/workflows/publish-wasm.yml b/.github/workflows/publish-wasm.yml index 0e481ead..d4eeadf9 100644 --- a/.github/workflows/publish-wasm.yml +++ b/.github/workflows/publish-wasm.yml @@ -28,38 +28,8 @@ jobs: - name: Build WASM bindings run: bash ./.github/workflows/build_steps.sh build_test_wasm - - - name: Prepare clean package manifest in dist - run: | - if [ -f wasm/package.npm.json ]; then - mkdir -p wasm/dist - cp wasm/package.npm.json wasm/dist/package.json - echo "Copied wasm/package.npm.json -> wasm/dist/package.json" - else - echo "No wasm/package.npm.json found; aborting" - exit 1 - fi - - - name: Prepare README for dist - run: | - mkdir -p wasm/dist - cp wasm/README.md wasm/dist/README.md - - - - name: Sync version with tag (dist) - run: | - VERSION=${GITHUB_REF#refs/tags/} - echo "Setting package version to: ${VERSION}" - npm --prefix wasm/dist version "${VERSION}" --no-git-tag-version - - - name: Configure npm auth + - name: Package WASM bindings env: NPM_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }} - run: | - echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc - - - name: Preview package (dist) - run: npm pack --dry-run ./wasm/dist - - - name: Publish package (dist) - run: npm publish ./wasm/dist --access public + GITHUB_REF: ${{ github.ref }} + run: bash ./.github/workflows/build_steps.sh package_wasm From bad7e29d3bd017c7c2881f88123e0eec51c1f21a Mon Sep 17 00:00:00 2001 From: MatiasManevi Date: Tue, 24 Feb 2026 17:10:06 -0300 Subject: [PATCH 09/10] Cleanup readme and publish step --- .github/workflows/build_steps.sh | 5 +---- .github/workflows/publish-wasm.yml | 1 + wasm/README.md | 6 +++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build_steps.sh b/.github/workflows/build_steps.sh index fc290633..6cc80792 100755 --- a/.github/workflows/build_steps.sh +++ b/.github/workflows/build_steps.sh @@ -231,21 +231,18 @@ build_test_wasm() { } package_wasm() { - mkdir -p wasm/dist + echo "Publishing WASM bindings..." cp wasm/package.npm.json wasm/dist/package.json cp wasm/README.md wasm/dist/README.md || true VERSION=${GITHUB_REF#refs/tags/} npm --prefix wasm/dist version "${VERSION}" --no-git-tag-version - if [ -z "${NPM_TOKEN}" ]; then echo "NPM_TOKEN is not set or is invalid; aborting publish" exit 1 fi echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc - npm pack --dry-run ./wasm/dist - npm publish ./wasm/dist --access public } diff --git a/.github/workflows/publish-wasm.yml b/.github/workflows/publish-wasm.yml index d4eeadf9..33c5b7c1 100644 --- a/.github/workflows/publish-wasm.yml +++ b/.github/workflows/publish-wasm.yml @@ -28,6 +28,7 @@ jobs: - name: Build WASM bindings run: bash ./.github/workflows/build_steps.sh build_test_wasm + - name: Package WASM bindings env: NPM_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }} diff --git a/wasm/README.md b/wasm/README.md index efc6d396..3eb78fd9 100644 --- a/wasm/README.md +++ b/wasm/README.md @@ -134,9 +134,9 @@ Use the generated `.delete()` method on wrapped instances to destroy the underlying C++ object: ```typescript - const obj = new Module.MyClass(...); - // ... use obj ... - obj.delete(); // free the C++ memory +const obj = new Module.MyClass(...); +// ... use obj ... +obj.delete(); // free the C++ memory ``` Be careful to call `.delete()` exactly once per created object (double-delete From 07fdf1f4659633509affd8d27d8641c6c768bf44 Mon Sep 17 00:00:00 2001 From: MatiasManevi Date: Mon, 2 Mar 2026 15:20:44 -0300 Subject: [PATCH 10/10] Cleanup wasm package script --- .github/workflows/build_steps.sh | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build_steps.sh b/.github/workflows/build_steps.sh index 6cc80792..3a562e5a 100755 --- a/.github/workflows/build_steps.sh +++ b/.github/workflows/build_steps.sh @@ -233,15 +233,13 @@ build_test_wasm() { package_wasm() { echo "Publishing WASM bindings..." cp wasm/package.npm.json wasm/dist/package.json - cp wasm/README.md wasm/dist/README.md || true + cp wasm/README.md wasm/dist/README.md VERSION=${GITHUB_REF#refs/tags/} npm --prefix wasm/dist version "${VERSION}" --no-git-tag-version - if [ -z "${NPM_TOKEN}" ]; then - echo "NPM_TOKEN is not set or is invalid; aborting publish" - exit 1 - fi - echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc + + echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > ~/.npmrc + npm pack --dry-run ./wasm/dist npm publish ./wasm/dist --access public }