Remove dead React 17 compatibility (#81080)

Remove dead React 17 compat
This commit is contained in:
Andrei Chernykh
2026-08-26 12:21:23 -07:00
committed by GitHub
parent 55d7df2c9c
commit bceebfaee4
16 changed files with 15 additions and 154 deletions

View File

@@ -161,7 +161,6 @@
"typescript7": "npm:typescript@7.0.2",
"underscore": "^1.13.8",
"url": "^0.11.4",
"use-sync-external-store": "^1.6.0",
"yup": "^0.32.11",
},
"devDependencies": {
@@ -251,7 +250,6 @@
"@types/semver": "^7.7.1",
"@types/sinonjs__fake-timers": "^15.0.1",
"@types/underscore": "^1.13.0",
"@types/use-sync-external-store": "^1.5.0",
"@vitejs/plugin-react": "^6.0.2",
"ajv": "^8.5.0",
"babel-loader": "^9.1.2",
@@ -1986,7 +1984,7 @@
"@types/unist": ["@types/unist@2.0.5", "", {}, "sha512-wnra4Vw9dopnuybR6HBywJ/URYpYrKLoepBTEtgfJup8Ahoi2zJECPP2cwiXp7btTvOT2CULv87aQRA4eZSP6g=="],
"@types/use-sync-external-store": ["@types/use-sync-external-store@1.5.0", "", {}, "sha512-5dyB8nLC/qogMrlCizZnYWQTA4lnb/v+It+sqNl5YnSRAPMlIqY/X0Xn+gZw8vOL+TgTTr28VEbn3uf8fUtAkw=="],
"@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.6", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="],
"@types/uuid": ["@types/uuid@9.0.8", "", {}, "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA=="],
@@ -5382,7 +5380,7 @@
"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" } }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
"use-sync-external-store": ["use-sync-external-store@1.5.0", "", {}, "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A=="],
"util": ["util@0.12.5", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="],
@@ -5866,10 +5864,6 @@
"@tiptap/extension-bubble-menu/@floating-ui/dom": ["@floating-ui/dom@1.6.12", "", { "dependencies": { "@floating-ui/core": "^1.6.0", "@floating-ui/utils": "^0.2.8" } }, "sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w=="],
"@tiptap/react/@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.6", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="],
"@tiptap/react/use-sync-external-store": ["use-sync-external-store@1.5.0", "", {}, "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A=="],
"@tiptap/starter-kit/@tiptap/extension-blockquote": ["@tiptap/extension-blockquote@3.2.0", "", {}, "sha512-ivOvkzkpj/+1i6cZwUr1M+T/knci2XMl8NW/Q6g70Kx2TinEoFjOGnDi34j1mVznyzhxVlY9jm3Xvv4s2+Ni/w=="],
"@tiptap/starter-kit/@tiptap/extension-bold": ["@tiptap/extension-bold@3.2.0", "", {}, "sha512-YuwK/muBzc0r7VFV0CcnF5Otg90zfUW2D5x+KpSF0e9bUBltSvitHgRaKWFwbPAgXYwkh6+ftxEazbXeqyTv8Q=="],
@@ -6642,6 +6636,8 @@
"miller-rabin/bn.js": ["bn.js@4.11.9", "", {}, "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="],
"mobx-react-lite/use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
"mocha/chokidar": ["chokidar@3.5.3", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw=="],
"mocha/glob": ["glob@8.1.0", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^5.0.1", "once": "^1.3.0" } }, "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ=="],
@@ -7212,8 +7208,6 @@
"zrender/tslib": ["tslib@2.3.0", "", {}, "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="],
"zustand/use-sync-external-store": ["use-sync-external-store@1.5.0", "", {}, "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A=="],
"@babel/generator/@jridgewell/trace-mapping/@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@babel/generator/@jridgewell/trace-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.4.15", "", {}, "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="],

View File

@@ -76,7 +76,6 @@ const RESOLVE_ALIASES = {
process.env.MB_EDITION === "ee"
? ENTERPRISE_SRC_PATH + "/sdk-plugins"
: SRC_PATH + "/plugins/noop",
"sdk-specific-imports": SRC_PATH + "/utils/noop",
};
module.exports = { RESOLVE_ALIASES };

View File

@@ -80,9 +80,7 @@
"frontend/src/embedding-sdk-bundle/components/private/SdkBreadcrumbs/setup.spec.tsx": "self",
"frontend/src/embedding-sdk-bundle/embedding-sdk-bootstrap.ts": "entry",
"frontend/src/embedding-sdk-bundle/index.ts": "entry",
"frontend/src/embedding-sdk-bundle/lib/polyfill/use-sync-external-store.ts": "global",
"frontend/src/embedding-sdk-bundle/lib/sdk-public-path.ts": "global",
"frontend/src/embedding-sdk-bundle/lib/sdk-specific-imports.ts": "entry",
"frontend/src/embedding-sdk-bundle/store/index.ts": "self",
"frontend/src/embedding-sdk-bundle/store/listener-middleware.ts": "self",
"frontend/src/embedding-sdk-bundle/store/use-sdk-selector.ts": "self",
@@ -201,7 +199,6 @@
"frontend/src/metabase/utils/csp-setup.ts": "global",
"frontend/src/metabase/utils/date-time.ts": "self",
"frontend/src/metabase/utils/i18n.ts": "global",
"frontend/src/metabase/utils/react-compat.unit.ts": "self",
"frontend/src/metabase/utils/scripts-sandbox/distortions-blocked-apis.ts": "self",
"frontend/src/metabase/utils/scripts-sandbox/distortions-dom-mutate.ts": "self",
"frontend/src/metabase/utils/settings.ts": "self",

View File

@@ -18,9 +18,6 @@ import { initializePlugins } from "sdk-ee-plugins";
initializePlugins();
// Imports which are only applicable to the embedding sdk, and not the new iframe embedding.
import "sdk-specific-imports";
import { sdkBundleExports } from "./sdk-bundle-exports";
import { defineBuildInfo } from "metabase/embedding-sdk/lib/define-build-info";

View File

@@ -1,11 +0,0 @@
import React from "react";
import { useSyncExternalStore } from "use-sync-external-store/shim";
// Monkey-patches useSyncExternalStore if we are in React 17,
// where useSyncExternalStore is not available.
// This is used in react-redux v8, until they've dropped the shim in v9.
const shouldShimExternalStore = () => !("useSyncExternalStore" in React);
if (shouldShimExternalStore()) {
React.useSyncExternalStore = useSyncExternalStore;
}

View File

@@ -6,8 +6,7 @@
*
* Without this, an on-demand chunk would be requested relative to the host
* page's base URL (where the SDK chunks don't exist) and fail with a
* `ChunkLoadError`. This is why those dependencies used to be force-imported
* eagerly in `./sdk-specific-imports`.
* `ChunkLoadError`.
*
* Both the chunked bootstrap and the legacy monolithic bundle are served from
* `app/embedding-sdk.js`, with their split/on-demand chunks living one level

View File

@@ -1,18 +0,0 @@
/**
* The imports here are only called from the Embedding SDK.
* This file is not imported from the main app, including the new iframe embedding.
*
* This is aliased as `sdk-specific-imports` in the SDK's webpack config.
*/
// Polyfills useSyncExternalStore for React 17 for backwards compatibility.
import "./polyfill/use-sync-external-store";
/**
* NOTE: heavy dependencies that are lazily imported in the main codebase
* (`jspdf`, `html2canvas-pro` for PDF / image export; `react-virtualized` via
* the pivot table grid; `leaflet` via the map renderer) are intentionally NOT
* eagerly imported here. They are loaded on demand via dynamic `import()`, so
* they no longer weigh down the SDK's critical-path bundle. On-demand chunks
* resolve against the runtime `publicPath` set in `./sdk-public-path`.
*/

View File

@@ -4,13 +4,12 @@ import {
flexRender,
} from "@tanstack/react-table";
import React, { useCallback } from "react";
import type { Root } from "react-dom/client";
import { type Root, createRoot } from "react-dom/client";
import type { ColumnOptions, DataGridTheme } from "metabase/data-grid/types";
import { pickRowsToMeasure } from "metabase/data-grid/utils/column-sizing";
import { MeasurementProviders } from "metabase/ui/components/theme/MeasurementProviders";
import { createMeasurementContainer } from "metabase/utils/measure-container";
import { renderRoot, unmountRoot } from "metabase/utils/react-compat";
import { isNotNull } from "metabase/utils/types";
import { DataGridThemeProvider } from "./use-table-theme";
@@ -90,7 +89,7 @@ export const useMeasureColumnWidths = <TData, TValue>(
onMeasured(measuredColumnSizingMap);
setTimeout(() => {
unmountRoot(measureRootTree, measureRoot);
measureRootTree?.unmount();
document.body.removeChild(measureRoot);
}, 0);
};
@@ -167,7 +166,8 @@ export const useMeasureColumnWidths = <TData, TValue>(
? measurementRenderWrapper(wrappedContent)
: wrappedContent;
measureRootTree = renderRoot(content, measureRoot);
measureRootTree = createRoot(measureRoot);
measureRootTree.render(content);
},
[table, columnsOptions, theme, measurementRenderWrapper],
);

View File

@@ -1,6 +1,6 @@
import { type Row, type Table, flexRender } from "@tanstack/react-table";
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from "react";
import type { Root } from "react-dom/client";
import { type Root, createRoot } from "react-dom/client";
import { SortableHeaderPill } from "metabase/ui";
import { MeasurementProviders } from "metabase/ui/components/theme/MeasurementProviders";
@@ -8,7 +8,6 @@ import {
createMeasurementContainer,
removeMeasurementContainer,
} from "metabase/utils/measure-container";
import { renderRoot } from "metabase/utils/react-compat";
import {
CELL_HORIZONTAL_PADDING,
@@ -383,7 +382,7 @@ export function useColumnSizing<TData extends TreeNodeData>({
const [isMeasured, setIsMeasured] = useState(false);
const measureRootRef = useRef<{
element: HTMLDivElement;
tree: Root | undefined;
tree: Root;
} | null>(null);
const allRows = table.getRowModel().flatRows;
@@ -460,7 +459,8 @@ export function useColumnSizing<TData extends TreeNodeData>({
</MeasurementProviders>
);
const tree = renderRoot(content, measureElement);
const tree = createRoot(measureElement);
tree.render(content);
measureRootRef.current = { element: measureElement, tree };
}, [
measurementColumnsKey,

View File

@@ -1,7 +1,5 @@
import type { Root } from "react-dom/client";
import { unmountRoot } from "metabase/utils/react-compat";
interface MeasurementContainerOptions {
fontSize?: string;
}
@@ -31,10 +29,10 @@ export function createMeasurementContainer(
export function removeMeasurementContainer(
container: HTMLDivElement,
tree: Root | undefined,
tree: Root,
): void {
setTimeout(() => {
unmountRoot(tree, container);
tree.unmount();
document.body.removeChild(container);
}, 0);
}

View File

@@ -1,42 +0,0 @@
// Support React 17 backwards compatibility for the Embedding SDK
import React from "react";
import ReactDOM from "react-dom";
import { type Root, createRoot } from "react-dom/client";
export function renderRoot(
content: React.JSX.Element,
element: Element,
): Root | undefined {
const reactVersion = getMajorReactVersion();
if (reactVersion <= 17) {
// eslint-disable-next-line react/no-deprecated -- legacy usage
ReactDOM.render(content, element);
return;
}
const root = createRoot(element);
root.render(content);
return root;
}
export function unmountRoot(root?: Root, element?: Element) {
const reactVersion = getMajorReactVersion();
if (reactVersion <= 17 && element) {
// eslint-disable-next-line react/no-deprecated -- legacy usage
ReactDOM.unmountComponentAtNode(element);
return;
}
if (root) {
root.unmount();
}
}
export const getMajorReactVersion = () => {
const versionParts = React.version.split(".").map(Number);
return versionParts[0];
};

View File

@@ -1,41 +0,0 @@
import React from "react";
import { getMajorReactVersion } from "./react-compat";
describe("getMajorReactVersion", () => {
const versions = [
{ version: "0.14.0", expected: 0 },
{ version: "15.0.0", expected: 15 },
{ version: "16.0.0", expected: 16 },
{ version: "16.8.0", expected: 16 },
{ version: "17.0.0", expected: 17 },
{ version: "17.0.2", expected: 17 },
{ version: "18.0.0", expected: 18 },
{ version: "18.1.0", expected: 18 },
];
beforeEach(() => {
jest.resetModules();
});
it.each(versions)(
"should return $expected for React version $version",
({ version, expected }) => {
Object.defineProperty(React, "version", {
value: version,
writable: true,
});
expect(getMajorReactVersion()).toBe(expected);
},
);
it("should return NaN for an invalid version string", () => {
Object.defineProperty(React, "version", {
value: "invalid-version",
writable: true,
});
expect(getMajorReactVersion()).toBeNaN();
});
});

View File

@@ -69,11 +69,6 @@ const baseConfig = {
"<rootDir>/frontend/src/metabase/utils/noop.ts",
"ee-plugins": "<rootDir>/frontend/src/metabase/utils/noop.ts",
"ee-overrides": "<rootDir>/frontend/src/metabase/utils/noop.ts",
/**
* Imports which are only applicable to the embedding sdk.
* As we use SDK components in new iframe embedding, we need to import them here.
**/
"sdk-specific-imports": "<rootDir>/frontend/src/metabase/utils/noop.ts",
/**
* Docs snippets are loaded as raw text (asset/source) in rspack.
* In Jest, mock them as plain strings.

View File

@@ -170,7 +170,6 @@
"typescript7": "npm:typescript@7.0.2",
"underscore": "^1.13.8",
"url": "^0.11.4",
"use-sync-external-store": "^1.6.0",
"yup": "^0.32.11"
},
"devDependencies": {
@@ -260,7 +259,6 @@
"@types/semver": "^7.7.1",
"@types/sinonjs__fake-timers": "^15.0.1",
"@types/underscore": "^1.13.0",
"@types/use-sync-external-store": "^1.5.0",
"@vitejs/plugin-react": "^6.0.2",
"ajv": "^8.5.0",
"babel-loader": "^9.1.2",

View File

@@ -432,9 +432,6 @@ config.resolve.alias = {
"sdk-iframe-embedding-ee-plugins":
ENTERPRISE_SRC_PATH + "/sdk-iframe-embedding-plugins",
"ee-overrides": ENTERPRISE_SRC_PATH + "/overrides",
// Allows importing side effects that applies only to the SDK.
"sdk-specific-imports": SDK_BUNDLE_SRC_PATH + "/lib/sdk-specific-imports.ts",
};
if (config.cache) {

View File

@@ -17,7 +17,6 @@
"zrender/lib/core/types": ["./node_modules/zrender/lib/core/types"],
"ee-plugins": ["./frontend/src/metabase/plugins/noop"],
"sdk-ee-plugins": ["./frontend/src/metabase/plugins/noop"],
"sdk-specific-imports": ["./frontend/src/metabase/utils/noop"],
"sdk-iframe-embedding-ee-plugins": [
"./frontend/src/metabase/plugins/noop"
],