From fda84248a50648a3d393b5f1a90000b16a97ac36 Mon Sep 17 00:00:00 2001 From: Ricky Date: Mon, 29 Apr 2024 22:30:37 -0400 Subject: [PATCH 01/11] Convert "Canary" callouts to "React 19 beta" (#6811) * Convert "Canary" callouts to "React 19 beta" * Starting in * Bump version string --- src/components/Icon/IconRocket.tsx | 32 +++++++++++ src/components/Layout/Sidebar/SidebarLink.tsx | 15 ++++-- .../Layout/Sidebar/SidebarRouteTree.tsx | 6 +-- src/components/Layout/getRouteMeta.tsx | 4 +- src/components/MDX/ExpandableCallout.tsx | 34 ++++++++++-- src/components/MDX/MDXComponents.tsx | 32 +++++++++++ .../learn/manipulating-the-dom-with-refs.md | 6 +-- .../reference/react-dom/client/createRoot.md | 12 +++-- .../reference/react-dom/client/hydrateRoot.md | 12 +++-- .../reference/react-dom/components/common.md | 5 +- .../reference/react-dom/components/form.md | 11 ++-- .../reference/react-dom/components/input.md | 18 ++++--- .../reference/react-dom/components/link.md | 11 ++-- .../reference/react-dom/components/meta.md | 11 ++-- .../reference/react-dom/components/script.md | 11 ++-- .../reference/react-dom/components/style.md | 11 ++-- .../reference/react-dom/components/title.md | 11 ++-- .../reference/react-dom/hooks/index.md | 8 +-- .../react-dom/hooks/useFormStatus.md | 8 +-- src/content/reference/react-dom/preconnect.md | 9 ++-- .../reference/react-dom/prefetchDNS.md | 9 ++-- src/content/reference/react-dom/preinit.md | 9 ++-- .../reference/react-dom/preinitModule.md | 9 ++-- src/content/reference/react-dom/preload.md | 9 ++-- .../reference/react-dom/preloadModule.md | 9 ++-- src/content/reference/react/cache.md | 14 +++-- src/content/reference/react/use.md | 7 ++- src/content/reference/react/useActionState.md | 13 ++--- .../reference/react/useDeferredValue.md | 2 +- src/content/reference/react/useOptimistic.md | 9 ++-- src/content/reference/react/useTransition.md | 8 +-- src/content/reference/rsc/directives.md | 9 ++-- src/content/reference/rsc/server-actions.md | 7 ++- .../reference/rsc/server-components.md | 9 +++- src/content/reference/rsc/use-client.md | 8 +-- src/content/reference/rsc/use-server.md | 7 ++- src/sidebarReference.json | 54 +++++++++---------- src/siteConfig.js | 2 +- 38 files changed, 302 insertions(+), 159 deletions(-) create mode 100644 src/components/Icon/IconRocket.tsx diff --git a/src/components/Icon/IconRocket.tsx b/src/components/Icon/IconRocket.tsx new file mode 100644 index 00000000000..457736c7c55 --- /dev/null +++ b/src/components/Icon/IconRocket.tsx @@ -0,0 +1,32 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {memo} from 'react'; + +export const IconRocket = memo< + JSX.IntrinsicElements['svg'] & {title?: string; size?: 's' | 'md'} +>(function IconRocket({className, size = 'md'}) { + return ( + + ); +}); diff --git a/src/components/Layout/Sidebar/SidebarLink.tsx b/src/components/Layout/Sidebar/SidebarLink.tsx index 8a71d9e6e37..4429989d2fb 100644 --- a/src/components/Layout/Sidebar/SidebarLink.tsx +++ b/src/components/Layout/Sidebar/SidebarLink.tsx @@ -16,7 +16,7 @@ interface SidebarLinkProps { selected?: boolean; title: string; level: number; - canary?: boolean; + version?: 'canary' | 'major'; icon?: React.ReactNode; isExpanded?: boolean; hideArrow?: boolean; @@ -27,7 +27,7 @@ export function SidebarLink({ href, selected = false, title, - canary, + version, level, isExpanded, hideArrow, @@ -75,10 +75,17 @@ export function SidebarLink({ {/* This here needs to be refactored ofc */}
{title}{' '} - {canary && ( + {version === 'major' && ( + + React 19 + + )} + {version === 'canary' && ( )}
diff --git a/src/components/Layout/Sidebar/SidebarRouteTree.tsx b/src/components/Layout/Sidebar/SidebarRouteTree.tsx index 3f058073cbe..54f02b92527 100644 --- a/src/components/Layout/Sidebar/SidebarRouteTree.tsx +++ b/src/components/Layout/Sidebar/SidebarRouteTree.tsx @@ -87,7 +87,7 @@ export function SidebarRouteTree({ path, title, routes, - canary, + version, heading, hasSectionHeader, sectionHeader, @@ -121,7 +121,7 @@ export function SidebarRouteTree({ selected={selected} level={level} title={title} - canary={canary} + version={version} isExpanded={isExpanded} hideArrow={isForceExpanded} /> @@ -145,7 +145,7 @@ export function SidebarRouteTree({ selected={selected} level={level} title={title} - canary={canary} + version={version} /> ); diff --git a/src/components/Layout/getRouteMeta.tsx b/src/components/Layout/getRouteMeta.tsx index 3564dd73850..b3d14725d46 100644 --- a/src/components/Layout/getRouteMeta.tsx +++ b/src/components/Layout/getRouteMeta.tsx @@ -19,8 +19,8 @@ export type RouteTag = export interface RouteItem { /** Page title (for the sidebar) */ title: string; - /** Optional canary flag for heading */ - canary?: boolean; + /** Optional version flag for heading */ + version?: 'canary' | 'major'; /** Optional page description for heading */ description?: string; /* Additional meta info for page tagging */ diff --git a/src/components/MDX/ExpandableCallout.tsx b/src/components/MDX/ExpandableCallout.tsx index 415d5d867a0..5f594063d5a 100644 --- a/src/components/MDX/ExpandableCallout.tsx +++ b/src/components/MDX/ExpandableCallout.tsx @@ -8,8 +8,16 @@ import {IconNote} from '../Icon/IconNote'; import {IconWarning} from '../Icon/IconWarning'; import {IconPitfall} from '../Icon/IconPitfall'; import {IconCanary} from '../Icon/IconCanary'; +import {IconRocket} from '../Icon/IconRocket'; -type CalloutVariants = 'deprecated' | 'pitfall' | 'note' | 'wip' | 'canary'; +type CalloutVariants = + | 'deprecated' + | 'pitfall' + | 'note' + | 'wip' + | 'canary' + | 'major' + | 'rsc'; interface ExpandableCalloutProps { children: React.ReactNode; @@ -59,6 +67,22 @@ const variantMap = { overlayGradient: 'linear-gradient(rgba(249, 247, 243, 0), rgba(249, 247, 243, 1)', }, + major: { + title: 'React 19', + Icon: IconRocket, + containerClasses: 'bg-blue-10 dark:bg-blue-60 dark:bg-opacity-20', + textColor: 'text-blue-50 dark:text-blue-40', + overlayGradient: + 'linear-gradient(rgba(249, 247, 243, 0), rgba(249, 247, 243, 1)', + }, + rsc: { + title: 'React Server Components', + Icon: null, + containerClasses: 'bg-blue-10 dark:bg-blue-60 dark:bg-opacity-20', + textColor: 'text-blue-50 dark:text-blue-40', + overlayGradient: + 'linear-gradient(rgba(249, 247, 243, 0), rgba(249, 247, 243, 1)', + }, }; function ExpandableCallout({children, type = 'note'}: ExpandableCalloutProps) { @@ -72,9 +96,11 @@ function ExpandableCallout({children, type = 'note'}: ExpandableCalloutProps) { variant.containerClasses )}>

- + {variant.Icon && ( + + )} {variant.title}

diff --git a/src/components/MDX/MDXComponents.tsx b/src/components/MDX/MDXComponents.tsx index a89edf9c88a..c71bfdd70e9 100644 --- a/src/components/MDX/MDXComponents.tsx +++ b/src/components/MDX/MDXComponents.tsx @@ -95,6 +95,14 @@ const Canary = ({children}: {children: React.ReactNode}) => ( {children} ); +const NextMajor = ({children}: {children: React.ReactNode}) => ( + {children} +); + +const RSC = ({children}: {children: React.ReactNode}) => ( + {children} +); + const CanaryBadge = ({title}: {title: string}) => ( ( ); +const NextMajorBadge = ({title}: {title: string}) => ( + + React 19 + +); + +const RSCBadge = ({title}: {title: string}) => ( + + RSC + +); + const Blockquote = ({ children, ...props @@ -448,6 +476,10 @@ export const MDXComponents = { Note, Canary, CanaryBadge, + NextMajor, + NextMajorBadge, + RSC, + RSCBadge, PackageImport, ReadBlogPost, Recap, diff --git a/src/content/learn/manipulating-the-dom-with-refs.md b/src/content/learn/manipulating-the-dom-with-refs.md index 2d44d735359..1862f721309 100644 --- a/src/content/learn/manipulating-the-dom-with-refs.md +++ b/src/content/learn/manipulating-the-dom-with-refs.md @@ -341,9 +341,9 @@ In this example, `itemsRef` doesn't hold a single DOM node. Instead, it holds a This lets you read individual DOM nodes from the Map later. - + -This example shows another approach for managing the Map with a `ref` callback cleanup function. +Starting in React 19, callback refs can return a cleanup function. When the cleanup function is provided, React will not call the `ref` callback with `null` and call the cleanup function instead: ```js
  • ``` - + diff --git a/src/content/reference/react-dom/client/createRoot.md b/src/content/reference/react-dom/client/createRoot.md index b336b6e5ed2..7a275548a57 100644 --- a/src/content/reference/react-dom/client/createRoot.md +++ b/src/content/reference/react-dom/client/createRoot.md @@ -45,8 +45,8 @@ An app fully built with React will usually only have one `createRoot` call for i * **optional** `options`: An object with options for this React root. - * **optional** `onCaughtError`: Callback called when React catches an error in an Error Boundary. Called with the `error` caught by the Error Boundary, and an `errorInfo` object containing the `componentStack`. - * **optional** `onUncaughtError`: Callback called when an error is thrown and not caught by an Error Boundary. Called with the `error` that was thrown, and an `errorInfo` object containing the `componentStack`. + * **optional** `onCaughtError`: Callback called when React catches an error in an Error Boundary. Called with the `error` caught by the Error Boundary, and an `errorInfo` object containing the `componentStack`. + * **optional** `onUncaughtError`: Callback called when an error is thrown and not caught by an Error Boundary. Called with the `error` that was thrown, and an `errorInfo` object containing the `componentStack`. * **optional** `onRecoverableError`: Callback called when React automatically recovers from errors. Called with an `error` React throws, and an `errorInfo` object containing the `componentStack`. Some recoverable errors may include the original error cause as `error.cause`. * **optional** `identifierPrefix`: A string prefix React uses for IDs generated by [`useId`.](/reference/react/useId) Useful to avoid conflicts when using multiple roots on the same page. @@ -346,11 +346,13 @@ It is uncommon to call `render` multiple times. Usually, your components will [u ### Show a dialog for uncaught errors {/*show-a-dialog-for-uncaught-errors*/} - + -`onUncaughtError` is only available in the latest React Canary release. +`onUncaughtError` is available in React 19 beta, and the React canary channel. - +Learn more about [React's release channels here](/community/versioning-policy#all-release-channels). + + By default, React will log all uncaught errors to the console. To implement your own error reporting, you can provide the optional `onUncaughtError` root option: diff --git a/src/content/reference/react-dom/client/hydrateRoot.md b/src/content/reference/react-dom/client/hydrateRoot.md index cc30ce22c1f..ab883c4ad7b 100644 --- a/src/content/reference/react-dom/client/hydrateRoot.md +++ b/src/content/reference/react-dom/client/hydrateRoot.md @@ -41,8 +41,8 @@ React will attach to the HTML that exists inside the `domNode`, and take over ma * **optional** `options`: An object with options for this React root. - * **optional** `onCaughtError`: Callback called when React catches an error in an Error Boundary. Called with the `error` caught by the Error Boundary, and an `errorInfo` object containing the `componentStack`. - * **optional** `onUncaughtError`: Callback called when an error is thrown and not caught by an Error Boundary. Called with the `error` that was thrown and an `errorInfo` object containing the `componentStack`. + * **optional** `onCaughtError`: Callback called when React catches an error in an Error Boundary. Called with the `error` caught by the Error Boundary, and an `errorInfo` object containing the `componentStack`. + * **optional** `onUncaughtError`: Callback called when an error is thrown and not caught by an Error Boundary. Called with the `error` that was thrown and an `errorInfo` object containing the `componentStack`. * **optional** `onRecoverableError`: Callback called when React automatically recovers from errors. Called with the `error` React throws, and an `errorInfo` object containing the `componentStack`. Some recoverable errors may include the original error cause as `error.cause`. * **optional** `identifierPrefix`: A string prefix React uses for IDs generated by [`useId`.](/reference/react/useId) Useful to avoid conflicts when using multiple roots on the same page. Must be the same prefix as used on the server. @@ -376,11 +376,13 @@ It is uncommon to call [`root.render`](#root-render) on a hydrated root. Usually ### Show a dialog for uncaught errors {/*show-a-dialog-for-uncaught-errors*/} - + -`onUncaughtError` is only available in the latest React Canary release. +`onUncaughtError` is currently available in React 19 beta, and the React canary channel. - +Learn more about [React's release channels here](/community/versioning-policy#all-release-channels). + + By default, React will log all uncaught errors to the console. To implement your own error reporting, you can provide the optional `onUncaughtError` root option: diff --git a/src/content/reference/react-dom/components/common.md b/src/content/reference/react-dom/components/common.md index 62ee08139db..1acbb6eb8a4 100644 --- a/src/content/reference/react-dom/components/common.md +++ b/src/content/reference/react-dom/components/common.md @@ -259,9 +259,10 @@ React will also call your `ref` callback whenever you pass a *different* `ref` c * `node`: A DOM node or `null`. React will pass you the DOM node when the ref gets attached, and `null` when the `ref` gets detached. Unless you pass the same function reference for the `ref` callback on every render, the callback will get temporarily detached and re-attached during every re-render of the component. - + #### Returns {/*returns*/} +In React 19 beta, ref callbacks can return a cleanup function. When a cleanup function is provided, React will not call the `ref` callback with `null` and will call the cleanup function instead. * **optional** `cleanup function`: When the `ref` is detached, React will call the cleanup function. If a function is not returned by the `ref` callback, React will call the callback again with `null` as the argument when the `ref` gets detached. @@ -282,7 +283,7 @@ React will also call your `ref` callback whenever you pass a *different* `ref` c * When Strict Mode is on, React will **run one extra development-only setup+cleanup cycle** before the first real setup. This is a stress-test that ensures that your cleanup logic "mirrors" your setup logic and that it stops or undoes whatever the setup is doing. If this causes a problem, implement the cleanup function. * When you pass a *different* `ref` callback, React will call the *previous* callback's cleanup function if provided. If not cleanup function is defined, the `ref` callback will be called with `null` as the argument. The *next* function will be called with the DOM node. - + --- diff --git a/src/content/reference/react-dom/components/form.md b/src/content/reference/react-dom/components/form.md index afdddc749c9..17305995f1d 100644 --- a/src/content/reference/react-dom/components/form.md +++ b/src/content/reference/react-dom/components/form.md @@ -1,13 +1,16 @@ --- title: "
    " -canary: true --- - + -React's extensions to `` are currently only available in React's canary and experimental channels. In stable releases of React, `` works only as a [built-in browser HTML component](https://react.dev/reference/react-dom/components#all-html-components). Learn more about [React's release channels here](/community/versioning-policy#all-release-channels). +React's extensions to `` are currently available in React 19 beta and React's canary and experimental channels. - +In stable releases of React, `` works only as a [built-in browser HTML component](https://react.dev/reference/react-dom/components#all-html-components). + +Learn more about [React's release channels here](/community/versioning-policy#all-release-channels). + + diff --git a/src/content/reference/react-dom/components/input.md b/src/content/reference/react-dom/components/input.md index 706b8ae8ae8..93adb94c67a 100644 --- a/src/content/reference/react-dom/components/input.md +++ b/src/content/reference/react-dom/components/input.md @@ -2,6 +2,16 @@ title: "" --- + + +React's extensions to the `formAction` prop of `` is currently available in React 19 beta and the React canary channel. + +In stable releases of React, `` works only as a [built-in browser HTML component](https://react.dev/reference/react-dom/components#all-html-components). + +Learn more about [React's release channels here](/community/versioning-policy#all-release-channels). + + + The [built-in browser `` component](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input) lets you render different kinds of form inputs. @@ -32,13 +42,7 @@ To display an input, render the [built-in browser ``](https://developer.m `` supports all [common element props.](/reference/react-dom/components/common#props) - - -React's extensions to the `formAction` prop are currently only available in React's Canary and experimental channels. In stable releases of React, `formAction` works only as a [built-in browser HTML component](/reference/react-dom/components#all-html-components). Learn more about [React's release channels here](/community/versioning-policy#all-release-channels). - - - -[`formAction`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#formaction): A string or function. Overrides the parent `` for `type="submit"` and `type="image"`. When a URL is passed to `action` the form will behave like a standard HTML form. When a function is passed to `formAction` the function will handle the form submission. See [``](/reference/react-dom/components/form#props). +- [`formAction`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#formaction): A string or function. Overrides the parent `` for `type="submit"` and `type="image"`. When a URL is passed to `action` the form will behave like a standard HTML form. When a function is passed to `formAction` the function will handle the form submission. See [``](/reference/react-dom/components/form#props). You can [make an input controlled](#controlling-an-input-with-a-state-variable) by passing one of these props: diff --git a/src/content/reference/react-dom/components/link.md b/src/content/reference/react-dom/components/link.md index c3331d94c21..69259772541 100644 --- a/src/content/reference/react-dom/components/link.md +++ b/src/content/reference/react-dom/components/link.md @@ -1,13 +1,16 @@ --- link: "" -canary: true --- - + -React's extensions to `` are currently only available in React's canary and experimental channels. In stable releases of React `` works only as a [built-in browser HTML component](https://react.dev/reference/react-dom/components#all-html-components). Learn more about [React's release channels here](/community/versioning-policy#all-release-channels). +React's extensions to `` are currently available in React 19 beta and the React canary channel. - +In stable releases of React `` works only as a [built-in browser HTML component](https://react.dev/reference/react-dom/components#all-html-components). + +Learn more about [React's release channels here](/community/versioning-policy#all-release-channels). + + diff --git a/src/content/reference/react-dom/components/meta.md b/src/content/reference/react-dom/components/meta.md index 801ca2af16a..cc8d027003e 100644 --- a/src/content/reference/react-dom/components/meta.md +++ b/src/content/reference/react-dom/components/meta.md @@ -1,13 +1,16 @@ --- meta: "" -canary: true --- - + -React's extensions to `` are currently only available in React's canary and experimental channels. In stable releases of React `` works only as a [built-in browser HTML component](https://react.dev/reference/react-dom/components#all-html-components). Learn more about [React's release channels here](/community/versioning-policy#all-release-channels). +React's extensions to `` are currently available in React 19 beta and the React canary channel. - +In stable releases of React `` works only as a [built-in browser HTML component](https://react.dev/reference/react-dom/components#all-html-components). + +Learn more about [React's release channels here](/community/versioning-policy#all-release-channels). + + diff --git a/src/content/reference/react-dom/components/script.md b/src/content/reference/react-dom/components/script.md index fc4b0127725..4db57ad7157 100644 --- a/src/content/reference/react-dom/components/script.md +++ b/src/content/reference/react-dom/components/script.md @@ -1,13 +1,16 @@ --- script: " +``` + +On the client, your bootstrap script should [hydrate the entire `document` with a call to `hydrateRoot`:](/reference/react-dom/client/hydrateRoot#hydrating-an-entire-document) + +```js [[1, 4, ""]] +import { hydrateRoot } from 'react-dom/client'; +import App from './App.js'; + +hydrateRoot(document, ); +``` + +This will attach event listeners to the static server-generated HTML and make it interactive. + + + +#### Reading CSS and JS asset paths from the build output {/*reading-css-and-js-asset-paths-from-the-build-output*/} + +The final asset URLs (like JavaScript and CSS files) are often hashed after the build. For example, instead of `styles.css` you might end up with `styles.123456.css`. Hashing static asset filenames guarantees that every distinct build of the same asset will have a different filename. This is useful because it lets you safely enable long-term caching for static assets: a file with a certain name would never change content. + +However, if you don't know the asset URLs until after the build, there's no way for you to put them in the source code. For example, hardcoding `"/styles.css"` into JSX like earlier wouldn't work. To keep them out of your source code, your root component can read the real filenames from a map passed as a prop: + +```js {1,6} +export default function App({ assetMap }) { + return ( + + + My app + + + ... + + ); +} +``` + +On the server, render `` and pass your `assetMap` with the asset URLs: + +```js {1-5,8,9} +// You'd need to get this JSON from your build tooling, e.g. read it from the build output. +const assetMap = { + 'styles.css': '/styles.123456.css', + 'main.js': '/main.123456.js' +}; + +async function handler(request) { + const {prelude} = await prerender(, { + bootstrapScripts: [assetMap['/main.js']] + }); + return new Response(prelude, { + headers: { 'content-type': 'text/html' }, + }); +} +``` + +Since your server is now rendering ``, you need to render it with `assetMap` on the client too to avoid hydration errors. You can serialize and pass `assetMap` to the client like this: + +```js {9-10} +// You'd need to get this JSON from your build tooling. +const assetMap = { + 'styles.css': '/styles.123456.css', + 'main.js': '/main.123456.js' +}; + +async function handler(request) { + const {prelude} = await prerender(, { + // Careful: It's safe to stringify() this because this data isn't user-generated. + bootstrapScriptContent: `window.assetMap = ${JSON.stringify(assetMap)};`, + bootstrapScripts: [assetMap['/main.js']], + }); + return new Response(prelude, { + headers: { 'content-type': 'text/html' }, + }); +} +``` + +In the example above, the `bootstrapScriptContent` option adds an extra inline ` +``` + +On the client, your bootstrap script should [hydrate the entire `document` with a call to `hydrateRoot`:](/reference/react-dom/client/hydrateRoot#hydrating-an-entire-document) + +```js [[1, 4, ""]] +import { hydrateRoot } from 'react-dom/client'; +import App from './App.js'; + +hydrateRoot(document, ); +``` + +This will attach event listeners to the static server-generated HTML and make it interactive. + + + +#### Reading CSS and JS asset paths from the build output {/*reading-css-and-js-asset-paths-from-the-build-output*/} + +The final asset URLs (like JavaScript and CSS files) are often hashed after the build. For example, instead of `styles.css` you might end up with `styles.123456.css`. Hashing static asset filenames guarantees that every distinct build of the same asset will have a different filename. This is useful because it lets you safely enable long-term caching for static assets: a file with a certain name would never change content. + +However, if you don't know the asset URLs until after the build, there's no way for you to put them in the source code. For example, hardcoding `"/styles.css"` into JSX like earlier wouldn't work. To keep them out of your source code, your root component can read the real filenames from a map passed as a prop: + +```js {1,6} +export default function App({ assetMap }) { + return ( + + + My app + + + ... + + ); +} +``` + +On the server, render `` and pass your `assetMap` with the asset URLs: + +```js {1-5,8,9} +// You'd need to get this JSON from your build tooling, e.g. read it from the build output. +const assetMap = { + 'styles.css': '/styles.123456.css', + 'main.js': '/main.123456.js' +}; + +app.use('/', async (request, response) => { + const { prelude } = await prerenderToNodeStream(, { + bootstrapScripts: [assetMap['/main.js']] + }); + + response.setHeader('Content-Type', 'text/html'); + prelude.pipe(response); +}); +``` + +Since your server is now rendering ``, you need to render it with `assetMap` on the client too to avoid hydration errors. You can serialize and pass `assetMap` to the client like this: + +```js {9-10} +// You'd need to get this JSON from your build tooling. +const assetMap = { + 'styles.css': '/styles.123456.css', + 'main.js': '/main.123456.js' +}; + +app.use('/', async (request, response) => { + const { prelude } = await prerenderToNodeStream(, { + // Careful: It's safe to stringify() this because this data isn't user-generated. + bootstrapScriptContent: `window.assetMap = ${JSON.stringify(assetMap)};`, + bootstrapScripts: [assetMap['/main.js']], + }); + + response.setHeader('Content-Type', 'text/html'); + prelude.pipe(response); +}); +``` + +In the example above, the `bootstrapScriptContent` option adds an extra inline `