# Installation

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

Source: https://docs.astra-void.xyz/loom/getting-started/installation/

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:

```bash
pnpm add -D loom-dev
```

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

```bash
pnpm add -D @loom-dev/preview react@^18.3.1 vite
```

**What 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 — 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" }`).

> **React 18 only**
>
> The React peer is `^18.3.1` and nothing wider. The adapter drives `react-reconciler@^0.29.2`, which
> reads React 18 internals that React 19 renamed, so a React 19 install fails at evaluation time:
>
> ```bash
> pnpm add -D react@^18.3.1 @types/react@^18.3.12
> ```

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

```json title="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](https://docs.astra-void.xyz/loom/guides/static-builds-and-embedding.md).

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

```bash title="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](https://docs.astra-void.xyz/loom/getting-started/first-preview.md).

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

```bash title="Set up a checkout"
git clone https://github.com/astra-void/loom.git
cd loom
pnpm install        # a prepare hook runs build:native
```

```json title="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](https://docs.astra-void.xyz/loom/guides/advanced-vite-setup.md#consuming-an-unbuilt-source-checkout).

**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](https://docs.astra-void.xyz/loom/getting-started/first-preview.md).
