Loomgetting started

Installation

Add loom to a project — the two packages, their peer requirements, the WASM layout engine, and what a source checkout needs instead.

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 itInstall
CLI onlyloom preview / loom build, no Vite project of your ownloom-dev
Plugin onlyloomPreview() in your own vite.config.ts@loom-dev/preview + react@18 + vite
Both — the CLI and your own Vite configall of the above

Most projects start on the first row. It is one dependency and no config file:

pnpm add -D loom-dev

Adding the plugin to a Vite project you already own means installing its peers yourself:

pnpm add -D @loom-dev/preview react@^18.3.1 vite
What is actually in each package
PackageWhat it isBin
@loom-dev/previewThe Vite plugin (loomPreview()) — generated page, entry detection, gallery mode — the @rbxts/react-roblox / @rbxts/services browser shims, and the Roblox globals installer.
loom-devThe 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 — not as peers — so the CLI is self-contained.

Both packages are ESM-only ("type": "module") and published under the MIT license. All @loom-dev/* packages are versioned in lockstep — the changesets config marks them fixed, so a bump to one bumps them all. Mixing versions across @loom-dev/* is never a supported combination.

Node 24 or newer is expected (engines: { node: ">=24" }).

Where the React pin bites, and the peers the plugin declares

Loom 0.4.0 narrowed the range to match the reconciler — before that it advertised || ^19.0.0, and the failure surfaced at first render instead of at install.

This is also why the plugin aliases bare react by absolute path rather than using resolve.dedupe — dedupe would re-anchor React at your project root and could pick a different major. If you see Warning: Invalid hook call with a “more than one copy of React” hint, a React version mismatch is the first thing to check.

Only the plugin has peers at all; the CLI vendors its own copies. @loom-dev/preview declares two:

From @loom-dev/preview's package.json
{
"peerDependencies": {
"react": "^18.3.1",
"vite": "^6.0.0 || ^7.0.0"
},
"peerDependenciesMeta": {
"vite": { "optional": true }
}
}

vite is optional because the package’s non-Vite entry points (/client, /globals, /services) are useful without it — but loomPreview() obviously 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 is compiled to WebAssembly and shipped inside @loom-dev/layout as a prebuilt .wasm — you never need a Rust toolchain to consume loom, only to develop it.

Published packages ship the wasm-opt-processed binary — about 178 kB, roughly 68 kB gzipped. Irrelevant for local previewing, worth knowing 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: minor bumps carry behavior changes. 0.3.0 is the first release where the plugin owns the whole preview — the generated page, entry detection and gallery mode — so anything written against 0.2.x that hand-rolled an index.html or a globals import still works, but no longer has to.

Verify

Smoke test
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.

Set up a checkout
git clone https://github.com/astra-void/loom.git
cd loom
pnpm install # a prepare hook runs build:native
package.json in your project
{
"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) and wasm-pack, alongside 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.