dev-utils,techdocs: add conditional support for React 18
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
In frontend builds and tests `process.env.HAS_REACT_DOM_CLIENT` will now be defined if `react-dom/client` is present, i.e. if using React 18. This allows for conditional imports of `react-dom/client`.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/dev-utils': patch
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Added support for React 18. The new `createRoot` API from `react-dom/client` will now be used if present.
|
||||
@@ -24,6 +24,13 @@ const envOptions = {
|
||||
oldTests: Boolean(process.env.BACKSTAGE_OLD_TESTS),
|
||||
};
|
||||
|
||||
try {
|
||||
require.resolve('react-dom/client');
|
||||
process.env.HAS_REACT_DOM_CLIENT = true;
|
||||
} catch {
|
||||
/* ignored */
|
||||
}
|
||||
|
||||
const transformIgnorePattern = [
|
||||
'@material-ui',
|
||||
'ajv',
|
||||
|
||||
@@ -81,6 +81,15 @@ async function readBuildInfo() {
|
||||
};
|
||||
}
|
||||
|
||||
function hasReactDomClient() {
|
||||
try {
|
||||
require.resolve('react-dom/client');
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function createConfig(
|
||||
paths: BundlingPaths,
|
||||
options: BundlingOptions,
|
||||
@@ -136,6 +145,9 @@ export async function createConfig(
|
||||
() => JSON.stringify(options.getFrontendAppConfigs()),
|
||||
true,
|
||||
),
|
||||
// This allows for conditional imports of react-dom/client, since there's no way
|
||||
// to check for presence of it in source code without module resolution errors.
|
||||
'process.env.HAS_REACT_DOM_CLIENT': JSON.stringify(hasReactDomClient()),
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -47,8 +47,8 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.13.1 || ^17.0.0",
|
||||
"react-dom": "^16.13.1 || ^17.0.0",
|
||||
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
"react-router-dom": "6.0.0-beta.0 || ^6.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -45,9 +45,19 @@ import {
|
||||
import { Box } from '@material-ui/core';
|
||||
import BookmarkIcon from '@material-ui/icons/Bookmark';
|
||||
import React, { ComponentType, ReactNode, PropsWithChildren } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoutesFromChildren, Route } from 'react-router-dom';
|
||||
import { SidebarThemeSwitcher } from './SidebarThemeSwitcher';
|
||||
import 'react-dom';
|
||||
|
||||
let ReactDOM:
|
||||
| typeof import('react-dom')
|
||||
// TODO: replace with import('react-dom/client') when repo is migrated to 18
|
||||
| { createRoot(el: HTMLElement): { render(el: JSX.Element): void } };
|
||||
if (process.env.HAS_REACT_DOM_CLIENT) {
|
||||
ReactDOM = require('react-dom/client');
|
||||
} else {
|
||||
ReactDOM = require('react-dom');
|
||||
}
|
||||
|
||||
export function isReactRouterBeta(): boolean {
|
||||
const [obj] = createRoutesFromChildren(<Route index element={<div />} />);
|
||||
@@ -235,7 +245,11 @@ export class DevAppBuilder {
|
||||
window.location.pathname = this.defaultPage;
|
||||
}
|
||||
|
||||
ReactDOM.render(<DevApp />, document.getElementById('root'));
|
||||
if ('createRoot' in ReactDOM) {
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(<DevApp />);
|
||||
} else {
|
||||
ReactDOM.render(<DevApp />, document.getElementById('root'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,8 +74,8 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.13.1 || ^17.0.0",
|
||||
"react-dom": "^16.13.1 || ^17.0.0",
|
||||
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
"react-router-dom": "6.0.0-beta.0 || ^6.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -21,8 +21,8 @@ import {
|
||||
} from '@backstage/integration';
|
||||
import FeedbackOutlinedIcon from '@material-ui/icons/FeedbackOutlined';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { renderReactElement } from './renderReactElement';
|
||||
|
||||
// requires repo
|
||||
export const addGitFeedbackLink = (
|
||||
@@ -75,7 +75,7 @@ export const addGitFeedbackLink = (
|
||||
default:
|
||||
return dom;
|
||||
}
|
||||
ReactDOM.render(React.createElement(FeedbackOutlinedIcon), feedbackLink);
|
||||
renderReactElement(React.createElement(FeedbackOutlinedIcon), feedbackLink);
|
||||
feedbackLink.style.paddingLeft = '5px';
|
||||
feedbackLink.title = 'Leave feedback for this page';
|
||||
feedbackLink.id = 'git-feedback-link';
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import type { Transformer } from './transformer';
|
||||
import MenuIcon from '@material-ui/icons/Menu';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { renderReactElement } from './renderReactElement';
|
||||
|
||||
export const addSidebarToggle = (): Transformer => {
|
||||
return dom => {
|
||||
@@ -33,7 +33,7 @@ export const addSidebarToggle = (): Transformer => {
|
||||
}
|
||||
|
||||
const toggleSidebar = mkdocsToggleSidebar.cloneNode() as HTMLLabelElement;
|
||||
ReactDOM.render(React.createElement(MenuIcon), toggleSidebar);
|
||||
renderReactElement(React.createElement(MenuIcon), toggleSidebar);
|
||||
toggleSidebar.id = 'toggle-sidebar';
|
||||
toggleSidebar.title = 'Toggle Sidebar';
|
||||
toggleSidebar.classList.add('md-content__button');
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { createTestShadowDom } from '../../test-utils';
|
||||
import { copyToClipboard } from './copyToClipboard';
|
||||
import { lightTheme } from '@backstage/theme';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import { act, waitFor } from '@testing-library/react';
|
||||
import useCopyToClipboard from 'react-use/lib/useCopyToClipboard';
|
||||
|
||||
const clipboardSpy = jest.fn();
|
||||
@@ -43,8 +43,11 @@ describe('copyToClipboard', () => {
|
||||
spy.mockReturnValue([{}, copy]);
|
||||
|
||||
const expectedClipboard = 'function foo() {return "bar";}';
|
||||
const shadowDom = await createTestShadowDom(
|
||||
`
|
||||
|
||||
let shadowDom: ShadowRoot;
|
||||
await act(async () => {
|
||||
shadowDom = await createTestShadowDom(
|
||||
`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
@@ -52,13 +55,20 @@ describe('copyToClipboard', () => {
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
{
|
||||
preTransformers: [],
|
||||
postTransformers: [copyToClipboard(lightTheme)],
|
||||
},
|
||||
);
|
||||
{
|
||||
preTransformers: [],
|
||||
postTransformers: [copyToClipboard(lightTheme)],
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
shadowDom.querySelector('button')?.click();
|
||||
await waitFor(() => {
|
||||
expect(shadowDom.querySelector('button')).not.toBe(null);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
shadowDom.querySelector('button')!.click();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const tooltip = document.querySelector('[role="tooltip"]');
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import ReactDom from 'react-dom';
|
||||
import { renderReactElement } from './renderReactElement';
|
||||
import {
|
||||
withStyles,
|
||||
Theme,
|
||||
@@ -91,7 +91,7 @@ export const copyToClipboard = (theme: Theme): Transformer => {
|
||||
const text = code.textContent || '';
|
||||
const container = document.createElement('div');
|
||||
code?.parentElement?.prepend(container);
|
||||
ReactDom.render(
|
||||
renderReactElement(
|
||||
<ThemeProvider theme={theme}>
|
||||
<CopyToClipboardButton text={text} />
|
||||
</ThemeProvider>,
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
let ReactDOM:
|
||||
| typeof import('react-dom')
|
||||
// TODO: replace with import('react-dom/client') when repo is migrated to 18
|
||||
| { createRoot(el: HTMLElement): { render(el: JSX.Element): void } };
|
||||
if (process.env.HAS_REACT_DOM_CLIENT) {
|
||||
ReactDOM = require('react-dom/client');
|
||||
} else {
|
||||
ReactDOM = require('react-dom');
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function renderReactElement(element: JSX.Element, root: HTMLElement) {
|
||||
if ('createRoot' in ReactDOM) {
|
||||
ReactDOM.createRoot(root).render(element);
|
||||
} else {
|
||||
ReactDOM.render(element, root);
|
||||
}
|
||||
}
|
||||
@@ -4267,8 +4267,8 @@ __metadata:
|
||||
react-use: ^17.2.4
|
||||
zen-observable: ^0.10.0
|
||||
peerDependencies:
|
||||
react: ^16.13.1 || ^17.0.0
|
||||
react-dom: ^16.13.1 || ^17.0.0
|
||||
react: ^16.13.1 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0
|
||||
react-router-dom: 6.0.0-beta.0 || ^6.3.0
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
@@ -9876,8 +9876,8 @@ __metadata:
|
||||
react-helmet: 6.1.0
|
||||
react-use: ^17.2.4
|
||||
peerDependencies:
|
||||
react: ^16.13.1 || ^17.0.0
|
||||
react-dom: ^16.13.1 || ^17.0.0
|
||||
react: ^16.13.1 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0
|
||||
react-router-dom: 6.0.0-beta.0 || ^6.3.0
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
Reference in New Issue
Block a user