@loadbare/app 0.5.6 → 0.7.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 (143) hide show
  1. package/README.md +3 -4
  2. package/dist/build/assemble.d.ts +1 -1
  3. package/dist/build/assemble.d.ts.map +1 -1
  4. package/dist/build/assemble.js +81 -7
  5. package/dist/build/assemble.js.map +1 -0
  6. package/dist/build/cli.d.ts +2 -2
  7. package/dist/build/cli.js +3 -2
  8. package/dist/build/cli.js.map +1 -0
  9. package/dist/build/elements.js +1 -0
  10. package/dist/build/elements.js.map +1 -0
  11. package/dist/build/expand.d.ts.map +1 -1
  12. package/dist/build/expand.js +20 -32
  13. package/dist/build/expand.js.map +1 -0
  14. package/dist/build/format.js +1 -0
  15. package/dist/build/format.js.map +1 -0
  16. package/dist/build/locations.d.ts +4 -4
  17. package/dist/build/locations.d.ts.map +1 -1
  18. package/dist/build/locations.js +16 -5
  19. package/dist/build/locations.js.map +1 -0
  20. package/dist/build/origins.d.ts +0 -13
  21. package/dist/build/origins.d.ts.map +1 -1
  22. package/dist/build/origins.js +33 -8
  23. package/dist/build/origins.js.map +1 -0
  24. package/dist/build/package-root.js +1 -0
  25. package/dist/build/package-root.js.map +1 -0
  26. package/dist/build/pages.d.ts +7 -3
  27. package/dist/build/pages.d.ts.map +1 -1
  28. package/dist/build/pages.js +14 -7
  29. package/dist/build/pages.js.map +1 -0
  30. package/dist/build/styles.js +1 -0
  31. package/dist/build/styles.js.map +1 -0
  32. package/dist/core/lb-constants.d.ts +15 -13
  33. package/dist/core/lb-constants.d.ts.map +1 -1
  34. package/dist/core/lb-constants.js +104 -53
  35. package/dist/core/lb-constants.js.map +1 -0
  36. package/dist/core/lb-types.d.ts +103 -62
  37. package/dist/core/lb-types.d.ts.map +1 -1
  38. package/dist/core/lb-types.js +12 -3
  39. package/dist/core/lb-types.js.map +1 -0
  40. package/dist/hub/lb-apply.d.ts +28 -4
  41. package/dist/hub/lb-apply.d.ts.map +1 -1
  42. package/dist/hub/lb-apply.js +227 -40
  43. package/dist/hub/lb-apply.js.map +1 -0
  44. package/dist/hub/lb-hub.browser.d.ts +1 -1
  45. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  46. package/dist/hub/lb-hub.browser.js +165 -110
  47. package/dist/hub/lb-hub.browser.js.map +1 -0
  48. package/dist/server/lb-express.d.ts +8 -5
  49. package/dist/server/lb-express.d.ts.map +1 -1
  50. package/dist/server/lb-express.js +46 -33
  51. package/dist/server/lb-express.js.map +1 -0
  52. package/dist/server/lb-server.d.ts +67 -45
  53. package/dist/server/lb-server.d.ts.map +1 -1
  54. package/dist/server/lb-server.js +56 -18
  55. package/dist/server/lb-server.js.map +1 -0
  56. package/docs/TECHREF-1.0.md +1107 -0
  57. package/docs/analysis-accidental-complexity.md +149 -0
  58. package/docs/reference/builder.md +4 -4
  59. package/docs/reference/chrome.md +10 -9
  60. package/docs/reference/custom-elements.md +54 -46
  61. package/docs/reference/data-binding.md +179 -97
  62. package/docs/reference/overview.md +1 -1
  63. package/docs/reference/page-files.md +64 -49
  64. package/docs/reference/server.md +25 -6
  65. package/docs/reference/widgets.md +22 -30
  66. package/docs/roadmap.md +68 -22
  67. package/docs/testing.md +47 -17
  68. package/docs/theory.md +116 -3
  69. package/docs/tutorials/010-pages-and-navigation.md +8 -8
  70. package/docs/tutorials/040-displaying-data.md +9 -9
  71. package/docs/tutorials/050-actions.md +5 -5
  72. package/docs/tutorials/060-custom-element-code.md +1 -1
  73. package/docs/tutorials/065-conditional-rendering.md +4 -4
  74. package/docs/tutorials/070-displaying-a-list.md +24 -47
  75. package/docs/tutorials/072-inserting-into-a-list.md +18 -15
  76. package/docs/tutorials/074-deleting-from-a-list.md +15 -17
  77. package/docs/tutorials/076-updating-a-list-item.md +20 -22
  78. package/docs/tutorials/080-widget-requests.md +22 -35
  79. package/docs/tutorials/090-using-widget-libraries.md +1 -1
  80. package/package.json +2 -3
  81. package/dist/hub/lb-rows.d.ts +0 -18
  82. package/dist/hub/lb-rows.d.ts.map +0 -1
  83. package/dist/hub/lb-rows.js +0 -106
  84. package/dist/tests/assemble.test.d.ts +0 -8
  85. package/dist/tests/assemble.test.d.ts.map +0 -1
  86. package/dist/tests/assemble.test.js +0 -58
  87. package/dist/tests/elements.test.d.ts +0 -8
  88. package/dist/tests/elements.test.d.ts.map +0 -1
  89. package/dist/tests/elements.test.js +0 -118
  90. package/dist/tests/expand.test.d.ts +0 -10
  91. package/dist/tests/expand.test.d.ts.map +0 -1
  92. package/dist/tests/expand.test.js +0 -250
  93. package/dist/tests/fixtures/elements/collision/imports.d.ts +0 -3
  94. package/dist/tests/fixtures/elements/collision/imports.d.ts.map +0 -1
  95. package/dist/tests/fixtures/elements/collision/imports.js +0 -1
  96. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts +0 -2
  97. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts.map +0 -1
  98. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.js +0 -1
  99. package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts +0 -2
  100. package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts.map +0 -1
  101. package/dist/tests/fixtures/elements/local/widgets/app-box.browser.js +0 -1
  102. package/dist/tests/fixtures/elements/manifest/imports.d.ts +0 -3
  103. package/dist/tests/fixtures/elements/manifest/imports.d.ts.map +0 -1
  104. package/dist/tests/fixtures/elements/manifest/imports.js +0 -1
  105. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts +0 -3
  106. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts.map +0 -1
  107. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.js +0 -1
  108. package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts +0 -5
  109. package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts.map +0 -1
  110. package/dist/tests/fixtures/elements/manifest-not-array/imports.js +0 -1
  111. package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts +0 -2
  112. package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts.map +0 -1
  113. package/dist/tests/fixtures/elements/pkg/acme-widget.browser.js +0 -1
  114. package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts +0 -6
  115. package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts.map +0 -1
  116. package/dist/tests/fixtures/elements/unmarked/widgets/app-box.js +0 -1
  117. package/dist/tests/helpers/console.d.ts +0 -20
  118. package/dist/tests/helpers/console.d.ts.map +0 -1
  119. package/dist/tests/helpers/console.js +0 -28
  120. package/dist/tests/helpers/dom.d.ts +0 -18
  121. package/dist/tests/helpers/dom.d.ts.map +0 -1
  122. package/dist/tests/helpers/dom.js +0 -22
  123. package/dist/tests/lb-apply.test.d.ts +0 -8
  124. package/dist/tests/lb-apply.test.d.ts.map +0 -1
  125. package/dist/tests/lb-apply.test.js +0 -153
  126. package/dist/tests/lb-express.test.d.ts +0 -14
  127. package/dist/tests/lb-express.test.d.ts.map +0 -1
  128. package/dist/tests/lb-express.test.js +0 -238
  129. package/dist/tests/lb-rows.test.d.ts +0 -12
  130. package/dist/tests/lb-rows.test.d.ts.map +0 -1
  131. package/dist/tests/lb-rows.test.js +0 -336
  132. package/dist/tests/lb-server.test.d.ts +0 -9
  133. package/dist/tests/lb-server.test.d.ts.map +0 -1
  134. package/dist/tests/lb-server.test.js +0 -495
  135. package/dist/tests/origins.test.d.ts +0 -10
  136. package/dist/tests/origins.test.d.ts.map +0 -1
  137. package/dist/tests/origins.test.js +0 -369
  138. package/dist/tests/pages.test.d.ts +0 -6
  139. package/dist/tests/pages.test.d.ts.map +0 -1
  140. package/dist/tests/pages.test.js +0 -98
  141. package/dist/tests/styles.test.d.ts +0 -7
  142. package/dist/tests/styles.test.d.ts.map +0 -1
  143. package/dist/tests/styles.test.js +0 -76
package/README.md CHANGED
@@ -4,17 +4,18 @@ The fundamentals of @loadbare/app are fixed, we are now working
4
4
  on completeness.
5
5
 
6
6
  > This package is being prepped for 1.0, current work is:
7
+ >
7
8
  > 1. docs (nearly done)
8
9
  > 2. demos (must be normalized and checked against docs)
9
10
  > 3. test a release/use cycle, make sure all instructions work
10
11
 
11
-
12
12
  ## Purpose
13
13
 
14
14
  Loadbare App is a web app framework built specifically to deliver high performance
15
- to the user for server-bound applications.
15
+ to the user for server-bound applications.
16
16
 
17
17
  Loadbare App has two twin primary goals
18
+
18
19
  - To follow the loadbare philosophy of reducing accidental complexity
19
20
  to near zero, so the app developer is spending maximum time on the app
20
21
  instead of the tooling.
@@ -62,5 +63,3 @@ each lesson.
62
63
  ## Roadmap Items for 1.0 and 1.1
63
64
 
64
65
  [Roadmap](./docs/roadmap.md)
65
-
66
-
@@ -6,7 +6,7 @@
6
6
  * built-in widgets and whatever definitions were found in the caller's own
7
7
  * source tree. `expand()` only ever sees a fragment, so the chrome's own is
8
8
  * the text between its `<body>` tags; a page's is the whole file. A page's
9
- * expanded host is wrapped in `<template id="page-<name>">` and, together
9
+ * expanded host is wrapped in `<template lb-page="<name>">` and, together
10
10
  * with the chrome's own expanded body content, spliced back into the
11
11
  * chrome's `<body>`. The result is the one document a server has to serve
12
12
  * for every route — see docs/tutorials/000-getting-started.md and
@@ -1 +1 @@
1
- {"version":3,"file":"assemble.d.ts","sourceRoot":"","sources":["../../build/assemble.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAeH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAuB/C,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,8EAA8E;IAC9E,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACvB;AAED,wBAAsB,QAAQ,CAAC,EAC7B,UAAU,EACV,KAAK,EACL,KAAK,GACN,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CA2CtC"}
1
+ {"version":3,"file":"assemble.d.ts","sourceRoot":"","sources":["../../build/assemble.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAoBH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAwH/C,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,8EAA8E;IAC9E,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACvB;AAED,wBAAsB,QAAQ,CAAC,EAC7B,UAAU,EACV,KAAK,EACL,KAAK,GACN,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CA2CtC"}
@@ -6,7 +6,7 @@
6
6
  * built-in widgets and whatever definitions were found in the caller's own
7
7
  * source tree. `expand()` only ever sees a fragment, so the chrome's own is
8
8
  * the text between its `<body>` tags; a page's is the whole file. A page's
9
- * expanded host is wrapped in `<template id="page-<name>">` and, together
9
+ * expanded host is wrapped in `<template lb-page="<name>">` and, together
10
10
  * with the chrome's own expanded body content, spliced back into the
11
11
  * chrome's `<body>`. The result is the one document a server has to serve
12
12
  * for every route — see docs/tutorials/000-getting-started.md and
@@ -20,12 +20,17 @@ import { readFile } from "node:fs/promises";
20
20
  import { JSDOM } from "jsdom";
21
21
  import { addDefinitions, customElementTags, expand, } from "./expand.js";
22
22
  import { formatHtml } from "./format.js";
23
- import { ATTR_UNKNOWN_PAGE, PAGE_TEMPLATE_PREFIX, } from "../core/lb-constants.js";
23
+ import { ATTR_CELL, ATTR_KEY, ATTR_LIST, ATTR_PAGE, ATTR_ROW, ATTR_UNKNOWN_PAGE, HUB_TAG_NAME, } from "../core/lb-constants.js";
24
24
  /**
25
- * The hub calls showModal() on whatever carries lb-unknown-page (hub/lb-hub.browser.ts),
26
- * so a chrome that puts the attribute on anything else would fail at
27
- * runtime, on whichever page happens to 404 first, rather than at build
28
- * time. The attribute is optional; only a wrong element on it is an error.
25
+ * The hub calls showModal() on whatever carries lb-unknown-page
26
+ * (hub/lb-hub.browser.ts), so a chrome that puts the attribute on anything
27
+ * else would fail at runtime, on whichever page happens to 404 first, rather
28
+ * than at build time. The attribute is optional; a wrong element on it, or a
29
+ * right one out of the hub's reach, is an error.
30
+ *
31
+ * Out of reach because the hub looks for the dialog inside itself, as it
32
+ * does for everything else it acts on. A dialog elsewhere in the body would
33
+ * never open and would say nothing about why.
29
34
  */
30
35
  function checkUnknownPageDialog(chromeFile, expandedBody) {
31
36
  const fragment = JSDOM.fragment(expandedBody);
@@ -36,6 +41,72 @@ function checkUnknownPageDialog(chromeFile, expandedBody) {
36
41
  throw new Error(`assemble: '${chromeFile}' declares ${ATTR_UNKNOWN_PAGE} on ` +
37
42
  `<${el.tagName.toLowerCase()}>, must be a <dialog>`);
38
43
  }
44
+ if (!el.closest(HUB_TAG_NAME)) {
45
+ throw new Error(`assemble: '${chromeFile}' declares ${ATTR_UNKNOWN_PAGE} outside ` +
46
+ `<${HUB_TAG_NAME}>, where the hub cannot reach it`);
47
+ }
48
+ }
49
+ /**
50
+ * The binding vocabulary, checked once against the finished document rather
51
+ * than discovered one console error at a time in a browser.
52
+ *
53
+ * Everything here is a fact the markup states about itself, so none of it
54
+ * needs the server: a key is named on a row template, a row template belongs
55
+ * to a list, and a cell belongs to a scope. What the markup and the server
56
+ * say about cardinality cannot be compared here, because they are separate
57
+ * artifacts — createHub holds that check. See docs/reference/data-binding.md.
58
+ *
59
+ * A list scope with no row template is not an error. It is bound to the list
60
+ * without displaying it, which is what an insert form naming the list it adds
61
+ * to already is.
62
+ *
63
+ * A `<template>`'s children are walked in the scope that encloses the
64
+ * template, because a row template's clones land in the list around it.
65
+ */
66
+ function walkBinding(node, scope, problems) {
67
+ for (const el of node.children) {
68
+ const listName = el.getAttribute(ATTR_LIST);
69
+ const rowName = el.getAttribute(ATTR_ROW);
70
+ const tag = el.localName;
71
+ let here = scope;
72
+ if (listName !== null && rowName !== null) {
73
+ problems.push(`<${tag}> carries both ${ATTR_LIST} and ${ATTR_ROW}; a scope is ` +
74
+ `one or the other, and a name answers with one shape`);
75
+ }
76
+ if (listName !== null)
77
+ here = { kind: "list", name: listName };
78
+ else if (rowName !== null)
79
+ here = { kind: "row", name: rowName };
80
+ if (el.hasAttribute(ATTR_CELL) && here === null) {
81
+ problems.push(`<${tag}> carries ${ATTR_CELL}="${el.getAttribute(ATTR_CELL)}" with ` +
82
+ `no ${ATTR_LIST} or ${ATTR_ROW} scope around it`);
83
+ }
84
+ if (el.hasAttribute(ATTR_KEY)) {
85
+ if (tag !== "template") {
86
+ problems.push(`<${tag}> carries ${ATTR_KEY}; it names the column that identifies ` +
87
+ `a row and belongs on the row <template>`);
88
+ }
89
+ else if (here === null || here.kind !== "list") {
90
+ problems.push(`a <template> carries ${ATTR_KEY} outside any ${ATTR_LIST} scope; ` +
91
+ `only a list has rows to identify`);
92
+ }
93
+ }
94
+ walkBinding(el, here, problems);
95
+ // By tag name rather than by instanceof: JSDOM.fragment() parses into a
96
+ // document with no window, so the constructor is not reachable from the
97
+ // node.
98
+ if (tag === "template") {
99
+ walkBinding(el.content, here, problems);
100
+ }
101
+ }
102
+ }
103
+ /** Check one expanded fragment, and name the file if anything is wrong. */
104
+ function checkBinding(file, html) {
105
+ const problems = [];
106
+ walkBinding(JSDOM.fragment(html), null, problems);
107
+ if (problems.length > 0) {
108
+ throw new Error(`assemble: '${file}' binding:\n ` + problems.join(`\n `));
109
+ }
39
110
  }
40
111
  export async function assemble({ chromeFile, pages, scans, }) {
41
112
  // One layer per origin, in order, so a later origin's definition overrides
@@ -49,7 +120,8 @@ export async function assemble({ chromeFile, pages, scans, }) {
49
120
  for (const { name, file } of pages) {
50
121
  const authored = await readFile(file, "utf-8");
51
122
  const host = expand(authored, defs);
52
- templates.push(`<template id="${PAGE_TEMPLATE_PREFIX}${name}">${host}</template>`);
123
+ checkBinding(file, host);
124
+ templates.push(`<template ${ATTR_PAGE}="${name}">${host}</template>`);
53
125
  }
54
126
  let chromeSource;
55
127
  try {
@@ -65,9 +137,11 @@ export async function assemble({ chromeFile, pages, scans, }) {
65
137
  const [whole, bodyAttrs, bodyContent] = body;
66
138
  const expandedBody = expand(bodyContent, defs);
67
139
  checkUnknownPageDialog(chromeFile, expandedBody);
140
+ checkBinding(chromeFile, expandedBody);
68
141
  const withPages = chromeSource.replace(whole, () => `<body${bodyAttrs}>${expandedBody}\n${templates.join("\n")}\n</body>`);
69
142
  return {
70
143
  html: await formatHtml(withPages),
71
144
  usedTags: customElementTags(withPages),
72
145
  };
73
146
  }
147
+ //# sourceMappingURL=assemble.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assemble.js","sourceRoot":"","sources":["../../build/assemble.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Assembling one document out of a chrome file and a set of pages.\n *\n * Every `.html` file in play — the chrome and each page — goes through the\n * same `expand()` (see build/expand.ts) against the same definitions: the\n * built-in widgets and whatever definitions were found in the caller's own\n * source tree. `expand()` only ever sees a fragment, so the chrome's own is\n * the text between its `<body>` tags; a page's is the whole file. A page's\n * expanded host is wrapped in `<template lb-page=\"<name>\">` and, together\n * with the chrome's own expanded body content, spliced back into the\n * chrome's `<body>`. The result is the one document a server has to serve\n * for every route — see docs/tutorials/000-getting-started.md and\n * docs/reference/builder.md.\n *\n * Every path here is a parameter, resolved once by build/locations.ts — this\n * module never reconstructs one of its own, and never reads a directory\n * looking for pages or widgets; it is handed exactly which files those are.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { JSDOM } from \"jsdom\";\nimport {\n addDefinitions,\n customElementTags,\n expand,\n type Definitions,\n} from \"./expand.js\";\nimport { formatHtml } from \"./format.js\";\nimport {\n ATTR_CELL,\n ATTR_KEY,\n ATTR_LIST,\n ATTR_PAGE,\n ATTR_ROW,\n ATTR_UNKNOWN_PAGE,\n HUB_TAG_NAME,\n} from \"../core/lb-constants.js\";\nimport type { PageFile } from \"./locations.js\";\nimport type { OriginScan } from \"./origins.js\";\n\n/**\n * The hub calls showModal() on whatever carries lb-unknown-page\n * (hub/lb-hub.browser.ts), so a chrome that puts the attribute on anything\n * else would fail at runtime, on whichever page happens to 404 first, rather\n * than at build time. The attribute is optional; a wrong element on it, or a\n * right one out of the hub's reach, is an error.\n *\n * Out of reach because the hub looks for the dialog inside itself, as it\n * does for everything else it acts on. A dialog elsewhere in the body would\n * never open and would say nothing about why.\n */\nfunction checkUnknownPageDialog(\n chromeFile: string,\n expandedBody: string,\n): void {\n const fragment = JSDOM.fragment(expandedBody);\n const el = fragment.querySelector(`[${ATTR_UNKNOWN_PAGE}]`);\n if (!el) return;\n if (el.tagName !== \"DIALOG\") {\n throw new Error(\n `assemble: '${chromeFile}' declares ${ATTR_UNKNOWN_PAGE} on ` +\n `<${el.tagName.toLowerCase()}>, must be a <dialog>`,\n );\n }\n if (!el.closest(HUB_TAG_NAME)) {\n throw new Error(\n `assemble: '${chromeFile}' declares ${ATTR_UNKNOWN_PAGE} outside ` +\n `<${HUB_TAG_NAME}>, where the hub cannot reach it`,\n );\n }\n}\n\n/**\n * One scope while the binding check walks a document: which rung it is, the\n * name it carries, and whether a list has found its row template yet.\n */\ninterface Scope {\n kind: \"list\" | \"row\";\n name: string;\n}\n\n/**\n * The binding vocabulary, checked once against the finished document rather\n * than discovered one console error at a time in a browser.\n *\n * Everything here is a fact the markup states about itself, so none of it\n * needs the server: a key is named on a row template, a row template belongs\n * to a list, and a cell belongs to a scope. What the markup and the server\n * say about cardinality cannot be compared here, because they are separate\n * artifacts — createHub holds that check. See docs/reference/data-binding.md.\n *\n * A list scope with no row template is not an error. It is bound to the list\n * without displaying it, which is what an insert form naming the list it adds\n * to already is.\n *\n * A `<template>`'s children are walked in the scope that encloses the\n * template, because a row template's clones land in the list around it.\n */\nfunction walkBinding(\n node: ParentNode,\n scope: Scope | null,\n problems: string[],\n): void {\n for (const el of node.children) {\n const listName = el.getAttribute(ATTR_LIST);\n const rowName = el.getAttribute(ATTR_ROW);\n const tag = el.localName;\n let here = scope;\n\n if (listName !== null && rowName !== null) {\n problems.push(\n `<${tag}> carries both ${ATTR_LIST} and ${ATTR_ROW}; a scope is ` +\n `one or the other, and a name answers with one shape`,\n );\n }\n if (listName !== null) here = { kind: \"list\", name: listName };\n else if (rowName !== null) here = { kind: \"row\", name: rowName };\n\n if (el.hasAttribute(ATTR_CELL) && here === null) {\n problems.push(\n `<${tag}> carries ${ATTR_CELL}=\"${el.getAttribute(ATTR_CELL)}\" with ` +\n `no ${ATTR_LIST} or ${ATTR_ROW} scope around it`,\n );\n }\n\n if (el.hasAttribute(ATTR_KEY)) {\n if (tag !== \"template\") {\n problems.push(\n `<${tag}> carries ${ATTR_KEY}; it names the column that identifies ` +\n `a row and belongs on the row <template>`,\n );\n } else if (here === null || here.kind !== \"list\") {\n problems.push(\n `a <template> carries ${ATTR_KEY} outside any ${ATTR_LIST} scope; ` +\n `only a list has rows to identify`,\n );\n }\n }\n\n walkBinding(el, here, problems);\n // By tag name rather than by instanceof: JSDOM.fragment() parses into a\n // document with no window, so the constructor is not reachable from the\n // node.\n if (tag === \"template\") {\n walkBinding((el as HTMLTemplateElement).content, here, problems);\n }\n }\n}\n\n/** Check one expanded fragment, and name the file if anything is wrong. */\nfunction checkBinding(file: string, html: string): void {\n const problems: string[] = [];\n walkBinding(JSDOM.fragment(html), null, problems);\n if (problems.length > 0) {\n throw new Error(`assemble: '${file}' binding:\\n ` + problems.join(`\\n `));\n }\n}\n\nexport interface AssembleOptions {\n chromeFile: string;\n pages: PageFile[];\n /** Every origin, already scanned, in cascade order — see build/origins.ts. */\n scans: OriginScan[];\n}\n\nexport interface Assembled {\n html: string;\n /** Every custom element tag left in the document once expansion is done. */\n usedTags: Set<string>;\n}\n\nexport async function assemble({\n chromeFile,\n pages,\n scans,\n}: AssembleOptions): Promise<Assembled> {\n // One layer per origin, in order, so a later origin's definition overrides\n // an earlier one's for the same tag. Each layer is checked for cycles as it\n // lands, against everything merged so far.\n let defs: Definitions = {};\n for (const scan of scans) {\n defs = await addDefinitions(defs, scan.definitions);\n }\n\n const templates: string[] = [];\n for (const { name, file } of pages) {\n const authored = await readFile(file, \"utf-8\");\n const host = expand(authored, defs);\n checkBinding(file, host);\n templates.push(`<template ${ATTR_PAGE}=\"${name}\">${host}</template>`);\n }\n\n let chromeSource: string;\n try {\n chromeSource = await readFile(chromeFile, \"utf-8\");\n } catch {\n throw new Error(`assemble: no chrome file at '${chromeFile}'`);\n }\n\n const body = /<body([^>]*)>([\\s\\S]*)<\\/body>/i.exec(chromeSource);\n if (!body) {\n throw new Error(\n `assemble: '${chromeFile}' has no <body>...</body> to expand`,\n );\n }\n const [whole, bodyAttrs, bodyContent] = body;\n const expandedBody = expand(bodyContent!, defs);\n checkUnknownPageDialog(chromeFile, expandedBody);\n checkBinding(chromeFile, expandedBody);\n const withPages = chromeSource.replace(\n whole!,\n () => `<body${bodyAttrs}>${expandedBody}\\n${templates.join(\"\\n\")}\\n</body>`,\n );\n\n return {\n html: await formatHtml(withPages),\n usedTags: customElementTags(withPages),\n };\n}\n"]}
@@ -7,7 +7,7 @@
7
7
  * loadbare-app-build [--src src] [--out dist] [--watch] [--minify]
8
8
  *
9
9
  * `--src` names the application's own tree: the one `chrome.html` file,
10
- * every `*.page.html` page, its `*.hooks.ts`/`*.queries.ts` pairs, and
10
+ * every `*.page.html` page, its `*.requests.ts`/`*.queries.ts` pairs, and
11
11
  * `imports.ts` if there is one — see build/locations.ts. It is also the last
12
12
  * origin in the cascade that supplies widget definitions, widget scripts and
13
13
  * stylesheets; this package's own widgets come first and each package named
@@ -17,7 +17,7 @@
17
17
  * The assembled document is written to `<out>/app.html`; the client bundle
18
18
  * to `<out>/client.js`, by way of a generated `<out>/client-entry.ts` — see
19
19
  * build/elements.ts for how a tag is resolved to the script that registers
20
- * it. When any page has a hooks or queries file, the hub registry that used
20
+ * it. When any page has a requests or queries file, the hub registry that used
21
21
  * to be hand-maintained is written to `<out>/pages.ts` — see build/pages.ts.
22
22
  * Every origin's stylesheets are concatenated in that same cascade order and
23
23
  * written to `<out>/app.css` — see build/styles.ts.
package/dist/build/cli.js CHANGED
@@ -7,7 +7,7 @@
7
7
  * loadbare-app-build [--src src] [--out dist] [--watch] [--minify]
8
8
  *
9
9
  * `--src` names the application's own tree: the one `chrome.html` file,
10
- * every `*.page.html` page, its `*.hooks.ts`/`*.queries.ts` pairs, and
10
+ * every `*.page.html` page, its `*.requests.ts`/`*.queries.ts` pairs, and
11
11
  * `imports.ts` if there is one — see build/locations.ts. It is also the last
12
12
  * origin in the cascade that supplies widget definitions, widget scripts and
13
13
  * stylesheets; this package's own widgets come first and each package named
@@ -17,7 +17,7 @@
17
17
  * The assembled document is written to `<out>/app.html`; the client bundle
18
18
  * to `<out>/client.js`, by way of a generated `<out>/client-entry.ts` — see
19
19
  * build/elements.ts for how a tag is resolved to the script that registers
20
- * it. When any page has a hooks or queries file, the hub registry that used
20
+ * it. When any page has a requests or queries file, the hub registry that used
21
21
  * to be hand-maintained is written to `<out>/pages.ts` — see build/pages.ts.
22
22
  * Every origin's stylesheets are concatenated in that same cascade order and
23
23
  * written to `<out>/app.css` — see build/styles.ts.
@@ -123,3 +123,4 @@ async function main() {
123
123
  }
124
124
  }
125
125
  void main();
126
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../build/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n/**\n * loadbare-app-build — assemble a chrome file and its pages into one HTML\n * document, and bundle the client script for exactly the custom elements\n * that document uses. See docs/tutorials/000-getting-started.md.\n *\n * loadbare-app-build [--src src] [--out dist] [--watch] [--minify]\n *\n * `--src` names the application's own tree: the one `chrome.html` file,\n * every `*.page.html` page, its `*.requests.ts`/`*.queries.ts` pairs, and\n * `imports.ts` if there is one — see build/locations.ts. It is also the last\n * origin in the cascade that supplies widget definitions, widget scripts and\n * stylesheets; this package's own widgets come first and each package named\n * in `imports.ts` in between, all scanned the same way — see\n * build/origins.ts.\n *\n * The assembled document is written to `<out>/app.html`; the client bundle\n * to `<out>/client.js`, by way of a generated `<out>/client-entry.ts` — see\n * build/elements.ts for how a tag is resolved to the script that registers\n * it. When any page has a requests or queries file, the hub registry that used\n * to be hand-maintained is written to `<out>/pages.ts` — see build/pages.ts.\n * Every origin's stylesheets are concatenated in that same cascade order and\n * written to `<out>/app.css` — see build/styles.ts.\n *\n * `--minify` minifies both output files: esbuild's own minifier for\n * `client.js`, as part of the same bundling pass, and esbuild's CSS\n * transform for `app.css`, as a pass over the concatenation above. `app.html`\n * is never minified — it is markup an application may still want to read.\n *\n * Without `--watch`, this runs once and exits. With it, every `.html`,\n * `.ts` and `.css` file under `<src>` is watched — a page, a widget's\n * markup, a widget's class, a stylesheet, and `imports.ts` all feed the\n * same build — and a rebuild that throws is reported without stopping the\n * watch. There is no dev server and no browser reload here, only the files\n * on disk staying current for the next manual refresh.\n */\n\nimport { parseArgs } from \"node:util\";\nimport { mkdir, watch, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport * as esbuild from \"esbuild\";\nimport { assemble } from \"./assemble.js\";\nimport { clientEntrySource, resolveElements } from \"./elements.js\";\nimport { resolveLocations } from \"./locations.js\";\nimport { cssFrom, scanAll } from \"./origins.js\";\nimport { pagesSource } from \"./pages.js\";\nimport { stylesSource } from \"./styles.js\";\n\nconst { values } = parseArgs({\n options: {\n src: { type: \"string\", default: \"src\" },\n out: { type: \"string\", default: \"dist\" },\n watch: { type: \"boolean\", default: false },\n minify: { type: \"boolean\", default: false },\n },\n});\n\nconst srcDir = values.src!;\n\nasync function build(): Promise<void> {\n const locations = await resolveLocations({ src: srcDir, out: values.out! });\n const outFile = path.join(locations.outDir, \"app.html\");\n const entryFile = path.join(locations.outDir, \"client-entry.ts\");\n const clientFile = path.join(locations.outDir, \"client.js\");\n\n const scans = await scanAll(locations, srcDir, locations.importsFile);\n\n const { html, usedTags } = await assemble({ ...locations, scans });\n await mkdir(locations.outDir, { recursive: true });\n await writeFile(outFile, html, \"utf-8\");\n\n const { resolved } = resolveElements({ used: usedTags, scans });\n await writeFile(\n entryFile,\n clientEntrySource(resolved, locations.outDir),\n \"utf-8\",\n );\n await esbuild.build({\n entryPoints: [entryFile],\n bundle: true,\n format: \"iife\",\n outfile: clientFile,\n minify: values.minify,\n });\n\n const generatedPages = pagesSource(\n locations.pages,\n locations.pageSources,\n locations.outDir,\n locations.packageName,\n );\n let pagesMessage = \"\";\n if (generatedPages) {\n const pagesFile = path.join(locations.outDir, \"pages.ts\");\n await writeFile(pagesFile, generatedPages, \"utf-8\");\n pagesMessage = ` and ${pagesFile}`;\n }\n\n const concatenatedCss = await stylesSource(cssFrom(scans));\n let cssMessage = \"\";\n if (concatenatedCss) {\n const cssFile = path.join(locations.outDir, \"app.css\");\n const generatedCss = values.minify\n ? (\n await esbuild.transform(concatenatedCss, {\n loader: \"css\",\n minify: true,\n })\n ).code\n : concatenatedCss;\n await writeFile(cssFile, generatedCss, \"utf-8\");\n cssMessage = ` and ${cssFile}`;\n }\n\n console.log(\n `loadbare-app-build: wrote ${outFile} and ${clientFile}${pagesMessage}${cssMessage} ` +\n `(${resolved.size} custom element${resolved.size === 1 ? \"\" : \"s\"})`,\n );\n}\n\nasync function main(): Promise<void> {\n try {\n await build();\n } catch (err) {\n console.error(`loadbare-app-build: ${(err as Error).message}`);\n if (!values.watch) {\n process.exitCode = 1;\n return;\n }\n }\n\n if (!values.watch) return;\n\n console.log(`loadbare-app-build: watching ${srcDir}`);\n const watcher = watch(srcDir, { recursive: true });\n for await (const event of watcher) {\n if (\n !event.filename?.endsWith(\".html\") &&\n !event.filename?.endsWith(\".ts\") &&\n !event.filename?.endsWith(\".css\")\n )\n continue;\n try {\n await build();\n } catch (err) {\n console.error(`loadbare-app-build: ${(err as Error).message}`);\n }\n }\n}\n\nvoid main();\n"]}
@@ -86,3 +86,4 @@ export function clientEntrySource(resolved, outDir) {
86
86
  }
87
87
  return lines.join("\n") + "\n";
88
88
  }
89
+ //# sourceMappingURL=elements.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"elements.js","sourceRoot":"","sources":["../../build/elements.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validating that a custom element a build actually uses can do something —\n * and finding the script to import for it when it can.\n *\n * A used tag is valid if it has a script, a definition, or both:\n * - a script registers the class that upgrades it.\n * - a definition (an `.html` file) expands the tag's content at build\n * time. A widget with only a definition and no script is markup only:\n * expansion already gave it everything it will ever have, and the tag\n * survives as an inert wrapper (see expand.ts).\n *\n * A tag with neither — no script anywhere and no definition — would do\n * nothing were it ever to reach the browser, so that is an error. When a\n * module named after the tag exists but never said `.browser`, the error\n * names that file: forgetting the word is the ordinary way to arrive here,\n * and it is the one an author can fix in a rename.\n *\n * Both halves are looked for in the same ordered origins, and the later\n * origin wins: an application's own script overrides one from a package it\n * imported, which overrides a built-in. Two files claiming one tag *within*\n * one origin is the error case, and build/origins.ts raises it while\n * scanning. Nothing is scanned here.\n */\n\nimport path from \"node:path\";\nimport type { OriginScan } from \"./origins.js\";\n\nexport { loadElementManifest, type ElementManifest } from \"./origins.js\";\n\n/** A resolved tag: a bare specifier to import, or a local file to import by relative path. */\nexport type Resolved =\n { kind: \"specifier\"; value: string } | { kind: \"file\"; value: string };\n\nexport interface ResolveOptions {\n used: Set<string>;\n /** Every origin, already scanned, in cascade order — see build/origins.ts. */\n scans: OriginScan[];\n}\n\nexport interface ResolveResult {\n resolved: Map<string, Resolved>;\n}\n\nexport function resolveElements({\n used,\n scans,\n}: ResolveOptions): ResolveResult {\n const resolved = new Map<string, Resolved>();\n\n for (const tag of used) {\n let script: Resolved | undefined;\n let hasDefinition = false;\n\n // In order, so the last origin holding a script is the one that wins.\n for (const scan of scans) {\n if (scan.definitions.has(tag)) hasDefinition = true;\n const found = scan.scripts.get(tag);\n if (found === undefined) continue;\n script = scan.specifier\n ? { kind: \"specifier\", value: scan.specifier(tag) }\n : { kind: \"file\", value: found };\n }\n\n if (script) {\n resolved.set(tag, script);\n continue;\n }\n // No script anywhere. Valid only if a definition gave the tag its\n // content — otherwise nothing, ever, would upgrade or fill it, and the\n // tag would do nothing were it to reach the browser.\n if (!hasDefinition) {\n // The likeliest reason, checked first: a module named after the tag is\n // sitting right there without the word that would ship it. Say which\n // file, because \"there is no script\" reads as a lie to someone looking\n // straight at one.\n for (const scan of scans) {\n const near = scan.unmarked.get(tag);\n if (near === undefined) continue;\n throw new Error(\n `build: <${tag}> has no script, but '${near}' is named after it. ` +\n `A script reaches the browser only when its name says so — ` +\n `rename it '${path.basename(near, path.extname(near))}.browser` +\n `${path.extname(near)}' if that is what it is.`,\n );\n }\n throw new Error(\n `build: <${tag}> has neither a script nor a definition, so it would ` +\n `do nothing (checked ${scans.map((s) => `'${s.name}'`).join(\", \")})`,\n );\n }\n }\n\n return { resolved };\n}\n\n/**\n * The generated client entry point — every custom element the assembled\n * document actually uses, and nothing else. Written to `outDir` so a\n * relative import to a local widget resolves the ordinary way; a specifier\n * (built-in or third-party) is written as the bare string it is.\n */\nexport function clientEntrySource(\n resolved: Map<string, Resolved>,\n outDir: string,\n): string {\n const lines: string[] = [];\n for (const tag of [...resolved.keys()].sort()) {\n const entry = resolved.get(tag)!;\n if (entry.kind === \"specifier\") {\n lines.push(`import ${JSON.stringify(entry.value)};`);\n } else {\n const rel = path.relative(outDir, entry.value).split(path.sep).join(\"/\");\n lines.push(\n `import ${JSON.stringify(rel.startsWith(\".\") ? rel : `./${rel}`)};`,\n );\n }\n }\n return lines.join(\"\\n\") + \"\\n\";\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"expand.d.ts","sourceRoot":"","sources":["../../build/expand.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAkDH,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AA8BjD;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CAmB1E;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACzB,OAAO,CAAC,WAAW,CAAC,CAStB;AA4CD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAS3D;AAmPD;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,CAIhE"}
1
+ {"version":3,"file":"expand.d.ts","sourceRoot":"","sources":["../../build/expand.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAkCH,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AA8BjD;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CAmB1E;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACzB,OAAO,CAAC,WAAW,CAAC,CAStB;AA4CD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAS3D;AA8OD;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,CAIhE"}
@@ -11,23 +11,9 @@
11
11
  */
12
12
  import { readdir, readFile } from "node:fs/promises";
13
13
  import { JSDOM } from "jsdom";
14
- import { ATTR_SLOT, ATTR_TEMPLATE, HUB_TAG_NAME, } from "../core/lb-constants.js";
15
- /**
16
- * The framework's reserved namespace. Every one of these must resolve to a
17
- * definition — a typo here is caught immediately rather than shipped as an
18
- * inert tag. A tag outside this namespace still expands when a definition
19
- * exists for it (an application is free to name its own widgets anything),
20
- * but is not required to have one: with no definition, it is assumed to be
21
- * a plain custom element, registered by script alone.
22
- */
23
- const WIDGET_TAG = /^LB-/;
24
- /**
25
- * The one LB-* tag that is not a widget: the hub itself, always present in
26
- * the chrome, registered by script rather than by definition. Expanding the
27
- * chrome (see build/assemble.ts) would otherwise demand a definition for it
28
- * that could never exist.
29
- */
30
- const HUB_TAG = HUB_TAG_NAME.toUpperCase();
14
+ import { ATTR_SLOT, ATTR_TEMPLATE } from "../core/lb-constants.js";
15
+ /** Lowercase, hyphenated — the one shape a custom element tag has. */
16
+ const CUSTOM_ELEMENT = /-/;
31
17
  /** A placeholder is an entire attribute value or an entire text node. */
32
18
  /**
33
19
  * `{{name}}` or `{{name|default}}`. The default is a literal that stands in
@@ -40,8 +26,11 @@ const HUB_TAG = HUB_TAG_NAME.toUpperCase();
40
26
  const PLACEHOLDER = /^\{\{\s*([A-Za-z][\w-]*)\s*(?:\|\s*([^}]*?)\s*)?\}\}$/;
41
27
  /**
42
28
  * Marks an attribute as input to expansion. Three namespaces share the tag:
43
- * `lb-` is the hub's, `exp-` is expansion's, and everything unprefixed is
44
- * HTML's and means what HTML says it means. So an author is free to write
29
+ * `lb-` holds the names Loadbare defines, `exp-` the names a widget author
30
+ * defines, and everything unprefixed is HTML's and means what HTML says it
31
+ * means. The split is by owner rather than by phase — `lb-slot` and
32
+ * `lb-template` are expansion's own and never reach the browser, while
33
+ * `lb-list` and `lb-cell` are read at both times. So an author is free to write
45
34
  * `class`, `title` or `hidden` on any widget, whatever its parameters happen
46
35
  * to be named. The prefix marks the slot on the tag, where the ambiguity was;
47
36
  * inside a definition `{{ }}` already says the same thing, so `exp-label`
@@ -179,13 +168,13 @@ export function customElementTags(html) {
179
168
  }
180
169
  return tags;
181
170
  }
182
- /** Which widgets a definition uses. Undefined tags are caught during expansion. */
183
- function referencesOf(source) {
171
+ /** Which definitions a definition uses. A tag with no definition expands to nothing and cannot cycle. */
172
+ function referencesOf(source, defs) {
184
173
  const refs = new Set();
185
174
  for (const scope of scopes(parse(source))) {
186
175
  for (const el of scope.querySelectorAll("*")) {
187
176
  const tag = el.tagName.toUpperCase();
188
- if (WIDGET_TAG.test(tag))
177
+ if (CUSTOM_ELEMENT.test(tag) && tag in defs)
189
178
  refs.add(tag);
190
179
  }
191
180
  }
@@ -198,7 +187,7 @@ function referencesOf(source) {
198
187
  function checkAcyclic(defs) {
199
188
  const refs = new Map();
200
189
  for (const [tag, source] of Object.entries(defs)) {
201
- refs.set(tag, referencesOf(source));
190
+ refs.set(tag, referencesOf(source, defs));
202
191
  }
203
192
  const done = new Set();
204
193
  const path = [];
@@ -335,12 +324,8 @@ function fillSlot(tag, root, content) {
335
324
  slot.append(...content);
336
325
  slot.removeAttribute(ATTR_SLOT);
337
326
  }
338
- function expandElement(el, defs) {
327
+ function expandElement(el, defs, source) {
339
328
  const tag = el.tagName.toUpperCase();
340
- const source = defs[tag];
341
- if (source === undefined) {
342
- throw new Error(`expand: <${tag.toLowerCase()}> has no definition. Every hub tag must resolve.`);
343
- }
344
329
  // The tag survives expansion carrying every attribute the author wrote,
345
330
  // `exp-` ones included. They cost nothing where they are, and what ships
346
331
  // shows what was asked for beside what it produced.
@@ -369,10 +354,12 @@ function expandElement(el, defs) {
369
354
  }
370
355
  function walk(root, defs) {
371
356
  for (const child of [...root.children]) {
372
- const tag = child.tagName.toUpperCase();
373
- if (tag in defs || (WIDGET_TAG.test(tag) && tag !== HUB_TAG)) {
374
- expandElement(child, defs);
375
- }
357
+ const source = defs[child.tagName.toUpperCase()];
358
+ // A tag with no definition is left alone. It is a custom element a
359
+ // script registers, and build/elements.ts is where a tag that has
360
+ // neither a definition nor a script is refused — one rule, one place.
361
+ if (source !== undefined)
362
+ expandElement(child, defs, source);
376
363
  // Definitions may use other widgets, so descend after expanding. The
377
364
  // acyclicity check is what makes this terminate.
378
365
  walk(child, defs);
@@ -393,3 +380,4 @@ export function expand(source, defs) {
393
380
  walk(tree, defs);
394
381
  return tree.innerHTML;
395
382
  }
383
+ //# sourceMappingURL=expand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expand.js","sourceRoot":"","sources":["../../build/expand.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Expansion — see docs/reference/custom-elements.md, \"HTML\".\n *\n * A page author writes one tag and gets the tree it stands for. Expansion\n * runs at build time, before any request exists, so it has no data. That is\n * why there is no control flow here: nothing to branch on.\n *\n * Substitution is applied to a parsed tree with setAttribute and node values,\n * never to a string. A value applied through the DOM API cannot be reparsed\n * as markup, so injection is impossible by construction.\n */\n\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { JSDOM } from \"jsdom\";\nimport { ATTR_SLOT, ATTR_TEMPLATE } from \"../core/lb-constants.js\";\n\n/** Lowercase, hyphenated — the one shape a custom element tag has. */\nconst CUSTOM_ELEMENT = /-/;\n\n/** A placeholder is an entire attribute value or an entire text node. */\n/**\n * `{{name}}` or `{{name|default}}`. The default is a literal that stands in\n * when the tag supplies nothing; it is not evaluated and nothing branches on\n * it. Whitespace around the name and the default is not part of either, so\n * `{{button-text | OK}}` is the same placeholder as `{{button-text|OK}}` —\n * a spaced form that shipped as literal braces would be a silent failure\n * an author would find in the browser.\n */\nconst PLACEHOLDER = /^\\{\\{\\s*([A-Za-z][\\w-]*)\\s*(?:\\|\\s*([^}]*?)\\s*)?\\}\\}$/;\n\n/**\n * Marks an attribute as input to expansion. Three namespaces share the tag:\n * `lb-` holds the names Loadbare defines, `exp-` the names a widget author\n * defines, and everything unprefixed is HTML's and means what HTML says it\n * means. The split is by owner rather than by phase — `lb-slot` and\n * `lb-template` are expansion's own and never reach the browser, while\n * `lb-list` and `lb-cell` are read at both times. So an author is free to write\n * `class`, `title` or `hidden` on any widget, whatever its parameters happen\n * to be named. The prefix marks the slot on the tag, where the ambiguity was;\n * inside a definition `{{ }}` already says the same thing, so `exp-label`\n * fills `{{label}}`.\n */\nconst EXP_PREFIX = \"exp-\";\n\nexport type Definitions = Record<string, string>;\n\nconst dom = new JSDOM(\"\");\nconst doc = dom.window.document;\nconst { Element, HTMLTemplateElement, Node } = dom.window;\n\nfunction parse(source: string): HTMLElement {\n const holder = doc.createElement(\"div\");\n holder.innerHTML = source;\n return holder as unknown as HTMLElement;\n}\n\n/**\n * A tree and the content of every template in it, however deep.\n *\n * A template's children are parsed into a fragment rather than into the tree,\n * so `querySelectorAll` does not reach them. Everything expansion does has to\n * reach them anyway: a definition may supply its own row template, and\n * `lb-picker` does, so `{{key}}` written inside one has to be a\n * placeholder rather than literal text that ships. `walk` already descends\n * into template content to expand widget tags; this is the same rule stated\n * once for every pass that reads a tree.\n */\nfunction* scopes(root: ParentNode): Generator<ParentNode> {\n yield root;\n for (const el of root.querySelectorAll(\"template\")) {\n yield* scopes((el as HTMLTemplateElement).content);\n }\n}\n\n/**\n * Load one definition per file, named by the tag it defines.\n *\n * Later directories win, so an application can override a built-in widget.\n */\nexport async function loadDefinitions(dirs: string[]): Promise<Definitions> {\n const defs: Definitions = {};\n for (const dir of dirs) {\n let names: string[];\n try {\n names = await readdir(dir);\n } catch {\n continue;\n }\n for (const name of names) {\n if (!name.endsWith(\".html\")) continue;\n const file = `${dir}/${name}`;\n const source = await readFile(file, \"utf-8\");\n checkNames(file, source);\n defs[name.slice(0, -\".html\".length).toUpperCase()] = source;\n }\n }\n checkAcyclic(defs);\n return defs;\n}\n\n/**\n * Overlay definitions read from explicit files, each already keyed by the\n * tag it defines — for a source, like an application's own `src` tree, that\n * is discovered file by file rather than listed by directory. A tag here\n * overrides the same tag in `defs`, the same override `loadDefinitions`\n * gives a later directory over an earlier one.\n */\nexport async function addDefinitions(\n defs: Definitions,\n files: Map<string, string>,\n): Promise<Definitions> {\n const merged = { ...defs };\n for (const [tag, file] of files) {\n const source = await readFile(file, \"utf-8\");\n checkNames(file, source);\n merged[tag.toUpperCase()] = source;\n }\n checkAcyclic(merged);\n return merged;\n}\n\n/** Every placeholder a definition declares. */\nfunction placeholdersOf(source: string): string[] {\n const names: string[] = [];\n for (const scope of scopes(parse(source))) {\n for (const el of scope.querySelectorAll(\"*\")) {\n for (const attr of el.attributes) {\n const match = PLACEHOLDER.exec(attr.value.trim());\n if (match) names.push(match[1]!);\n }\n }\n const walker = doc.createTreeWalker(scope as globalThis.Node, 4 /* TEXT */);\n while (walker.nextNode()) {\n const match = PLACEHOLDER.exec((walker.currentNode as Text).data.trim());\n if (match) names.push(match[1]!);\n }\n }\n return names;\n}\n\n/**\n * A placeholder must be spelled the way a tag can supply it.\n *\n * HTML lowercases attribute names, so `exp-inputClass` reaches expansion as\n * `exp-inputclass` and could never fill `{{inputClass}}`. A definition is the\n * only file that can hold such a name and the only one that can fix it, so it\n * is rejected here, once, rather than at every page that uses the widget.\n *\n * Recognition stays broad on purpose. Narrowing PLACEHOLDER to lowercase would\n * make `{{inputClass}}` not a placeholder at all, and ship it to the browser as\n * literal text — the same silent failure somewhere new.\n */\nfunction checkNames(file: string, source: string): void {\n for (const name of placeholdersOf(source)) {\n if (!/[A-Z]/.test(name)) continue;\n const kebab = name.replace(/([a-z0-9])([A-Z])/g, \"$1-$2\").toLowerCase();\n throw new Error(\n `expand: ${file} declares {{${name}}}, but HTML lowercases attribute ` +\n `names, so no tag can supply it. Write {{${kebab}}}.`,\n );\n }\n}\n\n/**\n * Every hyphenated tag name left in a document, template content included.\n *\n * Meant to run on already-expanded output: an `LB-*` tag with a definition\n * is gone by then, replaced by the tree it stands for, so what remains is\n * whatever is left for the browser to upgrade itself — a built-in widget\n * class, an application's own custom element, or one from a package. A\n * hyphen in the tag name is the one thing a custom element is guaranteed to\n * have, per the Custom Elements spec, and nothing native has one.\n */\nexport function customElementTags(html: string): Set<string> {\n const tags = new Set<string>();\n for (const scope of scopes(parse(html))) {\n for (const el of scope.querySelectorAll(\"*\")) {\n const tag = el.tagName.toLowerCase();\n if (tag.includes(\"-\")) tags.add(tag);\n }\n }\n return tags;\n}\n\n/** Which definitions a definition uses. A tag with no definition expands to nothing and cannot cycle. */\nfunction referencesOf(source: string, defs: Definitions): string[] {\n const refs = new Set<string>();\n for (const scope of scopes(parse(source))) {\n for (const el of scope.querySelectorAll(\"*\")) {\n const tag = el.tagName.toUpperCase();\n if (CUSTOM_ELEMENT.test(tag) && tag in defs) refs.add(tag);\n }\n }\n return [...refs];\n}\n\n/**\n * Expansion is a fixpoint, so it terminates only on an acyclic graph. A depth\n * cap would hide the bug behind a limit instead of reporting it.\n */\nfunction checkAcyclic(defs: Definitions): void {\n const refs = new Map<string, string[]>();\n for (const [tag, source] of Object.entries(defs)) {\n refs.set(tag, referencesOf(source, defs));\n }\n const done = new Set<string>();\n const path: string[] = [];\n\n const visit = (tag: string): void => {\n if (done.has(tag)) return;\n const cycle = path.indexOf(tag);\n if (cycle !== -1) {\n throw new Error(\n `expand: cycle in the definition graph: ` +\n [...path.slice(cycle), tag].map((t) => t.toLowerCase()).join(\" → \"),\n );\n }\n path.push(tag);\n for (const ref of refs.get(tag) ?? []) visit(ref);\n path.pop();\n done.add(tag);\n };\n\n for (const tag of refs.keys()) visit(tag);\n}\n\n/**\n * Resolve a placeholder against what the author wrote: the supplied value,\n * else the definition's default, else absent.\n *\n * Every name the definition asks for is recorded, whether or not it was\n * supplied, so expansion can report a parameter the definition never declared.\n */\nfunction resolve(\n text: string,\n attrs: Map<string, string>,\n asked: Set<string>,\n): string | null {\n const match = PLACEHOLDER.exec(text.trim());\n if (!match) return text;\n const name = match[1]!;\n asked.add(name);\n return attrs.get(name) ?? match[2] ?? null;\n}\n\nfunction substitute(\n root: ParentNode,\n attrs: Map<string, string>,\n asked: Set<string>,\n): void {\n for (const scope of scopes(root)) {\n for (const el of scope.querySelectorAll(\"*\")) {\n for (const attr of [...el.attributes]) {\n const value = resolve(attr.value, attrs, asked);\n // Presence propagates: a placeholder with nothing behind it drops the\n // attribute rather than emitting it empty, which is what HTML's own\n // boolean-attribute semantics require.\n if (value === null) el.removeAttribute(attr.name);\n else if (value !== attr.value) el.setAttribute(attr.name, value);\n }\n }\n\n const walker = doc.createTreeWalker(scope as globalThis.Node, 4 /* TEXT */);\n const texts: Text[] = [];\n while (walker.nextNode()) texts.push(walker.currentNode as Text);\n for (const text of texts) {\n const raw = text.data;\n if (!PLACEHOLDER.test(raw.trim())) continue;\n const value = resolve(raw, attrs, asked) ?? \"\";\n // Keep the author's surrounding whitespace: \"{{label}} <input>\" should\n // still separate the label from the control.\n const lead = raw.slice(0, raw.length - raw.trimStart().length);\n const tail = raw.slice(raw.trimEnd().length);\n text.data = lead + value + tail;\n }\n }\n}\n\n/**\n * Fill the definition's named destinations from the authored templates that\n * name them, and hand back the content that is left for the slot.\n *\n * A destination is filled with the template's contents, not with the\n * template, so what ships is ordinary markup in an ordinary place — a real\n * `<tr>` inside a real `<thead>`, visible in view-source like everything\n * else. The wrapper existed only to survive the parser and is gone.\n *\n * Unlike the slot, there may be several, because the reason for wrapping is\n * per-destination: a table has a head and a body, and each needs its own\n * markup past the same obstacle. They are matched by name, and an empty name\n * is a name, so a widget with one destination costs the author nothing.\n */\nfunction fillTemplates(\n tag: string,\n root: ParentNode,\n content: globalThis.Node[],\n): globalThis.Node[] {\n const destinations = new Map<string, Element>();\n for (const el of root.querySelectorAll(`[${ATTR_TEMPLATE}]`)) {\n const name = el.getAttribute(ATTR_TEMPLATE)!;\n if (destinations.has(name)) {\n throw new Error(\n `expand: <${tag.toLowerCase()}> declares two ${ATTR_TEMPLATE}=\"${name}\" destinations`,\n );\n }\n destinations.set(name, el);\n el.removeAttribute(ATTR_TEMPLATE);\n }\n\n const rest: globalThis.Node[] = [];\n const filled = new Set<string>();\n for (const node of content) {\n const named =\n node instanceof HTMLTemplateElement && node.hasAttribute(ATTR_TEMPLATE);\n if (!named) {\n rest.push(node);\n continue;\n }\n const template = node as HTMLTemplateElement;\n const name = template.getAttribute(ATTR_TEMPLATE)!;\n const destination = destinations.get(name);\n if (!destination) {\n throw new Error(\n `expand: a <template ${ATTR_TEMPLATE}=\"${name}\"> was written inside ` +\n `<${tag.toLowerCase()}>, whose definition has no destination by that name.`,\n );\n }\n if (filled.has(name)) {\n throw new Error(\n `expand: <${tag.toLowerCase()}> was given two templates for '${name}'`,\n );\n }\n filled.add(name);\n destination.append(template.content);\n }\n return rest;\n}\n\n/** Place authored inner content in the single slot, if the definition has one. */\nfunction fillSlot(\n tag: string,\n root: ParentNode,\n content: globalThis.Node[],\n): void {\n const slots = root.querySelectorAll(`[${ATTR_SLOT}]`);\n if (slots.length > 1) {\n throw new Error(\n `expand: <${tag.toLowerCase()}> declares more than one ${ATTR_SLOT}`,\n );\n }\n if (slots.length === 0) {\n // Whitespace is not content. An author who writes only named templates\n // still leaves the newlines between them, and those are the file's\n // formatting rather than something anybody meant to place.\n const wrote = content.some(\n (node) => node.nodeType !== Node.TEXT_NODE || (node as Text).data.trim(),\n );\n if (wrote) {\n throw new Error(\n `expand: content was written inside <${tag.toLowerCase()}>, whose definition has no ${ATTR_SLOT}`,\n );\n }\n return;\n }\n const slot = slots[0]!;\n // The authored nodes move rather than copy. The caller replaces the tag's\n // children with the expanded tree immediately after, so there is nothing\n // left behind for a clone to protect.\n slot.append(...content);\n slot.removeAttribute(ATTR_SLOT);\n}\n\nfunction expandElement(el: Element, defs: Definitions, source: string): void {\n const tag = el.tagName.toUpperCase();\n\n // The tag survives expansion carrying every attribute the author wrote,\n // `exp-` ones included. They cost nothing where they are, and what ships\n // shows what was asked for beside what it produced.\n const attrs = new Map<string, string>();\n for (const attr of el.attributes) {\n if (!attr.name.startsWith(EXP_PREFIX)) continue;\n attrs.set(attr.name.slice(EXP_PREFIX.length), attr.value);\n }\n\n const tree = parse(source);\n const asked = new Set<string>();\n substitute(tree, attrs, asked);\n\n // A parameter the definition never declared is a name that resolves to\n // nothing. Unprefixed attributes could not support this check, because a\n // stray one is indistinguishable from a deliberate HTML attribute.\n for (const name of attrs.keys()) {\n if (asked.has(name)) continue;\n throw new Error(\n `expand: <${tag.toLowerCase()}> was given ${EXP_PREFIX}${name}, ` +\n `but its definition has no {{${name}}}.`,\n );\n }\n // Named destinations first: what they take is addressed to them, and only\n // what is left is the slot's.\n fillSlot(tag, tree, fillTemplates(tag, tree, [...el.childNodes]));\n el.replaceChildren(...tree.childNodes);\n}\n\nfunction walk(root: ParentNode, defs: Definitions): void {\n for (const child of [...root.children]) {\n const source = defs[child.tagName.toUpperCase()];\n // A tag with no definition is left alone. It is a custom element a\n // script registers, and build/elements.ts is where a tag that has\n // neither a definition nor a script is refused — one rule, one place.\n if (source !== undefined) expandElement(child, defs, source);\n // Definitions may use other widgets, so descend after expanding. The\n // acyclicity check is what makes this terminate.\n walk(child, defs);\n // Template content is markup in a file like any other, and it is inert,\n // so a row template ships already expanded.\n if (child instanceof HTMLTemplateElement) walk(child.content, defs);\n }\n}\n\n/**\n * Expand every widget tag in an authored page. Throws rather than shipping.\n *\n * Definitions must come from `loadDefinitions`, which is where the graph is\n * checked for cycles. Handed a cyclic map directly, this recurses forever.\n */\nexport function expand(source: string, defs: Definitions): string {\n const tree = parse(source);\n walk(tree, defs);\n return tree.innerHTML;\n}\n"]}
@@ -43,3 +43,4 @@ export async function formatHtml(source) {
43
43
  return source;
44
44
  }
45
45
  }
46
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../build/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,qEAAqE;AACrE,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAc;IAC7C,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;QAC1C,OAAO,MAAM,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE;YACnC,MAAM,EAAE,MAAM;YACd,yEAAyE;YACzE,sEAAsE;YACtE,uEAAuE;YACvE,yBAAyB,EAAE,KAAK;SACjC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CACV,6DAA6D,GAAG,EAAE,CACnE,CAAC;QACF,OAAO,MAAM,CAAC;IAChB,CAAC;AACH,CAAC","sourcesContent":["/**\n * Pretty-printing the assembled document — cosmetic, and deliberately so.\n *\n * Loadbare App ships the DOM the browser holds, so view-source is a real view of the\n * application. That only pays if it is legible. Expansion produces correct\n * markup but inherits its whitespace from the definition files, so the\n * assembled document reads like a concatenation, which is what it is.\n *\n * So prettier is a dependency rather than something an application author has\n * to know to add: legible output is the default install's job, not an\n * easter egg for whoever reads a warning closely enough to act on it. It runs\n * on the whole document once, after everything is assembled, and the one way\n * it can fail — throwing on a document — leaves the unformatted string in\n * place.\n *\n * Ignoring the throw is the point rather than an oversight. The correct output\n * is what `expand` already returned; formatting only makes it pleasant. If a\n * failure could fail the build, we would end up writing checks upstream whose\n * only purpose was to keep the formatter happy, and an error message about\n * markup the author never wrote is worse than an ugly line. So: nothing in this\n * codebase exists to keep prettier from throwing. It either helps or it doesn't.\n *\n * The one failure that would retire this outright is whitespace. Whitespace is\n * semantic in inline contexts — `<label>Name <input></label>` needs that space —\n * and prettier has no display information for `lb-*` elements. If formatting\n * is ever observed to change what renders, delete this file. A prettier page\n * that shows the wrong thing is not a trade worth making.\n */\n\n/** Format if we can, and return the input unchanged if we cannot. */\nexport async function formatHtml(source: string): Promise<string> {\n try {\n const prettier = await import(\"prettier\");\n return await prettier.format(source, {\n parser: \"html\",\n // The default. Prettier respects each element's CSS display default when\n // deciding where whitespace may move, and treats elements it does not\n // know — every hub widget — as inline, which is the conservative side.\n htmlWhitespaceSensitivity: \"css\",\n });\n } catch (err) {\n console.warn(\n `loadbare: shipping unformatted markup, prettier declined: ${err}`,\n );\n return source;\n }\n}\n"]}
@@ -8,7 +8,7 @@
8
8
  * filename suffix does instead: `chrome.html` is the one chrome file, and
9
9
  * `*.page.html` is a page.
10
10
  *
11
- * A page's server half is named the same way: `*.hooks.ts` and
11
+ * A page's server half is named the same way: `*.requests.ts` and
12
12
  * `*.queries.ts` pair with a `*.page.html` by sharing its basename, and are
13
13
  * free to sit anywhere too. Either may be absent — a page with no data needs
14
14
  * neither — but one with no matching `*.page.html` is a naming mistake and
@@ -32,9 +32,9 @@ export interface PageFile {
32
32
  file: string;
33
33
  }
34
34
  export interface PageSource {
35
- /** Absolute path, without extension, to the `.hooks.ts` module, if this page has one. */
36
- hooks?: string;
37
- /** Absolute path, without extension, to the `.queries.ts` module, if this page has one. */
35
+ /** Absolute path to the `.requests.ts` module, if this page has one. */
36
+ requests?: string;
37
+ /** Absolute path to the `.queries.ts` module, if this page has one. */
38
38
  queries?: string;
39
39
  }
40
40
  interface Discovered {
@@ -1 +1 @@
1
- {"version":3,"file":"locations.d.ts","sourceRoot":"","sources":["../../build/locations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAOH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAQlD,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,yFAAyF;IACzF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2FAA2F;IAC3F,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,UAAU;IAClB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACrC,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,gHAAgH;AAChH,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CA6ClE;AAED,MAAM,MAAM,WAAW,GAAG,WAAW,CAAC;AAEtC,wBAAsB,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC,CAmBxD;AAED,MAAM,WAAW,YAAY;IAC3B,8FAA8F;IAC9F,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,SAAU,SAAQ,WAAW;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC,CA6C7E"}
1
+ {"version":3,"file":"locations.d.ts","sourceRoot":"","sources":["../../build/locations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAOH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAQlD,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,UAAU;IAClB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACrC,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,gHAAgH;AAChH,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAyClE;AAED,MAAM,MAAM,WAAW,GAAG,WAAW,CAAC;AAEtC,wBAAsB,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC,CAmBxD;AAED,MAAM,WAAW,YAAY;IAC3B,8FAA8F;IAC9F,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,SAAU,SAAQ,WAAW;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC,CA2D7E"}
@@ -8,7 +8,7 @@
8
8
  * filename suffix does instead: `chrome.html` is the one chrome file, and
9
9
  * `*.page.html` is a page.
10
10
  *
11
- * A page's server half is named the same way: `*.hooks.ts` and
11
+ * A page's server half is named the same way: `*.requests.ts` and
12
12
  * `*.queries.ts` pair with a `*.page.html` by sharing its basename, and are
13
13
  * free to sit anywhere too. Either may be absent — a page with no data needs
14
14
  * neither — but one with no matching `*.page.html` is a naming mistake and
@@ -32,7 +32,7 @@ import { findPackageRoot } from "./package-root.js";
32
32
  export { CUSTOM_ELEMENT_TAG } from "./origins.js";
33
33
  const CHROME_FILENAME = "chrome.html";
34
34
  const PAGE_SUFFIX = ".page.html";
35
- const HOOKS_SUFFIX = ".hooks.ts";
35
+ const HOOKS_SUFFIX = ".requests.ts";
36
36
  const QUERIES_SUFFIX = ".queries.ts";
37
37
  const IMPORTS_FILENAME = "imports.ts";
38
38
  /** The raw walk, with no uniqueness checks — usable on its own by anything that does not need a chrome file. */
@@ -70,11 +70,10 @@ export async function discoverSrc(src) {
70
70
  pages.push({ name: entry.name.slice(0, -PAGE_SUFFIX.length), file });
71
71
  }
72
72
  else if (entry.name.endsWith(HOOKS_SUFFIX)) {
73
- pageSource(entry.name.slice(0, -HOOKS_SUFFIX.length)).hooks = file.slice(0, -".ts".length);
73
+ pageSource(entry.name.slice(0, -HOOKS_SUFFIX.length)).requests = file;
74
74
  }
75
75
  else if (entry.name.endsWith(QUERIES_SUFFIX)) {
76
- pageSource(entry.name.slice(0, -QUERIES_SUFFIX.length)).queries =
77
- file.slice(0, -".ts".length);
76
+ pageSource(entry.name.slice(0, -QUERIES_SUFFIX.length)).queries = file;
78
77
  }
79
78
  }
80
79
  pages.sort((a, b) => a.name.localeCompare(b.name));
@@ -116,6 +115,17 @@ export async function resolveLocations(args) {
116
115
  throw new Error(`locations: more than one ${IMPORTS_FILENAME} found under '${args.src}': ` +
117
116
  importsCandidates.join(", "));
118
117
  }
118
+ // Sorted by name above, so duplicates are adjacent. A stub is the page's
119
+ // whole identity — its route, its template and its server half all key on
120
+ // it — so two files claiming one stub is a naming mistake, not a merge.
121
+ for (let i = 1; i < pages.length; i++) {
122
+ const [prev, here] = [pages[i - 1], pages[i]];
123
+ if (prev.name === here.name) {
124
+ throw new Error(`locations: more than one '${here.name}${PAGE_SUFFIX}' found under ` +
125
+ `'${args.src}': ${prev.file}, ${here.file}; the page namespace is ` +
126
+ `flat, so a stub names one page`);
127
+ }
128
+ }
119
129
  const pageNames = new Set(pages.map((p) => p.name));
120
130
  for (const name of pageSources.keys()) {
121
131
  if (!pageNames.has(name)) {
@@ -133,3 +143,4 @@ export async function resolveLocations(args) {
133
143
  outDir: args.out,
134
144
  };
135
145
  }
146
+ //# sourceMappingURL=locations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locations.js","sourceRoot":"","sources":["../../build/locations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Finding the application's own files by what they're named, not by which\n * directory they happen to sit in.\n *\n * A page and a widget definition can be byte-for-byte identical markup —\n * the only thing that says which one a file is, is extrinsic to its\n * content. `pages/` vs. `widgets/` used to carry that meaning; here a\n * filename suffix does instead: `chrome.html` is the one chrome file, and\n * `*.page.html` is a page.\n *\n * A page's server half is named the same way: `*.requests.ts` and\n * `*.queries.ts` pair with a `*.page.html` by sharing its basename, and are\n * free to sit anywhere too. Either may be absent — a page with no data needs\n * neither — but one with no matching `*.page.html` is a naming mistake and\n * is rejected below.\n *\n * Widget definitions, widget scripts and stylesheets are not found here.\n * They arrive from more than one origin — this package's own widgets, an\n * imported package, or `src` — and all three are scanned the same way by\n * build/origins.ts. `src` is simply the last origin in that cascade.\n *\n * `--src` names the one tree scanned this way; `--out` names where the\n * build lands. Those, plus this package's own root (for its built-in\n * widgets and hub/), are the only locations this module resolves — every\n * other module takes the result as a plain parameter and never\n * reconstructs one of its own.\n */\n\nimport { readdir, readFile } from \"node:fs/promises\";\nimport type { Dirent } from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { findPackageRoot } from \"./package-root.js\";\nimport type { BuiltinDirs } from \"./origins.js\";\n\nexport { CUSTOM_ELEMENT_TAG } from \"./origins.js\";\n\nconst CHROME_FILENAME = \"chrome.html\";\nconst PAGE_SUFFIX = \".page.html\";\nconst HOOKS_SUFFIX = \".requests.ts\";\nconst QUERIES_SUFFIX = \".queries.ts\";\nconst IMPORTS_FILENAME = \"imports.ts\";\n\nexport interface PageFile {\n name: string;\n file: string;\n}\n\nexport interface PageSource {\n /** Absolute path to the `.requests.ts` module, if this page has one. */\n requests?: string;\n /** Absolute path to the `.queries.ts` module, if this page has one. */\n queries?: string;\n}\n\ninterface Discovered {\n chromeCandidates: string[];\n pages: PageFile[];\n pageSources: Map<string, PageSource>;\n importsCandidates: string[];\n}\n\n/** The raw walk, with no uniqueness checks — usable on its own by anything that does not need a chrome file. */\nexport async function discoverSrc(src: string): Promise<Discovered> {\n const chromeCandidates: string[] = [];\n const pages: PageFile[] = [];\n const pageSources = new Map<string, PageSource>();\n const importsCandidates: string[] = [];\n\n let entries: Dirent[];\n try {\n entries = await readdir(src, { withFileTypes: true, recursive: true });\n } catch {\n entries = [];\n }\n\n const pageSource = (name: string): PageSource => {\n let p = pageSources.get(name);\n if (!p) {\n p = {};\n pageSources.set(name, p);\n }\n return p;\n };\n\n for (const entry of entries) {\n if (!entry.isFile()) continue;\n const file = path.join(entry.parentPath, entry.name);\n\n if (entry.name === IMPORTS_FILENAME) {\n importsCandidates.push(file);\n } else if (entry.name === CHROME_FILENAME) {\n chromeCandidates.push(file);\n } else if (entry.name.endsWith(PAGE_SUFFIX)) {\n pages.push({ name: entry.name.slice(0, -PAGE_SUFFIX.length), file });\n } else if (entry.name.endsWith(HOOKS_SUFFIX)) {\n pageSource(entry.name.slice(0, -HOOKS_SUFFIX.length)).requests = file;\n } else if (entry.name.endsWith(QUERIES_SUFFIX)) {\n pageSource(entry.name.slice(0, -QUERIES_SUFFIX.length)).queries = file;\n }\n }\n\n pages.sort((a, b) => a.name.localeCompare(b.name));\n return { chromeCandidates, pages, pageSources, importsCandidates };\n}\n\nexport type PackageInfo = BuiltinDirs;\n\nexport async function packageInfo(): Promise<PackageInfo> {\n const here = path.dirname(fileURLToPath(import.meta.url));\n const root = await findPackageRoot(here);\n let pkg: { name: string };\n try {\n pkg = JSON.parse(await readFile(path.join(root, \"package.json\"), \"utf-8\"));\n } catch (err) {\n throw new Error(\n `locations: could not read '${root}/package.json': ${(err as Error).message}`,\n );\n }\n return {\n // `hub/` beside this module, not beside package.json: running from source\n // that is the package's own hub/, and running compiled it is dist/hub —\n // the one an installed package ships, since `files` carries dist and not\n // the source tree.\n builtinHubDir: path.join(path.dirname(here), \"hub\"),\n packageName: pkg.name,\n };\n}\n\nexport interface LocationArgs {\n /** The application's source tree, e.g. \"src\". Scanned as a whole — see this file's header. */\n src: string;\n out: string;\n}\n\nexport interface Locations extends PackageInfo {\n srcDir: string;\n chromeFile: string;\n pages: PageFile[];\n pageSources: Map<string, PageSource>;\n importsFile?: string;\n outDir: string;\n}\n\nexport async function resolveLocations(args: LocationArgs): Promise<Locations> {\n const [info, discovered] = await Promise.all([\n packageInfo(),\n discoverSrc(args.src),\n ]);\n const { chromeCandidates, importsCandidates, pages, pageSources } =\n discovered;\n\n if (chromeCandidates.length === 0) {\n throw new Error(\n `locations: no ${CHROME_FILENAME} file found under '${args.src}'`,\n );\n }\n if (chromeCandidates.length > 1) {\n throw new Error(\n `locations: more than one ${CHROME_FILENAME} file found under '${args.src}': ` +\n chromeCandidates.join(\", \"),\n );\n }\n if (importsCandidates.length > 1) {\n throw new Error(\n `locations: more than one ${IMPORTS_FILENAME} found under '${args.src}': ` +\n importsCandidates.join(\", \"),\n );\n }\n\n // Sorted by name above, so duplicates are adjacent. A stub is the page's\n // whole identity — its route, its template and its server half all key on\n // it — so two files claiming one stub is a naming mistake, not a merge.\n for (let i = 1; i < pages.length; i++) {\n const [prev, here] = [pages[i - 1]!, pages[i]!];\n if (prev.name === here.name) {\n throw new Error(\n `locations: more than one '${here.name}${PAGE_SUFFIX}' found under ` +\n `'${args.src}': ${prev.file}, ${here.file}; the page namespace is ` +\n `flat, so a stub names one page`,\n );\n }\n }\n\n const pageNames = new Set(pages.map((p) => p.name));\n for (const name of pageSources.keys()) {\n if (!pageNames.has(name)) {\n throw new Error(\n `locations: '${name}${HOOKS_SUFFIX}' or '${name}${QUERIES_SUFFIX}' found under ` +\n `'${args.src}' but there is no '${name}${PAGE_SUFFIX}'`,\n );\n }\n }\n\n return {\n ...info,\n srcDir: args.src,\n chromeFile: chromeCandidates[0]!,\n pages,\n pageSources,\n ...(importsCandidates[0] ? { importsFile: importsCandidates[0] } : {}),\n outDir: args.out,\n };\n}\n"]}
@@ -71,19 +71,6 @@ export interface OriginScan {
71
71
  /** Every `.css` file here, sorted by filename with the full path breaking a tie. */
72
72
  css: string[];
73
73
  }
74
- /**
75
- * One origin's contribution, classified by filename alone.
76
- *
77
- * A `.html` file is a widget's only when its basename is a tag, and a module
78
- * only when it is `<tag>.browser.ts` — which is what excludes `chrome.html`,
79
- * `about.page.html`, `about.hooks.ts` and `imports.ts` here without naming
80
- * any of them. Those are the application's own files, and
81
- * build/locations.ts picks them out of `src` separately.
82
- *
83
- * A module named after a tag without the `.browser` goes in `unmarked`, so
84
- * an author who forgot the word gets told which file to rename rather than
85
- * being told their tag does not exist.
86
- */
87
74
  export declare function scanOrigin(origin: Origin): Promise<OriginScan>;
88
75
  /** The shape of an `imports.ts` module's default export: the packages an application pulls widgets from. */
89
76
  export type ElementManifest = string[];