2018-08-30 14:02:18 +02:00
|
|
|
/**
|
|
|
|
@copyright (c) 2017-present James Kyle <me@thejameskyle.com>
|
|
|
|
MIT License
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining
|
|
|
|
a copy of this software and associated documentation files (the
|
|
|
|
"Software"), to deal in the Software without restriction, including
|
|
|
|
without limitation the rights to use, copy, modify, merge, publish,
|
|
|
|
distribute, sublicense, and/or sell copies of the Software, and to
|
|
|
|
permit persons to whom the Software is furnished to do so, subject to
|
|
|
|
the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be
|
|
|
|
included in all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
|
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
|
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
|
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
|
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
2018-08-30 15:09:53 +02:00
|
|
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
|
2018-08-30 14:02:18 +02:00
|
|
|
*/
|
|
|
|
// https://github.com/jamiebuilds/react-loadable/blob/v5.5.0/src/index.js
|
|
|
|
// Modified to be compatible with webpack 4 / Next.js
|
|
|
|
|
|
|
|
import React from 'react'
|
2019-10-18 17:23:06 +02:00
|
|
|
import { useSubscription } from 'use-subscription'
|
2019-03-23 21:00:31 +01:00
|
|
|
import { LoadableContext } from './loadable-context'
|
2018-08-30 14:02:18 +02:00
|
|
|
|
|
|
|
const ALL_INITIALIZERS = []
|
2019-02-17 19:52:00 +01:00
|
|
|
const READY_INITIALIZERS = []
|
2018-09-25 15:27:09 +02:00
|
|
|
let initialized = false
|
2018-08-30 14:02:18 +02:00
|
|
|
|
2019-11-11 04:24:53 +01:00
|
|
|
function load(loader) {
|
2018-08-30 14:02:18 +02:00
|
|
|
let promise = loader()
|
|
|
|
|
|
|
|
let state = {
|
|
|
|
loading: true,
|
|
|
|
loaded: null,
|
2019-11-11 04:24:53 +01:00
|
|
|
error: null,
|
2018-08-30 14:02:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
state.promise = promise
|
2020-05-18 21:24:37 +02:00
|
|
|
.then((loaded) => {
|
2018-08-30 14:02:18 +02:00
|
|
|
state.loading = false
|
|
|
|
state.loaded = loaded
|
|
|
|
return loaded
|
|
|
|
})
|
2020-05-18 21:24:37 +02:00
|
|
|
.catch((err) => {
|
2018-08-30 14:02:18 +02:00
|
|
|
state.loading = false
|
|
|
|
state.error = err
|
|
|
|
throw err
|
|
|
|
})
|
|
|
|
|
|
|
|
return state
|
|
|
|
}
|
|
|
|
|
2019-11-11 04:24:53 +01:00
|
|
|
function loadMap(obj) {
|
2018-08-30 14:02:18 +02:00
|
|
|
let state = {
|
|
|
|
loading: false,
|
|
|
|
loaded: {},
|
2019-11-11 04:24:53 +01:00
|
|
|
error: null,
|
2018-08-30 14:02:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
let promises = []
|
|
|
|
|
|
|
|
try {
|
2020-05-18 21:24:37 +02:00
|
|
|
Object.keys(obj).forEach((key) => {
|
2018-08-30 14:02:18 +02:00
|
|
|
let result = load(obj[key])
|
|
|
|
|
|
|
|
if (!result.loading) {
|
|
|
|
state.loaded[key] = result.loaded
|
|
|
|
state.error = result.error
|
|
|
|
} else {
|
|
|
|
state.loading = true
|
|
|
|
}
|
|
|
|
|
|
|
|
promises.push(result.promise)
|
|
|
|
|
|
|
|
result.promise
|
2020-05-18 21:24:37 +02:00
|
|
|
.then((res) => {
|
2018-08-30 14:02:18 +02:00
|
|
|
state.loaded[key] = res
|
|
|
|
})
|
2020-05-18 21:24:37 +02:00
|
|
|
.catch((err) => {
|
2018-08-30 14:02:18 +02:00
|
|
|
state.error = err
|
|
|
|
})
|
|
|
|
})
|
|
|
|
} catch (err) {
|
|
|
|
state.error = err
|
|
|
|
}
|
|
|
|
|
|
|
|
state.promise = Promise.all(promises)
|
2020-05-18 21:24:37 +02:00
|
|
|
.then((res) => {
|
2018-08-30 14:02:18 +02:00
|
|
|
state.loading = false
|
|
|
|
return res
|
|
|
|
})
|
2020-05-18 21:24:37 +02:00
|
|
|
.catch((err) => {
|
2018-08-30 14:02:18 +02:00
|
|
|
state.loading = false
|
|
|
|
throw err
|
|
|
|
})
|
|
|
|
|
|
|
|
return state
|
|
|
|
}
|
|
|
|
|
2019-11-11 04:24:53 +01:00
|
|
|
function resolve(obj) {
|
2018-08-30 14:02:18 +02:00
|
|
|
return obj && obj.__esModule ? obj.default : obj
|
|
|
|
}
|
|
|
|
|
2019-11-11 04:24:53 +01:00
|
|
|
function render(loaded, props) {
|
2018-08-30 14:02:18 +02:00
|
|
|
return React.createElement(resolve(loaded), props)
|
|
|
|
}
|
|
|
|
|
2019-11-11 04:24:53 +01:00
|
|
|
function createLoadableComponent(loadFn, options) {
|
2018-08-30 14:02:18 +02:00
|
|
|
let opts = Object.assign(
|
|
|
|
{
|
|
|
|
loader: null,
|
|
|
|
loading: null,
|
|
|
|
delay: 200,
|
|
|
|
timeout: null,
|
|
|
|
render: render,
|
|
|
|
webpack: null,
|
2019-11-11 04:24:53 +01:00
|
|
|
modules: null,
|
2018-08-30 14:02:18 +02:00
|
|
|
},
|
|
|
|
options
|
|
|
|
)
|
|
|
|
|
2019-10-18 17:23:06 +02:00
|
|
|
let subscription = null
|
2018-08-30 14:02:18 +02:00
|
|
|
|
2019-11-11 04:24:53 +01:00
|
|
|
function init() {
|
2019-10-18 17:23:06 +02:00
|
|
|
if (!subscription) {
|
|
|
|
const sub = new LoadableSubscription(loadFn, opts)
|
|
|
|
subscription = {
|
|
|
|
getCurrentValue: sub.getCurrentValue.bind(sub),
|
|
|
|
subscribe: sub.subscribe.bind(sub),
|
|
|
|
retry: sub.retry.bind(sub),
|
2019-11-11 04:24:53 +01:00
|
|
|
promise: sub.promise.bind(sub),
|
2019-10-18 17:23:06 +02:00
|
|
|
}
|
2018-08-30 14:02:18 +02:00
|
|
|
}
|
2019-10-18 17:23:06 +02:00
|
|
|
return subscription.promise()
|
2018-08-30 14:02:18 +02:00
|
|
|
}
|
|
|
|
|
2018-09-25 15:27:09 +02:00
|
|
|
// Server only
|
|
|
|
if (typeof window === 'undefined') {
|
|
|
|
ALL_INITIALIZERS.push(init)
|
|
|
|
}
|
2018-08-30 14:02:18 +02:00
|
|
|
|
2018-09-25 15:27:09 +02:00
|
|
|
// Client only
|
2019-05-29 13:57:26 +02:00
|
|
|
if (
|
|
|
|
!initialized &&
|
|
|
|
typeof window !== 'undefined' &&
|
|
|
|
typeof opts.webpack === 'function'
|
|
|
|
) {
|
2018-09-25 15:27:09 +02:00
|
|
|
const moduleIds = opts.webpack()
|
2020-05-18 21:24:37 +02:00
|
|
|
READY_INITIALIZERS.push((ids) => {
|
2019-02-17 19:52:00 +01:00
|
|
|
for (const moduleId of moduleIds) {
|
|
|
|
if (ids.indexOf(moduleId) !== -1) {
|
|
|
|
return init()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2018-08-30 14:02:18 +02:00
|
|
|
}
|
|
|
|
|
2019-10-18 17:23:06 +02:00
|
|
|
const LoadableComponent = (props, ref) => {
|
|
|
|
init()
|
|
|
|
|
|
|
|
const context = React.useContext(LoadableContext)
|
|
|
|
const state = useSubscription(subscription)
|
|
|
|
|
2020-04-02 09:22:04 +02:00
|
|
|
React.useImperativeHandle(
|
|
|
|
ref,
|
|
|
|
() => ({
|
|
|
|
retry: subscription.retry,
|
|
|
|
}),
|
|
|
|
[]
|
|
|
|
)
|
2018-08-30 14:02:18 +02:00
|
|
|
|
2019-10-18 17:23:06 +02:00
|
|
|
if (context && Array.isArray(opts.modules)) {
|
2020-05-18 21:24:37 +02:00
|
|
|
opts.modules.forEach((moduleName) => {
|
2019-10-18 17:23:06 +02:00
|
|
|
context(moduleName)
|
|
|
|
})
|
2018-08-30 14:02:18 +02:00
|
|
|
}
|
|
|
|
|
2020-04-02 09:22:04 +02:00
|
|
|
return React.useMemo(() => {
|
|
|
|
if (state.loading || state.error) {
|
|
|
|
return React.createElement(opts.loading, {
|
|
|
|
isLoading: state.loading,
|
|
|
|
pastDelay: state.pastDelay,
|
|
|
|
timedOut: state.timedOut,
|
|
|
|
error: state.error,
|
|
|
|
retry: subscription.retry,
|
|
|
|
})
|
|
|
|
} else if (state.loaded) {
|
|
|
|
return opts.render(state.loaded, props)
|
|
|
|
} else {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}, [props, state])
|
2019-10-18 17:23:06 +02:00
|
|
|
}
|
2018-08-30 14:02:18 +02:00
|
|
|
|
2019-10-18 17:23:06 +02:00
|
|
|
LoadableComponent.preload = () => init()
|
|
|
|
LoadableComponent.displayName = 'LoadableComponent'
|
2018-08-30 14:02:18 +02:00
|
|
|
|
2019-10-18 17:23:06 +02:00
|
|
|
return React.forwardRef(LoadableComponent)
|
|
|
|
}
|
2018-08-30 14:02:18 +02:00
|
|
|
|
2019-10-18 17:23:06 +02:00
|
|
|
class LoadableSubscription {
|
2019-11-11 04:24:53 +01:00
|
|
|
constructor(loadFn, opts) {
|
2019-10-18 17:23:06 +02:00
|
|
|
this._loadFn = loadFn
|
|
|
|
this._opts = opts
|
|
|
|
this._callbacks = new Set()
|
|
|
|
this._delay = null
|
|
|
|
this._timeout = null
|
|
|
|
|
|
|
|
this.retry()
|
|
|
|
}
|
|
|
|
|
2019-11-11 04:24:53 +01:00
|
|
|
promise() {
|
2019-10-18 17:23:06 +02:00
|
|
|
return this._res.promise
|
|
|
|
}
|
|
|
|
|
2019-11-11 04:24:53 +01:00
|
|
|
retry() {
|
2019-10-18 17:23:06 +02:00
|
|
|
this._clearTimeouts()
|
|
|
|
this._res = this._loadFn(this._opts.loader)
|
|
|
|
|
|
|
|
this._state = {
|
|
|
|
pastDelay: false,
|
2019-11-11 04:24:53 +01:00
|
|
|
timedOut: false,
|
2019-10-18 17:23:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const { _res: res, _opts: opts } = this
|
|
|
|
|
|
|
|
if (res.loading) {
|
2018-08-30 14:02:18 +02:00
|
|
|
if (typeof opts.delay === 'number') {
|
|
|
|
if (opts.delay === 0) {
|
2019-10-18 17:23:06 +02:00
|
|
|
this._state.pastDelay = true
|
2018-08-30 14:02:18 +02:00
|
|
|
} else {
|
|
|
|
this._delay = setTimeout(() => {
|
2019-10-18 17:23:06 +02:00
|
|
|
this._update({
|
2019-11-11 04:24:53 +01:00
|
|
|
pastDelay: true,
|
2019-10-18 17:23:06 +02:00
|
|
|
})
|
2018-08-30 14:02:18 +02:00
|
|
|
}, opts.delay)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof opts.timeout === 'number') {
|
|
|
|
this._timeout = setTimeout(() => {
|
2019-10-18 17:23:06 +02:00
|
|
|
this._update({ timedOut: true })
|
2018-08-30 14:02:18 +02:00
|
|
|
}, opts.timeout)
|
|
|
|
}
|
2019-10-18 17:23:06 +02:00
|
|
|
}
|
2018-08-30 14:02:18 +02:00
|
|
|
|
2019-10-18 17:23:06 +02:00
|
|
|
this._res.promise
|
|
|
|
.then(() => {
|
2020-04-02 09:22:04 +02:00
|
|
|
this._update({})
|
2018-08-30 14:02:18 +02:00
|
|
|
this._clearTimeouts()
|
2019-10-18 17:23:06 +02:00
|
|
|
})
|
|
|
|
// eslint-disable-next-line handle-callback-err
|
2020-05-18 21:24:37 +02:00
|
|
|
.catch((err) => {
|
2020-04-02 09:22:04 +02:00
|
|
|
this._update({})
|
2019-10-18 17:23:06 +02:00
|
|
|
this._clearTimeouts()
|
|
|
|
})
|
|
|
|
this._update({})
|
|
|
|
}
|
2018-08-30 14:02:18 +02:00
|
|
|
|
2019-11-11 04:24:53 +01:00
|
|
|
_update(partial) {
|
2019-10-18 17:23:06 +02:00
|
|
|
this._state = {
|
|
|
|
...this._state,
|
2020-04-02 09:22:04 +02:00
|
|
|
error: this._res.error,
|
|
|
|
loaded: this._res.loaded,
|
|
|
|
loading: this._res.loading,
|
2019-11-11 04:24:53 +01:00
|
|
|
...partial,
|
2018-08-30 14:02:18 +02:00
|
|
|
}
|
2020-05-18 21:24:37 +02:00
|
|
|
this._callbacks.forEach((callback) => callback())
|
2019-10-18 17:23:06 +02:00
|
|
|
}
|
2018-08-30 14:02:18 +02:00
|
|
|
|
2019-11-11 04:24:53 +01:00
|
|
|
_clearTimeouts() {
|
2019-10-18 17:23:06 +02:00
|
|
|
clearTimeout(this._delay)
|
|
|
|
clearTimeout(this._timeout)
|
|
|
|
}
|
2018-08-30 14:02:18 +02:00
|
|
|
|
2019-11-11 04:24:53 +01:00
|
|
|
getCurrentValue() {
|
2020-04-02 09:22:04 +02:00
|
|
|
return this._state
|
2019-10-18 17:23:06 +02:00
|
|
|
}
|
2018-08-30 14:02:18 +02:00
|
|
|
|
2019-11-11 04:24:53 +01:00
|
|
|
subscribe(callback) {
|
2019-10-18 17:23:06 +02:00
|
|
|
this._callbacks.add(callback)
|
|
|
|
return () => {
|
|
|
|
this._callbacks.delete(callback)
|
2018-08-30 14:02:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-11 04:24:53 +01:00
|
|
|
function Loadable(opts) {
|
2018-08-30 14:02:18 +02:00
|
|
|
return createLoadableComponent(load, opts)
|
|
|
|
}
|
|
|
|
|
2019-11-11 04:24:53 +01:00
|
|
|
function LoadableMap(opts) {
|
2018-08-30 14:02:18 +02:00
|
|
|
if (typeof opts.render !== 'function') {
|
|
|
|
throw new Error('LoadableMap requires a `render(loaded, props)` function')
|
|
|
|
}
|
|
|
|
|
|
|
|
return createLoadableComponent(loadMap, opts)
|
|
|
|
}
|
|
|
|
|
|
|
|
Loadable.Map = LoadableMap
|
|
|
|
|
2019-11-11 04:24:53 +01:00
|
|
|
function flushInitializers(initializers, ids) {
|
2018-08-30 14:02:18 +02:00
|
|
|
let promises = []
|
|
|
|
|
|
|
|
while (initializers.length) {
|
|
|
|
let init = initializers.pop()
|
2019-02-17 19:52:00 +01:00
|
|
|
promises.push(init(ids))
|
2018-08-30 14:02:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.all(promises).then(() => {
|
|
|
|
if (initializers.length) {
|
2019-02-17 19:52:00 +01:00
|
|
|
return flushInitializers(initializers, ids)
|
2018-08-30 14:02:18 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
Loadable.preloadAll = () => {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
flushInitializers(ALL_INITIALIZERS).then(resolve, reject)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-07-16 14:52:01 +02:00
|
|
|
Loadable.preloadReady = (ids = []) => {
|
2020-05-18 21:24:37 +02:00
|
|
|
return new Promise((resolve) => {
|
2019-02-17 19:52:00 +01:00
|
|
|
const res = () => {
|
|
|
|
initialized = true
|
|
|
|
return resolve()
|
|
|
|
}
|
2018-08-30 14:02:18 +02:00
|
|
|
// We always will resolve, errors should be handled within loading UIs.
|
2019-02-17 19:52:00 +01:00
|
|
|
flushInitializers(READY_INITIALIZERS, ids).then(res, res)
|
2018-08-30 14:02:18 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-07-16 14:52:01 +02:00
|
|
|
if (typeof window !== 'undefined') {
|
|
|
|
window.__NEXT_PRELOADREADY = Loadable.preloadReady
|
|
|
|
}
|
|
|
|
|
2018-12-13 01:00:46 +01:00
|
|
|
export default Loadable
|