close
Skip to content

Interactivity API: refactor directives into self-registering modules - #79975

Merged
DAreRodz merged 3 commits into
WordPress:trunkfrom
nickchomey:update/refactor-iapi-directives
Jul 16, 2026
Merged

Interactivity API: refactor directives into self-registering modules#79975
DAreRodz merged 3 commits into
WordPress:trunkfrom
nickchomey:update/refactor-iapi-directives

Conversation

@nickchomey

@nickchomey nickchomey commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

What

Closes #79977

Refactors the Interactivity API's directive registration system so each directive lives in its own file under src/directives/ and registers itself via a module-scoped directive() call.

Why

  • Maintainability: Each directive is a single, focused file instead of hundreds of lines in a monolithic directives.tsx.
  • Extensibility: Adding a new directive means creating one new file and adding one import — no need to touch the core registration function.
  • Custom bundles: By commenting out, or otherwise modifying, the imports in index.ts, you can create a minimal or custom build that only includes the directives you need.

Changes

Directives split into individual files

src/directives.tsx (1060 lines) was split into:

  • src/directives/context.tsdata-wp-context (priority 5)
  • src/directives/watch.tsdata-wp-watch
  • src/directives/init.tsdata-wp-init
  • src/directives/on.tsdata-wp-on, on-async, on-window, on-document (and deprecated async variants)
  • src/directives/class.tsdata-wp-class
  • src/directives/style.tsdata-wp-style
  • src/directives/bind.tsdata-wp-bind
  • src/directives/text.tsdata-wp-text
  • src/directives/run.tsdata-wp-run
  • src/directives/each.tsdata-wp-each (priority 20), data-wp-each-child (priority 1)
  • src/directives/ignore.tsdata-wp-ignore (deprecated)
  • src/directives/router-region.tsdata-wp-router-region (priority 1), also exports routerRegions

Each file calls directive() at module scope, so importing the file is enough to register it.

New files

| File | Purpose |
| src/directives/utils/warnings.ts | Shared warning helpers (warnUniqueIdWithTwoHyphens, warnUniqueIdNotSupported, warnWithSyncEvent) |
| src/directives/index.ts| clean entry point for the directives that can be overridden.

Comment out any directive import to exclude it from the build, replace them with local customizations, or add new ones.

Removed

  • src/directives.tsx — replaced by individual directive files + barrel imports in index.ts

Reviewer notes

This is a pure refactor. No directive behavior or registration order was changed — the same directive() calls happen in the same priority order as before. The only difference is they execute at module scope (during import resolution) rather than when the old registerDirectives() function was called. Since directive() only stores a callback in a map, the timing is irrelevant.

Use of AI Tools

AI assistance: Yes
Tool(s): VSCode Copilot Chat
Model(s): Deepseek V4 Flash
Used for: Pair programmer. I guided and reviewed it all

@github-actions github-actions Bot added [Package] Interactivity /packages/interactivity First-time Contributor Pull request opened by a first-time contributor to Gutenberg repository labels Jul 8, 2026
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

👋 Thanks for your first Pull Request and for helping build the future of Gutenberg and WordPress, @nickchomey! In case you missed it, we'd love to have you join us in our Slack community.

If you want to learn more about WordPress development in general, check out the Core Handbook full of helpful information.

@nickchomey
nickchomey force-pushed the update/refactor-iapi-directives branch 2 times, most recently from 77c3163 to 746faa0 Compare July 8, 2026 02:53
@nickchomey
nickchomey marked this pull request as ready for review July 8, 2026 09:16
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

Warning: Type of PR label mismatch

To merge this PR, it requires exactly 1 label indicating the type of PR. Other labels are optional and not being checked here.

  • Required label: Any label starting with [Type].
  • Labels found: First-time Contributor, [Package] Interactivity.

Read more about Type labels in Gutenberg. Don't worry if you don't have the required permissions to add labels; the PR reviewer should be able to help with the task.

1 similar comment
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

Warning: Type of PR label mismatch

To merge this PR, it requires exactly 1 label indicating the type of PR. Other labels are optional and not being checked here.

  • Required label: Any label starting with [Type].
  • Labels found: First-time Contributor, [Package] Interactivity.

Read more about Type labels in Gutenberg. Don't worry if you don't have the required permissions to add labels; the PR reviewer should be able to help with the task.

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: nickchomey <nickchomey@git.wordpress.org>
Co-authored-by: DAreRodz <darerodz@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@DAreRodz DAreRodz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hi, @nickchomey! 👋 I'm not opposed to moving each directive to its own file; that would help organizing the repo. However, I'd prefer if you modify less files.

You could create an index.ts file inside /directives, with it being in charge of importing all directives and exporting a function that does the same as the ' directives. tsx ' file currently does. That way we don't need to introduce changes in the runtime initialization.

Thanks! 🙂

@nickchomey
nickchomey force-pushed the update/refactor-iapi-directives branch 2 times, most recently from d19ed81 to 09f6874 Compare July 8, 2026 23:28
@nickchomey

Copy link
Copy Markdown
Contributor Author

Thanks for the review and sorry for the overreach! I knew that would be a problem, so should have just put those changes in a later commit to make it easier to revert.

Anyway, I believe it now looks how you requested. Please let me know if you'd like to see anything else change.

@DAreRodz DAreRodz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, @nickchomey! Now that the scope of the changes is narrow, I'm paying more attention to them. 🙂

I left some comments below. Just address them and the PR is ready to go.

Comment thread packages/interactivity/src/directives/utils/warnings.ts Outdated
Comment thread packages/interactivity/src/directives/bind.ts Outdated
}

// Preserve the initial inner HTML
const cached = useMemo( () => innerHTML, [ innerHTML ] );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Oops, it seems like this directive's behavior has changed. 👀

What this line should do is to preserve the initial value. If we introduce innerHTML as a useMemo's dependency, the cached value would be mistakenly updated.

It should be this instead. It could require adding // eslint-disable-next-line react-hooks/exhaustive-deps to prevent a linting error.

Suggested change
const cached = useMemo( () => innerHTML, [ innerHTML ] );
const cached = useMemo( () => innerHTML, [] );

@nickchomey nickchomey Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, i added that because of the linting error. Was silly to incorporate it as part of this PR, and especially without its own commit...

Should there be a test somewhere that would have caught any behavioural changes from that addition? Or perhaps now that it has the lint comment it isnt necessary. Plus it is already a deprecated directive...

Comment thread packages/interactivity/src/directives/on.ts
Comment thread packages/interactivity/src/directives/style.ts Outdated
@nickchomey
nickchomey force-pushed the update/refactor-iapi-directives branch 2 times, most recently from e09e05d to 7f7eee2 Compare July 13, 2026 18:05
@nickchomey

Copy link
Copy Markdown
Contributor Author

I think I've addressed it all now

@DAreRodz

Copy link
Copy Markdown
Contributor

@nickchomey, I wanted to make a small change before merging the PR, but I don't have permission to do so in your fork. Basically, the change removes the registerDirectives function.

Could you please apply this patch? Ping me, and I can merge the PR afterward. 🙂

diff --git a/packages/interactivity/src/directives/index.ts b/packages/interactivity/src/directives/index.ts
index dd8132eda00..b116dd580aa 100644
--- a/packages/interactivity/src/directives/index.ts
+++ b/packages/interactivity/src/directives/index.ts
@@ -2,11 +2,9 @@
  * Registers all core Interactivity API directives.
  *
  * Each directive file registers itself via `directive()` at module scope when
- * imported. This module aggregates all of them and exports a single
- * initialization function so that `index.ts` can call it uniformly.
+ * imported. This module aggregates all of them.
  */
 
-// Import each directive module so it self-registers.
 import './bind';
 import './class';
 import './context';
@@ -19,14 +17,3 @@ import './run';
 import './style';
 import './text';
 import './watch';
-
-// Re-export so the caller can reference the same singleton.
-export { routerRegions } from './router-region';
-
-/**
- * Initializes all core directives.
- *
- * Directives register themselves at import time, so this function is a no-op
- * that exists only to satisfy the calling convention in `index.ts`.
- */
-export default function registerDirectives(): void {}
diff --git a/packages/interactivity/src/index.ts b/packages/interactivity/src/index.ts
index b12cefefa20..24b63d9fa3d 100644
--- a/packages/interactivity/src/index.ts
+++ b/packages/interactivity/src/index.ts
@@ -11,7 +11,8 @@ import { batch, effect } from '@preact/signals';
 /**
  * Internal dependencies
  */
-import registerDirectives, { routerRegions } from './directives';
+import './directives'; // Registers all the core directives.
+import { routerRegions } from './directives/router-region';
 import {
 	initialVdomPromise,
 	hydrateRegions,
@@ -100,10 +101,8 @@ export const privateApis = (
 	throw new Error( 'Forbidden access.' );
 };
 
-// Parses and populates the initial state and config. All the core directives
-// are registered at this point as well.
+// Parses and populates the initial state and config.
 populateServerData( parseServerData() );
-registerDirectives();
 
 // Hydrates all interactive regions when `DOMContentLoaded` is dispatched, or as
 // soon as the `@wordpress/interactivity` module is evaluated in the case that

…strap.ts and index.ts becomes a clean entry point where you can specify which directives you want to use
Copilot AI review requested due to automatic review settings July 15, 2026 18:43
@nickchomey
nickchomey force-pushed the update/refactor-iapi-directives branch from 7f7eee2 to 99e7a39 Compare July 15, 2026 18:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Refactors the @wordpress/interactivity directive registration so each directive lives in its own module under src/directives/ and registers itself via a module-scoped directive() call, enabling a “directives entrypoint” that can be customized for bespoke builds.

Changes:

  • Replace the old registerDirectives() flow with a side-effect import of ./directives from src/index.ts.
  • Split the monolithic src/directives.tsx into per-directive modules under src/directives/.
  • Extract shared warning helpers into src/directives/utils/warnings.ts.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/interactivity/src/index.ts Switches directive registration to a side-effect import and removes the explicit registerDirectives() call; continues exposing routerRegions via privateApis.
packages/interactivity/src/directives/index.ts New directives “entrypoint” that imports (and thus registers) all core directives.
packages/interactivity/src/directives/utils/warnings.ts Centralizes shared warning helpers used by multiple directives.
packages/interactivity/src/directives/context.ts data-wp-context directive moved to a dedicated self-registering module.
packages/interactivity/src/directives/watch.ts data-wp-watch directive moved to a dedicated self-registering module.
packages/interactivity/src/directives/init.ts data-wp-init directive moved to a dedicated self-registering module.
packages/interactivity/src/directives/on.ts data-wp-on family of directives moved to a dedicated self-registering module.
packages/interactivity/src/directives/class.ts data-wp-class directive moved to a dedicated self-registering module.
packages/interactivity/src/directives/style.ts data-wp-style directive moved to a dedicated self-registering module.
packages/interactivity/src/directives/bind.ts data-wp-bind directive moved to a dedicated self-registering module.
packages/interactivity/src/directives/text.ts data-wp-text directive moved to a dedicated self-registering module.
packages/interactivity/src/directives/run.ts data-wp-run directive moved to a dedicated self-registering module.
packages/interactivity/src/directives/each.ts data-wp-each / data-wp-each-child directives moved to a dedicated self-registering module.
packages/interactivity/src/directives/ignore.ts data-wp-ignore (deprecated) directive moved to a dedicated self-registering module.
packages/interactivity/src/directives/router-region.ts data-wp-router-region directive + routerRegions map moved to a dedicated self-registering module.
packages/interactivity/src/directives.tsx Removed monolithic directives file in favor of per-directive modules.

Comment thread packages/interactivity/src/index.ts

@DAreRodz DAreRodz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, @nickchomey!

@DAreRodz
DAreRodz merged commit e42aaa2 into WordPress:trunk Jul 16, 2026
45 of 47 checks passed
@github-actions github-actions Bot added this to the Gutenberg 23.7 milestone Jul 16, 2026
@nickchomey
nickchomey deleted the update/refactor-iapi-directives branch July 16, 2026 15:25
@jonathanbossenger jonathanbossenger added the [Type] Code Quality Issues or PRs that relate to code quality label Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

First-time Contributor Pull request opened by a first-time contributor to Gutenberg repository [Package] Interactivity /packages/interactivity [Type] Code Quality Issues or PRs that relate to code quality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor Interactivity API package to allow for custom builds

4 participants