@loadbare/app 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build/skills-cli.d.ts +12 -0
- package/dist/build/skills-cli.d.ts.map +1 -0
- package/dist/build/skills-cli.js +81 -0
- package/dist/build/skills-cli.js.map +1 -0
- package/dist/build/skills.d.ts +47 -0
- package/dist/build/skills.d.ts.map +1 -0
- package/dist/build/skills.js +124 -0
- package/dist/build/skills.js.map +1 -0
- package/dist/core/lb-constants.d.ts +2 -0
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +18 -4
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/hub/lb-apply.d.ts +10 -0
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +15 -1
- package/dist/hub/lb-apply.js.map +1 -1
- package/dist/hub/lb-hub.browser.d.ts.map +1 -1
- package/dist/hub/lb-hub.browser.js +116 -35
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/dist/server/lb-express.d.ts +13 -5
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +26 -7
- package/dist/server/lb-express.js.map +1 -1
- package/dist/server/lb-server.d.ts +3 -0
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js.map +1 -1
- package/docs/TECHREF-1.0.md +93 -22
- package/docs/comparison.md +8 -7
- package/docs/reference/chrome.md +5 -3
- package/docs/reference/data-binding.md +28 -0
- package/docs/reference/server.md +11 -0
- package/docs/reference/widgets.md +8 -4
- package/docs/roadmap.md +16 -0
- package/docs/theory.md +8 -1
- package/docs/tutorials/010-pages-and-navigation.md +2 -1
- package/package.json +8 -4
- package/skills/loadbare-app/SKILL.md +275 -0
- package/skills/loadbare-app/references/TECHREF-1.0.md +1260 -0
- package/skills/loadbare-app/references/builder.md +134 -0
- package/skills/loadbare-app/references/chrome.md +160 -0
- package/skills/loadbare-app/references/css.md +44 -0
- package/skills/loadbare-app/references/custom-elements.md +397 -0
- package/skills/loadbare-app/references/data-binding.md +485 -0
- package/skills/loadbare-app/references/overview.md +38 -0
- package/skills/loadbare-app/references/page-files.md +194 -0
- package/skills/loadbare-app/references/server.md +153 -0
- package/skills/loadbare-app/references/widgets.md +178 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# The Builder
|
|
2
|
+
|
|
3
|
+
The application runs `loadbare-app-build`. The builder reads the application's
|
|
4
|
+
source tree and writes the files the server serves.
|
|
5
|
+
|
|
6
|
+
## Running the builder
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
loadbare-app-build [--src src] [--out dist] [--watch] [--minify]
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
| Option | Effect |
|
|
13
|
+
|------------|------------------------------------------------------|
|
|
14
|
+
| `--src` | The tree to scan, default `src` |
|
|
15
|
+
| `--out` | Where output lands, default `dist` |
|
|
16
|
+
| `--watch` | Build once, then again on every change under `--src` |
|
|
17
|
+
| `--minify` | Minify `client.js` and `app.css` |
|
|
18
|
+
|
|
19
|
+
Run it from the application's own `package.json`:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"scripts": {
|
|
24
|
+
"dev": "loadbare-app-build --watch & tsx server.ts"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`--watch` watches every `.html`, `.ts`, and `.css` file under `--src`, and
|
|
30
|
+
reports a failed build to the console without stopping. It runs no dev server
|
|
31
|
+
and reloads no browser.
|
|
32
|
+
|
|
33
|
+
`--minify` leaves `app.html` alone.
|
|
34
|
+
|
|
35
|
+
## What the builder reads
|
|
36
|
+
|
|
37
|
+
The builder classifies by name, not location.
|
|
38
|
+
|
|
39
|
+
| Name | Role |
|
|
40
|
+
|----------------------------|---------------------------------------------------|
|
|
41
|
+
| `chrome.html` | The chrome — see [`chrome.html`](./chrome.md) |
|
|
42
|
+
| `*.page.html` | A page |
|
|
43
|
+
| `*.requests.ts` | A page's requests, matched by base name |
|
|
44
|
+
| `*.queries.ts` | A page's queries, matched by base name |
|
|
45
|
+
| `imports.ts` | The packages this app takes widgets from |
|
|
46
|
+
| `*.css` | A stylesheet — see [CSS](./css.md) |
|
|
47
|
+
| Any other `.html` | A widget definition, named for the tag it defines |
|
|
48
|
+
| `<tag>.browser.ts` | A widget script, named for the tag it registers |
|
|
49
|
+
|
|
50
|
+
Give the application exactly one `chrome.html` and at most one `imports.ts`.
|
|
51
|
+
Give every `.requests.ts` and `.queries.ts` a `.page.html` of the same base name.
|
|
52
|
+
|
|
53
|
+
Name a widget script `<tag>.browser.ts`, not `<tag>.ts`. Only a file whose
|
|
54
|
+
name carries `.browser` is bundled for the browser; every other module under
|
|
55
|
+
`--src` stays on the server side of the build, whatever it is called. Write
|
|
56
|
+
`<tag>.ts` and the tag goes unregistered — the build says so, and names the
|
|
57
|
+
file to rename.
|
|
58
|
+
|
|
59
|
+
## Widgets from packages
|
|
60
|
+
|
|
61
|
+
List a package in `imports.ts` to take widgets from it:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
// src/imports.ts
|
|
65
|
+
export default ["@acme/widgets"];
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The builder scans one directory of that package the same way it scans `--src`,
|
|
69
|
+
finding a widget by its filename. Nothing names a tag: `acme-widget.html` and
|
|
70
|
+
`acme-widget.js` there define and register `<acme-widget>`.
|
|
71
|
+
|
|
72
|
+
Which directory is the package's own to declare, in its `package.json`:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"loadbare": { "widgets": "./dist" }
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
A package that declares nothing has its whole installed directory scanned.
|
|
81
|
+
That is what a package of hand-written widgets wants and it needs no field at
|
|
82
|
+
all; a package that compiles wants the field, because its source and its
|
|
83
|
+
compiled output both carry `acme-widget`, and two files claiming one tag in one
|
|
84
|
+
origin is an error. Declaring the directory that ships settles which one the
|
|
85
|
+
builder means, and leaves the rest of the package — the README, the docs, the
|
|
86
|
+
tests — out of the question entirely.
|
|
87
|
+
|
|
88
|
+
The path must be inside the package and must exist in it once installed;
|
|
89
|
+
either failure names the package and stops the build.
|
|
90
|
+
|
|
91
|
+
An application author writes nothing for this and installs the package as they
|
|
92
|
+
would any other. The declaration is the package author's, made once.
|
|
93
|
+
|
|
94
|
+
## Where the builder looks
|
|
95
|
+
|
|
96
|
+
Definitions, scripts and stylesheets all come from the same ordered origins:
|
|
97
|
+
|
|
98
|
+
| Order | Origin |
|
|
99
|
+
|-------|----------------------------------------------------|
|
|
100
|
+
| 1 | `@loadbare/app` itself, which supplies `lb-hub` |
|
|
101
|
+
| 2 | Each package in `imports.ts`, in the order listed |
|
|
102
|
+
| 3 | The application's own `--src` tree |
|
|
103
|
+
|
|
104
|
+
The first origin holds one tag. Every application has a hub whether or not it
|
|
105
|
+
says so, so the builder supplies that one and nothing else; a widget comes
|
|
106
|
+
from a package the application lists, including
|
|
107
|
+
[`@loadbare/widgets`](./widgets.md).
|
|
108
|
+
|
|
109
|
+
Two files claiming one tag within a single origin is an error, naming both.
|
|
110
|
+
Across origins the later one wins, so the application overrides a package,
|
|
111
|
+
and a package overrides `lb-hub`. Stylesheets follow the same order — see
|
|
112
|
+
[CSS](./css.md).
|
|
113
|
+
|
|
114
|
+
A tag with neither a script nor a definition in any origin is an error.
|
|
115
|
+
|
|
116
|
+
## What the builder writes
|
|
117
|
+
|
|
118
|
+
| File | Contents |
|
|
119
|
+
|-------------|-----------------------------------------------------|
|
|
120
|
+
| `app.html` | The chrome, with every page inside a `<template>` |
|
|
121
|
+
| `client.js` | Every widget class the document uses, in one bundle |
|
|
122
|
+
| `app.css` | Every stylesheet, concatenated |
|
|
123
|
+
| `pages.ts` | The `hub` the server passes to `hubRoutes` |
|
|
124
|
+
|
|
125
|
+
The builder expands the chrome and every page against the available widget
|
|
126
|
+
definitions — see [Custom Elements](./custom-elements.md#html) for the
|
|
127
|
+
substitution rules — wraps each expanded page in
|
|
128
|
+
`<template lb-page="<name>">`, and splices them into the chrome's `<body>`. It formats the result with
|
|
129
|
+
Prettier when the application has it installed.
|
|
130
|
+
|
|
131
|
+
The builder writes `app.css` only when it finds a stylesheet, and `pages.ts`
|
|
132
|
+
only when some page has a `.requests.ts` or a `.queries.ts` file. Import `hub`
|
|
133
|
+
from `pages.ts` — see [The Express Server](./server.md) for the rest of the
|
|
134
|
+
wiring.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# chrome.html
|
|
2
|
+
|
|
3
|
+
The chrome is the application's one HTML document. Banner, navigation,
|
|
4
|
+
footer, dialogs — everything that is not a page lives in the chrome, written
|
|
5
|
+
once.
|
|
6
|
+
|
|
7
|
+
The builder finds the chrome by name: exactly one file called `chrome.html`,
|
|
8
|
+
anywhere in the `src/` tree. Any directory will do.
|
|
9
|
+
|
|
10
|
+
## A complete chrome
|
|
11
|
+
|
|
12
|
+
Here is a minimal but fully complaint chrome for a typical app:
|
|
13
|
+
|
|
14
|
+
```html
|
|
15
|
+
<!-- src/chrome.html -->
|
|
16
|
+
<!doctype html>
|
|
17
|
+
<html lang="en">
|
|
18
|
+
<head>
|
|
19
|
+
<meta charset="utf-8" />
|
|
20
|
+
<title>Membership Roster</title>
|
|
21
|
+
<script src="/client.js" defer></script>
|
|
22
|
+
<link rel="stylesheet" href="/app.css" />
|
|
23
|
+
<noscript><style>body[hidden] { display: block; }</style></noscript>
|
|
24
|
+
</head>
|
|
25
|
+
<body hidden>
|
|
26
|
+
<lb-hub>
|
|
27
|
+
<header lb-row="lb-navigation">
|
|
28
|
+
<h1>Membership Roster</h1>
|
|
29
|
+
<h2 lb-cell="page-label"></h2>
|
|
30
|
+
</header>
|
|
31
|
+
<nav>
|
|
32
|
+
<a href="/" lb-nav-link>Home</a>
|
|
33
|
+
<a href="/members" lb-nav-link>Members</a>
|
|
34
|
+
<a href="https://example.org/">Our website</a>
|
|
35
|
+
</nav>
|
|
36
|
+
<main></main>
|
|
37
|
+
<dialog lb-unknown-page lb-row="lb-navigation">
|
|
38
|
+
The URL <span lb-cell="page-uri"></span> is not in this app.
|
|
39
|
+
</dialog>
|
|
40
|
+
</lb-hub>
|
|
41
|
+
</body>
|
|
42
|
+
</html>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The chrome is plain HTML; nothing in it is generated or templated.
|
|
46
|
+
|
|
47
|
+
## The parts of a chrome
|
|
48
|
+
|
|
49
|
+
| Required | Description |
|
|
50
|
+
|-------------------------------------|-------------------------------------------|
|
|
51
|
+
| Exactly one `chrome.html` in `src/` | The one chrome file, found by name |
|
|
52
|
+
| A complete HTML document | Doctype, `<html>`, `<head>`, `<body>` |
|
|
53
|
+
| `<lb-hub>` inside `<body>` | The application's live element |
|
|
54
|
+
| An empty `<main>` inside `<lb-hub>` | Holds the current page |
|
|
55
|
+
| `<script src="/client.js" defer>` | Defines `<lb-hub>` and every other widget |
|
|
56
|
+
|
|
57
|
+
Everything else is optional:
|
|
58
|
+
|
|
59
|
+
| Optional | Description |
|
|
60
|
+
|-------------------------------------------|---------------------------------------------|
|
|
61
|
+
| `<link rel="stylesheet" href="/app.css">` | The bundled stylesheet |
|
|
62
|
+
| `<a lb-nav-link>` | Navigation between pages |
|
|
63
|
+
| `lb-row="lb-navigation"` | Where the page is — see below |
|
|
64
|
+
| `<dialog lb-unknown-page>` | A message when a URL matches no page |
|
|
65
|
+
| Custom elements | The chrome, decomposed into widget files |
|
|
66
|
+
| `<body hidden>` + a `<noscript>` fallback | Avoids a first-load blink — see below |
|
|
67
|
+
| Any other HTML | Header, footer, skip links, meta tags, etc. |
|
|
68
|
+
|
|
69
|
+
## Rules for writing chrome
|
|
70
|
+
|
|
71
|
+
Anything the user interacts with must be inside `<lb-hub>`. The hub normally
|
|
72
|
+
sits directly inside `<body>`, with banner, nav, footer and `<main>` inside
|
|
73
|
+
it, so Loadbare can act on all of them.
|
|
74
|
+
|
|
75
|
+
A navigation anchor's `href` is a path, and the path names a page:
|
|
76
|
+
`/members` shows `members.page.html`. A query string on it is kept, so
|
|
77
|
+
`/members?team=Engines` opens the page narrowed; see
|
|
78
|
+
[Query parms](./data-binding.md#query-parms). A bare `/` resolves to `index`, so the
|
|
79
|
+
landing page is the one named `index.page.html`. An anchor without
|
|
80
|
+
`lb-nav-link` is left alone and behaves like any other link.
|
|
81
|
+
|
|
82
|
+
The `lb-unknown-page` attribute, if used, must appear on a `<dialog>` inside
|
|
83
|
+
`<lb-hub>`; the builder rejects it anywhere else. The hub opens it when a
|
|
84
|
+
path names no page. What it says is up to the chrome:
|
|
85
|
+
the dialog is a subtree like any other, and it displays where the page is by
|
|
86
|
+
naming the hub's own query, described next.
|
|
87
|
+
|
|
88
|
+
## Where the page is
|
|
89
|
+
|
|
90
|
+
On every navigation the hub lands a query of its own, `lb-navigation`, on
|
|
91
|
+
any subtree inside the hub that names it. It arrives the way a server's
|
|
92
|
+
row arrives — `lb-row` on the subtree, `lb-cell` on each element that
|
|
93
|
+
shows a value — so a chrome displays the current page with no code at all:
|
|
94
|
+
|
|
95
|
+
```html
|
|
96
|
+
<header lb-row="lb-navigation">
|
|
97
|
+
<h1>Membership Roster</h1>
|
|
98
|
+
<h2 lb-cell="page-label"></h2>
|
|
99
|
+
</header>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
| Cell | Holds |
|
|
103
|
+
|--------------|-----------------------------------------------------------|
|
|
104
|
+
| `page-label` | The text of the `lb-nav-link` anchor for the path, or empty if none |
|
|
105
|
+
| `page-uri` | The path and query string as the browser has them, such as `/members?team=Engines` |
|
|
106
|
+
|
|
107
|
+
The label is the nav's. The hub takes it from the first `lb-nav-link`
|
|
108
|
+
anchor whose path names the current page, whatever its query string, so a click, a reload and the
|
|
109
|
+
back button all land the same text, and a path no anchor names lands an
|
|
110
|
+
empty label. A chrome that shows the label somewhere fixed should expect
|
|
111
|
+
that case for a page reachable only by URL.
|
|
112
|
+
|
|
113
|
+
The `lb-` prefix on the query name is what keeps it out of the server's
|
|
114
|
+
namespace: no server answers a query so named. It is landed only where a
|
|
115
|
+
subtree names it, so a chrome that displays no navigation is not warned
|
|
116
|
+
about a query with no scope.
|
|
117
|
+
|
|
118
|
+
The same row is what an unknown-page dialog has to work with. It lands
|
|
119
|
+
before the page host is looked up, so a miss has it too. Name the query on
|
|
120
|
+
the dialog and show whichever cell fits:
|
|
121
|
+
|
|
122
|
+
```html
|
|
123
|
+
<dialog lb-unknown-page lb-row="lb-navigation">
|
|
124
|
+
The URL <span lb-cell="page-uri"></span> is not in this app.
|
|
125
|
+
</dialog>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`@loadbare/widgets` ships this dialog as a widget, `<lb-unknown-page>`, for a
|
|
129
|
+
chrome that would rather write one tag — see
|
|
130
|
+
[The Basic Widget Library](./widgets.md#lb-unknown-page).
|
|
131
|
+
|
|
132
|
+
## Preventing the first-load blink
|
|
133
|
+
|
|
134
|
+
`client.js` loads with `defer`, so the browser can — and typically does —
|
|
135
|
+
paint the document before the script has run. Without help, that means a
|
|
136
|
+
visible flash: the chrome appears first, then `<main>`'s real content pops in
|
|
137
|
+
a moment later and shifts everything around it.
|
|
138
|
+
|
|
139
|
+
`<body hidden>` avoids this by hiding the whole document, not just `<main>`,
|
|
140
|
+
until the hub has something to show. `<lb-hub>` un-hides `<body>` itself the
|
|
141
|
+
first time `navigate()` finishes, so the chrome and the first page's content
|
|
142
|
+
always appear together, already in their final layout — there is no
|
|
143
|
+
intermediate state to flash.
|
|
144
|
+
|
|
145
|
+
Hiding `<main>` alone doesn't work: an empty `<main>` already renders at zero
|
|
146
|
+
height, so hiding it changes nothing visible. The pop-in comes from the
|
|
147
|
+
chrome being shown *before* `<main>` has real content, not from `<main>`
|
|
148
|
+
being visibly empty — so it's the whole document that needs to wait, not
|
|
149
|
+
just the piece that was empty.
|
|
150
|
+
|
|
151
|
+
The `<noscript>` block is the escape hatch for a visitor with JavaScript
|
|
152
|
+
disabled. `<lb-hub>` is what removes `hidden` from `<body>`, so a browser
|
|
153
|
+
that never runs `client.js` would otherwise be stuck looking at a
|
|
154
|
+
permanently blank page. `<noscript>` content is only rendered when scripting
|
|
155
|
+
is off, so the fallback rule only ever applies in exactly that case — it
|
|
156
|
+
never runs, and never races with the hub, on a normal visit.
|
|
157
|
+
|
|
158
|
+
Both are optional. An app that doesn't mind the blink, or has no chrome
|
|
159
|
+
complex enough for it to be noticeable, can leave `<body>` unhidden and skip
|
|
160
|
+
the `<noscript>` block entirely.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# CSS
|
|
2
|
+
|
|
3
|
+
The builder concatenates every `.css` file it finds into one stylesheet,
|
|
4
|
+
`<out>/app.css`. Nothing is scoped, renamed, or removed.
|
|
5
|
+
|
|
6
|
+
Write ordinary CSS against ordinary markup. Loadbare uses light DOM, so
|
|
7
|
+
every selector reaches every element, in the chrome and in a widget alike.
|
|
8
|
+
|
|
9
|
+
Put stylesheets anywhere under `src/`. The builder finds them by extension
|
|
10
|
+
and pairs them with nothing — a `.css` file is not a widget's, a page's, or
|
|
11
|
+
the chrome's.
|
|
12
|
+
|
|
13
|
+
## Order
|
|
14
|
+
|
|
15
|
+
The bundle is assembled in partitions, one per origin:
|
|
16
|
+
|
|
17
|
+
| Partition | Holds |
|
|
18
|
+
|----------------------------------|-------------------------------------------|
|
|
19
|
+
| Each package in `imports.ts` | Every `.css` file inside that package |
|
|
20
|
+
| The application's own `src` tree | Every `.css` file under `--src` |
|
|
21
|
+
|
|
22
|
+
Package partitions come first, in the order `imports.ts` first mentions each
|
|
23
|
+
package. The application's own partition is always last, so its rules have
|
|
24
|
+
the final say over any imported package's.
|
|
25
|
+
|
|
26
|
+
A package's partition excludes that package's own nested `node_modules`.
|
|
27
|
+
|
|
28
|
+
Within a partition, files sort by filename. The directory does not affect
|
|
29
|
+
the order, and the full path only breaks a tie. Name a global stylesheet
|
|
30
|
+
`00-reset.css`, or similar, to sort it to the front of its partition.
|
|
31
|
+
|
|
32
|
+
## What ships
|
|
33
|
+
|
|
34
|
+
The builder writes `app.css` only when it finds at least one stylesheet.
|
|
35
|
+
Serve it at `/app.css` and link it from the chrome:
|
|
36
|
+
|
|
37
|
+
```html
|
|
38
|
+
<link rel="stylesheet" href="/app.css" />
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
See [`chrome.html`](./chrome.md) for the link, [The Express
|
|
42
|
+
server](./server.md) for the route, and
|
|
43
|
+
[`loadbare-app-build`](./builder.md#running-the-builder) for `--minify`.
|
|
44
|
+
</content>
|