2019-04-25 17:51:36 +02:00
import React from 'react'
import Loadable from './loadable'
const isServerSide = typeof window === 'undefined'
2019-05-30 03:19:32 +02:00
export type LoaderComponent < P = {} > = Promise <
React . ComponentType < P > | { default : React . ComponentType < P > }
>
2019-04-25 17:51:36 +02:00
export type Loader < P = {} > = ( ( ) = > LoaderComponent < P > ) | LoaderComponent < P >
export type LoaderMap = { [ mdule : string ] : ( ) = > Loader < any > }
export type LoadableGeneratedOptions = {
2019-05-30 03:19:32 +02:00
webpack ? ( ) : any
modules ? ( ) : LoaderMap
2019-04-25 17:51:36 +02:00
}
2022-06-15 02:33:18 +02:00
export type DynamicOptionsLoadingProps = {
error? : Error | null
isLoading? : boolean
pastDelay? : boolean
retry ? : ( ) = > void
timedOut? : boolean
}
2022-05-27 22:11:34 +02:00
export type DynamicOptions < P = {} > = LoadableGeneratedOptions & {
2022-06-15 02:33:18 +02:00
loading ? : ( loadingProps : DynamicOptionsLoadingProps ) = > JSX . Element | null
2019-04-25 17:51:36 +02:00
loader? : Loader < P > | LoaderMap
loadableGenerated? : LoadableGeneratedOptions
ssr? : boolean
2021-08-13 23:08:45 +02:00
suspense? : boolean
}
2022-05-27 22:11:34 +02:00
export type LoadableOptions < P = {} > = DynamicOptions < P >
2019-04-25 17:51:36 +02:00
2019-05-30 03:19:32 +02:00
export type LoadableFn < P = {} > = (
2022-05-27 22:11:34 +02:00
opts : LoadableOptions < P >
2019-05-30 03:19:32 +02:00
) = > React . ComponentType < P >
2019-04-25 17:51:36 +02:00
export type LoadableComponent < P = {} > = React . ComponentType < P >
2019-05-30 03:19:32 +02:00
export function noSSR < P = {} > (
LoadableInitializer : LoadableFn < P > ,
2022-05-27 22:11:34 +02:00
loadableOptions : DynamicOptions < P >
2020-05-25 00:44:05 +02:00
) : React . ComponentType < P > {
2019-04-25 17:51:36 +02:00
// Removing webpack and modules means react-loadable won't try preloading
delete loadableOptions . webpack
delete loadableOptions . modules
2020-07-19 06:38:20 +02:00
// This check is necessary to prevent react-loadable from initializing on the server
2019-04-25 17:51:36 +02:00
if ( ! isServerSide ) {
return LoadableInitializer ( loadableOptions )
}
const Loading = loadableOptions . loading !
// This will only be rendered on the server side
2019-05-30 03:19:32 +02:00
return ( ) = > (
< Loading error = { null } isLoading pastDelay = { false } timedOut = { false } / >
)
2019-04-25 17:51:36 +02:00
}
export default function dynamic < P = {} > (
dynamicOptions : DynamicOptions < P > | Loader < P > ,
2019-05-30 03:19:32 +02:00
options? : DynamicOptions < P >
2019-04-25 17:51:36 +02:00
) : React . ComponentType < P > {
let loadableFn : LoadableFn < P > = Loadable
2022-09-09 21:46:23 +02:00
let loadableOptions : LoadableOptions < P > = options ? . suspense
? { }
: // only provide a default loading component when suspense is disabled
{
// A loading component is not required, so we default it
loading : ( { error , isLoading , pastDelay } ) = > {
if ( ! pastDelay ) return null
if ( process . env . NODE_ENV === 'development' ) {
if ( isLoading ) {
return null
}
if ( error ) {
return (
< p >
{ error . message }
< br / >
{ error . stack }
< / p >
)
}
}
2019-06-28 03:07:07 +02:00
return null
2022-09-09 21:46:23 +02:00
} ,
2019-04-25 17:51:36 +02:00
}
// Support for direct import(), eg: dynamic(import('../hello-world'))
// Note that this is only kept for the edge case where someone is passing in a promise as first argument
// The react-loadable babel plugin will turn dynamic(import('../hello-world')) into dynamic(() => import('../hello-world'))
// To make sure we don't execute the import without rendering first
if ( dynamicOptions instanceof Promise ) {
loadableOptions . loader = ( ) = > dynamicOptions
2019-05-30 03:19:32 +02:00
// Support for having import as a function, eg: dynamic(() => import('../hello-world'))
2019-04-25 17:51:36 +02:00
} else if ( typeof dynamicOptions === 'function' ) {
loadableOptions . loader = dynamicOptions
2019-05-30 03:19:32 +02:00
// Support for having first argument being options, eg: dynamic({loader: import('../hello-world')})
2019-04-25 17:51:36 +02:00
} else if ( typeof dynamicOptions === 'object' ) {
loadableOptions = { . . . loadableOptions , . . . dynamicOptions }
}
// Support for passing options, eg: dynamic(import('../hello-world'), {loading: () => <p>Loading something</p>})
loadableOptions = { . . . loadableOptions , . . . options }
2022-03-28 14:32:34 +02:00
// Error if Fizz rendering is not enabled and `suspense` option is set to true
2022-05-27 22:11:34 +02:00
if ( ! process . env . __NEXT_REACT_ROOT && loadableOptions . suspense ) {
2022-03-28 14:32:34 +02:00
throw new Error (
` Invalid suspense option usage in next/dynamic. Read more: https://nextjs.org/docs/messages/invalid-dynamic-suspense `
)
2021-08-13 23:08:45 +02:00
}
2022-09-09 21:46:23 +02:00
if ( loadableOptions . suspense ) {
if ( process . env . NODE_ENV !== 'production' ) {
2022-08-18 15:53:23 +02:00
/ * *
* TODO : Currently , next / dynamic will opt - in to React . lazy if { suspense : true } is used
* React 18 will always resolve the Suspense boundary on the server - side , effectively ignoring the ssr option
*
* In the future , when React Suspense with third - party libraries is stable , we can implement a custom version of
* React . lazy that can suspense on the server - side while only loading the component on the client - side
* /
if ( loadableOptions . ssr === false ) {
console . warn (
` "ssr: false" is ignored by next/dynamic because you can not enable "suspense" while disabling "ssr" at the same time. Read more: https://nextjs.org/docs/messages/invalid-dynamic-suspense `
)
}
if ( loadableOptions . loading != null ) {
console . warn (
` "loading" is ignored by next/dynamic because you have enabled "suspense". Place your loading element in your suspense boundary's "fallback" prop instead. Read more: https://nextjs.org/docs/messages/invalid-dynamic-suspense `
)
}
}
2022-09-09 21:46:23 +02:00
delete loadableOptions . ssr
delete loadableOptions . loading
2022-08-18 15:53:23 +02:00
}
2019-04-25 17:51:36 +02:00
// coming from build/babel/plugins/react-loadable-plugin.js
if ( loadableOptions . loadableGenerated ) {
2019-05-30 03:19:32 +02:00
loadableOptions = {
. . . loadableOptions ,
. . . loadableOptions . loadableGenerated ,
}
2019-04-25 17:51:36 +02:00
delete loadableOptions . loadableGenerated
}
2022-05-27 22:11:34 +02:00
// support for disabling server side rendering, eg: dynamic(import('../hello-world'), {ssr: false}).
// skip `ssr` for suspense mode and opt-in React.lazy directly
if ( typeof loadableOptions . ssr === 'boolean' && ! loadableOptions . suspense ) {
2019-04-25 17:51:36 +02:00
if ( ! loadableOptions . ssr ) {
delete loadableOptions . ssr
return noSSR ( loadableFn , loadableOptions )
}
delete loadableOptions . ssr
}
return loadableFn ( loadableOptions )
}