logo

Babylon.js Market

Members Only

Inject in Practice

Download, inject, and sell assets & code on BabylonJS Market

A worked example, end to end. The component is Bouncer — a small Component+System pair that reflects an entity's velocity off a surface normal. It lives in your project. You want it in the arcade library.

The starting tree

text
my-project/
├── package.json
├── src/
│   ├── components/
│   │   └── Bouncer/
│   │       ├── Bouncer.ts
│   │       ├── Bouncer.core.ts
│   │       ├── Bouncer.test.ts
│   │       └── meta.json
│   ├── registry.ts
│   └── scene.ts
└── vitest.config.ts

Bouncer.ts defines BouncerComponent, BouncerSystem, BouncerEvents, BouncerInputEvents. Bouncer.core.ts holds reflectVelocity(...). Bouncer.test.ts exercises the core function and one System integration. meta.json declares the public surface and lists Physics as a dependency.

src/registry.ts has Bouncer wired into it from a previous eject-style scaffold:

TypeScript
export const REGISTRY = {
  Bouncer: () => import('./components/Bouncer/Bouncer'),
  // ...other components
};

Step 1: Dry-run

The first run is always a dry-run. Nothing writes. You read the plan.

Terminal
cd my-project
bjs inject Bouncer --dry-run

The output:

text
Inject plan (dry run — nothing written):
  target:     arcade
  components: Bouncer
  + src/components/Bouncer/
  patched registry: Bouncer

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

Read top to bottom:

  • target: arcade — no viz imports, no Solid, no PanelDebuggerSystem subclasses. The auto-detect chose arcade.
  • components: Bouncer — the closure is one entry. Bouncer imports nothing from ../Sibling/, so nothing else came along.
  • + src/components/Bouncer/ — the destination folder. The whole src/components/Bouncer/ will be cp -r'd.
  • patched registry: Bouncer — one new line in the project's src/registry.ts.

If the target is wrong, fix it with --target and run the dry-run again. If the closure is wrong, follow the ../Sibling/ imports that brought in the surprise and decide whether to inject the sibling too or refactor it out of Bouncer.

Step 2: Apply

Terminal
bjs inject Bouncer

Same plan, no dry-run header, a green confirmation at the end:

text
Injecting:
  target:     arcade
  components: Bouncer
  + src/components/Bouncer/
  patched registry: Bouncer

Done. Components landed in the arcade library.

Step 3: Inspect the diff

The new folder in the arcade library:

text
src/components/Bouncer/
├── Bouncer.ts
├── Bouncer.core.ts
├── Bouncer.test.ts
└── meta.json

Files are byte-identical to the source unless they had a sibling import to rewrite. For arcade injection, the rewrite replaces @babylonjsmarket/arcade/<Sibling> with the relative ../<Sibling>/<Sibling> only when <Sibling> was co-injected. Bouncer has no co-injected siblings, so no rewrites apply.

The registry diff:

diff
 export const ARCADE_COMPONENT_REGISTRY: Record<string, LazyComponentResolver> = {
+  Bouncer: () => import('./components/Bouncer/Bouncer'),
   // ...existing entries
 };

The patch is idempotent. Re-running bjs inject Bouncer does not add a second line — the registry text is scanned for ./components/Bouncer/Bouncer first, and the patch skips if it's already there.

Step 4: Run the tests

Terminal
npm test

Your test should pass in the library exactly as it passed in your project. If it doesn't, the test had a hidden dependency on your project — a fixture file outside the component folder, an alias the project's vitest.config.ts set up, a shared test helper. Fix the test (decouple, or co-inject the helper) and re-run.

A multi-name example

If your Bouncer imported a sibling Surface via ../Surface/Surface, the closure walk would have picked it up automatically and the dry-run would have shown two components:

text
Inject plan (dry run — nothing written):
  target:     arcade
  components: Bouncer, Surface
  + src/components/Bouncer/
  + src/components/Surface/
  patched registry: Bouncer
  patched registry: Surface

Sibling-import rewrites only matter when at least two components in the same run reference each other via ../. Then the relative paths stay valid inside the library (both siblings live under src/components/), and the rewrite collapses any @babylonjsmarket/arcade/Surface-style import back to ../Surface/Surface.

A --source example

When your seed lives outside <cwd>/src/components/<Name>/ — for example in a demo project under packages/demo/:

Terminal
bjs inject Bouncer --source ./packages/demo/src/components/Bouncer

--source applies to one seed only. The transitive closure walk is skipped — when you point at an arbitrary directory, the tool can't reliably reason about which other components the project considers siblings, so it copies just the seed itself.

A practical consequence: if your --source seed imports ../Sibling/, the import won't be rewritten and the file will end up with a broken ../ reference in the pro package. Co-inject the sibling explicitly, or refactor the import out of the seed before running.

A viz example

A panel-debugger seed:

text
src/components/MyDebugger/
├── MyDebugger.ts            # extends PanelDebuggerSystem
├── MyDebugger.viz.tsx       # imports solid-js
└── meta.json

Dry-run:

Terminal
bjs inject MyDebugger --dry-run

Output:

text
Inject plan (dry run — nothing written):
  target:     viz
  components: MyDebugger
  + viz/ecs/MyDebugger.ts
  + viz/solid/MyDebugger.viz.tsx
  patched ecs/index.ts: MyDebugger
  patched solid/index.ts: MyDebugger.viz
  hint: if you want named re-exports from the package root, edit viz/src/index.ts by hand.

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

The extends PanelDebuggerSystem tipped the target to viz. The .ts file landed in ecs/; the .viz.tsx landed in solid/. Each layer's index.ts gained one export * from './<File>';. The root src/index.ts is left alone — by convention, named re-exports there are author-curated.

What's next

The next section, publish as a product, explains how bjs inject fits into the larger publishing flow: how curator review picks up your submission and how a future new-product wizard will close the loop from a member's CLI to a live marketplace product.

Was this page helpful?

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

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search