@loadbare/app 0.9.0 → 0.11.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.
- package/README.md +3 -3
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +107 -90
- package/dist/build/assemble.js.map +1 -1
- package/dist/build/expand.d.ts +6 -1
- package/dist/build/expand.d.ts.map +1 -1
- package/dist/build/expand.js +112 -26
- package/dist/build/expand.js.map +1 -1
- package/dist/build/locations.d.ts +2 -3
- package/dist/build/locations.d.ts.map +1 -1
- package/dist/build/locations.js +2 -3
- package/dist/build/locations.js.map +1 -1
- package/dist/build/pages.d.ts +3 -4
- package/dist/build/pages.d.ts.map +1 -1
- package/dist/build/pages.js +3 -4
- package/dist/build/pages.js.map +1 -1
- package/dist/core/lb-constants.d.ts +27 -24
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +103 -168
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/core/lb-types.d.ts +64 -77
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js +40 -7
- package/dist/core/lb-types.js.map +1 -1
- package/dist/hub/lb-apply.d.ts +47 -37
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +195 -199
- package/dist/hub/lb-apply.js.map +1 -1
- package/dist/hub/lb-hub.browser.d.ts +1 -1
- package/dist/hub/lb-hub.browser.d.ts.map +1 -1
- package/dist/hub/lb-hub.browser.js +410 -449
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/dist/server/lb-express.d.ts +5 -5
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +35 -66
- package/dist/server/lb-express.js.map +1 -1
- package/dist/server/lb-server.d.ts +77 -135
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +132 -79
- package/dist/server/lb-server.js.map +1 -1
- package/docs/TECHREF-1.0.md +908 -585
- package/docs/comparison.md +243 -185
- package/docs/prior-art.md +15 -14
- package/docs/reference/builder.md +9 -3
- package/docs/reference/chrome.md +107 -56
- package/docs/reference/custom-elements.md +291 -173
- package/docs/reference/data-binding.md +381 -374
- package/docs/reference/overview.md +12 -10
- package/docs/reference/page-files.md +164 -99
- package/docs/reference/server.md +2 -2
- package/docs/reference/widgets.md +104 -110
- package/docs/roadmap.md +32 -39
- package/docs/terms-of-art.md +57 -0
- package/docs/testing.md +97 -68
- package/docs/theory.md +92 -58
- package/docs/tutorials/010-pages-and-navigation.md +20 -12
- package/docs/tutorials/020-css.md +6 -3
- package/docs/tutorials/030-html-decomposition.md +9 -7
- package/docs/tutorials/040-displaying-data.md +30 -13
- package/docs/tutorials/{050-actions.md → 050-requests.md} +25 -15
- package/docs/tutorials/060-custom-element-code.md +17 -16
- package/docs/tutorials/065-conditional-rendering.md +34 -23
- package/docs/tutorials/070-displaying-a-list.md +29 -21
- package/docs/tutorials/072-inserting-into-a-list.md +24 -16
- package/docs/tutorials/074-deleting-from-a-list.md +9 -7
- package/docs/tutorials/076-updating-a-list-item.md +11 -10
- package/docs/tutorials/080-widget-requests.md +71 -43
- package/docs/tutorials/090-using-widget-libraries.md +22 -22
- package/docs/what-does-loadbare-extend.md +124 -0
- package/package.json +1 -1
- package/skills/loadbare-app/SKILL.md +201 -123
- package/skills/loadbare-app/references/TECHREF-1.0.md +908 -585
- package/skills/loadbare-app/references/builder.md +9 -3
- package/skills/loadbare-app/references/chrome.md +107 -56
- package/skills/loadbare-app/references/custom-elements.md +291 -173
- package/skills/loadbare-app/references/data-binding.md +381 -374
- package/skills/loadbare-app/references/overview.md +12 -10
- package/skills/loadbare-app/references/page-files.md +164 -99
- package/skills/loadbare-app/references/server.md +2 -2
- package/skills/loadbare-app/references/widgets.md +104 -110
- package/docs/analysis-accidental-complexity.md +0 -149
- package/docs/analysis-closed-set.md +0 -210
package/README.md
CHANGED
|
@@ -25,8 +25,8 @@ Loadbare App has two twin primary goals
|
|
|
25
25
|
## Widgets
|
|
26
26
|
|
|
27
27
|
This package ships one custom element, `<lb-hub>`. The widgets an application
|
|
28
|
-
displays data with — `lb-input`, `lb-select`, `lb-
|
|
29
|
-
`lb-table`, `lb-
|
|
28
|
+
displays data with — `lb-input`, `lb-select`, `lb-options`, `lb-picker`,
|
|
29
|
+
`lb-table`, `lb-unknown-page` — ship separately, in
|
|
30
30
|
[@loadbare/widgets](../widgets/README.md):
|
|
31
31
|
|
|
32
32
|
```
|
|
@@ -42,7 +42,7 @@ The builder resolves that package the way it resolves anyone else's, so a
|
|
|
42
42
|
widget library written by a third party, or by the application's own team,
|
|
43
43
|
arrives through the same one line. See
|
|
44
44
|
[The Basic Widget Library](./docs/reference/widgets.md) for what each widget
|
|
45
|
-
|
|
45
|
+
shows and sends, and
|
|
46
46
|
[Using Widget Libraries](./docs/tutorials/090-using-widget-libraries.md) for
|
|
47
47
|
what listing a package does.
|
|
48
48
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assemble.d.ts","sourceRoot":"","sources":["../../build/assemble.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;
|
|
1
|
+
{"version":3,"file":"assemble.d.ts","sourceRoot":"","sources":["../../build/assemble.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAwBH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AA6M/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,CAsDtC"}
|
package/dist/build/assemble.js
CHANGED
|
@@ -17,103 +17,88 @@
|
|
|
17
17
|
* looking for pages or widgets; it is handed exactly which files those are.
|
|
18
18
|
*/
|
|
19
19
|
import { readFile } from "node:fs/promises";
|
|
20
|
-
import {
|
|
21
|
-
import { addDefinitions, customElementTags, expand, } from "./expand.js";
|
|
20
|
+
import { addDefinitions, customElementTags, expandTree, } from "./expand.js";
|
|
22
21
|
import { formatHtml } from "./format.js";
|
|
23
|
-
import {
|
|
22
|
+
import { ATTR_PAGE, ATTR_PAGE_TITLE, ATTR_QUERY, ATTR_REQUEST, ATTR_SHOW, ATTR_URL_LINK, ATTR_URL_PUSH, ATTR_URL_UNKNOWN, DEVELOPER_ATTRIBUTES, HUB_TAG_NAME, LB_RESERVED_PREFIX, } from "../core/lb-constants.js";
|
|
23
|
+
import { isRowRequest } from "../core/lb-types.js";
|
|
24
24
|
/**
|
|
25
|
-
* The hub calls showModal() on
|
|
26
|
-
* (hub/lb-hub.browser.ts),
|
|
27
|
-
* else
|
|
28
|
-
*
|
|
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.
|
|
25
|
+
* The hub calls showModal() on the `<dialog>` carrying lb-url-unknown
|
|
26
|
+
* (hub/lb-hub.browser.ts), and looks for it inside itself, as it does for
|
|
27
|
+
* everything else it acts on. A dialog elsewhere in the body would never open
|
|
28
|
+
* and would say nothing about why.
|
|
34
29
|
*/
|
|
35
|
-
function checkUnknownPageDialog(chromeFile,
|
|
36
|
-
const
|
|
37
|
-
const el = fragment.querySelector(`[${ATTR_UNKNOWN_PAGE}]`);
|
|
30
|
+
function checkUnknownPageDialog(chromeFile, body) {
|
|
31
|
+
const el = body.querySelector(`[${ATTR_URL_UNKNOWN}]`);
|
|
38
32
|
if (!el)
|
|
39
33
|
return;
|
|
40
|
-
if (el.tagName !== "DIALOG") {
|
|
41
|
-
throw new Error(`assemble: '${chromeFile}' declares ${ATTR_UNKNOWN_PAGE} on ` +
|
|
42
|
-
`<${el.tagName.toLowerCase()}>, must be a <dialog>`);
|
|
43
|
-
}
|
|
44
34
|
if (!el.closest(HUB_TAG_NAME)) {
|
|
45
|
-
throw new Error(`assemble: '${chromeFile}' declares ${
|
|
35
|
+
throw new Error(`assemble: '${chromeFile}' declares ${ATTR_URL_UNKNOWN} outside ` +
|
|
46
36
|
`<${HUB_TAG_NAME}>, where the hub cannot reach it`);
|
|
47
37
|
}
|
|
48
38
|
}
|
|
49
39
|
/**
|
|
50
|
-
* The
|
|
51
|
-
*
|
|
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.
|
|
40
|
+
* The markup, checked once against the finished document rather than
|
|
41
|
+
* discovered one console error at a time in a browser.
|
|
58
42
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
43
|
+
* Every rule here is a fact the markup states about itself. The developer
|
|
44
|
+
* writes `lb-*` attributes in markup, and script never assigns them, which is
|
|
45
|
+
* what makes checking the markup sound. See docs/reference/data-binding.md.
|
|
62
46
|
*
|
|
63
47
|
* A `<template>`'s children are walked in the scope that encloses the
|
|
64
|
-
* template, because a row template's clones land in the
|
|
65
|
-
*
|
|
66
|
-
* `lb-show` is judged by the scope around the element, not one the element
|
|
67
|
-
* carries, because its condition is a cell of the row around it. It needs a
|
|
68
|
-
* row: in a list scope but outside the row template, nothing ever lands on
|
|
69
|
-
* it, and the branch would never show.
|
|
48
|
+
* template, because a row template's clones land in the element around it.
|
|
70
49
|
*/
|
|
71
|
-
function
|
|
50
|
+
function walkMarkup(node, scope, problems) {
|
|
72
51
|
for (const el of node.children) {
|
|
73
|
-
const listName = el.getAttribute(ATTR_LIST);
|
|
74
|
-
const rowName = el.getAttribute(ATTR_ROW);
|
|
75
52
|
const tag = el.localName;
|
|
76
53
|
let here = scope;
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
54
|
+
if (el.hasAttribute(ATTR_QUERY))
|
|
55
|
+
here = { row: "unknown" };
|
|
56
|
+
for (const { name } of el.attributes) {
|
|
57
|
+
if (name.startsWith(LB_RESERVED_PREFIX) &&
|
|
58
|
+
!DEVELOPER_ATTRIBUTES.includes(name)) {
|
|
59
|
+
problems.push(`<${tag}> carries ${name}, which is not an attribute a developer ` +
|
|
60
|
+
`writes`);
|
|
61
|
+
}
|
|
80
62
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
`no ${ATTR_LIST} or ${ATTR_ROW} scope around it`);
|
|
63
|
+
const request = el.getAttribute(ATTR_REQUEST);
|
|
64
|
+
if (request !== null &&
|
|
65
|
+
request.startsWith(LB_RESERVED_PREFIX) &&
|
|
66
|
+
!isRowRequest(request)) {
|
|
67
|
+
problems.push(`<${tag}> carries ${ATTR_REQUEST}="${request}", which begins with ` +
|
|
68
|
+
`'${LB_RESERVED_PREFIX}' but names no request Loadbare provides`);
|
|
88
69
|
}
|
|
89
|
-
if (el.hasAttribute(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
70
|
+
if (el.hasAttribute(ATTR_URL_LINK) && tag !== "a") {
|
|
71
|
+
problems.push(`<${tag}> carries ${ATTR_URL_LINK}; it goes on an <a>`);
|
|
72
|
+
}
|
|
73
|
+
if (el.hasAttribute(ATTR_URL_PUSH) && request === null) {
|
|
74
|
+
problems.push(`<${tag}> carries ${ATTR_URL_PUSH} and no ${ATTR_REQUEST}; it goes ` +
|
|
75
|
+
`on an element with ${ATTR_REQUEST}`);
|
|
76
|
+
}
|
|
77
|
+
if (el.hasAttribute(ATTR_URL_UNKNOWN) && tag !== "dialog") {
|
|
78
|
+
problems.push(`<${tag}> carries ${ATTR_URL_UNKNOWN}; it goes on a <dialog>`);
|
|
98
79
|
}
|
|
80
|
+
// Judged by the scope around the element, not one the element carries:
|
|
81
|
+
// its condition is a column of the row around it. With no lb-query
|
|
82
|
+
// around it at all, nothing ever lands on it, and the element never
|
|
83
|
+
// shows.
|
|
99
84
|
const show = el.getAttribute(ATTR_SHOW);
|
|
100
85
|
if (show !== null) {
|
|
101
86
|
if (tag === "template") {
|
|
102
87
|
problems.push(`a <template> carries ${ATTR_SHOW}="${show}"; write it on the ` +
|
|
103
88
|
`element it shows, and Loadbare supplies the template`);
|
|
104
89
|
}
|
|
105
|
-
else if (scope === null ||
|
|
90
|
+
else if (scope === null || scope.row === false) {
|
|
106
91
|
problems.push(`<${tag}> carries ${ATTR_SHOW}="${show}" with no row around it ` +
|
|
107
92
|
`to take the value from`);
|
|
108
93
|
}
|
|
109
94
|
}
|
|
110
|
-
|
|
111
|
-
// By tag name rather than by instanceof:
|
|
95
|
+
walkMarkup(el, here, problems);
|
|
96
|
+
// By tag name rather than by instanceof: template content belongs to a
|
|
112
97
|
// document with no window, so the constructor is not reachable from the
|
|
113
98
|
// node.
|
|
114
99
|
if (tag === "template") {
|
|
115
100
|
const content = el.content;
|
|
116
|
-
const isRowTemplate = el.
|
|
101
|
+
const isRowTemplate = scope !== null && !!el.parentElement?.closest(`[${ATTR_QUERY}]`);
|
|
117
102
|
if (isRowTemplate) {
|
|
118
103
|
for (const root of content.children) {
|
|
119
104
|
if (root.hasAttribute(ATTR_SHOW)) {
|
|
@@ -123,7 +108,7 @@ function walkBinding(node, scope, problems) {
|
|
|
123
108
|
}
|
|
124
109
|
}
|
|
125
110
|
}
|
|
126
|
-
|
|
111
|
+
walkMarkup(content, isRowTemplate ? { row: true } : here, problems);
|
|
127
112
|
}
|
|
128
113
|
}
|
|
129
114
|
}
|
|
@@ -133,15 +118,12 @@ function walkBinding(node, scope, problems) {
|
|
|
133
118
|
* A page is in the document before its data arrives, and a value that has not
|
|
134
119
|
* arrived is no value.
|
|
135
120
|
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* gone into a template, because the references are held.
|
|
121
|
+
* Elements are collected before any is moved, and a wrapped element is still
|
|
122
|
+
* reached once its outer one has gone into a template, because the references
|
|
123
|
+
* are held.
|
|
140
124
|
*/
|
|
141
|
-
function wrapShown(
|
|
142
|
-
const doc =
|
|
143
|
-
const holder = doc.createElement("template");
|
|
144
|
-
holder.innerHTML = html;
|
|
125
|
+
function wrapShown(holder) {
|
|
126
|
+
const doc = holder.ownerDocument;
|
|
145
127
|
const shown = [];
|
|
146
128
|
const collect = (root) => {
|
|
147
129
|
shown.push(...root.querySelectorAll(`[${ATTR_SHOW}]`));
|
|
@@ -150,24 +132,53 @@ function wrapShown(html) {
|
|
|
150
132
|
}
|
|
151
133
|
};
|
|
152
134
|
collect(holder.content);
|
|
153
|
-
if (shown.length === 0)
|
|
154
|
-
return html;
|
|
155
135
|
for (const el of shown) {
|
|
156
136
|
const parked = doc.createElement("template");
|
|
157
137
|
parked.setAttribute(ATTR_SHOW, el.getAttribute(ATTR_SHOW));
|
|
158
138
|
el.replaceWith(parked);
|
|
159
139
|
parked.content.append(el);
|
|
160
140
|
}
|
|
161
|
-
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Expand one file's markup, naming the file if expansion refuses it.
|
|
144
|
+
*
|
|
145
|
+
* What comes back is the tree the browser will build, so every pass after
|
|
146
|
+
* this reads and changes it in place, and it is serialized once, when it
|
|
147
|
+
* ships.
|
|
148
|
+
*/
|
|
149
|
+
function expandFile(file, source, defs) {
|
|
150
|
+
try {
|
|
151
|
+
return expandTree(source, defs);
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
throw new Error(`assemble: '${file}': ${e.message}`);
|
|
155
|
+
}
|
|
162
156
|
}
|
|
163
157
|
/** Check one expanded fragment, and name the file if anything is wrong. */
|
|
164
|
-
function
|
|
158
|
+
function checkMarkup(file, root) {
|
|
165
159
|
const problems = [];
|
|
166
|
-
|
|
160
|
+
walkMarkup(root, null, problems);
|
|
167
161
|
if (problems.length > 0) {
|
|
168
|
-
throw new Error(`assemble: '${file}'
|
|
162
|
+
throw new Error(`assemble: '${file}' markup:\n ` + problems.join(`\n `));
|
|
169
163
|
}
|
|
170
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* A page file's `<title>` names the page. It is lifted out, since a page is
|
|
167
|
+
* a fragment inside `<main>` and a `<title>` belongs in a document's head,
|
|
168
|
+
* and its text content is stamped on the page as ATTR_PAGE_TITLE.
|
|
169
|
+
*/
|
|
170
|
+
function liftTitle(holder) {
|
|
171
|
+
const title = holder.content.querySelector("title");
|
|
172
|
+
if (!title)
|
|
173
|
+
return null;
|
|
174
|
+
const text = title.textContent?.trim() ?? "";
|
|
175
|
+
title.remove();
|
|
176
|
+
return text;
|
|
177
|
+
}
|
|
178
|
+
/** An attribute value, escaped for a double-quoted attribute. */
|
|
179
|
+
function attributeValue(text) {
|
|
180
|
+
return text.replace(/&/g, "&").replace(/"/g, """);
|
|
181
|
+
}
|
|
171
182
|
export async function assemble({ chromeFile, pages, scans, }) {
|
|
172
183
|
// One layer per origin, in order, so a later origin's definition overrides
|
|
173
184
|
// an earlier one's for the same tag. Each layer is checked for cycles as it
|
|
@@ -176,12 +187,19 @@ export async function assemble({ chromeFile, pages, scans, }) {
|
|
|
176
187
|
for (const scan of scans) {
|
|
177
188
|
defs = await addDefinitions(defs, scan.definitions);
|
|
178
189
|
}
|
|
190
|
+
// Tags are collected from each tree as it goes by. What ships is these
|
|
191
|
+
// trees and the chrome around its body, which is collected on its own.
|
|
192
|
+
const usedTags = new Set();
|
|
179
193
|
const templates = [];
|
|
180
194
|
for (const { name, file } of pages) {
|
|
181
195
|
const authored = await readFile(file, "utf-8");
|
|
182
|
-
const host =
|
|
183
|
-
|
|
184
|
-
|
|
196
|
+
const host = expandFile(file, authored, defs);
|
|
197
|
+
const title = liftTitle(host);
|
|
198
|
+
checkMarkup(file, host.content);
|
|
199
|
+
wrapShown(host);
|
|
200
|
+
customElementTags(host.content, usedTags);
|
|
201
|
+
const titled = title === null ? "" : ` ${ATTR_PAGE_TITLE}="${attributeValue(title)}"`;
|
|
202
|
+
templates.push(`<template ${ATTR_PAGE}="${name}"${titled}>${host.innerHTML}</template>`);
|
|
185
203
|
}
|
|
186
204
|
let chromeSource;
|
|
187
205
|
try {
|
|
@@ -195,14 +213,13 @@ export async function assemble({ chromeFile, pages, scans, }) {
|
|
|
195
213
|
throw new Error(`assemble: '${chromeFile}' has no <body>...</body> to expand`);
|
|
196
214
|
}
|
|
197
215
|
const [whole, bodyAttrs, bodyContent] = body;
|
|
198
|
-
const
|
|
199
|
-
checkUnknownPageDialog(chromeFile,
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
};
|
|
216
|
+
const chromeBody = expandFile(chromeFile, bodyContent, defs);
|
|
217
|
+
checkUnknownPageDialog(chromeFile, chromeBody.content);
|
|
218
|
+
checkMarkup(chromeFile, chromeBody.content);
|
|
219
|
+
wrapShown(chromeBody);
|
|
220
|
+
customElementTags(chromeBody.content, usedTags);
|
|
221
|
+
customElementTags(chromeSource.replace(whole, ""), usedTags);
|
|
222
|
+
const withPages = chromeSource.replace(whole, () => `<body${bodyAttrs}>${chromeBody.innerHTML}\n${templates.join("\n")}\n</body>`);
|
|
223
|
+
return { html: await formatHtml(withPages), usedTags };
|
|
207
224
|
}
|
|
208
225
|
//# sourceMappingURL=assemble.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assemble.js","sourceRoot":"","sources":["../../build/assemble.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAC9B,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,MAAM,GAEP,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,SAAS,EACT,QAAQ,EACR,SAAS,EACT,SAAS,EACT,QAAQ,EACR,SAAS,EACT,iBAAiB,EACjB,YAAY,GACb,MAAM,yBAAyB,CAAC;AAIjC;;;;;;;;;;GAUG;AACH,SAAS,sBAAsB,CAC7B,UAAkB,EAClB,YAAoB;IAEpB,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IAC9C,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,IAAI,iBAAiB,GAAG,CAAC,CAAC;IAC5D,IAAI,CAAC,EAAE;QAAE,OAAO;IAChB,IAAI,EAAE,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CACb,cAAc,UAAU,cAAc,iBAAiB,MAAM;YAC3D,IAAI,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,uBAAuB,CACtD,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CACb,cAAc,UAAU,cAAc,iBAAiB,WAAW;YAChE,IAAI,YAAY,kCAAkC,CACrD,CAAC;IACJ,CAAC;AACH,CAAC;AAaD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,SAAS,WAAW,CAClB,IAAgB,EAChB,KAAmB,EACnB,QAAkB;IAElB,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC/B,MAAM,QAAQ,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;QAC5C,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QAC1C,MAAM,GAAG,GAAG,EAAE,CAAC,SAAS,CAAC;QACzB,IAAI,IAAI,GAAG,KAAK,CAAC;QAEjB,IAAI,QAAQ,KAAK,IAAI,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YAC1C,QAAQ,CAAC,IAAI,CACX,IAAI,GAAG,kBAAkB,SAAS,QAAQ,QAAQ,eAAe;gBAC/D,qDAAqD,CACxD,CAAC;QACJ,CAAC;QACD,IAAI,QAAQ,KAAK,IAAI;YAAE,IAAI,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;aACtE,IAAI,OAAO,KAAK,IAAI;YAAE,IAAI,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;QAE5E,IAAI,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAChD,QAAQ,CAAC,IAAI,CACX,IAAI,GAAG,aAAa,SAAS,KAAK,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,SAAS;gBACnE,MAAM,SAAS,OAAO,QAAQ,kBAAkB,CACnD,CAAC;QACJ,CAAC;QAED,IAAI,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9B,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;gBACvB,QAAQ,CAAC,IAAI,CACX,IAAI,GAAG,aAAa,QAAQ,wCAAwC;oBAClE,yCAAyC,CAC5C,CAAC;YACJ,CAAC;iBAAM,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBACjD,QAAQ,CAAC,IAAI,CACX,wBAAwB,QAAQ,gBAAgB,SAAS,UAAU;oBACjE,kCAAkC,CACrC,CAAC;YACJ,CAAC;QACH,CAAC;QAED,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;QACxC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;gBACvB,QAAQ,CAAC,IAAI,CACX,wBAAwB,SAAS,KAAK,IAAI,qBAAqB;oBAC7D,sDAAsD,CACzD,CAAC;YACJ,CAAC;iBAAM,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;gBACxC,QAAQ,CAAC,IAAI,CACX,IAAI,GAAG,aAAa,SAAS,KAAK,IAAI,0BAA0B;oBAC9D,wBAAwB,CAC3B,CAAC;YACJ,CAAC;QACH,CAAC;QAED,WAAW,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QAChC,wEAAwE;QACxE,wEAAwE;QACxE,QAAQ;QACR,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;YACvB,MAAM,OAAO,GAAI,EAA0B,CAAC,OAAO,CAAC;YACpD,MAAM,aAAa,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,IAAI,KAAK,MAAM,CAAC;YACzE,IAAI,aAAa,EAAE,CAAC;gBAClB,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;oBACpC,IAAI,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,CAAC;wBACjC,QAAQ,CAAC,IAAI,CACX,IAAI,IAAI,CAAC,SAAS,yCAAyC;4BACzD,GAAG,SAAS,8CAA8C;4BAC1D,WAAW,CACd,CAAC;oBACJ,CAAC;gBACH,CAAC;YACH,CAAC;YACD,WAAW,CACT,OAAO,EACP,aAAa,CAAC,CAAC,CAAC,EAAE,GAAG,IAAK,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,EAC9C,QAAQ,CACT,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,SAAS,CAAC,IAAY;IAC7B,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;IAC1C,MAAM,MAAM,GAAG,GAAG,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;IAC7C,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC;IACxB,MAAM,KAAK,GAAc,EAAE,CAAC;IAC5B,MAAM,OAAO,GAAG,CAAC,IAAgB,EAAQ,EAAE;QACzC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,SAAS,GAAG,CAAC,CAAC,CAAC;QACvD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,EAAE,CAAC;YAClD,OAAO,CAAE,CAAyB,CAAC,OAAO,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC,CAAC;IACF,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACxB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,GAAG,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QAC7C,MAAM,CAAC,YAAY,CAAC,SAAS,EAAE,EAAE,CAAC,YAAY,CAAC,SAAS,CAAE,CAAC,CAAC;QAC5D,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACvB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,MAAM,CAAC,SAAS,CAAC;AAC1B,CAAC;AAED,2EAA2E;AAC3E,SAAS,YAAY,CAAC,IAAY,EAAE,IAAY;IAC9C,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IAClD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,gBAAgB,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9E,CAAC;AACH,CAAC;AAeD,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,EAC7B,UAAU,EACV,KAAK,EACL,KAAK,GACW;IAChB,2EAA2E;IAC3E,4EAA4E;IAC5E,2CAA2C;IAC3C,IAAI,IAAI,GAAgB,EAAE,CAAC;IAC3B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IACtD,CAAC;IAED,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,KAAK,EAAE,CAAC;QACnC,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC/C,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACpC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACzB,SAAS,CAAC,IAAI,CACZ,aAAa,SAAS,KAAK,IAAI,KAAK,SAAS,CAAC,IAAI,CAAC,aAAa,CACjE,CAAC;IACJ,CAAC;IAED,IAAI,YAAoB,CAAC;IACzB,IAAI,CAAC;QACH,YAAY,GAAG,MAAM,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,gCAAgC,UAAU,GAAG,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,IAAI,GAAG,iCAAiC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAClE,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CACb,cAAc,UAAU,qCAAqC,CAC9D,CAAC;IACJ,CAAC;IACD,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,WAAW,CAAC,GAAG,IAAI,CAAC;IAC7C,MAAM,YAAY,GAAG,MAAM,CAAC,WAAY,EAAE,IAAI,CAAC,CAAC;IAChD,sBAAsB,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IACjD,YAAY,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IACvC,MAAM,WAAW,GAAG,SAAS,CAAC,YAAY,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CACpC,KAAM,EACN,GAAG,EAAE,CAAC,QAAQ,SAAS,IAAI,WAAW,KAAK,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAC3E,CAAC;IAEF,OAAO;QACL,IAAI,EAAE,MAAM,UAAU,CAAC,SAAS,CAAC;QACjC,QAAQ,EAAE,iBAAiB,CAAC,SAAS,CAAC;KACvC,CAAC;AACJ,CAAC","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_SHOW,\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 there is a row here to take a value from. A\n * row scope always has one. A list has one only inside its row template.\n */\ninterface Scope {\n kind: \"list\" | \"row\";\n name: string;\n row: boolean;\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 *\n * `lb-show` is judged by the scope around the element, not one the element\n * carries, because its condition is a cell of the row around it. It needs a\n * row: in a list scope but outside the row template, nothing ever lands on\n * it, and the branch would never show.\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, row: false };\n else if (rowName !== null) here = { kind: \"row\", name: rowName, row: true };\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 const show = el.getAttribute(ATTR_SHOW);\n if (show !== null) {\n if (tag === \"template\") {\n problems.push(\n `a <template> carries ${ATTR_SHOW}=\"${show}\"; write it on the ` +\n `element it shows, and Loadbare supplies the template`,\n );\n } else if (scope === null || !scope.row) {\n problems.push(\n `<${tag}> carries ${ATTR_SHOW}=\"${show}\" with no row around it ` +\n `to take the value from`,\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 const content = (el as HTMLTemplateElement).content;\n const isRowTemplate = el.hasAttribute(ATTR_KEY) && here?.kind === \"list\";\n if (isRowTemplate) {\n for (const root of content.children) {\n if (root.hasAttribute(ATTR_SHOW)) {\n problems.push(\n `<${root.localName}> is a row template's root and carries ` +\n `${ATTR_SHOW}; a row that should not show is left out by ` +\n `the query`,\n );\n }\n }\n }\n walkBinding(\n content,\n isRowTemplate ? { ...here!, row: true } : here,\n problems,\n );\n }\n }\n}\n\n/**\n * Ship every `lb-show` element already inside the template the hub keeps an\n * absent branch in, so nothing conditional shows until its value says so.\n * A page is in the document before its data arrives, and a value that has not\n * arrived is no value.\n *\n * Parsed as template content, which accepts a `<tr>` or an `<option>` at the\n * top level where a `<div>` would drop it. Elements are collected before any\n * is moved, and a wrapped element is still reached once its outer one has\n * gone into a template, because the references are held.\n */\nfunction wrapShown(html: string): string {\n const doc = new JSDOM(\"\").window.document;\n const holder = doc.createElement(\"template\");\n holder.innerHTML = html;\n const shown: Element[] = [];\n const collect = (root: ParentNode): void => {\n shown.push(...root.querySelectorAll(`[${ATTR_SHOW}]`));\n for (const t of root.querySelectorAll(\"template\")) {\n collect((t as HTMLTemplateElement).content);\n }\n };\n collect(holder.content);\n if (shown.length === 0) return html;\n for (const el of shown) {\n const parked = doc.createElement(\"template\");\n parked.setAttribute(ATTR_SHOW, el.getAttribute(ATTR_SHOW)!);\n el.replaceWith(parked);\n parked.content.append(el);\n }\n return holder.innerHTML;\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(\n `<template ${ATTR_PAGE}=\"${name}\">${wrapShown(host)}</template>`,\n );\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 shippedBody = wrapShown(expandedBody);\n const withPages = chromeSource.replace(\n whole!,\n () => `<body${bodyAttrs}>${shippedBody}\\n${templates.join(\"\\n\")}\\n</body>`,\n );\n\n return {\n html: await formatHtml(withPages),\n usedTags: customElementTags(withPages),\n };\n}\n"]}
|
|
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 {\n addDefinitions,\n customElementTags,\n expandTree,\n type Definitions,\n} from \"./expand.js\";\nimport { formatHtml } from \"./format.js\";\nimport {\n ATTR_PAGE,\n ATTR_PAGE_TITLE,\n ATTR_QUERY,\n ATTR_REQUEST,\n ATTR_SHOW,\n ATTR_URL_LINK,\n ATTR_URL_PUSH,\n ATTR_URL_UNKNOWN,\n DEVELOPER_ATTRIBUTES,\n HUB_TAG_NAME,\n LB_RESERVED_PREFIX,\n} from \"../core/lb-constants.js\";\nimport { isRowRequest } from \"../core/lb-types.js\";\nimport type { PageFile } from \"./locations.js\";\nimport type { OriginScan } from \"./origins.js\";\n\n/**\n * The hub calls showModal() on the `<dialog>` carrying lb-url-unknown\n * (hub/lb-hub.browser.ts), and looks for it inside itself, as it does for\n * everything else it acts on. A dialog elsewhere in the body would never open\n * and would say nothing about why.\n */\nfunction checkUnknownPageDialog(chromeFile: string, body: ParentNode): void {\n const el = body.querySelector(`[${ATTR_URL_UNKNOWN}]`);\n if (!el) return;\n if (!el.closest(HUB_TAG_NAME)) {\n throw new Error(\n `assemble: '${chromeFile}' declares ${ATTR_URL_UNKNOWN} outside ` +\n `<${HUB_TAG_NAME}>, where the hub cannot reach it`,\n );\n }\n}\n\n/**\n * Where the markup stands while the check walks it: inside some `lb-query`,\n * and whether there is a row there to take a value from. Inside a row\n * template there always is. Inside an `lb-query` outside one, there is when\n * the query is of kind `row`, which only the server knows.\n */\ninterface Scope {\n row: boolean | \"unknown\";\n}\n\n/**\n * The markup, checked once against the finished document rather than\n * discovered one console error at a time in a browser.\n *\n * Every rule here is a fact the markup states about itself. The developer\n * writes `lb-*` attributes in markup, and script never assigns them, which is\n * what makes checking the markup sound. See docs/reference/data-binding.md.\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 element around it.\n */\nfunction walkMarkup(\n node: ParentNode,\n scope: Scope | null,\n problems: string[],\n): void {\n for (const el of node.children) {\n const tag = el.localName;\n let here = scope;\n if (el.hasAttribute(ATTR_QUERY)) here = { row: \"unknown\" };\n\n for (const { name } of el.attributes) {\n if (\n name.startsWith(LB_RESERVED_PREFIX) &&\n !(DEVELOPER_ATTRIBUTES as readonly string[]).includes(name)\n ) {\n problems.push(\n `<${tag}> carries ${name}, which is not an attribute a developer ` +\n `writes`,\n );\n }\n }\n\n const request = el.getAttribute(ATTR_REQUEST);\n if (\n request !== null &&\n request.startsWith(LB_RESERVED_PREFIX) &&\n !isRowRequest(request)\n ) {\n problems.push(\n `<${tag}> carries ${ATTR_REQUEST}=\"${request}\", which begins with ` +\n `'${LB_RESERVED_PREFIX}' but names no request Loadbare provides`,\n );\n }\n if (el.hasAttribute(ATTR_URL_LINK) && tag !== \"a\") {\n problems.push(`<${tag}> carries ${ATTR_URL_LINK}; it goes on an <a>`);\n }\n if (el.hasAttribute(ATTR_URL_PUSH) && request === null) {\n problems.push(\n `<${tag}> carries ${ATTR_URL_PUSH} and no ${ATTR_REQUEST}; it goes ` +\n `on an element with ${ATTR_REQUEST}`,\n );\n }\n if (el.hasAttribute(ATTR_URL_UNKNOWN) && tag !== \"dialog\") {\n problems.push(\n `<${tag}> carries ${ATTR_URL_UNKNOWN}; it goes on a <dialog>`,\n );\n }\n\n // Judged by the scope around the element, not one the element carries:\n // its condition is a column of the row around it. With no lb-query\n // around it at all, nothing ever lands on it, and the element never\n // shows.\n const show = el.getAttribute(ATTR_SHOW);\n if (show !== null) {\n if (tag === \"template\") {\n problems.push(\n `a <template> carries ${ATTR_SHOW}=\"${show}\"; write it on the ` +\n `element it shows, and Loadbare supplies the template`,\n );\n } else if (scope === null || scope.row === false) {\n problems.push(\n `<${tag}> carries ${ATTR_SHOW}=\"${show}\" with no row around it ` +\n `to take the value from`,\n );\n }\n }\n\n walkMarkup(el, here, problems);\n // By tag name rather than by instanceof: template content belongs to a\n // document with no window, so the constructor is not reachable from the\n // node.\n if (tag === \"template\") {\n const content = (el as HTMLTemplateElement).content;\n const isRowTemplate =\n scope !== null && !!el.parentElement?.closest(`[${ATTR_QUERY}]`);\n if (isRowTemplate) {\n for (const root of content.children) {\n if (root.hasAttribute(ATTR_SHOW)) {\n problems.push(\n `<${root.localName}> is a row template's root and carries ` +\n `${ATTR_SHOW}; a row that should not show is left out by ` +\n `the query`,\n );\n }\n }\n }\n walkMarkup(content, isRowTemplate ? { row: true } : here, problems);\n }\n }\n}\n\n/**\n * Ship every `lb-show` element already inside the template the hub keeps an\n * absent branch in, so nothing conditional shows until its value says so.\n * A page is in the document before its data arrives, and a value that has not\n * arrived is no value.\n *\n * Elements are collected before any is moved, and a wrapped element is still\n * reached once its outer one has gone into a template, because the references\n * are held.\n */\nfunction wrapShown(holder: HTMLTemplateElement): void {\n const doc = holder.ownerDocument;\n const shown: Element[] = [];\n const collect = (root: ParentNode): void => {\n shown.push(...root.querySelectorAll(`[${ATTR_SHOW}]`));\n for (const t of root.querySelectorAll(\"template\")) {\n collect((t as HTMLTemplateElement).content);\n }\n };\n collect(holder.content);\n for (const el of shown) {\n const parked = doc.createElement(\"template\");\n parked.setAttribute(ATTR_SHOW, el.getAttribute(ATTR_SHOW)!);\n el.replaceWith(parked);\n parked.content.append(el);\n }\n}\n\n/**\n * Expand one file's markup, naming the file if expansion refuses it.\n *\n * What comes back is the tree the browser will build, so every pass after\n * this reads and changes it in place, and it is serialized once, when it\n * ships.\n */\nfunction expandFile(\n file: string,\n source: string,\n defs: Definitions,\n): HTMLTemplateElement {\n try {\n return expandTree(source, defs);\n } catch (e) {\n throw new Error(`assemble: '${file}': ${(e as Error).message}`);\n }\n}\n\n/** Check one expanded fragment, and name the file if anything is wrong. */\nfunction checkMarkup(file: string, root: ParentNode): void {\n const problems: string[] = [];\n walkMarkup(root, null, problems);\n if (problems.length > 0) {\n throw new Error(`assemble: '${file}' markup:\\n ` + problems.join(`\\n `));\n }\n}\n\n/**\n * A page file's `<title>` names the page. It is lifted out, since a page is\n * a fragment inside `<main>` and a `<title>` belongs in a document's head,\n * and its text content is stamped on the page as ATTR_PAGE_TITLE.\n */\nfunction liftTitle(holder: HTMLTemplateElement): string | null {\n const title = holder.content.querySelector(\"title\");\n if (!title) return null;\n const text = title.textContent?.trim() ?? \"\";\n title.remove();\n return text;\n}\n\n/** An attribute value, escaped for a double-quoted attribute. */\nfunction attributeValue(text: string): string {\n return text.replace(/&/g, \"&\").replace(/\"/g, \""\");\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 // Tags are collected from each tree as it goes by. What ships is these\n // trees and the chrome around its body, which is collected on its own.\n const usedTags = new Set<string>();\n const templates: string[] = [];\n for (const { name, file } of pages) {\n const authored = await readFile(file, \"utf-8\");\n const host = expandFile(file, authored, defs);\n const title = liftTitle(host);\n checkMarkup(file, host.content);\n wrapShown(host);\n customElementTags(host.content, usedTags);\n const titled =\n title === null ? \"\" : ` ${ATTR_PAGE_TITLE}=\"${attributeValue(title)}\"`;\n templates.push(\n `<template ${ATTR_PAGE}=\"${name}\"${titled}>${host.innerHTML}</template>`,\n );\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 chromeBody = expandFile(chromeFile, bodyContent!, defs);\n checkUnknownPageDialog(chromeFile, chromeBody.content);\n checkMarkup(chromeFile, chromeBody.content);\n wrapShown(chromeBody);\n customElementTags(chromeBody.content, usedTags);\n customElementTags(chromeSource.replace(whole!, \"\"), usedTags);\n const withPages = chromeSource.replace(\n whole!,\n () =>\n `<body${bodyAttrs}>${chromeBody.innerHTML}\\n${templates.join(\"\\n\")}\\n</body>`,\n );\n\n return { html: await formatHtml(withPages), usedTags };\n}\n"]}
|
package/dist/build/expand.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export declare function addDefinitions(defs: Definitions, files: Map<string, str
|
|
|
34
34
|
* hyphen in the tag name is the one thing a custom element is guaranteed to
|
|
35
35
|
* have, per the Custom Elements spec, and nothing native has one.
|
|
36
36
|
*/
|
|
37
|
-
export declare function customElementTags(
|
|
37
|
+
export declare function customElementTags(from: string | ParentNode, tags?: Set<string>): Set<string>;
|
|
38
38
|
/**
|
|
39
39
|
* Expand every widget tag in an authored page. Throws rather than shipping.
|
|
40
40
|
*
|
|
@@ -42,4 +42,9 @@ export declare function customElementTags(html: string): Set<string>;
|
|
|
42
42
|
* checked for cycles. Handed a cyclic map directly, this recurses forever.
|
|
43
43
|
*/
|
|
44
44
|
export declare function expand(source: string, defs: Definitions): string;
|
|
45
|
+
/**
|
|
46
|
+
* The same, as the tree the browser builds from it: a template, whose content
|
|
47
|
+
* the builder's later passes read and change before serializing it once.
|
|
48
|
+
*/
|
|
49
|
+
export declare function expandTree(source: string, defs: Definitions): HTMLTemplateElement;
|
|
45
50
|
//# sourceMappingURL=expand.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AA8CjD;;;;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,CAC/B,IAAI,EAAE,MAAM,GAAG,UAAU,EACzB,IAAI,cAAoB,GACvB,GAAG,CAAC,MAAM,CAAC,CAQb;AA+OD;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,CAEhE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,WAAW,GAChB,mBAAmB,CAIrB"}
|