2018-07-24 09:24:40 +00:00
// @flow
import type { ElementType } from 'react'
2017-06-16 13:19:34 +00:00
2018-07-24 09:24:40 +00:00
import React from 'react'
2018-08-30 12:02:18 +00:00
import Loadable from './loadable'
2018-07-24 09:24:40 +00:00
type ImportedComponent = Promise < null | ElementType >
type ComponentMapping = { [ componentName : string ] : ImportedComponent }
type NextDynamicOptions = {
loader ? : ComponentMapping | ( ) => ImportedComponent ,
loading : ElementType ,
timeout ? : number ,
delay ? : number ,
ssr ? : boolean ,
render ? : ( props : any , loaded : { [ componentName : string ] : ElementType } ) => ElementType ,
modules ? : ( ) => ComponentMapping ,
loadableGenerated ? : {
webpack ? : any ,
modules ? : any
2017-06-16 13:19:34 +00:00
}
2018-07-24 09:24:40 +00:00
}
2017-04-17 20:15:50 +00:00
2018-07-24 09:24:40 +00:00
type LoadableOptions = {
loader ? : ComponentMapping | ( ) => ImportedComponent ,
loading : ElementType ,
timeout ? : number ,
delay ? : number ,
render ? : ( props : any , loaded : { [ componentName : string ] : ElementType } ) => ElementType ,
webpack ? : any ,
modules ? : any
}
2017-06-16 13:19:34 +00:00
2018-07-24 09:24:40 +00:00
const isServerSide = typeof window === 'undefined'
2017-06-16 13:19:34 +00:00
2018-07-24 09:24:40 +00:00
export function noSSR ( LoadableInitializer : ( loadableOptions : LoadableOptions ) => ElementType , loadableOptions : LoadableOptions ) {
// Removing webpack and modules means react-loadable won't try preloading
delete loadableOptions . webpack
delete loadableOptions . modules
2017-06-16 13:19:34 +00:00
2018-07-24 09:24:40 +00:00
// This check is neccesary to prevent react-loadable from initializing on the server
if ( ! isServerSide ) {
2018-08-30 13:09:53 +00:00
return LoadableInitializer ( loadableOptions )
2018-07-24 09:24:40 +00:00
}
2017-06-16 13:19:34 +00:00
2018-08-30 13:09:53 +00:00
// This will only be rendered on the server side
return ( ) => < loadableOptions . loading error = { null } isLoading pastDelay = { false } timedOut = { false } / >
}
2017-06-16 13:19:34 +00:00
2018-08-30 13:09:53 +00:00
function DefaultLoading ( ) {
return < p > loading ... < / p >
2017-04-17 20:15:50 +00:00
}
2018-07-24 09:24:40 +00:00
export default function dynamic ( dynamicOptions : any , options : NextDynamicOptions ) {
let loadableFn = Loadable
let loadableOptions : NextDynamicOptions = {
// A loading component is not required, so we default it
loading : ( { error , isLoading } ) => {
if ( process . env . NODE _ENV === 'development' ) {
if ( isLoading ) {
2018-08-30 13:09:53 +00:00
return < DefaultLoading / >
2018-07-24 09:24:40 +00:00
}
if ( error ) {
return < p > { error . message } < br / > { error . stack } < / p >
}
}
2017-04-19 18:25:06 +00:00
2018-08-30 13:09:53 +00:00
return < DefaultLoading / >
2018-07-24 09:24:40 +00:00
}
2018-01-30 15:40:52 +00:00
}
2018-07-24 09:24:40 +00:00
// Support for direct import(), eg: dynamic(import('../hello-world'))
2018-09-25 13:27:09 +00:00
// 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
2018-07-24 09:24:40 +00:00
if ( typeof dynamicOptions . then === 'function' ) {
loadableOptions . loader = ( ) => dynamicOptions
2018-09-25 13:27:09 +00:00
// Support for having import as a function, eg: dynamic(() => import('../hello-world'))
} else if ( typeof dynamicOptions === 'function' ) {
loadableOptions . loader = dynamicOptions
2018-07-24 09:24:40 +00:00
// Support for having first argument being options, eg: dynamic({loader: import('../hello-world')})
} else if ( typeof dynamicOptions === 'object' ) {
loadableOptions = { ... loadableOptions , ... dynamicOptions }
2017-04-19 18:25:06 +00:00
}
2018-07-24 09:24:40 +00:00
// Support for passing options, eg: dynamic(import('../hello-world'), {loading: () => <p>Loading something</p>})
loadableOptions = { ... loadableOptions , ... options }
2017-04-19 18:25:06 +00:00
2018-07-24 09:24:40 +00:00
// Support for `render` when using a mapping, eg: `dynamic({ modules: () => {return {HelloWorld: import('../hello-world')}, render(props, loaded) {} } })
if ( dynamicOptions . render ) {
loadableOptions . render = ( loaded , props ) => dynamicOptions . render ( props , loaded )
2017-04-19 18:25:06 +00:00
}
2018-07-24 09:24:40 +00:00
// Support for `modules` when using a mapping, eg: `dynamic({ modules: () => {return {HelloWorld: import('../hello-world')}, render(props, loaded) {} } })
if ( dynamicOptions . modules ) {
loadableFn = Loadable . Map
const loadModules = { }
const modules = dynamicOptions . modules ( )
Object . keys ( modules ) . forEach ( key => {
const value = modules [ key ]
if ( typeof value . then === 'function' ) {
loadModules [ key ] = ( ) => value . then ( mod => mod . default || mod )
return
2017-04-19 18:25:06 +00:00
}
2018-07-24 09:24:40 +00:00
loadModules [ key ] = value
} )
loadableOptions . loader = loadModules
2017-04-19 18:25:06 +00:00
}
2018-07-24 09:24:40 +00:00
// coming from build/babel/plugins/react-loadable-plugin.js
if ( loadableOptions . loadableGenerated ) {
loadableOptions = { ... loadableOptions , ... loadableOptions . loadableGenerated }
delete loadableOptions . loadableGenerated
}
2017-04-19 18:25:06 +00:00
2018-07-24 09:24:40 +00:00
// support for disabling server side rendering, eg: dynamic(import('../hello-world'), {ssr: false})
if ( typeof loadableOptions . ssr === 'boolean' ) {
if ( ! loadableOptions . ssr ) {
delete loadableOptions . ssr
return noSSR ( loadableFn , loadableOptions )
2017-04-19 18:25:06 +00:00
}
2018-07-24 09:24:40 +00:00
delete loadableOptions . ssr
2017-04-19 18:25:06 +00:00
}
2017-04-28 00:12:20 +00:00
2018-07-24 09:24:40 +00:00
return loadableFn ( loadableOptions )
2017-04-19 18:25:06 +00:00
}