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'
import Loadable from 'react-loadable'
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 ) {
let LoadableComponent
2017-06-16 13:19:34 +00:00
2018-07-24 09:24:40 +00:00
// 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 ) {
LoadableComponent = LoadableInitializer ( loadableOptions )
}
2017-06-16 13:19:34 +00:00
2018-07-24 09:24:40 +00:00
return class NoSSR extends React . Component < any , { mounted : boolean } > {
state = { mounted : false }
2017-06-16 13:19:34 +00:00
2017-04-17 20:15:50 +00:00
componentDidMount ( ) {
2018-07-24 09:24:40 +00:00
this . setState ( { mounted : true } )
2018-05-11 15:07:06 +00:00
}
2017-04-17 20:15:50 +00:00
render ( ) {
2018-07-24 09:24:40 +00:00
const { mounted } = this . state
2017-04-17 20:15:50 +00:00
2018-07-24 09:24:40 +00:00
if ( mounted && LoadableComponent ) {
return < LoadableComponent { ... this . props } / >
}
2017-06-16 13:19:34 +00:00
2018-07-24 09:24:40 +00:00
// Run loading component on the server and when mounting, when mounted we load the LoadableComponent
return < loadableOptions . loading error = { null } isLoading pastDelay = { false } timedOut = { false } / >
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 ) {
return < p > loading ... < / p >
}
if ( error ) {
return < p > { error . message } < br / > { error . stack } < / p >
}
}
2017-04-19 18:25:06 +00:00
2018-07-24 09:24:40 +00:00
return < p > loading ... < / p >
}
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'))
if ( typeof dynamicOptions . then === '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 }
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
}