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