import React from 'react' import { getDisplayName } from './utils' let currentChunks = new Set() export default function dynamicComponent (p, o) { let promise let options if (p instanceof SameLoopPromise) { promise = p options = o || {} } else { // Now we are trying to use the modules and render fields in options to load modules. if (!p.modules || !p.render) { const errorMessage = 'Options to `next/dynamic` should contains `modules` and `render` fields.' throw new Error(errorMessage) } if (o) { const errorMessage = 'Include options in the first argument which contains `modules` and `render` fields.' throw new Error(errorMessage) } options = p } return class DynamicComponent extends React.Component { constructor (...args) { super(...args) this.LoadingComponent = options.loading ? options.loading : () => (

loading...

) this.ssr = options.ssr === false ? options.ssr : true this.state = { AsyncComponent: null, asyncElement: null } this.isServer = typeof window === 'undefined' // This flag is used to load the bundle again, if needed this.loadBundleAgain = null // This flag keeps track of the whether we are loading a bundle or not. this.loadingBundle = false if (this.ssr) { this.load() } } load () { if (promise) { this.loadComponent() } else { this.loadBundle(this.props) } } loadComponent () { promise.then((AsyncComponent) => { // Set a readable displayName for the wrapper component const asyncCompName = getDisplayName(AsyncComponent) if (asyncCompName) { DynamicComponent.displayName = `DynamicComponent for ${asyncCompName}` } if (this.mounted) { this.setState({ AsyncComponent }) } else { if (this.isServer) { registerChunk(AsyncComponent.__webpackChunkName) } this.state.AsyncComponent = AsyncComponent } }) } loadBundle (props) { this.loadBundleAgain = null this.loadingBundle = true // Run this for prop changes as well. const modulePromiseMap = options.modules(props) const moduleNames = Object.keys(modulePromiseMap) let remainingPromises = moduleNames.length const moduleMap = {} const renderModules = () => { if (this.loadBundleAgain) { this.loadBundle(this.loadBundleAgain) return } this.loadingBundle = false DynamicComponent.displayName = 'DynamicBundle' const asyncElement = options.render(props, moduleMap) if (this.mounted) { this.setState({ asyncElement }) } else { this.state.asyncElement = asyncElement } } const loadModule = (name) => { const promise = modulePromiseMap[name] promise.then((Component) => { if (this.isServer) { registerChunk(Component.__webpackChunkName) } moduleMap[name] = Component remainingPromises-- if (remainingPromises === 0) { renderModules() } }) } moduleNames.forEach(loadModule) } componentDidMount () { this.mounted = true if (!this.ssr) { this.load() } } componentWillReceiveProps (nextProps) { if (promise) return this.setState({ asyncElement: null }) if (this.loadingBundle) { this.loadBundleAgain = nextProps return } this.loadBundle(nextProps) } render () { const { AsyncComponent, asyncElement } = this.state const { LoadingComponent } = this if (asyncElement) return asyncElement if (AsyncComponent) return () return () } } } export function registerChunk (chunk) { currentChunks.add(chunk) } export function flushChunks () { const chunks = Array.from(currentChunks) currentChunks.clear() return chunks } export class SameLoopPromise { constructor (cb) { this.onResultCallbacks = [] this.onErrorCallbacks = [] this.cb = cb } setResult (result) { this.gotResult = true this.result = result this.onResultCallbacks.forEach((cb) => cb(result)) this.onResultCallbacks = [] } setError (error) { this.gotError = true this.error = error this.onErrorCallbacks.forEach((cb) => cb(error)) this.onErrorCallbacks = [] } then (onResult, onError) { this.runIfNeeded() const promise = new SameLoopPromise() const handleError = () => { if (onError) { promise.setResult(onError(this.error)) } else { promise.setError(this.error) } } const handleResult = () => { promise.setResult(onResult(this.result)) } if (this.gotResult) { handleResult() return promise } if (this.gotError) { handleError() return promise } this.onResultCallbacks.push(handleResult) this.onErrorCallbacks.push(handleError) return promise } catch (onError) { this.runIfNeeded() const promise = new SameLoopPromise() const handleError = () => { promise.setResult(onError(this.error)) } const handleResult = () => { promise.setResult(this.result) } if (this.gotResult) { handleResult() return promise } if (this.gotError) { handleError() return promise } this.onErrorCallbacks.push(handleError) this.onResultCallbacks.push(handleResult) return promise } runIfNeeded () { if (!this.cb) return if (this.ran) return this.ran = true this.cb( (result) => this.setResult(result), (error) => this.setError(error) ) } }