Skip to content

Add Inlined React Runtime check for React 19 incompatibilities#1380

Open
gunjanjaswal wants to merge 4 commits into
WordPress:trunkfrom
gunjanjaswal:add/inlined-react-runtime-check
Open

Add Inlined React Runtime check for React 19 incompatibilities#1380
gunjanjaswal wants to merge 4 commits into
WordPress:trunkfrom
gunjanjaswal:add/inlined-react-runtime-check

Conversation

@gunjanjaswal

@gunjanjaswal gunjanjaswal commented Jun 29, 2026

Copy link
Copy Markdown

Closes #1356.

This adds a check (inlined_react_runtime) for plugins that bundle their own copy of the React JSX runtime. WordPress is going to React 19 at some point, and the usual reason a plugin breaks across that jump isn't some exotic API, it's that the build inlined react/jsx-runtime into the bundle instead of loading it from WordPress. React 19 changed the shape of the element object, so anything an old bundled runtime creates gets rejected. The plugin runs fine today and then falls over the day WordPress updates, so the point is to catch it ahead of time.

The detection is pretty crude, honestly: it greps the plugin's JS for Symbol.for('react.element'), which only shows up when a pre-19 runtime got bundled in. React 19 switched that marker to react.transitional.element, so anyone externalizing the runtime properly won't trip it. And if the runtime is externalized there's nothing to warn about, so it stays quiet when it spots window.ReactJSXRuntime in the file or react-jsx-runtime listed in the matching *.asset.php. On top of that it flags a few APIs that got dropped in 19 (unmountComponentAtNode, findDOMNode, ReactCurrentOwner). Everything's a warning, nothing errors out. It's wired into Default_Check_Repository with the usual docs and changelog entries.

Inlined_React_Runtime_Check_Tests covers both sides: the -with-errors fixture throws two warnings (inlined_jsx_runtime on index.js, react_removed_api on legacy.js), and the -without-errors one stays clean because the runtime's externalized there, once through index.asset.php and once through window.ReactJSXRuntime. I'm not married to the category or severity if you'd rather scope it differently.

Open WordPress Playground Preview

AI Usage Disclosure

  • This PR includes AI-assisted code or content

AI assistance (Claude Code) was used to help draft the check and its tests. I reviewed and understand every line and take responsibility for it.

Adds a static check that scans a plugin's JavaScript files for a bundled,
outdated React runtime that breaks once WordPress upgrades to React 19.

The primary, high-confidence signal is `Symbol.for( 'react.element' )`, which
is only emitted by an inlined pre-React 19 JSX runtime (React 19 uses the
`react.transitional.element` marker). The warning is suppressed when the
runtime is externalized, detected via a `window.ReactJSXRuntime` reference or a
`react-jsx-runtime` dependency in the sibling `*.asset.php` file. Usage of React
APIs removed in React 19 (unmountComponentAtNode, findDOMNode, ReactCurrentOwner)
is reported as a secondary signal.

Registers the check, adds PHPUnit tests with passing/failing fixtures, and
documents it in docs/checks.md and the changelog.

Fixes WordPress#1356
@github-actions

github-actions Bot commented Jun 29, 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: gunjanjaswal <gunjanjaswal@git.wordpress.org>
Co-authored-by: davidperezgar <davidperez@git.wordpress.org>
Co-authored-by: jonathanbossenger <psykro@git.wordpress.org>

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

PHPMD flagged $matched as an undefined variable in look_for_removed_react_apis
since it was only created via the by-reference argument. Initialize it first.
…runtime-check

# Conflicts:
#	docs/checks.md
#	includes/Checker/Default_Check_Repository.php
@davidperezgar

Copy link
Copy Markdown
Member

Hello, I've got these findings from Codex. Could you check it?

Findings
[P1] Asset dependency suppresses a real inlined-runtime signal
Inlined_React_Runtime_Check.php (line 167) suppresses Symbol.for( 'react.element' ) whenever the sibling asset file contains react-jsx-runtime. But the marker is in the JS file being scanned; an asset dependency only proves the file declares an external dependency, not that it did not also inline a pre-19 runtime. This can miss the exact problem the check is meant to catch, especially with stale/manual asset files or mixed builds. I’d only use react-jsx-runtime in the asset file as supporting context, not as a bypass once the marker is present.

[P2] Removed API regex reports comments/strings as usage
Inlined_React_Runtime_Check.php (line 125) scans raw JS for unmountComponentAtNode, findDOMNode, and ReactCurrentOwner, so a comment, changelog string, translation string, or compatibility message will produce a warning. Since this is registered as a stable default check, that could create noisy false positives. Consider limiting this to property/member access patterns or parsing tokens enough to skip comments and string literals.

Address the review feedback on the React 19 runtime check:

- Stop treating a react-jsx-runtime dependency in the sibling .asset.php
  file as proof the runtime is externalized. The Symbol.for( 'react.element' )
  marker means the file already inlines a pre-19 runtime, so a declared
  dependency can hide a stale or mixed build. Only an in-file
  window.ReactJSXRuntime reference now suppresses the warning.

- Ignore the removed-API identifiers when they appear only in comments or
  string literals, so changelog notes and translation strings no longer
  produce false positives.

Update the fixtures and tests to cover both cases.
@gunjanjaswal

Copy link
Copy Markdown
Author

Thanks @davidperezgar, both are fair points. I pushed a fix for each.

P1 (asset dependency bypass): You're right that the .asset.php dependency only tells us the file declares react-jsx-runtime, not that it avoided inlining a pre-19 runtime. Since Symbol.for( 'react.element' ) is already a definitive inline marker, trusting the asset file there can hide the exact case the check is meant to catch. I dropped the asset file as a bypass, so now only an in-file window.ReactJSXRuntime reference suppresses the warning. I also added a fixture (asset-declared.js with its .asset.php) that inlines the runtime while declaring the dependency, and it's now flagged correctly.

P2 (comments and strings): Also right. The raw scan would flag unmountComponentAtNode, findDOMNode, or ReactCurrentOwner even inside a comment, changelog line, or translation string. It now blanks out comments and string literals before scanning, keeping the length and newlines intact so the reported line and column stay accurate, and only real usages get flagged. I added a comment-only.js fixture that mentions all three in prose and stays clean.

Happy to adjust either if you'd prefer a different approach.

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.

Enhancement: Add a check to warn about React 19 incompatibilities / bundled outdated React

2 participants