Skip to content

feat(extensions): nativescript.commands map — per-command lazy loading for extensions - #6102

Draft
edusperoni wants to merge 5 commits into
feat/define-commandfrom
feat/extension-manifests
Draft

feat(extensions): nativescript.commands map — per-command lazy loading for extensions#6102
edusperoni wants to merge 5 commits into
feat/define-commandfrom
feat/extension-manifests

Conversation

@edusperoni

Copy link
Copy Markdown
Collaborator

Stacked on #6101 (feat/define-command), which stacks on #6099.

PR Checklist

What is the current behavior?

Every installed extension is eagerly require()d on every CLI invocation, before the command is even known — the extension's whole module tree loads so its top-level side effects can register commands against global.$injector. nativescript.commands in an extension's package.json is a string[] used only to suggest installs for unknown commands. Two extensions claiming the same command name crash at startup.

What is the new behavior?

nativescript.commands also accepts a map of command name → module path, which becomes authoritative:

"nativescript": {
	"commands": {
		"widget|add": "./dist/commands/widget-add.js"
	}
}
  • Per-command lazy loading: the CLI registers each entry as a deferred require; nothing from the extension loads until one of its commands actually executes. The extension main is never required, and map-manifest extensions are no longer flagged by the deprecation tracer (the legacy array/eager path keeps working verbatim, tracer included).
  • A command module may self-register on load (legacy shape) or simply export a defineCommand definition — the deferred loader adapts and registers the export under the manifest key, including the subtle hierarchical case where dispatch resolves the parent dispatcher before any child module has loaded.
  • Conflicts warn instead of crash, naming both the losing and the owning extension; malformed manifest entries warn and are skipped; a broken extension still never aborts startup (settlePromises isolation preserved).
  • Install suggestions for unknown commands now understand both manifest shapes, keeping the |* default-command short-form behavior.
  • IExtensionData gains commands?: string[].
  • New authoring guide: extensions.md; dependency-injection.md now cross-links all three API guides.

12 new tests (lazy registration, eager-path preservation, malformed/conflict handling, both suggestion shapes, pure-definition modules incl. the parent-first case). Full stacked suite: 111 files, 1628 passed / 38 skipped; yok oracle, public-API test, and compat fixtures untouched.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b1ab3f63-73ca-4d4c-bbf0-4f5b62a61fed

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@edusperoni
edusperoni force-pushed the feat/define-command branch from b15734b to 863f964 Compare July 30, 2026 01:39
@edusperoni
edusperoni force-pushed the feat/extension-manifests branch from 220e027 to 02d6f7c Compare July 30, 2026 01:41
@edusperoni
edusperoni force-pushed the feat/define-command branch from 863f964 to 74caa8f Compare July 30, 2026 01:44
@edusperoni
edusperoni force-pushed the feat/extension-manifests branch from 02d6f7c to d8a8fcf Compare July 30, 2026 01:44
@edusperoni
edusperoni force-pushed the feat/define-command branch from 74caa8f to cafa737 Compare July 30, 2026 02:27
@edusperoni
edusperoni force-pushed the feat/extension-manifests branch from d8a8fcf to e0c671c Compare July 30, 2026 02:28
…s map

An extension whose package.json declares nativescript.commands as a map of
command name to module path is no longer require()d at startup. Each entry is
registered with injector.requireCommand against the module's absolute path, so
a command's implementation loads only when that command is first resolved, and
the CLI stops paying every installed extension's load cost on every
invocation.

Entries are validated: a command name or module path that is not a non-empty
string is warned about and skipped, and a name already claimed by another
extension is reported as a warning naming both extensions rather than
propagating the injector's "require'd twice" failure.

The legacy array shape (and a missing commands key) keeps today's behavior
verbatim - eager require of the extension main plus the
extensions.require-time-registration deprecation report. Both shapes now feed
IExtensionData.commands and the npm install suggestion for unknown commands.
A manifest entry may now point at a module that exports a defineCommand
definition instead of registering itself on load: the deferred loader
adapts and registers the export under the manifest key. The override
also lands on a parent record the entry just created, because dispatch
resolves the hierarchical parent before any child module has loaded and
the dispatcher only comes into existence once a child registers.

Also cross-links the authoring guides from dependency-injection.md.
… seam

The service takes $injector as a constructor dependency instead of the
module-level import, so manifest registration and the definition-aware
loaders target the instance that resolved it. Tests assert on their own
per-test injector; the process-wide injector is swapped only because
legacy-shape fixture modules register through the published global
surface at load, and that seam is labeled as such.

extensions.md no longer teaches the global-injector patterns: the legacy
array path and self-registering modules are described under their
deprecation framing without runnable samples.
Registry operations go through the narrow subsystem contract; the full
facade stays only for container-record operations (has, provider
registration). First consumer of the per-face tokens.
@edusperoni
edusperoni force-pushed the feat/define-command branch from cafa737 to a1ba0ef Compare July 30, 2026 02:51
@edusperoni
edusperoni force-pushed the feat/extension-manifests branch from e0c671c to 10aaa87 Compare July 30, 2026 02:52
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.

1 participant