React 19: upgrade standalone Calypso build - final bump#112095
Conversation
Move react and react-dom to 19.2.7 (with matching resolutions) and bump @wordpress/element to 8.0.0. @wordpress/element 8.0.0 still imports findDOMNode/render/hydrate/ unmountComponentAtNode from react-dom, all of which React 19 removed, so add a Yarn patch that reimplements render/hydrate/unmountComponentAtNode on top of createRoot/hydrateRoot and throws from findDOMNode. No published @wordpress/element (through 8.1.0) ships a React-19-compatible build yet; the patch can be dropped once one does. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the global JSX namespace shim (client/global-jsx.d.ts) and adjust client tsconfig, the shared TypeScript config package, and the apps Jest preset to compile cleanly against React 19's revised type definitions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve type_check_client failures under React 19's stricter typings: widen ref receivers, drop redundant JSDoc prop-type annotations and unused JSX type imports, make optional props optional, and update the theme test snapshot. Touches client/components, layout, my-sites, me, site-profiler, dashboard, reader, and the components / wpcom-template-parts packages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Jetpack Cloud Live (direct link)
Automattic for Agencies Live (direct link)
Dashboard Live (dotcom) (direct link)
|
| render, | ||
| unmountComponentAtNode | ||
| }; | ||
| -//# sourceMappingURL=react-platform.mjs.map |
There was a problem hiding this comment.
I don't love this file, but here's why this patch exists:
This repo is now pinned to React 19.2.7 (react + react-dom, including resolutions). @wordpress/element@8.0.0's react-platform module does named imports of findDOMNode, render, hydrate, and unmountComponentAtNode from react-dom — all of which React 19 removed (findDOMNode is gone; the other three moved to react-dom/client as createRoot/hydrateRoot).
Under React 19 those bindings resolve to undefined, and the ESM build's named imports trigger webpack export not found errors at build time.
The patch shims them: a WeakMap-backed render/hydrate/unmountComponentAtNode implemented on top of createRoot/hydrateRoot, plus a throwing findDOMNode. This keeps @wordpress/element's export surface intact for the many downstream consumers that import from it.
Why a Yarn patch (vs. alternatives)
- Bumping the dep doesn't help — 8.0.1 and 8.1.0 (latest) ship the identical broken react-platform.mjs. There is no fixed upstream version to move to yet.
webpack alias / NormalModuleReplacementPlugin would only fix webpack; the same broken import still has to resolve under Jest, node/ESM, Storybook, etc. The patch fixes it once at the source for every toolchain. - Aliasing react-dom to a shim is more invasive — it would pollute react-dom globally and could mask other React-19 migration issues we want to surface.
- The patch is declarative, version-pinned (package.json + yarn.lock), surgical (touches only the two react-platform build files), applied deterministically at install, and regenerable via yarn patch. It's the idiomatic Yarn Berry mechanism for exactly this situation.
When can we remove it?
As soon as WordPress ships a React-19-compatible @wordpress/element (i.e. a release whose react-platform no longer imports the removed APIs from react-dom). At that point: bump the dependency and delete this patch entry. Worth re-checking on each @wordpress/element upgrade.
|
|
||
| import './style.scss'; | ||
|
|
||
| /** |
There was a problem hiding this comment.
It looks like these JSDoc @param {{ ... }} props blocks weren't documentation in a .jsx file, they're the only mechanism to mark destructured props (className, id, status, context, …) as optional in react 19. The alternative would be moving them to a .d file, which doesn't seem better.
| "strict": true, | ||
| "checkJs": false, | ||
| "isolatedModules": true, | ||
| "skipLibCheck": true, |
There was a problem hiding this comment.
[follow-up] Issue: Adding skipLibCheck: true to the shared js-package.json config affects every package that extends it — it suppresses type checking on all .d.ts files project-wide, not just the React 19-related declaration-emit issues.
This could mask real type errors in package declarations or dependencies. The client/tsconfig.json changes in this PR (setting composite: false, declaration: false) already address the specific React 19 declaration-emit problem for the client. Is skipLibCheck also needed here, or is it papering over additional errors that should be fixed at the source?
Suggestion: If this is needed to unblock the migration, consider adding a comment noting it should be revisited/reverted once the React 19 migration completes — or scope it to a narrower config (e.g., only the configs that hit declaration-emit errors) rather than the shared base.
Source: https://github.com/Automattic/wp-calypso/blob/trunk/AGENTS.md
If it fails, fix the type errors at the source — do not silence them with
// @ts-expect-error,// @ts-ignore, oras anyunless you can justify it in the PR description.
There was a problem hiding this comment.
This is intentional. A few points:
-
It's a parity fix, not a new policy. The sibling base configs already skip lib checks:
ts-package.jsonhas"skipLibCheck": true, andmixed-package.json(whichclient/extends) inherits it.js-package.jsonwas the lone outlier, so.d.tschecking was already effectively off everywhere except the JS base. This just makes it consistent. -
skipLibCheckonly skips.d.tsfiles, not our own source. It doesn't suppress errors in any.ts/.tsxwe write — only declaration files (third-party@typesand the declarations emitted by project references). So it's narrower than// @ts-ignore/as any, which silence errors in source. -
The trigger is a generated/third-party declaration we don't own. Under the React 19 dependency bumps, the emitted
.d.tsfor@automattic/api-queriesreferences@tanstackDataTagsymbols that fail lib-check. There's no "source" on our side to fix — it would require upstream changes to the TanStack/api-queries type emit. The AGENTS.md line quoted targets@ts-ignore/as anyin our own code; this is a different situation.
Given the other base configs already enable it, scoping it per-package would actually increase the inconsistency rather than reduce it. I'm happy to add an inline comment next to the option noting the api-queries/DataTag trigger so the rationale is discoverable for future readers.
There was a problem hiding this comment.
I don't know the exact issues that you encountered, but it does seem like a worsening to enable skipLibCheck. In parallel it'd be good to drive whatever upstream improvements need to be done if that's where the issues lie, or perhaps patching them directly.
There was a problem hiding this comment.
With skipLibCheck off, type_check_packages produces 5 errors, all inside @wordpress/element's bundled .d.ts, so until it supports React 19, we'll have two options:
- Keep as is and remove once we can update
@wordpress/elementto React 19 - Remove it and add another patch to wordpress/element which I'm exploring here
The second option seems more contained, though it's another patch. How do you feel about it, @aduth?
|
Looks like one of the E2E tests has failed. You can fix them following these steps:
|
Replace the blanket YN0060/YN0002 log filters with message patterns targeting the i18n-calypso React peer mismatch and the missing @testing-library/dom peer from viewport-react and i18n-calypso. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each case renders the full checkout tree and drives it through many userEvent interactions, every one forcing a re-render. React 19's slower dev-mode renderer pushes the heaviest cases (VAT cases ~4s locally) past the previous 12s per-test cap on slower CI agents, where they time out. Raise the per-test timeout to 30s for headroom; the suite still passes locally with all 19 cases green. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…112133) Patch @wordpress/block-editor's iframe so its srcDoc keeps an empty <body> instead of self-removing it via document.currentScript.parentElement.remove(). Under React 19, <html>/<head>/<body> are host singletons that React *adopts* from the owner document rather than creating. With the iframe's placeholder body removed, resolveSingletonInstance found document.body null and threw "React expected a <body> element (document.body) to exist" every time a Gutenberg iframe mounted — e.g. the theme sheet style-variation previews on /theme/:slug. Keeping an empty <body> lets React 19 adopt it, apply the class/ref, and mount the portal children into it. The existing get-compatibility-styles export hunk is preserved in the same patch. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| }, | ||
| "react-native": "src/index", | ||
| "wpScript": true, | ||
| diff --git a/build-module/components/iframe/index.mjs b/build-module/components/iframe/index.mjs |
There was a problem hiding this comment.
This fix comes from this PR. We can only remove it after wordpress-block-editor has been migrated to be React 19 compatible.
| jest.mock( 'calypso/state/products-list/selectors/is-marketplace-product' ); | ||
| jest.mock( 'calypso/lib/navigate' ); | ||
|
|
||
| // These tests seem to be particularly slow (it might be because of using |
There was a problem hiding this comment.
This fix comes from this PR. No issues with code, turns out React 19 local dev tests are a big slower than React 18.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
I remember trying it last week, and it didn't work out, so I tried again today @taipeicoder, and unfortunately, this is circular:
The initial attempt on this PR had ~35 files and ~250 lines of code, which looked promising, but the linter failed, and to make them happy, we reduced it to only ~53 line changes, which still didn't make the unit tests pass. |
Ah, that's unfortunate. Thanks for checking. |
|
Running an AI adversarial review resulted in the following findings:
|
|
@copilot resolve the merge conflicts in this pull request and rebase trunk |
Thanks for this additional check @taipeicoder, it was super helpful! |
Rebased this locally onto current |
# Conflicts: # client/dashboard/components/responsive-menu/index.tsx
|
This is ready and will be deployed Monday Morning. Context: p1783039897732629/1782841690.373669-slack-C02DQP0FP |
…mattic#112095)" (Automattic#112336) This reverts commit cd149b8.
Fixes DOTCOM-17552
Proposed Changes
Upgrade the standalone Calypso build to React 19.2.7. This is a clean re-split of the long-running spike branch (#111325) into three reviewable commits, rebased onto current
trunk:@wordpress/element—react/react-dom→ 19.2.7 (with matchingresolutions),@wordpress/element→ 8.0.0, and a Yarn patch for@wordpress/element'sreact-platform.client/tsconfig.json, the shared TS config package, and the apps Jest preset.About the
@wordpress/elementpatchReact 19 removed
findDOMNodeand movedrender/hydrate/unmountComponentAtNodeout ofreact-dom, but@wordpress/element@8.0.0still imports all four by name fromreact-dom, so the named ESM imports fail to resolve at build time. The patch reimplementsrender/hydrate/unmountComponentAtNodeon top ofcreateRoot/hydrateRootand makesfindDOMNodethrow, keeping the package's export surface intact.A Yarn patch is the right mechanism here: no published
@wordpress/element(through 8.1.0) ships a React-19-compatible build, and the patch fixes the module once at the source for every toolchain (webpack, Jest, node), versus per-tool aliasing. It can be removed as soon as a React-19-compatible@wordpress/elementis released — bump the dependency and drop the patch entry.Why are these changes being made?
Part of the React 19 migration (DOTCOM-17400). This re-split replaces the spike branch's 23-commit history with a clean, reviewable sequence on top of current
trunk.Testing Instructions
Check that Yarn install works as expected and local calypso as well:
CI
type_check_clientshould pass.Build and smoke-test the standalone Calypso client; verify no React/TypeScript console errors.
Pre-merge Checklist
🤖 Generated with Claude Code