@loadbare/app 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +107 -90
- package/dist/build/assemble.js.map +1 -1
- package/dist/build/expand.d.ts +6 -1
- package/dist/build/expand.d.ts.map +1 -1
- package/dist/build/expand.js +112 -26
- package/dist/build/expand.js.map +1 -1
- package/dist/build/locations.d.ts +2 -3
- package/dist/build/locations.d.ts.map +1 -1
- package/dist/build/locations.js +2 -3
- package/dist/build/locations.js.map +1 -1
- package/dist/build/pages.d.ts +3 -4
- package/dist/build/pages.d.ts.map +1 -1
- package/dist/build/pages.js +3 -4
- package/dist/build/pages.js.map +1 -1
- package/dist/core/lb-constants.d.ts +27 -24
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +103 -168
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/core/lb-types.d.ts +64 -77
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js +40 -7
- package/dist/core/lb-types.js.map +1 -1
- package/dist/hub/lb-apply.d.ts +47 -37
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +195 -199
- package/dist/hub/lb-apply.js.map +1 -1
- package/dist/hub/lb-hub.browser.d.ts +1 -1
- package/dist/hub/lb-hub.browser.d.ts.map +1 -1
- package/dist/hub/lb-hub.browser.js +410 -449
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/dist/server/lb-express.d.ts +5 -5
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +35 -66
- package/dist/server/lb-express.js.map +1 -1
- package/dist/server/lb-server.d.ts +77 -135
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +132 -79
- package/dist/server/lb-server.js.map +1 -1
- package/docs/TECHREF-1.0.md +908 -585
- package/docs/comparison.md +243 -185
- package/docs/prior-art.md +15 -14
- package/docs/reference/builder.md +9 -3
- package/docs/reference/chrome.md +107 -56
- package/docs/reference/custom-elements.md +291 -173
- package/docs/reference/data-binding.md +381 -374
- package/docs/reference/overview.md +12 -10
- package/docs/reference/page-files.md +164 -99
- package/docs/reference/server.md +2 -2
- package/docs/reference/widgets.md +104 -110
- package/docs/roadmap.md +32 -39
- package/docs/terms-of-art.md +57 -0
- package/docs/testing.md +97 -68
- package/docs/theory.md +92 -58
- package/docs/tutorials/010-pages-and-navigation.md +20 -12
- package/docs/tutorials/020-css.md +6 -3
- package/docs/tutorials/030-html-decomposition.md +9 -7
- package/docs/tutorials/040-displaying-data.md +30 -13
- package/docs/tutorials/{050-actions.md → 050-requests.md} +25 -15
- package/docs/tutorials/060-custom-element-code.md +17 -16
- package/docs/tutorials/065-conditional-rendering.md +34 -23
- package/docs/tutorials/070-displaying-a-list.md +29 -21
- package/docs/tutorials/072-inserting-into-a-list.md +24 -16
- package/docs/tutorials/074-deleting-from-a-list.md +9 -7
- package/docs/tutorials/076-updating-a-list-item.md +11 -10
- package/docs/tutorials/080-widget-requests.md +71 -43
- package/docs/tutorials/090-using-widget-libraries.md +22 -22
- package/docs/what-does-loadbare-extend.md +124 -0
- package/package.json +1 -1
- package/skills/loadbare-app/SKILL.md +201 -123
- package/skills/loadbare-app/references/TECHREF-1.0.md +908 -585
- package/skills/loadbare-app/references/builder.md +9 -3
- package/skills/loadbare-app/references/chrome.md +107 -56
- package/skills/loadbare-app/references/custom-elements.md +291 -173
- package/skills/loadbare-app/references/data-binding.md +381 -374
- package/skills/loadbare-app/references/overview.md +12 -10
- package/skills/loadbare-app/references/page-files.md +164 -99
- package/skills/loadbare-app/references/server.md +2 -2
- package/skills/loadbare-app/references/widgets.md +104 -110
- package/docs/analysis-accidental-complexity.md +0 -149
- package/docs/analysis-closed-set.md +0 -210
package/dist/build/expand.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { readdir, readFile } from "node:fs/promises";
|
|
13
13
|
import { JSDOM } from "jsdom";
|
|
14
|
-
import {
|
|
14
|
+
import { ATTR_EXP_SLOT, ATTR_EXP_TEMPLATE } from "../core/lb-constants.js";
|
|
15
15
|
/** Lowercase, hyphenated — the one shape a custom element tag has. */
|
|
16
16
|
const CUSTOM_ELEMENT = /-/;
|
|
17
17
|
/** A placeholder is an entire attribute value or an entire text node. */
|
|
@@ -26,15 +26,15 @@ const CUSTOM_ELEMENT = /-/;
|
|
|
26
26
|
const PLACEHOLDER = /^\{\{\s*([A-Za-z][\w-]*)\s*(?:\|\s*([^}]*?)\s*)?\}\}$/;
|
|
27
27
|
/**
|
|
28
28
|
* Marks an attribute as input to expansion. Three namespaces share the tag:
|
|
29
|
-
* `lb-` holds the names Loadbare defines, `exp-` the names
|
|
29
|
+
* `lb-` holds the names Loadbare defines, `exp-` the names an element file
|
|
30
30
|
* defines, and everything unprefixed is HTML's and means what HTML says it
|
|
31
|
-
* means. The split is by owner rather than by phase — `lb-slot` and
|
|
32
|
-
* `lb-template` are expansion's own and never reach the browser, while
|
|
33
|
-
* `lb-
|
|
31
|
+
* means. The split is by owner rather than by phase — `lb-exp-slot` and
|
|
32
|
+
* `lb-exp-template` are expansion's own and never reach the browser, while
|
|
33
|
+
* `lb-query` and `lb-column` are read at both times. So an author is free to write
|
|
34
34
|
* `class`, `title` or `hidden` on any widget, whatever its parameters happen
|
|
35
|
-
* to be named. The prefix
|
|
36
|
-
*
|
|
37
|
-
* fills `{{label}}`.
|
|
35
|
+
* to be named. The prefix is needed on the tag, where a parameter and an
|
|
36
|
+
* HTML attribute would otherwise share one namespace; inside a definition
|
|
37
|
+
* `{{ }}` already marks a parameter, so `exp-label` fills `{{label}}`.
|
|
38
38
|
*/
|
|
39
39
|
const EXP_PREFIX = "exp-";
|
|
40
40
|
const dom = new JSDOM("");
|
|
@@ -45,6 +45,20 @@ function parse(source) {
|
|
|
45
45
|
holder.innerHTML = source;
|
|
46
46
|
return holder;
|
|
47
47
|
}
|
|
48
|
+
const parsedDefinitions = new Map();
|
|
49
|
+
/**
|
|
50
|
+
* A definition's tree, parsed once however many times it is checked or used.
|
|
51
|
+
* Keyed by the text, so an edited definition is a new entry. Read it, or
|
|
52
|
+
* clone it before changing anything.
|
|
53
|
+
*/
|
|
54
|
+
function parseDefinition(source) {
|
|
55
|
+
let tree = parsedDefinitions.get(source);
|
|
56
|
+
if (tree === undefined) {
|
|
57
|
+
tree = parse(source);
|
|
58
|
+
parsedDefinitions.set(source, tree);
|
|
59
|
+
}
|
|
60
|
+
return tree;
|
|
61
|
+
}
|
|
48
62
|
/**
|
|
49
63
|
* A tree and the content of every template in it, however deep.
|
|
50
64
|
*
|
|
@@ -109,7 +123,7 @@ export async function addDefinitions(defs, files) {
|
|
|
109
123
|
/** Every placeholder a definition declares. */
|
|
110
124
|
function placeholdersOf(source) {
|
|
111
125
|
const names = [];
|
|
112
|
-
for (const scope of scopes(
|
|
126
|
+
for (const scope of scopes(parseDefinition(source))) {
|
|
113
127
|
for (const el of scope.querySelectorAll("*")) {
|
|
114
128
|
for (const attr of el.attributes) {
|
|
115
129
|
const match = PLACEHOLDER.exec(attr.value.trim());
|
|
@@ -157,9 +171,8 @@ function checkNames(file, source) {
|
|
|
157
171
|
* hyphen in the tag name is the one thing a custom element is guaranteed to
|
|
158
172
|
* have, per the Custom Elements spec, and nothing native has one.
|
|
159
173
|
*/
|
|
160
|
-
export function customElementTags(
|
|
161
|
-
const
|
|
162
|
-
for (const scope of scopes(parse(html))) {
|
|
174
|
+
export function customElementTags(from, tags = new Set()) {
|
|
175
|
+
for (const scope of scopes(typeof from === "string" ? parse(from) : from)) {
|
|
163
176
|
for (const el of scope.querySelectorAll("*")) {
|
|
164
177
|
const tag = el.tagName.toLowerCase();
|
|
165
178
|
if (tag.includes("-"))
|
|
@@ -171,7 +184,7 @@ export function customElementTags(html) {
|
|
|
171
184
|
/** Which definitions a definition uses. A tag with no definition expands to nothing and cannot cycle. */
|
|
172
185
|
function referencesOf(source, defs) {
|
|
173
186
|
const refs = new Set();
|
|
174
|
-
for (const scope of scopes(
|
|
187
|
+
for (const scope of scopes(parseDefinition(source))) {
|
|
175
188
|
for (const el of scope.querySelectorAll("*")) {
|
|
176
189
|
const tag = el.tagName.toUpperCase();
|
|
177
190
|
if (CUSTOM_ELEMENT.test(tag) && tag in defs)
|
|
@@ -270,27 +283,28 @@ function substitute(root, attrs, asked) {
|
|
|
270
283
|
*/
|
|
271
284
|
function fillTemplates(tag, root, content) {
|
|
272
285
|
const destinations = new Map();
|
|
273
|
-
for (const el of root.querySelectorAll(`[${
|
|
274
|
-
const name = el.getAttribute(
|
|
286
|
+
for (const el of root.querySelectorAll(`[${ATTR_EXP_TEMPLATE}]`)) {
|
|
287
|
+
const name = el.getAttribute(ATTR_EXP_TEMPLATE);
|
|
275
288
|
if (destinations.has(name)) {
|
|
276
|
-
throw new Error(`expand: <${tag.toLowerCase()}> declares two ${
|
|
289
|
+
throw new Error(`expand: <${tag.toLowerCase()}> declares two ${ATTR_EXP_TEMPLATE}="${name}" destinations`);
|
|
277
290
|
}
|
|
278
291
|
destinations.set(name, el);
|
|
279
|
-
el.removeAttribute(
|
|
292
|
+
el.removeAttribute(ATTR_EXP_TEMPLATE);
|
|
280
293
|
}
|
|
281
294
|
const rest = [];
|
|
282
295
|
const filled = new Set();
|
|
283
296
|
for (const node of content) {
|
|
284
|
-
const named = node instanceof HTMLTemplateElement &&
|
|
297
|
+
const named = node instanceof HTMLTemplateElement &&
|
|
298
|
+
node.hasAttribute(ATTR_EXP_TEMPLATE);
|
|
285
299
|
if (!named) {
|
|
286
300
|
rest.push(node);
|
|
287
301
|
continue;
|
|
288
302
|
}
|
|
289
303
|
const template = node;
|
|
290
|
-
const name = template.getAttribute(
|
|
304
|
+
const name = template.getAttribute(ATTR_EXP_TEMPLATE);
|
|
291
305
|
const destination = destinations.get(name);
|
|
292
306
|
if (!destination) {
|
|
293
|
-
throw new Error(`expand: a <template ${
|
|
307
|
+
throw new Error(`expand: a <template ${ATTR_EXP_TEMPLATE}="${name}"> was written inside ` +
|
|
294
308
|
`<${tag.toLowerCase()}>, whose definition has no destination by that name.`);
|
|
295
309
|
}
|
|
296
310
|
if (filled.has(name)) {
|
|
@@ -303,9 +317,9 @@ function fillTemplates(tag, root, content) {
|
|
|
303
317
|
}
|
|
304
318
|
/** Place authored inner content in the single slot, if the definition has one. */
|
|
305
319
|
function fillSlot(tag, root, content) {
|
|
306
|
-
const slots = root.querySelectorAll(`[${
|
|
320
|
+
const slots = root.querySelectorAll(`[${ATTR_EXP_SLOT}]`);
|
|
307
321
|
if (slots.length > 1) {
|
|
308
|
-
throw new Error(`expand: <${tag.toLowerCase()}> declares more than one ${
|
|
322
|
+
throw new Error(`expand: <${tag.toLowerCase()}> declares more than one ${ATTR_EXP_SLOT}`);
|
|
309
323
|
}
|
|
310
324
|
if (slots.length === 0) {
|
|
311
325
|
// Whitespace is not content. An author who writes only named templates
|
|
@@ -313,7 +327,7 @@ function fillSlot(tag, root, content) {
|
|
|
313
327
|
// formatting rather than something anybody meant to place.
|
|
314
328
|
const wrote = content.some((node) => node.nodeType !== Node.TEXT_NODE || node.data.trim());
|
|
315
329
|
if (wrote) {
|
|
316
|
-
throw new Error(`expand: content was written inside <${tag.toLowerCase()}>, whose definition has no ${
|
|
330
|
+
throw new Error(`expand: content was written inside <${tag.toLowerCase()}>, whose definition has no ${ATTR_EXP_SLOT}`);
|
|
317
331
|
}
|
|
318
332
|
return;
|
|
319
333
|
}
|
|
@@ -322,7 +336,7 @@ function fillSlot(tag, root, content) {
|
|
|
322
336
|
// children with the expanded tree immediately after, so there is nothing
|
|
323
337
|
// left behind for a clone to protect.
|
|
324
338
|
slot.append(...content);
|
|
325
|
-
slot.removeAttribute(
|
|
339
|
+
slot.removeAttribute(ATTR_EXP_SLOT);
|
|
326
340
|
}
|
|
327
341
|
function expandElement(el, defs, source) {
|
|
328
342
|
const tag = el.tagName.toUpperCase();
|
|
@@ -335,7 +349,7 @@ function expandElement(el, defs, source) {
|
|
|
335
349
|
continue;
|
|
336
350
|
attrs.set(attr.name.slice(EXP_PREFIX.length), attr.value);
|
|
337
351
|
}
|
|
338
|
-
const tree =
|
|
352
|
+
const tree = parseDefinition(source).cloneNode(true);
|
|
339
353
|
const asked = new Set();
|
|
340
354
|
substitute(tree, attrs, asked);
|
|
341
355
|
// A parameter the definition never declared is a name that resolves to
|
|
@@ -376,8 +390,80 @@ function walk(root, defs) {
|
|
|
376
390
|
* checked for cycles. Handed a cyclic map directly, this recurses forever.
|
|
377
391
|
*/
|
|
378
392
|
export function expand(source, defs) {
|
|
393
|
+
return expandTree(source, defs).innerHTML;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* The same, as the tree the browser builds from it: a template, whose content
|
|
397
|
+
* the builder's later passes read and change before serializing it once.
|
|
398
|
+
*/
|
|
399
|
+
export function expandTree(source, defs) {
|
|
379
400
|
const tree = parse(source);
|
|
380
401
|
walk(tree, defs);
|
|
381
|
-
return tree
|
|
402
|
+
return parsesBack(tree);
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* The tree the browser builds from the expanded tree's text, provided it is
|
|
406
|
+
* the same tree.
|
|
407
|
+
*
|
|
408
|
+
* Each file was parsed on its own, so everything inside one file already has
|
|
409
|
+
* the shape the parser gives it. Expansion then moves one file's nodes into
|
|
410
|
+
* another's through the DOM, which applies none of HTML's rules for what an
|
|
411
|
+
* element may hold. Where two files meet, the tree can hold what no parse
|
|
412
|
+
* would build: a definition's `<dialog>` inside a page's `<p>`. The browser
|
|
413
|
+
* reads the text and closes the `<p>` early, and the dialog lands outside its
|
|
414
|
+
* element with nothing logged.
|
|
415
|
+
*
|
|
416
|
+
* Reparsed in a template, the context a page ships in. Only a text that
|
|
417
|
+
* differs is walked, to name what moved.
|
|
418
|
+
*/
|
|
419
|
+
function parsesBack(tree) {
|
|
420
|
+
const html = tree.innerHTML;
|
|
421
|
+
const reparsed = doc.createElement("template");
|
|
422
|
+
reparsed.innerHTML = html;
|
|
423
|
+
if (reparsed.innerHTML === html)
|
|
424
|
+
return reparsed;
|
|
425
|
+
const moved = firstMoved(tree, reparsed.content) ?? tree;
|
|
426
|
+
const path = [];
|
|
427
|
+
let owner = null;
|
|
428
|
+
for (let el = moved; el && el !== tree; el = el.parentNode) {
|
|
429
|
+
if (!(el instanceof Element))
|
|
430
|
+
continue;
|
|
431
|
+
const tag = el.tagName.toLowerCase();
|
|
432
|
+
path.unshift(tag);
|
|
433
|
+
if (owner === null && el !== moved && CUSTOM_ELEMENT.test(tag)) {
|
|
434
|
+
owner = tag;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const what = path.length > 0 ? `<${path.at(-1)}>` : "content";
|
|
438
|
+
const where = path.length > 0 ? path.join(" > ") : "the top level";
|
|
439
|
+
throw new Error(`expand: ${owner ? `<${owner}> puts` : "expansion puts"} ${what} ` +
|
|
440
|
+
`where the browser's parser would not keep it, at ${where}. HTML does ` +
|
|
441
|
+
`not let every element hold every other, and the browser moves what ` +
|
|
442
|
+
`breaks the rule.`);
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* The first node, in document order, that the reparse does not have in the
|
|
446
|
+
* same place. A node the reparse gained reports its parent.
|
|
447
|
+
*/
|
|
448
|
+
function firstMoved(built, parsed) {
|
|
449
|
+
const ours = built.childNodes;
|
|
450
|
+
const theirs = parsed.childNodes;
|
|
451
|
+
for (let i = 0; i < ours.length; i++) {
|
|
452
|
+
const node = ours[i];
|
|
453
|
+
const other = theirs[i];
|
|
454
|
+
if (!other ||
|
|
455
|
+
node.nodeName !== other.nodeName ||
|
|
456
|
+
(node.nodeType !== Node.ELEMENT_NODE &&
|
|
457
|
+
node.nodeValue !== other.nodeValue)) {
|
|
458
|
+
return node.nodeType === Node.ELEMENT_NODE ? node : built;
|
|
459
|
+
}
|
|
460
|
+
const inner = firstMoved(node, other) ??
|
|
461
|
+
(node instanceof HTMLTemplateElement
|
|
462
|
+
? firstMoved(node.content, other.content)
|
|
463
|
+
: null);
|
|
464
|
+
if (inner)
|
|
465
|
+
return inner;
|
|
466
|
+
}
|
|
467
|
+
return ours.length < theirs.length ? built : null;
|
|
382
468
|
}
|
|
383
469
|
//# sourceMappingURL=expand.js.map
|
package/dist/build/expand.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expand.js","sourceRoot":"","sources":["../../build/expand.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Expansion — see docs/reference/custom-elements.md, \"HTML\".\n *\n * A page author writes one tag and gets the tree it stands for. Expansion\n * runs at build time, before any request exists, so it has no data. That is\n * why there is no control flow here: nothing to branch on.\n *\n * Substitution is applied to a parsed tree with setAttribute and node values,\n * never to a string. A value applied through the DOM API cannot be reparsed\n * as markup, so injection is impossible by construction.\n */\n\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { JSDOM } from \"jsdom\";\nimport { ATTR_SLOT, ATTR_TEMPLATE } from \"../core/lb-constants.js\";\n\n/** Lowercase, hyphenated — the one shape a custom element tag has. */\nconst CUSTOM_ELEMENT = /-/;\n\n/** A placeholder is an entire attribute value or an entire text node. */\n/**\n * `{{name}}` or `{{name|default}}`. The default is a literal that stands in\n * when the tag supplies nothing; it is not evaluated and nothing branches on\n * it. Whitespace around the name and the default is not part of either, so\n * `{{button-text | OK}}` is the same placeholder as `{{button-text|OK}}` —\n * a spaced form that shipped as literal braces would be a silent failure\n * an author would find in the browser.\n */\nconst PLACEHOLDER = /^\\{\\{\\s*([A-Za-z][\\w-]*)\\s*(?:\\|\\s*([^}]*?)\\s*)?\\}\\}$/;\n\n/**\n * Marks an attribute as input to expansion. Three namespaces share the tag:\n * `lb-` holds the names Loadbare defines, `exp-` the names a widget author\n * defines, and everything unprefixed is HTML's and means what HTML says it\n * means. The split is by owner rather than by phase — `lb-slot` and\n * `lb-template` are expansion's own and never reach the browser, while\n * `lb-list` and `lb-cell` are read at both times. So an author is free to write\n * `class`, `title` or `hidden` on any widget, whatever its parameters happen\n * to be named. The prefix marks the slot on the tag, where the ambiguity was;\n * inside a definition `{{ }}` already says the same thing, so `exp-label`\n * fills `{{label}}`.\n */\nconst EXP_PREFIX = \"exp-\";\n\nexport type Definitions = Record<string, string>;\n\nconst dom = new JSDOM(\"\");\nconst doc = dom.window.document;\nconst { Element, HTMLTemplateElement, Node } = dom.window;\n\nfunction parse(source: string): HTMLElement {\n const holder = doc.createElement(\"div\");\n holder.innerHTML = source;\n return holder as unknown as HTMLElement;\n}\n\n/**\n * A tree and the content of every template in it, however deep.\n *\n * A template's children are parsed into a fragment rather than into the tree,\n * so `querySelectorAll` does not reach them. Everything expansion does has to\n * reach them anyway: a definition may supply its own row template, and\n * `lb-picker` does, so `{{key}}` written inside one has to be a\n * placeholder rather than literal text that ships. `walk` already descends\n * into template content to expand widget tags; this is the same rule stated\n * once for every pass that reads a tree.\n */\nfunction* scopes(root: ParentNode): Generator<ParentNode> {\n yield root;\n for (const el of root.querySelectorAll(\"template\")) {\n yield* scopes((el as HTMLTemplateElement).content);\n }\n}\n\n/**\n * Load one definition per file, named by the tag it defines.\n *\n * Later directories win, so an application can override a built-in widget.\n */\nexport async function loadDefinitions(dirs: string[]): Promise<Definitions> {\n const defs: Definitions = {};\n for (const dir of dirs) {\n let names: string[];\n try {\n names = await readdir(dir);\n } catch {\n continue;\n }\n for (const name of names) {\n if (!name.endsWith(\".html\")) continue;\n const file = `${dir}/${name}`;\n const source = await readFile(file, \"utf-8\");\n checkNames(file, source);\n defs[name.slice(0, -\".html\".length).toUpperCase()] = source;\n }\n }\n checkAcyclic(defs);\n return defs;\n}\n\n/**\n * Overlay definitions read from explicit files, each already keyed by the\n * tag it defines — for a source, like an application's own `src` tree, that\n * is discovered file by file rather than listed by directory. A tag here\n * overrides the same tag in `defs`, the same override `loadDefinitions`\n * gives a later directory over an earlier one.\n */\nexport async function addDefinitions(\n defs: Definitions,\n files: Map<string, string>,\n): Promise<Definitions> {\n const merged = { ...defs };\n for (const [tag, file] of files) {\n const source = await readFile(file, \"utf-8\");\n checkNames(file, source);\n merged[tag.toUpperCase()] = source;\n }\n checkAcyclic(merged);\n return merged;\n}\n\n/** Every placeholder a definition declares. */\nfunction placeholdersOf(source: string): string[] {\n const names: string[] = [];\n for (const scope of scopes(parse(source))) {\n for (const el of scope.querySelectorAll(\"*\")) {\n for (const attr of el.attributes) {\n const match = PLACEHOLDER.exec(attr.value.trim());\n if (match) names.push(match[1]!);\n }\n }\n const walker = doc.createTreeWalker(scope as globalThis.Node, 4 /* TEXT */);\n while (walker.nextNode()) {\n const match = PLACEHOLDER.exec((walker.currentNode as Text).data.trim());\n if (match) names.push(match[1]!);\n }\n }\n return names;\n}\n\n/**\n * A placeholder must be spelled the way a tag can supply it.\n *\n * HTML lowercases attribute names, so `exp-inputClass` reaches expansion as\n * `exp-inputclass` and could never fill `{{inputClass}}`. A definition is the\n * only file that can hold such a name and the only one that can fix it, so it\n * is rejected here, once, rather than at every page that uses the widget.\n *\n * Recognition stays broad on purpose. Narrowing PLACEHOLDER to lowercase would\n * make `{{inputClass}}` not a placeholder at all, and ship it to the browser as\n * literal text — the same silent failure somewhere new.\n */\nfunction checkNames(file: string, source: string): void {\n for (const name of placeholdersOf(source)) {\n if (!/[A-Z]/.test(name)) continue;\n const kebab = name.replace(/([a-z0-9])([A-Z])/g, \"$1-$2\").toLowerCase();\n throw new Error(\n `expand: ${file} declares {{${name}}}, but HTML lowercases attribute ` +\n `names, so no tag can supply it. Write {{${kebab}}}.`,\n );\n }\n}\n\n/**\n * Every hyphenated tag name left in a document, template content included.\n *\n * Meant to run on already-expanded output: an `LB-*` tag with a definition\n * is gone by then, replaced by the tree it stands for, so what remains is\n * whatever is left for the browser to upgrade itself — a built-in widget\n * class, an application's own custom element, or one from a package. A\n * hyphen in the tag name is the one thing a custom element is guaranteed to\n * have, per the Custom Elements spec, and nothing native has one.\n */\nexport function customElementTags(html: string): Set<string> {\n const tags = new Set<string>();\n for (const scope of scopes(parse(html))) {\n for (const el of scope.querySelectorAll(\"*\")) {\n const tag = el.tagName.toLowerCase();\n if (tag.includes(\"-\")) tags.add(tag);\n }\n }\n return tags;\n}\n\n/** Which definitions a definition uses. A tag with no definition expands to nothing and cannot cycle. */\nfunction referencesOf(source: string, defs: Definitions): string[] {\n const refs = new Set<string>();\n for (const scope of scopes(parse(source))) {\n for (const el of scope.querySelectorAll(\"*\")) {\n const tag = el.tagName.toUpperCase();\n if (CUSTOM_ELEMENT.test(tag) && tag in defs) refs.add(tag);\n }\n }\n return [...refs];\n}\n\n/**\n * Expansion is a fixpoint, so it terminates only on an acyclic graph. A depth\n * cap would hide the bug behind a limit instead of reporting it.\n */\nfunction checkAcyclic(defs: Definitions): void {\n const refs = new Map<string, string[]>();\n for (const [tag, source] of Object.entries(defs)) {\n refs.set(tag, referencesOf(source, defs));\n }\n const done = new Set<string>();\n const path: string[] = [];\n\n const visit = (tag: string): void => {\n if (done.has(tag)) return;\n const cycle = path.indexOf(tag);\n if (cycle !== -1) {\n throw new Error(\n `expand: cycle in the definition graph: ` +\n [...path.slice(cycle), tag].map((t) => t.toLowerCase()).join(\" → \"),\n );\n }\n path.push(tag);\n for (const ref of refs.get(tag) ?? []) visit(ref);\n path.pop();\n done.add(tag);\n };\n\n for (const tag of refs.keys()) visit(tag);\n}\n\n/**\n * Resolve a placeholder against what the author wrote: the supplied value,\n * else the definition's default, else absent.\n *\n * Every name the definition asks for is recorded, whether or not it was\n * supplied, so expansion can report a parameter the definition never declared.\n */\nfunction resolve(\n text: string,\n attrs: Map<string, string>,\n asked: Set<string>,\n): string | null {\n const match = PLACEHOLDER.exec(text.trim());\n if (!match) return text;\n const name = match[1]!;\n asked.add(name);\n return attrs.get(name) ?? match[2] ?? null;\n}\n\nfunction substitute(\n root: ParentNode,\n attrs: Map<string, string>,\n asked: Set<string>,\n): void {\n for (const scope of scopes(root)) {\n for (const el of scope.querySelectorAll(\"*\")) {\n for (const attr of [...el.attributes]) {\n const value = resolve(attr.value, attrs, asked);\n // Presence propagates: a placeholder with nothing behind it drops the\n // attribute rather than emitting it empty, which is what HTML's own\n // boolean-attribute semantics require.\n if (value === null) el.removeAttribute(attr.name);\n else if (value !== attr.value) el.setAttribute(attr.name, value);\n }\n }\n\n const walker = doc.createTreeWalker(scope as globalThis.Node, 4 /* TEXT */);\n const texts: Text[] = [];\n while (walker.nextNode()) texts.push(walker.currentNode as Text);\n for (const text of texts) {\n const raw = text.data;\n if (!PLACEHOLDER.test(raw.trim())) continue;\n const value = resolve(raw, attrs, asked) ?? \"\";\n // Keep the author's surrounding whitespace: \"{{label}} <input>\" should\n // still separate the label from the control.\n const lead = raw.slice(0, raw.length - raw.trimStart().length);\n const tail = raw.slice(raw.trimEnd().length);\n text.data = lead + value + tail;\n }\n }\n}\n\n/**\n * Fill the definition's named destinations from the authored templates that\n * name them, and hand back the content that is left for the slot.\n *\n * A destination is filled with the template's contents, not with the\n * template, so what ships is ordinary markup in an ordinary place — a real\n * `<tr>` inside a real `<thead>`, visible in view-source like everything\n * else. The wrapper existed only to survive the parser and is gone.\n *\n * Unlike the slot, there may be several, because the reason for wrapping is\n * per-destination: a table has a head and a body, and each needs its own\n * markup past the same obstacle. They are matched by name, and an empty name\n * is a name, so a widget with one destination costs the author nothing.\n */\nfunction fillTemplates(\n tag: string,\n root: ParentNode,\n content: globalThis.Node[],\n): globalThis.Node[] {\n const destinations = new Map<string, Element>();\n for (const el of root.querySelectorAll(`[${ATTR_TEMPLATE}]`)) {\n const name = el.getAttribute(ATTR_TEMPLATE)!;\n if (destinations.has(name)) {\n throw new Error(\n `expand: <${tag.toLowerCase()}> declares two ${ATTR_TEMPLATE}=\"${name}\" destinations`,\n );\n }\n destinations.set(name, el);\n el.removeAttribute(ATTR_TEMPLATE);\n }\n\n const rest: globalThis.Node[] = [];\n const filled = new Set<string>();\n for (const node of content) {\n const named =\n node instanceof HTMLTemplateElement && node.hasAttribute(ATTR_TEMPLATE);\n if (!named) {\n rest.push(node);\n continue;\n }\n const template = node as HTMLTemplateElement;\n const name = template.getAttribute(ATTR_TEMPLATE)!;\n const destination = destinations.get(name);\n if (!destination) {\n throw new Error(\n `expand: a <template ${ATTR_TEMPLATE}=\"${name}\"> was written inside ` +\n `<${tag.toLowerCase()}>, whose definition has no destination by that name.`,\n );\n }\n if (filled.has(name)) {\n throw new Error(\n `expand: <${tag.toLowerCase()}> was given two templates for '${name}'`,\n );\n }\n filled.add(name);\n destination.append(template.content);\n }\n return rest;\n}\n\n/** Place authored inner content in the single slot, if the definition has one. */\nfunction fillSlot(\n tag: string,\n root: ParentNode,\n content: globalThis.Node[],\n): void {\n const slots = root.querySelectorAll(`[${ATTR_SLOT}]`);\n if (slots.length > 1) {\n throw new Error(\n `expand: <${tag.toLowerCase()}> declares more than one ${ATTR_SLOT}`,\n );\n }\n if (slots.length === 0) {\n // Whitespace is not content. An author who writes only named templates\n // still leaves the newlines between them, and those are the file's\n // formatting rather than something anybody meant to place.\n const wrote = content.some(\n (node) => node.nodeType !== Node.TEXT_NODE || (node as Text).data.trim(),\n );\n if (wrote) {\n throw new Error(\n `expand: content was written inside <${tag.toLowerCase()}>, whose definition has no ${ATTR_SLOT}`,\n );\n }\n return;\n }\n const slot = slots[0]!;\n // The authored nodes move rather than copy. The caller replaces the tag's\n // children with the expanded tree immediately after, so there is nothing\n // left behind for a clone to protect.\n slot.append(...content);\n slot.removeAttribute(ATTR_SLOT);\n}\n\nfunction expandElement(el: Element, defs: Definitions, source: string): void {\n const tag = el.tagName.toUpperCase();\n\n // The tag survives expansion carrying every attribute the author wrote,\n // `exp-` ones included. They cost nothing where they are, and what ships\n // shows what was asked for beside what it produced.\n const attrs = new Map<string, string>();\n for (const attr of el.attributes) {\n if (!attr.name.startsWith(EXP_PREFIX)) continue;\n attrs.set(attr.name.slice(EXP_PREFIX.length), attr.value);\n }\n\n const tree = parse(source);\n const asked = new Set<string>();\n substitute(tree, attrs, asked);\n\n // A parameter the definition never declared is a name that resolves to\n // nothing. Unprefixed attributes could not support this check, because a\n // stray one is indistinguishable from a deliberate HTML attribute.\n for (const name of attrs.keys()) {\n if (asked.has(name)) continue;\n throw new Error(\n `expand: <${tag.toLowerCase()}> was given ${EXP_PREFIX}${name}, ` +\n `but its definition has no {{${name}}}.`,\n );\n }\n // Named destinations first: what they take is addressed to them, and only\n // what is left is the slot's.\n fillSlot(tag, tree, fillTemplates(tag, tree, [...el.childNodes]));\n el.replaceChildren(...tree.childNodes);\n}\n\nfunction walk(root: ParentNode, defs: Definitions): void {\n for (const child of [...root.children]) {\n const source = defs[child.tagName.toUpperCase()];\n // A tag with no definition is left alone. It is a custom element a\n // script registers, and build/elements.ts is where a tag that has\n // neither a definition nor a script is refused — one rule, one place.\n if (source !== undefined) expandElement(child, defs, source);\n // Definitions may use other widgets, so descend after expanding. The\n // acyclicity check is what makes this terminate.\n walk(child, defs);\n // Template content is markup in a file like any other, and it is inert,\n // so a row template ships already expanded.\n if (child instanceof HTMLTemplateElement) walk(child.content, defs);\n }\n}\n\n/**\n * Expand every widget tag in an authored page. Throws rather than shipping.\n *\n * Definitions must come from `loadDefinitions`, which is where the graph is\n * checked for cycles. Handed a cyclic map directly, this recurses forever.\n */\nexport function expand(source: string, defs: Definitions): string {\n const tree = parse(source);\n walk(tree, defs);\n return tree.innerHTML;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"expand.js","sourceRoot":"","sources":["../../build/expand.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Expansion — see docs/reference/custom-elements.md, \"HTML\".\n *\n * A page author writes one tag and gets the tree it stands for. Expansion\n * runs at build time, before any request exists, so it has no data. That is\n * why there is no control flow here: nothing to branch on.\n *\n * Substitution is applied to a parsed tree with setAttribute and node values,\n * never to a string. A value applied through the DOM API cannot be reparsed\n * as markup, so injection is impossible by construction.\n */\n\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { JSDOM } from \"jsdom\";\nimport { ATTR_EXP_SLOT, ATTR_EXP_TEMPLATE } from \"../core/lb-constants.js\";\n\n/** Lowercase, hyphenated — the one shape a custom element tag has. */\nconst CUSTOM_ELEMENT = /-/;\n\n/** A placeholder is an entire attribute value or an entire text node. */\n/**\n * `{{name}}` or `{{name|default}}`. The default is a literal that stands in\n * when the tag supplies nothing; it is not evaluated and nothing branches on\n * it. Whitespace around the name and the default is not part of either, so\n * `{{button-text | OK}}` is the same placeholder as `{{button-text|OK}}` —\n * a spaced form that shipped as literal braces would be a silent failure\n * an author would find in the browser.\n */\nconst PLACEHOLDER = /^\\{\\{\\s*([A-Za-z][\\w-]*)\\s*(?:\\|\\s*([^}]*?)\\s*)?\\}\\}$/;\n\n/**\n * Marks an attribute as input to expansion. Three namespaces share the tag:\n * `lb-` holds the names Loadbare defines, `exp-` the names an element file\n * defines, and everything unprefixed is HTML's and means what HTML says it\n * means. The split is by owner rather than by phase — `lb-exp-slot` and\n * `lb-exp-template` are expansion's own and never reach the browser, while\n * `lb-query` and `lb-column` are read at both times. So an author is free to write\n * `class`, `title` or `hidden` on any widget, whatever its parameters happen\n * to be named. The prefix is needed on the tag, where a parameter and an\n * HTML attribute would otherwise share one namespace; inside a definition\n * `{{ }}` already marks a parameter, so `exp-label` fills `{{label}}`.\n */\nconst EXP_PREFIX = \"exp-\";\n\nexport type Definitions = Record<string, string>;\n\nconst dom = new JSDOM(\"\");\nconst doc = dom.window.document;\nconst { Element, HTMLTemplateElement, Node } = dom.window;\n\nfunction parse(source: string): HTMLElement {\n const holder = doc.createElement(\"div\");\n holder.innerHTML = source;\n return holder as unknown as HTMLElement;\n}\n\nconst parsedDefinitions = new Map<string, HTMLElement>();\n\n/**\n * A definition's tree, parsed once however many times it is checked or used.\n * Keyed by the text, so an edited definition is a new entry. Read it, or\n * clone it before changing anything.\n */\nfunction parseDefinition(source: string): HTMLElement {\n let tree = parsedDefinitions.get(source);\n if (tree === undefined) {\n tree = parse(source);\n parsedDefinitions.set(source, tree);\n }\n return tree;\n}\n\n/**\n * A tree and the content of every template in it, however deep.\n *\n * A template's children are parsed into a fragment rather than into the tree,\n * so `querySelectorAll` does not reach them. Everything expansion does has to\n * reach them anyway: a definition may supply its own row template, and\n * `lb-picker` does, so `{{key}}` written inside one has to be a\n * placeholder rather than literal text that ships. `walk` already descends\n * into template content to expand widget tags; this is the same rule stated\n * once for every pass that reads a tree.\n */\nfunction* scopes(root: ParentNode): Generator<ParentNode> {\n yield root;\n for (const el of root.querySelectorAll(\"template\")) {\n yield* scopes((el as HTMLTemplateElement).content);\n }\n}\n\n/**\n * Load one definition per file, named by the tag it defines.\n *\n * Later directories win, so an application can override a built-in widget.\n */\nexport async function loadDefinitions(dirs: string[]): Promise<Definitions> {\n const defs: Definitions = {};\n for (const dir of dirs) {\n let names: string[];\n try {\n names = await readdir(dir);\n } catch {\n continue;\n }\n for (const name of names) {\n if (!name.endsWith(\".html\")) continue;\n const file = `${dir}/${name}`;\n const source = await readFile(file, \"utf-8\");\n checkNames(file, source);\n defs[name.slice(0, -\".html\".length).toUpperCase()] = source;\n }\n }\n checkAcyclic(defs);\n return defs;\n}\n\n/**\n * Overlay definitions read from explicit files, each already keyed by the\n * tag it defines — for a source, like an application's own `src` tree, that\n * is discovered file by file rather than listed by directory. A tag here\n * overrides the same tag in `defs`, the same override `loadDefinitions`\n * gives a later directory over an earlier one.\n */\nexport async function addDefinitions(\n defs: Definitions,\n files: Map<string, string>,\n): Promise<Definitions> {\n const merged = { ...defs };\n for (const [tag, file] of files) {\n const source = await readFile(file, \"utf-8\");\n checkNames(file, source);\n merged[tag.toUpperCase()] = source;\n }\n checkAcyclic(merged);\n return merged;\n}\n\n/** Every placeholder a definition declares. */\nfunction placeholdersOf(source: string): string[] {\n const names: string[] = [];\n for (const scope of scopes(parseDefinition(source))) {\n for (const el of scope.querySelectorAll(\"*\")) {\n for (const attr of el.attributes) {\n const match = PLACEHOLDER.exec(attr.value.trim());\n if (match) names.push(match[1]!);\n }\n }\n const walker = doc.createTreeWalker(scope as globalThis.Node, 4 /* TEXT */);\n while (walker.nextNode()) {\n const match = PLACEHOLDER.exec((walker.currentNode as Text).data.trim());\n if (match) names.push(match[1]!);\n }\n }\n return names;\n}\n\n/**\n * A placeholder must be spelled the way a tag can supply it.\n *\n * HTML lowercases attribute names, so `exp-inputClass` reaches expansion as\n * `exp-inputclass` and could never fill `{{inputClass}}`. A definition is the\n * only file that can hold such a name and the only one that can fix it, so it\n * is rejected here, once, rather than at every page that uses the widget.\n *\n * Recognition stays broad on purpose. Narrowing PLACEHOLDER to lowercase would\n * make `{{inputClass}}` not a placeholder at all, and ship it to the browser as\n * literal text — the same silent failure somewhere new.\n */\nfunction checkNames(file: string, source: string): void {\n for (const name of placeholdersOf(source)) {\n if (!/[A-Z]/.test(name)) continue;\n const kebab = name.replace(/([a-z0-9])([A-Z])/g, \"$1-$2\").toLowerCase();\n throw new Error(\n `expand: ${file} declares {{${name}}}, but HTML lowercases attribute ` +\n `names, so no tag can supply it. Write {{${kebab}}}.`,\n );\n }\n}\n\n/**\n * Every hyphenated tag name left in a document, template content included.\n *\n * Meant to run on already-expanded output: an `LB-*` tag with a definition\n * is gone by then, replaced by the tree it stands for, so what remains is\n * whatever is left for the browser to upgrade itself — a built-in widget\n * class, an application's own custom element, or one from a package. A\n * hyphen in the tag name is the one thing a custom element is guaranteed to\n * have, per the Custom Elements spec, and nothing native has one.\n */\nexport function customElementTags(\n from: string | ParentNode,\n tags = new Set<string>(),\n): Set<string> {\n for (const scope of scopes(typeof from === \"string\" ? parse(from) : from)) {\n for (const el of scope.querySelectorAll(\"*\")) {\n const tag = el.tagName.toLowerCase();\n if (tag.includes(\"-\")) tags.add(tag);\n }\n }\n return tags;\n}\n\n/** Which definitions a definition uses. A tag with no definition expands to nothing and cannot cycle. */\nfunction referencesOf(source: string, defs: Definitions): string[] {\n const refs = new Set<string>();\n for (const scope of scopes(parseDefinition(source))) {\n for (const el of scope.querySelectorAll(\"*\")) {\n const tag = el.tagName.toUpperCase();\n if (CUSTOM_ELEMENT.test(tag) && tag in defs) refs.add(tag);\n }\n }\n return [...refs];\n}\n\n/**\n * Expansion is a fixpoint, so it terminates only on an acyclic graph. A depth\n * cap would hide the bug behind a limit instead of reporting it.\n */\nfunction checkAcyclic(defs: Definitions): void {\n const refs = new Map<string, string[]>();\n for (const [tag, source] of Object.entries(defs)) {\n refs.set(tag, referencesOf(source, defs));\n }\n const done = new Set<string>();\n const path: string[] = [];\n\n const visit = (tag: string): void => {\n if (done.has(tag)) return;\n const cycle = path.indexOf(tag);\n if (cycle !== -1) {\n throw new Error(\n `expand: cycle in the definition graph: ` +\n [...path.slice(cycle), tag].map((t) => t.toLowerCase()).join(\" → \"),\n );\n }\n path.push(tag);\n for (const ref of refs.get(tag) ?? []) visit(ref);\n path.pop();\n done.add(tag);\n };\n\n for (const tag of refs.keys()) visit(tag);\n}\n\n/**\n * Resolve a placeholder against what the author wrote: the supplied value,\n * else the definition's default, else absent.\n *\n * Every name the definition asks for is recorded, whether or not it was\n * supplied, so expansion can report a parameter the definition never declared.\n */\nfunction resolve(\n text: string,\n attrs: Map<string, string>,\n asked: Set<string>,\n): string | null {\n const match = PLACEHOLDER.exec(text.trim());\n if (!match) return text;\n const name = match[1]!;\n asked.add(name);\n return attrs.get(name) ?? match[2] ?? null;\n}\n\nfunction substitute(\n root: ParentNode,\n attrs: Map<string, string>,\n asked: Set<string>,\n): void {\n for (const scope of scopes(root)) {\n for (const el of scope.querySelectorAll(\"*\")) {\n for (const attr of [...el.attributes]) {\n const value = resolve(attr.value, attrs, asked);\n // Presence propagates: a placeholder with nothing behind it drops the\n // attribute rather than emitting it empty, which is what HTML's own\n // boolean-attribute semantics require.\n if (value === null) el.removeAttribute(attr.name);\n else if (value !== attr.value) el.setAttribute(attr.name, value);\n }\n }\n\n const walker = doc.createTreeWalker(scope as globalThis.Node, 4 /* TEXT */);\n const texts: Text[] = [];\n while (walker.nextNode()) texts.push(walker.currentNode as Text);\n for (const text of texts) {\n const raw = text.data;\n if (!PLACEHOLDER.test(raw.trim())) continue;\n const value = resolve(raw, attrs, asked) ?? \"\";\n // Keep the author's surrounding whitespace: \"{{label}} <input>\" should\n // still separate the label from the control.\n const lead = raw.slice(0, raw.length - raw.trimStart().length);\n const tail = raw.slice(raw.trimEnd().length);\n text.data = lead + value + tail;\n }\n }\n}\n\n/**\n * Fill the definition's named destinations from the authored templates that\n * name them, and hand back the content that is left for the slot.\n *\n * A destination is filled with the template's contents, not with the\n * template, so what ships is ordinary markup in an ordinary place — a real\n * `<tr>` inside a real `<thead>`, visible in view-source like everything\n * else. The wrapper existed only to survive the parser and is gone.\n *\n * Unlike the slot, there may be several, because the reason for wrapping is\n * per-destination: a table has a head and a body, and each needs its own\n * markup past the same obstacle. They are matched by name, and an empty name\n * is a name, so a widget with one destination costs the author nothing.\n */\nfunction fillTemplates(\n tag: string,\n root: ParentNode,\n content: globalThis.Node[],\n): globalThis.Node[] {\n const destinations = new Map<string, Element>();\n for (const el of root.querySelectorAll(`[${ATTR_EXP_TEMPLATE}]`)) {\n const name = el.getAttribute(ATTR_EXP_TEMPLATE)!;\n if (destinations.has(name)) {\n throw new Error(\n `expand: <${tag.toLowerCase()}> declares two ${ATTR_EXP_TEMPLATE}=\"${name}\" destinations`,\n );\n }\n destinations.set(name, el);\n el.removeAttribute(ATTR_EXP_TEMPLATE);\n }\n\n const rest: globalThis.Node[] = [];\n const filled = new Set<string>();\n for (const node of content) {\n const named =\n node instanceof HTMLTemplateElement &&\n node.hasAttribute(ATTR_EXP_TEMPLATE);\n if (!named) {\n rest.push(node);\n continue;\n }\n const template = node as HTMLTemplateElement;\n const name = template.getAttribute(ATTR_EXP_TEMPLATE)!;\n const destination = destinations.get(name);\n if (!destination) {\n throw new Error(\n `expand: a <template ${ATTR_EXP_TEMPLATE}=\"${name}\"> was written inside ` +\n `<${tag.toLowerCase()}>, whose definition has no destination by that name.`,\n );\n }\n if (filled.has(name)) {\n throw new Error(\n `expand: <${tag.toLowerCase()}> was given two templates for '${name}'`,\n );\n }\n filled.add(name);\n destination.append(template.content);\n }\n return rest;\n}\n\n/** Place authored inner content in the single slot, if the definition has one. */\nfunction fillSlot(\n tag: string,\n root: ParentNode,\n content: globalThis.Node[],\n): void {\n const slots = root.querySelectorAll(`[${ATTR_EXP_SLOT}]`);\n if (slots.length > 1) {\n throw new Error(\n `expand: <${tag.toLowerCase()}> declares more than one ${ATTR_EXP_SLOT}`,\n );\n }\n if (slots.length === 0) {\n // Whitespace is not content. An author who writes only named templates\n // still leaves the newlines between them, and those are the file's\n // formatting rather than something anybody meant to place.\n const wrote = content.some(\n (node) => node.nodeType !== Node.TEXT_NODE || (node as Text).data.trim(),\n );\n if (wrote) {\n throw new Error(\n `expand: content was written inside <${tag.toLowerCase()}>, whose definition has no ${ATTR_EXP_SLOT}`,\n );\n }\n return;\n }\n const slot = slots[0]!;\n // The authored nodes move rather than copy. The caller replaces the tag's\n // children with the expanded tree immediately after, so there is nothing\n // left behind for a clone to protect.\n slot.append(...content);\n slot.removeAttribute(ATTR_EXP_SLOT);\n}\n\nfunction expandElement(el: Element, defs: Definitions, source: string): void {\n const tag = el.tagName.toUpperCase();\n\n // The tag survives expansion carrying every attribute the author wrote,\n // `exp-` ones included. They cost nothing where they are, and what ships\n // shows what was asked for beside what it produced.\n const attrs = new Map<string, string>();\n for (const attr of el.attributes) {\n if (!attr.name.startsWith(EXP_PREFIX)) continue;\n attrs.set(attr.name.slice(EXP_PREFIX.length), attr.value);\n }\n\n const tree = parseDefinition(source).cloneNode(true) as HTMLElement;\n const asked = new Set<string>();\n substitute(tree, attrs, asked);\n\n // A parameter the definition never declared is a name that resolves to\n // nothing. Unprefixed attributes could not support this check, because a\n // stray one is indistinguishable from a deliberate HTML attribute.\n for (const name of attrs.keys()) {\n if (asked.has(name)) continue;\n throw new Error(\n `expand: <${tag.toLowerCase()}> was given ${EXP_PREFIX}${name}, ` +\n `but its definition has no {{${name}}}.`,\n );\n }\n // Named destinations first: what they take is addressed to them, and only\n // what is left is the slot's.\n fillSlot(tag, tree, fillTemplates(tag, tree, [...el.childNodes]));\n el.replaceChildren(...tree.childNodes);\n}\n\nfunction walk(root: ParentNode, defs: Definitions): void {\n for (const child of [...root.children]) {\n const source = defs[child.tagName.toUpperCase()];\n // A tag with no definition is left alone. It is a custom element a\n // script registers, and build/elements.ts is where a tag that has\n // neither a definition nor a script is refused — one rule, one place.\n if (source !== undefined) expandElement(child, defs, source);\n // Definitions may use other widgets, so descend after expanding. The\n // acyclicity check is what makes this terminate.\n walk(child, defs);\n // Template content is markup in a file like any other, and it is inert,\n // so a row template ships already expanded.\n if (child instanceof HTMLTemplateElement) walk(child.content, defs);\n }\n}\n\n/**\n * Expand every widget tag in an authored page. Throws rather than shipping.\n *\n * Definitions must come from `loadDefinitions`, which is where the graph is\n * checked for cycles. Handed a cyclic map directly, this recurses forever.\n */\nexport function expand(source: string, defs: Definitions): string {\n return expandTree(source, defs).innerHTML;\n}\n\n/**\n * The same, as the tree the browser builds from it: a template, whose content\n * the builder's later passes read and change before serializing it once.\n */\nexport function expandTree(\n source: string,\n defs: Definitions,\n): HTMLTemplateElement {\n const tree = parse(source);\n walk(tree, defs);\n return parsesBack(tree);\n}\n\n/**\n * The tree the browser builds from the expanded tree's text, provided it is\n * the same tree.\n *\n * Each file was parsed on its own, so everything inside one file already has\n * the shape the parser gives it. Expansion then moves one file's nodes into\n * another's through the DOM, which applies none of HTML's rules for what an\n * element may hold. Where two files meet, the tree can hold what no parse\n * would build: a definition's `<dialog>` inside a page's `<p>`. The browser\n * reads the text and closes the `<p>` early, and the dialog lands outside its\n * element with nothing logged.\n *\n * Reparsed in a template, the context a page ships in. Only a text that\n * differs is walked, to name what moved.\n */\nfunction parsesBack(tree: HTMLElement): HTMLTemplateElement {\n const html = tree.innerHTML;\n const reparsed = doc.createElement(\"template\");\n reparsed.innerHTML = html;\n if (reparsed.innerHTML === html) return reparsed;\n const moved = firstMoved(tree, reparsed.content) ?? tree;\n const path: string[] = [];\n let owner: string | null = null;\n for (let el: Node | null = moved; el && el !== tree; el = el.parentNode) {\n if (!(el instanceof Element)) continue;\n const tag = el.tagName.toLowerCase();\n path.unshift(tag);\n if (owner === null && el !== moved && CUSTOM_ELEMENT.test(tag)) {\n owner = tag;\n }\n }\n const what = path.length > 0 ? `<${path.at(-1)}>` : \"content\";\n const where = path.length > 0 ? path.join(\" > \") : \"the top level\";\n throw new Error(\n `expand: ${owner ? `<${owner}> puts` : \"expansion puts\"} ${what} ` +\n `where the browser's parser would not keep it, at ${where}. HTML does ` +\n `not let every element hold every other, and the browser moves what ` +\n `breaks the rule.`,\n );\n}\n\n/**\n * The first node, in document order, that the reparse does not have in the\n * same place. A node the reparse gained reports its parent.\n */\nfunction firstMoved(built: Node, parsed: Node): Node | null {\n const ours = built.childNodes;\n const theirs = parsed.childNodes;\n for (let i = 0; i < ours.length; i++) {\n const node = ours[i]!;\n const other = theirs[i];\n if (\n !other ||\n node.nodeName !== other.nodeName ||\n (node.nodeType !== Node.ELEMENT_NODE &&\n node.nodeValue !== other.nodeValue)\n ) {\n return node.nodeType === Node.ELEMENT_NODE ? node : built;\n }\n const inner =\n firstMoved(node, other) ??\n (node instanceof HTMLTemplateElement\n ? firstMoved(node.content, (other as HTMLTemplateElement).content)\n : null);\n if (inner) return inner;\n }\n return ours.length < theirs.length ? built : null;\n}\n"]}
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
* A page and a widget definition can be byte-for-byte identical markup —
|
|
6
6
|
* the only thing that says which one a file is, is extrinsic to its
|
|
7
|
-
* content.
|
|
8
|
-
*
|
|
9
|
-
* `*.page.html` is a page.
|
|
7
|
+
* content. A filename suffix carries that meaning: `chrome.html` is the one
|
|
8
|
+
* chrome file, and `*.page.html` is a page.
|
|
10
9
|
*
|
|
11
10
|
* A page's server half is named the same way: `*.requests.ts` and
|
|
12
11
|
* `*.queries.ts` pair with a `*.page.html` by sharing its basename, and are
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locations.d.ts","sourceRoot":"","sources":["../../build/locations.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"locations.d.ts","sourceRoot":"","sources":["../../build/locations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAOH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAQlD,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,UAAU;IAClB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACrC,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,gHAAgH;AAChH,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAyClE;AAED,MAAM,MAAM,WAAW,GAAG,WAAW,CAAC;AAEtC,wBAAsB,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC,CAmBxD;AAED,MAAM,WAAW,YAAY;IAC3B,8FAA8F;IAC9F,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,SAAU,SAAQ,WAAW;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC,CA2D7E"}
|
package/dist/build/locations.js
CHANGED
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
* A page and a widget definition can be byte-for-byte identical markup —
|
|
6
6
|
* the only thing that says which one a file is, is extrinsic to its
|
|
7
|
-
* content.
|
|
8
|
-
*
|
|
9
|
-
* `*.page.html` is a page.
|
|
7
|
+
* content. A filename suffix carries that meaning: `chrome.html` is the one
|
|
8
|
+
* chrome file, and `*.page.html` is a page.
|
|
10
9
|
*
|
|
11
10
|
* A page's server half is named the same way: `*.requests.ts` and
|
|
12
11
|
* `*.queries.ts` pair with a `*.page.html` by sharing its basename, and are
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locations.js","sourceRoot":"","sources":["../../build/locations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Finding the application's own files by what they're named, not by which\n * directory they happen to sit in.\n *\n * A page and a widget definition can be byte-for-byte identical markup —\n * the only thing that says which one a file is, is extrinsic to its\n * content. `pages/` vs. `widgets/` used to carry that meaning; here a\n * filename suffix does instead: `chrome.html` is the one chrome file, and\n * `*.page.html` is a page.\n *\n * A page's server half is named the same way: `*.requests.ts` and\n * `*.queries.ts` pair with a `*.page.html` by sharing its basename, and are\n * free to sit anywhere too. Either may be absent — a page with no data needs\n * neither — but one with no matching `*.page.html` is a naming mistake and\n * is rejected below.\n *\n * Widget definitions, widget scripts and stylesheets are not found here.\n * They arrive from more than one origin — this package's own widgets, an\n * imported package, or `src` — and all three are scanned the same way by\n * build/origins.ts. `src` is simply the last origin in that cascade.\n *\n * `--src` names the one tree scanned this way; `--out` names where the\n * build lands. Those, plus this package's own root (for its built-in\n * widgets and hub/), are the only locations this module resolves — every\n * other module takes the result as a plain parameter and never\n * reconstructs one of its own.\n */\n\nimport { readdir, readFile } from \"node:fs/promises\";\nimport type { Dirent } from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { findPackageRoot } from \"./package-root.js\";\nimport type { BuiltinDirs } from \"./origins.js\";\n\nexport { CUSTOM_ELEMENT_TAG } from \"./origins.js\";\n\nconst CHROME_FILENAME = \"chrome.html\";\nconst PAGE_SUFFIX = \".page.html\";\nconst HOOKS_SUFFIX = \".requests.ts\";\nconst QUERIES_SUFFIX = \".queries.ts\";\nconst IMPORTS_FILENAME = \"imports.ts\";\n\nexport interface PageFile {\n name: string;\n file: string;\n}\n\nexport interface PageSource {\n /** Absolute path to the `.requests.ts` module, if this page has one. */\n requests?: string;\n /** Absolute path to the `.queries.ts` module, if this page has one. */\n queries?: string;\n}\n\ninterface Discovered {\n chromeCandidates: string[];\n pages: PageFile[];\n pageSources: Map<string, PageSource>;\n importsCandidates: string[];\n}\n\n/** The raw walk, with no uniqueness checks — usable on its own by anything that does not need a chrome file. */\nexport async function discoverSrc(src: string): Promise<Discovered> {\n const chromeCandidates: string[] = [];\n const pages: PageFile[] = [];\n const pageSources = new Map<string, PageSource>();\n const importsCandidates: string[] = [];\n\n let entries: Dirent[];\n try {\n entries = await readdir(src, { withFileTypes: true, recursive: true });\n } catch {\n entries = [];\n }\n\n const pageSource = (name: string): PageSource => {\n let p = pageSources.get(name);\n if (!p) {\n p = {};\n pageSources.set(name, p);\n }\n return p;\n };\n\n for (const entry of entries) {\n if (!entry.isFile()) continue;\n const file = path.join(entry.parentPath, entry.name);\n\n if (entry.name === IMPORTS_FILENAME) {\n importsCandidates.push(file);\n } else if (entry.name === CHROME_FILENAME) {\n chromeCandidates.push(file);\n } else if (entry.name.endsWith(PAGE_SUFFIX)) {\n pages.push({ name: entry.name.slice(0, -PAGE_SUFFIX.length), file });\n } else if (entry.name.endsWith(HOOKS_SUFFIX)) {\n pageSource(entry.name.slice(0, -HOOKS_SUFFIX.length)).requests = file;\n } else if (entry.name.endsWith(QUERIES_SUFFIX)) {\n pageSource(entry.name.slice(0, -QUERIES_SUFFIX.length)).queries = file;\n }\n }\n\n pages.sort((a, b) => a.name.localeCompare(b.name));\n return { chromeCandidates, pages, pageSources, importsCandidates };\n}\n\nexport type PackageInfo = BuiltinDirs;\n\nexport async function packageInfo(): Promise<PackageInfo> {\n const here = path.dirname(fileURLToPath(import.meta.url));\n const root = await findPackageRoot(here);\n let pkg: { name: string };\n try {\n pkg = JSON.parse(await readFile(path.join(root, \"package.json\"), \"utf-8\"));\n } catch (err) {\n throw new Error(\n `locations: could not read '${root}/package.json': ${(err as Error).message}`,\n );\n }\n return {\n // `hub/` beside this module, not beside package.json: running from source\n // that is the package's own hub/, and running compiled it is dist/hub —\n // the one an installed package ships, since `files` carries dist and not\n // the source tree.\n builtinHubDir: path.join(path.dirname(here), \"hub\"),\n packageName: pkg.name,\n };\n}\n\nexport interface LocationArgs {\n /** The application's source tree, e.g. \"src\". Scanned as a whole — see this file's header. */\n src: string;\n out: string;\n}\n\nexport interface Locations extends PackageInfo {\n srcDir: string;\n chromeFile: string;\n pages: PageFile[];\n pageSources: Map<string, PageSource>;\n importsFile?: string;\n outDir: string;\n}\n\nexport async function resolveLocations(args: LocationArgs): Promise<Locations> {\n const [info, discovered] = await Promise.all([\n packageInfo(),\n discoverSrc(args.src),\n ]);\n const { chromeCandidates, importsCandidates, pages, pageSources } =\n discovered;\n\n if (chromeCandidates.length === 0) {\n throw new Error(\n `locations: no ${CHROME_FILENAME} file found under '${args.src}'`,\n );\n }\n if (chromeCandidates.length > 1) {\n throw new Error(\n `locations: more than one ${CHROME_FILENAME} file found under '${args.src}': ` +\n chromeCandidates.join(\", \"),\n );\n }\n if (importsCandidates.length > 1) {\n throw new Error(\n `locations: more than one ${IMPORTS_FILENAME} found under '${args.src}': ` +\n importsCandidates.join(\", \"),\n );\n }\n\n // Sorted by name above, so duplicates are adjacent. A stub is the page's\n // whole identity — its route, its template and its server half all key on\n // it — so two files claiming one stub is a naming mistake, not a merge.\n for (let i = 1; i < pages.length; i++) {\n const [prev, here] = [pages[i - 1]!, pages[i]!];\n if (prev.name === here.name) {\n throw new Error(\n `locations: more than one '${here.name}${PAGE_SUFFIX}' found under ` +\n `'${args.src}': ${prev.file}, ${here.file}; the page namespace is ` +\n `flat, so a stub names one page`,\n );\n }\n }\n\n const pageNames = new Set(pages.map((p) => p.name));\n for (const name of pageSources.keys()) {\n if (!pageNames.has(name)) {\n throw new Error(\n `locations: '${name}${HOOKS_SUFFIX}' or '${name}${QUERIES_SUFFIX}' found under ` +\n `'${args.src}' but there is no '${name}${PAGE_SUFFIX}'`,\n );\n }\n }\n\n return {\n ...info,\n srcDir: args.src,\n chromeFile: chromeCandidates[0]!,\n pages,\n pageSources,\n ...(importsCandidates[0] ? { importsFile: importsCandidates[0] } : {}),\n outDir: args.out,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"locations.js","sourceRoot":"","sources":["../../build/locations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Finding the application's own files by what they're named, not by which\n * directory they happen to sit in.\n *\n * A page and a widget definition can be byte-for-byte identical markup —\n * the only thing that says which one a file is, is extrinsic to its\n * content. A filename suffix carries that meaning: `chrome.html` is the one\n * chrome file, and `*.page.html` is a page.\n *\n * A page's server half is named the same way: `*.requests.ts` and\n * `*.queries.ts` pair with a `*.page.html` by sharing its basename, and are\n * free to sit anywhere too. Either may be absent — a page with no data needs\n * neither — but one with no matching `*.page.html` is a naming mistake and\n * is rejected below.\n *\n * Widget definitions, widget scripts and stylesheets are not found here.\n * They arrive from more than one origin — this package's own widgets, an\n * imported package, or `src` — and all three are scanned the same way by\n * build/origins.ts. `src` is simply the last origin in that cascade.\n *\n * `--src` names the one tree scanned this way; `--out` names where the\n * build lands. Those, plus this package's own root (for its built-in\n * widgets and hub/), are the only locations this module resolves — every\n * other module takes the result as a plain parameter and never\n * reconstructs one of its own.\n */\n\nimport { readdir, readFile } from \"node:fs/promises\";\nimport type { Dirent } from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { findPackageRoot } from \"./package-root.js\";\nimport type { BuiltinDirs } from \"./origins.js\";\n\nexport { CUSTOM_ELEMENT_TAG } from \"./origins.js\";\n\nconst CHROME_FILENAME = \"chrome.html\";\nconst PAGE_SUFFIX = \".page.html\";\nconst HOOKS_SUFFIX = \".requests.ts\";\nconst QUERIES_SUFFIX = \".queries.ts\";\nconst IMPORTS_FILENAME = \"imports.ts\";\n\nexport interface PageFile {\n name: string;\n file: string;\n}\n\nexport interface PageSource {\n /** Absolute path to the `.requests.ts` module, if this page has one. */\n requests?: string;\n /** Absolute path to the `.queries.ts` module, if this page has one. */\n queries?: string;\n}\n\ninterface Discovered {\n chromeCandidates: string[];\n pages: PageFile[];\n pageSources: Map<string, PageSource>;\n importsCandidates: string[];\n}\n\n/** The raw walk, with no uniqueness checks — usable on its own by anything that does not need a chrome file. */\nexport async function discoverSrc(src: string): Promise<Discovered> {\n const chromeCandidates: string[] = [];\n const pages: PageFile[] = [];\n const pageSources = new Map<string, PageSource>();\n const importsCandidates: string[] = [];\n\n let entries: Dirent[];\n try {\n entries = await readdir(src, { withFileTypes: true, recursive: true });\n } catch {\n entries = [];\n }\n\n const pageSource = (name: string): PageSource => {\n let p = pageSources.get(name);\n if (!p) {\n p = {};\n pageSources.set(name, p);\n }\n return p;\n };\n\n for (const entry of entries) {\n if (!entry.isFile()) continue;\n const file = path.join(entry.parentPath, entry.name);\n\n if (entry.name === IMPORTS_FILENAME) {\n importsCandidates.push(file);\n } else if (entry.name === CHROME_FILENAME) {\n chromeCandidates.push(file);\n } else if (entry.name.endsWith(PAGE_SUFFIX)) {\n pages.push({ name: entry.name.slice(0, -PAGE_SUFFIX.length), file });\n } else if (entry.name.endsWith(HOOKS_SUFFIX)) {\n pageSource(entry.name.slice(0, -HOOKS_SUFFIX.length)).requests = file;\n } else if (entry.name.endsWith(QUERIES_SUFFIX)) {\n pageSource(entry.name.slice(0, -QUERIES_SUFFIX.length)).queries = file;\n }\n }\n\n pages.sort((a, b) => a.name.localeCompare(b.name));\n return { chromeCandidates, pages, pageSources, importsCandidates };\n}\n\nexport type PackageInfo = BuiltinDirs;\n\nexport async function packageInfo(): Promise<PackageInfo> {\n const here = path.dirname(fileURLToPath(import.meta.url));\n const root = await findPackageRoot(here);\n let pkg: { name: string };\n try {\n pkg = JSON.parse(await readFile(path.join(root, \"package.json\"), \"utf-8\"));\n } catch (err) {\n throw new Error(\n `locations: could not read '${root}/package.json': ${(err as Error).message}`,\n );\n }\n return {\n // `hub/` beside this module, not beside package.json: running from source\n // that is the package's own hub/, and running compiled it is dist/hub —\n // the one an installed package ships, since `files` carries dist and not\n // the source tree.\n builtinHubDir: path.join(path.dirname(here), \"hub\"),\n packageName: pkg.name,\n };\n}\n\nexport interface LocationArgs {\n /** The application's source tree, e.g. \"src\". Scanned as a whole — see this file's header. */\n src: string;\n out: string;\n}\n\nexport interface Locations extends PackageInfo {\n srcDir: string;\n chromeFile: string;\n pages: PageFile[];\n pageSources: Map<string, PageSource>;\n importsFile?: string;\n outDir: string;\n}\n\nexport async function resolveLocations(args: LocationArgs): Promise<Locations> {\n const [info, discovered] = await Promise.all([\n packageInfo(),\n discoverSrc(args.src),\n ]);\n const { chromeCandidates, importsCandidates, pages, pageSources } =\n discovered;\n\n if (chromeCandidates.length === 0) {\n throw new Error(\n `locations: no ${CHROME_FILENAME} file found under '${args.src}'`,\n );\n }\n if (chromeCandidates.length > 1) {\n throw new Error(\n `locations: more than one ${CHROME_FILENAME} file found under '${args.src}': ` +\n chromeCandidates.join(\", \"),\n );\n }\n if (importsCandidates.length > 1) {\n throw new Error(\n `locations: more than one ${IMPORTS_FILENAME} found under '${args.src}': ` +\n importsCandidates.join(\", \"),\n );\n }\n\n // Sorted by name above, so duplicates are adjacent. A stub is the page's\n // whole identity — its route, its template and its server half all key on\n // it — so two files claiming one stub is a naming mistake, not a merge.\n for (let i = 1; i < pages.length; i++) {\n const [prev, here] = [pages[i - 1]!, pages[i]!];\n if (prev.name === here.name) {\n throw new Error(\n `locations: more than one '${here.name}${PAGE_SUFFIX}' found under ` +\n `'${args.src}': ${prev.file}, ${here.file}; the page namespace is ` +\n `flat, so a stub names one page`,\n );\n }\n }\n\n const pageNames = new Set(pages.map((p) => p.name));\n for (const name of pageSources.keys()) {\n if (!pageNames.has(name)) {\n throw new Error(\n `locations: '${name}${HOOKS_SUFFIX}' or '${name}${QUERIES_SUFFIX}' found under ` +\n `'${args.src}' but there is no '${name}${PAGE_SUFFIX}'`,\n );\n }\n }\n\n return {\n ...info,\n srcDir: args.src,\n chromeFile: chromeCandidates[0]!,\n pages,\n pageSources,\n ...(importsCandidates[0] ? { importsFile: importsCandidates[0] } : {}),\n outDir: args.out,\n };\n}\n"]}
|
package/dist/build/pages.d.ts
CHANGED
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
* build/elements.ts's client entry point.
|
|
4
4
|
*
|
|
5
5
|
* `*.requests.ts` and `*.queries.ts` are ordinary TypeScript, found by
|
|
6
|
-
* build/locations.ts and handed here already paired by page name.
|
|
7
|
-
* module writes
|
|
8
|
-
*
|
|
9
|
-
* `createHub()` call. It is written to `<out>/pages.ts` rather than compiled
|
|
6
|
+
* build/locations.ts and handed here already paired by page name. This
|
|
7
|
+
* module writes an import of each pair, a `Pages` object naming them by
|
|
8
|
+
* page, and the one `createHub()` call. It is written to `<out>/pages.ts` rather than compiled
|
|
10
9
|
* — the application runs it directly, exactly as it does its own server
|
|
11
10
|
* code, so it never needs a bundler step the way the browser-bound client
|
|
12
11
|
* entry does.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../build/pages.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../build/pages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAY3D;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,QAAQ,EAAE,EACjB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EACpC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAClB,MAAM,GAAG,SAAS,CAsCpB"}
|
package/dist/build/pages.js
CHANGED
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
* build/elements.ts's client entry point.
|
|
4
4
|
*
|
|
5
5
|
* `*.requests.ts` and `*.queries.ts` are ordinary TypeScript, found by
|
|
6
|
-
* build/locations.ts and handed here already paired by page name.
|
|
7
|
-
* module writes
|
|
8
|
-
*
|
|
9
|
-
* `createHub()` call. It is written to `<out>/pages.ts` rather than compiled
|
|
6
|
+
* build/locations.ts and handed here already paired by page name. This
|
|
7
|
+
* module writes an import of each pair, a `Pages` object naming them by
|
|
8
|
+
* page, and the one `createHub()` call. It is written to `<out>/pages.ts` rather than compiled
|
|
10
9
|
* — the application runs it directly, exactly as it does its own server
|
|
11
10
|
* code, so it never needs a bundler step the way the browser-bound client
|
|
12
11
|
* entry does.
|
package/dist/build/pages.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pages.js","sourceRoot":"","sources":["../../build/pages.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"pages.js","sourceRoot":"","sources":["../../build/pages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,qFAAqF;AACrF,SAAS,UAAU,CAAC,IAAY,EAAE,MAA8B;IAC9D,OAAO,QAAQ,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC,IAAI,MAAM,EAAE,CAAC;AAChE,CAAC;AAED,SAAS,cAAc,CAAC,MAAc,EAAE,IAAY;IAClD,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClE,OAAO,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;AAChD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CACzB,KAAiB,EACjB,WAAoC,EACpC,MAAc,EACd,WAAmB;IAEnB,IAAI,WAAW,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAE7C,MAAM,OAAO,GAAa;QACxB,6BAA6B,IAAI,CAAC,SAAS,CAAC,GAAG,WAAW,SAAS,CAAC,GAAG;KACxE,CAAC;IACF,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,MAAM;YAAE,SAAS;QAEtB,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ;YAChC,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC;YAC9B,CAAC,CAAC,SAAS,CAAC;QACd,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAE3E,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YACpB,OAAO,CAAC,IAAI,CACV,wBAAwB,UAAU,WAAW,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CACxG,CAAC;QACJ,CAAC;QACD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,OAAO,CAAC,IAAI,CACV,uBAAuB,SAAS,WAAW,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CACrG,CAAC;QACJ,CAAC;QAED,OAAO,CAAC,IAAI,CACV,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,iBAAiB,UAAU,IAAI,IAAI,cAAc,SAAS,IAAI,IAAI,KAAK,CACjG,CAAC;IACJ,CAAC;IAED,OAAO,CACL,CAAC,GAAG,OAAO,EAAE,EAAE,EAAE,gCAAgC,EAAE,GAAG,OAAO,EAAE,KAAK,CAAC,CAAC,IAAI,CACxE,IAAI,CACL,GAAG,IAAI,CACT,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Generating the hub's page registry — the server-side counterpart to\n * build/elements.ts's client entry point.\n *\n * `*.requests.ts` and `*.queries.ts` are ordinary TypeScript, found by\n * build/locations.ts and handed here already paired by page name. This\n * module writes an import of each pair, a `Pages` object naming them by\n * page, and the one `createHub()` call. It is written to `<out>/pages.ts` rather than compiled\n * — the application runs it directly, exactly as it does its own server\n * code, so it never needs a bundler step the way the browser-bound client\n * entry does.\n *\n * Each import keeps its `.ts` extension. Node's loader, including its own\n * type stripping, looks for exactly the path it is given and never tries an\n * extension, so a path without one runs under tsx and nowhere else.\n *\n * Nothing here is emitted for a page with neither a requests nor a queries\n * file — a purely static page needs no hub entry, and createHub is only\n * called at all when there is at least one.\n */\n\nimport path from \"node:path\";\nimport type { PageFile, PageSource } from \"./locations.js\";\n\n/** A page name may contain anything a filename can; an import identifier may not. */\nfunction identifier(name: string, suffix: \"Requests\" | \"Queries\"): string {\n return `page_${name.replace(/[^a-zA-Z0-9]/g, \"_\")}_${suffix}`;\n}\n\nfunction relativeImport(outDir: string, file: string): string {\n const rel = path.relative(outDir, file).split(path.sep).join(\"/\");\n return rel.startsWith(\".\") ? rel : `./${rel}`;\n}\n\n/**\n * `undefined` when no page has a requests or queries file — the caller writes\n * nothing in that case, the same way an app with no custom elements gets no\n * client entry beyond the built-ins.\n */\nexport function pagesSource(\n pages: PageFile[],\n pageSources: Map<string, PageSource>,\n outDir: string,\n packageName: string,\n): string | undefined {\n if (pageSources.size === 0) return undefined;\n\n const imports: string[] = [\n `import { createHub } from ${JSON.stringify(`${packageName}/server`)};`,\n ];\n const entries: string[] = [];\n\n for (const { name } of pages) {\n const source = pageSources.get(name);\n if (!source) continue;\n\n const requestsId = source.requests\n ? identifier(name, \"Requests\")\n : undefined;\n const queriesId = source.queries ? identifier(name, \"Queries\") : undefined;\n\n if (source.requests) {\n imports.push(\n `import { requests as ${requestsId} } from ${JSON.stringify(relativeImport(outDir, source.requests))};`,\n );\n }\n if (source.queries) {\n imports.push(\n `import { queries as ${queriesId} } from ${JSON.stringify(relativeImport(outDir, source.queries))};`,\n );\n }\n\n entries.push(\n ` ${JSON.stringify(name)}: { requests: ${requestsId ?? \"{}\"}, queries: ${queriesId ?? \"{}\"} },`,\n );\n }\n\n return (\n [...imports, \"\", \"export const hub = createHub({\", ...entries, \"});\"].join(\n \"\\n\",\n ) + \"\\n\"\n );\n}\n"]}
|
|
@@ -1,32 +1,35 @@
|
|
|
1
|
-
export declare const
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const ATTR_KEY = "lb-key";
|
|
4
|
-
export declare const ATTR_KEY_VALUE = "lb-key-value";
|
|
5
|
-
export declare const ATTR_CELL = "lb-cell";
|
|
1
|
+
export declare const ATTR_QUERY = "lb-query";
|
|
2
|
+
export declare const ATTR_COLUMN = "lb-column";
|
|
6
3
|
export declare const ATTR_SHOW = "lb-show";
|
|
7
|
-
export declare const
|
|
8
|
-
export declare const
|
|
4
|
+
export declare const ATTR_COLUMN_VALUE = "lb-column-value";
|
|
5
|
+
export declare const ATTR_KEY_VALUE = "lb-key-value";
|
|
6
|
+
export declare const ATTR_ROW_LIVE = "lb-row-live";
|
|
7
|
+
export declare const LIVE_ROW = "[lb-row-live]";
|
|
8
|
+
export declare const ATTR_QUERY_ROW_COUNT = "lb-query-row-count";
|
|
9
|
+
export declare const ATTR_REQUEST = "lb-request";
|
|
9
10
|
export declare const LB_EVENT_NAME = "lb-request";
|
|
10
|
-
export declare const
|
|
11
|
+
export declare const ATTR_REQUEST_PENDING = "lb-request-pending";
|
|
12
|
+
export declare const ATTR_REQUEST_ERROR = "lb-request-error";
|
|
11
13
|
export declare const LB_RESERVED_PREFIX = "lb-";
|
|
12
|
-
export declare const
|
|
13
|
-
export declare const
|
|
14
|
-
export declare const
|
|
15
|
-
export declare const
|
|
16
|
-
export declare const
|
|
17
|
-
export declare const
|
|
18
|
-
export declare const
|
|
19
|
-
export declare const
|
|
20
|
-
export declare const
|
|
14
|
+
export declare const REQUEST_ROW_INSERT = "lb-row-insert";
|
|
15
|
+
export declare const REQUEST_ROW_UPDATE = "lb-row-update";
|
|
16
|
+
export declare const REQUEST_ROW_DELETE = "lb-row-delete";
|
|
17
|
+
export declare const LB_ROW_REQUESTS: readonly ["lb-row-insert", "lb-row-update", "lb-row-delete"];
|
|
18
|
+
export declare const KIND_ROW = "row";
|
|
19
|
+
export declare const KIND_ROWS = "rows";
|
|
20
|
+
export declare const URL_QUERY = "lb-url";
|
|
21
|
+
export declare const URL_COLUMN_PATH = "lb-path";
|
|
22
|
+
export declare const URL_COLUMN_PAGE_LABEL = "lb-page-label";
|
|
23
|
+
export declare const URL_COLUMN_PAGE_UNKNOWN = "lb-page-unknown";
|
|
24
|
+
export declare const ATTR_URL_LINK = "lb-url-link";
|
|
25
|
+
export declare const ATTR_URL_PUSH = "lb-url-push";
|
|
26
|
+
export declare const ATTR_URL_UNKNOWN = "lb-url-unknown";
|
|
27
|
+
export declare const ATTR_EXP_SLOT = "lb-exp-slot";
|
|
28
|
+
export declare const ATTR_EXP_TEMPLATE = "lb-exp-template";
|
|
21
29
|
export declare const ATTR_PAGE = "lb-page";
|
|
22
|
-
export declare const
|
|
23
|
-
export declare const
|
|
24
|
-
export declare const NAV_CELL_URI = "page-uri";
|
|
25
|
-
export declare const QUERY_PARMS_ROW = "lb-query-parms";
|
|
26
|
-
export declare const ATTR_UNKNOWN_PAGE = "lb-unknown-page";
|
|
30
|
+
export declare const ATTR_PAGE_TITLE = "lb-page-title";
|
|
31
|
+
export declare const DEVELOPER_ATTRIBUTES: readonly ["lb-query", "lb-column", "lb-show", "lb-request", "lb-url-link", "lb-url-push", "lb-url-unknown"];
|
|
27
32
|
export declare const HUB_TAG_NAME = "lb-hub";
|
|
28
|
-
export declare const ATTR_PENDING = "lb-pending";
|
|
29
|
-
export declare const ATTR_ERROR = "lb-error";
|
|
30
33
|
export declare const LB_ENDPOINT = "/lb";
|
|
31
34
|
export declare const LB_REQUEST_TIMEOUT_MS = 10000;
|
|
32
35
|
//# sourceMappingURL=lb-constants.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lb-constants.d.ts","sourceRoot":"","sources":["../../core/lb-constants.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lb-constants.d.ts","sourceRoot":"","sources":["../../core/lb-constants.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,UAAU,aAAa,CAAC;AAKrC,eAAO,MAAM,WAAW,cAAc,CAAC;AAavC,eAAO,MAAM,SAAS,YAAY,CAAC;AAKnC,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AAKnD,eAAO,MAAM,cAAc,iBAAiB,CAAC;AAM7C,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAI3C,eAAO,MAAM,QAAQ,kBAAuB,CAAC;AAK7C,eAAO,MAAM,oBAAoB,uBAAuB,CAAC;AAKzD,eAAO,MAAM,YAAY,eAAe,CAAC;AAIzC,eAAO,MAAM,aAAa,eAAe,CAAC;AAI1C,eAAO,MAAM,oBAAoB,uBAAuB,CAAC;AAKzD,eAAO,MAAM,kBAAkB,qBAAqB,CAAC;AAOrD,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AAIxC,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAClD,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAClD,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAElD,eAAO,MAAM,eAAe,8DAIlB,CAAC;AAGX,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAC9B,eAAO,MAAM,SAAS,SAAS,CAAC;AAKhC,eAAO,MAAM,SAAS,WAAW,CAAC;AAClC,eAAO,MAAM,eAAe,YAAY,CAAC;AACzC,eAAO,MAAM,qBAAqB,kBAAkB,CAAC;AACrD,eAAO,MAAM,uBAAuB,oBAAoB,CAAC;AAGzD,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAI3C,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAG3C,eAAO,MAAM,gBAAgB,mBAAmB,CAAC;AAMjD,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAK3C,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AAGnD,eAAO,MAAM,SAAS,YAAY,CAAC;AAInC,eAAO,MAAM,eAAe,kBAAkB,CAAC;AAK/C,eAAO,MAAM,oBAAoB,6GAQvB,CAAC;AAQX,eAAO,MAAM,YAAY,WAAW,CAAC;AASrC,eAAO,MAAM,WAAW,QAAQ,CAAC;AAIjC,eAAO,MAAM,qBAAqB,QAAS,CAAC"}
|