Skip to content

fix: enqueue Vite entries as script modules, after WordPress's import map - #357

Merged
ogorzalka merged 3 commits into
developfrom
fix/vite-entries-as-script-modules
Sep 29, 2026
Merged

ogorzalka merged 3 commits into
developfrom
fix/vite-entries-as-script-modules

Conversation

@ogorzalka

Copy link
Copy Markdown
Member

Problem

A Vite entry is an ES module, but AssetEnqueuer enqueued it as a classic script (wp_enqueue_script + a rewritten <script type="module"> tag), so it followed the classic default: the <head>. WordPress prints its import map later:

Theme Import map Vite entry (before)
Block <head>, after classic scripts <head>, before it
Classic footer <head>, before it

Firefox and Safari ignore an import map once a module has started, so every WordPress module on the page then fails with @wordpress/interactivity was a bare specifier — the navigation block's menu no longer opens. Chromium tolerates the order, which hid it. A classic theme (theme-default, apiary) was exposed as soon as an author inserted a navigation, search or lightbox block. Found by Buzz's Firefox tests (Pollora/theme-buzz#1).

Change

On wp_enqueue_scripts and admin_enqueue_scripts — the pages where WordPress prints modules — a Vite entry is enqueued with wp_enqueue_script_module, so WordPress places it exactly as it places its own modules:

  • block theme: <head> after the import map, or footer with loadInFooter() (passed as in_footer);
  • classic theme: footer, as WordPress forces for modules.

Details:

  • dependencies(), localize(), inline(): a module takes none of them, so they go on a classic companion {handle}-data (registered with no source), printed in the head, which runs before the deferred module. No first-party theme or plugin uses them with Vite.
  • No version appended (null, not false): a module is identified by its URL, and ?ver= would let a chunk importing the entry load a second copy.
  • crossorigin kept, via wp_script_attributes.
  • HMR: the dev server's @vite/client is enqueued as a module too (new ViteManagerInterface::clientUrl()); it had the same problem.
  • Editor, login screen, Customizer: unchanged — WordPress prints no modules there.

Tests

  • Feature: 7 cases in AssetEnqueuerTest (front/admin module, in_footer, no version, editor stays classic, crossorigin on this tag only, companion, Vite client). Full suite 1312 passed; Pint, PHPStan, Rector clean.
  • E2E script-modules.spec.ts: a post with a navigation block; the import map precedes every theme Vite entry (read from the HTML, so it fails in Chromium too), and the menu opens with no page error.
    • apiary (classic), Chromium + Firefox: ✅; without the fix: ❌ on both.
    • Buzz (block), Chromium + Firefox: ✅.
  • Checked by hand: block theme head / block theme loadInFooter() / classic theme footer, each after the import map.
  • Not run live: HMR (unit-tested only), WebKit (missing system libs on this machine; the nightly runs it).

CHANGELOG updated. Docs: Pollora/documentation PR to follow. Will conflict with #356 on CHANGELOG only.

… map

A Vite entry is an ES module, but it was enqueued as a classic script and
printed in the head, before WordPress's import map. Firefox and Safari then
ignore the map, so every WordPress module on the page failed on
@wordpress/interactivity. In a classic theme the map is always in the
footer, so any theme was exposed as soon as a navigation block was used.

On wp_enqueue_scripts and admin_enqueue_scripts, a Vite entry (and the dev
server's client) is now a script module: WordPress places it as it places
its own. Dependencies, localized data and inline script go on a classic
companion, {handle}-data. No version is appended: a module is identified by
its URL.
@ogorzalka
ogorzalka merged commit e618bb2 into develop Sep 29, 2026
11 checks passed
@ogorzalka
ogorzalka deleted the fix/vite-entries-as-script-modules branch September 29, 2026 14:30
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