Loom ships as two packages, and you rarely need both. Pick the row that matches how you want to run the preview:
| How you want to run it | Install |
|---|---|
CLI only — loom preview / loom build, no Vite project of your own | loom-dev |
Plugin only — loomPreview() in your own vite.config.ts | @loom-dev/preview + react@18 + vite |
| Both — the CLI and your own Vite config | all of the above |
Most projects start on the first row. It is one dependency and no config file:
pnpm add -D loom-devnpm install -D loom-devyarn add -D loom-devbun add -D loom-devAdding the plugin to a Vite project you already own means installing its peers yourself:
pnpm add -D @loom-dev/preview react@^18.3.1 vitenpm install -D @loom-dev/preview react@^18.3.1 viteyarn add -D @loom-dev/preview react@^18.3.1 vitebun add -D @loom-dev/preview react@^18.3.1 viteWhat is actually in each package
| Package | What it is | Bin |
|---|---|---|
@loom-dev/preview | The Vite plugin (loomPreview()) — generated page, entry detection, gallery mode — the @rbxts/react-roblox / @rbxts/services browser shims, and the Roblox globals installer. | — |
loom-dev | The loom preview / loom build CLI: the plugin with a Vite server around it, no config file needed. | loom |
loom-dev depends on @loom-dev/preview, React and Vite outright, so the CLI is self-contained.
Both packages are ESM-only and published under MIT. All @loom-dev/* packages are versioned in lockstep, so mixing versions across the scope is never supported.
Node 24 or newer is expected (engines: { node: ">=24" }).
The React peer is ^18.3.1 and nothing wider. The adapter drives react-reconciler@^0.29.2, which reads React 18 internals React 19 renamed, so a React 19 install fails at evaluation time:
pnpm add -D react@^18.3.1 @types/react@^18.3.12Where the React pin bites, and the peers the plugin declares
The range matches the reconciler, so a mismatch fails at install rather than at first render.
It is also why the plugin aliases bare react by absolute path rather than resolve.dedupe. Dedupe
would re-anchor React at your project root and could pick a different major.
Warning: Invalid hook call with a “more than one copy of React” hint means a version mismatch.
Only the plugin has peers at all, since the CLI vendors its own copies. @loom-dev/preview declares
two:
{ "peerDependencies": { "react": "^18.3.1", "vite": "^6.0.0 || ^7.0.0" }, "peerDependenciesMeta": { "vite": { "optional": true } }}vite is optional because the non-Vite entry points are useful without it: /client, /globals and /services. loomPreview() is not, so install Vite if you are using the plugin. The CLI brings its own.
What you do not install: no roblox-ts step, no Rust, no layout dependency
There is no roblox-ts compile step in the preview loop, and no separate layout dependency. The Rust layout engine ships inside @loom-dev/layout as a prebuilt .wasm, so a Rust toolchain is only needed to develop loom.
Published packages ship the wasm-opt-processed binary, about 178 kB or roughly 68 kB gzipped. That matters only if you ship a static gallery build.
Pre-1.0: what a minor bump is allowed to do
Loom is published from latest and versioned in lockstep across the scope, but it is still pre-1.0, so minor bumps carry behaviour changes. The plugin owns the whole preview: the generated page, entry detection and gallery mode. A hand-rolled index.html or globals import still works, but is no longer needed.
Verify
pnpm exec loom preview .Pointed at a roblox-ts project with a client entry, that serves an interactive preview on port 5173. If you get a dark page with your UI on it, the WASM engine, the runtime, the adapter, the renderer, and the plugin are all working. If you get a blank page, check the console — see Your first preview.
Working from a source checkout
Contributing to loom, or tracking main ahead of a release, means consuming the repo instead of the
registry. Two things differ: the toolchain widens, and you link rather than install.
git clone https://github.com/astra-void/loom.gitcd loompnpm install # a prepare hook runs build:native{ "dependencies": { "@loom-dev/preview": "link:../loom/packages/preview" }}What the checkout builds, and why link: rather than install
The toolchain widens. The layout engine is built from source, so you need Rust via rustup with the wasm32-unknown-unknown target, pinned in rust-toolchain.toml, plus wasm-pack, Node 24 and pnpm 11.
pnpm install builds the WASM engine through scripts/build-wasm.sh, emitting into
packages/layout/pkg/. Rebuild it with pnpm build:native, or
pnpm build:native:release for the wasm-opt-processed binary the release workflow ships. pnpm build:packages then builds the TypeScript packages with tsdown.
link: symlinks rather than copies, so the package’s own workspace:* dependencies resolve through the real path from the checkout’s node_modules. You never install loom’s dependency tree into your project. Importing the plugin from an unbuilt checkout has one extra wrinkle, covered in
Advanced Vite setup.
On macOS, rustup — not Homebrew — must win
Homebrew’s rustc ships no wasm32-unknown-unknown standard library, and Homebrew’s bin directory
usually sits ahead of rustup’s in PATH. scripts/build-wasm.sh compensates by prepending
$HOME/.cargo/bin to PATH itself, so the build works on a machine with both. If you invoke
wasm-pack by hand, you have to do that yourself.
Next step
Point loom at a project and get a preview on screen: Your first preview.