Skip to content

iAPI Router: Support new styles and script modules on client-side navigation #70353

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 135 commits into from
Jun 18, 2025

Conversation

DAreRodz
Copy link
Contributor

@DAreRodz DAreRodz commented Jun 6, 2025

What?

This PR updates the current Interactivity API's client-side navigation feature, addressing the following items:

  • The style assets manager introduced previously in #67826 and removed in #69222 has been reimplemented. It replaces the document.adoptedStyleSheets approach with another based on new <style> and <link rel=stylesheet> elements in the DOM with a media=preload attribute.
  • A script module manager has been included, supporting multiple importmaps from different pages.
  • The Interactivity API's router has been adapted to support full-page client-side navigation while sharing most of the logic used by the region-based navigation feature.

Why?

Style sheet manager

This piece is necessary to update the applied style sheets after a client-side navigation. New pages, even when navigating between pages with the same template, can have different style sheets, as they can contain different blocks.

The previous approach was based on the document.adoptedStyleSheets API, which requires constructed CSSStyleSheet instances to be passed. However, for third-party style sheets without the appropriate CORS configuration, these instances were impossible to create, as the CSS code is unreadable (more info at #69222). A different approach was necessary to support this case.

Script module manager

As new blocks may appear, not only should their style sheets be handled, but also their script modules when they are interactive. These pages can contain different importmaps, as new interactive blocks could have different dependencies than those on the initial page.

This needed to be addressed as well to support new interactive blocks on client-side navigation (whether the navigation mode was region-based or full-page).

Full-page client-side navigation

With these changes, the full-page client-side navigation now shares almost all of its logic with the region-based navigation, so it makes sense to add it again here. This experimental feature was removed in #70228.

How?

Style sheet manager

The new approach works as follows: when a new page is prefetched, the <style> and <link rel=stylesheet> elements are cloned and added to the DOM with a media="preload" attribute, saving the original media value for later. Once added, they are kept disabled until the corresponding page is visited, restoring the original media attribute value. The style sheets are preserved in the DOM between navigations to prevent additional network requests, enabling or disabling them depending on the active page, using the sheet.disabled property.

Script module manager

To support dynamic importmaps, the implementation utilizes the es-module-lexer package, making minimal modifications to import statements to direct them to the correct module specified in the corresponding importmap. These modified modules are later converted into Blobs to be imported as regular modules

Full-page client-side navigation

The event handlers that prefetch and navigate to new pages have been moved to a separate module, which dynamically imports the main module as needed.

The PHP part has been refactored and simplified.

DAreRodz and others added 30 commits June 5, 2025 13:44
Copy link
Member

@luisherranz luisherranz left a comment

Choose a reason for hiding this comment

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

LGTM, David!

I would still like to improve the comments and types of the files we have copied from external libraries, but we can do it in a follow-up pull request.

@DAreRodz DAreRodz enabled auto-merge (squash) June 17, 2025 17:28
@DAreRodz DAreRodz disabled auto-merge June 18, 2025 06:25
@DAreRodz DAreRodz force-pushed the feature/iapi-new-client-side-navigation branch from 3bd6771 to a27998a Compare June 18, 2025 06:35
@DAreRodz DAreRodz merged commit fc20efc into trunk Jun 18, 2025
69 of 70 checks passed
@DAreRodz DAreRodz deleted the feature/iapi-new-client-side-navigation branch June 18, 2025 07:20
@github-actions github-actions bot added this to the Gutenberg 21.1 milestone Jun 18, 2025
@sethrubenstein
Copy link
Contributor

giphy
Trying this out Monday morning!

@luisherranz
Copy link
Member

Thank you very much, Seth. It's an important change, so we need all the feedback and testing we can get 🙂

@danielpost
Copy link

This worked for me and fixes an issue I was facing, thanks!

@luisherranz
Copy link
Member

Thanks, Daniel. Could you describe that issue?

@danielpost
Copy link

@luisherranz Sure! It's basically the same issue as you describe in this comment: #63053 (comment)

I'm building a plugin with instant search, taxonomy filters etc. for a query loop. If you search for something that doesn't exist, it'll show the "No results" block and update the search query in the URL. If you then refresh the page, it'll also show the "No results" block (as expected).

If you then clear the search so results appear again some styles will be missing—in my case the styles for the grid with results.

(you can see it in the live demo here. try searching for something non-existent, refreshing, and then clearing the filters: https://betterloopwp.com/#demo)

@luisherranz
Copy link
Member

Nice, thank you very much for the example. I'm glad it's working well now.

By the way, you can add data-wp-key to the li elements to avoid the flickering of the images.

@danielpost
Copy link

@luisherranz Awesome, thank you for the tip! Implementing that right now.

chriszarate pushed a commit to chriszarate/gutenberg that referenced this pull request Jul 1, 2025
…igation (WordPress#70353)

* Revert "Remove the iAPI full-page experiment entirely (WordPress#70228)"

This reverts commit f693991.

* Reapply "iAPI Router: Handle styles assets on region-based navigation (WordPress#67826)"

This reverts commit 0a5be06.

* Reapply "iAPI Router: Fix CSS rule order in some constructed style sheets (WordPress#68923)"

This reverts commit a53bb20.

* Clone style elements and enable/disable them

* Combine `media` and `disabled`

* Add prefetch callback to style tests

* Remove `async` from `applyStyles`

* Add basic error handling

* Rename `baseUrl` to `url`

* Improve comment wrappers around new styles sheets

* Minor fixes

* Use a SCS algorithm to add new style sheets

* Refactor and document `updateStylesWithSCS`

* Fix promises returned by `updateStylesWithSCS`

* First unit tests (claude)

* Test improvements (claude)

* Tests for shortestCommonSupersequence (claude)

* Move scs tests a folder up

* Refactor scs tests a bit

* Refactor `updateStylesWithSCS` and `applyStyles` unit tests

* Add new test for media attributes

* Fix last appended elements

* Rename and document `areStylesEqual`

* Improve `areStyleEqual` TSDoc

* Add more TSDocs

* Add TSDoc for `shortestCommonSupersequence`

* Test stylesheet loading fails

* Wrap `url` with `getPagePath`

* Make `url` param mandatory in `prepareStyles`

* Replace wrong `prefetch` words with `preload`

* Fix failed to load message

* Clarify the `element.sheet` check.

* Wrap unit tests inside an additional `describe`

* Remove unnecesary comment

* Add TSDocs for helpers in styles.test.ts

* Add missing periods in test comments

* Normalize elements before calling `shortestCommonSupersequence`

* Refactor `normalizeMedia`

* Handle script modules

* Install dynamic-importmap

* Try with dynamic-importmap

* Try ignore originally imported modules

* Use a local copy of dynamic-importmap

* Make it work

* Cleaning up WIP

* Add test files

* Add an initial simple test

* Partially fix pattern to intercept js requests

* Add missing asset.php files

* Try adding a module dependency

* Disable module preloading

* WIP Attempt code reorganization

* WIP type Load objects

* WIP Refactor types and add functions for preloading

* Update router code

* Add prefetch action to tests

* Fix modules not being imported

* Refactor script file and add TSDocs

* Improve types and comments a bit

* Improve types and TSDocs a little bit more

* Add static dependencies to test modules

* Make prefetch action asynchronous

* Add failing test

* Fix media preload when the target style list is empty

* Update more tests

* Fix how initial style sheets are detected

* Detect first if the link has started prefetching

* Add a failing test

* Only enable/disable sheets that are ready

* Improve failing test

* Move styles and module preload to `regionsToVdom`

* Add more comments

* Remove try..catch around renderRegions call

* Rename `prepareStyles` to `preloadStyles`

* Start replacing modules term with script modules

* Replace modules term with script modules in test files

* Remove unnecessary promise around lexer.init

* Remove old import fix from edge

See https://github.com/guybedford/es-module-shims/pull/450/files#diff-d9f5f5cf3b132cd3d457a02a9202af63e78eb6b8b3284097e5d1cfd28b181e8eR351.

* Remove unnecessary fetch pool

* Simplify error messages in `fetchModule`

* Do not add module type in `fetchModule`

* Remove unused code

* Catch `regionsToVdom` errors in `fetchPage`

* Remove support for `import.meta`

* Update package-lock

* Ensure CSN checkers are hidden by default

* Resolve inconsistencies and rename `markScriptModuleAsResolved`

* Reset clientSideNavigation prop when navigation starts

* Reapply "Remove the iAPI full-page experiment entirely (WordPress#70228)"

This reverts commit 9cfe7ec.

* WIP Move full-page logic to its own module

* Fix interactivity-router TS with sub-packages

* Needs to be composite project

* Add empty files for good measure

* Add package.json export

* Remove extra whitespace

* Remove exports property

* Add full-page client-side navigation support

* Import script modules before render

* Turn php logic into a class

* Change default mode priority to 9

* Fix init action in WP_Interactivity_API_Full_Page_Navigation

* Prevent warning accessing to clientNavigationMode

* Change body router region ID to core/body

* Remove unnecessary `body` prop inside `regions`

* Add a filter to enable/disable the experimental feature

* Use `wp_body_open` instead of `wp_footer`

* Move the experiment filter to `set_default_mode`

* Remove full-page unlock

* Only add the client navigation mode when full page is enabled

* Move Full Page class to experimental folder

* End buffer on `wp_footer` instead of `wp_body_open`

* Enable the full-page CSN with a gutenberg experiment

* Rename WP to Gutenberg in full-page CSN class

* Remove "since" tag

* Update changelog

* Rename regionsToVdom and renderRegions to preparePage and renderPage

* Make url param mandatori in preparePage

* Remove unnecessary full-page mode from JS

* Remove unnecessary full-page mode from PHP

* Mention original code creators

* Add descriptive names to props in ModuleLoad

* Fix wrong JSON

* Use Interactivity API instead of iAPI in experiment label

* Avoid error messages in tests

* Check the page title after navigation

* Prevent warning about missing disable_server_directive_processing

* Fix remaining ModuleLoad prop names

* Simplify resolve function and remove unnecessary code

---------

Co-authored-by: DAreRodz <darerodz@git.wordpress.org>
Co-authored-by: luisherranz <luisherranz@git.wordpress.org>
Co-authored-by: sirreal <jonsurrell@git.wordpress.org>
@sethrubenstein
Copy link
Contributor

Whew, busy month, sorry for taking so long to check this out. Updated to the latest Gutenberg locally last night to test this out heres my findings:

  1. No issues with anything PROPERLY registered and enqueued, yay! All our plugins are good to go 😄, some 3rd party plugins that dynamic style generation or manipulation can have issues. This was the first thing I noticed as our current cookie banner plugin broke wherever we're doing a router navigation. (to which I had Cursor whip together a barebones Cookie Banner plugin that just has our styles hard coded 😬).
  2. Unrelated to the new asset loading (so I can continue this conversation elsewhere) the new region render changes do seem to have broken some of our current implementations. I see the doc notes about attachTo, and I may need to try that out/dive into it more to resolve our issues. I need to investigate further but just noting that others may want to test their router-region implementations.

@luisherranz
Copy link
Member

some 3rd party plugins that dynamic style generation or manipulation can have issues

Exactly. Dynamic style injection or manipulation using JavaScript is not something we can support in client-side navigation, as there's no way to know if those styles should be preserved or removed on the new page.

the new region render changes do seem to have broken some of our current implementations

Please keep us updated on your findings and feel free to report early any issues you encounter so we can take a look at them together.

As always, thank you very much for testing the Interactivity API improvements, Seth 🙂🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Interactivity API API to add frontend interactivity to blocks. [Package] Interactivity Router /packages/interactivity-router [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants