feat: @melonjs/capacitor-plugin + create-melonjs --template capacitor#1439
Open
feat: @melonjs/capacitor-plugin + create-melonjs --template capacitor#1439
Conversation
A `BasePlugin`-style adapter that bridges Capacitor's native
lifecycle and hardware-back events into the engine, so a melonJS
game wrapped as a hybrid iOS/Android app pauses on background,
resumes on foreground, and lets each Stage intercept hardware-back
without manual `event.on` / `event.off` boilerplate.
Public surface:
- `CapacitorPlugin` — register via
`plugin.register(CapacitorPlugin, "capacitor", options?)`.
Auto-wires `App.addListener("appStateChange")` →
`state.pause/resume` and `App.addListener("backButton")` → engine
event bus.
- `bindStageBack(stage, handler)` — per-stage back-button handler
whose lifetime matches `onResetEvent` / `onDestroyEvent`. Handler
receives a `CapacitorBackEvent` with `preventDefault()` to
suppress the default action (typically `App.exitApp()`).
- `onBackButton(handler)` — global, stage-agnostic equivalent.
- `lockOrientation` / `unlockOrientation` / `hideSplash` — thin
lazy wrappers around the matching Capacitor plugins
(`@capacitor/screen-orientation` and `@capacitor/splash-screen`,
declared as optional peer deps so they're only pulled when used).
Required runtime peer is `@capacitor/app`; melonJS peer pinned to
`>=19.3.0` (the version that shipped the surface this plugin
relies on).
Functionally a melonJS game already runs inside Capacitor's WebView
unmodified — this package is purely an ergonomics layer over the
standard `@capacitor/app` events. No engine changes were needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`npm create melonjs <name>` now accepts `--template <name>` (alias `-t`). Two templates are recognized: - `default` — current behavior, points at `melonjs/typescript-boilerplate`. - `capacitor` — points at `melonjs/typescript-boilerplate-capacitor` (a separate boilerplate repo, to be created), pre-wired with `@melonjs/capacitor-plugin` for iOS/Android wrapping via Capacitor. Unknown template names error out with the list of known templates. The output now reports which template was used and prints template-specific next-step instructions (the `capacitor` path includes `cap add ios/android`, `cap copy`, `cap open`). Bumped to 1.1.0. README updated with a templates table. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds first-class Capacitor support to the melonJS monorepo by introducing a new Capacitor integration plugin package and extending create-melonjs to scaffold a Capacitor-oriented starter.
Changes:
- Introduce new
@melonjs/capacitor-pluginpackage to bridge Capacitor lifecycle + Android hardware-back into melonJS (plus optional orientation/splash helpers). - Add
--template/-tsupport tocreate-melonjs, including a newcapacitortemplate option and updated docs/changelog. - Update lockfile to include Capacitor-related dependencies for the new package.
Reviewed changes
Copilot reviewed 20 out of 22 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Adds lock entries/importer config for the new packages/capacitor-plugin dependencies. |
| packages/create-melonjs/README.md | Documents the new --template flag and available templates. |
| packages/create-melonjs/package.json | Bumps version to 1.1.0 for the new feature. |
| packages/create-melonjs/CHANGELOG.md | Records the new --template feature and behavior changes. |
| packages/create-melonjs/bin/create-melonjs.js | Implements --template/-t parsing, template selection, and template-specific next steps. |
| packages/capacitor-plugin/tsconfig.json | Adds TS config for local typechecking in the new package. |
| packages/capacitor-plugin/tsconfig.build.json | Adds TS config for declaration-only builds into build/. |
| packages/capacitor-plugin/src/types.ts | Defines public option and event types (ConnectCapacitorOptions, CapacitorBackEvent). |
| packages/capacitor-plugin/src/splash.ts | Adds lazy @capacitor/splash-screen wrapper helper. |
| packages/capacitor-plugin/src/plugin.ts | Adds CapacitorPlugin (BasePlugin) that installs the wiring on registration. |
| packages/capacitor-plugin/src/orientation.ts | Adds lazy @capacitor/screen-orientation wrapper helpers. |
| packages/capacitor-plugin/src/index.ts | Public exports for the package API surface. |
| packages/capacitor-plugin/src/connect.ts | Wires @capacitor/app events to state.pause/resume and back-button forwarding. |
| packages/capacitor-plugin/src/bind-stage-back.ts | Adds per-Stage lifecycle helper for hardware-back subscription. |
| packages/capacitor-plugin/src/back-event.ts | Implements internal event-bus forwarding + subscription helpers. |
| packages/capacitor-plugin/scripts/clean.ts | Adds build folder cleanup script. |
| packages/capacitor-plugin/scripts/build.ts | Adds esbuild bundling script for ESM output + banner. |
| packages/capacitor-plugin/README.md | Documents installation, quickstart, and API usage for the plugin. |
| packages/capacitor-plugin/package.json | Adds package manifest (peers/devDeps/scripts/exports) for publishing. |
| packages/capacitor-plugin/LICENSE | Adds package license file. |
| packages/capacitor-plugin/CHANGELOG.md | Adds initial changelog entry for 1.0.0. |
| packages/capacitor-plugin/.gitignore | Adds ignores for build artifacts and local files. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Drop the legacy `boot()` + `video.init(...)` pair from the README's quick-start example in favor of `new Application(width, height, options)`, which is the modern entry point. The Application constructor handles boot + renderer init in one call. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five issues from Copilot, one real bug + four polish fixes: - bind-stage-back: subscribe immediately, not just on the next reset. The documented call site is inside the stage's `onResetEvent`, so the wrapped reset method only runs on subsequent resets — leaving the handler silent for the current activation. Now subscribes eagerly, with the wrapper still re-subscribing idempotently across destroy/reset cycles. - types: widen `onUnhandledBack` to `() => void | Promise<void>` so TypeScript accepts async handlers (e.g. confirm-dialog flows). - connect: when a user-provided `onUnhandledBack` rejects or throws, emit a console.warn instead of letting it bubble as an unhandled rejection. Same treatment in `teardown()` for failed `h.remove()` calls. - package.json: optional peer deps must appear in `peerDependencies` for `peerDependenciesMeta.optional` to actually take effect (npm ignores meta entries with no matching peer). Added `@capacitor/screen-orientation` and `@capacitor/splash-screen` to `peerDependencies` with the same `>=6.0.0` range as the required `@capacitor/app` peer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two follow-ups on PR #1439: - Default `onUnhandledBack` now returns the `App.exitApp()` promise (`() => App.exitApp()`) instead of swallowing it via `void`. Same rejection-handling path as user-provided async handlers — a failed exit surfaces as a console.warn instead of an unhandled rejection. - `connectCapacitor` returns an async teardown (`() => Promise<void>`) that awaits `Promise.allSettled(handles.map(h => h.remove()))`, so tests / hot-reload paths can `await cap.teardown()` for deterministic detachment. Calling without awaiting still works (fire-and-forget). `CapacitorPlugin.teardown` and the README's teardown blurb updated to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The plugin doesn't actually depend on anything 19.x-specific. It needs: - `new Application(width, height, options)` as a standalone entry point (added in 18.3.0 — auto-calls `boot()`). - `Stage.onResetEvent(app, ...args)` and `Stage.onDestroyEvent(app)` signatures with `app` as first parameter (added in 18.3.0). - `plugin.BasePlugin`, `state.pause(boolean)` / `state.resume(boolean)`, the `event` namespace — all stable on 18.3.0. Lowering the peer dep range from `>=19.3.0` to `>=18.3.0` makes the plugin install for users still on 18.x. Matches the floor used by other plugins in the monorepo. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The plugin's compatibility floor (`this.version` on `BasePlugin`) was hardcoded as `"18.3.0"` in `plugin.ts`, duplicating the `peerDependencies.melonjs` range in `package.json`. Bumping the floor required two edits in two files and risked drift. Now the build script reads `peerDependencies.melonjs`, strips the range prefix (`>=`, `^`, `~`, ...), and injects the bare semver as `__MELONJS_PEER__` via esbuild's `define`. `plugin.ts` references it as `declare const` so tsc still type-checks the source. Verified the bundle contains the substituted literal `"18.3.0"`. Future floor bumps are now a one-line edit to `package.json`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment on lines
+51
to
+55
| const origReset = stage.onResetEvent.bind(stage); | ||
| const origDestroy = stage.onDestroyEvent.bind(stage); | ||
| stage.onResetEvent = function (app: Application, ...args: unknown[]) { | ||
| subscribe(); | ||
| origReset(app, ...args); |
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.
Summary
Adds first-class Capacitor support to the melonJS monorepo:
@melonjs/capacitor-plugin— aBasePlugin-style adapter that bridges Capacitor's native lifecycle and hardware-back events into the engine. No engine changes were needed; this is purely an ergonomics layer over the standard@capacitor/appevents.create-melonjs --template capacitor— new template flag that scaffolds from a Capacitor-flavored boilerplate (melonjs/typescript-boilerplate-capacitor, to be created separately)./tmp/Capacitor.mdlocally; needs to be pasted into themelonjs/melonJS.wikirepo.What the plugin gives you
vs. ~25 lines of raw
App.addListener(...)boilerplate per game today.pauseOnBackground,pauseAudiooptionsappStateChange→state.pause/resumebindStageBack(stage, fn)/onBackButton(fn)backButton→ engine event bus,evt.preventDefault()to suppress default exitlockOrientation("portrait" | "landscape"),unlockOrientation()@capacitor/screen-orientationhideSplash({ fadeOutDuration })@capacitor/splash-screen@capacitor/appis the only required peer dep; orientation and splash deps are optional and dynamically imported only when the corresponding helpers are called.Files
packages/capacitor-plugin/— new package, ~150 LOC of source across 8src/*.tsfiles plus the standard scaffolding (package.json, tsconfigs, scripts, README, CHANGELOG, LICENSE).packages/create-melonjs/—--template <name>flag, version bump 1.0.1 → 1.1.0, README + CHANGELOG.Test plan
pnpm lint— 0 errors.pnpm build(packages/capacitor-plugin) — emitsbuild/index.js+.d.tscleanly.pnpm vitest run— 2933 melonjs tests still pass; no regressions.node packages/create-melonjs/bin/create-melonjs.js— usage banner shows the new flag and templates.node ... my-game --template bogus— errors out with the list of known templates.melonjs/typescript-boilerplate-capacitorrepo to exist).Follow-ups (not in this PR)
melonjs/typescript-boilerplate-capacitorGitHub repo (Vite + melonJS +@capacitor/*+@melonjs/capacitor-pluginpre-wired). The--template capacitorflag will 404 until this repo exists./tmp/Capacitor.mdinto themelonjs/melonJS.wikirepo.🤖 Generated with Claude Code