import { MinimalComponentProps, getComponent, getMergedComponentProperties, isWebComponent, registeredDatasources, registered, toKebabCase } from '@sitecore/byoc' import React from 'react' export * from '@sitecore/byoc' var NextClientsideRenderer: any = null var NextDynamicFunction: any = null export interface ComponentProps extends MinimalComponentProps { fallbackWrapper?: boolean fallback?: any clientFallback?: any } /** * A special case of Component designed for Next.js. * It uses `next/dynamic` to control rendering on the client, server, or both. * * @param {ComponentProps} props - The properties for the component. * @returns {JSX.Element} The JSX element representing the component. */ export function NextComponent(props: ComponentProps) { // See if component is registered in current context const { componentName, clientFallback, ...attributes } = props const Component = getComponent(componentName)?.component // Render component as is on the server const Regular = React.useMemo(() => , []) // maintain identity of wrapped component const External = React.useMemo( () => NextDynamicFunction(() => Promise.resolve(NextClientsideRenderer), { ssr: false, // Show server-rendered component during page load loading: () => Regular }), [] ) // remove functions from props to avoid them to be passed from server to client which is illegal var sanitizedAttributes = typeof window == 'undefined' ? JSON.parse(JSON.stringify(attributes)) : attributes return ( Promise.resolve(() => clientFallback), { ssr: false })) : props.fallback } > ) } /** * Enables Next.js client/server rendering options for registered components. * The `component` argument should be a function exported from a file with a `use client` directive. * * @param {any} dynamic - The Next dynamic function. * @param {any} component - The component function. * @returns {any} Augmented bundle. */ export function enableNextClientsideComponents(dynamic: any, component: any) { NextDynamicFunction = dynamic NextClientsideRenderer = component return component } /** * Renders a registered external component in a React tree. If enableNextClientsideComponents was called previously, * the component is rendered using NextComponent codepath, which allows rendering separate component on client and * server. * * @param {ComponentProps} props - The properties for the component. * @returns {JSX.Element} The JSX element representing the component. */ export function Component(props: ComponentProps) { if (Object.keys(props).length == 0) { return <> } // _dynamic property ensures the component does not endlessly loop in pre-app-router setup if (NextDynamicFunction && !props._dynamic) { return NextComponent({ _dynamic: true, ...props }) } return RegularComponent(props) } /** * Pass list of server side component registrations to clients, and embed clientside components to the page. */ export function Bundle() { return ( <> {/** Will not be rendered, but needs to be referenced */} {} ) } /** * A wrapper over External component that accepts a fallback for rendering in case of a missing component. * It accepts properties in camelcase format and ensures no hydration errors on the client-side. * * @param {ComponentProps} props - The properties for the component. * @returns {JSX.Element} The JSX element representing the component. */ export function RegularComponent(props: ComponentProps) { const { componentName, fallback, fallbackWrapper } = props const definition = getComponent(componentName) const Component = definition?.component as any const { attributes, properties, merged } = getMergedComponentProperties(props) if ((!Component && fallback) || !componentName) { if (fallbackWrapper === false) return <>{fallback} return ( {fallback} ) } if (definition && Component && isWebComponent(Component)) { const webComponentName = 'byoc-' + toKebabCase(definition.id) return React.createElement(webComponentName, { ...attributes, ref: (el: { sitecoreContextCallback: (context: typeof properties) => {} }) => { if (el && typeof window != 'undefined') { // web components can define sitecoreContextCallback defined that recieves props directly window.customElements?.whenDefined(webComponentName).then(() => { el.sitecoreContextCallback?.({ ...merged }) }) } } }) } return ( <> {Component == null ? null : } ) } declare global { namespace JSX { interface IntrinsicElements { 'feaas-external': { 'data-external-id': string children?: any dangerouslySetInnerHTML?: { __html: string } [key: string]: any } } } }