Loomgetting started

Installation

The two packages, their peers, the WASM layout engine, and what a source checkout needs.

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 only — loom preview / loom build, no Vite project of your ownloom-dev
Plugin only — loomPreview() 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, 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" }).

Where 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:

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 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

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, 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.