-
Notifications
You must be signed in to change notification settings - Fork 28k
/
Copy pathdynamic.tsx
152 lines (128 loc) · 5.12 KB
/
dynamic.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import React, { type JSX } from 'react'
import Loadable from './loadable.shared-runtime'
const isServerSide = typeof window === 'undefined'
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>)
| 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 DynamicOptions<P = {}> = LoadableGeneratedOptions & {
loading?: (loadingProps: DynamicOptionsLoadingProps) => JSX.Element | null
loader?: Loader<P> | LoaderMap
loadableGenerated?: LoadableGeneratedOptions
ssr?: boolean
/**
* @deprecated `suspense` prop is not required anymore
*/
suspense?: boolean
}
export type LoadableOptions<P = {}> = DynamicOptions<P>
export type LoadableFn<P = {}> = (
opts: LoadableOptions<P>
) => React.ComponentType<P>
export type LoadableComponent<P = {}> = React.ComponentType<P>
export function noSSR<P = {}>(
LoadableInitializer: LoadableFn<P>,
loadableOptions: DynamicOptions<P>
): React.ComponentType<P> {
// Removing webpack and modules means react-loadable won't try preloading
delete loadableOptions.webpack
delete loadableOptions.modules
// This check is necessary to prevent react-loadable from initializing on the server
if (!isServerSide) {
return LoadableInitializer(loadableOptions)
}
const Loading = loadableOptions.loading!
// This will only be rendered on the server side
return () => (
<Loading error={null} isLoading pastDelay={false} timedOut={false} />
)
}
/**
* This function lets you dynamically import a component.
* It uses [React.lazy()](https://react.dev/reference/react/lazy) with [Suspense](https://react.dev/reference/react/Suspense) under the hood.
*
* Read more: [Next.js Docs: `next/dynamic`](https://nextjs.org/docs/app/building-your-application/optimizing/lazy-loading#nextdynamic)
*/
export default function dynamic<P = {}>(
dynamicOptions: DynamicOptions<P> | Loader<P>,
options?: DynamicOptions<P>
): React.ComponentType<P> {
let loadableFn = Loadable as LoadableFn<P>
let loadableOptions: LoadableOptions<P> = {
// A loading component is not required, so we default it
loading: ({ error, isLoading, pastDelay }) => {
if (!pastDelay) return null
if (process.env.NODE_ENV !== 'production') {
if (isLoading) {
return null
}
if (error) {
return (
<p>
{error.message}
<br />
{error.stack}
</p>
)
}
}
return null
},
}
// 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
// Support for having import as a function, eg: dynamic(() => import('../hello-world'))
} else if (typeof dynamicOptions === 'function') {
loadableOptions.loader = dynamicOptions
// Support for having first argument being options, eg: dynamic({loader: import('../hello-world')})
} 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 }
const loaderFn = loadableOptions.loader as () => LoaderComponent<P>
const loader = () =>
loaderFn != null
? loaderFn().then(convertModule)
: Promise.resolve(convertModule(() => null))
// coming from build/babel/plugins/react-loadable-plugin.js
if (loadableOptions.loadableGenerated) {
loadableOptions = {
...loadableOptions,
...loadableOptions.loadableGenerated,
}
delete loadableOptions.loadableGenerated
}
// support for disabling server side rendering, eg: dynamic(() => import('../hello-world'), {ssr: false}).
if (typeof loadableOptions.ssr === 'boolean' && !loadableOptions.ssr) {
delete loadableOptions.webpack
delete loadableOptions.modules
return noSSR(loadableFn, loadableOptions)
}
return loadableFn({ ...loadableOptions, loader: loader as Loader<P> })
}