Skip to content

Conversation

DAreRodz
Copy link
Contributor

@DAreRodz DAreRodz commented Aug 7, 2025

What?

Closes #70870.

Revisits the initialization logic to clarify all the steps involved and at what moment they run.

For now, this PR serves more as an exploration to discuss possible implementations.

Why?

See #70870.

How?

Basically, the code changes are as follows:

  1. Initialization has been moved to index.ts. There is no longer any initialization of isolated parts in internal modules, as was the case with the initialization of stores.
  2. The init.ts file has been renamed hydration.ts, as it only involves the hydration part. The init() function is now called hydrateRegions().
  3. The postTask utility has been implemented to replace the use of setTimeout when starting hydration. It uses scheduler.postTask or a tiny fallback using MessageChannel.

Testing Instructions

TBD

@DAreRodz DAreRodz added [Type] Enhancement A suggestion for improvement. [Feature] Interactivity API API to add frontend interactivity to blocks. [Packages] Interactivity /packages/interactivity [Package] Interactivity Router /packages/interactivity-router labels Aug 7, 2025
init();

// Defer hydration to the end of the task queue.
postTask( hydrateRegions );
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is mostly the same as before, with the setTimeout function replaced by a new postTask function, which adds the hydrateRegions function to the end of the event loop.

Effectively, the result would be the same, as setTimeout wouldn't be affected by any of the limitations that could potentially delay the callback execution.

Comment on lines +73 to +78
const taskQueue: ( () => any )[] = [];
const taskChannel = new MessageChannel();
// Drain one task per message
taskChannel.port1.onmessage = () => {
taskQueue.shift()?.();
};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm reusing these for a scheduler fallback. Not entirely confident this is the most suitable approach, though. 🤔

Using MessageChannel could be considered more reliable than setTimeout() to schedule tasks (the scheduler polyfill from GoogleChromeLabs uses it when available), I feel that none of the delay issues setTimeout() has would apply here, at least for the initialization case.

@DAreRodz
Copy link
Contributor Author

DAreRodz commented Aug 7, 2025

I've been testing different implementations here for postTask. They all do the job, although the results are inconsistent between browsers.

As an example, these are the logs obtained for the MessageChannel implementation in Chrome, Firefox, and Safari.

| Chrome                     | Firefox                    | Safari                     |
| -------------------------- | -------------------------- | -------------------------- |
| scheduler                  | scheduler                  | scheduler                  |
| test1                      | test1                      | DOMContentLoaded scheduler |
| test2                      | test2                      | test1                      |
| test3                      | test3                      | test2                      |
| DOMContentLoaded scheduler | postTask scheduler         | test3                      |
| postTask scheduler         | DOMContentLoaded scheduler | postTask scheduler         |
| postTask test1             | postTask test1             | postTask test1             |
| postTask test2             | postTask test2             | postTask test2             |
| postTask test3             | postTask test3             | postTask test3             |
| after await test1          | after await test1          | after await test1          |
| after await test2          | after await test2          | after await test2          |
| after await test3          | after await test3          | after await test3          |

And these are for the implementation using setTimeout:

| Chrome                     | Firefox                    | Safari                     |
| -------------------------- | -------------------------- | -------------------------- |
| scheduler                  | scheduler                  | scheduler                  |
| test1                      | test1                      | test1                      |
| test2                      | test2                      | DOMContentLoaded scheduler |
| test3                      | test3                      | test2                      |
| DOMContentLoaded scheduler | DOMContentLoaded scheduler | test3                      |
| postTask scheduler         | postTask scheduler         | postTask scheduler         |
| postTask test1             | postTask test1             | postTask test1             |
| postTask test2             | postTask test2             | postTask test2             |
| postTask test3             | postTask test3             | postTask test3             |
| after await test1          | after await test1          | after await test1          |
| after await test2          | after await test2          | after await test2          |
| after await test3          | after await test3          | after await test3          |

One interesting conclusion from these results is that, in Safari, the DOMContentLoaded event seems to be dispatched even before all the modules have been executed, so it is, in fact, not reliable to initialize the interactivity API.

@DAreRodz
Copy link
Contributor Author

DAreRodz commented Aug 7, 2025

Another interesting finding is that we should avoid using top-level awaits in our code.

When a module statically imports another module using a top-level await, the module is effectively awaited as if it were imported dynamically, delaying the module execution. This is how modules work natively.

Let's consider the init code that delays the hydration. Imagine we await postTask:

// Defer hydration to the end of the task queue.
await postTask( hydrateRegions );

All view.js files statically importing @wordpress/interactivity would stop until the hydration is complete, and all store() calls would run after hydration.

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 [Packages] Interactivity /packages/interactivity [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

iAPI: Review the initialization logic
1 participant