-
Notifications
You must be signed in to change notification settings - Fork 13
/
Loadable.svelte
170 lines (139 loc) · 3.59 KB
/
Loadable.svelte
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
<script context="module">
export const ALL_LOADERS = new Map()
export const LOADED = new Map()
const STATES = Object.freeze({
INITIALIZED: 0,
LOADING: 1,
SUCCESS: 2,
ERROR: 3,
TIMEOUT: 4,
})
function findByResolved(resolved) {
for (const [loader, r] of ALL_LOADERS) {
if (r === resolved) return loader
}
return null
}
export function register(loadable) {
const resolved = loadable.resolve()
const loader = findByResolved(resolved)
if (loader) return loader
ALL_LOADERS.set(loadable.loader, resolved)
return loadable.loader
}
export function preloadAll() {
return Promise.all(
Array.from(ALL_LOADERS.keys())
.filter((loader) => !LOADED.has(loader))
.map((loader) => loadComponent(loader)),
).then(() => {
/** If new loaders have been registered by loaded components, load them next. */
if (ALL_LOADERS.size > LOADED.size) {
return preloadAll()
}
})
}
async function loadComponent(loader, unloader) {
const componentModule = await loader()
const component = componentModule.default || componentModule
if (!unloader) {
LOADED.set(loader, component)
}
return component
}
</script>
<script>
import { onMount, getContext, createEventDispatcher } from 'svelte'
export let delay = 200
export let timeout = null
export let loader = null
export let unloader = false
export let component = null
export let error = null
const slots = $$props.$$slots
let load_timer = null
let timeout_timer = null
let state = STATES.INITIALIZED
let componentProps
let mounted = false
$: {
// eslint-disable-next-line no-shadow
const { delay, timeout, loader, component, error, ...rest } = $$props
componentProps = rest
}
const dispatch = createEventDispatcher()
const capture = getContext('svelte-loadable-capture')
if (typeof capture === 'function' && ALL_LOADERS.has(loader)) {
capture(loader)
}
function clearTimers() {
clearTimeout(load_timer)
clearTimeout(timeout_timer)
}
async function load() {
clearTimers()
if (typeof loader !== 'function') {
return
}
error = null
component = null
if (delay > 0) {
state = STATES.INITIALIZED
load_timer = setTimeout(() => {
state = STATES.LOADING
}, parseFloat(delay))
} else {
state = STATES.LOADING
}
if (timeout) {
timeout_timer = setTimeout(() => {
state = STATES.TIMEOUT
}, parseFloat(timeout))
}
try {
component = await loadComponent(loader, unloader)
state = STATES.SUCCESS
} catch (e) {
state = STATES.ERROR
error = e
if (slots == null || slots.error == null) {
throw e
}
}
clearTimers()
}
if (LOADED.has(loader)) {
state = STATES.SUCCESS
component = LOADED.get(loader)
} else {
onMount(() => {
mounted = true
load().then(() => {
if (mounted) {
dispatch('load')
}
})
return () => {
mounted = false
if (typeof unloader === 'function') {
unloader()
}
}
})
}
</script>
{#if state === STATES.ERROR}
<slot name="error" {error} />
{:else if state === STATES.TIMEOUT}
<slot name="timeout" />
{:else if state === STATES.LOADING}
<slot name="loading" />
{:else if state === STATES.SUCCESS}
{#if slots && slots.success}
<slot name="success" {component} />
{:else if slots && slots.default}
<slot {component} retry={load} />
{:else}
<svelte:component this={component} {...componentProps} />
{/if}
{/if}