The Unity project ReactUnity is developed and manually tested against — most features at once, across UGUI, UI Toolkit, a world-space canvas, VR and raw HTML. It is also the sample users are pointed at, so it is published standalone: see the kitchen-sink branch.
For smaller, more focused examples, see samples/.
Open the project in Unity, open Assets/Scenes/MainScene.unity and press Play, or drive it headlessly:
pnpm unity open kitchen-sinkThe UI is a React app in react/. Start its dev server and press Play — Unity connects to it and hot-reloads as you edit react/src/pages/…:
pnpm --filter reactunity-kitchen-sink startpnpm --filter reactunity-kitchen-sink build writes the bundle to Assets/Resources/react, which is committed so the project plays without Node.
Unlike tests/, this project is pinned to 6000.6.0f1 and is not part of any CI matrix.
Two Unity WebGL builds come out of this project. Neither is tracked here — together they are ~52 MB — and neither is built by CI, which has no WebGL module. They live permanently under Unity/ on the gh-pages branch of reactunity.github.io, which the docs deploy is configured not to wipe.
| Build profile | Published to | Loaded by |
|---|---|---|
WebInjectable |
Unity/injectable/ |
every docs example, via instance.tsx |
Previewer |
Unity/previewer/<version>/ |
the @reactunity/scripts dev server, via index.html |
Building one is a build against an open Editor — pnpm unity has no WebGL command, and could not run one while an Editor holds the project lock anyway. Each takes about ten minutes cold and under a minute once the other has warmed the IL2CPP cache:
unity command build --project-path kitchen-sink -- --target WebGL --profileName Previewer --scenes '["Assets/Scenes/Internal/Previewer.unity"]' --outputPath Logs/web/Previewer --confirm trueFour things decide whether the result is usable at those URLs, and nothing checks any of them:
- Name the scene, do not trust
--profileNameto. The command resolves the scene list before it activates the profile, so a profile switch does not reach the build it is switching for: asking forPreviewerwhileWebInjectablewas active produces the WebInjectable scene under the namePreviewer.data, succeeds, and looks right until someone opens it.--scenesis the only reliable half; the profile still has to be named, for the platform settings. - The output folder name becomes the file name. Unity writes
Build/<folder name>.wasm.unityweb, not the product or profile name, so the folder has to beWebInjectableorPreviewer— that is what both consumers request. The directory it is published into isinjectable/previewer; onlyBuild/is copied over (neither scene usesStreamingAssets, so no build produces one). - Compression is Gzip with
webGLDecompressionFallbackon, which is what the.unitywebsuffix on three of the four files means: the loader inflates them itself, because GitHub Pages cannot be told to sendContent-Encoding. Both consumers ask for those names. The project is set that way, so nothing has to be flipped before a build — but flipping it produces file names neither consumer requests, and Unity says nothing. Off, the same player is ~82 MB rather than ~26 MB; the remaining bulk is aCode OptimizationofShorter Build Time. - The previewer's version is in its path, so a new build means a new directory and the matching
PREVIEWER_VERSIONbump inpackages/scripts/config/public/index.html. Upload first: a bump pointing at a directory that is not there yet gives every dev server a dead previewer. The injectable player has no version in its path, so uploading one replaces what the live docs site is loading — add the new files, let the docs deploy switch over, and only then delete what the previous build left behind.
Check a build before uploading it, because a wrong one succeeds quietly. python -m http.server in the output folder and open index.html: the previewer fetches /index.js from that origin, so dropping the injectable bundle there with its /*INJECT_CODE*/ marker replaced renders a real component; the injectable player renders nothing until something calls SetJSX then RenderBridge on ReactCanvas, which is what the docs page does.
WebInjectable bakes in unity/core/Tests/Runtime/Resources/ReactUnity/tests/injectable/index.js — the bundle the C# suite injects fixtures into — so regenerate it with pnpm --filter @reactunity/injectable build before building the player, or the docs examples run against whatever the renderer looked like when that bundle was last committed.
This directory is not cloneable on its own: Packages/manifest.json consumes the Unity packages as file:../../unity/* and react/package.json uses workspace:*, both so that the project always exercises the working tree. scripts/kitchen-sink/prepare.mts rewrites those to published versions and drops the developer-only pieces; release-kitchen-sink.yml commits the result to the orphan branch.
To see exactly what a user gets:
pnpm kitchen-sink Logs/kitchen-sink --forceAnything added here that only resolves inside this checkout has to be handled in prepare.mts. It fails loudly on the cases it knows about — a leftover file: or workspace: dependency, a scene pinned to an engine the export drops, a version OpenUPM has not published yet — but it cannot guess at a new one, and the failure in a user's Editor is a project that imports with no ReactUnity in it rather than an error they can read.