diff --git a/packages/kitchen-sink/src/main.tsx b/packages/kitchen-sink/src/main.tsx index 64b212f52b..bf388189a3 100644 --- a/packages/kitchen-sink/src/main.tsx +++ b/packages/kitchen-sink/src/main.tsx @@ -1,11 +1,19 @@ -import { StrictMode, useState, type ComponentType } from 'react'; -import { createRoot } from 'react-dom/client'; +import { version, StrictMode, useState, type ComponentType } from 'react'; import './style.css'; import { ErrorBoundary } from 'react-error-boundary'; type Module = Record; (async () => { + let createRootElement; + if (version.startsWith('18')) { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { createRoot } = await import('react-dom/client'); + createRootElement = createRoot; + } else { + const ReactDOM = await import('react-dom'); + createRootElement = ReactDOM.render; + } const modules = await Promise.all( Object.entries(import.meta.glob('./examples/*.tsx')).map( async ([key, mod]) => @@ -85,9 +93,16 @@ type Module = Record; ); } - createRoot(document.getElementById('root')!).render( - - - , - ); + version.startsWith('18') + ? createRootElement(document.getElementById('root')!).render( + + + , + ) + : createRootElement( + + + , + document.getElementById('root')!, + ); })(); diff --git a/packages/react/utils.ts b/packages/react/utils.ts index b9b2d7f51e..a593b20208 100644 --- a/packages/react/utils.ts +++ b/packages/react/utils.ts @@ -1,8 +1,6 @@ -import { Fragment, createElement, isValidElement } from 'react'; -import { createRoot } from 'react-dom/client'; +import { Fragment, createElement, isValidElement, version } from 'react'; import { REACT_ROOT, REGISTRY, RENDER_SCOPE } from './constants'; import type { ComponentProps, ReactNode, Ref } from 'react'; -import type { Root } from 'react-dom/client'; import type { VNode } from '../million'; // TODO: access perf impact of this @@ -45,12 +43,25 @@ export const renderReactScope = (vnode: ReactNode, unstable?: boolean) => { } const scope = (el: HTMLElement | null) => { + let root; const parent = el ?? document.createElement(RENDER_SCOPE); - const root = - REACT_ROOT in parent - ? (parent[REACT_ROOT] as Root) - : (parent[REACT_ROOT] = createRoot(parent)); - root.render(vnode); + if (version.startsWith('18')) { + import('react-dom/client') + .then((res) => { + root = + REACT_ROOT in parent + ? parent[REACT_ROOT] + : (parent[REACT_ROOT] = res.createRoot(parent)); + root.render(vnode); + }) + .catch((e) => { + // eslint-disable-next-line no-console + console.error(e); + }); + } else { + root = parent[REACT_ROOT]; + root.render(vnode); + } return parent; }; diff --git a/website/components/million-library/react.cjs b/website/components/million-library/react.cjs index b7ef7a68dd..7557f7a9f1 100644 --- a/website/components/million-library/react.cjs +++ b/website/components/million-library/react.cjs @@ -5,21 +5,30 @@ Object.defineProperty(exports, '__esModule', { value: true }); const react = require('react'); const block$1 = require('./chunks/block.cjs'); const constants = require('./chunks/constants.cjs'); -const client = require('react-dom/client'); -const REACT_ROOT = "__react_root"; -const RENDER_SCOPE = "million-render-scope"; +const REACT_ROOT = '__react_root'; +const RENDER_SCOPE = 'million-render-scope'; const renderReactScope = (jsx) => { return (el) => { + let root; const parent = el ?? block$1.document$.createElement(RENDER_SCOPE); - const root = REACT_ROOT in parent ? parent[REACT_ROOT] : parent[REACT_ROOT] = client.createRoot(parent); + if (react.version.startsWith('18')) { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { createRoot } = require('react-dom/client'); + root = + REACT_ROOT in parent + ? parent[REACT_ROOT] + : (parent[REACT_ROOT] = createRoot(parent)); + } else { + root = parent[REACT_ROOT]; + } root.render(jsx); return parent; }; }; const unwrap = (vnode) => { - if (typeof vnode !== "object" || vnode === null || !("type" in vnode)) { - if (typeof vnode === "number" || vnode === true) { + if (typeof vnode !== 'object' || vnode === null || !('type' in vnode)) { + if (typeof vnode === 'number' || vnode === true) { return String(vnode); } else if (!vnode) { return void 0; @@ -27,30 +36,35 @@ const unwrap = (vnode) => { return vnode; } const type = vnode.type; - if (typeof type === "function") { + if (typeof type === 'function') { return unwrap(type(vnode.props ?? {})); } - if (typeof type === "object" && "$" in type) - return type; + if (typeof type === 'object' && '$' in type) return type; const props = { ...vnode.props }; const children = vnode.props?.children; if (children !== void 0 && children !== null) { - props.children = flatten(vnode.props.children).map( - (child) => unwrap(child) + props.children = flatten(vnode.props.children).map((child) => + unwrap(child), ); } return { type, - props + props, }; }; const flatten = (rawChildren) => { - if (rawChildren === void 0 || rawChildren === null) - return []; - if (typeof rawChildren === "object" && "type" in rawChildren && rawChildren.type === react.Fragment) { + if (rawChildren === void 0 || rawChildren === null) return []; + if ( + typeof rawChildren === 'object' && + 'type' in rawChildren && + rawChildren.type === react.Fragment + ) { return flatten(rawChildren.props.children); } - if (!Array.isArray(rawChildren) || typeof rawChildren === "object" && "$" in rawChildren) { + if ( + !Array.isArray(rawChildren) || + (typeof rawChildren === 'object' && '$' in rawChildren) + ) { return [rawChildren]; } const flattenedChildren = rawChildren.flat(Infinity); @@ -67,14 +81,16 @@ if (CSSStyleSheet.prototype.replaceSync) { sheet.replaceSync(css); document.adoptedStyleSheets = [sheet]; } else { - const style = document.createElement("style"); + const style = document.createElement('style'); document.head.appendChild(style); - style.type = "text/css"; + style.type = 'text/css'; style.appendChild(document.createTextNode(css)); } const REGISTRY = new constants.Map$(); const block = (fn, options = {}) => { - const block2 = constants.MapHas$.call(REGISTRY, fn) ? constants.MapGet$.call(REGISTRY, fn) : block$1.block(fn, unwrap); + const block2 = constants.MapHas$.call(REGISTRY, fn) + ? constants.MapGet$.call(REGISTRY, fn) + : block$1.block(fn, unwrap); function MillionBlock(props) { const ref = react.useRef(null); const patch = react.useRef(null); @@ -98,7 +114,7 @@ const block = (fn, options = {}) => { react.Fragment, null, marker, - react.createElement(Effect, { effect }) + react.createElement(Effect, { effect }), ); return vnode; } @@ -117,24 +133,25 @@ const MillionArray = ({ each, children }) => { const fragmentRef = react.useRef(null); const cache = react.useRef({ each: null, - children: null + children: null, }); if (fragmentRef.current && each !== cache.current.each) { const newChildren = createChildren(each, children, cache); - block$1.arrayPatch$.call(fragmentRef.current, block$1.mapArray(newChildren)); + block$1.arrayPatch$.call( + fragmentRef.current, + block$1.mapArray(newChildren), + ); } react.useEffect(() => { - if (fragmentRef.current) - return; + if (fragmentRef.current) return; const newChildren = createChildren(each, children, cache); fragmentRef.current = block$1.mapArray(newChildren); block$1.arrayMount$.call(fragmentRef.current, ref.current); }, []); return react.createElement(RENDER_SCOPE, { ref }); }; -const For = react.memo( - MillionArray, - (oldProps, newProps) => Object.is(newProps.each, oldProps.each) +const For = react.memo(MillionArray, (oldProps, newProps) => + Object.is(newProps.each, oldProps.each), ); const createChildren = (each, getComponent, cache) => { const children = Array(each.length); @@ -153,13 +170,13 @@ const createChildren = (each, getComponent, cache) => { const block = block$1.block((props) => { return { type: RENDER_SCOPE, - props: { children: [props?.__scope] } + props: { children: [props?.__scope] }, }; }); const currentBlock = (props) => { return block({ props, - __scope: renderReactScope(react.createElement(vnode.type, props)) + __scope: renderReactScope(react.createElement(vnode.type, props)), }); }; constants.MapSet$.call(REGISTRY, vnode.type, currentBlock); diff --git a/website/components/million-library/react.mjs b/website/components/million-library/react.mjs index 03e2be792d..3be79e9f51 100644 --- a/website/components/million-library/react.mjs +++ b/website/components/million-library/react.mjs @@ -1,21 +1,60 @@ -import { Fragment, useRef, useCallback, useMemo, createElement, useEffect, memo } from 'react'; -import { d as document$, b as block$1, e as mount$, p as patch, g as remove$, h as arrayPatch$, a as mapArray, i as arrayMount$ } from './chunks/block.mjs'; -import { M as Map$, i as MapHas$, j as MapGet$, h as MapSet$ } from './chunks/constants.mjs'; -import { createRoot } from 'react-dom/client'; +import { + version, + Fragment, + useRef, + useCallback, + useMemo, + createElement, + useEffect, + memo, +} from 'react'; +import { + d as document$, + b as block$1, + e as mount$, + p as patch, + g as remove$, + h as arrayPatch$, + a as mapArray, + i as arrayMount$, +} from './chunks/block.mjs'; +import { + M as Map$, + i as MapHas$, + j as MapGet$, + h as MapSet$, +} from './chunks/constants.mjs'; -const REACT_ROOT = "__react_root"; -const RENDER_SCOPE = "million-render-scope"; +const REACT_ROOT = '__react_root'; +const RENDER_SCOPE = 'million-render-scope'; const renderReactScope = (jsx) => { return (el) => { const parent = el ?? document$.createElement(RENDER_SCOPE); - const root = REACT_ROOT in parent ? parent[REACT_ROOT] : parent[REACT_ROOT] = createRoot(parent); - root.render(jsx); + + let root; + if (version.startsWith('18')) { + import('react-dom/client') + .then((res) => { + root = + REACT_ROOT in parent + ? parent[REACT_ROOT] + : (parent[REACT_ROOT] = res.createRoot(parent)); + root.render(jsx); + }) + .catch((e) => { + // eslint-disable-next-line no-console + console.error(e); + }); + } else { + root = parent[REACT_ROOT]; + root.render(jsx); + } return parent; }; }; const unwrap = (vnode) => { - if (typeof vnode !== "object" || vnode === null || !("type" in vnode)) { - if (typeof vnode === "number" || vnode === true) { + if (typeof vnode !== 'object' || vnode === null || !('type' in vnode)) { + if (typeof vnode === 'number' || vnode === true) { return String(vnode); } else if (!vnode) { return void 0; @@ -23,30 +62,35 @@ const unwrap = (vnode) => { return vnode; } const type = vnode.type; - if (typeof type === "function") { + if (typeof type === 'function') { return unwrap(type(vnode.props ?? {})); } - if (typeof type === "object" && "$" in type) - return type; + if (typeof type === 'object' && '$' in type) return type; const props = { ...vnode.props }; const children = vnode.props?.children; if (children !== void 0 && children !== null) { - props.children = flatten(vnode.props.children).map( - (child) => unwrap(child) + props.children = flatten(vnode.props.children).map((child) => + unwrap(child), ); } return { type, - props + props, }; }; const flatten = (rawChildren) => { - if (rawChildren === void 0 || rawChildren === null) - return []; - if (typeof rawChildren === "object" && "type" in rawChildren && rawChildren.type === Fragment) { + if (rawChildren === void 0 || rawChildren === null) return []; + if ( + typeof rawChildren === 'object' && + 'type' in rawChildren && + rawChildren.type === Fragment + ) { return flatten(rawChildren.props.children); } - if (!Array.isArray(rawChildren) || typeof rawChildren === "object" && "$" in rawChildren) { + if ( + !Array.isArray(rawChildren) || + (typeof rawChildren === 'object' && '$' in rawChildren) + ) { return [rawChildren]; } const flattenedChildren = rawChildren.flat(Infinity); @@ -63,14 +107,16 @@ if (CSSStyleSheet.prototype.replaceSync) { sheet.replaceSync(css); document.adoptedStyleSheets = [sheet]; } else { - const style = document.createElement("style"); + const style = document.createElement('style'); document.head.appendChild(style); - style.type = "text/css"; + style.type = 'text/css'; style.appendChild(document.createTextNode(css)); } const REGISTRY = new Map$(); const block = (fn, options = {}) => { - const block2 = MapHas$.call(REGISTRY, fn) ? MapGet$.call(REGISTRY, fn) : block$1(fn, unwrap); + const block2 = MapHas$.call(REGISTRY, fn) + ? MapGet$.call(REGISTRY, fn) + : block$1(fn, unwrap); function MillionBlock(props) { const ref = useRef(null); const patch$1 = useRef(null); @@ -94,7 +140,7 @@ const block = (fn, options = {}) => { Fragment, null, marker, - createElement(Effect, { effect }) + createElement(Effect, { effect }), ); return vnode; } @@ -113,24 +159,22 @@ const MillionArray = ({ each, children }) => { const fragmentRef = useRef(null); const cache = useRef({ each: null, - children: null + children: null, }); if (fragmentRef.current && each !== cache.current.each) { const newChildren = createChildren(each, children, cache); arrayPatch$.call(fragmentRef.current, mapArray(newChildren)); } useEffect(() => { - if (fragmentRef.current) - return; + if (fragmentRef.current) return; const newChildren = createChildren(each, children, cache); fragmentRef.current = mapArray(newChildren); arrayMount$.call(fragmentRef.current, ref.current); }, []); return createElement(RENDER_SCOPE, { ref }); }; -const For = memo( - MillionArray, - (oldProps, newProps) => Object.is(newProps.each, oldProps.each) +const For = memo(MillionArray, (oldProps, newProps) => + Object.is(newProps.each, oldProps.each), ); const createChildren = (each, getComponent, cache) => { const children = Array(each.length); @@ -149,13 +193,13 @@ const createChildren = (each, getComponent, cache) => { const block = block$1((props) => { return { type: RENDER_SCOPE, - props: { children: [props?.__scope] } + props: { children: [props?.__scope] }, }; }); const currentBlock = (props) => { return block({ props, - __scope: renderReactScope(createElement(vnode.type, props)) + __scope: renderReactScope(createElement(vnode.type, props)), }); }; MapSet$.call(REGISTRY, vnode.type, currentBlock);