ignifx0.x · unpublished
GitHub

API reference·skills/ignifx/references/api/vite-plugin.md

@ignifx/vite-plugin

@ignifx/vite-plugin public barrel: asset manifest generation, scene and prefab JSON validation, WASM and asset handling, and HMR hooks (docs/architecture/00-overview.md §2).

The package depends on nothing from the engine — it is a build-time tool that produces the files @ignifx/core consumes at runtime. Explicit named re-exports only, no export * (coding standards §4), and no default export: the plugin is ignifx.

Example#

typescript
// vite.config.tsimport { defineConfig } from "vite";import { ignifx } from "@ignifx/vite-plugin";export default defineConfig({ plugins: [ignifx()] });

Classes#

VitePluginError#

The error every @ignifx/vite-plugin API throws for a failure it can describe (CONSTITUTION.md §3.9). It always carries a stable IGX-#### code so that a build log stays machine-readable even when the message is truncated.

Example#

typescript
try {  await scanAssets({ assetRoot: "assets", hashLength: 8 });} catch (error) {  if (error instanceof VitePluginError && error.code === VitePluginErrorCode.assetRootMissing) {    // create the directory, or point `assetRoot` somewhere else  }}

Extends#

  • Error

Constructors#

Constructor#

new VitePluginError(code, message, options?): VitePluginError

Creates a plugin error.

Parameters#
code#

VitePluginErrorCode

The stable IGX-#### code for the failure.

message#

string

An actionable description of what went wrong and how to fix it.

options?#

ErrorOptions

Standard Error options; use cause to keep the original failure.

Returns#

VitePluginError

Overrides#

Error.constructor

Properties#

cause?#

optional cause?: unknown

Inherited from#

Error.cause

code#

readonly code: VitePluginErrorCode

The stable diagnostic code for this failure.

message#

message: string

Inherited from#

Error.message

name#

name: string

Inherited from#

Error.name

stack?#

optional stack?: string

Inherited from#

Error.stack

stackTraceLimit#

static stackTraceLimit: number

The Error.stackTraceLimit property specifies the number of stack frames collected by a stack trace (whether generated by new Error().stack or Error.captureStackTrace(obj)).

The default value is 10 but may be set to any valid JavaScript number. Changes will affect any stack trace captured after the value has been changed.

If set to a non-number value, or set to a negative number, stack traces will not capture any frames.

Inherited from#

Error.stackTraceLimit

Methods#

captureStackTrace()#

static captureStackTrace(targetObject, constructorOpt?): void

Creates a .stack property on targetObject, which when accessed returns a string representing the location in the code at which Error.captureStackTrace() was called.

javascript
const myObject = {};Error.captureStackTrace(myObject);myObject.stack;  // Similar to `new Error().stack`

The first line of the trace will be prefixed with ${myObject.name}: ${myObject.message}.

The optional constructorOpt argument accepts a function. If given, all frames above constructorOpt, including constructorOpt, will be omitted from the generated stack trace.

The constructorOpt argument is useful for hiding implementation details of error generation from the user. For instance:

javascript
function a() {  b();}function b() {  c();}function c() {  // Create an error without stack trace to avoid calculating the stack trace twice.  const { stackTraceLimit } = Error;  Error.stackTraceLimit = 0;  const error = new Error();  Error.stackTraceLimit = stackTraceLimit;  // Capture the stack trace above function b  Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace  throw error;}a();
Parameters#
targetObject#

object

constructorOpt?#

Function

Returns#

void

Inherited from#

Error.captureStackTrace

prepareStackTrace()#

static prepareStackTrace(err, stackTraces): any

Parameters#
err#

Error

stackTraces#

CallSite[]

Returns#

any

See#

https://v8.dev/docs/stack-trace-api#customizing-stack-traces

Inherited from#

Error.prepareStackTrace

Interfaces#

AssetChangedPayload#

The payload of an ASSET_CHANGED_EVENT message.

Properties#

address#

readonly address: string

The address of the asset, relative to the asset root.

kind#

readonly kind: "added" | "changed" | "removed"

What happened to it on disk.

url#

readonly url: string

The development URL the asset is served from; still meaningful for "removed".


AssetManifest#

The generated assets.manifest.json.

Properties#

entries#

readonly entries: readonly AssetManifestEntry[]

Every asset, sorted by address so two builds of the same tree produce the same bytes.

format#

readonly format: "ignifx.manifest"

Always ASSET_MANIFEST_FORMAT.

formatVersion#

readonly formatVersion: 1

Always ASSET_MANIFEST_FORMAT_VERSION before 1.0.

root#

readonly root: string

The asset root the addresses are relative to, as configured, /-separated.


AssetManifestEntry#

One asset in the manifest.

Properties#

address#

readonly address: string

The address, a /-separated path relative to the asset root.

bytes#

readonly bytes: number

The asset's size in bytes, used for bytes-weighted load progress.

groups#

readonly groups: readonly string[]

Group labels from the .meta.json sidecar; [] when the asset declares none.

hash#

readonly hash: string

The truncated lowercase hex sha256 of the file's contents.

meta?#

readonly optional meta?: JsonObject

The rest of the sidecar, passed through untouched for the loader that understands it.

type#

readonly type: AssetType

The asset type, which decides the loader (docs/architecture/05-assets-and-loading.md §5).

url#

readonly url: string

Where the asset is fetched from: root-relative in development, content-hashed in a build.


ExtensionPublicAsset#

One file an extension asked to have served as a static asset.

Properties#

fileName#

readonly fileName: string

The name the file is copied under, which is the name its loader will ask for.

filePath#

readonly filePath: string

The absolute path of the file inside the installed package.

packageName#

readonly packageName: string

The npm name of the package that declared the file.


FormatHeader#

The format header of a file that declares one.

Properties#

format#

readonly format: string

The format field, for example "ignifx.scene".

formatVersion#

readonly formatVersion: number

The formatVersion field, a positive integer.


IgnifxPluginApi#

The plugin's own API object, reachable through Vite's plugin.api.

Remarks#

Watcher callbacks are synchronous, so the rescan a file change triggers runs detached. Anything that needs to observe the result — a test, or another plugin that reads the served manifest — waits for it here instead of guessing at a delay.

Methods#

whenIdle()#

whenIdle(): Promise<void>

Waits until the plugin has finished reacting to every file-system event seen so far.

Returns#

Promise<void>

A promise that settles once the queue of watcher work is empty.


IgnifxPluginOptions#

Options for ignifx.

Properties#

assetRoot?#

readonly optional assetRoot?: string

The directory scanned for assets, relative to the Vite root (or absolute).

Default Value#

"assets"

config?#

readonly optional config?: string | false

Path to the project config injected as import.meta.env.IGNIFX_CONFIG (docs/architecture/04-extensions.md §5). Pass false to inject {} and load nothing.

Default Value#

auto-detected at the Vite root: ignifx.config.ts, .mts, .js, then .mjs.

hashLength?#

readonly optional hashLength?: number

How many hex characters of each asset's sha256 to keep, in the manifest and in hashed file names. Must be between 4 and 64.

Default Value#

8

manifestFileName?#

readonly optional manifestFileName?: string

The manifest's file name inside build.outDir. It is also the path the dev server serves the development manifest from.

Default Value#

"assets.manifest.json"

publicPath?#

readonly optional publicPath?: string

The directory inside build.outDir that hashed assets are written to, and the URL prefix they are served under in a build. A trailing / is added when missing.

Default Value#

"assets/"

schemas?#

readonly optional schemas?: JsonSchemaProvider

JSON Schemas to validate against, keyed by file format. Without them only the format/formatVersion header is checked.

Default Value#
typescript
no schemasheader validation only.
scripts?#

readonly optional scripts?: string

The glob, relative to the Vite root, that virtual:ignifx/scripts collects component and script classes from.

Default Value#

DEFAULT_SCRIPTS_PATTERN

validate?#

readonly optional validate?: boolean

Whether format-headed JSON under the asset root is validated. A failure fails the build.

Default Value#

true


JsonSchemaProvider#

The JSON Schemas the plugin validates against.

Remarks#

The plugin cannot generate these itself: they are derived from the component schemas a game registers, which only @ignifx/core knows. A game passes them in from its vite.config.ts (ignifx({ schemas: { scene: sceneFileJsonSchema } })); when nothing is supplied, only the format/formatVersion header is checked.

Properties#

formats?#

readonly optional formats?: Readonly<Record<string, JsonObject>>

Schemas keyed by the file's format header, for every other format.

scene?#

readonly optional scene?: JsonObject

The schema for ignifx.scene files — *.scene.json and *.prefab.json alike (ADR-0005).


ResolvedIgnifxConfig#

The outcome of resolving the project config.

Properties#

config#

readonly config: JsonObject

The config object, always JSON-safe; {} when no config exists.

dependencies#

readonly dependencies: readonly string[]

Files the loader read, so the dev server can watch them for a full reload.

path#

readonly path: string | null

The absolute path of the file that was loaded, or null when no config exists.


ResolvedIgnifxPluginOptions#

IgnifxPluginOptions with every default applied and every value normalized.

Properties#

assetRoot#

readonly assetRoot: string

The asset root exactly as configured; the plugin resolves it against the Vite root later.

configFile#

readonly configFile: string | false | null

The configured config path, null to auto-detect, or false to inject {}.

hashLength#

readonly hashLength: number

The truncated hash length.

manifestFileName#

readonly manifestFileName: string

The manifest file name, without a leading /.

publicPath#

readonly publicPath: string

The public path, without a leading / and with a trailing /.

schemas#

readonly schemas: JsonSchemaProvider | null

The supplied schemas, or null.

scriptsPattern#

readonly scriptsPattern: string

The scripts glob, rewritten to be root-absolute.

validate#

readonly validate: boolean

Whether validation runs.


ScanAssetRootOptions#

Options for scanAssetRoot.

Properties#

assetRoot#

readonly assetRoot: string

Absolute path of the directory to scan.

hashLength?#

readonly optional hashLength?: number

How many hex characters of the sha256 to keep.

Default Value#

8


ScannedAsset#

An asset found on disk, before a URL has been decided for it.

Properties#

address#

readonly address: string

The address, a /-separated path relative to the asset root.

bytes#

readonly bytes: number

The file's size in bytes.

filePath#

readonly filePath: string

The absolute path of the file on disk.

groups#

readonly groups: readonly string[]

Group labels from the sidecar, or [].

hash#

readonly hash: string

The truncated lowercase hex sha256 of the file's contents.

meta#

readonly meta: JsonObject | null

The sidecar minus its groups key, or null when there is no sidecar or nothing is left.

type#

readonly type: AssetType

The asset type derived from the address.


SchemaViolation#

One failed constraint, addressed by a JSON pointer into the validated document.

Properties#

message#

readonly message: string

What the value is, and what the schema required instead.

pointer#

readonly pointer: string

RFC 6901 pointer to the offending value; "" for the document root.


ScriptsModuleOptions#

Options accepted by scriptsModuleSource.

Properties#

hot?#

readonly optional hot?: boolean

Emit the HMR client. false — the default, and what a build uses — leaves the module with no import.meta.hot reference at all, so none of the client reaches production (docs/architecture/15-devtools-and-diagnostics.md §5).


ValidateJsonOptions#

Options for validateJsonValue.

Properties#

maxDepth?#

readonly optional maxDepth?: number

How deep the validator may recurse before it gives up on a recursive schema.

Default Value#

64

root?#

readonly optional root?: JsonObject

The document $ref targets are resolved against.

Default Value#

the schema argument itself, which is what a self-contained schema wants.


ValidationProblem#

One validation failure, addressed by asset and JSON pointer.

Properties#

address#

readonly address: string

The address of the offending asset.

code#

readonly code: VitePluginErrorCode

The diagnostic code: IGX-0650, IGX-0651, or IGX-0652.

filePath#

readonly filePath: string

The absolute path of the offending file, for editors that jump to it.

message#

readonly message: string

What is wrong, in one sentence.

pointer#

readonly pointer: string

RFC 6901 pointer to the offending value; "" for the document as a whole.

Type Aliases#

AssetType#

AssetType = typeof ASSET_TYPE_BY_SUFFIX[keyof typeof ASSET_TYPE_BY_SUFFIX] | typeof ASSET_TYPE_BY_EXTENSION[keyof typeof ASSET_TYPE_BY_EXTENSION]

The asset type recorded in a manifest entry. Derived from the two tables so that adding a row widens the union automatically (coding standards §5.2 prefers as const tables over enums).


JsonArray#

JsonArray = readonly JsonValue[]

A JSON array.


JsonObject#

JsonObject = object

A JSON object.

Index Signature#

[key: string]: JsonValue


JsonSchemaObject#

JsonSchemaObject = JsonObject

A JSON Schema fragment, kept as a plain JSON object exactly as @ignifx/core emits it.


JsonValue#

JsonValue = string | number | boolean | null | JsonArray | JsonObject

A JSON value.


VitePluginErrorCode#

VitePluginErrorCode = typeof VitePluginErrorCode[keyof typeof VitePluginErrorCode]

The union of the diagnostic codes this package can report.

Variables#

ASSET_CHANGED_EVENT#

const ASSET_CHANGED_EVENT: "ignifx:asset-changed" = "ignifx:asset-changed"

The HMR event the plugin sends when an asset under the asset root changes.

Remarks#

The assets service listens for it and reloads the affected handles, firing AssetHandle.onReplaced (docs/architecture/05-assets-and-loading.md §7). The policy of a script hot reload — patch or recreate — is Phase 10's; this package only ships the channel.

Example#

typescript
import.meta.hot?.on("ignifx:asset-changed", (payload) => {  console.log(payload.address, payload.kind);});

ASSET_MANIFEST_FORMAT#

const ASSET_MANIFEST_FORMAT: "ignifx.manifest" = "ignifx.manifest"

The format header every generated manifest carries (docs/architecture/06-serialization-and-scene-format.md §6).


ASSET_MANIFEST_FORMAT_VERSION#

const ASSET_MANIFEST_FORMAT_VERSION: 1 = 1

The manifest format version. It stays 1 for the whole 0.x line (CONSTITUTION.md §4.2).


ASSET_TYPE_BY_EXTENSION#

const ASSET_TYPE_BY_EXTENSION: object

Asset types keyed by file extension, from the core loader table of docs/architecture/05-assets-and-loading.md §5. audio is listed here even though @ignifx/audio registers the loader: the manifest is written once, by this plugin, for every extension that will later read it.

Type Declaration#

.basis#

readonly .basis: "texture" = "texture"

.bin#

readonly .bin: "binary" = "binary"

.csv#

readonly .csv: "text" = "text"

.dds#

readonly .dds: "environment" = "environment"

.env#

readonly .env: "environment" = "environment"

.glb#

readonly .glb: "model" = "model"

.gltf#

readonly .gltf: "model" = "model"

.hdr#

readonly .hdr: "environment" = "environment"

.jpeg#

readonly .jpeg: "texture" = "texture"

.jpg#

readonly .jpg: "texture" = "texture"

.json#

readonly .json: "json" = "json"

.ktx2#

readonly .ktx2: "texture" = "texture"

.md#

readonly .md: "text" = "text"

.mp3#

readonly .mp3: "audio" = "audio"

.ogg#

readonly .ogg: "audio" = "audio"

.otf#

readonly .otf: "font" = "font"

.png#

readonly .png: "texture" = "texture"

.ttf#

readonly .ttf: "font" = "font"

.txt#

readonly .txt: "text" = "text"

.wasm#

readonly .wasm: "binary" = "binary"

.wav#

readonly .wav: "audio" = "audio"

.webp#

readonly .webp: "texture" = "texture"


ASSET_TYPE_BY_SUFFIX#

const ASSET_TYPE_BY_SUFFIX: object

Asset types keyed by the two-segment JSON extension that identifies them. These are checked before ASSET_TYPE_BY_EXTENSION because every one of them also ends in .json (docs/architecture/06-serialization-and-scene-format.md §6).

Type Declaration#

.animator.json#

readonly .animator.json: "animator" = "animator"

.atlas.json#

readonly .atlas.json: "spriteatlas" = "spriteatlas"

.audio.json#

readonly .audio.json: "audiobuses" = "audiobuses"

.i18n.json#

readonly .i18n.json: "i18n" = "i18n"

.input.json#

readonly .input.json: "inputactions" = "inputactions"

.material.json#

readonly .material.json: "material" = "material"

.physicsmaterial.json#

readonly .physicsmaterial.json: "physicsmaterial" = "physicsmaterial"

.prefab.json#

readonly .prefab.json: "scene" = "scene"

.scene.json#

readonly .scene.json: "scene" = "scene"

.spriteanim.json#

readonly .spriteanim.json: "spriteanimation" = "spriteanimation"

.tilemap.json#

readonly .tilemap.json: "tilemap" = "tilemap"


DEFAULT_ASSET_ROOT#

const DEFAULT_ASSET_ROOT: "assets" = "assets"

The asset root, relative to the Vite root, when the option is not given.


DEFAULT_ASSET_TYPE#

const DEFAULT_ASSET_TYPE: "binary" = "binary"

The type given to a file whose extension is in neither table. Bytes are always loadable, so an unknown extension is a binary asset rather than a build failure.


DEFAULT_HASH_LENGTH#

const DEFAULT_HASH_LENGTH: 8 = 8

The default number of hex characters kept from a content hash, matching the length Vite itself uses for asset file names.


DEFAULT_MANIFEST_FILE_NAME#

const DEFAULT_MANIFEST_FILE_NAME: "assets.manifest.json" = "assets.manifest.json"

The manifest's file name inside build.outDir, when the option is not given.


DEFAULT_PUBLIC_PATH#

const DEFAULT_PUBLIC_PATH: "assets/" = "assets/"

The directory inside build.outDir that assets are copied into, when the option is not given.


DEFAULT_SCRIPTS_PATTERN#

const DEFAULT_SCRIPTS_PATTERN: "src/scripts/**/*.ts" = "src/scripts/**/*.ts"

The default glob the script registry is built from, relative to the Vite root.


EXTENSION_KEYWORD#

const EXTENSION_KEYWORD: "ignifx-extension" = "ignifx-extension"

The keyword a package outside the @ignifx scope sets to opt into extension discovery (docs/architecture/04-extensions.md §6).


IGNIFX_CONFIG_DEFINE_KEY#

const IGNIFX_CONFIG_DEFINE_KEY: "import.meta.env.IGNIFX_CONFIG" = "import.meta.env.IGNIFX_CONFIG"

The define key the resolved project config is injected under (docs/architecture/04-extensions.md §5).


IGNIFX_CONFIG_FILE_NAMES#

const IGNIFX_CONFIG_FILE_NAMES: readonly ["ignifx.config.ts", "ignifx.config.mts", "ignifx.config.js", "ignifx.config.mjs"]

The file names auto-detected at the Vite root, in the order they are tried.


MANIFEST_MODULE_ID#

const MANIFEST_MODULE_ID: "virtual:ignifx/manifest" = "virtual:ignifx/manifest"

The module a game imports to read the asset manifest without fetching it.

Example#

typescript
import { manifest } from "virtual:ignifx/manifest";

PLUGIN_NAME#

const PLUGIN_NAME: "ignifx" = "ignifx"

The plugin's name, as it appears in Vite logs and in PLUGIN_ERROR diagnostics.


RESOLVED_MANIFEST_MODULE_ID#

const RESOLVED_MANIFEST_MODULE_ID: string

The resolved id of MANIFEST_MODULE_ID.


RESOLVED_SCRIPTS_MODULE_ID#

const RESOLVED_SCRIPTS_MODULE_ID: string

The resolved id of SCRIPTS_MODULE_ID.


SCRIPTS_HOT_RELOAD_EXPORT#

const SCRIPTS_HOT_RELOAD_EXPORT: "acceptHotReload" = "acceptHotReload"

The function virtual:ignifx/scripts exports for wiring an app to script hot reload (docs/architecture/15-devtools-and-diagnostics.md §5).

Remarks#

The generated module cannot know which app — or how many apps — a page built, and a module-level app reference is exactly what CONSTITUTION.md §3.5/§3.6 forbid. So the hand-off is explicit and one line of game code: the game passes its app in, and the module calls app.hotReload.apply whenever Vite replaces a script module. The export exists in a production build too, with an empty body, so the same source builds either way.

Example#

typescript
import { acceptHotReload, scripts } from "virtual:ignifx/scripts";const app = await createApp({ canvas });app.registerComponents(scripts);acceptHotReload(app);

SCRIPTS_MODULE_ID#

const SCRIPTS_MODULE_ID: "virtual:ignifx/scripts" = "virtual:ignifx/scripts"

The module a game imports to get every script and component class in its source tree (docs/architecture/15-devtools-and-diagnostics.md §5).

Example#

typescript
import { scripts } from "virtual:ignifx/scripts";const app = await createApp({ canvas, components: scripts });

VitePluginErrorCode#

const VitePluginErrorCode: object

The diagnostic codes this package can report, in the 05xx (assets) and 06xx (serialization) ranges reserved by docs/architecture/15-devtools-and-diagnostics.md §1.

Type Declaration#

assetRootMissing#

readonly assetRootMissing: "IGX-0550" = "IGX-0550"

The configured asset root does not exist or is not a directory.

duplicateOutputFile#

readonly duplicateOutputFile: "IGX-0552" = "IGX-0552"

Two assets hash to the same output file name, so one would overwrite the other.

extensionAssetMissing#

readonly extensionAssetMissing: "IGX-0553" = "IGX-0553"

An ignifx.assets.public entry of an extension package could not be read.

invalidOption#

readonly invalidOption: "IGX-0555" = "IGX-0555"

A plugin option is outside its documented domain.

invalidProjectConfig#

readonly invalidProjectConfig: "IGX-0554" = "IGX-0554"

ignifx.config.ts could not be loaded, or its default export is not an object.

invalidSidecar#

readonly invalidSidecar: "IGX-0551" = "IGX-0551"

A .meta.json sidecar could not be parsed, or its groups field is not an array of strings.

malformedJson#

readonly malformedJson: "IGX-0650" = "IGX-0650"

A JSON asset under the asset root is not parseable JSON.

missingFormatHeader#

readonly missingFormatHeader: "IGX-0651" = "IGX-0651"

A format-headed JSON file is missing its format/formatVersion header or the header is malformed.

schemaViolation#

readonly schemaViolation: "IGX-0652" = "IGX-0652"

A format-headed JSON file failed validation against the JSON Schema supplied for its format.

unsupportedSchema#

readonly unsupportedSchema: "IGX-0653" = "IGX-0653"

A supplied JSON Schema uses a keyword or $ref target this validator does not implement.

Functions#

addressFromRelativePath()#

addressFromRelativePath(relativePath): string

Converts an OS path relative to the asset root into an address.

Parameters#

relativePath#

string

The path relative to the asset root, in platform separators.

Returns#

string

The /-separated address.


appendPointer()#

appendPointer(pointer, segment): string

Appends a segment to a JSON pointer.

Parameters#

pointer#

string

The pointer to the parent value.

segment#

string

The property name or array index to append.

Returns#

string

The pointer to the child value.

Example#

typescript
appendPointer("", "entities"); // "/entities"appendPointer("/entities", "0"); // "/entities/0"

assetTypeForAddress()#

assetTypeForAddress(address): AssetType

Classifies an asset address by its extension.

Parameters#

address#

string

The asset address, a /-separated path relative to the asset root.

Returns#

AssetType

The asset type, or DEFAULT_ASSET_TYPE when the extension is unknown.

Example#

typescript
assetTypeForAddress("levels/level1.scene.json"); // "scene"assetTypeForAddress("sprites/hero.png"); // "texture"assetTypeForAddress("data/loot.json"); // "json"

buildManifest()#

buildManifest(assets, root, urlFor): AssetManifest

Turns scanned assets into a manifest.

Parameters#

assets#

readonly ScannedAsset[]

The assets to list, already sorted by address.

root#

string

The asset root as configured, recorded in the manifest for diagnostics.

urlFor#

(asset) => string

Maps an asset to the URL the runtime fetches it from.

Returns#

AssetManifest

The manifest, ready to be serialized.

Example#

typescript
const manifest = buildManifest(assets, "assets", (asset) => `/assets/${asset.address}`);

collectExtensionPublicAssets()#

collectExtensionPublicAssets(root): Promise<readonly ExtensionPublicAsset[]>

Collects every file declared by an installed extension's ignifx.assets.public.

Parameters#

root#

string

The absolute project root to resolve packages from.

Returns#

Promise<readonly ExtensionPublicAsset[]>

The files to copy, sorted by package name and then file name.

Remarks#

Packages in the @ignifx scope are always inspected; any other package must carry the EXTENSION_KEYWORD keyword. node_modules directories are walked from the project root upwards, so a pnpm workspace finds hoisted packages, and the nearest installation of a package wins.

Throws#

A VitePluginError with code IGX-0553 when a declared file does not exist, or when two packages declare files with the same name, which would silently overwrite one.

Example#

typescript
const files = await collectExtensionPublicAssets("/project");// [{ packageName: "@ignifx/physics", fileName: "HavokPhysics.wasm", filePath: "…" }]

defineConfig()#

defineConfig<T>(config): T

Identity helper that gives a game's ignifx.config.ts its types without widening the object.

Type Parameters#

T#

T

The shape of the project config, inferred from the argument.

Parameters#

config#

T

The project config object.

Returns#

T

The same object, unchanged.

Remarks#

ignifx/config re-exports this in Phase 12 so that a game can write import { defineConfig } from "ignifx/config" rather than reaching into the build plugin. The function is deliberately generic and lossless: it returns the argument, so literal types such as the layer-name tuple survive into the game's own typings.

Example#

typescript
// ignifx.config.tsimport { defineConfig } from "@ignifx/vite-plugin";export default defineConfig({  layers: ["Default", "Ground", "Player"],  time: { fixedDeltaTime: 1 / 60 },  assets: { root: "./assets", preload: ["boot"] },});

findIgnifxConfigFile()#

findIgnifxConfigFile(root): Promise<string | null>

Finds the project config file at a Vite root.

Parameters#

root#

string

The absolute Vite root directory.

Returns#

Promise<string | null>

The absolute path of the first name in IGNIFX_CONFIG_FILE_NAMES that exists, or null when the project has no config file.


formatValidationProblem()#

formatValidationProblem(problem): string

Renders a problem as a single log line: <address> <pointer>: <message> (<code>).

Parameters#

problem#

ValidationProblem

The problem to render.

Returns#

string

The line, without a trailing newline.

Example#

typescript
formatValidationProblem({  address: "levels/level1.scene.json",  filePath: "/p/assets/levels/level1.scene.json",  pointer: "/entities/0",  message: 'missing required property "uid"',  code: "IGX-0652",});// 'levels/level1.scene.json /entities/0: missing required property "uid" (IGX-0652)'

hashedAddress()#

hashedAddress(address, hash): string

Builds the content-hashed output name for an address.

Parameters#

address#

string

The asset address, a /-separated path relative to the asset root.

hash#

string

The truncated content hash to insert.

Returns#

string

The address with the hash inserted before the extension.

Example#

typescript
hashedAddress("levels/level1.scene.json", "0a1b2c3d"); // "levels/level1.0a1b2c3d.scene.json"

ignifx()#

ignifx(options?): Plugin<IgnifxPluginApi>

The ignifx Vite plugin.

Parameters#

options?#

IgnifxPluginOptions = {}

Plugin options and their documented defaults; see IgnifxPluginOptions.

Returns#

Plugin<IgnifxPluginApi>

The Vite plugin, to be listed in vite.config.ts.

Remarks#

What it does, in the order the hooks run:

  • config resolves ignifx.config.ts and injects it as import.meta.env.IGNIFX_CONFIG (docs/architecture/04-extensions.md §5).
  • buildStart scans the asset root, hashes every file, reads .meta.json sidecars, and validates every format-headed JSON file. A validation failure fails the build.
  • virtual:ignifx/manifest and virtual:ignifx/scripts are served from resolveId/load.
  • In development the manifest is also served from /<manifestFileName>, the asset root is watched, and every change is announced on the ignifx:asset-changed HMR channel; editing the project config triggers a full reload.
  • generateBundle emits every asset under <outDir>/<publicPath> with a content-hashed, immutable-cacheable name, copies each extension's ignifx.assets.public files unhashed next to them, and writes the manifest with the hashed URLs.

Throws#

A VitePluginError with code IGX-0555 when an option is outside its domain.

Example#

typescript
// vite.config.tsimport { defineConfig } from "vite";import { ignifx } from "@ignifx/vite-plugin";export default defineConfig({  plugins: [ignifx({ assetRoot: "assets" })],});

isJsonArray()#

isJsonArray(value): value is JsonArray

Reports whether a JSON value is an array.

Parameters#

value#

JsonValue

The value to test.

Returns#

value is JsonArray

true when the value is a JSON array, narrowed with its element type intact.


isJsonObject()#

isJsonObject(value): value is JsonObject

Reports whether a JSON value is an object rather than an array, null, or a primitive.

Parameters#

value#

JsonValue

The value to test.

Returns#

value is JsonObject

true when the value is a JSON object.

Remarks#

Array.isArray alone does not narrow a JsonValue: JsonArray is a readonly array, which the compiler cannot subtract in the false branch, and its true branch widens to any[]. This predicate and isJsonArray are the one place that gap is papered over, so every other module narrows in a single call and keeps its element types.

Example#

typescript
isJsonObject({ a: 1 }); // trueisJsonObject([1, 2]); // false

isSidecarFileName()#

isSidecarFileName(fileName): boolean

Reports whether a file name is an asset sidecar rather than an asset.

Parameters#

fileName#

string

The base name of the file.

Returns#

boolean

true for *.meta.json, which the scanner reads but never lists as an entry.


jsonProperty()#

jsonProperty(object, key): JsonValue | undefined

Reads a property of a JSON object without tripping noPropertyAccessFromIndexSignature, and without ever reaching the prototype chain.

Parameters#

object#

JsonObject

The object to read.

key#

string

The property name.

Returns#

JsonValue | undefined

The value, or undefined when the object has no such own property.


loadIgnifxConfig()#

loadIgnifxConfig(configFile, root, env): Promise<ResolvedIgnifxConfig>

Loads ignifx.config.ts through Vite's own config loader.

Parameters#

configFile#

string | null

Absolute path of the config file, or null to resolve to an empty config.

root#

string

The absolute Vite root, used as the loader's config root.

env#

ConfigEnv

The Vite command and mode the config is being loaded for.

Returns#

Promise<ResolvedIgnifxConfig>

The resolved config, its path, and the files the loader depended on.

Remarks#

Vite's loadConfigFromFile is used rather than a bare dynamic import because the config is TypeScript: the loader bundles it with the same pipeline that loads vite.config.ts, so the game needs no separate build step and the file may import helpers from its own source tree. The loader types its result as a Vite UserConfig; it is in fact whatever the module exports by default, which is the boundary assertion below (coding standards §5.2).

Throws#

A VitePluginError with code IGX-0554 when the file cannot be loaded or does not export a JSON object by default.

Example#

typescript
const resolved = await loadIgnifxConfig("/project/ignifx.config.ts", "/project", {  command: "build",  mode: "production",});resolved.config; // { layers: [...], time: {...} }

manifestModuleSource()#

manifestModuleSource(manifest): string

Generates the source of virtual:ignifx/manifest.

Parameters#

manifest#

AssetManifest

The manifest to embed.

Returns#

string

An ES module exporting the manifest as manifest.


normalizeScriptsPattern()#

normalizeScriptsPattern(pattern): string

Turns a scripts glob into the root-relative form import.meta.glob needs.

Parameters#

pattern#

string

The pattern as configured, relative to the Vite root or already root-absolute.

Returns#

string

The root-absolute pattern.

Remarks#

import.meta.glob resolves a relative pattern against the importing file, and a virtual module has no directory to be relative to, so the pattern is rewritten to start at the Vite root. Vite documents a leading / as exactly that.

Throws#

A VitePluginError with code IGX-0555 when the pattern is empty or escapes the root with ...

Example#

typescript
normalizeScriptsPattern("src/scripts/player.ts"); // "/src/scripts/player.ts"

parseJsonValue()#

parseJsonValue(text): JsonValue

Parses JSON text into the JSON value model.

Parameters#

text#

string

The JSON text.

Returns#

JsonValue

The parsed value.

Remarks#

This is the one place in the package where JSON.parse's any is narrowed. Every caller works in JsonValue from here on, which is what lets the rest of the code check shapes instead of asserting them (coding standards §5.2).

Throws#

A SyntaxError when the text is not JSON; callers turn that into a diagnostic.


requiresFormatHeader()#

requiresFormatHeader(address): boolean

Reports whether a JSON file must carry a format/formatVersion header.

Parameters#

address#

string

The asset address.

Returns#

boolean

true when a missing header is an error.

Remarks#

Every ignifx file format is a two-segment JSON extension — .scene.json, .prefab.json, .material.json, .atlas.json, .input.json, and the rest of the table in docs/architecture/06-serialization-and-scene-format.md §6 — so a two-segment name is the signal that a header is expected. A plain .json file is game data and is validated only when it happens to declare a header of its own.


resolvePluginOptions()#

resolvePluginOptions(options): ResolvedIgnifxPluginOptions

Applies the documented defaults and rejects options outside their domain.

Parameters#

options#

IgnifxPluginOptions

The options as given to ignifx.

Returns#

ResolvedIgnifxPluginOptions

The resolved options.

Throws#

A VitePluginError with code IGX-0555 when an option is outside its domain.

Example#

typescript
resolvePluginOptions({}).publicPath; // "assets/"resolvePluginOptions({ publicPath: "/static" }).publicPath; // "static/"

resolveVirtualModuleId()#

resolveVirtualModuleId(id): string | null

Maps a bare virtual specifier to its resolved id.

Parameters#

id#

string

The specifier as written in the importing module.

Returns#

string | null

The resolved id, or null when the specifier belongs to another plugin.


scanAssetRoot()#

scanAssetRoot(options): Promise<readonly ScannedAsset[]>

Scans an asset root and describes every file it holds.

Parameters#

options#

ScanAssetRootOptions

The directory to scan and the hash length; see ScanAssetRootOptions.

Returns#

Promise<readonly ScannedAsset[]>

Every asset found, sorted by address.

Remarks#

Dot-prefixed files and directories are skipped, and so are *.meta.json sidecars — they are read for the asset they belong to instead of listed as assets of their own.

Throws#

A VitePluginError with code IGX-0550 when the asset root does not exist or is not a directory, or IGX-0551 when a sidecar is malformed.

Example#

typescript
const assets = await scanAssetRoot({ assetRoot: "/project/assets" });// [{ address: "sprites/hero.png", type: "texture", hash: "0a1b2c3d", … }]

scriptsModuleSource()#

scriptsModuleSource(pattern, options?): string

Generates the source of virtual:ignifx/scripts.

Parameters#

pattern#

string

The root-absolute glob, from normalizeScriptsPattern.

options?#

ScriptsModuleOptions

Whether to emit the development-only HMR client.

Returns#

string

An ES module exporting the registry as scripts and the wiring as acceptHotReload.

Remarks#

The registry keeps every exported class that declares a typeId, which is what makes a class a registrable component (docs/architecture/03-scripting-and-components.md). Plain helper exports from the same files are ignored, and the module keys are sorted so that the registration order is the same on every machine. Because the imports are real static imports produced by import.meta.glob's eager form, Vite's HMR graph sees each script file and can push updates for it.

The HMR half. Script hot reload needs no channel of its own: unlike an asset, a script module already sits in Vite's module graph, so the generated module self-accepts and Vite hands it the replacement namespace. The handler diffs the old and new registries by typeId for the log line and hands the whole new registry to app.hotReload.apply, which is the half that decides what a change means — patch or recreate — and skips the classes that did not change. The set of subscribed apps lives in import.meta.hot.data, which Vite carries from one instance of a module to the next, so the module that handles the second update still knows about them.


serializeManifest()#

serializeManifest(manifest): string

Serializes a manifest the way the plugin writes it: two-space indentation and a trailing newline, so that a committed or inspected manifest reads as an ordinary JSON file.

Parameters#

manifest#

AssetManifest

The manifest to serialize.

Returns#

string

The manifest as JSON text.


splitAssetFileName()#

splitAssetFileName(fileName): object

Splits an asset file name into the part a content hash is inserted after and the extension that must survive it.

Parameters#

fileName#

string

The base name of the file, without a directory.

Returns#

object

The stem and the extension, whose concatenation is fileName.

extension#

readonly extension: string

stem#

readonly stem: string

Remarks#

Every compound ignifx format is a two-segment JSON extension (.scene.json, .atlas.json, .input.json, …), and the runtime picks its loader from that whole suffix, so the hash has to go in front of it: level1.scene.json becomes level1.<hash>.scene.json, not level1.scene.<hash>.json. Every other name keeps the ordinary single extension.

Example#

typescript
splitAssetFileName("level1.scene.json"); // { stem: "level1", extension: ".scene.json" }splitAssetFileName("hero.glb"); // { stem: "hero", extension: ".glb" }

validateJsonAsset()#

validateJsonAsset(address, filePath, text, schemas): readonly ValidationProblem[]

Validates one JSON document that lives under the asset root.

Parameters#

address#

string

The asset address, used in messages.

filePath#

string

The absolute path of the file.

text#

string

The file's contents.

schemas#

JsonSchemaProvider | null

The schemas the game supplied, or null to check only the header.

Returns#

readonly ValidationProblem[]

Every problem found, in document order.

Example#

typescript
const problems = validateJsonAsset("a.scene.json", "/p/assets/a.scene.json", "{}", null);// [{ code: "IGX-0651", message: 'is missing its "format"/"formatVersion" header', … }]

validateJsonAssets()#

validateJsonAssets(assets, schemas): Promise<readonly ValidationProblem[]>

Validates every JSON asset in a scan.

Parameters#

assets#

readonly ScannedAsset[]

The scanned assets to check; anything that is not JSON is skipped.

schemas#

JsonSchemaProvider | null

The schemas the game supplied, or null to check only headers.

Returns#

Promise<readonly ValidationProblem[]>

Every problem found, ordered by address.

Throws#

A VitePluginError with code IGX-0653 when a supplied schema uses a keyword outside the implemented subset.


validateJsonValue()#

validateJsonValue(value, schema, options?): readonly SchemaViolation[]

Validates a JSON document against the supported JSON Schema subset: type, required, properties, additionalProperties, items, enum, const, minimum, maximum, minItems, maxItems, oneOf, anyOf, and $ref to #/$defs/*.

Parameters#

value#

JsonValue

The parsed document to validate.

schema#

JsonObject

The schema to validate against.

options?#

ValidateJsonOptions

Reference root and recursion limit; see ValidateJsonOptions.

Returns#

readonly SchemaViolation[]

Every violation found, in document order; an empty array means the document is valid.

Throws#

A VitePluginError with code IGX-0653 when the schema uses a keyword outside the subset, references something other than #/$defs/*, or recurses without bound.

Example#

typescript
const problems = validateJsonValue({ format: "ignifx.scene" }, {  type: "object",  required: ["format", "formatVersion"],  properties: { format: { const: "ignifx.scene" }, formatVersion: { type: "integer" } },});// [{ pointer: "", message: 'missing required property "formatVersion"' }]