diff --git a/packages/react-markup/README.md b/packages/react-markup/README.md index 103e656f8aa..f01c9be6b36 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) { @@ -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. diff --git a/packages/react-markup/package.json b/packages/react-markup/package.json index f0b98c7a8ab..6292cd6f975 100644 --- a/packages/react-markup/package.json +++ b/packages/react-markup/package.json @@ -1,8 +1,7 @@ { "name": "react-markup", "version": "19.0.0", - "private": true, - "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", diff --git a/packages/react-markup/src/ReactMarkupClient.js b/packages/react-markup/src/ReactMarkupClient.js index 4fb18e27c7d..e9cf6891041 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 c6d71e976b2..8608034293a 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 { diff --git a/packages/react-markup/src/__tests__/ReactMarkupClient-test.js b/packages/react-markup/src/__tests__/ReactMarkupClient-test.js index a9193474234..e1e8eab4068 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 169c4ba5e6c..1e0cce3c9a1 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) {