From 92350f0d57b7ad3e5c240a758ca347f7a16eaa14 Mon Sep 17 00:00:00 2001 From: Exoridus <1218727+Exoridus@users.noreply.github.com> Date: Thu, 24 Sep 2026 05:38:04 +0200 Subject: [PATCH 01/31] docs: clarify product entry and rebuild the onboarding path --- README.md | 193 +++++------------- _migration/source.json | 13 ++ .../content/guide/getting-started/setup.mdx | 98 +++++---- .../guide/getting-started/what-is-exojs.mdx | 55 +++-- .../getting-started/your-first-scene.mdx | 103 +++------- 5 files changed, 172 insertions(+), 290 deletions(-) create mode 100644 _migration/source.json diff --git a/README.md b/README.md index 4ebf26240..3cb092d86 100644 --- a/README.md +++ b/README.md @@ -5,55 +5,32 @@ ExoJS -[![Latest](https://img.shields.io/github/v/release/Exoridus/ExoJS?style=for-the-badge&label=Latest&logo=github&color=44cc11)](https://github.com/Exoridus/ExoJS/releases/latest) -[![npm](https://img.shields.io/npm/v/%40codexo%2Fexojs?style=for-the-badge&logo=npm&label=npm&color=44cc11)](https://www.npmjs.com/package/@codexo/exojs) -[![CI](https://img.shields.io/github/actions/workflow/status/Exoridus/ExoJS/ci.yml?branch=main&style=for-the-badge&logo=githubactions&logoColor=fff&label=CI)](https://github.com/Exoridus/ExoJS/actions/workflows/ci.yml) -[![Coverage](https://img.shields.io/codecov/c/github/Exoridus/ExoJS?style=for-the-badge&logo=codecov&logoColor=fff&label=Coverage)](https://app.codecov.io/gh/Exoridus/ExoJS) -[![License](https://img.shields.io/github/license/Exoridus/ExoJS?style=for-the-badge&color=44cc11)](https://github.com/Exoridus/ExoJS/blob/main/LICENSE) +[![npm](https://img.shields.io/npm/v/%40codexo%2Fexojs?label=npm)](https://www.npmjs.com/package/@codexo/exojs) +[![CI](https://img.shields.io/github/actions/workflow/status/Exoridus/ExoJS/ci.yml?branch=main&label=CI)](https://github.com/Exoridus/ExoJS/actions/workflows/ci.yml) +[![License](https://img.shields.io/github/license/Exoridus/ExoJS)](LICENSE) -A TypeScript-first browser 2D engine for games and interactive apps. +**A TypeScript-first 2D runtime for browser games and interactive applications.** -**[Try the playground](https://exoridus.github.io/ExoJS/en/playground/)** · **[Read the guide](https://exoridus.github.io/ExoJS/en/guide/)** · **[Browse the API](https://exoridus.github.io/ExoJS/en/api/)** +[Guide](https://exoridus.github.io/ExoJS/en/guide/) · [Playground](https://exoridus.github.io/ExoJS/en/playground/) · [API reference](https://exoridus.github.io/ExoJS/en/api/) -The ExoJS companion, a small waving robot +ExoJS brings scenes, rendering, input, audio, UI, and asset lifetimes into one application model. Build a game, a visualization, or an interactive canvas inside an existing web application. Keep the surrounding page in your web framework; use ExoJS for the canvas. -ExoJS combines an explicit scene graph with WebGPU/WebGL2 rendering, physics, audio, UI, assets, serialization, and focused extension packages. It is built as one coherent runtime rather than a renderer surrounded by unrelated integrations. +**Pre-1.0:** minor releases may change public APIs. Pin exact package versions, keep official runtime packages on a compatible release line, and read the [release notes](https://github.com/Exoridus/ExoJS/releases) before upgrading. The `next` branch can contain work that is not yet published on npm. -> **Pre-1.0:** the public API is still being refined, and minor releases may contain breaking changes. Pin exact versions in downstream projects. `1.0.0` will mark the first stable API contract. +## Start a project -## Why ExoJS - -| | | -| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | -| **TypeScript is the design input** | Strict types, discoverable APIs, typed assets and extension contracts, with `noUncheckedIndexedAccess` and `exactOptionalPropertyTypes` throughout. | -| **Two real graphics backends** | WebGPU-first rendering with automatic WebGL2 fallback, backend parity tests, custom GLSL/WGSL materials, render targets, filters, and readback. | -| **A complete 2D runtime** | Scenes, cameras, input, UI, text, audio, persistence, serialization, coroutines, and deterministic lifetime management ship together. | -| **Serious optional systems** | Native rigid-body physics, GPU particles, tilemaps, lighting, pathfinding, React bindings, and format adapters stay opt-in and tree-shakeable. | -| **Explicit ownership** | Application-scoped managers, local extension descriptors, and `Destroyable`/`DisposalScope` lifetimes avoid hidden global state. | -| **Performance is reproducible** | Structural CI gates and browser/GPU benchmark profiles record the workload, hardware, browser, versions, medians, p95s, and measurement spread. | - -## Start in 30 seconds - -Create a project and choose a starter interactively: - -```bash -npm create exo-app@latest my-game +```sh +npm create exo-app@latest my-game -- --template minimal cd my-game npm install npm run dev ``` -Or select a template directly: - -```bash -npm create exo-app@latest my-game -- --template minimal -npm create exo-app@latest my-game -- --template platformer -npm create exo-app@latest my-game -- --template top-down -``` +The starter is a Vite + TypeScript project with a visible, animated scene. The [Setup guide](https://exoridus.github.io/ExoJS/en/guide/getting-started/setup/) explains the other templates, project layout, and installation into an existing application. -The smallest application is still ordinary TypeScript: +A scene contains ordinary TypeScript state and explicitly chooses what to render: ```ts import { Application, Color, Graphics, type RenderingContext, Scene, type Seconds } from '@codexo/exojs'; @@ -61,20 +38,18 @@ import { Application, Color, Graphics, type RenderingContext, Scene, type Second class MainScene extends Scene { private readonly box = new Graphics(); - public constructor() { - super(); - + override init(): void { this.box.fillColor = Color.white; this.box.drawRectangle(-40, -40, 80, 80); - this.box.setPosition(400, 300); - this.addChild(this.box); + this.box.setPosition(this.app.width / 2, this.app.height / 2); + this.root.addChild(this.box); } - public override update(delta: Seconds): void { - this.box.rotate(delta * 90); + override update(delta: Seconds): void { + this.box.rotate(90 * delta); } - public override draw(context: RenderingContext): void { + override draw(context: RenderingContext): void { context.render(this.root); } } @@ -88,124 +63,64 @@ const app = new Application({ await app.start(MainScene); ``` -Continue with the [guide](https://exoridus.github.io/ExoJS/en/guide/), inspect runnable code in the [playground](https://exoridus.github.io/ExoJS/en/playground/), or look up a symbol in the [API reference](https://exoridus.github.io/ExoJS/en/api/). - -## What you can build - -### Rendering and presentation - -- Sprites, animated sprites, nine-slice and repeating sprites, immediate geometry, instanced batches, SDF text, bitmap text, and video. -- WebGPU and WebGL2 backends selected automatically or explicitly through `ApplicationOptions.backend`. -- Render textures, retained render plans, filter chains, visual masks, cache-as-bitmap, custom sprite materials, and custom renderers through the public renderer SDK. -- Linear and radial gradients, pixel snapping, blend modes, frame passes, asynchronous pixel readback, and render statistics including GPU memory and upload accounting. -- Forward, shadowed lightmap, and radiance-cascade lighting through `@codexo/exojs-lighting`, with normal maps, multiple light shapes, cookies, and reusable occluder sources. +Follow [Your first scene](https://exoridus.github.io/ExoJS/en/guide/getting-started/your-first-scene/) for the explanation. The [Playground](https://exoridus.github.io/ExoJS/en/playground/) supplies editable demonstrations; the [API reference](https://exoridus.github.io/ExoJS/en/api/) supplies exact contracts. -### Worlds and gameplay +## Why investigate ExoJS? -- Scene navigation with preload/unload, pause/resume, and built-in or custom transitions. -- Cameras with follow, shake, zoom, bounds clamping, and multiple views. -- Keyboard, pointer, touch, and gamepad input with action bindings, focus traversal, hit areas, and modal focus scopes. -- Native 2D rigid-body physics with continuous collision, joints, sensors, sleeping islands, contact modification, queries, and a debug overlay. -- Weighted-grid and waypoint-graph pathfinding, streamed tilemap worlds, Tiled and LDtk adapters, and Aseprite animation import. +| Capability | What it means for a project | +| --- | --- | +| **One runtime, explicit lifetimes** | Scene-scoped assets, input, systems, animation, and audio follow scene teardown. Application-level resources can outlive an individual scene. | +| **WebGPU and WebGL2** | Choose a backend or use automatic selection. Share the high-level scene API, while checking capability-specific features on target devices. | +| **Rendering beyond sprites** | Compose text, geometry, masks, filters, render targets, multiple views, and custom materials. Use the renderer SDK only when the high-level rendering paths do not fit. | +| **Optional gameplay and visual systems** | Add physics, tilemaps, pathfinding, particles, lighting, or editor-format adapters without making them mandatory Core dependencies. | +| **TypeScript throughout** | Typed scene navigation, asset loading, extension contracts, and declarations make the engine usable from an ordinary editor and build pipeline. | -### Player experience and application state - -- Screen-fixed UI widgets, themes, anchoring, scrolling, tooltips, progress bars, and labels. -- Spatial audio, audio sprites, generated and streamed sources, buses, effects, analysis, worklets, and beat detection. -- Typed asset catalogs, deduplicated loading, scoped asset lifetimes, binary containers, and persistent key-value stores. -- Scene serialization, prefabs, deterministic systems, tweens, signals, and frame-budgeted coroutines for long-running work. +ExoJS is a code-first runtime, not a visual game editor. Its benchmarks describe particular workloads, not a guarantee that an arbitrary application will be faster than one built with another engine. ## Packages -Install only the systems your project uses. Official runtime packages share the Core release line and declare compatible peer ranges. - -| Package | Purpose | -| ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -| [`@codexo/exojs`](https://www.npmjs.com/package/@codexo/exojs) | Core scene, rendering, audio, UI, asset, and serialization runtime | -| [`@codexo/exojs-physics`](https://www.npmjs.com/package/@codexo/exojs-physics) | Native 2D rigid-body physics with a TGS-Soft solver | -| [`@codexo/exojs-particles`](https://www.npmjs.com/package/@codexo/exojs-particles) | GPU-compute particle simulation with a CPU fallback | -| [`@codexo/exojs-tilemap`](https://www.npmjs.com/package/@codexo/exojs-tilemap) | Format-neutral tilemap runtime, streaming, object spawning, and rendering | -| [`@codexo/exojs-tiled`](https://www.npmjs.com/package/@codexo/exojs-tiled) | Tiled JSON adapter | -| [`@codexo/exojs-ldtk`](https://www.npmjs.com/package/@codexo/exojs-ldtk) | LDtk world and level adapter | -| [`@codexo/exojs-aseprite`](https://www.npmjs.com/package/@codexo/exojs-aseprite) | Aseprite sprite-sheet and animation adapter | -| [`@codexo/exojs-tilemap-physics`](https://www.npmjs.com/package/@codexo/exojs-tilemap-physics) | Static physics colliders generated from tilemap collision geometry | -| [`@codexo/exojs-lighting`](https://www.npmjs.com/package/@codexo/exojs-lighting) | Forward, shadowed lightmap, and radiance-cascade 2D lighting | -| [`@codexo/exojs-pathfinding`](https://www.npmjs.com/package/@codexo/exojs-pathfinding) | A* pathfinding over weighted grids and waypoint graphs | -| [`@codexo/exojs-audio-fx`](https://www.npmjs.com/package/@codexo/exojs-audio-fx) | Audio effects, worklets, analysis, and beat detection | -| [`@codexo/exojs-react`](https://www.npmjs.com/package/@codexo/exojs-react) | React canvas hosting, scene composition, and hooks | - -Project tooling is available separately: - -| Package | Purpose | -| -------------------------------------------------------------------------- | -------------------------------------------------------------- | -| [`create-exo-app`](https://www.npmjs.com/package/create-exo-app) | Interactive project scaffolding and maintained starters | -| [`@codexo/exojs-cli`](./packages/exojs-cli) | Static serving, project checks, scaffolding, and asset packs | -| [`@codexo/exojs-build`](https://www.npmjs.com/package/@codexo/exojs-build) | Vite/Rollup transforms for shaders, workers, and AudioWorklets | -| [`@codexo/eslint-plugin-exojs`](./packages/eslint-plugin-exojs) | Lifecycle and hot-path correctness rules for ExoJS projects | - -## Installation and distribution - -```bash -npm install @codexo/exojs -``` - -ExoJS is ESM-first and works with modern bundlers. Optional packages install independently, for example: +Core owns the application, scenes, scene graph, rendering, input, UI, asset loading, and basic audio. Install optional packages for the systems you use. Each package README contains its activation example and constraints. -```bash -npm install @codexo/exojs @codexo/exojs-physics @codexo/exojs-lighting -``` - -Prebuilt script-tag bundles are also included: `dist/exo.iife.js` contains Core, while `dist/exo.full.iife.js` contains Core and the official runtime extensions except React. Both expose the `Exo` global. Minified variants are provided alongside them. +| Package | Use it for | +| --- | --- | +| [`@codexo/exojs`](https://www.npmjs.com/package/@codexo/exojs) | Core runtime | +| [`@codexo/exojs-physics`](packages/exojs-physics/README.md) | 2D rigid bodies, colliders, joints, and queries | +| [`@codexo/exojs-particles`](packages/exojs-particles/README.md) | Particle emitters and simulation | +| [`@codexo/exojs-tilemap`](packages/exojs-tilemap/README.md) | Tile rendering, chunks, and world loading | +| [`@codexo/exojs-tiled`](packages/exojs-tiled/README.md), [`@codexo/exojs-ldtk`](packages/exojs-ldtk/README.md) | Tiled and LDtk imports | +| [`@codexo/exojs-aseprite`](packages/exojs-aseprite/README.md) | Aseprite sheets and tagged animations | +| [`@codexo/exojs-tilemap-physics`](packages/exojs-tilemap-physics/README.md) | Physics colliders from tilemap geometry | +| [`@codexo/exojs-lighting`](packages/exojs-lighting/README.md) | Forward, shadowed lightmap, and radiance-cascade lighting | +| [`@codexo/exojs-pathfinding`](packages/exojs-pathfinding/README.md) | Weighted grids and waypoint graphs | +| [`@codexo/exojs-audio-fx`](packages/exojs-audio-fx/README.md) | Audio effects, analysis, worklets, and beat detection | +| [`@codexo/exojs-react`](packages/exojs-react/README.md) | React hosting and hooks | -## Measured performance +Project tooling is separate: [create-exo-app](packages/create-exo-app/README.md) scaffolds projects; [exojs-cli](packages/exojs-cli/README.md) provides project and asset commands; [exojs-build](packages/exojs-build/README.md) transforms shaders, workers, and worklets; [eslint-plugin-exojs](packages/eslint-plugin-exojs/README.md) checks lifecycle and hot-path mistakes. -ExoJS maintains two complementary kinds of performance evidence: +## Distribution -- deterministic structural gates for draw calls, batches, binds, uploads, and other exact work counters; -- real-browser comparison profiles for rendering and physics, with pinned competitors and stamped hardware, browser, workload, warmup, sample count, median, p95, and run-to-run spread. +For a bundler-based application: -The numbers are deliberately not copied into this README because they change with the engine, competitor versions, browser, and reference machine. Read the [current published profiles](./packages/exojs-bench/results) and the [benchmark methodology](./packages/exojs-bench/docs/harness.md) together. +```sh +npm install --save-exact @codexo/exojs +``` -## Roadmap +The package provides ESM and TypeScript declarations. It also includes script-tag bundles: `dist/exo.iife.js` for Core and `dist/exo.full.iife.js` for Core plus official runtime extensions except React, with minified variants alongside them. Both expose the `Exo` global. Do not mix independent copies of Core in one application. -Work toward the `1.0.0` API freeze is directional, not a release commitment. Current longer-term areas include: +## Performance evidence -- rich text with style spans and inline content; -- worker-backed execution through the same coroutine ownership model; -- platform adapters for Worker and headless runtimes; -- the final public API audit and stabilization pass. +The [benchmark pages](https://exoridus.github.io/ExoJS/en/benchmarks/) explain the measured scenarios and limitations. The [versioned results](packages/exojs-bench/results/README.md) preserve provenance; the [harness documentation](packages/exojs-bench/docs/harness.md) explains reproduction. Structural counters and browser timings answer different questions. Neither produces an overall engine winner. ## Contributing -Development requires Node 24 and the pnpm version pinned in `package.json`. +Repository development requires Node 24 and the pnpm version pinned in `package.json`: -```bash +```sh pnpm bootstrap:dev pnpm doctor ``` -`bootstrap:dev` installs dependencies and hooks, builds Core and every package, links benchmark competitors, installs Chromium, and reports anything still missing. During development, use the narrow command for the area you changed: - -```bash -pnpm typecheck -pnpm lint -pnpm test -pnpm build:all -pnpm lanes -``` - -See [CONTRIBUTING.md](./CONTRIBUTING.md) for branch policy, imports, package boundaries, public API conventions, validation, and distribution rules. - -## Links - -- GitHub Pages: -- Guide: -- API reference: -- Playground: -- Repository: -- Releases: -- Issues: -- Changelog: [CHANGELOG.md](CHANGELOG.md) +Use [CONTRIBUTING.md](CONTRIBUTING.md) for package boundaries, code conventions, and change-specific validation. Product changes belong in the relevant Guide, package README, or source JSDoc; development history belongs in [releases](https://github.com/Exoridus/ExoJS/releases), [CHANGELOG.md](CHANGELOG.md), and Git. ## License diff --git a/_migration/source.json b/_migration/source.json new file mode 100644 index 000000000..a6e53bdc6 --- /dev/null +++ b/_migration/source.json @@ -0,0 +1,13 @@ +{ + "schemaVersion": 1, + "repository": "Exoridus/ExoJS", + "sourceRef": "next", + "sourceCommit": "d0538029bc81095cbc969a702a343fd382da79bb", + "sourceTree": "af5b7642ecf4d710c2eb025ce0b95e3e63b6b806", + "deliveryBranch": "docs/documentation-refresh-d0538029", + "openPullRequestsAtAuditStart": [], + "unmergedCodeIncluded": false, + "deliveryMode": "isolated GitHub branch with complete files and temporary migration aids", + "temporaryPaths": ["_migration/"], + "validationPolicy": "Report commands as executed only when their output has been observed." +} diff --git a/site/src/content/guide/getting-started/setup.mdx b/site/src/content/guide/getting-started/setup.mdx index cfd05f9f9..cd3e83b22 100644 --- a/site/src/content/guide/getting-started/setup.mdx +++ b/site/src/content/guide/getting-started/setup.mdx @@ -1,84 +1,92 @@ --- -title: 'Setup' -description: 'Create a typed ExoJS project with create-exo-app, run the dev server, and produce a production build.' +title: 'Setup and project layout' +description: 'Create a Vite and TypeScript project, choose a starter, locate its scene code, and build it for production.' --- -import Callout from '../../../components/Callout.astro'; import NextStep from '../../../components/NextStep.astro'; -# Setup +# Setup and project layout -The fastest way to start is `create-exo-app`. It scaffolds a Vite + TypeScript project that already imports `@codexo/exojs`, runs a scene, and is ready for `dev` and `build`. - -## Create a project - -Run the scaffolder and follow the prompts: - -```sh -npm create exo-app@latest my-game -``` - -Then install dependencies and start the dev server: +`create-exo-app` creates a Vite + TypeScript project with an ExoJS scene already running. Use `minimal` for the shortest learning path: ```sh +npm create exo-app@latest my-game -- --template minimal cd my-game npm install npm run dev ``` -The dev server prints a local URL. Open it and you'll see a running scene with hot reload. +Open the local URL printed by Vite. Keep the development server running while editing the scene. Use a Node version supported by the generated project's tooling; contributing to the ExoJS repository itself requires Node 24. -## Choose a template +## Choose a starting point -`create-exo-app` ships three templates. Pass one with `--template`, or pick it interactively: +Omit `--template` to use the interactive picker, or choose one explicitly: -| Template | Starts with | -|----------|-------------| -| `minimal` | One scene drawing and animating a single shape — the smallest real project. | -| `game-starter` | A player object, a game scene, and a game-over scene with restart. | -| `audio-reactive` | An analyser-driven scene that turns sound into a live spectrum. | +| Template | Starting point | +| --- | --- | +| `minimal` | One scene and one visible animated object. | +| `game-starter` | Keyboard-controlled gameplay, player code, and a game-over scene. | +| `platformer` | Side-scrolling movement, physics bodies, camera follow, and jump handling. | +| `top-down` | Tilemaps, physics, and click-to-move pathfinding, with procedural or Tiled input. | +| `ui-app` | A settings screen using the Core UI widgets. | +| `audio-reactive` | Shapes and animation driven by an audio analyser. | -```sh -npm create exo-app@latest my-game -- --template game-starter +The specialized templates include more systems; they are starting projects, not prerequisites for learning the engine. Template availability follows the version of `create-exo-app` you run. This Guide describes the repository's current template set, which may be ahead of an older installed release. + +## Project layout + +The minimal starter separates application startup from scene behavior: + +```text +my-game/ + index.html + package.json + tsconfig.json + vite.config.ts + public/ + assets/ + src/ + main.ts + scenes/ + MainScene.ts ``` -The [Project structure](/ExoJS/en/guide/getting-started/project-structure/) chapter walks through what each file does. +`src/main.ts` creates the application, registers the scene class, mounts the canvas, and awaits `app.start(MainScene)`. `src/scenes/MainScene.ts` holds the objects, frame updates, and drawing. The next chapter explains both files using the maintained template source. -## Build for production +Keep asset files under `public/assets/` when you want them served unchanged. `public/` is not part of the URL: `public/assets/hero.png` is requested as `assets/hero.png`. A loader `basePath` can supply the common directory; do not repeat it in individual asset paths. Deployment under a subdirectory needs the base-path treatment in [Deployment](/ExoJS/en/guide/shipping/deployment/). -Vite produces a static bundle you can host anywhere: +## Production build ```sh -npm run build # output in dist/ -npm run preview # serve the production build locally +npm run build +npm run preview ``` -The [Deployment](/ExoJS/en/guide/shipping/deployment/) chapter covers hosting, base paths, and assets. - -## Add ExoJS to an existing project +`build` produces the static application in `dist/`. `preview` serves that build locally; it is a way to check the output, not a production hosting service. Verify the production build as well as the development server, especially asset URLs and capability-dependent code. -If you already have a bundler-based project, install the runtime directly: +## Add ExoJS to an existing application ```sh -npm install @codexo/exojs +npm install --save-exact @codexo/exojs ``` -The package ships as ESM with TypeScript declarations, so any modern bundler (Vite, esbuild, Rollup, webpack, Parcel) picks up both the code and the types. ExoJS renders into a regular `HTMLCanvasElement`; create one yourself or let `Application` create it for you: +Use the package's ESM entry point from your bundler. TypeScript declarations are included. Pin compatible exact versions for any official runtime packages you add. + +A canvas can be supplied or created by the application: ```ts import { Application } from '@codexo/exojs'; -// Pass an existing canvas... -const canvas = document.querySelector('#scene')!; -const app = new Application({ canvas: { element: canvas } }); +const canvas = document.querySelector('#scene'); +if (canvas === null) { + throw new Error('Missing #scene canvas.'); +} -// ...or let the application create one and mount app.element yourself. +const app = new Application({ canvas: { element: canvas } }); ``` - -`app.element` is the active `HTMLCanvasElement` the runtime renders into, or `null` when the surface is an `OffscreenCanvas`. Append it wherever your layout needs it, or let `canvas.mount` do it. - +This configures an application; it does not start a scene. Register and start one as shown next. For a generated canvas, use `canvas.mount` to place it in the DOM. `app.element` is the HTML canvas, or `null` for an offscreen surface. A framework host must also destroy the application when that host is permanently removed; the [React guide](/ExoJS/en/guide/integrations/react/) covers the maintained React boundary. - -See where the entry point, scenes, and assets live in a generated project. + +Draw and animate an object before introducing asset loading. diff --git a/site/src/content/guide/getting-started/what-is-exojs.mdx b/site/src/content/guide/getting-started/what-is-exojs.mdx index ed4d86134..6ff39a54b 100644 --- a/site/src/content/guide/getting-started/what-is-exojs.mdx +++ b/site/src/content/guide/getting-started/what-is-exojs.mdx @@ -1,57 +1,52 @@ --- title: 'What is ExoJS?' -description: 'A short tour of what ExoJS is, where it fits, how a project is shaped, and how to read this guide.' +description: 'Decide whether ExoJS fits your project and learn the application, scene, and documentation model.' --- -import Callout from '../../../components/Callout.astro'; -import SourceSnippet from '../../../components/SourceSnippet.astro'; import NextStep from '../../../components/NextStep.astro'; # What is ExoJS? -ExoJS is a TypeScript-first, zero-dependency 2D runtime for browser games and interactive apps. +ExoJS is a TypeScript-first 2D runtime for browser games and interactive applications. It provides rendering, scenes, input, audio, UI, and asset loading; optional packages add systems such as physics, tilemaps, particles, lighting, and pathfinding. -It gives you the core pieces of a small engine — scenes, sprites, graphics, input, audio, effects, render targets, and a frame loop — while staying close to regular web development. You can use it from JavaScript or TypeScript, render into a canvas, and keep project structure in code. +Use it for a canvas-based game, visualization, tool, or interactive surface. It is code-first, not a visual game editor. A mostly DOM-based application can keep its existing UI framework and let ExoJS own only the canvas. The [React integration](/ExoJS/en/guide/integrations/react/) provides that boundary for React applications. -## Where ExoJS fits +## The model in one minute -Use ExoJS when you want to build a browser-based 2D game, visualization, toy, editor, or interactive scene with code. +An [`Application`](/ExoJS/en/api/application/) owns the canvas, rendering backend, frame loop, and application-level services. A [`Scene`](/ExoJS/en/api/scene/) organizes one screen or activity: it loads resources, sets up objects, updates state, and chooses what to draw. -It gives you engine-level building blocks without forcing a particular app framework or editor workflow. If you're building a mostly DOM-based interface, keep using your UI framework of choice. If you're building an interactive canvas surface, ExoJS can own that part. +The scene's `root` is a hierarchy of drawable objects. Adding a child establishes hierarchy; `draw(context)` explicitly renders it. The separate `scene.ui` layer is a screen-fixed overlay and is rendered automatically. Scene-scoped services follow the scene's lifetime; application-scoped services can be shared across scenes. -## How a project is shaped +You do not need custom renderers, an entity-component framework, or an extension to draw your first object. Begin with one application and one scene, then add structure when the project needs it. -A typical ExoJS project starts with two pieces: an [`Application`](/ExoJS/en/api/application/) and a [`Scene`](/ExoJS/en/api/scene/). +## Coming from another engine? -The application owns the runtime configuration — render backend, canvas size, asset path, frame loop. The scene loads resources, updates state, and draws each frame. +Treat an ExoJS scene as a lifecycle host, not as a complete saved project. Containers compose transforms and drawing order. Scene navigation manages activation, pause, retention, and teardown. Physics bodies, rendered objects, and serialized data are separate concepts even when a helper connects them. - +Those boundaries are useful when embedding a canvas in a web application: the browser page still owns its routing, DOM, and accessibility. ExoJS owns the runtime you put inside it. -From there, the same shape scales up: add sprites, split logic across multiple scenes, attach input, play audio, render effects, and move code into reusable objects as the project grows. +## Distribution and maturity -## Distribution +ExoJS provides ESM, TypeScript declarations, and prebuilt script-tag bundles. `exo.iife.js` contains Core; `exo.full.iife.js` also includes the official runtime extensions except React. Both expose `Exo`. The [Setup guide](/ExoJS/en/guide/getting-started/setup/) starts with ESM and a bundler. -ExoJS ships as ESM with TypeScript declarations. JavaScript projects can use the runtime directly; TypeScript users get typed APIs out of the box. A standalone browser bundle for ` +const deploymentBase = new URL(import.meta.env.BASE_URL, document.baseURI); +const assetBase = new URL('assets/', deploymentBase).href; +const app = new Application({ loader: { basePath: assetBase } }); ``` -For the debug bundle (`exo.debug.esm.js`), note that it is an **external-core** bundle: it imports `@codexo/exojs` from outside itself. You must map that specifier to the core bundle using an import map: +Add the scene and canvas options from your application entry point; the fragment above isolates URL configuration. A descriptor such as `image/hero.png` then resolves below that asset base. Do not prefix it again with `assets/` or `public/`. -```html - +Files under `public/` are copied unchanged. Imported assets follow the bundler's asset pipeline. Check exact filename casing and ensure that a missing asset returns a real failure rather than the host's HTML application fallback. - -``` +Use a static host that can serve the entire output with correct relative paths and headers. For an embedded HTML game, package the contents of `dist/` at the archive root and configure the embedding surface to match the application's sizing policy. -Without the import map, `exo.debug.esm.js` cannot resolve `@codexo/exojs` and will fail with a module not found error. The debug bundle is not a standalone engine; it extends the core bundle. +For GitHub Pages or another CI-based host, use its maintained deployment workflow and publish only after the build succeeds. Keep hosting credentials in the host's secret store, not in application JavaScript. A browser bundle cannot keep a secret from its user. -Import maps are supported in all modern browsers. If you need to support older environments, use the npm + Vite workflow instead. +Verify JavaScript MIME types, font and media responses, HTTPS, and any required CORS headers. Both `text/javascript` and other standards-compatible JavaScript MIME types can be valid; an HTML fallback or `text/plain` response for a module is a common failure. -### Script-tag (IIFE) bundle +## Using a release bundle without a bundler -If you don't have any module-aware setup — no bundler, no import maps, just a plain HTML file with ` - -``` - -There is no `import` statement anywhere in this path — `Exo` is already in scope by the time the second ` + ``` -Prefer the ES module bundle when your host supports import maps and you want the browser to load only what you use. Prefer the IIFE bundle when you need a true zero-build setup — for example a single static HTML file, a CMS or forum post that only allows pasting a ` ``` -The Core-only bundle is `exo.iife.min.js`. `exo.full.iife.min.js` contains Core and the official runtime packages except React on the same `Exo` global. Choose one; do not load separate independent Core copies together. The full bundle includes APIs, but package-specific systems and application configuration still need to be used correctly. +The Core-only bundle is `exo.iife.min.js`. `exo.full.iife.min.js` contains Core and the official runtime packages except React and tilemap physics on the same `Exo` global. Choose one; do not load separate independent Core copies together. The full bundle includes APIs, but package-specific systems and application configuration still need to be used correctly. For browser ESM, the debug bundle is external-Core and needs `@codexo/exojs` resolved to the same Core module, for example through an import map. Keep the map, engine, debug module, and optional packages version-aligned. Avoid unversioned CDN URLs in a reproducible production deployment. diff --git a/src/debug/RenderPassInspectorLayer.ts b/src/debug/RenderPassInspectorLayer.ts index 7edbc1972..1e30dfc5d 100644 --- a/src/debug/RenderPassInspectorLayer.ts +++ b/src/debug/RenderPassInspectorLayer.ts @@ -61,9 +61,26 @@ export interface RenderPipelineRow { } /** - * Inspects the visible scene-root nodes that have attached filters and optionally displays a logical render pipeline. + * Debug layer that lists every visible {@link RenderNode} in the scene tree + * with an attached filter chain, and optionally a {@link RenderPipeline} set + * with {@link setPipeline}. Renders a compact text panel with per-node rows + * showing the filter sequence, bounding-box dimensions, and mask/cache status. * - * The pass total counts attached filters plus a mask flag per collected entry. It is a structural estimate, not a hardware-pass count or GPU timing: multi-step filters are not expanded, cached work is not subtracted, and mask-only nodes are not collected. Returned entries are reused on update. + * Use during development to answer: + * + * - "Is my filter actually attached?" - it appears in the list + * - "Is this node configured to render through a cache?" - `[cached]` flag + * + * The pass total counts the attached filters plus one per collected entry with + * a mask. It is a structural estimate, not a hardware-pass count or GPU timing: + * multi-step filters are not expanded, cached work is not subtracted, and + * mask-only nodes are not collected. The returned entries are reused on every + * update. + * + * For deep per-pass inspection (intermediate render-target contents, GLSL/WGSL + * source, uniform values), use Spector.js or Chrome DevTools' WebGPU panel. + * On WebGPU, draws with a custom mesh or sprite material carry debug-group + * labels, so those tools can tell them apart. */ export class RenderPassInspectorLayer extends DebugLayer { private readonly _entries: RenderPassInspectorEntry[] = []; From 846eb9b2bccd3e72c9068970015d94288cef277a Mon Sep 17 00:00:00 2001 From: Exoridus Date: Sat, 26 Sep 2026 09:05:02 +0200 Subject: [PATCH 30/31] docs(site): share one localized guide chapter redirect --- .../pages/GuideChapterRedirect.astro | 44 +++++++++++++++++++ .../components/pages/GuidePartRedirect.astro | 29 +----------- site/src/pages/de/guide/debugging/index.astro | 14 +----- .../project-structure/index.astro | 6 +-- site/src/pages/de/guide/input/index.astro | 14 +----- .../pages/de/guide/integrations/index.astro | 14 +----- .../pages/de/guide/pathfinding/index.astro | 14 +----- site/src/pages/de/guide/physics/index.astro | 14 +----- .../recipes/audio-reactive-scene/index.astro | 6 +-- .../de/guide/recipes/hud-overlay/index.astro | 6 +-- .../de/guide/recipes/split-screen/index.astro | 6 +-- site/src/pages/en/guide/debugging/index.astro | 14 +----- .../project-structure/index.astro | 6 +-- site/src/pages/en/guide/input/index.astro | 14 +----- .../pages/en/guide/integrations/index.astro | 14 +----- .../pages/en/guide/pathfinding/index.astro | 14 +----- site/src/pages/en/guide/physics/index.astro | 14 +----- .../recipes/audio-reactive-scene/index.astro | 6 +-- .../en/guide/recipes/hud-overlay/index.astro | 6 +-- .../en/guide/recipes/split-screen/index.astro | 6 +-- 20 files changed, 90 insertions(+), 171 deletions(-) create mode 100644 site/src/components/pages/GuideChapterRedirect.astro diff --git a/site/src/components/pages/GuideChapterRedirect.astro b/site/src/components/pages/GuideChapterRedirect.astro new file mode 100644 index 000000000..6bb294c0c --- /dev/null +++ b/site/src/components/pages/GuideChapterRedirect.astro @@ -0,0 +1,44 @@ +--- +import { getCollection } from 'astro:content'; +import type { CollectionEntry } from 'astro:content'; +import { GUIDE_CHAPTER_BY_PATH } from '../../lib/guide-structure'; + +interface Props { + locale: 'en' | 'de'; + chapterPath: string; +} + +const { locale, chapterPath } = Astro.props; +const chapter = GUIDE_CHAPTER_BY_PATH.get(chapterPath); + +if (!chapter) { + throw new Error(`Unknown guide chapter: ${chapterPath}`); +} + +const guideEntries = await getCollection('guide'); +const chapterTitle = + guideEntries.find((entry: CollectionEntry<'guide'>) => entry.id.replace(/\.(md|mdx)$/, '') === chapter.path)?.data.title ?? chapter.slug; + +const targetHref = `${import.meta.env.BASE_URL}${locale}/guide/${chapter.path}/`; +const redirectLabel = locale === 'de' ? 'Weiterleitung zu' : 'Redirecting to'; +--- + + + + + + + + + {chapterTitle} | ExoJS Guide + + + + +

{redirectLabel} {chapterTitle}...

+ + diff --git a/site/src/components/pages/GuidePartRedirect.astro b/site/src/components/pages/GuidePartRedirect.astro index 390370de0..05eae34c6 100644 --- a/site/src/components/pages/GuidePartRedirect.astro +++ b/site/src/components/pages/GuidePartRedirect.astro @@ -1,7 +1,6 @@ --- -import { getCollection } from 'astro:content'; -import type { CollectionEntry } from 'astro:content'; import { GUIDE_PARTS } from '../../lib/guide-structure'; +import GuideChapterRedirect from './GuideChapterRedirect.astro'; interface Props { locale: 'en' | 'de'; @@ -19,30 +18,6 @@ const firstChapter = part.chapters[0]; if (!firstChapter) { throw new Error(`Guide part has no chapters: ${partSlug}`); } - -const guideEntries = await getCollection('guide'); -const firstChapterTitle = - guideEntries.find((entry: CollectionEntry<'guide'>) => entry.id.replace(/\.(md|mdx)$/, '') === firstChapter.path)?.data - .title ?? firstChapter.slug; - -const targetHref = `${import.meta.env.BASE_URL}${locale}/guide/${firstChapter.path}/`; -const redirectLabel = locale === 'de' ? 'Weiterleitung zu' : 'Redirecting to'; --- - - - - - - {part.title} | ExoJS Guide - - - - -

{redirectLabel} {firstChapterTitle}...

- - + diff --git a/site/src/pages/de/guide/debugging/index.astro b/site/src/pages/de/guide/debugging/index.astro index c32716a4a..9bbb7ae8e 100644 --- a/site/src/pages/de/guide/debugging/index.astro +++ b/site/src/pages/de/guide/debugging/index.astro @@ -1,15 +1,5 @@ --- -const target = `${import.meta.env.BASE_URL}de/guide/debugging/debugging-and-inspection/`; +import GuideChapterRedirect from '../../../../components/pages/GuideChapterRedirect.astro'; --- - - - - - - - - ExoJS Guide - -

Continue to the guide

- + diff --git a/site/src/pages/de/guide/getting-started/project-structure/index.astro b/site/src/pages/de/guide/getting-started/project-structure/index.astro index 226de51c5..8165f50f4 100644 --- a/site/src/pages/de/guide/getting-started/project-structure/index.astro +++ b/site/src/pages/de/guide/getting-started/project-structure/index.astro @@ -1,5 +1,5 @@ --- -const target = `${import.meta.env.BASE_URL}de/guide/getting-started/setup/`; +import GuideChapterRedirect from '../../../../../components/pages/GuideChapterRedirect.astro'; --- - -Guide page moved

This topic is now part of the consolidated Guide chapter.

+ + diff --git a/site/src/pages/de/guide/input/index.astro b/site/src/pages/de/guide/input/index.astro index f9a435b1a..38e18511c 100644 --- a/site/src/pages/de/guide/input/index.astro +++ b/site/src/pages/de/guide/input/index.astro @@ -1,15 +1,5 @@ --- -const target = `${import.meta.env.BASE_URL}de/guide/input/keyboard-and-actions/`; +import GuideChapterRedirect from '../../../../components/pages/GuideChapterRedirect.astro'; --- - - - - - - - - ExoJS Guide - -

Continue to the guide

- + diff --git a/site/src/pages/de/guide/integrations/index.astro b/site/src/pages/de/guide/integrations/index.astro index f9c2702d0..b3c10a528 100644 --- a/site/src/pages/de/guide/integrations/index.astro +++ b/site/src/pages/de/guide/integrations/index.astro @@ -1,15 +1,5 @@ --- -const target = `${import.meta.env.BASE_URL}de/guide/integrations/react/`; +import GuideChapterRedirect from '../../../../components/pages/GuideChapterRedirect.astro'; --- - - - - - - - - ExoJS Guide - -

Continue to the guide

- + diff --git a/site/src/pages/de/guide/pathfinding/index.astro b/site/src/pages/de/guide/pathfinding/index.astro index 055da2b4a..b8537e833 100644 --- a/site/src/pages/de/guide/pathfinding/index.astro +++ b/site/src/pages/de/guide/pathfinding/index.astro @@ -1,15 +1,5 @@ --- -const target = `${import.meta.env.BASE_URL}de/guide/pathfinding/grid-pathfinding/`; +import GuideChapterRedirect from '../../../../components/pages/GuideChapterRedirect.astro'; --- - - - - - - - - ExoJS Guide - -

Continue to the guide

- + diff --git a/site/src/pages/de/guide/physics/index.astro b/site/src/pages/de/guide/physics/index.astro index 6d426aa6f..1a65a721f 100644 --- a/site/src/pages/de/guide/physics/index.astro +++ b/site/src/pages/de/guide/physics/index.astro @@ -1,15 +1,5 @@ --- -const target = `${import.meta.env.BASE_URL}de/guide/physics/physics-basics/`; +import GuideChapterRedirect from '../../../../components/pages/GuideChapterRedirect.astro'; --- - - - - - - - - ExoJS Guide - -

Continue to the guide

- + diff --git a/site/src/pages/de/guide/recipes/audio-reactive-scene/index.astro b/site/src/pages/de/guide/recipes/audio-reactive-scene/index.astro index 49d5f6142..cccaf29ad 100644 --- a/site/src/pages/de/guide/recipes/audio-reactive-scene/index.astro +++ b/site/src/pages/de/guide/recipes/audio-reactive-scene/index.astro @@ -1,5 +1,5 @@ --- -const target = `${import.meta.env.BASE_URL}de/guide/audio/audio-reactive-visualization/`; +import GuideChapterRedirect from '../../../../../components/pages/GuideChapterRedirect.astro'; --- - -Guide page moved

This topic is now part of the consolidated Guide chapter.

+ + diff --git a/site/src/pages/de/guide/recipes/hud-overlay/index.astro b/site/src/pages/de/guide/recipes/hud-overlay/index.astro index 02a1f23e2..2f75256b3 100644 --- a/site/src/pages/de/guide/recipes/hud-overlay/index.astro +++ b/site/src/pages/de/guide/recipes/hud-overlay/index.astro @@ -1,5 +1,5 @@ --- -const target = `${import.meta.env.BASE_URL}de/guide/runtime/ui-and-widgets/`; +import GuideChapterRedirect from '../../../../../components/pages/GuideChapterRedirect.astro'; --- - -Guide page moved

This topic is now part of the consolidated Guide chapter.

+ + diff --git a/site/src/pages/de/guide/recipes/split-screen/index.astro b/site/src/pages/de/guide/recipes/split-screen/index.astro index f1de74c8f..ef810f569 100644 --- a/site/src/pages/de/guide/recipes/split-screen/index.astro +++ b/site/src/pages/de/guide/recipes/split-screen/index.astro @@ -1,5 +1,5 @@ --- -const target = `${import.meta.env.BASE_URL}de/guide/runtime/coordinates-and-views/`; +import GuideChapterRedirect from '../../../../../components/pages/GuideChapterRedirect.astro'; --- - -Guide page moved

This topic is now part of the consolidated Guide chapter.

+ + diff --git a/site/src/pages/en/guide/debugging/index.astro b/site/src/pages/en/guide/debugging/index.astro index d69d61447..6d30b7c50 100644 --- a/site/src/pages/en/guide/debugging/index.astro +++ b/site/src/pages/en/guide/debugging/index.astro @@ -1,15 +1,5 @@ --- -const target = `${import.meta.env.BASE_URL}en/guide/debugging/debugging-and-inspection/`; +import GuideChapterRedirect from '../../../../components/pages/GuideChapterRedirect.astro'; --- - - - - - - - - ExoJS Guide - -

Continue to the guide

- + diff --git a/site/src/pages/en/guide/getting-started/project-structure/index.astro b/site/src/pages/en/guide/getting-started/project-structure/index.astro index 8b6367958..cfafa7eaf 100644 --- a/site/src/pages/en/guide/getting-started/project-structure/index.astro +++ b/site/src/pages/en/guide/getting-started/project-structure/index.astro @@ -1,5 +1,5 @@ --- -const target = `${import.meta.env.BASE_URL}en/guide/getting-started/setup/`; +import GuideChapterRedirect from '../../../../../components/pages/GuideChapterRedirect.astro'; --- - -Guide page moved

This topic is now part of the consolidated Guide chapter.

+ + diff --git a/site/src/pages/en/guide/input/index.astro b/site/src/pages/en/guide/input/index.astro index 00f73d01c..99fa3d43e 100644 --- a/site/src/pages/en/guide/input/index.astro +++ b/site/src/pages/en/guide/input/index.astro @@ -1,15 +1,5 @@ --- -const target = `${import.meta.env.BASE_URL}en/guide/input/keyboard-and-actions/`; +import GuideChapterRedirect from '../../../../components/pages/GuideChapterRedirect.astro'; --- - - - - - - - - ExoJS Guide - -

Continue to the guide

- + diff --git a/site/src/pages/en/guide/integrations/index.astro b/site/src/pages/en/guide/integrations/index.astro index 59b79db1b..07347a5d1 100644 --- a/site/src/pages/en/guide/integrations/index.astro +++ b/site/src/pages/en/guide/integrations/index.astro @@ -1,15 +1,5 @@ --- -const target = `${import.meta.env.BASE_URL}en/guide/integrations/react/`; +import GuideChapterRedirect from '../../../../components/pages/GuideChapterRedirect.astro'; --- - - - - - - - - ExoJS Guide - -

Continue to the guide

- + diff --git a/site/src/pages/en/guide/pathfinding/index.astro b/site/src/pages/en/guide/pathfinding/index.astro index 170359090..3216c8f3c 100644 --- a/site/src/pages/en/guide/pathfinding/index.astro +++ b/site/src/pages/en/guide/pathfinding/index.astro @@ -1,15 +1,5 @@ --- -const target = `${import.meta.env.BASE_URL}en/guide/pathfinding/grid-pathfinding/`; +import GuideChapterRedirect from '../../../../components/pages/GuideChapterRedirect.astro'; --- - - - - - - - - ExoJS Guide - -

Continue to the guide

- + diff --git a/site/src/pages/en/guide/physics/index.astro b/site/src/pages/en/guide/physics/index.astro index fc820c495..fca4b938d 100644 --- a/site/src/pages/en/guide/physics/index.astro +++ b/site/src/pages/en/guide/physics/index.astro @@ -1,15 +1,5 @@ --- -const target = `${import.meta.env.BASE_URL}en/guide/physics/physics-basics/`; +import GuideChapterRedirect from '../../../../components/pages/GuideChapterRedirect.astro'; --- - - - - - - - - ExoJS Guide - -

Continue to the guide

- + diff --git a/site/src/pages/en/guide/recipes/audio-reactive-scene/index.astro b/site/src/pages/en/guide/recipes/audio-reactive-scene/index.astro index 36c725133..9c4402889 100644 --- a/site/src/pages/en/guide/recipes/audio-reactive-scene/index.astro +++ b/site/src/pages/en/guide/recipes/audio-reactive-scene/index.astro @@ -1,5 +1,5 @@ --- -const target = `${import.meta.env.BASE_URL}en/guide/audio/audio-reactive-visualization/`; +import GuideChapterRedirect from '../../../../../components/pages/GuideChapterRedirect.astro'; --- - -Guide page moved

This topic is now part of the consolidated Guide chapter.

+ + diff --git a/site/src/pages/en/guide/recipes/hud-overlay/index.astro b/site/src/pages/en/guide/recipes/hud-overlay/index.astro index fc564b610..23d8e86fa 100644 --- a/site/src/pages/en/guide/recipes/hud-overlay/index.astro +++ b/site/src/pages/en/guide/recipes/hud-overlay/index.astro @@ -1,5 +1,5 @@ --- -const target = `${import.meta.env.BASE_URL}en/guide/runtime/ui-and-widgets/`; +import GuideChapterRedirect from '../../../../../components/pages/GuideChapterRedirect.astro'; --- - -Guide page moved

This topic is now part of the consolidated Guide chapter.

+ + diff --git a/site/src/pages/en/guide/recipes/split-screen/index.astro b/site/src/pages/en/guide/recipes/split-screen/index.astro index 39e22d3a8..cd04d7c01 100644 --- a/site/src/pages/en/guide/recipes/split-screen/index.astro +++ b/site/src/pages/en/guide/recipes/split-screen/index.astro @@ -1,5 +1,5 @@ --- -const target = `${import.meta.env.BASE_URL}en/guide/runtime/coordinates-and-views/`; +import GuideChapterRedirect from '../../../../../components/pages/GuideChapterRedirect.astro'; --- - -Guide page moved

This topic is now part of the consolidated Guide chapter.

+ + From 21006d9d00191e86f7bc840bb60ed8836ffdc5b4 Mon Sep 17 00:00:00 2001 From: Exoridus Date: Sat, 26 Sep 2026 09:16:18 +0200 Subject: [PATCH 31/31] test(ci): use a prose-only file for the docs-only plan cases --- test/ci/plan.test.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/ci/plan.test.ts b/test/ci/plan.test.ts index 42a61bf90..60dc125a7 100644 --- a/test/ci/plan.test.ts +++ b/test/ci/plan.test.ts @@ -91,7 +91,7 @@ describe('plan for a push with a resolved diff', () => { }); it('runs only the gates for a docs-only change', () => { - const plan = push(['README.md']); + const plan = push(['CONTRIBUTING.md']); expect(ids(plan.gates)).toEqual(['typecheck', 'lint', 'sync']); expect(plan.test).toEqual([]); expect(plan.verify).toEqual([]); @@ -127,13 +127,20 @@ describe('plan for a pull request', () => { }); it('runs only the gates for a docs-only change', () => { - const plan = pullRequest(['README.md']); + const plan = pullRequest(['CONTRIBUTING.md']); expect(ids(plan.gates)).toEqual(['typecheck', 'lint', 'sync']); expect(plan.test).toEqual([]); expect(plan.verify).toEqual([]); expect(plan).toMatchObject({ build: false, site: false, smoke: false, skipBudget: false }); }); + it('runs the unit lane for a README whose examples are typechecked', () => { + const plan = pullRequest(['README.md']); + expect(ids(plan.test)).toEqual(['unit']); + expect(plan.verify).toEqual([]); + expect(plan).toMatchObject({ site: false, smoke: false }); + }); + it('adds the audio lane for an audio-fx change and the tilemap lane for a tilemap change', () => { expect(ids(pullRequest(['packages/exojs-audio-fx/src/reverb.ts']).test)).toContain('audio'); expect(ids(pullRequest(['packages/exojs-tilemap/src/worker.ts']).test)).toContain('tilemap');