@loadbare/app 0.5.4 → 0.5.5

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.
@@ -229,6 +229,7 @@ class LbHub extends HTMLElement {
229
229
  if (!(template instanceof HTMLTemplateElement)) {
230
230
  console.error(`lb-hub: no page host for '${page}'`);
231
231
  this.reportUnknownPage(page);
232
+ this.reveal();
232
233
  return;
233
234
  }
234
235
  this.page = page;
@@ -236,6 +237,7 @@ class LbHub extends HTMLElement {
236
237
  // template is already in the document, so holding it back behind a
237
238
  // network round trip only leaves <main> empty for no reason.
238
239
  this.main.replaceChildren(template.content.cloneNode(true));
240
+ this.reveal();
239
241
  let data = {};
240
242
  try {
241
243
  data = await fetchData(`${LB_DATA_ENDPOINT}?page=${encodeURIComponent(page)}`);
@@ -245,6 +247,15 @@ class LbHub extends HTMLElement {
245
247
  }
246
248
  applyData(this, data);
247
249
  }
250
+ /**
251
+ * An app that ships `<body hidden>` (see docs/reference/chrome.md) is
252
+ * asking to stay invisible until there is something coherent to show,
253
+ * rather than flash the chrome before `<main>` has real content. This is
254
+ * a no-op for an app that doesn't use that convention.
255
+ */
256
+ reveal() {
257
+ document.body.hidden = false;
258
+ }
248
259
  /**
249
260
  * A path that resolves to no page host is discovered client-side, after a
250
261
  * successful 200 — every route gets the same document, so there is no
@@ -20,8 +20,9 @@ Here is a minimal but fully complaint chrome for a typical app:
20
20
  <title>Membership Roster</title>
21
21
  <script src="/client.js" defer></script>
22
22
  <link rel="stylesheet" href="/app.css" />
23
+ <noscript><style>body[hidden] { display: block; }</style></noscript>
23
24
  </head>
24
- <body>
25
+ <body hidden>
25
26
  <lb-hub>
26
27
  <header><h1>Membership Roster</h1></header>
27
28
  <nav>
@@ -58,6 +59,7 @@ Everything else is optional:
58
59
  | `<a lb-nav-link>` | Navigation between pages |
59
60
  | `<dialog lb-unknown-page>` | A message when a URL matches no page |
60
61
  | Custom elements | The chrome, decomposed into widget files |
62
+ | `<body hidden>` + a `<noscript>` fallback | Avoids a first-load blink — see below |
61
63
  | Any other HTML | Header, footer, skip links, meta tags, etc. |
62
64
 
63
65
  ## Rules for writing chrome
@@ -72,4 +74,34 @@ landing page is the one named `index.page.html`. An anchor without
72
74
  `lb-nav-link` is left alone and behaves like any other link.
73
75
 
74
76
  The `lb-unknown-page` attribute, if used, must appear on a `<dialog>`.
75
- Inside it, `lb-cell="page"` shows the name of the page that was asked for.
77
+ Inside it, `lb-cell="page"` shows the name of the page that was asked for.
78
+
79
+ ## Preventing the first-load blink
80
+
81
+ `client.js` loads with `defer`, so the browser can — and typically does —
82
+ paint the document before the script has run. Without help, that means a
83
+ visible flash: the chrome appears first, then `<main>`'s real content pops in
84
+ a moment later and shifts everything around it.
85
+
86
+ `<body hidden>` avoids this by hiding the whole document, not just `<main>`,
87
+ until the hub has something to show. `<lb-hub>` un-hides `<body>` itself the
88
+ first time `navigate()` finishes, so the chrome and the first page's content
89
+ always appear together, already in their final layout — there is no
90
+ intermediate state to flash.
91
+
92
+ Hiding `<main>` alone doesn't work: an empty `<main>` already renders at zero
93
+ height, so hiding it changes nothing visible. The pop-in comes from the
94
+ chrome being shown *before* `<main>` has real content, not from `<main>`
95
+ being visibly empty — so it's the whole document that needs to wait, not
96
+ just the piece that was empty.
97
+
98
+ The `<noscript>` block is the escape hatch for a visitor with JavaScript
99
+ disabled. `<lb-hub>` is what removes `hidden` from `<body>`, so a browser
100
+ that never runs `client.js` would otherwise be stuck looking at a
101
+ permanently blank page. `<noscript>` content is only rendered when scripting
102
+ is off, so the fallback rule only ever applies in exactly that case — it
103
+ never runs, and never races with the hub, on a normal visit.
104
+
105
+ Both are optional. An app that doesn't mind the blink, or has no chrome
106
+ complex enough for it to be noticeable, can leave `<body>` unhidden and skip
107
+ the `<noscript>` block entirely.
@@ -26,8 +26,9 @@ At minimum, the chrome must contain:
26
26
  <meta charset="utf-8" />
27
27
  <title>@Loadbare/app Tutorials</title>
28
28
  <script src="/client.js" defer></script>
29
+ <noscript><style>body[hidden] { display: block; }</style></noscript>
29
30
  </head>
30
- <body>
31
+ <body hidden>
31
32
  <lb-hub>
32
33
  <main></main>
33
34
  </lb-hub>
@@ -41,6 +42,14 @@ the custom elements used anywhere in `src/`. In this basic starting point,
41
42
  we have `<lb-hub>` as the only custom element, so `client.js` will just
42
43
  contain the Javascript class `LbHub`.
43
44
 
45
+ `<body hidden>` and the `<noscript>` rule next to it are optional, not part
46
+ of the minimum above. `<lb-hub>` removes `hidden` from `<body>` as soon as it
47
+ has something to show, so the document stays invisible for a moment rather
48
+ than flashing an empty page and then popping in real content — see
49
+ [chrome.html](../reference/chrome.md#preventing-the-first-load-blink) for
50
+ why. Every chrome in the rest of these tutorials carries this forward; drop
51
+ it if you'd rather not have it.
52
+
44
53
  ## The dev script
45
54
 
46
55
  Add a command to package.json that builds the app.
@@ -46,8 +46,9 @@ display something to the user if they type a URL that has no matching page.
46
46
  <meta charset="utf-8" />
47
47
  <title>Pages and Navigation</title>
48
48
  <script src="/client.js" defer></script>
49
+ <noscript><style>body[hidden] { display: block; }</style></noscript>
49
50
  </head>
50
- <body>
51
+ <body hidden>
51
52
  <lb-hub>
52
53
  <nav>
53
54
  <!-- a bare link to "/" goes to page "index" -->
@@ -60,8 +60,9 @@ Add the stylesheet link to the chrome:
60
60
  <script src="/client.js" defer></script>
61
61
  <!-- Add a standard stylesheet link -->
62
62
  <link rel="stylesheet" href="/app.css" />
63
+ <noscript><style>body[hidden] { display: block; }</style></noscript>
63
64
  </head>
64
- <body>
65
+ <body hidden>
65
66
  <lb-hub>
66
67
  <nav>
67
68
  <a href="/" lb-nav-link>Home</a>
@@ -27,8 +27,9 @@ element, not yet defined:
27
27
  <title>Pages and Navigation</title>
28
28
  <script src="/client.js" defer></script>
29
29
  <link rel="stylesheet" href="/app.css" />
30
+ <noscript><style>body[hidden] { display: block; }</style></noscript>
30
31
  </head>
31
- <body>
32
+ <body hidden>
32
33
  <lb-hub>
33
34
  <!-- replace this from the previous tutorial...
34
35
  <nav>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@loadbare/app",
3
3
  "description": "High performance web app framework for server-bound applications",
4
- "version": "0.5.4",
4
+ "version": "0.5.5",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",