@loadbare/app 0.4.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 (171) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +95 -0
  3. package/dist/build/assemble.d.ts +31 -0
  4. package/dist/build/assemble.d.ts.map +1 -0
  5. package/dist/build/assemble.js +53 -0
  6. package/dist/build/cli.d.ts +30 -0
  7. package/dist/build/cli.d.ts.map +1 -0
  8. package/dist/build/cli.js +107 -0
  9. package/dist/build/elements.d.ts +61 -0
  10. package/dist/build/elements.d.ts.map +1 -0
  11. package/dist/build/elements.js +158 -0
  12. package/dist/build/expand.d.ts +45 -0
  13. package/dist/build/expand.d.ts.map +1 -0
  14. package/dist/build/expand.js +386 -0
  15. package/dist/build/format.d.ts +28 -0
  16. package/dist/build/format.d.ts.map +1 -0
  17. package/dist/build/format.js +42 -0
  18. package/dist/build/locations.d.ts +87 -0
  19. package/dist/build/locations.d.ts.map +1 -0
  20. package/dist/build/locations.js +173 -0
  21. package/dist/build/package-root.d.ts +9 -0
  22. package/dist/build/package-root.d.ts.map +1 -0
  23. package/dist/build/package-root.js +24 -0
  24. package/dist/build/pages.d.ts +25 -0
  25. package/dist/build/pages.d.ts.map +1 -0
  26. package/dist/build/pages.js +54 -0
  27. package/dist/build/styles.d.ts +13 -0
  28. package/dist/build/styles.d.ts.map +1 -0
  29. package/dist/build/styles.js +18 -0
  30. package/dist/client.js +522 -0
  31. package/dist/core/lb-constants.d.ts +23 -0
  32. package/dist/core/lb-constants.d.ts.map +1 -0
  33. package/dist/core/lb-constants.js +95 -0
  34. package/dist/core/lb-types.d.ts +88 -0
  35. package/dist/core/lb-types.d.ts.map +1 -0
  36. package/dist/core/lb-types.js +5 -0
  37. package/dist/demo-static/src/widgets/app-box.d.ts +15 -0
  38. package/dist/demo-static/src/widgets/app-box.d.ts.map +1 -0
  39. package/dist/demo-static/src/widgets/app-box.js +19 -0
  40. package/dist/hub/lb-apply.d.ts +13 -0
  41. package/dist/hub/lb-apply.d.ts.map +1 -0
  42. package/dist/hub/lb-apply.js +77 -0
  43. package/dist/hub/lb-hub.d.ts +2 -0
  44. package/dist/hub/lb-hub.d.ts.map +1 -0
  45. package/dist/hub/lb-hub.js +242 -0
  46. package/dist/hub/lb-rows.d.ts +18 -0
  47. package/dist/hub/lb-rows.d.ts.map +1 -0
  48. package/dist/hub/lb-rows.js +106 -0
  49. package/dist/server/lb-express.d.ts +28 -0
  50. package/dist/server/lb-express.d.ts.map +1 -0
  51. package/dist/server/lb-express.js +77 -0
  52. package/dist/server/lb-server.d.ts +174 -0
  53. package/dist/server/lb-server.d.ts.map +1 -0
  54. package/dist/server/lb-server.js +79 -0
  55. package/dist/tests/assemble.test.d.ts +8 -0
  56. package/dist/tests/assemble.test.d.ts.map +1 -0
  57. package/dist/tests/assemble.test.js +51 -0
  58. package/dist/tests/elements.test.d.ts +8 -0
  59. package/dist/tests/elements.test.d.ts.map +1 -0
  60. package/dist/tests/elements.test.js +111 -0
  61. package/dist/tests/expand.test.d.ts +10 -0
  62. package/dist/tests/expand.test.d.ts.map +1 -0
  63. package/dist/tests/expand.test.js +226 -0
  64. package/dist/tests/fixtures/elements/collision/elements.d.ts +5 -0
  65. package/dist/tests/fixtures/elements/collision/elements.d.ts.map +1 -0
  66. package/dist/tests/fixtures/elements/collision/elements.js +3 -0
  67. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.d.ts +2 -0
  68. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.d.ts.map +1 -0
  69. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.js +1 -0
  70. package/dist/tests/fixtures/elements/local/widgets/app-box.d.ts +2 -0
  71. package/dist/tests/fixtures/elements/local/widgets/app-box.d.ts.map +1 -0
  72. package/dist/tests/fixtures/elements/local/widgets/app-box.js +1 -0
  73. package/dist/tests/fixtures/elements/manifest/elements.d.ts +5 -0
  74. package/dist/tests/fixtures/elements/manifest/elements.d.ts.map +1 -0
  75. package/dist/tests/fixtures/elements/manifest/elements.js +3 -0
  76. package/dist/tests/fixtures/elements/manifest-bad-tag/elements.d.ts +5 -0
  77. package/dist/tests/fixtures/elements/manifest-bad-tag/elements.d.ts.map +1 -0
  78. package/dist/tests/fixtures/elements/manifest-bad-tag/elements.js +3 -0
  79. package/dist/tests/fixtures/elements/manifest-bad-value/elements.d.ts +5 -0
  80. package/dist/tests/fixtures/elements/manifest-bad-value/elements.d.ts.map +1 -0
  81. package/dist/tests/fixtures/elements/manifest-bad-value/elements.js +3 -0
  82. package/dist/tests/golden.test.d.ts +19 -0
  83. package/dist/tests/golden.test.d.ts.map +1 -0
  84. package/dist/tests/golden.test.js +60 -0
  85. package/dist/tests/helpers/console.d.ts +20 -0
  86. package/dist/tests/helpers/console.d.ts.map +1 -0
  87. package/dist/tests/helpers/console.js +28 -0
  88. package/dist/tests/helpers/dom.d.ts +18 -0
  89. package/dist/tests/helpers/dom.d.ts.map +1 -0
  90. package/dist/tests/helpers/dom.js +22 -0
  91. package/dist/tests/helpers/window.d.ts +43 -0
  92. package/dist/tests/helpers/window.d.ts.map +1 -0
  93. package/dist/tests/helpers/window.js +78 -0
  94. package/dist/tests/lb-apply.test.d.ts +8 -0
  95. package/dist/tests/lb-apply.test.d.ts.map +1 -0
  96. package/dist/tests/lb-apply.test.js +153 -0
  97. package/dist/tests/lb-express.test.d.ts +14 -0
  98. package/dist/tests/lb-express.test.d.ts.map +1 -0
  99. package/dist/tests/lb-express.test.js +238 -0
  100. package/dist/tests/lb-input.test.d.ts +9 -0
  101. package/dist/tests/lb-input.test.d.ts.map +1 -0
  102. package/dist/tests/lb-input.test.js +78 -0
  103. package/dist/tests/lb-list.test.d.ts +12 -0
  104. package/dist/tests/lb-list.test.d.ts.map +1 -0
  105. package/dist/tests/lb-list.test.js +44 -0
  106. package/dist/tests/lb-options.test.d.ts +10 -0
  107. package/dist/tests/lb-options.test.d.ts.map +1 -0
  108. package/dist/tests/lb-options.test.js +121 -0
  109. package/dist/tests/lb-picker.test.d.ts +14 -0
  110. package/dist/tests/lb-picker.test.d.ts.map +1 -0
  111. package/dist/tests/lb-picker.test.js +59 -0
  112. package/dist/tests/lb-rows.test.d.ts +12 -0
  113. package/dist/tests/lb-rows.test.d.ts.map +1 -0
  114. package/dist/tests/lb-rows.test.js +336 -0
  115. package/dist/tests/lb-select.test.d.ts +9 -0
  116. package/dist/tests/lb-select.test.d.ts.map +1 -0
  117. package/dist/tests/lb-select.test.js +71 -0
  118. package/dist/tests/lb-server.test.d.ts +9 -0
  119. package/dist/tests/lb-server.test.d.ts.map +1 -0
  120. package/dist/tests/lb-server.test.js +495 -0
  121. package/dist/tests/lb-table.test.d.ts +15 -0
  122. package/dist/tests/lb-table.test.d.ts.map +1 -0
  123. package/dist/tests/lb-table.test.js +205 -0
  124. package/dist/tests/pages.test.d.ts +6 -0
  125. package/dist/tests/pages.test.d.ts.map +1 -0
  126. package/dist/tests/pages.test.js +98 -0
  127. package/dist/tests/styles.test.d.ts +7 -0
  128. package/dist/tests/styles.test.d.ts.map +1 -0
  129. package/dist/tests/styles.test.js +73 -0
  130. package/dist/widgets/index.d.ts +7 -0
  131. package/dist/widgets/index.d.ts.map +1 -0
  132. package/dist/widgets/index.js +6 -0
  133. package/dist/widgets/lb-input.d.ts +2 -0
  134. package/dist/widgets/lb-input.d.ts.map +1 -0
  135. package/dist/widgets/lb-input.js +48 -0
  136. package/dist/widgets/lb-list.d.ts +2 -0
  137. package/dist/widgets/lb-list.d.ts.map +1 -0
  138. package/dist/widgets/lb-list.js +17 -0
  139. package/dist/widgets/lb-options.d.ts +26 -0
  140. package/dist/widgets/lb-options.d.ts.map +1 -0
  141. package/dist/widgets/lb-options.js +72 -0
  142. package/dist/widgets/lb-picker.d.ts +2 -0
  143. package/dist/widgets/lb-picker.d.ts.map +1 -0
  144. package/dist/widgets/lb-picker.js +25 -0
  145. package/dist/widgets/lb-select.d.ts +2 -0
  146. package/dist/widgets/lb-select.d.ts.map +1 -0
  147. package/dist/widgets/lb-select.js +43 -0
  148. package/dist/widgets/lb-table.d.ts +2 -0
  149. package/dist/widgets/lb-table.d.ts.map +1 -0
  150. package/dist/widgets/lb-table.js +113 -0
  151. package/docs/application-chrome.md +36 -0
  152. package/docs/building-html-pages.md +130 -0
  153. package/docs/getting-started.md +120 -0
  154. package/docs/guide.md +1164 -0
  155. package/docs/hosting.md +218 -0
  156. package/docs/latent-risks.md +20 -0
  157. package/docs/theory.md +226 -0
  158. package/package.json +85 -0
  159. package/widgets/index.ts +6 -0
  160. package/widgets/lb-input.html +1 -0
  161. package/widgets/lb-input.ts +64 -0
  162. package/widgets/lb-list.html +1 -0
  163. package/widgets/lb-list.ts +21 -0
  164. package/widgets/lb-options.html +4 -0
  165. package/widgets/lb-options.ts +88 -0
  166. package/widgets/lb-picker.html +7 -0
  167. package/widgets/lb-picker.ts +27 -0
  168. package/widgets/lb-select.html +4 -0
  169. package/widgets/lb-select.ts +55 -0
  170. package/widgets/lb-table.html +8 -0
  171. package/widgets/lb-table.ts +126 -0
@@ -0,0 +1,43 @@
1
+ /// <reference lib="dom" />
2
+ import { ATTR_ACTION, ATTR_VALUE, LB_EVENT_NAME, } from "../core/lb-constants";
3
+ /**
4
+ * A leaf cell wrapping a <select>. It receives its value like any other cell,
5
+ * and on change it sends the action the page declared for it — the request
6
+ * vocabulary and the addressing vocabulary running in both directions on one
7
+ * widget.
8
+ *
9
+ * The choice is the interaction, so this is the case where an action carries
10
+ * a value. The server still decides what that choice means.
11
+ */
12
+ class LbSelect extends HTMLElement {
13
+ static observedAttributes = [ATTR_VALUE];
14
+ attributeChangedCallback(_name, _old, value) {
15
+ const select = this.querySelector("select");
16
+ if (!select) {
17
+ console.error(`lb-select: no <select> to receive the value`);
18
+ return;
19
+ }
20
+ select.value = value;
21
+ }
22
+ connectedCallback() {
23
+ this.addEventListener("change", () => {
24
+ const select = this.querySelector("select");
25
+ if (!select) {
26
+ console.error(`lb-select: change with no <select>, ignoring`);
27
+ return;
28
+ }
29
+ const name = this.getAttribute(ATTR_ACTION);
30
+ if (!name) {
31
+ console.error(`lb-select: no ${ATTR_ACTION}, nothing to send`);
32
+ return;
33
+ }
34
+ const detail = {
35
+ op: "action",
36
+ name,
37
+ value: select.value,
38
+ };
39
+ this.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));
40
+ });
41
+ }
42
+ }
43
+ customElements.define("lb-select", LbSelect);
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=lb-table.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lb-table.d.ts","sourceRoot":"","sources":["../../widgets/lb-table.ts"],"names":[],"mappings":""}
@@ -0,0 +1,113 @@
1
+ /// <reference lib="dom" />
2
+ import { ATTR_CELL, ATTR_GROUP, ATTR_KEY, ATTR_SORT, } from "../core/lb-constants";
3
+ import { applyRows } from "../hub/lb-rows";
4
+ /**
5
+ * A table that owns its own scaffolding, and the case that shows what a list
6
+ * widget is for once the repeater exists.
7
+ *
8
+ * `lb-list` goes around a table the page author wrote. This one supplies
9
+ * the table, and the page writes what only the page knows: the heading row
10
+ * and the row template. That division is forced rather than chosen — a
11
+ * `<thead>` written inside a custom element is not inside a table, so the
12
+ * parser drops the tag and keeps the text. A `<template>` survives anywhere,
13
+ * so the heading row arrives wrapped and expansion unwraps it into the
14
+ * `lb-template` destination beside the slot.
15
+ *
16
+ * What it adds at run time is placement, and only placement. `lb-sort` names
17
+ * the column rows are ordered by; `lb-group` names the column they are
18
+ * sectioned by, and is the same attribute a grouped `<select>` reads, because
19
+ * a table with section headings and an `<optgroup>` are asking the same thing
20
+ * of the same data.
21
+ *
22
+ * The `foot` destination is the head's argument a second time and needs no
23
+ * code here. A `<tfoot>` is a place on the page, not a row the hub delivers,
24
+ * so what lands in it is an ordinary scope carrying its own `lb-query` — a
25
+ * grand total is a second projection of the same table, and the hub resolves
26
+ * it by name like any other. See demo/pages/ledger.html.
27
+ */
28
+ /**
29
+ * The heading row of a section, carrying the value it stands for.
30
+ *
31
+ * The widget's own scaffolding, so it is HTML's namespace rather than
32
+ * Loadbare App's: it is derived from rows the hub knows and is invisible to the hub
33
+ * itself, exactly as the `<optgroup>` in `lb-options` is. Nothing
34
+ * addresses it, so it carries no `lb-key` and no name from the vocabulary.
35
+ */
36
+ const GROUP_ROW = "data-group";
37
+ class LbTable extends HTMLElement {
38
+ acceptRows(result) {
39
+ applyRows(this, result, this.place);
40
+ // A section is derived, so it goes when its last row does.
41
+ for (const heading of this.querySelectorAll(`tr[${GROUP_ROW}]`)) {
42
+ if (this.section(heading).rows.length === 0)
43
+ heading.remove();
44
+ }
45
+ }
46
+ /**
47
+ * Group first, then sort within the group.
48
+ *
49
+ * Inserting a row before the first one that sorts after it keeps the
50
+ * section ordered, whatever else is in it — which is what lets a whole set
51
+ * and a single patched row take the same path.
52
+ */
53
+ place = (row, tuple, template) => {
54
+ const groupCell = template.getAttribute(ATTR_GROUP);
55
+ const sortCell = template.getAttribute(ATTR_SORT);
56
+ const heading = groupCell
57
+ ? this.headingFor(template, tuple[groupCell] ?? "")
58
+ : null;
59
+ const { rows, end } = this.section(heading);
60
+ const value = sortCell ? (tuple[sortCell] ?? "") : "";
61
+ const after = sortCell
62
+ ? rows.find((other) => other !== row && cellOf(other, sortCell) > value)
63
+ : undefined;
64
+ template.parentElement.insertBefore(row, after ?? end);
65
+ };
66
+ /**
67
+ * The rows of one section, and the node it ends at.
68
+ *
69
+ * A section runs from its heading to the next heading, or to the template
70
+ * if there is none after it. Without grouping there is one section and it
71
+ * is the whole body — the heading row the page wrote is skipped along the
72
+ * way, because it carries no key and is therefore not a row.
73
+ */
74
+ section(heading) {
75
+ const template = this.querySelector("template");
76
+ const rows = [];
77
+ let node = heading
78
+ ? heading.nextElementSibling
79
+ : template.parentElement.firstElementChild;
80
+ while (node && node !== template && !node.hasAttribute(GROUP_ROW)) {
81
+ if (node.hasAttribute(ATTR_KEY))
82
+ rows.push(node);
83
+ node = node.nextElementSibling;
84
+ }
85
+ return { rows, end: node ?? template };
86
+ }
87
+ /** The section with this label, or a new one in front of the template. */
88
+ headingFor(template, label) {
89
+ const body = template.parentElement;
90
+ for (const heading of body.querySelectorAll(`tr[${GROUP_ROW}]`)) {
91
+ if (heading.getAttribute(GROUP_ROW) === label)
92
+ return heading;
93
+ }
94
+ const heading = document.createElement("tr");
95
+ heading.setAttribute(GROUP_ROW, label);
96
+ const cell = document.createElement("th");
97
+ cell.setAttribute("scope", "rowgroup");
98
+ // How wide the table is, from the row the page author wrote. Nothing
99
+ // else knows, and nothing had to be told twice.
100
+ cell.colSpan = template.content.firstElementChild?.childElementCount ?? 1;
101
+ cell.textContent = label;
102
+ heading.append(cell);
103
+ body.insertBefore(heading, template);
104
+ return heading;
105
+ }
106
+ }
107
+ /** What a live row is showing in one column. */
108
+ function cellOf(row, cell) {
109
+ const selector = `[${ATTR_CELL}="${cell}"]`;
110
+ const el = row.matches(selector) ? row : row.querySelector(selector);
111
+ return el?.textContent ?? "";
112
+ }
113
+ customElements.define("lb-table", LbTable);
@@ -0,0 +1,36 @@
1
+ # Application Chrome
2
+
3
+ The application Chrome - banner, nav, main and footer, is static HTML.
4
+
5
+ It is found by name, not by location: it can be placed anywhere in the tree
6
+ the builder scans, as any file ending `.chrome.html` — there must be exactly
7
+ one. By convention we name it `src/chrome.chrome.html`.
8
+
9
+ In our docs we consider chrome to be invariant across pages. This is
10
+ not a strict technical requirement, as loadbare can update anything,
11
+ but we do not provide examples or tests of dynamic chrome.
12
+
13
+ The minimum requirements for the chrome file are:
14
+
15
+ - the `<lb-hub>` element as first element below `<body>`
16
+ - an empty `<main></main>` element. Its content is populated by
17
+ the hub from the application pages.
18
+
19
+ ```html
20
+ <!doctype html>
21
+ <html lang="en">
22
+ <head>
23
+ <meta charset="utf-8" />
24
+ <title><!-- content goes here --></title>
25
+ <script src="/client.js" defer></script>
26
+ </head>
27
+ <body>
28
+ <lb-hub>
29
+ <header><!-- content goes here --></header>
30
+ <nav><!-- content goes here --></nav>
31
+ <main></main>
32
+ <footer><!-- content goes here --></footer>
33
+ </lb-hub>
34
+ </body>
35
+ </html>
36
+ ```
@@ -0,0 +1,130 @@
1
+ # Building HTML Pages
2
+
3
+ A page is authored as one HTML file. It may contain widget tags. Expansion
4
+ turns each one that has a definition into the tree that definition stands
5
+ for. Expansion runs at build time, before there is a request, and before
6
+ there is any data.
7
+
8
+ `LB-*` is the framework's reserved namespace — Loadbare App's own widgets, and
9
+ the convention its docs use. An application's own widgets should use their
10
+ own prefix instead (`app-nav`, not `lb-nav`); a tag outside the `LB-*`
11
+ namespace expands exactly the same way when it has a definition, but is not
12
+ required to have one — with none, it is assumed to be a plain custom element,
13
+ registered by script alone (see build/elements.ts).
14
+
15
+ ## Definitions
16
+
17
+ A definition is one HTML file per tag. The file name gives the tag:
18
+ `lb-options.html` defines `LB-OPTIONS`; `app-nav.html` defines `APP-NAV`.
19
+
20
+ Loadbare App's own widgets live in this package's own `widgets/`, read by
21
+ `loadDefinitions(dirs)`, which takes a list of directories in order — a later
22
+ one overrides an earlier one for the same tag name. An application's own
23
+ definitions are not confined to a directory at all: they are found by
24
+ filename anywhere in its source tree (see `docs/getting-started.md`) and
25
+ overlaid on the built-in set with `addDefinitions`, the same override an
26
+ application directory would give a built-in widget.
27
+
28
+ The full set of definitions is checked for a cycle in which definitions
29
+ reference each other by tag, and throws if one exists.
30
+
31
+ ## Expanding a page
32
+
33
+ `expand(source, defs)` walks the authored tree and replaces every widget tag
34
+ that has a matching entry in `defs` with the tree that entry produces. A tag
35
+ in the `LB-*` namespace is required to have one — an unresolved tag there
36
+ throws. Outside that namespace, a tag with no definition is left exactly as
37
+ authored.
38
+
39
+ A definition may itself use other widget tags. Expansion resolves those the
40
+ same way, as deep as the definitions go.
41
+
42
+ ## Parameters
43
+
44
+ A definition may declare a placeholder, written `{{name}}`, as the entire
45
+ value of an attribute or the entire text of a text node.
46
+
47
+ An authored tag supplies a placeholder's value with an attribute named
48
+ `exp-<name>`. `exp-label="Member:"` supplies `{{label}}`.
49
+
50
+ HTML lowercases attribute names, so a placeholder name may not contain an
51
+ uppercase letter — `loadDefinitions` rejects a definition that declares one.
52
+
53
+ Supplying `exp-x` on a tag whose definition has no `{{x}}` is an error.
54
+
55
+ If a placeholder has no value supplied, the attribute it fills is dropped
56
+ rather than emitted empty; a text placeholder becomes empty text.
57
+
58
+ ## The slot
59
+
60
+ A definition may mark one element `lb-slot`. The authored tag's child nodes
61
+ move into that element, replacing it. A definition with no `lb-slot` rejects
62
+ any non-whitespace content written inside the authored tag.
63
+
64
+ ## Named template destinations
65
+
66
+ A definition may mark more than one element `lb-template="<name>"`, each a
67
+ destination. An authored tag supplies content for a destination by writing a
68
+ `<template lb-template="<name>">` among its children; expansion moves the
69
+ template's content into the destination and discards the wrapping `<template>`
70
+ tag. A destination with no matching template is left as the definition wrote
71
+ it.
72
+
73
+ Two templates naming the same destination, or a template naming a destination
74
+ the definition does not declare, is an error.
75
+
76
+ A definition applies named destinations before the slot: content addressed to
77
+ a destination by name does not also count as slot content.
78
+
79
+ ### Example
80
+
81
+ `widgets/lb-table.html`:
82
+
83
+ ```html
84
+ <table>
85
+ <caption>
86
+ {{caption}}
87
+ </caption>
88
+ <thead lb-template="head"></thead>
89
+ <tbody lb-slot></tbody>
90
+ <tfoot lb-template="foot"></tfoot>
91
+ </table>
92
+ ```
93
+
94
+ Authored in a page:
95
+
96
+ ```html
97
+ <lb-table exp-caption="Everyone, by team">
98
+ <template lb-template="head">
99
+ <tr>
100
+ <th>Name</th>
101
+ <th>Role</th>
102
+ </tr>
103
+ </template>
104
+ <tr>
105
+ <td>Row content for the slot</td>
106
+ </tr>
107
+ </lb-table>
108
+ ```
109
+
110
+ The `head` template's content becomes the `<thead>`'s children. The `<tr>`
111
+ written outside any `<template>` is slot content and becomes a child of
112
+ `<tbody>`. There is no template addressed to `foot`, so the `<tfoot>` ships
113
+ empty, exactly as the definition wrote it.
114
+
115
+ ## Template content elsewhere on the page
116
+
117
+ Expansion also descends into the content of any `<template>` on the page,
118
+ including one with no `lb-template` attribute — for example a row template
119
+ consumed by a widget at runtime rather than by expansion. A placeholder or
120
+ widget tag written inside such a template is still expanded.
121
+
122
+ ## Output
123
+
124
+ `expand(source, defs)` returns HTML text. Substitution is applied to a parsed
125
+ tree through DOM APIs — `setAttribute`, text node values — never to a string,
126
+ so a value supplied through `exp-*` cannot be reparsed as markup.
127
+
128
+ What `expand` returns is what ships. No value from a request or a database is
129
+ added after expansion; those values land on the already-expanded tree later,
130
+ as attributes.
@@ -0,0 +1,120 @@
1
+ # Getting Started
2
+
3
+ We begin with the smallest working application. This app does not even
4
+ have any pages, but it will load without error, and is inspectable.
5
+
6
+ ## The chrome file
7
+
8
+ Write a file ending `.chrome.html` — say, `src/chrome.chrome.html`. There
9
+ must be exactly one of these anywhere under `src/`; the builder finds it by
10
+ that name, not by which directory it's in.
11
+
12
+ At minimum, the chrome must contain:
13
+
14
+ - a link to the `client.js` script
15
+ - The `<lb-hub>` element just inside `<body>`
16
+ - an empty `<main>` inside `<lb-hub>`
17
+
18
+ ```html
19
+ <!doctype html>
20
+ <html lang="en">
21
+ <head>
22
+ <meta charset="utf-8" />
23
+ <title>Getting Started</title>
24
+ <script src="/client.js" defer></script>
25
+ </head>
26
+ <body>
27
+ <lb-hub>
28
+ <main></main>
29
+ </lb-hub>
30
+ </body>
31
+ </html>
32
+ ```
33
+
34
+ The client script is generated by the builder, and contains some core code
35
+ and the Javascript
36
+ for all custom elements in the app. Since we have only `lb-hub`, the build
37
+ will produce a `client.js` containing the core code and only the class for `lb-hub`.
38
+
39
+ ## Serving It
40
+
41
+ Create `server.js` as an express server:
42
+
43
+ ```js
44
+ import express from "express";
45
+ import { readFileSync } from "node:fs";
46
+
47
+ const app = express();
48
+
49
+ app.get("/", (_req, res) =>
50
+ res.type("html").send(readFileSync("dist/app.html", "utf-8")),
51
+ );
52
+ app.use("/client.js", express.static("dist/client.js"));
53
+
54
+ app.listen(8787);
55
+ ```
56
+
57
+ `dist/app.html` is read on every request rather than once at startup, so a
58
+ rebuild in watch mode is visible without restarting the server.
59
+
60
+ ## The dev Script
61
+
62
+ Add to package.json:
63
+
64
+ ```json
65
+ {
66
+ "scripts": {
67
+ "dev": "loadbare-app-build --watch & node server.js"
68
+ }
69
+ }
70
+ ```
71
+
72
+ `loadbare-app-build` scans `src/` by default; `--src` overrides which tree. It
73
+ takes no other location flags — everything under that tree is found by what
74
+ a file is named, not by which directory holds it:
75
+
76
+ - `*.chrome.html` — the one chrome file
77
+ - `*.page.html` — a page, named for the part before the suffix
78
+ - any other `.html` — a widget definition, named for its own filename
79
+ - a `.ts` file shaped like a custom element tag (e.g. `lb-nav.ts`,
80
+ `app-box.ts`) — a widget's script
81
+ - `*.hooks.ts` / `*.queries.ts` — a page's server half, paired with a
82
+ `*.page.html` by sharing its basename
83
+ - `.css` — a stylesheet, concatenated with every other one found
84
+ - `elements.ts` — third-party custom elements: a default export mapping tag
85
+ to import specifier, e.g. `{ "acme-date-picker": "@acme/date-picker" }`
86
+
87
+ A widget's markup and script don't need to sit in the same place, or in any
88
+ particular directory at all — `src/pages/home.page.html` and
89
+ `src/lb-nav.html` are both found the same way `src/widgets/lb-nav.html`
90
+ would be.
91
+
92
+ ## CSS
93
+
94
+ Every `.css` file anywhere under `src/` is concatenated into `dist/app.css`,
95
+ in one order: sorted by filename, not by directory. Put a stylesheet next to
96
+ the markup it styles, wherever that is convenient — a global one sorts to
97
+ the front on its own by naming itself `00-reset.css` or `aa-globals.css`,
98
+ regardless of which directory it lives in. Nothing here knows about widgets
99
+ or pages; it is exactly the concatenation of what was authored, with no
100
+ scoping and no plain `style="..."` attribute anywhere the framework writes
101
+ markup, since that is inline code the same way `<script>` is, and Loadbare App
102
+ targets a CSP that refuses both.
103
+
104
+ The chrome links it like any other asset — `<link rel="stylesheet"
105
+ href="/app.css" />` — the builder never writes into the chrome itself.
106
+
107
+ This script runs the Loadbare App builder in watch mode, alongside the server.
108
+ With `--watch`, every `.html`, `.ts` and `.css` file under `src/` is watched —
109
+ a page, a widget's markup, a widget's class, a stylesheet, and `elements.ts`
110
+ all feed the build.
111
+
112
+ Loadbare App does not reload the browser on a rebuild. Watch mode keeps
113
+ `dist/app.html`, `dist/client.js` and `dist/app.css` current on disk; the
114
+ browser picks up a change on the next manual refresh.
115
+
116
+ ## Run it
117
+
118
+ Try `npm run dev`, and navigate the browser to localhost:8787, you
119
+ should see an empty page, with a console message, and the view source
120
+ option should show exactly our page.