From 874ab96a05c382530a76b75872c6c76dc6ad17b5 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 23 Oct 2026 13:01:39 -0700 Subject: [PATCH 01/11] Split renderWebChat()'s render schema into legacyReactDOMSchema and reactRootSchema --- packages/bundle/src/renderWebChat.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/bundle/src/renderWebChat.tsx b/packages/bundle/src/renderWebChat.tsx index d1e9230573..dc8821e3b1 100644 --- a/packages/bundle/src/renderWebChat.tsx +++ b/packages/bundle/src/renderWebChat.tsx @@ -1,6 +1,7 @@ +import { reactNode } from '@msinternal/botframework-webchat-react-valibot'; import React, { type ComponentType, type ReactNode } from 'react'; import ReactDOM from 'react-dom'; -import { custom, function_, is, object, picklist } from 'valibot'; +import { args, custom, function_, is, object, picklist, pipe, tuple } from 'valibot'; /** A root from `createRoot()` of "react-dom/client". */ type RenderWebChatRoot = { render(children: ReactNode): void }; @@ -26,13 +27,16 @@ const DOCUMENT_NODE = 9; const DOCUMENT_FRAGMENT_NODE = 11; // Containers `ReactDOM.render()` accepts: element, document and document fragment, by `nodeType` to work across realms. -// Checked before `renderSchema`: some custom elements (e.g. Lit) have their own `render()` method. +// Checked before `reactRootSchema`: some custom elements (e.g. Lit) have their own `render()` method. const containerSchema = custom>(value => is(object({ nodeType: picklist([DOCUMENT_FRAGMENT_NODE, DOCUMENT_NODE, ELEMENT_NODE]) }), value) ); -// Both `ReactDOM.render()` (React 18 or earlier) and `root.render()`. -const renderSchema = object({ render: function_() }); +// `ReactDOM.render(children, container)`, React 18 or earlier. +const legacyReactDOMSchema = object({ render: pipe(function_(), args(tuple([reactNode(), containerSchema]))) }); + +// `root.render(children)`, root from `createRoot()` of "react-dom/client". +const reactRootSchema = object({ render: pipe(function_(), args(tuple([reactNode()]))) }); export default function renderWebChat( ReactWebChat: ComponentType, @@ -43,7 +47,7 @@ export default function renderWebChat( if (is(containerSchema, elementOrRoot)) { // React 19 or later removed `ReactDOM.render()`: Web Chat does not import "react-dom/client", the host creates the root. - if (!is(renderSchema, ReactDOM)) { + if (!is(legacyReactDOMSchema, ReactDOM)) { throw new Error( 'botframework-webchat: renderWebChat() on React 19 or later needs a root: pass createRoot(element) from "react-dom/client" instead of the element.' ); @@ -51,7 +55,7 @@ export default function renderWebChat( // React 16 to 18, and Preact. Also the IIFE build, which bundles React DOM 16.8.6 unless `window.ReactDOM` is set. ReactDOM.render(children, elementOrRoot); - } else if (is(renderSchema, elementOrRoot)) { + } else if (is(reactRootSchema, elementOrRoot)) { // The host created the root: render with it on any React version, it is the host's to unmount. elementOrRoot.render(children); } else { From d82b8c0e64c16d3383bdee187beb9b6baa829f73 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 23 Oct 2026 06:05:00 -0700 Subject: [PATCH 02/11] Use the React DOM of the page in the IIFE build of the Fluent theme The IIFE build bundled React DOM 16.8.6 (through renderWebChat() of botframework-webchat) and evaluated it on load against window.React, which crashes with React 19. Like "react", "react-dom" now resolves to window.ReactDOM; the bundled copy was only used by renderWebChat(), which the IIFE build does not export. --- packages/fluent-theme/src/external.umd/react-dom.ts | 4 ++++ packages/fluent-theme/tsup.config.ts | 6 ++++++ 2 files changed, 10 insertions(+) create mode 100644 packages/fluent-theme/src/external.umd/react-dom.ts diff --git a/packages/fluent-theme/src/external.umd/react-dom.ts b/packages/fluent-theme/src/external.umd/react-dom.ts new file mode 100644 index 0000000000..ea199e65a0 --- /dev/null +++ b/packages/fluent-theme/src/external.umd/react-dom.ts @@ -0,0 +1,4 @@ +/// + +// The React DOM of the page, as with "react": a bundled React DOM cannot initialize with another React, such as React 19. +module.exports = (globalThis as any).ReactDOM; diff --git a/packages/fluent-theme/tsup.config.ts b/packages/fluent-theme/tsup.config.ts index 291ddd36e2..d665a89a7b 100644 --- a/packages/fluent-theme/tsup.config.ts +++ b/packages/fluent-theme/tsup.config.ts @@ -16,6 +16,12 @@ const umdResolvePlugin = { path: join(fileURLToPath(import.meta.url), '../src/external.umd/react.ts') })); + // ESBuild use Go regular expressions and does not understand Unicode flag. + // eslint-disable-next-line require-unicode-regexp + build.onResolve({ filter: /^react-dom$/ }, () => ({ + path: join(fileURLToPath(import.meta.url), '../src/external.umd/react-dom.ts') + })); + // ESBuild use Go regular expressions and does not understand Unicode flag. // eslint-disable-next-line require-unicode-regexp build.onResolve({ filter: /^botframework-webchat-api$/ }, () => ({ From e2ddf199830ab58e47850c6e8649467b5e1717fd Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 23 Oct 2026 06:05:01 -0700 Subject: [PATCH 03/11] Add a React 19 test for the IIFE build of the Fluent theme --- __tests__/html2/react19/fluentTheme.iife.html | 79 ++++++++++++++++++ .../react19/fluentTheme.iife.html.snap-1.png | Bin 0 -> 10852 bytes 2 files changed, 79 insertions(+) create mode 100644 __tests__/html2/react19/fluentTheme.iife.html create mode 100644 __tests__/html2/react19/fluentTheme.iife.html.snap-1.png diff --git a/__tests__/html2/react19/fluentTheme.iife.html b/__tests__/html2/react19/fluentTheme.iife.html new file mode 100644 index 0000000000..82c665d2c1 --- /dev/null +++ b/__tests__/html2/react19/fluentTheme.iife.html @@ -0,0 +1,79 @@ + + + + React 19: the IIFE build of the Fluent theme sends a message and shows the reply + + + + + + + + + +
+ + + diff --git a/__tests__/html2/react19/fluentTheme.iife.html.snap-1.png b/__tests__/html2/react19/fluentTheme.iife.html.snap-1.png new file mode 100644 index 0000000000000000000000000000000000000000..ca3f67cbf3940961c10af07b9348afa2843a4339 GIT binary patch literal 10852 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 literal 0 HcmV?d00001 From 48fa368324c2be4b9ff6df401e95dcf88cab1514 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 23 Oct 2026 06:54:55 -0700 Subject: [PATCH 04/11] Remove react-dom/client from the baseline import map in HOSTING.md static/react-dom.baseline/client.js is never built (React 16 has no react-dom/client), and renderWebChat() does not import react-dom/client: on React 19, the host passes a root from createRoot(). --- docs/HOSTING.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/HOSTING.md b/docs/HOSTING.md index 993f092618..6c533d5054 100644 --- a/docs/HOSTING.md +++ b/docs/HOSTING.md @@ -118,8 +118,7 @@ When deploying to locked down environment, clients may not have access to smart "imports": { "botframework-webchat": "https://cdn.botframework.com/4.19.0/static/botframework-webchat.js", "react": "https://cdn.botframework.com/4.19.0/static/react.baseline.js", - "react-dom": "https://cdn.botframework.com/4.19.0/static/react-dom.baseline.js", - "react-dom/client": "https://cdn.botframework.com/4.19.0/static/react-dom.baseline/client.js" + "react-dom": "https://cdn.botframework.com/4.19.0/static/react-dom.baseline.js" } } From fa51b91c85db6852ba0f24b8f758037f5218f841 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 23 Oct 2026 13:44:38 -0700 Subject: [PATCH 05/11] Test that renderWebChat() throws an error from root.render() synchronously --- packages/bundle/src/renderWebChat.spec.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/bundle/src/renderWebChat.spec.tsx b/packages/bundle/src/renderWebChat.spec.tsx index 1ca156f321..70dff7f674 100644 --- a/packages/bundle/src/renderWebChat.spec.tsx +++ b/packages/bundle/src/renderWebChat.spec.tsx @@ -44,6 +44,18 @@ test('Root should be rendered by root.render() instead of ReactDOM.render()', () expect(render).not.toHaveBeenCalled(); }); +test('Error from root.render() should be thrown synchronously to the host', () => { + // React throws this when rendering with a root after `root.unmount()`. + const root = { + render: jest.fn(() => { + throw new Error('Cannot update an unmounted root.'); + }) + }; + + expect(() => renderWebChat(ReactWebChat, {}, root)).toThrow('Cannot update an unmounted root.'); + expect(root.render).toHaveBeenCalledTimes(1); +}); + test('Element on React 19 should throw an error asking for a root', () => { // React 19 removed `ReactDOM.render()`. Object.assign(ReactDOM, { render: undefined }); From a52527b5c6a77cbb01e98d3c1ab1064dd1c3245d Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 23 Oct 2026 13:44:39 -0700 Subject: [PATCH 06/11] Add a React 19 test for the copy button of code blocks with the IIFE build Ported from #5955: the page creates its own root with createRoot() and renders with React 19. --- .../html2/react19/codeBlockCopyButton.html | 91 ++++++++++++++++++ .../codeBlockCopyButton.html.snap-1.png | Bin 0 -> 8077 bytes 2 files changed, 91 insertions(+) create mode 100644 __tests__/html2/react19/codeBlockCopyButton.html create mode 100644 __tests__/html2/react19/codeBlockCopyButton.html.snap-1.png diff --git a/__tests__/html2/react19/codeBlockCopyButton.html b/__tests__/html2/react19/codeBlockCopyButton.html new file mode 100644 index 0000000000..7f29a1321b --- /dev/null +++ b/__tests__/html2/react19/codeBlockCopyButton.html @@ -0,0 +1,91 @@ + + + + React 19: renders the copy button of code blocks + + + + + + + + +
+ + + diff --git a/__tests__/html2/react19/codeBlockCopyButton.html.snap-1.png b/__tests__/html2/react19/codeBlockCopyButton.html.snap-1.png new file mode 100644 index 0000000000000000000000000000000000000000..f931af3deb197f4a9ed7a55de666b197082e5ae2 GIT binary patch literal 8077 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 literal 0 HcmV?d00001 From 46e59d55c07747ea71926c76d38f5add44a8d9fb Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 23 Oct 2026 14:03:17 -0700 Subject: [PATCH 07/11] Name ReactDOM.createRoot() in the error of renderWebChat() for an invalid container --- __tests__/html2/react19/renderWebChat.iife.html | 2 +- packages/bundle/src/renderWebChat.spec.tsx | 2 +- packages/bundle/src/renderWebChat.tsx | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/__tests__/html2/react19/renderWebChat.iife.html b/__tests__/html2/react19/renderWebChat.iife.html index 221d62ab38..4ebc859775 100644 --- a/__tests__/html2/react19/renderWebChat.iife.html +++ b/__tests__/html2/react19/renderWebChat.iife.html @@ -54,7 +54,7 @@ // WHEN: Web Chat is rendered by renderWebChat() with something that is neither an element nor a root. // THEN: It throws. expect(() => renderWebChat({ directLine, store }, {})).toThrow( - 'renderWebChat() expects an HTMLElement or a root from createRoot().' + 'renderWebChat() expects an HTMLElement or a root from ReactDOM.createRoot().' ); // WHEN: Web Chat is rendered by renderWebChat() with a root from createRoot() instead of the element, without the send box, as the error says. diff --git a/packages/bundle/src/renderWebChat.spec.tsx b/packages/bundle/src/renderWebChat.spec.tsx index 70dff7f674..b569dfcada 100644 --- a/packages/bundle/src/renderWebChat.spec.tsx +++ b/packages/bundle/src/renderWebChat.spec.tsx @@ -67,5 +67,5 @@ test('Element on React 19 should throw an error asking for a root', () => { test('Neither a container nor a root should throw', () => expect(() => renderWebChat(ReactWebChat, {}, {} as unknown as HTMLElement)).toThrow( - 'renderWebChat() expects an HTMLElement or a root from createRoot().' + 'renderWebChat() expects an HTMLElement or a root from ReactDOM.createRoot().' )); diff --git a/packages/bundle/src/renderWebChat.tsx b/packages/bundle/src/renderWebChat.tsx index dc8821e3b1..9f12077a9d 100644 --- a/packages/bundle/src/renderWebChat.tsx +++ b/packages/bundle/src/renderWebChat.tsx @@ -59,7 +59,9 @@ export default function renderWebChat( // The host created the root: render with it on any React version, it is the host's to unmount. elementOrRoot.render(children); } else { - throw new Error('botframework-webchat: renderWebChat() expects an HTMLElement or a root from createRoot().'); + throw new Error( + 'botframework-webchat: renderWebChat() expects an HTMLElement or a root from ReactDOM.createRoot().' + ); } } From e7b64d7ef79b9fdaf0ca0c0a72633eedc615ab13 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 23 Oct 2026 14:03:46 -0700 Subject: [PATCH 08/11] Say what keeps the ESM renderWebChat() test on emulated React 19: react-film imports render() by name --- __tests__/html2/react19/renderWebChat.esm.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/html2/react19/renderWebChat.esm.html b/__tests__/html2/react19/renderWebChat.esm.html index ac79ff7a4d..2ee05557ca 100644 --- a/__tests__/html2/react19/renderWebChat.esm.html +++ b/__tests__/html2/react19/renderWebChat.esm.html @@ -8,7 +8,7 @@