Skip to content

feat: lazy useDrupalCe().loadLibrary() for on-demand Drupal JS libraries#504

Merged
fago merged 5 commits into
2.xfrom
feature/mcd-605-load-library
Jul 6, 2026
Merged

feat: lazy useDrupalCe().loadLibrary() for on-demand Drupal JS libraries#504
fago merged 5 commits into
2.xfrom
feature/mcd-605-load-library

Conversation

@drubot

@drubot drubot commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Expose a useDrupalCe().loadLibrary() API so a component can lazily load a Drupal JS library (form #states, autocomplete, …).

Design

The heavy loader — ordered script queue + dedup, drupalSettings seeding, run-once Drupal.attachBehaviors() — lives in its own module (drupalLibraryLoader.ts) reached via a dynamic import() from the composable, so the loader and the Drupal JS it pulls in are split into a separate chunk fetched only on first loadLibrary() call — nothing ships in the main bundle for pages that never touch a Drupal library.

loadLibrary(library) takes the backend-resolved library { js, drupalSettings } — the shape the backend emits (JS files in dependency order + merged settings) on a <drupal-library-*> custom element. The frontend just loads it; there is no name→assets resolution on the client. Server-side it's a no-op.

The loader import() is cached in a single module-level promise so all callers await the same promise and enqueue in strict call order — otherwise per-call import() can resolve out of order (Vite dev) and scramble the load order.

Context

Foundation for decoupled webform conditions — drunomics YouTrack MCD-605 / MCD-607. The backend that generates the <drupal-library-*> elements lives in the custom_elements module (drupal.org MR pending).


Drafted with Claude Code from the loki dev VM.

…braries

Expose a loadLibrary() API so custom components can lazily load a Drupal
library (form #states, autocomplete, …) exactly when needed. The loader —
ordered script queue, dedup, drupalSettings seeding, run-once attachBehaviors
— lives in its own module (drupalLibraryLoader) reached via a dynamic import(),
so it and the Drupal JS it pulls in are split into a separate chunk fetched
only on first use, not shipped in the main bundle.

Accepts a resolved library ({ js, drupalSettings }, as emitted on a
<drupal-library-*> element) or a library name resolved via a companion
/drupal-library/{name} backend endpoint.
loki et fago added 3 commits July 6, 2026 11:22
Cache the dynamic import() of the loader in one module-level promise so all
loadLibrary() callers await the same promise; their continuations then run in
strict call order, keeping libraries enqueued in dependency order even when
many <drupal-library-*> elements call it in the same tick (per-call import()
can resolve out of order under Vite dev, scrambling the load order).
The backend emits each library's resolved JS (and merged drupalSettings) on the
<drupal-library-*> element, so loadLibrary() just loads that — drop the
by-name variant and the /drupal-library/{name} fetch.
Unit-test drupalLibraryLoader: ordered/absolute script injection, async=false
tagging, dedup across calls, dependency order across calls, and drupalSettings
seeding (with a mocked script insertion that loads immediately).
Ship the renderless drupal-library--default scaffold component (a thin consumer
of loadLibrary()) alongside the other drupal-* defaults, and cover it: renders
no markup and loads the resolved library on mount.
@fago fago merged commit 211bbdd into 2.x Jul 6, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants