@loadbare/app 0.11.0 → 0.13.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/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +61 -1
- package/dist/build/assemble.js.map +1 -1
- package/dist/build/cli.js +2 -1
- package/dist/build/cli.js.map +1 -1
- package/dist/build/elements.d.ts +9 -1
- package/dist/build/elements.d.ts.map +1 -1
- package/dist/build/elements.js +50 -0
- package/dist/build/elements.js.map +1 -1
- package/dist/build/origins.d.ts +2 -0
- package/dist/build/origins.d.ts.map +1 -1
- package/dist/build/origins.js +1 -1
- package/dist/build/origins.js.map +1 -1
- package/dist/core/lb-constants.d.ts +26 -1
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +79 -0
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/core/lb-types.d.ts +58 -18
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js +74 -11
- package/dist/core/lb-types.js.map +1 -1
- package/dist/hub/lb-apply.d.ts +44 -16
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +694 -49
- package/dist/hub/lb-apply.js.map +1 -1
- package/dist/hub/lb-hub.browser.d.ts.map +1 -1
- package/dist/hub/lb-hub.browser.js +168 -36
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/dist/server/lb-express.d.ts +6 -4
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +32 -14
- package/dist/server/lb-express.js.map +1 -1
- package/dist/server/lb-server.d.ts +49 -10
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +87 -28
- package/dist/server/lb-server.js.map +1 -1
- package/docs/TECHREF-1.0.md +364 -72
- package/docs/comparison.md +16 -10
- package/docs/possible-ideas.md +188 -0
- package/docs/reference/chrome.md +22 -1
- package/docs/reference/custom-elements.md +90 -29
- package/docs/reference/data-binding.md +132 -7
- package/docs/reference/page-files.md +48 -11
- package/docs/reference/server.md +4 -0
- package/docs/reference/widgets.md +94 -24
- package/docs/roadmap.md +10 -6
- package/docs/testing.md +21 -10
- package/package.json +2 -3
- package/skills/loadbare-app/SKILL.md +85 -12
- package/skills/loadbare-app/references/TECHREF-1.0.md +364 -72
- package/skills/loadbare-app/references/chrome.md +22 -1
- package/skills/loadbare-app/references/custom-elements.md +90 -29
- package/skills/loadbare-app/references/data-binding.md +132 -7
- package/skills/loadbare-app/references/page-files.md +48 -11
- package/skills/loadbare-app/references/server.md +4 -0
- package/skills/loadbare-app/references/widgets.md +94 -24
|
@@ -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;AA4BH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AA0R/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,CAwDtC"}
|
package/dist/build/assemble.js
CHANGED
|
@@ -19,8 +19,41 @@
|
|
|
19
19
|
import { readFile } from "node:fs/promises";
|
|
20
20
|
import { addDefinitions, customElementTags, expandTree, } from "./expand.js";
|
|
21
21
|
import { formatHtml } from "./format.js";
|
|
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";
|
|
22
|
+
import { AGGREGATES, ATTR_COUNT, ATTR_GROUP, 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, SHOW_NOT, } from "../core/lb-constants.js";
|
|
23
23
|
import { isRowRequest } from "../core/lb-types.js";
|
|
24
|
+
/**
|
|
25
|
+
* The hub is one element, and it swaps each page into the one `<main>`
|
|
26
|
+
* inside it (hub/lb-hub.browser.ts), so the chrome holds exactly one of each,
|
|
27
|
+
* the `<main>` inside the hub and empty, since whatever it held would be
|
|
28
|
+
* replaced by the first page. A page is swapped into that `<main>`, so it
|
|
29
|
+
* carries neither.
|
|
30
|
+
*/
|
|
31
|
+
function checkChrome(chromeFile, body) {
|
|
32
|
+
const where = `assemble: '${chromeFile}'`;
|
|
33
|
+
const hubs = body.querySelectorAll(HUB_TAG_NAME).length;
|
|
34
|
+
if (hubs !== 1) {
|
|
35
|
+
throw new Error(`${where} has ${hubs} <${HUB_TAG_NAME}>; it needs exactly one`);
|
|
36
|
+
}
|
|
37
|
+
const mains = body.querySelectorAll("main");
|
|
38
|
+
if (mains.length !== 1) {
|
|
39
|
+
throw new Error(`${where} has ${mains.length} <main>; it needs exactly one`);
|
|
40
|
+
}
|
|
41
|
+
const main = mains[0];
|
|
42
|
+
if (!main.closest(HUB_TAG_NAME)) {
|
|
43
|
+
throw new Error(`${where} has its <main> outside <${HUB_TAG_NAME}>, where the hub cannot reach it`);
|
|
44
|
+
}
|
|
45
|
+
if (main.children.length > 0 || main.textContent.trim() !== "") {
|
|
46
|
+
throw new Error(`${where} has content in its <main>, which the first page replaces; it goes empty`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** A page lands inside the chrome's `<main>`, so it brings no second one and no hub. */
|
|
50
|
+
function checkPage(file, root) {
|
|
51
|
+
for (const tag of [HUB_TAG_NAME, "main"]) {
|
|
52
|
+
if (root.querySelector(tag)) {
|
|
53
|
+
throw new Error(`assemble: '${file}' carries a <${tag}>, which belongs to the chrome alone`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
24
57
|
/**
|
|
25
58
|
* The hub calls showModal() on the `<dialog>` carrying lb-url-unknown
|
|
26
59
|
* (hub/lb-hub.browser.ts), and looks for it inside itself, as it does for
|
|
@@ -77,6 +110,27 @@ function walkMarkup(node, scope, problems) {
|
|
|
77
110
|
if (el.hasAttribute(ATTR_URL_UNKNOWN) && tag !== "dialog") {
|
|
78
111
|
problems.push(`<${tag}> carries ${ATTR_URL_UNKNOWN}; it goes on a <dialog>`);
|
|
79
112
|
}
|
|
113
|
+
// A group is a template holding a heading and the template its
|
|
114
|
+
// contents go in; an aggregate is set on an element, from the column it
|
|
115
|
+
// names.
|
|
116
|
+
if (el.hasAttribute(ATTR_GROUP)) {
|
|
117
|
+
if (tag !== "template") {
|
|
118
|
+
problems.push(`<${tag}> carries ${ATTR_GROUP}; it goes on a <template>`);
|
|
119
|
+
}
|
|
120
|
+
else if (!el.content.querySelector(`template:not([${ATTR_SHOW}])`)) {
|
|
121
|
+
problems.push(`a <template ${ATTR_GROUP}> holds no <template> for its contents`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
for (const name of AGGREGATES) {
|
|
125
|
+
if (!el.hasAttribute(name))
|
|
126
|
+
continue;
|
|
127
|
+
if (tag === "template") {
|
|
128
|
+
problems.push(`a <template> carries ${name}; it goes on an element`);
|
|
129
|
+
}
|
|
130
|
+
else if (name !== ATTR_COUNT && !el.getAttribute(name)) {
|
|
131
|
+
problems.push(`<${tag}> carries ${name} naming no column`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
80
134
|
// Judged by the scope around the element, not one the element carries:
|
|
81
135
|
// its condition is a column of the row around it. With no lb-query
|
|
82
136
|
// around it at all, nothing ever lands on it, and the element never
|
|
@@ -91,6 +145,10 @@ function walkMarkup(node, scope, problems) {
|
|
|
91
145
|
problems.push(`<${tag}> carries ${ATTR_SHOW}="${show}" with no row around it ` +
|
|
92
146
|
`to take the value from`);
|
|
93
147
|
}
|
|
148
|
+
else if (show === SHOW_NOT || show.startsWith(SHOW_NOT + SHOW_NOT)) {
|
|
149
|
+
problems.push(`<${tag}> carries ${ATTR_SHOW}="${show}"; write one ` +
|
|
150
|
+
`${SHOW_NOT} and then the column it reverses`);
|
|
151
|
+
}
|
|
94
152
|
}
|
|
95
153
|
walkMarkup(el, here, problems);
|
|
96
154
|
// By tag name rather than by instanceof: template content belongs to a
|
|
@@ -195,6 +253,7 @@ export async function assemble({ chromeFile, pages, scans, }) {
|
|
|
195
253
|
const authored = await readFile(file, "utf-8");
|
|
196
254
|
const host = expandFile(file, authored, defs);
|
|
197
255
|
const title = liftTitle(host);
|
|
256
|
+
checkPage(file, host.content);
|
|
198
257
|
checkMarkup(file, host.content);
|
|
199
258
|
wrapShown(host);
|
|
200
259
|
customElementTags(host.content, usedTags);
|
|
@@ -214,6 +273,7 @@ export async function assemble({ chromeFile, pages, scans, }) {
|
|
|
214
273
|
}
|
|
215
274
|
const [whole, bodyAttrs, bodyContent] = body;
|
|
216
275
|
const chromeBody = expandFile(chromeFile, bodyContent, defs);
|
|
276
|
+
checkChrome(chromeFile, chromeBody.content);
|
|
217
277
|
checkUnknownPageDialog(chromeFile, chromeBody.content);
|
|
218
278
|
checkMarkup(chromeFile, chromeBody.content);
|
|
219
279
|
wrapShown(chromeBody);
|
|
@@ -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,EACL,cAAc,EACd,iBAAiB,EACjB,UAAU,GAEX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,SAAS,EACT,eAAe,EACf,UAAU,EACV,YAAY,EACZ,SAAS,EACT,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,oBAAoB,EACpB,YAAY,EACZ,kBAAkB,GACnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAInD;;;;;GAKG;AACH,SAAS,sBAAsB,CAAC,UAAkB,EAAE,IAAgB;IAClE,MAAM,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,gBAAgB,GAAG,CAAC,CAAC;IACvD,IAAI,CAAC,EAAE;QAAE,OAAO;IAChB,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CACb,cAAc,UAAU,cAAc,gBAAgB,WAAW;YAC/D,IAAI,YAAY,kCAAkC,CACrD,CAAC;IACJ,CAAC;AACH,CAAC;AAYD;;;;;;;;;;GAUG;AACH,SAAS,UAAU,CACjB,IAAgB,EAChB,KAAmB,EACnB,QAAkB;IAElB,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,EAAE,CAAC,SAAS,CAAC;QACzB,IAAI,IAAI,GAAG,KAAK,CAAC;QACjB,IAAI,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC;YAAE,IAAI,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;QAE3D,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,UAAU,EAAE,CAAC;YACrC,IACE,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC;gBACnC,CAAE,oBAA0C,CAAC,QAAQ,CAAC,IAAI,CAAC,EAC3D,CAAC;gBACD,QAAQ,CAAC,IAAI,CACX,IAAI,GAAG,aAAa,IAAI,0CAA0C;oBAChE,QAAQ,CACX,CAAC;YACJ,CAAC;QACH,CAAC;QAED,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;QAC9C,IACE,OAAO,KAAK,IAAI;YAChB,OAAO,CAAC,UAAU,CAAC,kBAAkB,CAAC;YACtC,CAAC,YAAY,CAAC,OAAO,CAAC,EACtB,CAAC;YACD,QAAQ,CAAC,IAAI,CACX,IAAI,GAAG,aAAa,YAAY,KAAK,OAAO,uBAAuB;gBACjE,IAAI,kBAAkB,0CAA0C,CACnE,CAAC;QACJ,CAAC;QACD,IAAI,EAAE,CAAC,YAAY,CAAC,aAAa,CAAC,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;YAClD,QAAQ,CAAC,IAAI,CAAC,IAAI,GAAG,aAAa,aAAa,qBAAqB,CAAC,CAAC;QACxE,CAAC;QACD,IAAI,EAAE,CAAC,YAAY,CAAC,aAAa,CAAC,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACvD,QAAQ,CAAC,IAAI,CACX,IAAI,GAAG,aAAa,aAAa,WAAW,YAAY,YAAY;gBAClE,sBAAsB,YAAY,EAAE,CACvC,CAAC;QACJ,CAAC;QACD,IAAI,EAAE,CAAC,YAAY,CAAC,gBAAgB,CAAC,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;YAC1D,QAAQ,CAAC,IAAI,CACX,IAAI,GAAG,aAAa,gBAAgB,yBAAyB,CAC9D,CAAC;QACJ,CAAC;QAED,uEAAuE;QACvE,mEAAmE;QACnE,oEAAoE;QACpE,SAAS;QACT,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,KAAK,CAAC,GAAG,KAAK,KAAK,EAAE,CAAC;gBACjD,QAAQ,CAAC,IAAI,CACX,IAAI,GAAG,aAAa,SAAS,KAAK,IAAI,0BAA0B;oBAC9D,wBAAwB,CAC3B,CAAC;YACJ,CAAC;QACH,CAAC;QAED,UAAU,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC/B,uEAAuE;QACvE,wEAAwE;QACxE,QAAQ;QACR,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;YACvB,MAAM,OAAO,GAAI,EAA0B,CAAC,OAAO,CAAC;YACpD,MAAM,aAAa,GACjB,KAAK,KAAK,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC,aAAa,EAAE,OAAO,CAAC,IAAI,UAAU,GAAG,CAAC,CAAC;YACnE,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,UAAU,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,SAAS,CAAC,MAA2B;IAC5C,MAAM,GAAG,GAAG,MAAM,CAAC,aAAa,CAAC;IACjC,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,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;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,UAAU,CACjB,IAAY,EACZ,MAAc,EACd,IAAiB;IAEjB,IAAI,CAAC;QACH,OAAO,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,MAAO,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;IAClE,CAAC;AACH,CAAC;AAED,2EAA2E;AAC3E,SAAS,WAAW,CAAC,IAAY,EAAE,IAAgB;IACjD,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IACjC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,eAAe,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7E,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,SAAS,CAAC,MAA2B;IAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IACpD,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC7C,KAAK,CAAC,MAAM,EAAE,CAAC;IACf,OAAO,IAAI,CAAC;AACd,CAAC;AAED,iEAAiE;AACjE,SAAS,cAAc,CAAC,IAAY;IAClC,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7D,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,uEAAuE;IACvE,uEAAuE;IACvE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,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,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC9C,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;QAC9B,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAChC,SAAS,CAAC,IAAI,CAAC,CAAC;QAChB,iBAAiB,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QAC1C,MAAM,MAAM,GACV,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,eAAe,KAAK,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC;QACzE,SAAS,CAAC,IAAI,CACZ,aAAa,SAAS,KAAK,IAAI,IAAI,MAAM,IAAI,IAAI,CAAC,SAAS,aAAa,CACzE,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,UAAU,GAAG,UAAU,CAAC,UAAU,EAAE,WAAY,EAAE,IAAI,CAAC,CAAC;IAC9D,sBAAsB,CAAC,UAAU,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;IACvD,WAAW,CAAC,UAAU,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;IAC5C,SAAS,CAAC,UAAU,CAAC,CAAC;IACtB,iBAAiB,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAChD,iBAAiB,CAAC,YAAY,CAAC,OAAO,CAAC,KAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CACpC,KAAM,EACN,GAAG,EAAE,CACH,QAAQ,SAAS,IAAI,UAAU,CAAC,SAAS,KAAK,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAChF,CAAC;IAEF,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;AACzD,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 {\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"]}
|
|
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 AGGREGATES,\n ATTR_COUNT,\n ATTR_GROUP,\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 SHOW_NOT,\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 is one element, and it swaps each page into the one `<main>`\n * inside it (hub/lb-hub.browser.ts), so the chrome holds exactly one of each,\n * the `<main>` inside the hub and empty, since whatever it held would be\n * replaced by the first page. A page is swapped into that `<main>`, so it\n * carries neither.\n */\nfunction checkChrome(chromeFile: string, body: ParentNode): void {\n const where = `assemble: '${chromeFile}'`;\n const hubs = body.querySelectorAll(HUB_TAG_NAME).length;\n if (hubs !== 1) {\n throw new Error(\n `${where} has ${hubs} <${HUB_TAG_NAME}>; it needs exactly one`,\n );\n }\n const mains = body.querySelectorAll(\"main\");\n if (mains.length !== 1) {\n throw new Error(\n `${where} has ${mains.length} <main>; it needs exactly one`,\n );\n }\n const main = mains[0]!;\n if (!main.closest(HUB_TAG_NAME)) {\n throw new Error(\n `${where} has its <main> outside <${HUB_TAG_NAME}>, where the hub cannot reach it`,\n );\n }\n if (main.children.length > 0 || main.textContent!.trim() !== \"\") {\n throw new Error(\n `${where} has content in its <main>, which the first page replaces; it goes empty`,\n );\n }\n}\n\n/** A page lands inside the chrome's `<main>`, so it brings no second one and no hub. */\nfunction checkPage(file: string, root: ParentNode): void {\n for (const tag of [HUB_TAG_NAME, \"main\"]) {\n if (root.querySelector(tag)) {\n throw new Error(\n `assemble: '${file}' carries a <${tag}>, which belongs to the chrome alone`,\n );\n }\n }\n}\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 // A group is a template holding a heading and the template its\n // contents go in; an aggregate is set on an element, from the column it\n // names.\n if (el.hasAttribute(ATTR_GROUP)) {\n if (tag !== \"template\") {\n problems.push(\n `<${tag}> carries ${ATTR_GROUP}; it goes on a <template>`,\n );\n } else if (\n !(el as HTMLTemplateElement).content.querySelector(\n `template:not([${ATTR_SHOW}])`,\n )\n ) {\n problems.push(\n `a <template ${ATTR_GROUP}> holds no <template> for its contents`,\n );\n }\n }\n for (const name of AGGREGATES) {\n if (!el.hasAttribute(name)) continue;\n if (tag === \"template\") {\n problems.push(`a <template> carries ${name}; it goes on an element`);\n } else if (name !== ATTR_COUNT && !el.getAttribute(name)) {\n problems.push(`<${tag}> carries ${name} naming no column`);\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 } else if (show === SHOW_NOT || show.startsWith(SHOW_NOT + SHOW_NOT)) {\n problems.push(\n `<${tag}> carries ${ATTR_SHOW}=\"${show}\"; write one ` +\n `${SHOW_NOT} and then the column it reverses`,\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 checkPage(file, host.content);\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 checkChrome(chromeFile, chromeBody.content);\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/cli.js
CHANGED
|
@@ -39,7 +39,7 @@ import { mkdir, watch, writeFile } from "node:fs/promises";
|
|
|
39
39
|
import path from "node:path";
|
|
40
40
|
import * as esbuild from "esbuild";
|
|
41
41
|
import { assemble } from "./assemble.js";
|
|
42
|
-
import { clientEntrySource, resolveElements } from "./elements.js";
|
|
42
|
+
import { checkReservedMethods, clientEntrySource, resolveElements, } from "./elements.js";
|
|
43
43
|
import { resolveLocations } from "./locations.js";
|
|
44
44
|
import { cssFrom, scanAll } from "./origins.js";
|
|
45
45
|
import { pagesSource } from "./pages.js";
|
|
@@ -63,6 +63,7 @@ async function build() {
|
|
|
63
63
|
await mkdir(locations.outDir, { recursive: true });
|
|
64
64
|
await writeFile(outFile, html, "utf-8");
|
|
65
65
|
const { resolved } = resolveElements({ used: usedTags, scans });
|
|
66
|
+
await checkReservedMethods(resolved);
|
|
66
67
|
await writeFile(entryFile, clientEntrySource(resolved, locations.outDir), "utf-8");
|
|
67
68
|
await esbuild.build({
|
|
68
69
|
entryPoints: [entryFile],
|
package/dist/build/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../build/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,
|
|
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 {\n checkReservedMethods,\n clientEntrySource,\n resolveElements,\n} 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 checkReservedMethods(resolved);\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"]}
|
package/dist/build/elements.d.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* one origin is the error case, and build/origins.ts raises it while
|
|
22
22
|
* scanning. Nothing is scanned here.
|
|
23
23
|
*/
|
|
24
|
-
import type
|
|
24
|
+
import { type OriginScan } from "./origins.js";
|
|
25
25
|
export { loadElementManifest, type ElementManifest } from "./origins.js";
|
|
26
26
|
/** A resolved tag: a bare specifier to import, or a local file to import by relative path. */
|
|
27
27
|
export type Resolved = {
|
|
@@ -40,6 +40,14 @@ export interface ResolveResult {
|
|
|
40
40
|
resolved: Map<string, Resolved>;
|
|
41
41
|
}
|
|
42
42
|
export declare function resolveElements({ used, scans, }: ResolveOptions): ResolveResult;
|
|
43
|
+
/**
|
|
44
|
+
* Loadbare owns every `lb` method name on a custom element. A script that
|
|
45
|
+
* declares one Loadbare does not call is either a misspelling of one it does,
|
|
46
|
+
* which the hub would then silently never call, or a name Loadbare may define
|
|
47
|
+
* tomorrow with a meaning of its own. Both are refused here, in every script
|
|
48
|
+
* the build ships but Loadbare's own.
|
|
49
|
+
*/
|
|
50
|
+
export declare function checkReservedMethods(resolved: Map<string, Resolved>): Promise<void>;
|
|
43
51
|
/**
|
|
44
52
|
* The generated client entry point — every custom element the assembled
|
|
45
53
|
* document actually uses, and nothing else. Written to `outDir` so a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"elements.d.ts","sourceRoot":"","sources":["../../build/elements.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;
|
|
1
|
+
{"version":3,"file":"elements.d.ts","sourceRoot":"","sources":["../../build/elements.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAKH,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAEhE,OAAO,EAAE,mBAAmB,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAEzE,8FAA8F;AAC9F,MAAM,MAAM,QAAQ,GAClB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAClB,8EAA8E;IAC9E,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;CACjC;AAED,wBAAgB,eAAe,CAAC,EAC9B,IAAI,EACJ,KAAK,GACN,EAAE,cAAc,GAAG,aAAa,CA+ChC;AAWD;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC,CAmBf;AAeD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,EAC/B,MAAM,EAAE,MAAM,GACb,MAAM,CAcR"}
|
package/dist/build/elements.js
CHANGED
|
@@ -21,7 +21,10 @@
|
|
|
21
21
|
* one origin is the error case, and build/origins.ts raises it while
|
|
22
22
|
* scanning. Nothing is scanned here.
|
|
23
23
|
*/
|
|
24
|
+
import { access, readFile } from "node:fs/promises";
|
|
24
25
|
import path from "node:path";
|
|
26
|
+
import { LB_METHODS } from "../core/lb-constants.js";
|
|
27
|
+
import { MODULE_SUFFIXES } from "./origins.js";
|
|
25
28
|
export { loadElementManifest } from "./origins.js";
|
|
26
29
|
export function resolveElements({ used, scans, }) {
|
|
27
30
|
const resolved = new Map();
|
|
@@ -66,6 +69,53 @@ export function resolveElements({ used, scans, }) {
|
|
|
66
69
|
}
|
|
67
70
|
return { resolved };
|
|
68
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* A declaration at the start of a line whose name begins with `lb` and a
|
|
74
|
+
* capital: a method, an accessor, or a field, with whatever modifiers come
|
|
75
|
+
* before it. A call through `this.` or `el.` never starts a line with the
|
|
76
|
+
* name, so it is not taken for one.
|
|
77
|
+
*/
|
|
78
|
+
const LB_DECLARATION = /^[ \t]*(?:(?:static|async|override|public|private|protected|readonly|get|set)[ \t]+)*(lb[A-Z]\w*)[ \t]*(?:[(<=:!]|\?[ \t]*[(<:])/gm;
|
|
79
|
+
/**
|
|
80
|
+
* Loadbare owns every `lb` method name on a custom element. A script that
|
|
81
|
+
* declares one Loadbare does not call is either a misspelling of one it does,
|
|
82
|
+
* which the hub would then silently never call, or a name Loadbare may define
|
|
83
|
+
* tomorrow with a meaning of its own. Both are refused here, in every script
|
|
84
|
+
* the build ships but Loadbare's own.
|
|
85
|
+
*/
|
|
86
|
+
export async function checkReservedMethods(resolved) {
|
|
87
|
+
const known = LB_METHODS;
|
|
88
|
+
const problems = [];
|
|
89
|
+
for (const [tag, entry] of resolved) {
|
|
90
|
+
if (entry.kind !== "file")
|
|
91
|
+
continue;
|
|
92
|
+
const file = await moduleFile(entry.value);
|
|
93
|
+
const source = await readFile(file, "utf-8");
|
|
94
|
+
for (const [, name] of source.matchAll(LB_DECLARATION)) {
|
|
95
|
+
if (known.includes(name))
|
|
96
|
+
continue;
|
|
97
|
+
problems.push(`<${tag}> in '${file}' declares ${name}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (problems.length > 0) {
|
|
101
|
+
throw new Error(`build: a method name beginning with lb belongs to Loadbare, which ` +
|
|
102
|
+
`defines only ${known.join(", ")}:\n ` +
|
|
103
|
+
problems.join("\n "));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** The file behind an import path that has had its extension taken off. */
|
|
107
|
+
async function moduleFile(stripped) {
|
|
108
|
+
for (const ext of MODULE_SUFFIXES) {
|
|
109
|
+
try {
|
|
110
|
+
await access(stripped + ext);
|
|
111
|
+
return stripped + ext;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Try the next extension.
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
throw new Error(`build: no script at '${stripped}'`);
|
|
118
|
+
}
|
|
69
119
|
/**
|
|
70
120
|
* The generated client entry point — every custom element the assembled
|
|
71
121
|
* document actually uses, and nothing else. Written to `outDir` so a
|
|
@@ -1 +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
|
+
{"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 { access, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { LB_METHODS } from \"../core/lb-constants.js\";\nimport { MODULE_SUFFIXES, 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 * A declaration at the start of a line whose name begins with `lb` and a\n * capital: a method, an accessor, or a field, with whatever modifiers come\n * before it. A call through `this.` or `el.` never starts a line with the\n * name, so it is not taken for one.\n */\nconst LB_DECLARATION =\n /^[ \\t]*(?:(?:static|async|override|public|private|protected|readonly|get|set)[ \\t]+)*(lb[A-Z]\\w*)[ \\t]*(?:[(<=:!]|\\?[ \\t]*[(<:])/gm;\n\n/**\n * Loadbare owns every `lb` method name on a custom element. A script that\n * declares one Loadbare does not call is either a misspelling of one it does,\n * which the hub would then silently never call, or a name Loadbare may define\n * tomorrow with a meaning of its own. Both are refused here, in every script\n * the build ships but Loadbare's own.\n */\nexport async function checkReservedMethods(\n resolved: Map<string, Resolved>,\n): Promise<void> {\n const known: readonly string[] = LB_METHODS;\n const problems: string[] = [];\n for (const [tag, entry] of resolved) {\n if (entry.kind !== \"file\") continue;\n const file = await moduleFile(entry.value);\n const source = await readFile(file, \"utf-8\");\n for (const [, name] of source.matchAll(LB_DECLARATION)) {\n if (known.includes(name!)) continue;\n problems.push(`<${tag}> in '${file}' declares ${name}`);\n }\n }\n if (problems.length > 0) {\n throw new Error(\n `build: a method name beginning with lb belongs to Loadbare, which ` +\n `defines only ${known.join(\", \")}:\\n ` +\n problems.join(\"\\n \"),\n );\n }\n}\n\n/** The file behind an import path that has had its extension taken off. */\nasync function moduleFile(stripped: string): Promise<string> {\n for (const ext of MODULE_SUFFIXES) {\n try {\n await access(stripped + ext);\n return stripped + ext;\n } catch {\n // Try the next extension.\n }\n }\n throw new Error(`build: no script at '${stripped}'`);\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"]}
|
package/dist/build/origins.d.ts
CHANGED
|
@@ -41,6 +41,8 @@
|
|
|
41
41
|
*/
|
|
42
42
|
/** Lowercase, hyphenated — the one shape every custom element tag has, and the one native elements never do. */
|
|
43
43
|
export declare const CUSTOM_ELEMENT_TAG: RegExp;
|
|
44
|
+
/** What an unmarked module ends in — enough to recognise a near miss and say so. */
|
|
45
|
+
export declare const MODULE_SUFFIXES: string[];
|
|
44
46
|
export interface Origin {
|
|
45
47
|
/** How this origin is named in an error message: a package name, or "src". */
|
|
46
48
|
name: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"origins.d.ts","sourceRoot":"","sources":["../../build/origins.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAOH,gHAAgH;AAChH,eAAO,MAAM,kBAAkB,QAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"origins.d.ts","sourceRoot":"","sources":["../../build/origins.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAOH,gHAAgH;AAChH,eAAO,MAAM,kBAAkB,QAAkC,CAAC;AAKlE,oFAAoF;AACpF,eAAO,MAAM,eAAe,UAAiB,CAAC;AAG9C,MAAM,WAAW,MAAM;IACrB,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf;;;;;OAKG;IACH,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;CACrC;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,qCAAqC;IACrC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,oEAAoE;IACpE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,oFAAoF;IACpF,GAAG,EAAE,MAAM,EAAE,CAAC;CACf;AA2DD,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CA4EpE;AAED,4GAA4G;AAC5G,MAAM,MAAM,eAAe,GAAG,MAAM,EAAE,CAAC;AAEvC;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,eAAe,CAAC,CA4B1B;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CAkBjB;AAwED,MAAM,WAAW,WAAW;IAC1B,+FAA+F;IAC/F,aAAa,EAAE,MAAM,CAAC;IACtB,kGAAkG;IAClG,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,eAAe,EACzB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,EAAE,CAAC,CAkBnB;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,WAAW,EACpB,GAAG,EAAE,MAAM,EACX,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,UAAU,EAAE,CAAC,CAIvB;AAED,iEAAiE;AACjE,wBAAgB,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,EAAE,CAErD"}
|
package/dist/build/origins.js
CHANGED
|
@@ -48,7 +48,7 @@ const HTML_SUFFIX = ".html";
|
|
|
48
48
|
/** A widget script says so in its name: `lb-thing.browser.ts` and nothing else. */
|
|
49
49
|
const BROWSER_SUFFIXES = [".browser.ts", ".browser.js"];
|
|
50
50
|
/** What an unmarked module ends in — enough to recognise a near miss and say so. */
|
|
51
|
-
const MODULE_SUFFIXES = [".ts", ".js"];
|
|
51
|
+
export const MODULE_SUFFIXES = [".ts", ".js"];
|
|
52
52
|
const CSS_SUFFIX = ".css";
|
|
53
53
|
/**
|
|
54
54
|
* Every file under `dir`, its own nested `node_modules` excluded — those
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"origins.js","sourceRoot":"","sources":["../../build/origins.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Where a build's files come from — see docs/reference/builder.md.\n *\n * Three kinds of file reach the assembled application: a `.html` definition\n * that expands a tag, a `.browser.ts` script that registers its class, and a\n * `.css` stylesheet. All three arrive from the same places, so all three are\n * found the same way: one ordered list of origins, scanned once each, read by\n * whoever needs them.\n *\n * A script says `.browser` because shipping is the thing worth being sure\n * about. Under a plain `lb-thing.ts` rule a filename alone decides what\n * crosses to the browser, so a module named after a tag someone else writes\n * later is bundled without anyone having chosen that — and the author who\n * would most like to know is the one who never considered it. Requiring the\n * word puts the decision where a reader can see it, and leaves every\n * unmarked module in a source tree exactly what it looks like: not browser\n * code. It costs a widget author one word, once.\n *\n * The order is the cascade, and it is the same for every file kind:\n *\n * 1. this package's own `hub/`\n * 2. each package declared in `imports.ts`, in first-mention order\n * 3. the application's own `--src` tree\n *\n * A package contributes one directory of its own choosing — `loadbare.widgets`\n * in its package.json, or the whole package when it says nothing. See\n * `widgetDir` below for why a package gets to answer that.\n *\n * The one built-in origin holds one tag, `lb-hub`, and it is there because\n * every application has a hub whether or not it says so. A widget is not in\n * that position: `@loadbare/widgets` is a declared package like any other,\n * and the builder cannot tell it from someone else's.\n *\n * Two rules fall out of that list, and there is no third:\n *\n * - Within one origin, two files claiming the same tag are an error. That\n * is a typo, and the origin's own author is the one who can fix it.\n * - Across origins, the later one wins. An application always has the\n * final say over a package it imported, and a package over a built-in,\n * without anyone having to out-name a dependency they do not control.\n */\n\nimport { readdir, readFile, stat } from \"node:fs/promises\";\nimport type { Dirent } from \"node:fs\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\n/** Lowercase, hyphenated — the one shape every custom element tag has, and the one native elements never do. */\nexport const CUSTOM_ELEMENT_TAG = /^[a-z][a-z0-9]*(-[a-z0-9]+)+$/;\n\nconst HTML_SUFFIX = \".html\";\n/** A widget script says so in its name: `lb-thing.browser.ts` and nothing else. */\nconst BROWSER_SUFFIXES = [\".browser.ts\", \".browser.js\"];\n/** What an unmarked module ends in — enough to recognise a near miss and say so. */\nconst MODULE_SUFFIXES = [\".ts\", \".js\"];\nconst CSS_SUFFIX = \".css\";\n\nexport interface Origin {\n /** How this origin is named in an error message: a package name, or \"src\". */\n name: string;\n /** The roots to scan. */\n dirs: string[];\n /**\n * How a script from here is imported, when a bare specifier is wanted\n * instead of the scanned file's own path. Only this package's `hub/` uses\n * it: `lb-hub` is the package's main entry point rather than a file an\n * application should reach into.\n */\n specifier?: (tag: string) => string;\n}\n\nexport interface OriginScan {\n name: string;\n specifier?: (tag: string) => string;\n /** Tag to its `.html` definition. */\n definitions: Map<string, string>;\n /** Tag to its `.browser.ts` script, the trailing `.ts` stripped. */\n scripts: Map<string, string>;\n /**\n * Tag to the unmarked module that would have claimed it — `lb-thing.ts`\n * where `lb-thing.browser.ts` was meant. Nothing is built from this. It is\n * here so build/elements.ts can name the file when the tag turns out to\n * have nothing to register it.\n */\n unmarked: Map<string, string>;\n /** Every `.css` file here, sorted by filename with the full path breaking a tie. */\n css: string[];\n}\n\n/**\n * Every file under `dir`, its own nested `node_modules` excluded — those\n * belong to the packages that declare them, not to this one.\n */\nasync function filesUnder(dir: string): Promise<string[]> {\n let entries: Dirent[];\n try {\n entries = await readdir(dir, { withFileTypes: true, recursive: true });\n } catch {\n return [];\n }\n return entries\n .filter((entry) => {\n if (!entry.isFile()) return false;\n const relative = path.relative(dir, entry.parentPath);\n return !relative.split(path.sep).includes(\"node_modules\");\n })\n .map((entry) => path.join(entry.parentPath, entry.name));\n}\n\n/**\n * One origin's contribution, classified by filename alone.\n *\n * A `.html` file is a widget's only when its basename is a tag, and a module\n * only when it is `<tag>.browser.ts` — which is what excludes `chrome.html`,\n * `about.page.html`, `about.requests.ts` and `imports.ts` here without naming\n * any of them. Those are the application's own files, and\n * build/locations.ts picks them out of `src` separately.\n *\n * A module named after a tag without the `.browser` goes in `unmarked`, so\n * an author who forgot the word gets told which file to rename rather than\n * being told their tag does not exist.\n */\n/**\n * The tag a filename claims, or null when it claims none.\n *\n * A filename is the tag verbatim, never folded. HTML matches an element name\n * case-insensitively, so folding would let `My-Widget.html` and\n * `my-widget.html` be two files for one tag — a collision on a\n * case-sensitive filesystem and one file on a case-insensitive one, which is\n * a difference between machines rather than a rule anyone can read.\n *\n * A name that is a tag only once folded is refused rather than ignored: the\n * author meant a widget, and silence would leave them with a tag that never\n * expands.\n */\nfunction tagIn(stem: string, file: string): string | null {\n if (CUSTOM_ELEMENT_TAG.test(stem)) return stem;\n const folded = stem.toLowerCase();\n if (!CUSTOM_ELEMENT_TAG.test(folded)) return null;\n const suffix = path.basename(file).slice(stem.length);\n throw new Error(\n `origins: '${file}' carries capitals; a tag is lowercase, so this ` +\n `file must be named '${folded}${suffix}'`,\n );\n}\n\nexport async function scanOrigin(origin: Origin): Promise<OriginScan> {\n const definitions = new Map<string, string>();\n const scripts = new Map<string, string>();\n const unmarked = new Map<string, string>();\n const css: string[] = [];\n\n const claim = (\n into: Map<string, string>,\n what: string,\n tag: string,\n file: string,\n ): void => {\n const already = into.get(tag);\n if (already) {\n throw new Error(\n `origins: '${origin.name}' has two ${what} files for <${tag}>: ` +\n `'${already}' and '${file}'`,\n );\n }\n into.set(tag, file);\n };\n\n for (const dir of origin.dirs) {\n for (const file of await filesUnder(dir)) {\n const name = path.basename(file);\n if (name.endsWith(CSS_SUFFIX)) {\n css.push(file);\n } else if (name.endsWith(HTML_SUFFIX)) {\n const tag = tagIn(name.slice(0, -HTML_SUFFIX.length), file);\n if (tag) claim(definitions, \"definition\", tag, file);\n } else {\n const browser = BROWSER_SUFFIXES.find((s) => name.endsWith(s));\n if (browser) {\n const tag = tagIn(name.slice(0, -browser.length), file);\n // Only the trailing \".ts\" comes off, never the \".browser\": what is\n // left becomes an import specifier, and esbuild resolves\n // \"lb-thing.browser\" to \"lb-thing.browser.ts\" the same way as to\n // \".js\".\n if (tag) {\n const ext = MODULE_SUFFIXES.find((e) => name.endsWith(e))!;\n claim(scripts, \"script\", tag, file.slice(0, -ext.length));\n }\n continue;\n }\n const ext = MODULE_SUFFIXES.find((e) => name.endsWith(e));\n if (!ext) continue;\n // A module whose name is a tag but which never said it was browser\n // code. It contributes nothing. Remembering it costs a map entry and\n // buys an error message that names the file instead of describing an\n // absence. A `.d.ts` never reaches here — stripping \".ts\" leaves a\n // dot, which is not a tag. Tested rather than tagIn()'d: an unmarked\n // module was never claimed to be a widget, so a capital in its name\n // is not a mistake anyone here can see.\n const tag = name.slice(0, -ext.length);\n if (CUSTOM_ELEMENT_TAG.test(tag) && !unmarked.has(tag)) {\n unmarked.set(tag, file);\n }\n }\n }\n }\n\n // By filename first, the full path only to break a tie. A directory a file\n // happens to sit in never affects the order.\n css.sort(\n (a, b) =>\n path.basename(a).localeCompare(path.basename(b)) || a.localeCompare(b),\n );\n\n return {\n name: origin.name,\n ...(origin.specifier ? { specifier: origin.specifier } : {}),\n definitions,\n scripts,\n unmarked,\n css,\n };\n}\n\n/** The shape of an `imports.ts` module's default export: the packages an application pulls widgets from. */\nexport type ElementManifest = string[];\n\n/**\n * `imports.ts`, executed rather than parsed — it runs through the same\n * loader as the rest of the build, so it is ordinary TypeScript, not a data\n * format with its own rules. It lists the packages an application pulls\n * widgets from, and each one becomes an origin, scanned exactly as `src` is.\n */\nexport async function loadElementManifest(\n file: string,\n): Promise<ElementManifest> {\n let mod: { default?: unknown };\n try {\n // pathToFileURL requires an absolute path; the caller's may not be one.\n mod = (await import(pathToFileURL(path.resolve(file)).href)) as {\n default?: unknown;\n };\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ERR_MODULE_NOT_FOUND\")\n return [];\n throw new Error(\n `elements: '${file}' failed to load: ${(err as Error).message}`,\n );\n }\n const declared = mod.default ?? [];\n if (!Array.isArray(declared)) {\n throw new Error(\n `imports.ts: must default-export an array of package names, got ${typeof declared}`,\n );\n }\n for (const name of declared) {\n if (typeof name !== \"string\" || name === \"\") {\n throw new Error(\n `imports.ts: every entry must be a package name, got ${JSON.stringify(name)}`,\n );\n }\n }\n return declared as ElementManifest;\n}\n\n/**\n * The ordinary node_modules walk, stopping at the first directory that has\n * the package installed — not a full resolution of an entry file, on\n * purpose: this only needs the package's root directory, and stopping short\n * avoids caring whether the package exposes an \"exports\" map at all. Which\n * part of that root holds the widgets is the package's own answer to give —\n * `widgetDir` asks it.\n */\nexport async function findInstalledPackage(\n packageName: string,\n fromDir: string,\n): Promise<string> {\n let dir = path.resolve(fromDir);\n for (;;) {\n const candidate = path.join(dir, \"node_modules\", packageName);\n try {\n await stat(path.join(candidate, \"package.json\"));\n return candidate;\n } catch {\n // not here — keep walking up\n }\n const parent = path.dirname(dir);\n if (parent === dir) {\n throw new Error(\n `origins: could not find installed package '${packageName}' above '${fromDir}'`,\n );\n }\n dir = parent;\n }\n}\n\n/**\n * Where in an installed package its widgets are, from the package's own\n * manifest:\n *\n * \"loadbare\": { \"widgets\": \"./dist\" }\n *\n * A package that says nothing has its whole directory scanned, which is what\n * every package got before this field existed.\n *\n * The field is here because a published package is not a source tree. It is a\n * distribution artifact with a shape npm defines — compiled output in `dist/`,\n * the source excluded, plus a README, a licence, docs and whatever else its\n * author ships — and scanning all of it makes every one of those files a\n * possible widget. A package that compiles then carries the same tag twice,\n * once as `.ts` and once as `.js`, and the duplicate-tag error fires on a\n * package the application does not control and cannot fix.\n *\n * So the package says where to look, and the rest of it is none of the\n * builder's business. This costs an application author nothing: they still\n * list the package in `imports.ts` and write no import statement. It costs the\n * package author one line, in the file npm already expects to carry exactly\n * this kind of declaration.\n */\nasync function widgetDir(root: string, packageName: string): Promise<string> {\n let manifest: { loadbare?: { widgets?: unknown } };\n try {\n manifest = JSON.parse(\n await readFile(path.join(root, \"package.json\"), \"utf-8\"),\n );\n } catch (err) {\n throw new Error(\n `origins: could not read the package.json of '${packageName}': ${(err as Error).message}`,\n );\n }\n\n const declared = manifest.loadbare?.widgets;\n if (declared === undefined) return root;\n\n if (typeof declared !== \"string\" || declared === \"\") {\n throw new Error(\n `origins: '${packageName}' has a \"loadbare.widgets\" that is not a path: ` +\n `${JSON.stringify(declared)}`,\n );\n }\n\n // A path out of the package would put a package's declaration in charge of\n // directories it does not own — including the application's own.\n const dir = path.resolve(root, declared);\n const inside = path.relative(root, dir);\n if (path.isAbsolute(declared) || inside.startsWith(\"..\")) {\n throw new Error(\n `origins: '${packageName}' declares widgets at '${declared}', which is outside the package`,\n );\n }\n\n // A declared directory that is not there means the package shipped without\n // its widgets — its build did not run, or `files` left the directory out.\n // Scanning the package root instead would find some of them and look like it\n // worked, so this stops.\n try {\n await stat(dir);\n } catch {\n throw new Error(\n `origins: '${packageName}' declares widgets at '${declared}', which is not in the installed package`,\n );\n }\n\n return dir;\n}\n\nexport interface BuiltinDirs {\n /** This package's own hub/ — where lb-hub's script lives, found the same way as any widget. */\n builtinHubDir: string;\n /** This package's own name, e.g. \"@loadbare/app\" — for naming the built-in's import specifier. */\n packageName: string;\n}\n\n/**\n * The cascade, in order. A declared package is an origin like any other: the\n * directory it points `loadbare.widgets` at is scanned the way `src` is — its\n * whole installed directory, when it points at nothing — and a widget there is\n * found by its filename, not by being named in a manifest.\n *\n * `hub/` is the only origin naming a specifier, because `lb-hub` is the\n * package's main entry point rather than a file to reach into.\n */\nexport async function buildOrigins(\n builtin: BuiltinDirs,\n declared: ElementManifest,\n src: string,\n): Promise<Origin[]> {\n const { builtinHubDir, packageName } = builtin;\n\n const packages: Origin[] = [];\n for (const name of declared) {\n const root = await findInstalledPackage(name, src);\n packages.push({ name, dirs: [await widgetDir(root, name)] });\n }\n\n return [\n {\n name: packageName,\n dirs: [builtinHubDir],\n specifier: () => packageName,\n },\n ...packages,\n { name: src, dirs: [src] },\n ];\n}\n\n/**\n * The whole cascade, scanned. Everything a build reads from disk that is not\n * a page, the chrome, or a page's server half comes back from here.\n */\nexport async function scanAll(\n builtin: BuiltinDirs,\n src: string,\n importsFile?: string,\n): Promise<OriginScan[]> {\n const declared = importsFile ? await loadElementManifest(importsFile) : [];\n const origins = await buildOrigins(builtin, declared, src);\n return Promise.all(origins.map(scanOrigin));\n}\n\n/** Every origin's stylesheets, concatenated in cascade order. */\nexport function cssFrom(scans: OriginScan[]): string[] {\n return scans.flatMap((scan) => scan.css);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"origins.js","sourceRoot":"","sources":["../../build/origins.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Where a build's files come from — see docs/reference/builder.md.\n *\n * Three kinds of file reach the assembled application: a `.html` definition\n * that expands a tag, a `.browser.ts` script that registers its class, and a\n * `.css` stylesheet. All three arrive from the same places, so all three are\n * found the same way: one ordered list of origins, scanned once each, read by\n * whoever needs them.\n *\n * A script says `.browser` because shipping is the thing worth being sure\n * about. Under a plain `lb-thing.ts` rule a filename alone decides what\n * crosses to the browser, so a module named after a tag someone else writes\n * later is bundled without anyone having chosen that — and the author who\n * would most like to know is the one who never considered it. Requiring the\n * word puts the decision where a reader can see it, and leaves every\n * unmarked module in a source tree exactly what it looks like: not browser\n * code. It costs a widget author one word, once.\n *\n * The order is the cascade, and it is the same for every file kind:\n *\n * 1. this package's own `hub/`\n * 2. each package declared in `imports.ts`, in first-mention order\n * 3. the application's own `--src` tree\n *\n * A package contributes one directory of its own choosing — `loadbare.widgets`\n * in its package.json, or the whole package when it says nothing. See\n * `widgetDir` below for why a package gets to answer that.\n *\n * The one built-in origin holds one tag, `lb-hub`, and it is there because\n * every application has a hub whether or not it says so. A widget is not in\n * that position: `@loadbare/widgets` is a declared package like any other,\n * and the builder cannot tell it from someone else's.\n *\n * Two rules fall out of that list, and there is no third:\n *\n * - Within one origin, two files claiming the same tag are an error. That\n * is a typo, and the origin's own author is the one who can fix it.\n * - Across origins, the later one wins. An application always has the\n * final say over a package it imported, and a package over a built-in,\n * without anyone having to out-name a dependency they do not control.\n */\n\nimport { readdir, readFile, stat } from \"node:fs/promises\";\nimport type { Dirent } from \"node:fs\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\n/** Lowercase, hyphenated — the one shape every custom element tag has, and the one native elements never do. */\nexport const CUSTOM_ELEMENT_TAG = /^[a-z][a-z0-9]*(-[a-z0-9]+)+$/;\n\nconst HTML_SUFFIX = \".html\";\n/** A widget script says so in its name: `lb-thing.browser.ts` and nothing else. */\nconst BROWSER_SUFFIXES = [\".browser.ts\", \".browser.js\"];\n/** What an unmarked module ends in — enough to recognise a near miss and say so. */\nexport const MODULE_SUFFIXES = [\".ts\", \".js\"];\nconst CSS_SUFFIX = \".css\";\n\nexport interface Origin {\n /** How this origin is named in an error message: a package name, or \"src\". */\n name: string;\n /** The roots to scan. */\n dirs: string[];\n /**\n * How a script from here is imported, when a bare specifier is wanted\n * instead of the scanned file's own path. Only this package's `hub/` uses\n * it: `lb-hub` is the package's main entry point rather than a file an\n * application should reach into.\n */\n specifier?: (tag: string) => string;\n}\n\nexport interface OriginScan {\n name: string;\n specifier?: (tag: string) => string;\n /** Tag to its `.html` definition. */\n definitions: Map<string, string>;\n /** Tag to its `.browser.ts` script, the trailing `.ts` stripped. */\n scripts: Map<string, string>;\n /**\n * Tag to the unmarked module that would have claimed it — `lb-thing.ts`\n * where `lb-thing.browser.ts` was meant. Nothing is built from this. It is\n * here so build/elements.ts can name the file when the tag turns out to\n * have nothing to register it.\n */\n unmarked: Map<string, string>;\n /** Every `.css` file here, sorted by filename with the full path breaking a tie. */\n css: string[];\n}\n\n/**\n * Every file under `dir`, its own nested `node_modules` excluded — those\n * belong to the packages that declare them, not to this one.\n */\nasync function filesUnder(dir: string): Promise<string[]> {\n let entries: Dirent[];\n try {\n entries = await readdir(dir, { withFileTypes: true, recursive: true });\n } catch {\n return [];\n }\n return entries\n .filter((entry) => {\n if (!entry.isFile()) return false;\n const relative = path.relative(dir, entry.parentPath);\n return !relative.split(path.sep).includes(\"node_modules\");\n })\n .map((entry) => path.join(entry.parentPath, entry.name));\n}\n\n/**\n * One origin's contribution, classified by filename alone.\n *\n * A `.html` file is a widget's only when its basename is a tag, and a module\n * only when it is `<tag>.browser.ts` — which is what excludes `chrome.html`,\n * `about.page.html`, `about.requests.ts` and `imports.ts` here without naming\n * any of them. Those are the application's own files, and\n * build/locations.ts picks them out of `src` separately.\n *\n * A module named after a tag without the `.browser` goes in `unmarked`, so\n * an author who forgot the word gets told which file to rename rather than\n * being told their tag does not exist.\n */\n/**\n * The tag a filename claims, or null when it claims none.\n *\n * A filename is the tag verbatim, never folded. HTML matches an element name\n * case-insensitively, so folding would let `My-Widget.html` and\n * `my-widget.html` be two files for one tag — a collision on a\n * case-sensitive filesystem and one file on a case-insensitive one, which is\n * a difference between machines rather than a rule anyone can read.\n *\n * A name that is a tag only once folded is refused rather than ignored: the\n * author meant a widget, and silence would leave them with a tag that never\n * expands.\n */\nfunction tagIn(stem: string, file: string): string | null {\n if (CUSTOM_ELEMENT_TAG.test(stem)) return stem;\n const folded = stem.toLowerCase();\n if (!CUSTOM_ELEMENT_TAG.test(folded)) return null;\n const suffix = path.basename(file).slice(stem.length);\n throw new Error(\n `origins: '${file}' carries capitals; a tag is lowercase, so this ` +\n `file must be named '${folded}${suffix}'`,\n );\n}\n\nexport async function scanOrigin(origin: Origin): Promise<OriginScan> {\n const definitions = new Map<string, string>();\n const scripts = new Map<string, string>();\n const unmarked = new Map<string, string>();\n const css: string[] = [];\n\n const claim = (\n into: Map<string, string>,\n what: string,\n tag: string,\n file: string,\n ): void => {\n const already = into.get(tag);\n if (already) {\n throw new Error(\n `origins: '${origin.name}' has two ${what} files for <${tag}>: ` +\n `'${already}' and '${file}'`,\n );\n }\n into.set(tag, file);\n };\n\n for (const dir of origin.dirs) {\n for (const file of await filesUnder(dir)) {\n const name = path.basename(file);\n if (name.endsWith(CSS_SUFFIX)) {\n css.push(file);\n } else if (name.endsWith(HTML_SUFFIX)) {\n const tag = tagIn(name.slice(0, -HTML_SUFFIX.length), file);\n if (tag) claim(definitions, \"definition\", tag, file);\n } else {\n const browser = BROWSER_SUFFIXES.find((s) => name.endsWith(s));\n if (browser) {\n const tag = tagIn(name.slice(0, -browser.length), file);\n // Only the trailing \".ts\" comes off, never the \".browser\": what is\n // left becomes an import specifier, and esbuild resolves\n // \"lb-thing.browser\" to \"lb-thing.browser.ts\" the same way as to\n // \".js\".\n if (tag) {\n const ext = MODULE_SUFFIXES.find((e) => name.endsWith(e))!;\n claim(scripts, \"script\", tag, file.slice(0, -ext.length));\n }\n continue;\n }\n const ext = MODULE_SUFFIXES.find((e) => name.endsWith(e));\n if (!ext) continue;\n // A module whose name is a tag but which never said it was browser\n // code. It contributes nothing. Remembering it costs a map entry and\n // buys an error message that names the file instead of describing an\n // absence. A `.d.ts` never reaches here — stripping \".ts\" leaves a\n // dot, which is not a tag. Tested rather than tagIn()'d: an unmarked\n // module was never claimed to be a widget, so a capital in its name\n // is not a mistake anyone here can see.\n const tag = name.slice(0, -ext.length);\n if (CUSTOM_ELEMENT_TAG.test(tag) && !unmarked.has(tag)) {\n unmarked.set(tag, file);\n }\n }\n }\n }\n\n // By filename first, the full path only to break a tie. A directory a file\n // happens to sit in never affects the order.\n css.sort(\n (a, b) =>\n path.basename(a).localeCompare(path.basename(b)) || a.localeCompare(b),\n );\n\n return {\n name: origin.name,\n ...(origin.specifier ? { specifier: origin.specifier } : {}),\n definitions,\n scripts,\n unmarked,\n css,\n };\n}\n\n/** The shape of an `imports.ts` module's default export: the packages an application pulls widgets from. */\nexport type ElementManifest = string[];\n\n/**\n * `imports.ts`, executed rather than parsed — it runs through the same\n * loader as the rest of the build, so it is ordinary TypeScript, not a data\n * format with its own rules. It lists the packages an application pulls\n * widgets from, and each one becomes an origin, scanned exactly as `src` is.\n */\nexport async function loadElementManifest(\n file: string,\n): Promise<ElementManifest> {\n let mod: { default?: unknown };\n try {\n // pathToFileURL requires an absolute path; the caller's may not be one.\n mod = (await import(pathToFileURL(path.resolve(file)).href)) as {\n default?: unknown;\n };\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ERR_MODULE_NOT_FOUND\")\n return [];\n throw new Error(\n `elements: '${file}' failed to load: ${(err as Error).message}`,\n );\n }\n const declared = mod.default ?? [];\n if (!Array.isArray(declared)) {\n throw new Error(\n `imports.ts: must default-export an array of package names, got ${typeof declared}`,\n );\n }\n for (const name of declared) {\n if (typeof name !== \"string\" || name === \"\") {\n throw new Error(\n `imports.ts: every entry must be a package name, got ${JSON.stringify(name)}`,\n );\n }\n }\n return declared as ElementManifest;\n}\n\n/**\n * The ordinary node_modules walk, stopping at the first directory that has\n * the package installed — not a full resolution of an entry file, on\n * purpose: this only needs the package's root directory, and stopping short\n * avoids caring whether the package exposes an \"exports\" map at all. Which\n * part of that root holds the widgets is the package's own answer to give —\n * `widgetDir` asks it.\n */\nexport async function findInstalledPackage(\n packageName: string,\n fromDir: string,\n): Promise<string> {\n let dir = path.resolve(fromDir);\n for (;;) {\n const candidate = path.join(dir, \"node_modules\", packageName);\n try {\n await stat(path.join(candidate, \"package.json\"));\n return candidate;\n } catch {\n // not here — keep walking up\n }\n const parent = path.dirname(dir);\n if (parent === dir) {\n throw new Error(\n `origins: could not find installed package '${packageName}' above '${fromDir}'`,\n );\n }\n dir = parent;\n }\n}\n\n/**\n * Where in an installed package its widgets are, from the package's own\n * manifest:\n *\n * \"loadbare\": { \"widgets\": \"./dist\" }\n *\n * A package that says nothing has its whole directory scanned, which is what\n * every package got before this field existed.\n *\n * The field is here because a published package is not a source tree. It is a\n * distribution artifact with a shape npm defines — compiled output in `dist/`,\n * the source excluded, plus a README, a licence, docs and whatever else its\n * author ships — and scanning all of it makes every one of those files a\n * possible widget. A package that compiles then carries the same tag twice,\n * once as `.ts` and once as `.js`, and the duplicate-tag error fires on a\n * package the application does not control and cannot fix.\n *\n * So the package says where to look, and the rest of it is none of the\n * builder's business. This costs an application author nothing: they still\n * list the package in `imports.ts` and write no import statement. It costs the\n * package author one line, in the file npm already expects to carry exactly\n * this kind of declaration.\n */\nasync function widgetDir(root: string, packageName: string): Promise<string> {\n let manifest: { loadbare?: { widgets?: unknown } };\n try {\n manifest = JSON.parse(\n await readFile(path.join(root, \"package.json\"), \"utf-8\"),\n );\n } catch (err) {\n throw new Error(\n `origins: could not read the package.json of '${packageName}': ${(err as Error).message}`,\n );\n }\n\n const declared = manifest.loadbare?.widgets;\n if (declared === undefined) return root;\n\n if (typeof declared !== \"string\" || declared === \"\") {\n throw new Error(\n `origins: '${packageName}' has a \"loadbare.widgets\" that is not a path: ` +\n `${JSON.stringify(declared)}`,\n );\n }\n\n // A path out of the package would put a package's declaration in charge of\n // directories it does not own — including the application's own.\n const dir = path.resolve(root, declared);\n const inside = path.relative(root, dir);\n if (path.isAbsolute(declared) || inside.startsWith(\"..\")) {\n throw new Error(\n `origins: '${packageName}' declares widgets at '${declared}', which is outside the package`,\n );\n }\n\n // A declared directory that is not there means the package shipped without\n // its widgets — its build did not run, or `files` left the directory out.\n // Scanning the package root instead would find some of them and look like it\n // worked, so this stops.\n try {\n await stat(dir);\n } catch {\n throw new Error(\n `origins: '${packageName}' declares widgets at '${declared}', which is not in the installed package`,\n );\n }\n\n return dir;\n}\n\nexport interface BuiltinDirs {\n /** This package's own hub/ — where lb-hub's script lives, found the same way as any widget. */\n builtinHubDir: string;\n /** This package's own name, e.g. \"@loadbare/app\" — for naming the built-in's import specifier. */\n packageName: string;\n}\n\n/**\n * The cascade, in order. A declared package is an origin like any other: the\n * directory it points `loadbare.widgets` at is scanned the way `src` is — its\n * whole installed directory, when it points at nothing — and a widget there is\n * found by its filename, not by being named in a manifest.\n *\n * `hub/` is the only origin naming a specifier, because `lb-hub` is the\n * package's main entry point rather than a file to reach into.\n */\nexport async function buildOrigins(\n builtin: BuiltinDirs,\n declared: ElementManifest,\n src: string,\n): Promise<Origin[]> {\n const { builtinHubDir, packageName } = builtin;\n\n const packages: Origin[] = [];\n for (const name of declared) {\n const root = await findInstalledPackage(name, src);\n packages.push({ name, dirs: [await widgetDir(root, name)] });\n }\n\n return [\n {\n name: packageName,\n dirs: [builtinHubDir],\n specifier: () => packageName,\n },\n ...packages,\n { name: src, dirs: [src] },\n ];\n}\n\n/**\n * The whole cascade, scanned. Everything a build reads from disk that is not\n * a page, the chrome, or a page's server half comes back from here.\n */\nexport async function scanAll(\n builtin: BuiltinDirs,\n src: string,\n importsFile?: string,\n): Promise<OriginScan[]> {\n const declared = importsFile ? await loadElementManifest(importsFile) : [];\n const origins = await buildOrigins(builtin, declared, src);\n return Promise.all(origins.map(scanOrigin));\n}\n\n/** Every origin's stylesheets, concatenated in cascade order. */\nexport function cssFrom(scans: OriginScan[]): string[] {\n return scans.flatMap((scan) => scan.css);\n}\n"]}
|