bjs inject reads src/components/<Name>/ and copies what it finds into the pro package. The folder shape is the same one arcade eject produces and the same one @babylonjsmarket/arcade ships with ā it's the lingua franca of this ecosystem.
Look at packages/arcade/src/Components/Health/ for a real reference: a Component, a System, a pure core, two test files, and a meta.json. Every file is small.
The full layout
bjs inject walks the folder and copies every .ts/.tsx file inside. meta.json is read for its dependencies array (used by the closure walk) and carried with the rest. Anything else in the folder ā README, assets, fixtures ā is copied too when the target is arcade, because the whole folder is cp -r'd.
<Name>.ts ā Component + System
The mandatory file. Defines the Component class (data), the System class (logic), and the Events constants that announce what the System emits and listens to. Convention:
The class names follow the {Name}Component / {Name}System / {Name}Events / {Name}InputEvents convention. This is what lets the lazy SceneLoader find your classes from a JSON scene file. The pattern is non-negotiable for arcade components ā break it and the scene loader won't find them.
<Name>.core.ts ā pure logic (optional but encouraged)
Splitting the System into a thin shell and a pure core makes the logic unit-testable without any renderer. Convention:
The System imports reflectVelocity and calls it inside onUpdate. Tests against Bouncer.core.ts are pure-function tests ā no World, no renderer, no setup.
Optional. Small Systems don't need it. Large Systems are easier to maintain with it.
<Name>.test.ts ā unit tests
Vitest unit tests. They run inside the pro package's CI once injected, so they have to be self-contained:
Optional. If you have them, they come along.
<Name>.contract.test.ts ā framework contract
Uses the ecs package's runMechanicContract helper to prove your component honors the framework contract: lifecycle hooks, event names, serialization round-trip.
Optional. Strongly recommended for anything shipping in the pro packages ā it's the single biggest defense against silent regressions when the framework evolves.
<Name>.viz.tsx ā debug viz panel (optional)
A Solid panel that visualizes live state. Renders a small UI showing the Component's fields and any per-frame data the System exposes.
Optional. When present, bjs inject notices the Solid import. If the seed otherwise looks like arcade material, the Solid .tsx alone won't tip it to viz ā but if you're authoring a panel-only seed (a debugger, a stepper), this file is what gets the seed routed to viz's solid layer.
<Name>.panel.tsx ā control panel (optional)
A Solid panel that lets you change Component fields at runtime. Sliders, toggles, the kind of thing you use to tune feel during development.
Optional. Same routing rules as the viz panel.
meta.json ā marketplace metadata
Read by:
bjs injectādependenciesfeeds the closure walk so a listed sibling is co-injected.- The marketplace product page (future, see publish as a product) ā renders title, description, category badge, dependency graph.
A malformed meta.json is tolerated ā bjs inject falls back to the import scan to find dependencies. A missing meta.json is fine for inject; the marketplace publish step will ask for one later.
Why each file is optional
The required surface is just <Name>.ts. Everything else is either a quality safeguard (tests, contract) or a presentation surface (viz, panel, meta). You can ship a Bouncer with one file. You shouldn't ā but you can.
The pro packages' CI doesn't reject components that lack tests. The marketplace listing pages render fine without a viz panel. Adding each optional file makes your component better; missing any one of them doesn't block the publish.
What's next
The next section, target selection, explains how bjs inject decides between arcade and viz and how to override the choice.