logo
By Lawrence

6 minutes

Eject a Built-In Component

TL;DR: npx arcade eject ArcCamera copies ArcCamera, and every component it depends on, from node_modules into src/components/. The registry then loads your copy instead of the package's. Edit a default in your copy and the game uses it, unless the scene sets that value itself.

bjs download gets source from the marketplace. The components the arcade package ships are already on your disk, inside node_modules/@babylonjsmarket/arcade. arcade eject copies them into your project, offline and free. It's a command of the arcade package, so you run it with npx inside my-game:

npx arcade eject --help
Usage: arcade eject [OPTION]... [COMPONENT]...

Copy arcade component source into your project so you own and can edit it
(shadcn-style). Ejected components land in src/components/<Name>/ and are
wired into src/registry.ts so they override the package's built-in copy.

With no component names, ejects every arcade component referenced by your
scenes (plus their dependencies). It scans src/scenes/*.ts,
public/games/*/scene.json and src/registry.ts (and the older
src/scene.ts / src/scene.json). Name components explicitly to
eject just those, or use --all for the whole library.
...
Options:
  --all          eject every arcade component (ignore scene/registry scan)
  --dry-run      print what would be copied/patched, write nothing
  -h, --help     print this help message

One name in, four components out

Always look before you copy. --dry-run prints the plan and writes nothing:

npx arcade eject LineOfSight --dry-run
Eject plan (dry run — nothing written):
  components: LineOfSight, Mesh, MeshPrimitive, Obstacle
  + src/components/LineOfSight/
  + src/components/Mesh/
  + src/components/MeshPrimitive/
  + src/components/Obstacle/
  registry auto-discovers components — no wiring needed for: LineOfSight, Mesh, MeshPrimitive, Obstacle
  + .claude/skills/babylonjsmarket/

Re-run without --dry-run to apply.

You named one and the plan has four. Each component's meta.json lists what it needs. LineOfSight lists MeshPrimitive and Obstacle, and Obstacle lists these:

node_modules/@babylonjsmarket/arcade/src/Components/Obstacle/meta.json
"dependencies": [
  "MeshPrimitive",
  "Mesh"
],

Eject follows those lists, and any ../Sibling/ import in the source, until nothing new turns up. That full set is the dependency closureA component plus everything it needs, and everything those need, until nothing new turns up.. A copy without Mesh would import a folder that isn't there.

With no names, ejectCopy a component's source out of the installed package into your own src/, so you can edit it. scans your scenes instead:

npx arcade eject --dry-run
Eject plan (dry run — nothing written):
  components: ArcCamera, DirectionalLight, HemisphericLight, Mesh, MeshPrimitive, Shadow
...

That's every built-in src/scenes/game.ts uses. Mesh is there because of the ship from Lesson 4. Spin, Hello and Lifetime aren't, because they're already your files.

Break it: a name the package doesn't ship

npx arcade eject Bullet
Unknown arcade component(s): Bullet
Available: AmbientOcclusion, Animation, ArcCamera, CabinetLink, CameraFollow, DirectionalLight, Enemy, EntityPool, EnvironmentTexture, FirstPersonCamera, Flash, Fog, GlowLayer, Health, HealthBar, HemisphericLight, Jump, KeyboardMover, LineOfSight, Mesh, MeshPrimitive, Movement, NeonSign, Obstacle, Physics, PlayerInput, PointLight, Pool, Score, Scoreboard, Shadow, Skybox, Sound, SpotLight, ToneMapping, TwinStickEnemy

Eject only reaches the package in node_modules. Bullet lives in the catalog, like Lifetime, so it comes through bjs download. The Available: list is everything eject can copy.

Eject the camera and change it

Eject writes into src/, so save a snapshot first. Git is the tool that does that: git commit saves a snapshot of your files, git diff shows what changed since, and git checkout -- puts a file back. If my-game isn't a git repository yet, run git init once.

git add -A && git commit -m "before eject"
npx arcade eject ArcCamera
Ejecting:
  components: ArcCamera, MeshPrimitive
  + src/components/ArcCamera/
  + src/components/MeshPrimitive/
  registry auto-discovers components — no wiring needed for: ArcCamera, MeshPrimitive
  + .claude/skills/babylonjsmarket/

Done. Ejected components now live in your project and override the package copies.

src/components/ArcCamera/ArcCamera.ts matches its folder, so the registry maps the scene's ArcCamera to your copy. The package's version is still installed; nothing reaches it any more.

Your ejected copy of a component takes precedence over the package's built-in version, shown as a card catalog pointing to your annotated copy

Open src/components/ArcCamera/ArcCamera.defs.ts. The constructor gives every setting a default with ??, used when the scene doesn't set it:

src/components/ArcCamera/ArcCamera.defs.ts
this.distance = data.distance ?? 15;
this.minDistance = data.minDistance ?? 5;
this.maxDistance = data.maxDistance ?? 50;
this.allowBelowGround = data.allowBelowGround ?? false;
this.minBeta = data.minBeta ?? 0.1;
this.maxBeta = data.maxBeta ?? (this.allowBelowGround ? Math.PI - 0.1 : Math.PI / 2 - 0.1);
this.alpha = data.alpha ?? Math.PI / 2;
this.beta = data.beta ?? Math.PI / 3;
this.inertia = data.inertia ?? 0.9;
this.panningInertia = data.panningInertia ?? 0.9;
this.wheelPrecision = data.wheelPrecision ?? 50;
this.angularSensibility = data.angularSensibility ?? 1000;
this.speed = data.speed ?? 1;
this.targetOffset = data.targetOffset ? cloneVec3(data.targetOffset) : [0, 0, 0];
this.autoRotate = data.autoRotate ?? false;
this.autoRotateSpeed = data.autoRotateSpeed ?? 0.5;

The starter scene never sets autoRotate. Change its default to true and save. The camera starts circling the capsule slowly, on its own. That's your copy running.

Break it: a default the scene overrides

Now change distance from ?? 15 to ?? 40 and save. Nothing moves. src/scenes/game.ts sets distance: 8, and ?? only uses the default when the scene leaves the value out. Delete the distance: 8 line from the scene and the camera pulls far back, the capsule a small shape in the middle of the floor. Put the line back and the default back to 15.

Scene data wins over a component's defaults. When an edit to an ejected copy seems to do nothing, check whether the scene sets that field.

Eject again, lose your edit

Commit the orbiting camera, eject ArcCamera once more, and ask git what changed:

git add -A && git commit -m "camera orbits"
npx arcade eject ArcCamera
git diff
diff --git a/src/components/ArcCamera/ArcCamera.defs.ts b/src/components/ArcCamera/ArcCamera.defs.ts
...
@@ -147,7 +147,7 @@ export class ArcCameraComponent extends Component {
     this.angularSensibility = data.angularSensibility ?? 1000;
     this.speed = data.speed ?? 1;
     this.targetOffset = data.targetOffset ? cloneVec3(data.targetOffset) : [0, 0, 0];
-    this.autoRotate = data.autoRotate ?? true;
+    this.autoRotate = data.autoRotate ?? false;
     this.autoRotateSpeed = data.autoRotateSpeed ?? 0.5;
     this.controlsEnabled = data.controlsEnabled ?? true;
   }

Eject copied a fresh ArcCamera over yours without asking. That's how you pick up a fix from a newer package, and it's why you commit first. git checkout -- src/components/ArcCamera puts your version back.

Next: Make It Marketplace-Ready: give Spin a meta.json, run the checks bjs submit runs, and see what a sibling import drags into a submission.

Was this page helpful?

We read every note — tell us what's working and what isn't.

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search