From 3f18edcd9f7f7aea8bba9d4afb3904b01735a8ab Mon Sep 17 00:00:00 2001 From: eps1lon Date: Wed, 14 Aug 2024 10:19:58 +0200 Subject: [PATCH 1/4] Publish experimental_renderToHTML --- packages/react-markup/README.md | 2 +- packages/react-markup/index.js | 2 +- packages/react-markup/package.json | 1 - .../react-markup/react-markup.react-server.js | 2 +- .../src/__tests__/ReactMarkupClient-test.js | 30 +++++++------ .../src/__tests__/ReactMarkupServer-test.js | 43 +++++++++++++------ 6 files changed, 51 insertions(+), 29 deletions(-) diff --git a/packages/react-markup/README.md b/packages/react-markup/README.md index 103e656f8aa1e..5629c155483f8 100644 --- a/packages/react-markup/README.md +++ b/packages/react-markup/README.md @@ -11,7 +11,7 @@ npm install react react-markup ## Usage ```js -import { renderToHTML } from 'react-markup'; +import { experimental_renderToHTML as renderToHTML } from 'react-markup'; import EmailTemplate from './my-email-template-component.js' async function action(email, name) { diff --git a/packages/react-markup/index.js b/packages/react-markup/index.js index 1e53a27a69614..0c4d09cf1f3ca 100644 --- a/packages/react-markup/index.js +++ b/packages/react-markup/index.js @@ -7,4 +7,4 @@ * @flow */ -export * from './src/ReactMarkupClient'; +export {renderToHTML as experimental_renderToHTML} from './src/ReactMarkupClient'; diff --git a/packages/react-markup/package.json b/packages/react-markup/package.json index f0b98c7a8ab67..9bc548b9f7da2 100644 --- a/packages/react-markup/package.json +++ b/packages/react-markup/package.json @@ -1,7 +1,6 @@ { "name": "react-markup", "version": "19.0.0", - "private": true, "description": "React package generating embedded HTML markup such as e-mails using Server Components.", "main": "index.js", "repository": { diff --git a/packages/react-markup/react-markup.react-server.js b/packages/react-markup/react-markup.react-server.js index 4b74acd707c14..aab4f038674b0 100644 --- a/packages/react-markup/react-markup.react-server.js +++ b/packages/react-markup/react-markup.react-server.js @@ -7,4 +7,4 @@ * @flow */ -export * from './src/ReactMarkupServer'; +export {renderToHTML as experimental_renderToHTML} from './src/ReactMarkupServer'; diff --git a/packages/react-markup/src/__tests__/ReactMarkupClient-test.js b/packages/react-markup/src/__tests__/ReactMarkupClient-test.js index a9193474234bb..e1e8eab4068e2 100644 --- a/packages/react-markup/src/__tests__/ReactMarkupClient-test.js +++ b/packages/react-markup/src/__tests__/ReactMarkupClient-test.js @@ -43,7 +43,7 @@ if (!__EXPERIMENTAL__) { return
hello world
; } - const html = await ReactMarkup.renderToHTML(); + const html = await ReactMarkup.experimental_renderToHTML(); expect(html).toBe('
hello world
'); }); @@ -52,14 +52,14 @@ if (!__EXPERIMENTAL__) { return
{'hello '.repeat(200)}world
; } - const html = await ReactMarkup.renderToHTML( + const html = await ReactMarkup.experimental_renderToHTML( React.createElement(Component), ); expect(html).toBe('
' + ('hello '.repeat(200) + 'world') + '
'); }); it('should prefix html tags with a doctype', async () => { - const html = await ReactMarkup.renderToHTML( + const html = await ReactMarkup.experimental_renderToHTML( hello , @@ -76,8 +76,10 @@ if (!__EXPERIMENTAL__) { } await expect(async () => { - await ReactMarkup.renderToHTML(); - }).rejects.toThrow(); + await ReactMarkup.experimental_renderToHTML(); + }).rejects.toThrow( + 'Cannot use state or effect Hooks in renderToHTML because this component will never be hydrated.', + ); }); it('should error on refs passed to host components', async () => { @@ -87,8 +89,10 @@ if (!__EXPERIMENTAL__) { } await expect(async () => { - await ReactMarkup.renderToHTML(); - }).rejects.toThrow(); + await ReactMarkup.experimental_renderToHTML(); + }).rejects.toThrow( + 'Cannot pass ref in renderToHTML because they will never be hydrated.', + ); }); it('should error on callbacks passed to event handlers', async () => { @@ -100,8 +104,10 @@ if (!__EXPERIMENTAL__) { } await expect(async () => { - await ReactMarkup.renderToHTML(); - }).rejects.toThrow(); + await ReactMarkup.experimental_renderToHTML(); + }).rejects.toThrow( + 'Cannot pass event handlers (onClick) in renderToHTML because the HTML will never be hydrated so they can never get called.', + ); }); it('supports the useId Hook', async () => { @@ -142,7 +148,7 @@ if (!__EXPERIMENTAL__) { ); } - const html = await ReactMarkup.renderToHTML(); + const html = await ReactMarkup.experimental_renderToHTML(); const container = document.createElement('div'); container.innerHTML = html; @@ -176,7 +182,7 @@ if (!__EXPERIMENTAL__) { ); } - const html = await ReactMarkup.renderToHTML(); + const html = await ReactMarkup.experimental_renderToHTML(); expect(html).toBe('
01
'); }); @@ -199,7 +205,7 @@ if (!__EXPERIMENTAL__) { } await expect(async () => { - await ReactMarkup.renderToHTML( + await ReactMarkup.experimental_renderToHTML(
, diff --git a/packages/react-markup/src/__tests__/ReactMarkupServer-test.js b/packages/react-markup/src/__tests__/ReactMarkupServer-test.js index 169c4ba5e6c5b..1e0cce3c9a150 100644 --- a/packages/react-markup/src/__tests__/ReactMarkupServer-test.js +++ b/packages/react-markup/src/__tests__/ReactMarkupServer-test.js @@ -64,7 +64,7 @@ if (!__EXPERIMENTAL__) { return React.createElement('div', null, 'hello world'); } - const html = await ReactMarkup.renderToHTML( + const html = await ReactMarkup.experimental_renderToHTML( React.createElement(Component), ); expect(html).toBe('
hello world
'); @@ -76,15 +76,14 @@ if (!__EXPERIMENTAL__) { return React.createElement('div', null, 'hello '.repeat(200) + 'world'); } - const html = await ReactMarkup.renderToHTML( + const html = await ReactMarkup.experimental_renderToHTML( React.createElement(Component), ); expect(html).toBe('
' + ('hello '.repeat(200) + 'world') + '
'); }); it('should prefix html tags with a doctype', async () => { - const html = await ReactMarkup.renderToHTML( - // We can't use JSX because that's client-JSX in our tests. + const html = await ReactMarkup.experimental_renderToHTML( React.createElement( 'html', null, @@ -104,8 +103,10 @@ if (!__EXPERIMENTAL__) { } await expect(async () => { - await ReactMarkup.renderToHTML(React.createElement(Component)); - }).rejects.toThrow(); + await ReactMarkup.experimental_renderToHTML( + React.createElement(Component), + ); + }).rejects.toThrow('React.useState is not a function'); }); it('should error on refs passed to host components', async () => { @@ -116,8 +117,12 @@ if (!__EXPERIMENTAL__) { } await expect(async () => { - await ReactMarkup.renderToHTML(React.createElement(Component)); - }).rejects.toThrow(); + await ReactMarkup.experimental_renderToHTML( + React.createElement(Component), + ); + }).rejects.toThrow( + 'Refs cannot be used in Server Components, nor passed to Client Components.', + ); }); it('should error on callbacks passed to event handlers', async () => { @@ -130,8 +135,20 @@ if (!__EXPERIMENTAL__) { } await expect(async () => { - await ReactMarkup.renderToHTML(React.createElement(Component)); - }).rejects.toThrow(); + await ReactMarkup.experimental_renderToHTML( + React.createElement(Component), + ); + }).rejects.toThrowError( + __DEV__ + ? `Event handlers cannot be passed to Client Component props.\n` + + '
\n' + + ' ^^^^^^^^^^^^^^^^^^\n' + + 'If you need interactivity, consider converting part of this to a Client Component.' + : `Event handlers cannot be passed to Client Component props.\n` + + ' {onClick: function onClick}\n' + + ' ^^^^^^^^^^^^^^^^\n' + + 'If you need interactivity, consider converting part of this to a Client Component.', + ); }); it('supports the useId Hook', async () => { @@ -173,7 +190,7 @@ if (!__EXPERIMENTAL__) { ); } - const html = await ReactMarkup.renderToHTML( + const html = await ReactMarkup.experimental_renderToHTML( React.createElement(Component), ); const container = document.createElement('div'); @@ -204,7 +221,7 @@ if (!__EXPERIMENTAL__) { return React.createElement('div', null, a, b); } - const html = await ReactMarkup.renderToHTML( + const html = await ReactMarkup.experimental_renderToHTML( React.createElement(Component), ); expect(html).toBe('
00
'); @@ -225,7 +242,7 @@ if (!__EXPERIMENTAL__) { } await expect(async () => { - await ReactMarkup.renderToHTML( + await ReactMarkup.experimental_renderToHTML( React.createElement('div', null, React.createElement(Foo)), { onError(error, errorInfo) { From ca7be5fcc36b5cc8c1355ef3ef217465f0c34671 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Wed, 14 Aug 2024 10:34:13 +0200 Subject: [PATCH 2/4] Accurate description --- packages/react-markup/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-markup/package.json b/packages/react-markup/package.json index 9bc548b9f7da2..6292cd6f97504 100644 --- a/packages/react-markup/package.json +++ b/packages/react-markup/package.json @@ -1,7 +1,7 @@ { "name": "react-markup", "version": "19.0.0", - "description": "React package generating embedded HTML markup such as e-mails using Server Components.", + "description": "React package generating embedded markup such as e-mails with support for Server Components.", "main": "index.js", "repository": { "type": "git", From ae76fdf64811cd7ca72498c0e721f73d2a583833 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Wed, 14 Aug 2024 14:09:29 +0200 Subject: [PATCH 3/4] Give up on renaming upon re-export --- packages/react-markup/index.js | 2 +- packages/react-markup/react-markup.react-server.js | 2 +- packages/react-markup/src/ReactMarkupClient.js | 2 +- packages/react-markup/src/ReactMarkupServer.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-markup/index.js b/packages/react-markup/index.js index 0c4d09cf1f3ca..1e53a27a69614 100644 --- a/packages/react-markup/index.js +++ b/packages/react-markup/index.js @@ -7,4 +7,4 @@ * @flow */ -export {renderToHTML as experimental_renderToHTML} from './src/ReactMarkupClient'; +export * from './src/ReactMarkupClient'; diff --git a/packages/react-markup/react-markup.react-server.js b/packages/react-markup/react-markup.react-server.js index aab4f038674b0..4b74acd707c14 100644 --- a/packages/react-markup/react-markup.react-server.js +++ b/packages/react-markup/react-markup.react-server.js @@ -7,4 +7,4 @@ * @flow */ -export {renderToHTML as experimental_renderToHTML} from './src/ReactMarkupServer'; +export * from './src/ReactMarkupServer'; diff --git a/packages/react-markup/src/ReactMarkupClient.js b/packages/react-markup/src/ReactMarkupClient.js index 4fb18e27c7d5b..e9cf689104169 100644 --- a/packages/react-markup/src/ReactMarkupClient.js +++ b/packages/react-markup/src/ReactMarkupClient.js @@ -31,7 +31,7 @@ type MarkupOptions = { onError?: (error: mixed, errorInfo: ErrorInfo) => ?string, }; -export function renderToHTML( +export function experimental_renderToHTML( children: ReactNodeList, options?: MarkupOptions, ): Promise { diff --git a/packages/react-markup/src/ReactMarkupServer.js b/packages/react-markup/src/ReactMarkupServer.js index c6d71e976b256..8608034293a4c 100644 --- a/packages/react-markup/src/ReactMarkupServer.js +++ b/packages/react-markup/src/ReactMarkupServer.js @@ -75,7 +75,7 @@ function noServerCallOrFormAction() { ); } -export function renderToHTML( +export function experimental_renderToHTML( children: ReactMarkupNodeList, options?: MarkupOptions, ): Promise { From 29b5f33a089cea664c1b1284b0d58d8f45303548 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Wed, 14 Aug 2024 19:28:40 +0200 Subject: [PATCH 4/4] acknowledge original owner --- packages/react-markup/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/react-markup/README.md b/packages/react-markup/README.md index 5629c155483f8..f01c9be6b36de 100644 --- a/packages/react-markup/README.md +++ b/packages/react-markup/README.md @@ -30,3 +30,7 @@ Note that this is an async function that needs to be awaited - unlike the legacy ### `react-markup` See https://react.dev/reference/react-markup + +## Thanks + +The React team thanks [Nikolai Mavrenkov](https://www.koluch.ru/) for donating the `react-markup` package name.