aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/lazy.js
blob: 30a564c206cea17021fa889f0e35107be42b0010 (plain) (blame)
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
// A component whose code is fetched the first time it is shown.
//
// Every static `import` reachable from app.js is downloaded before anything
// renders, whether or not the visitor ever opens it: the Videos app, the video
// player and the search page used to cost a member who only chats. Wrapping a
// component here moves its module out of that first download and into the
// moment it is needed; once loaded it is the component itself, rendered with
// the same props, and every later mount is immediate.
//
// A failed fetch (a network drop on a phone) is not remembered: the next time
// the component is shown it asks again, rather than failing for the session.

import { html, useState, useEffect } from './vendor/htm-preact.js';

const SPINNER = html`<p class="page-message"><span class="spinner"></span></p>`;

function lazy(load, name, placeholder = SPINNER) {
  let Loaded = null;
  let pending = null;

  function Lazy(props) {
    const [, setReady] = useState(Loaded !== null);
    useEffect(() => {
      if (Loaded) return undefined;
      let alive = true;
      if (!pending) {
        pending = load().then(
          (m) => { Loaded = m[name]; },
          (e) => { pending = null; throw e; });
      }
      pending.then(() => { if (alive) setReady(true); }, () => {});
      return () => { alive = false; };
    }, []);
    if (!Loaded) return placeholder;
    return html`<${Loaded} ...${props} />`;
  }
  Lazy.displayName = `Lazy(${name})`;
  return Lazy;
}

export { lazy };