packages/runtime/src/dynamic.tsx (37 lines of code) (raw):
import type { ReactNode } from 'react';
import React, { Suspense, lazy } from 'react';
import useMounted from './useMounted.js';
const isServer = import.meta.renderer === 'server';
type ComponentModule<P = {}> = { default: React.ComponentType<P> };
export type LoaderComponent<P = {}> = Promise<React.ComponentType<P> | ComponentModule<P>>;
export type Loader<P = {}> = (() => LoaderComponent<P>) | LoaderComponent<P>;
export interface DynamicOptions {
/** @default true */
ssr?: boolean;
/** the fallback UI to render before the actual is loaded */
fallback?: () => ReactNode;
}
// Normalize loader to return the module as form { default: Component } for `React.lazy`.
function convertModule<P>(mod: React.ComponentType<P> | ComponentModule<P>) {
return { default: (mod as ComponentModule<P>)?.default || mod };
}
const DefaultFallback = () => null;
export function dynamic<P = {}>(loader: Loader<P>, option?: DynamicOptions) {
const { ssr = true, fallback = DefaultFallback } = option || {};
let realLoader;
// convert dynamic(import('xxx')) to dynamic(() => import('xxx'))
if (loader instanceof Promise) {
realLoader = () => loader;
} else if (typeof loader === 'function') {
realLoader = loader;
}
if (!realLoader) return DefaultFallback;
const Fallback = fallback;
if (!ssr && isServer) {
return () => <Fallback />;
}
const LazyComp = lazy(() => realLoader().then(convertModule));
return (props) => {
const hasMounted = useMounted();
return ssr || hasMounted ? (
<Suspense fallback={<Fallback />}>
<LazyComp {...props} />
</Suspense>
) : (
<Fallback />
);
};
}