@loadbare/app 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/README.md +53 -82
  2. package/dist/build/assemble.d.ts +7 -5
  3. package/dist/build/assemble.d.ts.map +1 -1
  4. package/dist/build/assemble.js +29 -9
  5. package/dist/build/cli.d.ts +20 -12
  6. package/dist/build/cli.d.ts.map +1 -1
  7. package/dist/build/cli.js +34 -16
  8. package/dist/build/elements.d.ts +15 -29
  9. package/dist/build/elements.d.ts.map +1 -1
  10. package/dist/build/elements.js +25 -111
  11. package/dist/build/expand.d.ts +1 -1
  12. package/dist/build/expand.js +1 -1
  13. package/dist/build/locations.d.ts +14 -37
  14. package/dist/build/locations.d.ts.map +1 -1
  15. package/dist/build/locations.js +24 -67
  16. package/dist/build/origins.d.ts +109 -0
  17. package/dist/build/origins.d.ts.map +1 -0
  18. package/dist/build/origins.js +270 -0
  19. package/dist/core/lb-constants.d.ts +1 -0
  20. package/dist/core/lb-constants.d.ts.map +1 -1
  21. package/dist/core/lb-constants.js +15 -8
  22. package/dist/core/lb-types.d.ts +2 -2
  23. package/dist/core/lb-types.d.ts.map +1 -1
  24. package/dist/hub/lb-apply.js +1 -1
  25. package/dist/hub/lb-hub.d.ts.map +1 -1
  26. package/dist/hub/lb-hub.js +44 -17
  27. package/dist/hub/lb-rows.js +3 -3
  28. package/dist/server/lb-server.d.ts +5 -4
  29. package/dist/server/lb-server.d.ts.map +1 -1
  30. package/dist/tests/assemble.test.js +11 -4
  31. package/dist/tests/elements.test.js +47 -51
  32. package/dist/tests/expand.test.d.ts +1 -1
  33. package/dist/tests/expand.test.js +2 -2
  34. package/dist/tests/fixtures/elements/collision/imports.d.ts +3 -0
  35. package/dist/tests/fixtures/elements/collision/imports.d.ts.map +1 -0
  36. package/dist/tests/fixtures/elements/collision/imports.js +1 -0
  37. package/dist/tests/fixtures/elements/manifest/imports.d.ts +3 -0
  38. package/dist/tests/fixtures/elements/manifest/imports.d.ts.map +1 -0
  39. package/dist/tests/fixtures/elements/manifest/imports.js +1 -0
  40. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts +3 -0
  41. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts.map +1 -0
  42. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.js +1 -0
  43. package/dist/tests/fixtures/elements/{collision/elements.d.ts → manifest-not-array/imports.d.ts} +1 -1
  44. package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts.map +1 -0
  45. package/dist/tests/fixtures/elements/manifest-not-array/imports.js +1 -0
  46. package/dist/tests/fixtures/elements/pkg/acme-widget.d.ts +2 -0
  47. package/dist/tests/fixtures/elements/pkg/acme-widget.d.ts.map +1 -0
  48. package/dist/tests/fixtures/elements/pkg/acme-widget.js +1 -0
  49. package/dist/tests/lb-express.test.js +1 -1
  50. package/dist/tests/origins.test.d.ts +10 -0
  51. package/dist/tests/origins.test.d.ts.map +1 -0
  52. package/dist/tests/origins.test.js +326 -0
  53. package/dist/tests/pages.test.js +3 -3
  54. package/dist/tests/styles.test.js +7 -4
  55. package/docs/reference/builder.md +128 -0
  56. package/docs/reference/chrome.md +75 -0
  57. package/docs/reference/css.md +44 -0
  58. package/docs/reference/custom-elements.md +327 -0
  59. package/docs/reference/data-binding.md +240 -0
  60. package/docs/reference/overview.md +38 -0
  61. package/docs/reference/page-files.md +175 -0
  62. package/docs/reference/server.md +123 -0
  63. package/docs/reference/widgets.md +163 -0
  64. package/docs/roadmap.md +130 -0
  65. package/docs/testing.md +228 -0
  66. package/docs/theory.md +344 -223
  67. package/docs/tutorials/000-getting-started.md +86 -0
  68. package/docs/tutorials/010-pages-and-navigation.md +129 -0
  69. package/docs/tutorials/020-css.md +103 -0
  70. package/docs/tutorials/030-html-decomposition.md +79 -0
  71. package/docs/tutorials/040-displaying-data.md +169 -0
  72. package/docs/tutorials/050-actions.md +77 -0
  73. package/docs/tutorials/060-custom-element-code.md +73 -0
  74. package/docs/tutorials/065-conditional-rendering.md +161 -0
  75. package/docs/tutorials/070-displaying-a-list.md +137 -0
  76. package/docs/tutorials/072-inserting-into-a-list.md +88 -0
  77. package/docs/tutorials/074-deleting-from-a-list.md +77 -0
  78. package/docs/tutorials/076-updating-a-list-item.md +86 -0
  79. package/docs/tutorials/080-widget-requests.md +124 -0
  80. package/docs/tutorials/090-using-widget-libraries.md +75 -0
  81. package/package.json +4 -12
  82. package/dist/client.js +0 -522
  83. package/dist/demo-static/src/widgets/app-box.d.ts +0 -15
  84. package/dist/demo-static/src/widgets/app-box.d.ts.map +0 -1
  85. package/dist/demo-static/src/widgets/app-box.js +0 -19
  86. package/dist/tests/fixtures/elements/collision/elements.d.ts.map +0 -1
  87. package/dist/tests/fixtures/elements/collision/elements.js +0 -3
  88. package/dist/tests/fixtures/elements/manifest/elements.d.ts +0 -5
  89. package/dist/tests/fixtures/elements/manifest/elements.d.ts.map +0 -1
  90. package/dist/tests/fixtures/elements/manifest/elements.js +0 -3
  91. package/dist/tests/fixtures/elements/manifest-bad-tag/elements.d.ts +0 -5
  92. package/dist/tests/fixtures/elements/manifest-bad-tag/elements.d.ts.map +0 -1
  93. package/dist/tests/fixtures/elements/manifest-bad-tag/elements.js +0 -3
  94. package/dist/tests/fixtures/elements/manifest-bad-value/elements.d.ts +0 -5
  95. package/dist/tests/fixtures/elements/manifest-bad-value/elements.d.ts.map +0 -1
  96. package/dist/tests/fixtures/elements/manifest-bad-value/elements.js +0 -3
  97. package/dist/tests/golden.test.d.ts +0 -19
  98. package/dist/tests/golden.test.d.ts.map +0 -1
  99. package/dist/tests/golden.test.js +0 -60
  100. package/dist/tests/helpers/window.d.ts +0 -43
  101. package/dist/tests/helpers/window.d.ts.map +0 -1
  102. package/dist/tests/helpers/window.js +0 -78
  103. package/dist/tests/lb-input.test.d.ts +0 -9
  104. package/dist/tests/lb-input.test.d.ts.map +0 -1
  105. package/dist/tests/lb-input.test.js +0 -78
  106. package/dist/tests/lb-list.test.d.ts +0 -12
  107. package/dist/tests/lb-list.test.d.ts.map +0 -1
  108. package/dist/tests/lb-list.test.js +0 -44
  109. package/dist/tests/lb-options.test.d.ts +0 -10
  110. package/dist/tests/lb-options.test.d.ts.map +0 -1
  111. package/dist/tests/lb-options.test.js +0 -121
  112. package/dist/tests/lb-picker.test.d.ts +0 -14
  113. package/dist/tests/lb-picker.test.d.ts.map +0 -1
  114. package/dist/tests/lb-picker.test.js +0 -59
  115. package/dist/tests/lb-select.test.d.ts +0 -9
  116. package/dist/tests/lb-select.test.d.ts.map +0 -1
  117. package/dist/tests/lb-select.test.js +0 -71
  118. package/dist/tests/lb-table.test.d.ts +0 -15
  119. package/dist/tests/lb-table.test.d.ts.map +0 -1
  120. package/dist/tests/lb-table.test.js +0 -205
  121. package/dist/widgets/index.d.ts +0 -7
  122. package/dist/widgets/index.d.ts.map +0 -1
  123. package/dist/widgets/index.js +0 -6
  124. package/dist/widgets/lb-input.d.ts +0 -2
  125. package/dist/widgets/lb-input.d.ts.map +0 -1
  126. package/dist/widgets/lb-input.js +0 -48
  127. package/dist/widgets/lb-list.d.ts +0 -2
  128. package/dist/widgets/lb-list.d.ts.map +0 -1
  129. package/dist/widgets/lb-list.js +0 -17
  130. package/dist/widgets/lb-options.d.ts +0 -26
  131. package/dist/widgets/lb-options.d.ts.map +0 -1
  132. package/dist/widgets/lb-options.js +0 -72
  133. package/dist/widgets/lb-picker.d.ts +0 -2
  134. package/dist/widgets/lb-picker.d.ts.map +0 -1
  135. package/dist/widgets/lb-picker.js +0 -25
  136. package/dist/widgets/lb-select.d.ts +0 -2
  137. package/dist/widgets/lb-select.d.ts.map +0 -1
  138. package/dist/widgets/lb-select.js +0 -43
  139. package/dist/widgets/lb-table.d.ts +0 -2
  140. package/dist/widgets/lb-table.d.ts.map +0 -1
  141. package/dist/widgets/lb-table.js +0 -113
  142. package/docs/application-chrome.md +0 -36
  143. package/docs/building-html-pages.md +0 -130
  144. package/docs/getting-started.md +0 -120
  145. package/docs/guide.md +0 -1164
  146. package/docs/hosting.md +0 -218
  147. package/docs/latent-risks.md +0 -20
  148. package/widgets/index.ts +0 -6
  149. package/widgets/lb-input.html +0 -1
  150. package/widgets/lb-input.ts +0 -64
  151. package/widgets/lb-list.html +0 -1
  152. package/widgets/lb-list.ts +0 -21
  153. package/widgets/lb-options.html +0 -4
  154. package/widgets/lb-options.ts +0 -88
  155. package/widgets/lb-picker.html +0 -7
  156. package/widgets/lb-picker.ts +0 -27
  157. package/widgets/lb-select.html +0 -4
  158. package/widgets/lb-select.ts +0 -55
  159. package/widgets/lb-table.html +0 -8
  160. package/widgets/lb-table.ts +0 -126
@@ -3,127 +3,41 @@
3
3
  * and finding the script to import for it when it can.
4
4
  *
5
5
  * A used tag is valid if it has a script, a definition, or both:
6
- * - a script registers the class that upgrades it. It can come from a
7
- * built-in source (this package's own widgets/ or hub/ — hub/ is where
8
- * lb-hub itself lives, discovered exactly the same way as any built-in
9
- * widget, no special case for it here), an application's own — found
10
- * anywhere in its source tree by build/locations.ts, not confined to a
11
- * directory — or a third party's, declared by tag in `elements.ts`.
12
- * - a definition (an `.html` file, built-in or local) expands the tag's
13
- * content at build time. A widget with only a definition and no script
14
- * is markup only: expansion already gave it everything it will ever
15
- * have, and the tag survives just as an inert wrapper (see expand.ts).
6
+ * - a script registers the class that upgrades it.
7
+ * - a definition (an `.html` file) expands the tag's content at build
8
+ * time. A widget with only a definition and no script is markup only:
9
+ * expansion already gave it everything it will ever have, and the tag
10
+ * survives as an inert wrapper (see expand.ts).
16
11
  *
17
12
  * A tag with neither — no script anywhere and no definition — would do
18
13
  * nothing were it ever to reach the browser, so that is an error.
19
14
  *
20
- * Every directory and file here is a parameter, resolved once by
21
- * build/locations.ts — this module never reconstructs one of its own, and
22
- * the application's own widgets are handed over already found, not scanned
23
- * again here.
15
+ * Both halves are looked for in the same ordered origins, and the later
16
+ * origin wins: an application's own script overrides one from a package it
17
+ * imported, which overrides a built-in. Two files claiming one tag *within*
18
+ * one origin is the error case, and build/origins.ts raises it while
19
+ * scanning. Nothing is scanned here.
24
20
  */
25
- import { readdir } from "node:fs/promises";
26
21
  import path from "node:path";
27
- import { pathToFileURL } from "node:url";
28
- import { CUSTOM_ELEMENT_TAG } from "./locations.js";
29
- /** This package's own built-in sources are still a directory scan — that layout is fixed and internal, not discovered from an application's tree. */
30
- async function scanBuiltinDir(dir) {
31
- const found = new Map();
32
- let names;
33
- try {
34
- names = await readdir(dir);
35
- }
36
- catch {
37
- return found;
38
- }
39
- const entry = (tag) => {
40
- let w = found.get(tag);
41
- if (!w) {
42
- w = {};
43
- found.set(tag, w);
44
- }
45
- return w;
46
- };
47
- for (const name of names) {
48
- if (name.endsWith(".ts")) {
49
- entry(name.slice(0, -".ts".length).toLowerCase()).script = path.join(dir, name);
50
- }
51
- else if (name.endsWith(".html")) {
52
- entry(name.slice(0, -".html".length).toLowerCase()).html = path.join(dir, name);
53
- }
54
- }
55
- return found;
56
- }
57
- /**
58
- * `elements.ts`, executed rather than parsed — it runs through the same
59
- * loader as the rest of the build, so it is ordinary TypeScript, not a data
60
- * format with its own rules.
61
- */
62
- export async function loadElementManifest(file) {
63
- let mod;
64
- try {
65
- // pathToFileURL requires an absolute path; the caller's may not be one.
66
- mod = (await import(pathToFileURL(path.resolve(file)).href));
67
- }
68
- catch (err) {
69
- if (err.code === "ERR_MODULE_NOT_FOUND")
70
- return {};
71
- throw new Error(`elements: '${file}' failed to load: ${err.message}`);
72
- }
73
- const manifest = (mod.default ?? {});
74
- for (const [tag, spec] of Object.entries(manifest)) {
75
- if (!CUSTOM_ELEMENT_TAG.test(tag)) {
76
- throw new Error(`elements.ts: '${tag}' is not a valid custom element name`);
77
- }
78
- if (typeof spec !== "string") {
79
- throw new Error(`elements.ts: '${tag}' must map to an import specifier string, got ${typeof spec}`);
80
- }
81
- }
82
- return manifest;
83
- }
84
- export async function resolveElements({ used, widgets, elementsFile, builtinWidgetsDir, builtinHubDir, packageName, }) {
85
- // lb-hub is found here the same way as any other built-in: hub/ is just a
86
- // second built-in source, named specifiers differently because it is the
87
- // package's main entry point rather than a widgets/ subpath.
88
- const builtinSources = [
89
- {
90
- dir: builtinWidgetsDir,
91
- specifier: (tag) => `${packageName}/widgets/${tag}`,
92
- },
93
- { dir: builtinHubDir, specifier: () => packageName },
94
- ];
95
- const builtinDirs = await Promise.all(builtinSources.map((source) => scanBuiltinDir(source.dir)));
96
- const manifest = elementsFile ? await loadElementManifest(elementsFile) : {};
22
+ export { loadElementManifest } from "./origins.js";
23
+ export function resolveElements({ used, scans, }) {
97
24
  const resolved = new Map();
98
25
  for (const tag of used) {
99
- const hits = [];
26
+ let script;
100
27
  let hasDefinition = false;
101
- builtinSources.forEach((source, i) => {
102
- const entry = builtinDirs[i].get(tag);
103
- if (entry?.script) {
104
- hits.push([
105
- source.dir,
106
- { kind: "specifier", value: source.specifier(tag) },
107
- ]);
108
- }
109
- if (entry?.html)
28
+ // In order, so the last origin holding a script is the one that wins.
29
+ for (const scan of scans) {
30
+ if (scan.definitions.has(tag))
110
31
  hasDefinition = true;
111
- });
112
- const local = widgets.get(tag);
113
- if (local?.script) {
114
- hits.push(["src", { kind: "file", value: local.script }]);
115
- }
116
- if (local?.html)
117
- hasDefinition = true;
118
- if (tag in manifest) {
119
- hits.push([elementsFile, { kind: "specifier", value: manifest[tag] }]);
120
- }
121
- if (hits.length > 1) {
122
- throw new Error(`build: <${tag}> is declared in more than one place: ` +
123
- hits.map(([where]) => where).join(", "));
32
+ const found = scan.scripts.get(tag);
33
+ if (found === undefined)
34
+ continue;
35
+ script = scan.specifier
36
+ ? { kind: "specifier", value: scan.specifier(tag) }
37
+ : { kind: "file", value: found };
124
38
  }
125
- if (hits.length === 1) {
126
- resolved.set(tag, hits[0][1]);
39
+ if (script) {
40
+ resolved.set(tag, script);
127
41
  continue;
128
42
  }
129
43
  // No script anywhere. Valid only if a definition gave the tag its
@@ -131,7 +45,7 @@ export async function resolveElements({ used, widgets, elementsFile, builtinWidg
131
45
  // tag would do nothing were it to reach the browser.
132
46
  if (!hasDefinition) {
133
47
  throw new Error(`build: <${tag}> has neither a script nor a definition, so it would ` +
134
- `do nothing (checked built-in widgets, the src tree, and elements.ts)`);
48
+ `do nothing (checked ${scans.map((s) => `'${s.name}'`).join(", ")})`);
135
49
  }
136
50
  }
137
51
  return { resolved };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Expansion — see docs/expansion.md.
2
+ * Expansion — see docs/reference/custom-elements.md, "HTML".
3
3
  *
4
4
  * A page author writes one tag and gets the tree it stands for. Expansion
5
5
  * runs at build time, before any request exists, so it has no data. That is
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Expansion — see docs/expansion.md.
2
+ * Expansion — see docs/reference/custom-elements.md, "HTML".
3
3
  *
4
4
  * A page author writes one tag and gets the tree it stands for. Expansion
5
5
  * runs at build time, before any request exists, so it has no data. That is
@@ -1,16 +1,12 @@
1
1
  /**
2
- * Finding every file a build needs by what it's named, not by which
3
- * directory it happens to sit in.
2
+ * Finding the application's own files by what they're named, not by which
3
+ * directory they happen to sit in.
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
7
  * content. `pages/` vs. `widgets/` used to carry that meaning; here a
8
- * filename suffix does instead: `*.chrome.html` is the one chrome file,
9
- * `*.page.html` is a page, and anything else `.html` is a widget
10
- * definition. A `.ts` file shaped like a custom element tag is a widget
11
- * script, found the same way regardless of where it lives. Both halves of
12
- * one widget — its markup and its class — are free to sit side by side or
13
- * apart; nothing here requires a `widgets/` directory to exist at all.
8
+ * filename suffix does instead: `chrome.html` is the one chrome file, and
9
+ * `*.page.html` is a page.
14
10
  *
15
11
  * A page's server half is named the same way: `*.hooks.ts` and
16
12
  * `*.queries.ts` pair with a `*.page.html` by sharing its basename, and are
@@ -18,12 +14,10 @@
18
14
  * neither — but one with no matching `*.page.html` is a naming mistake and
19
15
  * is rejected below.
20
16
  *
21
- * A `.css` file is not paired with anything — it is not a widget's, a
22
- * page's, or the chrome's, because build/styles.ts does not distinguish
23
- * them. Every `.css` file anywhere under `src` is concatenated, in an order
24
- * controlled entirely by the author: sorted by filename first, so a global
25
- * stylesheet sorts to the front by naming itself `00-reset.css` or similar,
26
- * regardless of which directory it lives in.
17
+ * Widget definitions, widget scripts and stylesheets are not found here.
18
+ * They arrive from more than one origin — this package's own widgets, an
19
+ * imported package, or `src` — and all three are scanned the same way by
20
+ * build/origins.ts. `src` is simply the last origin in that cascade.
27
21
  *
28
22
  * `--src` names the one tree scanned this way; `--out` names where the
29
23
  * build lands. Those, plus this package's own root (for its built-in
@@ -31,18 +25,12 @@
31
25
  * other module takes the result as a plain parameter and never
32
26
  * reconstructs one of its own.
33
27
  */
34
- /** Lowercase, hyphenated — the one shape every custom element tag has, and the one native elements never do. */
35
- export declare const CUSTOM_ELEMENT_TAG: RegExp;
28
+ import type { BuiltinDirs } from "./origins.js";
29
+ export { CUSTOM_ELEMENT_TAG } from "./origins.js";
36
30
  export interface PageFile {
37
31
  name: string;
38
32
  file: string;
39
33
  }
40
- export interface WidgetSource {
41
- /** Absolute path to the `.html` definition, if this tag has one. */
42
- html?: string;
43
- /** Absolute path, without extension, to the `.ts` script, if this tag has one. */
44
- script?: string;
45
- }
46
34
  export interface PageSource {
47
35
  /** Absolute path, without extension, to the `.hooks.ts` module, if this page has one. */
48
36
  hooks?: string;
@@ -53,20 +41,11 @@ interface Discovered {
53
41
  chromeCandidates: string[];
54
42
  pages: PageFile[];
55
43
  pageSources: Map<string, PageSource>;
56
- widgets: Map<string, WidgetSource>;
57
- elementsCandidates: string[];
58
- cssFiles: string[];
44
+ importsCandidates: string[];
59
45
  }
60
46
  /** The raw walk, with no uniqueness checks — usable on its own by anything that does not need a chrome file. */
61
47
  export declare function discoverSrc(src: string): Promise<Discovered>;
62
- export interface PackageInfo {
63
- /** This package's own widgets/ — the built-in widget definitions and classes. */
64
- builtinWidgetsDir: string;
65
- /** This package's own hub/ — where lb-hub's script lives, found the same way as any built-in widget. */
66
- builtinHubDir: string;
67
- /** This package's own name, e.g. "@loadbare/app" — for naming a built-in's import specifier. */
68
- packageName: string;
69
- }
48
+ export type PackageInfo = BuiltinDirs;
70
49
  export declare function packageInfo(): Promise<PackageInfo>;
71
50
  export interface LocationArgs {
72
51
  /** The application's source tree, e.g. "src". Scanned as a whole — see this file's header. */
@@ -74,14 +53,12 @@ export interface LocationArgs {
74
53
  out: string;
75
54
  }
76
55
  export interface Locations extends PackageInfo {
56
+ srcDir: string;
77
57
  chromeFile: string;
78
58
  pages: PageFile[];
79
59
  pageSources: Map<string, PageSource>;
80
- widgets: Map<string, WidgetSource>;
81
- elementsFile?: string;
82
- cssFiles: string[];
60
+ importsFile?: string;
83
61
  outDir: string;
84
62
  }
85
63
  export declare function resolveLocations(args: LocationArgs): Promise<Locations>;
86
- export {};
87
64
  //# sourceMappingURL=locations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"locations.d.ts","sourceRoot":"","sources":["../../build/locations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAQH,gHAAgH;AAChH,eAAO,MAAM,kBAAkB,QAAkC,CAAC;AASlE,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,yFAAyF;IACzF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2FAA2F;IAC3F,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,UAAU;IAClB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACrC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACnC,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,gHAAgH;AAChH,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CA+ElE;AAED,MAAM,WAAW,WAAW;IAC1B,iFAAiF;IACjF,iBAAiB,EAAE,MAAM,CAAC;IAC1B,wGAAwG;IACxG,aAAa,EAAE,MAAM,CAAC;IACtB,gGAAgG;IAChG,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC,CAiBxD;AAED,MAAM,WAAW,YAAY;IAC3B,8FAA8F;IAC9F,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,SAAU,SAAQ,WAAW;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACrC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACnC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC,CAoD7E"}
1
+ {"version":3,"file":"locations.d.ts","sourceRoot":"","sources":["../../build/locations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAOH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAQlD,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,yFAAyF;IACzF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2FAA2F;IAC3F,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,UAAU;IAClB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACrC,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,gHAAgH;AAChH,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CA6ClE;AAED,MAAM,MAAM,WAAW,GAAG,WAAW,CAAC;AAEtC,wBAAsB,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC,CAgBxD;AAED,MAAM,WAAW,YAAY;IAC3B,8FAA8F;IAC9F,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,SAAU,SAAQ,WAAW;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC,CA6C7E"}
@@ -1,16 +1,12 @@
1
1
  /**
2
- * Finding every file a build needs by what it's named, not by which
3
- * directory it happens to sit in.
2
+ * Finding the application's own files by what they're named, not by which
3
+ * directory they happen to sit in.
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
7
  * content. `pages/` vs. `widgets/` used to carry that meaning; here a
8
- * filename suffix does instead: `*.chrome.html` is the one chrome file,
9
- * `*.page.html` is a page, and anything else `.html` is a widget
10
- * definition. A `.ts` file shaped like a custom element tag is a widget
11
- * script, found the same way regardless of where it lives. Both halves of
12
- * one widget — its markup and its class — are free to sit side by side or
13
- * apart; nothing here requires a `widgets/` directory to exist at all.
8
+ * filename suffix does instead: `chrome.html` is the one chrome file, and
9
+ * `*.page.html` is a page.
14
10
  *
15
11
  * A page's server half is named the same way: `*.hooks.ts` and
16
12
  * `*.queries.ts` pair with a `*.page.html` by sharing its basename, and are
@@ -18,12 +14,10 @@
18
14
  * neither — but one with no matching `*.page.html` is a naming mistake and
19
15
  * is rejected below.
20
16
  *
21
- * A `.css` file is not paired with anything — it is not a widget's, a
22
- * page's, or the chrome's, because build/styles.ts does not distinguish
23
- * them. Every `.css` file anywhere under `src` is concatenated, in an order
24
- * controlled entirely by the author: sorted by filename first, so a global
25
- * stylesheet sorts to the front by naming itself `00-reset.css` or similar,
26
- * regardless of which directory it lives in.
17
+ * Widget definitions, widget scripts and stylesheets are not found here.
18
+ * They arrive from more than one origin — this package's own widgets, an
19
+ * imported package, or `src` — and all three are scanned the same way by
20
+ * build/origins.ts. `src` is simply the last origin in that cascade.
27
21
  *
28
22
  * `--src` names the one tree scanned this way; `--out` names where the
29
23
  * build lands. Those, plus this package's own root (for its built-in
@@ -35,22 +29,18 @@ import { readdir, readFile } from "node:fs/promises";
35
29
  import path from "node:path";
36
30
  import { fileURLToPath } from "node:url";
37
31
  import { findPackageRoot } from "./package-root.js";
38
- /** Lowercase, hyphenated — the one shape every custom element tag has, and the one native elements never do. */
39
- export const CUSTOM_ELEMENT_TAG = /^[a-z][a-z0-9]*(-[a-z0-9]+)+$/;
40
- const CHROME_SUFFIX = ".chrome.html";
32
+ export { CUSTOM_ELEMENT_TAG } from "./origins.js";
33
+ const CHROME_FILENAME = "chrome.html";
41
34
  const PAGE_SUFFIX = ".page.html";
42
35
  const HOOKS_SUFFIX = ".hooks.ts";
43
36
  const QUERIES_SUFFIX = ".queries.ts";
44
- const CSS_SUFFIX = ".css";
45
- const ELEMENTS_FILENAME = "elements.ts";
37
+ const IMPORTS_FILENAME = "imports.ts";
46
38
  /** The raw walk, with no uniqueness checks — usable on its own by anything that does not need a chrome file. */
47
39
  export async function discoverSrc(src) {
48
40
  const chromeCandidates = [];
49
41
  const pages = [];
50
42
  const pageSources = new Map();
51
- const widgets = new Map();
52
- const elementsCandidates = [];
53
- const cssFiles = [];
43
+ const importsCandidates = [];
54
44
  let entries;
55
45
  try {
56
46
  entries = await readdir(src, { withFileTypes: true, recursive: true });
@@ -58,14 +48,6 @@ export async function discoverSrc(src) {
58
48
  catch {
59
49
  entries = [];
60
50
  }
61
- const widget = (tag) => {
62
- let w = widgets.get(tag);
63
- if (!w) {
64
- w = {};
65
- widgets.set(tag, w);
66
- }
67
- return w;
68
- };
69
51
  const pageSource = (name) => {
70
52
  let p = pageSources.get(name);
71
53
  if (!p) {
@@ -78,10 +60,10 @@ export async function discoverSrc(src) {
78
60
  if (!entry.isFile())
79
61
  continue;
80
62
  const file = path.join(entry.parentPath, entry.name);
81
- if (entry.name === ELEMENTS_FILENAME) {
82
- elementsCandidates.push(file);
63
+ if (entry.name === IMPORTS_FILENAME) {
64
+ importsCandidates.push(file);
83
65
  }
84
- else if (entry.name.endsWith(CHROME_SUFFIX)) {
66
+ else if (entry.name === CHROME_FILENAME) {
85
67
  chromeCandidates.push(file);
86
68
  }
87
69
  else if (entry.name.endsWith(PAGE_SUFFIX)) {
@@ -94,32 +76,9 @@ export async function discoverSrc(src) {
94
76
  pageSource(entry.name.slice(0, -QUERIES_SUFFIX.length)).queries =
95
77
  file.slice(0, -".ts".length);
96
78
  }
97
- else if (entry.name.endsWith(CSS_SUFFIX)) {
98
- cssFiles.push(file);
99
- }
100
- else if (entry.name.endsWith(".html")) {
101
- widget(entry.name.slice(0, -".html".length).toLowerCase()).html = file;
102
- }
103
- else if (entry.name.endsWith(".ts")) {
104
- const tag = entry.name.slice(0, -".ts".length).toLowerCase();
105
- // Without the extension: this becomes an import specifier, and esbuild
106
- // resolves ".ts" from the bare path the same way it resolves ".js".
107
- if (CUSTOM_ELEMENT_TAG.test(tag))
108
- widget(tag).script = file.slice(0, -".ts".length);
109
- }
110
79
  }
111
80
  pages.sort((a, b) => a.name.localeCompare(b.name));
112
- // By filename first, full path only to break a tie — see this file's
113
- // header. A directory a file happens to sit in never affects the order.
114
- cssFiles.sort((a, b) => path.basename(a).localeCompare(path.basename(b)) || a.localeCompare(b));
115
- return {
116
- chromeCandidates,
117
- pages,
118
- pageSources,
119
- widgets,
120
- elementsCandidates,
121
- cssFiles,
122
- };
81
+ return { chromeCandidates, pages, pageSources, importsCandidates };
123
82
  }
124
83
  export async function packageInfo() {
125
84
  const root = await findPackageRoot(path.dirname(fileURLToPath(import.meta.url)));
@@ -131,7 +90,6 @@ export async function packageInfo() {
131
90
  throw new Error(`locations: could not read '${root}/package.json': ${err.message}`);
132
91
  }
133
92
  return {
134
- builtinWidgetsDir: path.join(root, "widgets"),
135
93
  builtinHubDir: path.join(root, "hub"),
136
94
  packageName: pkg.name,
137
95
  };
@@ -141,17 +99,17 @@ export async function resolveLocations(args) {
141
99
  packageInfo(),
142
100
  discoverSrc(args.src),
143
101
  ]);
144
- const { chromeCandidates, elementsCandidates, pages, pageSources, widgets, cssFiles, } = discovered;
102
+ const { chromeCandidates, importsCandidates, pages, pageSources } = discovered;
145
103
  if (chromeCandidates.length === 0) {
146
- throw new Error(`locations: no *${CHROME_SUFFIX} file found under '${args.src}'`);
104
+ throw new Error(`locations: no ${CHROME_FILENAME} file found under '${args.src}'`);
147
105
  }
148
106
  if (chromeCandidates.length > 1) {
149
- throw new Error(`locations: more than one *${CHROME_SUFFIX} file found under '${args.src}': ` +
107
+ throw new Error(`locations: more than one ${CHROME_FILENAME} file found under '${args.src}': ` +
150
108
  chromeCandidates.join(", "));
151
109
  }
152
- if (elementsCandidates.length > 1) {
153
- throw new Error(`locations: more than one ${ELEMENTS_FILENAME} found under '${args.src}': ` +
154
- elementsCandidates.join(", "));
110
+ if (importsCandidates.length > 1) {
111
+ throw new Error(`locations: more than one ${IMPORTS_FILENAME} found under '${args.src}': ` +
112
+ importsCandidates.join(", "));
155
113
  }
156
114
  const pageNames = new Set(pages.map((p) => p.name));
157
115
  for (const name of pageSources.keys()) {
@@ -162,12 +120,11 @@ export async function resolveLocations(args) {
162
120
  }
163
121
  return {
164
122
  ...info,
123
+ srcDir: args.src,
165
124
  chromeFile: chromeCandidates[0],
166
125
  pages,
167
126
  pageSources,
168
- widgets,
169
- elementsFile: elementsCandidates[0],
170
- cssFiles,
127
+ ...(importsCandidates[0] ? { importsFile: importsCandidates[0] } : {}),
171
128
  outDir: args.out,
172
129
  };
173
130
  }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Where a build's files come from — see docs/reference/builder.md.
3
+ *
4
+ * Three kinds of file reach the assembled application: a `.html` definition
5
+ * that expands a tag, a `.ts` script that registers its class, and a `.css`
6
+ * stylesheet. All three arrive from the same places, so all three are found
7
+ * the same way: one ordered list of origins, scanned once each, read by
8
+ * whoever needs them.
9
+ *
10
+ * The order is the cascade, and it is the same for every file kind:
11
+ *
12
+ * 1. this package's own `hub/`
13
+ * 2. each package declared in `imports.ts`, in first-mention order
14
+ * 3. the application's own `--src` tree
15
+ *
16
+ * A package contributes one directory of its own choosing — `loadbare.widgets`
17
+ * in its package.json, or the whole package when it says nothing. See
18
+ * `widgetDir` below for why a package gets to answer that.
19
+ *
20
+ * The one built-in origin holds one tag, `lb-hub`, and it is there because
21
+ * every application has a hub whether or not it says so. A widget is not in
22
+ * that position: `@loadbare/widgets` is a declared package like any other,
23
+ * and the builder cannot tell it from someone else's.
24
+ *
25
+ * Two rules fall out of that list, and there is no third:
26
+ *
27
+ * - Within one origin, two files claiming the same tag are an error. That
28
+ * is a typo, and the origin's own author is the one who can fix it.
29
+ * - Across origins, the later one wins. An application always has the
30
+ * final say over a package it imported, and a package over a built-in,
31
+ * without anyone having to out-name a dependency they do not control.
32
+ */
33
+ /** Lowercase, hyphenated — the one shape every custom element tag has, and the one native elements never do. */
34
+ export declare const CUSTOM_ELEMENT_TAG: RegExp;
35
+ export interface Origin {
36
+ /** How this origin is named in an error message: a package name, or "src". */
37
+ name: string;
38
+ /** The roots to scan. */
39
+ dirs: string[];
40
+ /**
41
+ * How a script from here is imported, when a bare specifier is wanted
42
+ * instead of the scanned file's own path. Only this package's `hub/` uses
43
+ * it: `lb-hub` is the package's main entry point rather than a file an
44
+ * application should reach into.
45
+ */
46
+ specifier?: (tag: string) => string;
47
+ }
48
+ export interface OriginScan {
49
+ name: string;
50
+ specifier?: (tag: string) => string;
51
+ /** Tag to its `.html` definition. */
52
+ definitions: Map<string, string>;
53
+ /** Tag to its `.ts` script, extension stripped. */
54
+ scripts: Map<string, string>;
55
+ /** Every `.css` file here, sorted by filename with the full path breaking a tie. */
56
+ css: string[];
57
+ }
58
+ /**
59
+ * One origin's contribution, classified by filename alone.
60
+ *
61
+ * A `.html` or `.ts` file is a widget's only when its basename is a tag —
62
+ * which is what excludes `chrome.html`, `about.page.html`, `about.hooks.ts`
63
+ * and `imports.ts` here without naming any of them. Those are the
64
+ * application's own files, and build/locations.ts picks them out of `src`
65
+ * separately.
66
+ */
67
+ export declare function scanOrigin(origin: Origin): Promise<OriginScan>;
68
+ /** The shape of an `imports.ts` module's default export: the packages an application pulls widgets from. */
69
+ export type ElementManifest = string[];
70
+ /**
71
+ * `imports.ts`, executed rather than parsed — it runs through the same
72
+ * loader as the rest of the build, so it is ordinary TypeScript, not a data
73
+ * format with its own rules. It lists the packages an application pulls
74
+ * widgets from, and each one becomes an origin, scanned exactly as `src` is.
75
+ */
76
+ export declare function loadElementManifest(file: string): Promise<ElementManifest>;
77
+ /**
78
+ * The ordinary node_modules walk, stopping at the first directory that has
79
+ * the package installed — not a full resolution of an entry file, on
80
+ * purpose: this only needs the package's root directory, and stopping short
81
+ * avoids caring whether the package exposes an "exports" map at all. Which
82
+ * part of that root holds the widgets is the package's own answer to give —
83
+ * `widgetDir` asks it.
84
+ */
85
+ export declare function findInstalledPackage(packageName: string, fromDir: string): Promise<string>;
86
+ export interface BuiltinDirs {
87
+ /** This package's own hub/ — where lb-hub's script lives, found the same way as any widget. */
88
+ builtinHubDir: string;
89
+ /** This package's own name, e.g. "@loadbare/app" — for naming the built-in's import specifier. */
90
+ packageName: string;
91
+ }
92
+ /**
93
+ * The cascade, in order. A declared package is an origin like any other: the
94
+ * directory it points `loadbare.widgets` at is scanned the way `src` is — its
95
+ * whole installed directory, when it points at nothing — and a widget there is
96
+ * found by its filename, not by being named in a manifest.
97
+ *
98
+ * `hub/` is the only origin naming a specifier, because `lb-hub` is the
99
+ * package's main entry point rather than a file to reach into.
100
+ */
101
+ export declare function buildOrigins(builtin: BuiltinDirs, declared: ElementManifest, src: string): Promise<Origin[]>;
102
+ /**
103
+ * The whole cascade, scanned. Everything a build reads from disk that is not
104
+ * a page, the chrome, or a page's server half comes back from here.
105
+ */
106
+ export declare function scanAll(builtin: BuiltinDirs, src: string, importsFile?: string): Promise<OriginScan[]>;
107
+ /** Every origin's stylesheets, concatenated in cascade order. */
108
+ export declare function cssFrom(scans: OriginScan[]): string[];
109
+ //# sourceMappingURL=origins.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"origins.d.ts","sourceRoot":"","sources":["../../build/origins.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAOH,gHAAgH;AAChH,eAAO,MAAM,kBAAkB,QAAkC,CAAC;AAMlE,MAAM,WAAW,MAAM;IACrB,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf;;;;;OAKG;IACH,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;CACrC;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,qCAAqC;IACrC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,mDAAmD;IACnD,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,oFAAoF;IACpF,GAAG,EAAE,MAAM,EAAE,CAAC;CACf;AAsBD;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CA4DpE;AAED,4GAA4G;AAC5G,MAAM,MAAM,eAAe,GAAG,MAAM,EAAE,CAAC;AAEvC;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,eAAe,CAAC,CA4B1B;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CAkBjB;AAwED,MAAM,WAAW,WAAW;IAC1B,+FAA+F;IAC/F,aAAa,EAAE,MAAM,CAAC;IACtB,kGAAkG;IAClG,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,eAAe,EACzB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,EAAE,CAAC,CAkBnB;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,WAAW,EACpB,GAAG,EAAE,MAAM,EACX,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,UAAU,EAAE,CAAC,CAIvB;AAED,iEAAiE;AACjE,wBAAgB,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,EAAE,CAErD"}