Skip to content

best-practices: organizing client code across files - #8

Merged
topkoa merged 2 commits into
mainfrom
docs/best-practices-code-splitting
Jul 6, 2026
Merged

best-practices: organizing client code across files#8
topkoa merged 2 commits into
mainfrom
docs/best-practices-code-splitting

Conversation

@topkoa

@topkoa topkoa commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Answers "how do I split screen.js so my plugin isn't one giant file?" — ground-truthed against how the Host actually loads and serves plugin JS, so the advice matches what really works (not what you'd assume).

The constraints that shape the advice

  • screen.js is a classic script, not an ES module — the Host injects it with no type="module". So top-level import/export and import.meta don't work in it; split files share state through window (namespaced by id).
  • The plugin root is not servable. Only screen.js, screen.html, settings.html, tour.json, and everything under assets/ are served. /api/plugins/<id>/lib/util.js → 404; /api/plugins/<id>/assets/lib/util.js → served (traversal-guarded, correct JS MIME).
  • Relative URLs break. A relative import('./part.js') from a classic script resolves against the document base, not the script — so extra files must be referenced by absolute /api/plugins/<id>/assets/… URLs.
  • No bundler ships in-tree — plugins are served verbatim, so runtime splitting is the observed path (tuner splits into utils//visualization//workers/ via its own routes.py; highway_3d lazy-loads vendor code from assets/). Bundling is still the cleanest option, just your own build step.

Rules (27–29)

  • 27 — Prefer bundling to one screen.js; split at runtime only when you must. Plus the classic-script constraint and window-sharing.
  • 28 — Serve extra files from assets/, reference by absolute URL. assets/ is the built-in served dir; a routes.py is the escape hatch for other layouts. Dynamic import() of an ES module from assets/ works; classic <script> injection works for window-attaching helpers. Includes an ASSET_BASE example.
  • 29 — Load each split file exactly once. Re-hydration re-runs screen.js (rule 12), so de-dupe runtime loads with a Set/symbol check. Includes a loadScriptOnce helper.

Added a "Split client code" checklist block; Shipping renumbered to 30–34 (contiguous 1–34).

Scope & stacking

Docs only (spec/best-practices.md + CHANGELOG.md). No version bump. API facts framed as the current Host contract. Stacked on #7. Full stack: #2#4#3#5#6#7#8. mkdocs build --strict and check_versions.py pass.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 213339e0-c57c-4cc2-9e39-d83db65b2f02

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/best-practices-code-splitting

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds documentation guidance on splitting a plugin’s client-side screen.js into multiple files in a way that matches the Host’s actual loading/serving behavior (classic script execution, served paths, URL resolution, and re-hydration idempotency).

Changes:

  • Introduces a new “Organizing client code across files” section with rules 27–29 (bundling preferred; runtime splitting via assets/ + absolute URLs; load-once/idempotency).
  • Renumbers “Shipping & good citizenship” rules to 30–34 and adds a “Split client code” checklist block.
  • Updates CHANGELOG.md to describe the new best-practices section.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
spec/best-practices.md Adds new best-practices section + checklist for splitting client code; renumbers later rules.
CHANGELOG.md Documents the added best-practices section in Unreleased notes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread spec/best-practices.md Outdated
Comment thread spec/best-practices.md Outdated
Comment thread spec/best-practices.md Outdated
Comment on lines +554 to +555
- [ ] Split files share state via `window.<id>…` (classic script — no `import`/`export` in
`screen.js`).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: checklist now uses window["<id>"].

Comment thread spec/best-practices.md Outdated
@topkoa
topkoa force-pushed the docs/best-practices-minigames branch from ed41f2c to ee7d269 Compare July 6, 2026 04:27
topkoa and others added 2 commits July 6, 2026 00:27
Add guidance for splitting a plugin's client JS instead of one monolithic
screen.js, ground-truthed against how the Host loads and serves plugin JS.

Rules (27-29):
- Prefer bundling to one screen.js if you have build tooling — the Host loads one
  script, sidestepping the runtime gotchas. (No bundler ships in-tree; it's your
  own build step.)
- screen.js runs as a CLASSIC script: no top-level import/export, no import.meta.
  Split files share state via window (namespaced by id), not ES exports.
- Serve extra files from assets/ (the plugin root is NOT servable — a file there
  404s; under assets/ the Host serves it traversal-guarded with a JS MIME), or a
  routes.py-served dir. Reference by ABSOLUTE /api/plugins/<id>/assets/... URLs —
  a relative import() from a classic script resolves against the document, not
  the script. Dynamic import() of an ES module from assets/ works; classic
  <script> injection works for window-attaching helpers.
- Load each split file exactly once (idempotent) — the Host may re-run screen.js,
  so de-dupe with a loaded-set / window-symbol check or re-hydration double-loads.

Renumber Shipping to 30-34 and add a checklist block. Docs only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: K. O. A. <topkoa@gmail.com>
… await, robust loader)

- Rule 27 + checklist: key the window namespace with bracket notation
  (window['my-plugin']), since ids may contain '-' (window.my-plugin is a
  syntax error).
- Rule 28: the example no longer uses top-level await (a syntax error in a
  classic script) — use import(...).then / an async IIFE.
- Rule 29: rewrite loadScriptOnce to keep its cache on window (survives a
  screen.js re-run), cache the in-flight promise, and drop the entry on failure
  so a transient error can be retried.

Signed-off-by: K. O. A. <topkoa@gmail.com>
@topkoa
topkoa force-pushed the docs/best-practices-code-splitting branch from 25ce206 to 2c0f693 Compare July 6, 2026 04:29
Base automatically changed from docs/best-practices-minigames to main July 6, 2026 04:44
@topkoa
topkoa merged commit ef244d8 into main Jul 6, 2026
8 checks passed
@topkoa
topkoa deleted the docs/best-practices-code-splitting branch July 6, 2026 04:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants