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 React from 'react'
import Loadable from './lazy-dynamic/loadable' 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< export {
React.ComponentType<P> | ComponentModule<P> type LoadableGeneratedOptions,
> type DynamicOptionsLoadingProps,
type Loader,
export declare type Loader<P = {}> = () => LoaderComponent<P> type LoaderComponent,
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 DynamicOptions<P = {}> = LoadableGeneratedOptions & { export type DynamicOptions<P = {}> = LoadableGeneratedOptions & {
@ -50,8 +34,6 @@ export default function dynamic<P = {}>(
dynamicOptions: DynamicOptions<P> | Loader<P>, dynamicOptions: DynamicOptions<P> | Loader<P>,
options?: DynamicOptions<P> options?: DynamicOptions<P>
): React.ComponentType<P> { ): React.ComponentType<P> {
const loadableFn: LoadableFn<P> = Loadable
const loadableOptions: LoadableOptions<P> = { const loadableOptions: LoadableOptions<P> = {
// A loading component is not required, so we default it // A loading component is not required, so we default it
loading: ({ error, isLoading, pastDelay }) => { loading: ({ error, isLoading, pastDelay }) => {
@ -78,13 +60,5 @@ export default function dynamic<P = {}>(
loadableOptions.loader = dynamicOptions loadableOptions.loader = dynamicOptions
} }
Object.assign(loadableOptions, options) return Loadable({ ...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> })
} }

View file

@ -1,33 +1,42 @@
import React from 'react' import { Suspense, lazy, Fragment } from 'react'
import { NoSSR } from './dynamic-no-ssr' 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) { function Loadable(options: any) {
const opts = Object.assign( const opts = {
{ loader: null,
loader: null, loading: null,
loading: null, ssr: true,
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) { function LoadableComponent(props: any) {
const Loading = opts.loading const fallbackElement = Loading ? (
const fallbackElement = (
<Loading isLoading={true} pastDelay={true} error={null} /> <Loading isLoading={true} pastDelay={true} error={null} />
) ) : null
const Wrap = opts.ssr ? React.Fragment : NoSSR
const Lazy = opts.lazy
return ( return (
<React.Suspense fallback={fallbackElement}> <Suspense fallback={fallbackElement}>
<Wrap> <Wrap>
<Lazy {...props} /> <Lazy {...props} />
</Wrap> </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
}