refactor and simplify app dynamic components (#59658)

This commit is contained in:
Jiachi Liu 2023-12-15 15:33:46 +01:00 committed by GitHub
parent 7faaeeb9b0
commit d3205561d2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 61 additions and 56 deletions

View file

@ -1,34 +1,18 @@
import React from 'react'
import Loadable from './lazy-dynamic/loadable'
type ComponentModule<P = {}> = { default: React.ComponentType<P> }
import type {
LoadableGeneratedOptions,
DynamicOptionsLoadingProps,
Loader,
LoaderComponent,
} from './lazy-dynamic/types'
export declare type LoaderComponent<P = {}> = Promise<
React.ComponentType<P> | ComponentModule<P>
>
export declare type Loader<P = {}> = () => LoaderComponent<P>
export type LoaderMap = { [module: string]: () => Loader<any> }
export type LoadableGeneratedOptions = {
webpack?(): any
modules?(): LoaderMap
}
export type DynamicOptionsLoadingProps = {
error?: Error | null
isLoading?: boolean
pastDelay?: boolean
retry?: () => void
timedOut?: boolean
}
// Normalize loader to return the module as form { default: Component } for `React.lazy`.
// Also for backward compatible since next/dynamic allows to resolve a component directly with loader
// Client component reference proxy need to be converted to a module.
function convertModule<P>(mod: React.ComponentType<P> | ComponentModule<P>) {
return { default: (mod as ComponentModule<P>)?.default || mod }
export {
type LoadableGeneratedOptions,
type DynamicOptionsLoadingProps,
type Loader,
type LoaderComponent,
}
export type DynamicOptions<P = {}> = LoadableGeneratedOptions & {
@ -50,8 +34,6 @@ export default function dynamic<P = {}>(
dynamicOptions: DynamicOptions<P> | Loader<P>,
options?: DynamicOptions<P>
): React.ComponentType<P> {
const loadableFn: LoadableFn<P> = Loadable
const loadableOptions: LoadableOptions<P> = {
// A loading component is not required, so we default it
loading: ({ error, isLoading, pastDelay }) => {
@ -78,13 +60,5 @@ export default function dynamic<P = {}>(
loadableOptions.loader = dynamicOptions
}
Object.assign(loadableOptions, options)
const loaderFn = loadableOptions.loader as () => LoaderComponent<P>
const loader = () =>
loaderFn != null
? loaderFn().then(convertModule)
: Promise.resolve(convertModule(() => null))
return loadableFn({ ...loadableOptions, loader: loader as Loader<P> })
return Loadable({ ...loadableOptions, ...options })
}

View file

@ -1,33 +1,42 @@
import React from 'react'
import { Suspense, lazy, Fragment } from 'react'
import { NoSSR } from './dynamic-no-ssr'
import type { ComponentModule } from './types'
// Normalize loader to return the module as form { default: Component } for `React.lazy`.
// Also for backward compatible since next/dynamic allows to resolve a component directly with loader
// Client component reference proxy need to be converted to a module.
function convertModule<P>(mod: React.ComponentType<P> | ComponentModule<P>) {
return { default: (mod as ComponentModule<P>)?.default || mod }
}
function Loadable(options: any) {
const opts = Object.assign(
{
const opts = {
loader: null,
loading: null,
ssr: true,
},
options
)
...options,
}
opts.lazy = React.lazy(opts.loader)
const loader = () =>
opts.loader != null
? opts.loader().then(convertModule)
: Promise.resolve(convertModule(() => null))
const Lazy = lazy(loader)
const Loading = opts.loading
const Wrap = opts.ssr ? Fragment : NoSSR
function LoadableComponent(props: any) {
const Loading = opts.loading
const fallbackElement = (
const fallbackElement = Loading ? (
<Loading isLoading={true} pastDelay={true} error={null} />
)
const Wrap = opts.ssr ? React.Fragment : NoSSR
const Lazy = opts.lazy
) : null
return (
<React.Suspense fallback={fallbackElement}>
<Suspense fallback={fallbackElement}>
<Wrap>
<Lazy {...props} />
</Wrap>
</React.Suspense>
</Suspense>
)
}

View file

@ -0,0 +1,22 @@
export type ComponentModule<P = {}> = { default: React.ComponentType<P> }
export declare type LoaderComponent<P = {}> = Promise<
React.ComponentType<P> | ComponentModule<P>
>
export declare type Loader<P = {}> = () => LoaderComponent<P>
export type LoaderMap = { [module: string]: () => Loader<any> }
export type LoadableGeneratedOptions = {
webpack?(): any
modules?(): LoaderMap
}
export type DynamicOptionsLoadingProps = {
error?: Error | null
isLoading?: boolean
pastDelay?: boolean
retry?: () => void
timedOut?: boolean
}