ExoJS v0.12.0
[0.12.0] - 2026-06-09
The rendering-architecture and extension-system release. A composable,
context-aware render pipeline replaces the monolithic backend-level
RenderTargetPass; the Particles system is split into its own official
extension package; the Tiled map loader joins as a second official extension;
and the repository is reorganised into a pnpm-workspace monorepo with
code-split packages, a private shared configuration package, and a build-once
coordinated release pipeline.
Added
-
Composable
RenderPassarchitecture.RenderPassis a public,
abstract base class withexecute(context),enabled,label,resize,
anddestroy.RenderPipelineextendsRenderPassand owns an ordered
list of passes (addPass/insertPass/removePass), with add-time
cycle detection, reentrancy protection, and exclusive ownership (each pass
belongs to at most one pipeline). Pipelines nest freely. The
RenderPassInspectorLayervisualises the pipeline tree additively. A
context-awareCallbackRenderPassreceives the high-level
RenderingContext; its signature changed from the old backend-only
callback.BackendRenderPassremains the low-level backend interface,
bridged where needed via
callback(context) { context.backend.execute(myBackendPass) }. -
RenderNodePass. Renders a scene subtree (aRenderNode) as one pass
— into the active target, or off-screen when an optionaltargetrender
texture is set. Carries a fixedview, optionaltarget, and optional
clearcolour. The view and target are caller-owned; the target redirect
is created once and reused, so execution performs no per-frame redirect
allocation. -
Extension system.
ExtensionRegistryis a static catalogue of immutable
Extensiondescriptors. Each extension contributesRendererBindings
and/orAssetBindings. Renderer bindings are materialised once per
backend during backend creation; asset bindings and their handlers are
created once per Loader duringApplicationconstruction. After
initialisation, never looked up in the draw or load
hot paths. Extensions are provided either viaApplicationOptions.extensions
or globally via/register(which callsExtensionRegistry.registeras an
import side effect). All package roots are side-effect-free — only the
explicit/registerentry triggers registration. The registry is add-only:
registering the same object under the sameidis a no-op; a different
object under an existingidthrows. Snapshot-based deduplication and
rollback handle cleanup when backend or loader construction fails. -
@codexo/exojs-particles— official Particles extension. Extracted
from Core into an independent npm package (0.12.0, lockstep with Core).
ExposesParticleSystem, the full CPU and GPU module suite (spawn, update,
death, forces, colour/alpha/scale/velocity-over-lifetime, turbulence,
burst/rate spawn, WGSL contributions), distributions,
particlesExtension(default immutable descriptor),
createParticlesExtension({ batchSize })for application-local
configuration, andparticlesBuildInfofor frozen runtime metadata. Both
WebGL2 and WebGPU particle renderers are included. Side-effect-free root;
import@codexo/exojs-particles/registerfor global auto-registration. -
@codexo/exojs-tiled— official Tiled map extension. Loads Tiled JSON
(TMJ) maps as typed assets viaTiledMap, aTiledMapData-shaped data
model withTiledTileset,TiledLayer,TiledObject, and
TiledProperty. Maps load through the standard asset pipeline
(loader.load(TiledMap, 'map.tmj')); tileset textures are resolved
relative to the map source and loaded via theLoader. Exposes
tiledExtension,tiledBuildInfo, a side-effect-free root, and
@codexo/exojs-tiled/register. Orthogonal maps only; infinite and
non-orthogonal maps are rejected with clear errors. -
@codexo/exojs-config— private shared tooling package. Centralises
reusable TypeScript profiles, ESLint import-boundary rules, Prettier config,
Vitest project factories, the Rollup extension-config factory, the package
policy verifier, and the build-defines helper. Consumed without a build
step; not published to npm. -
Compile-time build metadata. Three canonical constants —
__DEV__(boolean),__VERSION__(per-package version),__REVISION__
(short Git SHA, appended with-dirtywhen local changes exist) — are
statically replaced at build time and tree-shaken in production. A public
frozenbuildInfo(Core) /particlesBuildInfo(Particles) /
tiledBuildInfo(Tiled) API exposes version, revision, and development
flag at runtime. -
Build-once coordinated release pipeline.
release:preparebuilds the
three official packages exactly once, packs tarballs (--ignore-scripts),
hashes them, runs ATTW and external-consumer smoke, assembles a Full GitHub
Release ZIP, and records arelease-manifest.json+checksums.sha256.
release:publishre-hashes the same files (refusing on drift), publishes
in ordered phases (Core → Particles → Tiled) to a temporary dist-tag, and
promotes tolatestonly after all three succeed. -
Rebuilt guide experience. A new information architecture with a "Start
Here" learning path, per-chapter learning goals and prerequisites,
Previous/Next navigation, and a Guide → Playground → API flow that links
each concept to a runnable example and its API page. New Project Structure,
Troubleshooting, and Deployment chapters, an Orb Dodge build walkthrough,
and source-backed snippets that render real, type-checked code straight
from the example sources. -
create-exo-appscaffolder. Start a typed project with
npm create exo-app, choosing from three templates —minimal,
game-starter, andaudio-reactive. An interactive prompt guides template
selection in a TTY; non-interactive environments default tominimal. This
is the first publiccreate-exo-apprelease (0.1.0), versioned
independently of the engine. -
Playground navigation. A "Start Here" featured set, full-text search,
category filtering, concrete per-example descriptions, and the Orb Dodge
sample turn the example catalog into something browsable rather than a flat
list. -
@codexo/exojs/debugentry point. Debug layers and overlays ship under
a dedicated@codexo/exojs/debugsubpath, bundled as an external-core
dist/exo.debug.esm.jsthat imports the engine at runtime rather than
duplicating it — ready for import-map consumption alongside the core.
Changed
-
RenderTargetPassreplaced. The publicRenderTargetPassis removed.
Internally,BackendTargetPasshandles target redirection.RenderNodePass
andCallbackRenderPassaccept an optionaltargetrender texture;
redirection is managed transparently through the internal target-redirect
path. -
CallbackRenderPasssignature. The callback now receives the high-level
RenderingContextinstead of only the low-levelRenderBackend.
context.render(node)and the full public draw surface are available; the
previouscontext.backendAPI is still accessible. -
Package-private
#subpath imports. Internal source imports moved from
@/pathaliases to Nodepackage.json#imports-based#pathspecifiers,
resolved via custom conditions (@codexo/source,
@codexo/exojs-particles-source). The declaration-rewrite script is
removed;.d.tsfiles keep#verbatim and resolve through each
package's imports map. Public consumer imports (@codexo/exojs,
@codexo/exojs-particles) are unchanged. -
Docs track the real API. Guide code blocks are type-checked against the
engine in CI, the guides teach the currentRenderingContextdraw API, and
the custom-shader chapter was corrected to the realShaderSource+
MeshMaterialmodel. Internal guide and API prose links are validated, and
the API reference is regenerated deterministically so the committed pages
stay in sync with the source.astro checkis clean.
Fixed
- Published TypeScript declarations resolve for consumers. The emitted
.d.tsfiles shipped internal@/…path aliases that a consumer's
TypeScript could not resolve, which silently dropped inherited members from
the public types (for example,GraphicslostDrawablemethods like
setPositionandrotate). Declarations now retain standards-based
package-private#specifiers, resolved through each package's
package.json#importsmap, so consumers receive the complete type surface
without a post-emit alias rewrite.
Breaking changes
-
Particles extracted from Core. All particle imports (
ParticleSystem,
modules, distributions) moved to@codexo/exojs-particles. Core no longer
exports any particle types. Consumers must add the new dependency and update
imports. -
RenderPassis now an abstract class; the old backend-only interface is
renamed toBackendRenderPass. The newRenderPass.execute(context)
receives aRenderingContext. Low-level backend pass implementations now
implementBackendRenderPassinstead. -
RenderTargetPassremoved. UseRenderNodePassor
CallbackRenderPasswith an optionaltarget. Advanced backend-level
integrations implementBackendRenderPassand execute it through
context.backend.execute(...). -
CallbackRenderPasscallback signature changed. The callback parameter
is nowRenderingContext(notRenderBackend). -
@/path aliases removed (internal). Internal source imports use
#subpath imports. Downstream forks or plugins that imported from engine
internals via@/must update to the#convention. Public consumer
imports (@codexo/exojs,@codexo/exojs-particles, etc.) are unchanged. -
No full aggregator package. Core and the official extensions remain
separate npm packages; no full aggregator package or Core/fullentry is
introduced.