crelte 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/LICENSE.md +41 -0
  2. package/dist/Crelte.d.ts +55 -0
  3. package/dist/Crelte.d.ts.map +1 -0
  4. package/dist/Crelte.js +106 -0
  5. package/dist/CrelteBase.d.ts +16 -0
  6. package/dist/CrelteBase.d.ts.map +1 -0
  7. package/dist/CrelteBase.js +1 -0
  8. package/dist/CrelteRouted.d.ts +50 -0
  9. package/dist/CrelteRouted.d.ts.map +1 -0
  10. package/dist/CrelteRouted.js +88 -0
  11. package/dist/blocks/Blocks.d.ts +35 -0
  12. package/dist/blocks/Blocks.d.ts.map +1 -0
  13. package/dist/blocks/Blocks.js +100 -0
  14. package/dist/blocks/Blocks.svelte +21 -0
  15. package/dist/blocks/Blocks.svelte.d.ts +24 -0
  16. package/dist/blocks/Blocks.svelte.d.ts.map +1 -0
  17. package/dist/blocks/index.d.ts +5 -0
  18. package/dist/blocks/index.d.ts.map +1 -0
  19. package/dist/blocks/index.js +3 -0
  20. package/dist/cookies/ClientCookies.d.ts +9 -0
  21. package/dist/cookies/ClientCookies.d.ts.map +1 -0
  22. package/dist/cookies/ClientCookies.js +22 -0
  23. package/dist/cookies/ServerCookies.d.ts +13 -0
  24. package/dist/cookies/ServerCookies.d.ts.map +1 -0
  25. package/dist/cookies/ServerCookies.js +31 -0
  26. package/dist/cookies/index.d.ts +20 -0
  27. package/dist/cookies/index.d.ts.map +1 -0
  28. package/dist/cookies/index.js +1 -0
  29. package/dist/cookies/utils.d.ts +12 -0
  30. package/dist/cookies/utils.d.ts.map +1 -0
  31. package/dist/cookies/utils.js +32 -0
  32. package/dist/graphql/GraphQl.d.ts +60 -0
  33. package/dist/graphql/GraphQl.d.ts.map +1 -0
  34. package/dist/graphql/GraphQl.js +197 -0
  35. package/dist/graphql/gql.test.d.ts +2 -0
  36. package/dist/graphql/gql.test.d.ts.map +1 -0
  37. package/dist/graphql/gql.test.js +80 -0
  38. package/dist/graphql/index.d.ts +3 -0
  39. package/dist/graphql/index.d.ts.map +1 -0
  40. package/dist/graphql/index.js +2 -0
  41. package/dist/index.d.ts +67 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +83 -0
  44. package/dist/init/client.d.ts +13 -0
  45. package/dist/init/client.d.ts.map +1 -0
  46. package/dist/init/client.js +129 -0
  47. package/dist/init/server.d.ts +38 -0
  48. package/dist/init/server.d.ts.map +1 -0
  49. package/dist/init/server.js +95 -0
  50. package/dist/init/shared.d.ts +29 -0
  51. package/dist/init/shared.d.ts.map +1 -0
  52. package/dist/init/shared.js +154 -0
  53. package/dist/loadData/Globals.d.ts +33 -0
  54. package/dist/loadData/Globals.d.ts.map +1 -0
  55. package/dist/loadData/Globals.js +119 -0
  56. package/dist/loadData/index.d.ts +25 -0
  57. package/dist/loadData/index.d.ts.map +1 -0
  58. package/dist/loadData/index.js +39 -0
  59. package/dist/plugins/Events.d.ts +11 -0
  60. package/dist/plugins/Events.d.ts.map +1 -0
  61. package/dist/plugins/Events.js +29 -0
  62. package/dist/plugins/Plugins.d.ts +12 -0
  63. package/dist/plugins/Plugins.d.ts.map +1 -0
  64. package/dist/plugins/Plugins.js +12 -0
  65. package/dist/plugins/index.d.ts +5 -0
  66. package/dist/plugins/index.d.ts.map +1 -0
  67. package/dist/plugins/index.js +2 -0
  68. package/dist/routing/History.d.ts +22 -0
  69. package/dist/routing/History.d.ts.map +1 -0
  70. package/dist/routing/History.js +36 -0
  71. package/dist/routing/InnerRouter.d.ts +111 -0
  72. package/dist/routing/InnerRouter.d.ts.map +1 -0
  73. package/dist/routing/InnerRouter.js +397 -0
  74. package/dist/routing/PageLoader.d.ts +37 -0
  75. package/dist/routing/PageLoader.d.ts.map +1 -0
  76. package/dist/routing/PageLoader.js +72 -0
  77. package/dist/routing/Route.d.ts +82 -0
  78. package/dist/routing/Route.d.ts.map +1 -0
  79. package/dist/routing/Route.js +134 -0
  80. package/dist/routing/Router.d.ts +162 -0
  81. package/dist/routing/Router.d.ts.map +1 -0
  82. package/dist/routing/Router.js +333 -0
  83. package/dist/routing/Site.d.ts +47 -0
  84. package/dist/routing/Site.d.ts.map +1 -0
  85. package/dist/routing/Site.js +48 -0
  86. package/dist/routing/index.d.ts +5 -0
  87. package/dist/routing/index.d.ts.map +1 -0
  88. package/dist/routing/index.js +4 -0
  89. package/dist/ssr/SsrCache.d.ts +12 -0
  90. package/dist/ssr/SsrCache.d.ts.map +1 -0
  91. package/dist/ssr/SsrCache.js +50 -0
  92. package/dist/ssr/SsrComponents.d.ts +7 -0
  93. package/dist/ssr/SsrComponents.d.ts.map +1 -0
  94. package/dist/ssr/SsrComponents.js +30 -0
  95. package/dist/ssr/index.d.ts +4 -0
  96. package/dist/ssr/index.d.ts.map +1 -0
  97. package/dist/ssr/index.js +3 -0
  98. package/package.json +79 -0
  99. package/src/Crelte.ts +135 -0
  100. package/src/CrelteBase.ts +24 -0
  101. package/src/CrelteRouted.ts +128 -0
  102. package/src/blocks/Blocks.svelte +68 -0
  103. package/src/blocks/Blocks.ts +155 -0
  104. package/src/blocks/index.ts +14 -0
  105. package/src/cookies/ClientCookies.ts +30 -0
  106. package/src/cookies/ServerCookies.ts +42 -0
  107. package/src/cookies/index.ts +24 -0
  108. package/src/cookies/utils.ts +53 -0
  109. package/src/graphql/GraphQl.ts +281 -0
  110. package/src/graphql/gql.test.ts +123 -0
  111. package/src/graphql/index.ts +8 -0
  112. package/src/index.ts +109 -0
  113. package/src/init/client.ts +190 -0
  114. package/src/init/server.ts +177 -0
  115. package/src/init/shared.ts +221 -0
  116. package/src/loadData/Globals.ts +150 -0
  117. package/src/loadData/index.ts +67 -0
  118. package/src/plugins/Events.ts +50 -0
  119. package/src/plugins/Plugins.ts +23 -0
  120. package/src/plugins/index.ts +5 -0
  121. package/src/routing/History.ts +52 -0
  122. package/src/routing/InnerRouter.ts +469 -0
  123. package/src/routing/PageLoader.ts +112 -0
  124. package/src/routing/Route.ts +184 -0
  125. package/src/routing/Router.ts +476 -0
  126. package/src/routing/Site.ts +65 -0
  127. package/src/routing/index.ts +5 -0
  128. package/src/ssr/SsrCache.ts +61 -0
  129. package/src/ssr/SsrComponents.ts +34 -0
  130. package/src/ssr/index.ts +4 -0
@@ -0,0 +1,52 @@
1
+ export default interface History {
2
+ scrollY(): number;
3
+ replaceState(data: any, url?: string): void;
4
+ pushState(data: any, url: string): void;
5
+ open(url: string): void;
6
+ }
7
+
8
+ export class ClientHistory implements History {
9
+ scrollY(): number {
10
+ return window.scrollY;
11
+ }
12
+
13
+ replaceState(data: any, url?: string): void {
14
+ history.replaceState(data, '', url);
15
+ }
16
+
17
+ pushState(data: any, url: string): void {
18
+ history.pushState(data, '', url);
19
+ }
20
+
21
+ open(url: string): void {
22
+ window.location.href = url;
23
+ }
24
+ }
25
+
26
+ export class ServerHistory implements History {
27
+ state: any | null;
28
+ url: string | null;
29
+
30
+ constructor() {
31
+ this.state = null;
32
+ this.url = null;
33
+ }
34
+
35
+ scrollY(): number {
36
+ return 0;
37
+ }
38
+
39
+ replaceState(data: any, url?: string): void {
40
+ this.state = data;
41
+ this.url = url ?? null;
42
+ }
43
+
44
+ pushState(data: any, url: string): void {
45
+ this.state = data;
46
+ this.url = url;
47
+ }
48
+
49
+ open(url: string): void {
50
+ this.url = url;
51
+ }
52
+ }
@@ -0,0 +1,469 @@
1
+ import Route from './Route.js';
2
+ import Site, { SiteFromGraphQl } from './Site.js';
3
+ import History, { ClientHistory, ServerHistory } from './History.js';
4
+
5
+ export type InnerRouterOpts = {
6
+ preloadOnMouseOver: boolean;
7
+ };
8
+
9
+ /**
10
+ * Manages event listeners or functions.
11
+ */
12
+ export default class InnerRouter {
13
+ sites: Site[];
14
+ route: Route | null;
15
+ site: Site;
16
+ history: History;
17
+ preloadOnMouseOver: boolean;
18
+ /**
19
+ * @param changeHistory returns a function you need to call when you are ready to
20
+ update the window history (note do not call this after another onRoute call was made)
21
+ */
22
+ onRoute: (route: Route, site: Site, changeHistory: () => void) => void;
23
+ onPreload: (route: Route, site: Site) => void;
24
+
25
+ private scrollDebounceTimeout: any | null;
26
+
27
+ /**
28
+ * Creates a new Router
29
+ *
30
+ * @param sites - sites needs to be from craft-graphql-sites plugin
31
+ * @param opts - Options for the router
32
+ */
33
+ constructor(sites: SiteFromGraphQl[], opts: InnerRouterOpts) {
34
+ this.sites = sites.map(s => new Site(s));
35
+
36
+ this.route = null;
37
+ this.site = this.defaultSite();
38
+ // @ts-ignore
39
+ this.history = import.meta.env.SSR
40
+ ? new ServerHistory()
41
+ : new ClientHistory();
42
+ this.preloadOnMouseOver = opts.preloadOnMouseOver;
43
+
44
+ // this.preloadListeners = new Listeners();
45
+
46
+ this.onRoute = () => {};
47
+ this.onPreload = () => {};
48
+
49
+ this.scrollDebounceTimeout = null;
50
+ }
51
+
52
+ /**
53
+ * Initializes the router when running on the client.
54
+ */
55
+ initClient() {
56
+ this.listen();
57
+
58
+ // let's first try to load from the state
59
+ const route = this.targetToRoute(window.location.href);
60
+ route._fillFromState(window.history.state);
61
+
62
+ route.origin = 'init';
63
+
64
+ if (route.search.get('x-craft-live-preview')) {
65
+ route.origin = 'live-preview-init';
66
+ }
67
+
68
+ window.history.scrollRestoration = 'manual';
69
+
70
+ this.open(route, false);
71
+ }
72
+
73
+ /**
74
+ * Initializes the router when running on the server.
75
+ */
76
+ initServer() {}
77
+
78
+ /**
79
+ * Get a site and if possible use the accept lang header.
80
+ *
81
+ * @param {(string|null)} [acceptLang=null] Accept Language header.
82
+ * @return {Site}
83
+ */
84
+ siteByAcceptLang(acceptLang: string | null = null): Site {
85
+ if (!acceptLang) return this.defaultSite();
86
+
87
+ const directives = acceptLang
88
+ .split(',')
89
+ .map(d => d.trim())
90
+ .filter(d => !!d);
91
+
92
+ // let's expect that weights are correctly ordered
93
+ const languages = directives
94
+ .map(d => {
95
+ const lang = d.split(';');
96
+ return lang[0].trim();
97
+ })
98
+ .filter(d => !!d);
99
+
100
+ // find a site which matches the language
101
+ // first try to match the full language
102
+ for (const lang of languages) {
103
+ const site = this.sites.find(s => s.language === lang);
104
+ if (site) return site;
105
+ }
106
+
107
+ // if we don't find any language which matches
108
+ // try to match languages without the -
109
+ for (let lang of languages) {
110
+ lang = lang.split('-')[0];
111
+ const site = this.sites.find(s => {
112
+ const sLang = s.language.split('-')[0];
113
+ return sLang === lang;
114
+ });
115
+ if (site) return site;
116
+ }
117
+
118
+ // we did not find a match then just return the first site
119
+ return this.defaultSite();
120
+ }
121
+
122
+ /**
123
+ * Get the default site
124
+ */
125
+ defaultSite(): Site {
126
+ return this.sites[0];
127
+ }
128
+
129
+ /**
130
+ * Tries to get a site by it's id
131
+ */
132
+ siteById(id: number): Site | null {
133
+ return this.sites.find(s => s.id === id) ?? null;
134
+ }
135
+
136
+ /**
137
+ * Resolve a url or Route and convert it to a Route
138
+ *
139
+ * @param target
140
+ * @return Returns null if the url does not match our host (the protocol get's ignored)
141
+ */
142
+ targetToRoute(target: string | URL | Route): Route {
143
+ if (typeof target === 'string') {
144
+ if (target.startsWith('/')) {
145
+ const site = this.site;
146
+ target = new URL(site.uri + target, site.url);
147
+ } else {
148
+ target = new URL(target);
149
+ }
150
+ }
151
+
152
+ if (target instanceof URL) {
153
+ return this.routeFromUrl(target);
154
+ }
155
+
156
+ return target;
157
+ }
158
+
159
+ /**
160
+ * Resolve a url and convert it to a Route
161
+ *
162
+ * @param url
163
+ * @return Returns null if the url does not match our host (the protocol get's ignored)
164
+ */
165
+ routeFromUrl(fullUrl: URL): Route {
166
+ // strip stuff we dont need from url
167
+ const route = new Route(fullUrl, null);
168
+ const url = route.url;
169
+
170
+ let site: Site | null = null;
171
+ // get the site which matches the url the most
172
+ for (const s of this.sites) {
173
+ const siteUri = s.uri;
174
+
175
+ // make sure the start of the url matches
176
+ if (url.host !== s.url.host || !url.pathname.startsWith(siteUri)) {
177
+ continue;
178
+ }
179
+
180
+ // make sure that after the base url a slash follows or nothing
181
+ const uri = url.pathname.substring(siteUri.length);
182
+ if (uri.length > 0 && !uri.startsWith('/')) continue;
183
+
184
+ /// make sure we get the most matched site
185
+ if (site && site.uri.length > siteUri.length) continue;
186
+
187
+ site = s;
188
+ }
189
+
190
+ route.site = site;
191
+
192
+ return route;
193
+ }
194
+
195
+ listen() {
196
+ window.addEventListener('click', async e => {
197
+ // @ts-ignore
198
+ const link = e.target.closest('a');
199
+ const openInNewTab = e.metaKey || e.ctrlKey || e.shiftKey;
200
+ const saveLink = e.altKey;
201
+ if (!link || !link.href || openInNewTab || saveLink) return;
202
+ if (link.target.toLowerCase() === '_blank') return;
203
+ if (!link.href.startsWith('http')) return;
204
+
205
+ e.preventDefault();
206
+
207
+ const route = this.routeFromUrl(link.href);
208
+ if (this.route?.eq(route)) return;
209
+
210
+ route.origin = 'click';
211
+
212
+ this.open(route);
213
+ });
214
+
215
+ if (this.preloadOnMouseOver) {
216
+ let currentMouseOver: any = null;
217
+ window.addEventListener('mouseover', e => {
218
+ // @ts-ignore
219
+ const link = e.target.closest('a');
220
+
221
+ if (currentMouseOver && link === currentMouseOver) return;
222
+ if (link && link.target.toLowerCase() === '_blank') return;
223
+
224
+ if (link && !link.hasAttribute('data-no-preload')) {
225
+ this.preload(link.href);
226
+ }
227
+
228
+ currentMouseOver = link;
229
+ });
230
+ }
231
+
232
+ // store the scrollY position every 200ms
233
+ // we can't do this at the time of the open call since the pop event
234
+ // has already changed to a new history state so we can't update our
235
+ // current/previous state
236
+ // eslint-disable-next-line no-constant-condition
237
+ if (true) {
238
+ window.addEventListener('scroll', () => {
239
+ const current = this.route;
240
+ if (!current) return;
241
+
242
+ // store the scroll position
243
+ current.scrollY = window.scrollY;
244
+
245
+ if (this.scrollDebounceTimeout) return;
246
+
247
+ // this might cause `Attempt to use history.replaceState() more than
248
+ // 100 times per 30 seconds` in safari
249
+ // since we wait a moment we should almost ever be fine
250
+ this.scrollDebounceTimeout = setTimeout(() => {
251
+ if (!this.route || !current.eq(this.route)) return;
252
+
253
+ // use the latest state
254
+ this.history.replaceState(this.route._toState());
255
+
256
+ if (current.origin === 'live-preview-init') {
257
+ sessionStorage.setItem(
258
+ 'live-preview-scroll',
259
+ // use the latest scrollY
260
+ this.route.scrollY + '',
261
+ );
262
+ }
263
+
264
+ this.scrollDebounceTimeout = null;
265
+ }, 280);
266
+ });
267
+ }
268
+
269
+ window.addEventListener('popstate', async e => {
270
+ if (!('route' in e.state)) return;
271
+
272
+ const route = this.targetToRoute(window.location.href);
273
+ route._fillFromState(e.state);
274
+ route.origin = 'pop';
275
+
276
+ // since the pop event replaced our state we can't replace the state
277
+ // for the scrollY in our open call so we just clear the current
278
+ // route since it is now already the new route
279
+ this.route = null;
280
+
281
+ this.open(route, false);
282
+ });
283
+ }
284
+
285
+ /**
286
+ * Open's a route
287
+ *
288
+ * @param route a route object or an url or uri, never input the same route object again
289
+ * @param pushState if true pushed the state to the window.history
290
+ */
291
+ open(target: string | URL | Route, pushState: boolean = true) {
292
+ const route = this.targetToRoute(target);
293
+
294
+ const current = this.route;
295
+ if (current) {
296
+ // if the scrollY will still be updated we clear the timeout
297
+ // since we should have the latest scrollY
298
+ if (this.scrollDebounceTimeout) {
299
+ clearTimeout(this.scrollDebounceTimeout);
300
+ this.scrollDebounceTimeout = null;
301
+ }
302
+
303
+ // store the scroll position
304
+ current.scrollY = this.history.scrollY();
305
+ this.history.replaceState(current._toState());
306
+ }
307
+
308
+ // if the domain of the current site is different than the domain of the
309
+ // new site we need to do a window.location.href call
310
+ if (current && current.url.origin !== route.url.origin) {
311
+ this.history.open(route.url.href);
312
+ return;
313
+ }
314
+
315
+ if (pushState) {
316
+ route.index = (current?.index ?? 0) + 1;
317
+ this.onRoute(route, route.site ?? this.site, () => {
318
+ this.pushState(route);
319
+ });
320
+ } else {
321
+ this.setRoute(route);
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Sets a route
327
+ *
328
+ * Will trigger an onRoute event but will not store any scroll progress
329
+ * or modify the history
330
+ *
331
+ * @param route
332
+ */
333
+ setRoute(route: Route) {
334
+ this.route = route;
335
+ if (route.site) this.site = route.site;
336
+
337
+ this.onRoute(route, this.site, () => {});
338
+ }
339
+
340
+ /**
341
+ * This pushes the state of the route without triggering a currentRoute
342
+ * or currentSiteId change
343
+ *
344
+ * You can use when using pagination for example change the route object
345
+ * (search argument) and then call pushState
346
+ *
347
+ * @param route, never input the same route object again
348
+ */
349
+ pushState(route: Route) {
350
+ const url = route.url;
351
+
352
+ this.history.pushState(
353
+ route._toState(),
354
+ url.pathname + url.search + url.hash,
355
+ );
356
+
357
+ this.route = route;
358
+ if (route.site) this.site = route.site;
359
+ }
360
+
361
+ /**
362
+ * This replaces the state of the route without triggering a currentRoute
363
+ * or currentSiteId change
364
+ *
365
+ * @param route, never input the same route object again
366
+ */
367
+ replaceState(route: Route) {
368
+ const url = route.url;
369
+
370
+ this.history.replaceState(
371
+ route._toState(),
372
+ url.pathname + url.search + url.hash,
373
+ );
374
+
375
+ this.route = route;
376
+ if (route.site) this.site = route.site;
377
+ }
378
+
379
+ /**
380
+ * Preload a url
381
+ *
382
+ * This will only work if the origin of the url matches the current site
383
+ *
384
+ * @param url
385
+ */
386
+ preload(target: string | URL | Route) {
387
+ const route = this.targetToRoute(target);
388
+
389
+ // if the domain of the current site is different than the domain of the
390
+ // new site id does not make sense to preload
391
+ if (this.site.url.origin !== route.url.origin) {
392
+ return;
393
+ }
394
+
395
+ const current = this.route;
396
+ const site = route.site ?? this.site;
397
+
398
+ // if the origin matches, the route will be able to be load
399
+ // so let's preload it
400
+ if (current && current.url.origin === route.url.origin) {
401
+ this.onPreload(route, site);
402
+ }
403
+ }
404
+
405
+ domReady(route: Route) {
406
+ // scroll to target
407
+ let scrollTo:
408
+ | { top: number; behavior: ScrollBehavior }
409
+ | { intoView: HTMLElement; behavior: ScrollBehavior }
410
+ | null = null;
411
+
412
+ // if the route is a live preview init and we have a scrollY stored
413
+ // scroll to that
414
+ if (route.origin === 'live-preview-init') {
415
+ const scrollY = sessionStorage.getItem('live-preview-scroll');
416
+ if (scrollY) {
417
+ scrollTo = {
418
+ top: parseInt(scrollY),
419
+ behavior: 'instant',
420
+ };
421
+ }
422
+ // if we have a hash and the route was not visited
423
+ } else if (
424
+ route.hash &&
425
+ ((route.origin === 'init' && typeof route.scrollY !== 'number') ||
426
+ route.origin === 'click')
427
+ ) {
428
+ const el = document.getElementById(route.hash.substring(1));
429
+ if (el) {
430
+ scrollTo = {
431
+ intoView: el,
432
+ behavior: 'smooth',
433
+ };
434
+ }
435
+ }
436
+
437
+ // restore scroll position
438
+ if (
439
+ !scrollTo &&
440
+ route.origin !== 'click' &&
441
+ typeof route.scrollY === 'number'
442
+ ) {
443
+ scrollTo = {
444
+ top: route.scrollY,
445
+ behavior: 'instant',
446
+ };
447
+ }
448
+
449
+ // scroll to the top if nothing else matches
450
+ if (!scrollTo) {
451
+ scrollTo = {
452
+ top: 0,
453
+ behavior: 'instant',
454
+ };
455
+ }
456
+
457
+ if ('top' in scrollTo) {
458
+ window.scrollTo({
459
+ top: scrollTo.top,
460
+ behavior: scrollTo.behavior,
461
+ });
462
+ } else {
463
+ scrollTo.intoView.scrollIntoView({
464
+ behavior: scrollTo.behavior,
465
+ block: 'start',
466
+ });
467
+ }
468
+ }
469
+ }
@@ -0,0 +1,112 @@
1
+ import Route from './Route.js';
2
+ import Site from './Site.js';
3
+
4
+ export type PageLoaderOptions = {
5
+ debugTiming: boolean;
6
+ };
7
+
8
+ export type LoadResponse = {
9
+ success: boolean;
10
+ data: any;
11
+ };
12
+
13
+ export type LoadFn = (
14
+ route: Route,
15
+ site: Site,
16
+ opts: LoadOptions,
17
+ ) => Promise<any> | any;
18
+
19
+ export type LoadOptions = {
20
+ setProgress: (num: number) => void;
21
+ };
22
+
23
+ /**
24
+ * The PageLoader which is responsible for loading page Data
25
+ */
26
+ export default class PageLoader<More> {
27
+ private debugTiming: boolean;
28
+ private preloadedUrls: Set<string>;
29
+
30
+ private loadingVersion: number;
31
+
32
+ onLoaded: (
33
+ resp: LoadResponse,
34
+ route: Route,
35
+ site: Site,
36
+ more: More,
37
+ ) => void;
38
+ onProgress: (loading: boolean, progress?: number) => void;
39
+ loadFn: LoadFn;
40
+
41
+ /**
42
+ * Creates a new PageLoader
43
+ *
44
+ * @param {Object} options `{debugTiming}`
45
+ */
46
+ constructor(options: PageLoaderOptions) {
47
+ this.debugTiming = options.debugTiming;
48
+ this.preloadedUrls = new Set();
49
+
50
+ this.loadingVersion = 0;
51
+
52
+ this.onLoaded = () => null!;
53
+ this.onProgress = () => null!;
54
+ this.loadFn = () => null!;
55
+ }
56
+
57
+ /**
58
+ * Discard the current page load if one is happening
59
+ */
60
+ discard() {
61
+ this.loadingVersion++;
62
+ this.onProgress(false);
63
+ }
64
+
65
+ async load(route: Route, site: Site, more: More) {
66
+ this.onProgress(true);
67
+
68
+ const version = ++this.loadingVersion;
69
+ const startTime = this.debugTiming ? Date.now() : null;
70
+
71
+ const setProgress = (num: number) => {
72
+ if (this.loadingVersion !== version) return;
73
+
74
+ this.onProgress(true, num);
75
+ };
76
+
77
+ const resp: LoadResponse = { success: false, data: null };
78
+ try {
79
+ resp.data = await this.loadFn(route, site, { setProgress });
80
+ resp.success = true;
81
+ } catch (e) {
82
+ resp.success = false;
83
+ resp.data = e;
84
+ }
85
+
86
+ if (startTime)
87
+ console.log('page load took ' + (Date.now() - startTime) + 'ms');
88
+
89
+ // if were the last that called loading, trigger the loaded event
90
+ if (this.loadingVersion !== version)
91
+ return console.log('route changed quickly, ignoring response');
92
+
93
+ this.onProgress(false);
94
+ this.onLoaded(resp, route, site, more);
95
+ }
96
+
97
+ // you don't need to wait on this call
98
+ async preload(route: Route, site: Site) {
99
+ const url = route.url.origin + route.url.pathname;
100
+ if (this.preloadedUrls.has(url)) return;
101
+
102
+ this.preloadedUrls.add(url);
103
+
104
+ try {
105
+ await this.loadFn(route, site, { setProgress: () => null });
106
+ } catch (e) {
107
+ console.log('preload failed');
108
+ // retry at another time
109
+ this.preloadedUrls.delete(url);
110
+ }
111
+ }
112
+ }