UNPKG

next-mdx-remote

Version:

utilities for loading mdx from any remote source as data, rather than as a local import

51 lines (50 loc) 2.72 kB
/** * Copyright (c) HashiCorp, Inc. * SPDX-License-Identifier: MPL-2.0 */ import './idle-callback-polyfill.js'; import React, { useEffect, useState, useMemo } from 'react'; import { jsxRuntime } from './jsx-runtime.cjs'; import * as mdx from '@mdx-js/react'; /** * Renders compiled source from next-mdx-remote/serialize. */ export function MDXRemote({ compiledSource, frontmatter, scope, components = {}, lazy, }) { const [isReadyToRender, setIsReadyToRender] = useState(!lazy || typeof window === 'undefined'); // if we're on the client side and `lazy` is set to true, we hydrate the // mdx content inside requestIdleCallback, allowing the page to get to // interactive quicker, but the mdx content to hydrate slower. useEffect(() => { if (lazy) { const handle = window.requestIdleCallback(() => { setIsReadyToRender(true); }); return () => window.cancelIdleCallback(handle); } }, []); const Content = useMemo(() => { // if we're ready to render, we can assemble the component tree and let React do its thing // first we set up the scope which has to include the mdx custom // create element function as well as any components we're using const fullScope = Object.assign({ opts: { ...mdx, ...jsxRuntime } }, { frontmatter }, scope); const keys = Object.keys(fullScope); const values = Object.values(fullScope); // now we eval the source code using a function constructor // in order for this to work we need to have React, the mdx createElement, // and all our components in scope for the function, which is the case here // we pass the names (via keys) in as the function's args, and execute the // function with the actual values. const hydrateFn = Reflect.construct(Function, keys.concat(`${compiledSource}`)); return hydrateFn.apply(hydrateFn, values).default; }, [scope, compiledSource]); if (!isReadyToRender) { // If we're not ready to render, return an empty div to preserve SSR'd markup return (React.createElement("div", { dangerouslySetInnerHTML: { __html: '' }, suppressHydrationWarning: true })); } // wrapping the content with MDXProvider will allow us to customize the standard // markdown components (such as "h1" or "a") with the "components" object const content = (React.createElement(mdx.MDXProvider, { components: components }, React.createElement(Content, null))); // If lazy = true, we need to render a wrapping div to preserve the same markup structure that was SSR'd return lazy ? React.createElement("div", null, content) : content; }