Claude skill to automate publishing js_of_ocaml OCaml to npm
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

import

+306
+306
SKILL.md
··· 1 + --- 2 + name: ocaml-to-npm 3 + description: Set up npm publishing for OCaml projects compiled to JavaScript/WASM. Use when creating an npm branch for js_of_ocaml or wasm_of_ocaml projects, configuring dune build rules for browser targets, or documenting the release workflow. 4 + license: ISC 5 + --- 6 + 7 + # OCaml to NPM Package Setup 8 + 9 + This skill documents the two-branch workflow for publishing OCaml libraries to npm via js_of_ocaml and wasm_of_ocaml. 10 + 11 + ## Branch Structure 12 + 13 + ``` 14 + main branch - OCaml source code, dune build files, opam packages 15 + npm branch - Built JavaScript/WASM assets, package.json, README for npm 16 + ``` 17 + 18 + The `main` branch contains the OCaml implementation. The `npm` branch is an orphan branch containing only the compiled JavaScript/WASM files and npm metadata. 19 + 20 + ## Dune Build Rules 21 + 22 + ### Library with Browser Bindings 23 + 24 + In `lib/js/dune` or wherever the JavaScript dune rules are, define the js_of_ocaml library and executables: 25 + 26 + ```dune 27 + ; Library compiled to bytecode (required for js_of_ocaml) 28 + (library 29 + (name mylib_js) 30 + (public_name mylib-js) 31 + (libraries mylib brr) 32 + (modes byte) 33 + (modules mylib_js)) 34 + 35 + ; Executable compiled to both JS and WASM 36 + (executable 37 + (name mylib_js_main) 38 + (libraries mylib_js) 39 + (js_of_ocaml 40 + (javascript_files)) 41 + (modes js wasm) 42 + (modules mylib_js_main)) 43 + 44 + ; Optional: Web Worker for background processing 45 + (executable 46 + (name mylib_js_worker) 47 + (libraries mylib brr) 48 + (js_of_ocaml 49 + (javascript_files)) 50 + (modes js wasm) 51 + (modules mylib_js_worker)) 52 + 53 + ; Copy to friendly filenames (JS) 54 + (rule 55 + (targets mylib.js) 56 + (deps mylib_js_main.bc.js) 57 + (action (copy %{deps} %{targets}))) 58 + 59 + (rule 60 + (targets mylib-worker.js) 61 + (deps mylib_js_worker.bc.js) 62 + (action (copy %{deps} %{targets}))) 63 + 64 + ; Copy to friendly filenames (WASM) 65 + (rule 66 + (targets mylib.wasm.js) 67 + (deps mylib_js_main.bc.wasm.js) 68 + (action (copy %{deps} %{targets}))) 69 + 70 + (rule 71 + (targets mylib-worker.wasm.js) 72 + (deps mylib_js_worker.bc.wasm.js) 73 + (action (copy %{deps} %{targets}))) 74 + 75 + ; Install web assets to share/mylib-js/ 76 + (install 77 + (package mylib-js) 78 + (section share) 79 + (files 80 + ; JavaScript bundles 81 + mylib.js 82 + mylib-worker.js 83 + ; WASM loader scripts 84 + mylib.wasm.js 85 + mylib-worker.wasm.js 86 + ; WASM assets (with content-hashed filenames) 87 + (glob_files_rec (mylib_js_main.bc.wasm.assets/* with_prefix mylib_js_main.bc.wasm.assets)) 88 + (glob_files_rec (mylib_js_worker.bc.wasm.assets/* with_prefix mylib_js_worker.bc.wasm.assets)))) 89 + ``` 90 + 91 + Key points: 92 + - `(modes byte)` required for the library (js_of_ocaml needs bytecode) 93 + - `(modes js wasm)` on executables produces both `.bc.js` and `.bc.wasm.js` 94 + - WASM assets go in `*.bc.wasm.assets/` directories with content-hashed filenames 95 + - Use `glob_files_rec` with `with_prefix` to preserve directory structure in install 96 + - Web Workers are useful for background processing without blocking the UI 97 + 98 + ### dune-project Package Definition 99 + 100 + ```dune 101 + (package 102 + (name mylib-js) 103 + (synopsis "Browser library via js_of_ocaml/wasm_of_ocaml") 104 + (depends 105 + (ocaml (>= 5.1.0)) 106 + (mylib (= :version)) ; Link to main library 107 + (js_of_ocaml (>= 5.0)) 108 + (js_of_ocaml-ppx (>= 5.0)) 109 + (wasm_of_ocaml-compiler (>= 5.0)) ; Required for WASM builds 110 + (brr (>= 0.0.6)))) 111 + ``` 112 + 113 + ### Package Disambiguation 114 + 115 + When adding a `-js` package to an existing project, dune may not be able to determine which package stanzas belong to. Add explicit `(package ...)` fields to disambiguate: 116 + 117 + ```dune 118 + ; In bin/myapp/dune - specify which package owns this binary 119 + (executable 120 + (name myapp) 121 + (public_name myapp) 122 + (package mylib) ; Add this to disambiguate 123 + (libraries mylib)) 124 + ``` 125 + 126 + ## Release Script 127 + 128 + Create `release.sh` on the npm branch to copy built assets: 129 + 130 + ```bash 131 + #!/bin/bash 132 + # Release script for mylib npm package 133 + # Run from npm branch after building on main 134 + 135 + set -e 136 + 137 + # Path to dune install directory (relative to repo root) 138 + INSTALL_DIR="_build/install/default/share/mylib-js" 139 + 140 + # Check we're on the npm branch 141 + BRANCH=$(git rev-parse --abbrev-ref HEAD) 142 + if [ "$BRANCH" != "npm" ]; then 143 + echo "Error: Must be on npm branch (currently on $BRANCH)" 144 + exit 1 145 + fi 146 + 147 + # Check the install directory exists 148 + if [ ! -d "$INSTALL_DIR" ]; then 149 + echo "Error: Install directory not found at $INSTALL_DIR" 150 + echo "Run 'opam exec -- dune build @install' on main branch first" 151 + exit 1 152 + fi 153 + 154 + # Copy JavaScript files 155 + echo "Copying JavaScript files..." 156 + cp "$INSTALL_DIR/mylib.js" . 157 + cp "$INSTALL_DIR/mylib-worker.js" . 158 + 159 + # Copy WASM loader scripts 160 + echo "Copying WASM loader scripts..." 161 + cp "$INSTALL_DIR/mylib.wasm.js" . 162 + cp "$INSTALL_DIR/mylib-worker.wasm.js" . 163 + 164 + # Copy WASM assets directories 165 + echo "Copying WASM assets..." 166 + rm -rf mylib_js_main.bc.wasm.assets mylib_js_worker.bc.wasm.assets 167 + cp -r "$INSTALL_DIR/mylib_js_main.bc.wasm.assets" . 168 + cp -r "$INSTALL_DIR/mylib_js_worker.bc.wasm.assets" . 169 + 170 + # Fix permissions 171 + echo "Fixing permissions..." 172 + chmod 644 *.js 173 + find mylib_js_main.bc.wasm.assets -type f -exec chmod 644 {} \; 174 + find mylib_js_worker.bc.wasm.assets -type f -exec chmod 644 {} \; 175 + 176 + echo "Done! Ready to commit and publish." 177 + echo "Run: git add -A && git commit -m 'Release X.Y.Z' && npm publish" 178 + ``` 179 + 180 + Build and release workflow: 181 + ```bash 182 + git checkout main 183 + opam exec -- dune build @install 184 + git checkout npm 185 + ./release.sh 186 + git add -A && git commit -m "Release X.Y.Z" 187 + npm publish 188 + ``` 189 + 190 + ## Creating the NPM Branch 191 + 192 + The npm branch is an orphan branch (no shared history with main). To create it: 193 + 194 + ```bash 195 + # Stash any uncommitted changes first 196 + git stash 197 + 198 + # Create orphan branch 199 + git switch --orphan npm 200 + 201 + # Add the npm-specific files 202 + # (package.json, README.md, LICENSE, release.sh, .gitignore) 203 + 204 + git add package.json README.md LICENSE release.sh .gitignore 205 + git commit -m "Initial npm package setup" 206 + 207 + # Switch back to main and restore changes 208 + git checkout main 209 + git stash pop 210 + ``` 211 + 212 + ## NPM Branch Files 213 + 214 + The npm branch contains: 215 + - `package.json` - npm metadata 216 + - `README.md` - Usage documentation (browser-only focus) 217 + - `LICENSE` - License file 218 + - `release.sh` - Script to copy built assets 219 + - `.gitignore` - Ignore build files and main-branch artifacts 220 + - `*.js` - Pure JavaScript bundles (copied by release.sh) 221 + - `*.wasm.js` - WASM loader scripts (copied by release.sh) 222 + - `*.bc.wasm.assets/` - WASM binaries (copied by release.sh) 223 + 224 + ### .gitignore for npm branch 225 + 226 + ```gitignore 227 + # Build outputs (shared with main branch working directory) 228 + _build/ 229 + *.opam 230 + 231 + # Test files from main branch 232 + examples/ 233 + tests/ 234 + third_party/ 235 + 236 + # Node modules (if used locally) 237 + node_modules/ 238 + ``` 239 + 240 + ## package.json Configuration 241 + 242 + ```json 243 + { 244 + "name": "mylib-jsoo", 245 + "version": "1.0.0", 246 + "description": "Description here", 247 + "browser": "mylib.js", 248 + "homepage": "https://example.com/mylib", 249 + "author": "Author Name", 250 + "license": "MIT", 251 + "repository": { 252 + "type": "git", 253 + "url": "git+https://example.com/mylib.git#npm" 254 + }, 255 + "keywords": [ 256 + "ocaml", 257 + "js_of_ocaml", 258 + "wasm", 259 + "webassembly" 260 + ], 261 + "files": [ 262 + "mylib.js", 263 + "mylib_js_main.bc.wasm.js", 264 + "mylib_js_main.bc.wasm.assets/", 265 + "README.md", 266 + "LICENSE" 267 + ] 268 + } 269 + ``` 270 + 271 + Key points: 272 + - Use `"browser"` not `"main"` (js_of_ocaml output is browser-only) 273 + - Use directory patterns with trailing `/` for WASM assets (captures hashed filenames) 274 + - Point repository URL to `#npm` branch 275 + 276 + ## README.md Structure 277 + 278 + Document for the npm audience with browser-only focus: 279 + 280 + 1. **Clear browser-only notice** at the top - js_of_ocaml output uses DOM APIs 281 + 2. Installation via npm 282 + 3. Browser usage with `<script>` tags (both JS and WASM versions) 283 + 4. Web Worker usage for background processing 284 + 5. Files included table 285 + 6. Browser compatibility requirements (ES6, WebAssembly, Web Workers) 286 + 7. Link back to source repository on main branch 287 + 288 + Example opening: 289 + 290 + ```markdown 291 + # mylib 292 + 293 + Description here. 294 + 295 + **Note: This package is browser-only.** It uses DOM APIs and browser events 296 + for initialization and cannot be used in Node.js. 297 + ``` 298 + 299 + ## Workflow Summary 300 + 301 + 1. Develop on `main` branch (OCaml source) 302 + 2. Build with `dune build @install` 303 + 3. Switch to `npm` branch 304 + 4. Run `release.sh` to copy built assets 305 + 5. Update version in `package.json` 306 + 6. Commit and `npm publish`