Repository navigation
NX.js compatibility: run melonJS without a DOM (experimental) - #1854
Merged
Merged
Conversation
melonJS can now run where there is no `document`: on nx.js (JavaScript homebrew for the Nintendo Switch) and in a Web Worker. The game hands the Application the canvas to draw to (`settings.canvas`, `screen` on nx.js). Browsers behave as before: every new path is taken only when `globalThis.document` is missing. - Application: no parent element to mount the canvas in, measure or observe; `parentElement` is undefined there, and `scale: "auto"` draws at the given size - a canvas with no `style` (nx.js's `screen`) is accepted wherever the engine set CSS on it (display, size, touch-action) - the WebGL support check reports whether WebGL 2 exists instead of probing a `<canvas>` it cannot create; creating the renderer decides, and `video.AUTO` still falls back to Canvas - scratch canvases fall back to OffscreenCanvas, and the Tiled tileset and Mesh code now go through the canvas factory - touch: a global `TouchEvent` counts as touch support without a DOM, so touches dispatched on the canvas reach pointer events - audio: unlocks at once instead of listening on `document` - element bounds without a DOM come from the canvas itself - Tiled: an XML map where there is no DOMParser fails saying to export it as JSON, and the loader keeps a parser's error as the `cause` of the "Failed loading resource" error tests/nxjs-compatibility.spec.js runs a game in a Web Worker made to look like nx.js, with the Canvas, WebGL and AUTO renderers: boot, image and JSON map loading, drawn pixels, touch, and the XML map error. Skills (deployment, tilemaps, audio, input) and the changelog describe running without a DOM and what nx.js lacks. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JnzwA4ysaW5ocy8xevRB4k
nx.js throws on `new OffscreenCanvas(0, 0)`, so the 0x0 capability probe read as "no OffscreenCanvas" there, and every scratch canvas (text, tiles, render targets) fell through to `document.createElement` and crashed. Probe with a 1x1 canvas instead, and where neither a document nor an OffscreenCanvas is available, fail with an error saying so rather than a TypeError on `document`. The nx.js test worker now refuses empty canvases as nx.js does, and its scene draws a Text, which needs a scratch canvas: without the fix the test fails with the crash above. Two browser-side tests pin that touch and OffscreenCanvas detection are unchanged where there is a DOM (desktop Chromium defines `TouchEvent` with no touchscreen, which the no-DOM touch fallback must not count). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JnzwA4ysaW5ocy8xevRB4k
Checked against nx.js's own runtime and native code rather than Chromium: - `import "melonjs"` threw on nx.js: `device.screenOrientation` read `screen.orientation` at import, which nx.js implements as a getter that throws "Method not implemented". Read it inside a try/catch. - every touch landed at (0, 0): nx.js's touches have `clientX/Y` but no `pageX/Y`. Without a DOM, page and client coordinates are the same, so use the client ones there. - no audio clip could load: nx.js's `Audio` has no `canPlayType`, so no format was supported and every clip was refused before decoding. With no DOM and no `canPlayType`, assume each format and let decoding fail. - Tiled image-collection tiles and `Mesh.toCanvas()` create a `<canvas>` again where there is a page, so a 0-sized one behaves as before; the canvas factory is only used without a DOM. - `loader.reload()` also keeps the parser's error as `cause`. The test worker now reproduces each of these (a throwing `screen.orientation`, touches without `pageX`, an nx.js-like Web Audio stand-in and an `Audio` without `canPlayType`), and covers text pixels, an image-collection tile cut through a scratch canvas, the no-DOM `enableSwipe` / `getElement` / `getParentElement`, and the error when there is neither a DOM nor an OffscreenCanvas. Reverting any one fix fails its test. Deployment skill: the font snippet no longer calls `FontFace.load()`, which throws on nx.js; notes on audio formats and the profile picker that reading `localStorage` opens on nx.js. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JnzwA4ysaW5ocy8xevRB4k
tests/nxjs-browser-parity.spec.js holds a real page to what it did before the no-DOM work: each capability it touched (touchEvent, pointerEvent, touch, maxTouchPoints, screenOrientation, offscreenCanvas, WebGL support, audio formats) against the formula it was computed with before, written out in the test; the DOM helpers (getElement, getParentElement, getElementBounds, enableSwipe, createCanvas) against what they returned; and an Application mounting in the page: its parent element, the canvas in it with display block, CSS size and touch-action, page coordinates mapped through the canvas's client rect, and XML Tiled maps still parsed. Removing the `hasDOM()` gate from touch detection or the WebGL check, or inverting the one on appendChild, fails it. The two browser-side tests of nxjs-compatibility.spec.js move here. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JnzwA4ysaW5ocy8xevRB4k
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JnzwA4ysaW5ocy8xevRB4k
A short section under Getting Started: melonJS on nx.js or rendering in a Web Worker, given the canvas as `settings.canvas`, marked experimental as it is tested against a simulation of nx.js and not yet on Switch hardware, with a link to the deployment skill for what differs. The changelog entry says the same. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JnzwA4ysaW5ocy8xevRB4k
Every remaining `typeof document` test in the engine goes through `hasDOM()`: the audio engine and context, the font face loader and its unload, the glTF base URL, getUriFragment, the wheel, pointer lock, video and fullscreen probes, and DOMContentLoaded. `hasDOM` moves above DOMContentLoaded in dom.ts, which now uses it. The changelog entry is shortened and names nx.js with its link, and the README calls it the Nintendo Switch homebrew framework. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01SeWCkETjW35ZbXDRswV1hx
This was referenced Nov 12, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
melonJS can now run where there is no
document: on nx.js (JavaScript homebrew for the Nintendo Switch) and, for rendering and loading, in a Web Worker. The game hands the Application the canvas to draw to:Browsers are unchanged. Every new path is gated on
documentbeing missing (an internalhasDOM()insrc/system/dom.ts), or on an API being missing. The few lines a page still runs give the same result as before, such as theOffscreenCanvascheck, now done with a 1×1 canvas instead of 0×0.Engine changes (
packages/melonjs/src)app.parentElementisundefinedthere and keeps itsHTMLElementtype, so existing TypeScript games don't break.scale: "auto"draws at the given size.style: a canvas with nostyle(nx.js'sscreen) is accepted wherever the engine sets CSS on it:display, CSS size andtouch-action.<canvas>it cannot create. Creating the renderer decides, andvideo.AUTOstill falls back to Canvas.OffscreenCanvaswith no DOM, and gives a clear error when there is neither a DOM nor anOffscreenCanvas. TheOffscreenCanvascheck now uses a 1×1 canvas, because nx.js rejects 0×0.TouchEventcounts as touch support, and touches use client coordinates. nx.js touches have nopageX/Y.Audiohas nocanPlayType(nx.js), every format is assumed playable and decoding reports the ones that fail.screen.orientationis read inside atry/catch; nx.js's getter throws, which brokeimport "melonjs".DOMParserfails with an error saying to export it as JSON (.tmj/.tsj). The loader keeps the parser's error as thecauseof its "Failed loading resource" error, in bothpreload()andreload().Tests
tests/nxjs-compatibility.spec.js: runs a game in a Web Worker made to look like nx.js, using nx.js's own source as the reference: a style-lessscreenwhoseorientationthrows,OffscreenCanvasthat is 2D-only and rejects 0×0, touches withoutpageX, a Web Audio stand-in, anAudiowithoutcanPlayType, and no XHR,DOMParseror WebGPU. With the Canvas, WebGL and AUTO renderers it checks:OffscreenCanvas.tests/nxjs-browser-parity.spec.js: in a real page, checks each capability touched against the formula it was computed with before (touchEvent,pointerEvent,touch,maxTouchPoints,screenOrientation,offscreenCanvas, WebGL support, audio formats). It also checks the DOM helpers, and that anApplicationstill mounts in the page with the same canvas styling, coordinate mapping and XML map parsing.Docs
melonjs-deployment, with setup, what differs and symptom rows. Notes inmelonjs-tilemaps(use JSON maps),melonjs-audio(no gesture needed; nodes nx.js lacks) andmelonjs-input, plus the router entry.Limits
OffscreenCanvas, which melonJS avoids in browsers (textImage2Ddoes not accept OffscreenCanvas anymore as a pixel source for texture #1180); it renders correctly in the Chromium worker but should be checked on a Switch.tone()/noise()and the audio effects, whose nodes nx.js doesn't implement;fontfaceassets, because nx.js'sFontFacetakes bytes, not a URL. The skill shows how to load the bytes and add the face by hand.AudioContextof its own, so those have to come from the page.import "melonjs"works there, but it can't run a game.Type of change
Checklist
pnpm lintpasses): run for themelonjspackage (ESLint oversrcandtests, Biome,tsc), with no new warnings on changed lines; no other package changedpnpm testpasses): the full engine suite (452 files, 10,810 tests) passed before the parity spec was added; the parity and nx.js specs (31 tests) pass togetherpnpm build): themelonjspackage buildRelated issues
None.
Generated by Claude Code