generator-white-label 9.0.0 → 10.0.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/CLI.md +13 -17
- package/EXISTING_APPLICATIONS.md +155 -0
- package/PACKAGE_MANAGERS.md +6 -4
- package/README.md +135 -153
- package/app/404.ts +28 -0
- package/app/README.md +19 -7
- package/{scaffold/no-jsx/app → app}/assets/script/index.ts +1 -0
- package/{scaffold/no-jsx/app → app}/assets/script/tasks/TaskView.ts +1 -1
- package/app/assets/view/CodeBlock.ts +15 -0
- package/app/assets/view/examples/tasks/TaskExample.ts +50 -0
- package/app/assets/view/examples/tasks/task-state.ts +1 -1
- package/app/assets/view/layout/SiteFooter.ts +11 -0
- package/app/assets/view/layout/SiteHeader.ts +17 -0
- package/app/assets/view/page-data.ts +2 -2
- package/app/assets/view/sections/GettingStartedSection.ts +26 -0
- package/app/assets/view/sections/HeroSection.ts +15 -0
- package/app/assets/view/sections/LiveExampleSection.ts +24 -0
- package/app/assets/view/sections/PackageDocsSection.ts +69 -0
- package/app/assets/view/sections/{SourceGuideSection.tsx → SourceGuideSection.ts} +11 -16
- package/app/index.ts +50 -0
- package/cli/index.ts +4 -41
- package/dist/app/404.d.ts +1 -1
- package/dist/app/404.js +18 -2
- package/dist/app/404.js.map +1 -1
- package/dist/app/assets/script/index.d.ts +1 -6
- package/dist/app/assets/script/index.js +1 -6
- package/dist/app/assets/script/index.js.map +1 -1
- package/dist/app/assets/script/tasks/TaskView.d.ts +1 -6
- package/dist/app/assets/script/tasks/TaskView.js +2 -8
- package/dist/app/assets/script/tasks/TaskView.js.map +1 -1
- package/dist/app/assets/view/CodeBlock.d.ts +3 -3
- package/dist/app/assets/view/CodeBlock.js +4 -2
- package/dist/app/assets/view/CodeBlock.js.map +1 -1
- package/dist/app/assets/view/examples/tasks/TaskExample.d.ts +2 -8
- package/dist/app/assets/view/examples/tasks/TaskExample.js +41 -9
- package/dist/app/assets/view/examples/tasks/TaskExample.js.map +1 -1
- package/dist/app/assets/view/examples/tasks/task-state.d.ts +1 -1
- package/dist/app/assets/view/examples/tasks/task-state.js.map +1 -1
- package/dist/app/assets/view/layout/SiteFooter.d.ts +2 -2
- package/dist/app/assets/view/layout/SiteFooter.js +8 -3
- package/dist/app/assets/view/layout/SiteFooter.js.map +1 -1
- package/dist/app/assets/view/layout/SiteHeader.d.ts +2 -8
- package/dist/app/assets/view/layout/SiteHeader.js +14 -9
- package/dist/app/assets/view/layout/SiteHeader.js.map +1 -1
- package/dist/app/assets/view/page-data.d.ts +2 -2
- package/dist/app/assets/view/page-data.js.map +1 -1
- package/dist/app/assets/view/sections/GettingStartedSection.d.ts +1 -1
- package/dist/app/assets/view/sections/GettingStartedSection.js +21 -6
- package/dist/app/assets/view/sections/GettingStartedSection.js.map +1 -1
- package/dist/app/assets/view/sections/HeroSection.d.ts +2 -8
- package/dist/app/assets/view/sections/HeroSection.js +12 -9
- package/dist/app/assets/view/sections/HeroSection.js.map +1 -1
- package/dist/app/assets/view/sections/LiveExampleSection.d.ts +2 -6
- package/dist/app/assets/view/sections/LiveExampleSection.js +17 -7
- package/dist/app/assets/view/sections/LiveExampleSection.js.map +1 -1
- package/dist/app/assets/view/sections/PackageDocsSection.d.ts +1 -1
- package/dist/app/assets/view/sections/PackageDocsSection.js +57 -20
- package/dist/app/assets/view/sections/PackageDocsSection.js.map +1 -1
- package/dist/app/assets/view/sections/SourceGuideSection.d.ts +2 -7
- package/dist/app/assets/view/sections/SourceGuideSection.js +20 -8
- package/dist/app/assets/view/sections/SourceGuideSection.js.map +1 -1
- package/dist/app/index.d.ts +2 -8
- package/dist/app/index.js +36 -11
- package/dist/app/index.js.map +1 -1
- package/dist/cli/index.d.ts +0 -3
- package/dist/cli/index.js +4 -36
- package/dist/cli/index.js.map +1 -1
- package/dist/scaffold/index.d.ts +2 -13
- package/dist/scaffold/index.js +17 -53
- package/dist/scaffold/index.js.map +1 -1
- package/dist/scripts/build.js +97 -28
- package/dist/scripts/build.js.map +1 -1
- package/dist/server/handler.js +2 -9
- package/dist/server/handler.js.map +1 -1
- package/package.json +22 -22
- package/pnpm-workspace.yaml +0 -1
- package/scaffold/AGENTS.md +13 -8
- package/scaffold/index.ts +17 -57
- package/scripts/build.ts +90 -30
- package/server/handler.ts +2 -10
- package/tsconfig.site.json +1 -4
- package/app/404.tsx +0 -28
- package/app/assets/script/index.tsx +0 -13
- package/app/assets/script/tasks/TaskView.tsx +0 -24
- package/app/assets/view/CodeBlock.tsx +0 -17
- package/app/assets/view/examples/tasks/TaskExample.tsx +0 -63
- package/app/assets/view/layout/SiteFooter.tsx +0 -11
- package/app/assets/view/layout/SiteHeader.tsx +0 -23
- package/app/assets/view/sections/GettingStartedSection.tsx +0 -27
- package/app/assets/view/sections/HeroSection.tsx +0 -21
- package/app/assets/view/sections/LiveExampleSection.tsx +0 -29
- package/app/assets/view/sections/PackageDocsSection.tsx +0 -69
- package/app/index.tsx +0 -54
- package/scaffold/no-jsx/README.md +0 -73
- package/scaffold/no-jsx/app/404.ts +0 -10
- package/scaffold/no-jsx/app/assets/view/examples/tasks/TaskExample.ts +0 -39
- package/scaffold/no-jsx/app/index.ts +0 -58
- package/tsconfig.site.no-jsx.json +0 -24
package/README.md
CHANGED
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
# generator-white-label
|
|
2
2
|
|
|
3
|
-
`generator-white-label` is a framework-independent TypeScript project generator for small, accessible, SEO-friendly sites with progressive enhancement, composable White Label primitives, and a provider-neutral Node.js function example.
|
|
3
|
+
`generator-white-label` is a framework-independent TypeScript project generator for small, accessible, SEO-friendly, HTML-first sites with progressive enhancement, composable White Label primitives, first-party tagged HTML templates, and a provider-neutral Node.js function example.
|
|
4
4
|
|
|
5
5
|
[Documentation](https://whitelabeljs.org/docs/generator/) · [API reference](https://whitelabeljs.org/api/#generator) · [Demo site](https://whitelabeljs.org/)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
White Label does not prescribe a framework. It provides focused pieces that can be composed where useful and omitted where they are not.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
understand → see → build → verify
|
|
11
|
-
```
|
|
9
|
+
## Where the generator fits
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
Use the generator when starting a **new** site or small web application and you want:
|
|
12
|
+
|
|
13
|
+
- meaningful static HTML before client JavaScript runs;
|
|
14
|
+
- progressive enhancement instead of an SPA requirement;
|
|
15
|
+
- explicit Model/View/Mediator/Router boundaries;
|
|
16
|
+
- ordinary TypeScript with readable HTML-shaped templates;
|
|
17
|
+
- accessibility and search-oriented defaults;
|
|
18
|
+
- a small codebase that remains easy for humans and coding agents to inspect;
|
|
19
|
+
- provider-neutral Web `Request`/`Response` serverless composition.
|
|
20
|
+
|
|
21
|
+
The generated project is especially well suited to public sites, documentation/content experiences, agency or multi-client work, small product/account surfaces, and teams that want application structure without handing architecture to a full framework.
|
|
22
|
+
|
|
23
|
+
### Already have an application?
|
|
24
|
+
|
|
25
|
+
Do **not** use the generator to layer a scaffold over it. The generator intentionally rejects a non-empty destination.
|
|
26
|
+
|
|
27
|
+
Existing server-rendered, CMS, commerce, Rails/PHP/Java/.NET, or long-lived frontend applications should install the individual White Label runtime packages they need and adopt them incrementally. See [`EXISTING_APPLICATIONS.md`](EXISTING_APPLICATIONS.md) and the public [incremental server-rendered application guide](https://whitelabeljs.org/guides/incremental-javascript-for-server-rendered-apps/).
|
|
14
28
|
|
|
15
29
|
## The idea
|
|
16
30
|
|
|
@@ -30,21 +44,61 @@ URL / user action
|
|
|
30
44
|
DOM
|
|
31
45
|
```
|
|
32
46
|
|
|
33
|
-
These are responsibilities, not mandatory layers. Static content can stay plain
|
|
47
|
+
These are responsibilities, not mandatory layers. Static content can stay a plain TypeScript function that returns tagged HTML. A feature that does not need routing does not need a router; a feature that only needs local state can use Model by itself.
|
|
34
48
|
|
|
35
49
|
- [`white-label-router`](https://github.com/bshack/white-label-router) turns location into application intent.
|
|
36
50
|
- [`white-label-mediator`](https://github.com/bshack/white-label-mediator) coordinates application events.
|
|
37
51
|
- [`white-label-model`](https://github.com/bshack/white-label-model) owns observable state.
|
|
38
52
|
- [`white-label-view`](https://github.com/bshack/white-label-view) owns rendering and DOM lifecycle.
|
|
39
|
-
-
|
|
53
|
+
- `white-label-view/html` provides the first-party tagged-template helpers used by the generator.
|
|
40
54
|
|
|
41
55
|
The goal is simple boundaries with explicit composition.
|
|
42
56
|
|
|
57
|
+
## Tagged HTML templates
|
|
58
|
+
|
|
59
|
+
Generated pages and View templates are ordinary `.ts` functions:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import {html} from 'white-label-view/html';
|
|
63
|
+
|
|
64
|
+
export default function Page(data: {title: string}) {
|
|
65
|
+
return html`
|
|
66
|
+
<main>
|
|
67
|
+
<h1>${data.title}</h1>
|
|
68
|
+
</main>
|
|
69
|
+
`;
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Normal text and quoted-attribute interpolations are escaped. Tagged markup composes without double escaping, so small view functions can remain reusable without introducing a component runtime.
|
|
74
|
+
|
|
75
|
+
Conditional or grouped opening-tag attributes use `attributes()`:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import {attributes, html} from 'white-label-view/html';
|
|
79
|
+
|
|
80
|
+
const input = html`
|
|
81
|
+
<input ${attributes({
|
|
82
|
+
type: 'checkbox',
|
|
83
|
+
checked: complete,
|
|
84
|
+
'data-task-id': taskId
|
|
85
|
+
})}>
|
|
86
|
+
`;
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`unsafeHTML()` is deliberately explicit. Use it only for application-owned content that is already trusted or sanitized, such as controlled JSON-LD after applying the application's serialization policy. It is not a sanitizer and it is not a replacement for contextual URL/CSS validation.
|
|
90
|
+
|
|
91
|
+
The tagged-template runtime rejects interpolation in ambiguous or dangerous contexts such as script/style bodies, comments, tag names, and unquoted attribute positions rather than pretending generic escaping makes those contexts safe.
|
|
92
|
+
|
|
93
|
+
White Label View remains template-engine agnostic. JSX, Handlebars, Eta, EJS, Mustache, Nunjucks, Pug, and other renderers can remain application dependencies and return their rendered output from View's `template` function. JSX is a tested third-party option rather than a White Label-owned runtime.
|
|
94
|
+
|
|
95
|
+
See [Template engines](https://whitelabeljs.org/docs/view/#template-engines) for tested integrations and trust boundaries.
|
|
96
|
+
|
|
43
97
|
## Learn from the generated application
|
|
44
98
|
|
|
45
|
-
The generated landing page demonstrates the same architecture
|
|
99
|
+
The project is also a teaching tool. The generated landing page demonstrates the same architecture its source and tests document.
|
|
46
100
|
|
|
47
|
-
|
|
101
|
+
The task example shows all four runtime packages working together:
|
|
48
102
|
|
|
49
103
|
```text
|
|
50
104
|
TaskRouter
|
|
@@ -54,59 +108,19 @@ TaskMediator
|
|
|
54
108
|
TaskModel
|
|
55
109
|
↓ observable state
|
|
56
110
|
TaskView
|
|
57
|
-
↓ render
|
|
111
|
+
↓ tagged HTML render
|
|
58
112
|
DOM
|
|
59
113
|
```
|
|
60
114
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
## A simple View click event
|
|
64
|
-
|
|
65
|
-
Use View lifecycle hooks to add and remove browser listeners with the same callback reference:
|
|
66
|
-
|
|
67
|
-
```ts
|
|
68
|
-
import View from 'white-label-view';
|
|
69
|
-
|
|
70
|
-
class ButtonView extends View {
|
|
71
|
-
handleClick = () => {
|
|
72
|
-
console.log('Clicked');
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
addListeners() {
|
|
76
|
-
this.element.addEventListener('click', this.handleClick);
|
|
77
|
-
return this;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
removeListeners() {
|
|
81
|
-
this.element.removeEventListener('click', this.handleClick);
|
|
82
|
-
return this;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
`addListeners()` runs when the View mounts. `removeListeners()` runs before replacement or destruction, so the listener lifecycle stays owned by the View.
|
|
88
|
-
|
|
89
|
-
## Read the source
|
|
115
|
+
A useful reading order is:
|
|
90
116
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
2. [`app/assets/view/sections/LiveExampleSection.tsx`](app/assets/view/sections/LiveExampleSection.tsx) — static composition around an interactive feature.
|
|
95
|
-
3. [`app/assets/view/examples/tasks/TaskExample.tsx`](app/assets/view/examples/tasks/TaskExample.tsx) — shared JSX rendering.
|
|
117
|
+
1. [`app/index.ts`](app/index.ts) — page composition.
|
|
118
|
+
2. [`app/assets/view/sections/LiveExampleSection.ts`](app/assets/view/sections/LiveExampleSection.ts) — static composition around an interactive feature.
|
|
119
|
+
3. [`app/assets/view/examples/tasks/TaskExample.ts`](app/assets/view/examples/tasks/TaskExample.ts) — shared tagged-template rendering.
|
|
96
120
|
4. [`app/assets/script/tasks/TaskApplication.ts`](app/assets/script/tasks/TaskApplication.ts) — explicit dependency wiring.
|
|
97
121
|
5. `TaskRouter`, `TaskMediator`, `TaskModel`, and `TaskView` — one responsibility at a time.
|
|
98
|
-
6. [`server/handler.ts`](server/handler.ts) — provider-neutral `Request`/`Response`
|
|
99
|
-
7.
|
|
100
|
-
|
|
101
|
-
The `--no-jsx` scaffold mirrors the same structure with `.ts` files and HTML-string render functions.
|
|
102
|
-
|
|
103
|
-
Comments focus on why boundaries exist instead of narrating obvious TypeScript.
|
|
104
|
-
|
|
105
|
-
When documenting public APIs, examples use comments where they clarify intent, lifecycle, side effects, or non-obvious behavior. Public method documentation should also state the return value—including meaningful boolean/status values, chaining returns, `undefined`, promises, and relevant thrown/rejected errors.
|
|
106
|
-
|
|
107
|
-
A useful rule when extending the project is:
|
|
108
|
-
|
|
109
|
-
> Introduce an abstraction when it gives a concern a clear home, not merely to create another layer.
|
|
122
|
+
6. [`server/handler.ts`](server/handler.ts) — provider-neutral `Request`/`Response` composition.
|
|
123
|
+
7. Tests — executable contracts for browser and generated-project behavior.
|
|
110
124
|
|
|
111
125
|
## Create a project
|
|
112
126
|
|
|
@@ -114,23 +128,13 @@ Requirement:
|
|
|
114
128
|
|
|
115
129
|
- Node.js `^22.18.0` or `>=24.11.0`
|
|
116
130
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
With npm:
|
|
131
|
+
Run the package directly:
|
|
120
132
|
|
|
121
133
|
```sh
|
|
122
134
|
npx generator-white-label create my-project
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
With Yarn:
|
|
126
|
-
|
|
127
|
-
```sh
|
|
135
|
+
# or
|
|
128
136
|
yarn dlx generator-white-label create my-project
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
With pnpm:
|
|
132
|
-
|
|
133
|
-
```sh
|
|
137
|
+
# or
|
|
134
138
|
pnpm dlx generator-white-label create my-project
|
|
135
139
|
```
|
|
136
140
|
|
|
@@ -141,7 +145,7 @@ npm install --global generator-white-label
|
|
|
141
145
|
white-label create my-project
|
|
142
146
|
```
|
|
143
147
|
|
|
144
|
-
After generation, use
|
|
148
|
+
After generation, use one package manager consistently:
|
|
145
149
|
|
|
146
150
|
```sh
|
|
147
151
|
cd my-project
|
|
@@ -150,27 +154,48 @@ npm install && npm test
|
|
|
150
154
|
# or: pnpm install && pnpm test
|
|
151
155
|
```
|
|
152
156
|
|
|
153
|
-
|
|
157
|
+
There is one canonical scaffold. The CLI has no renderer prompt and no `--jsx`/`--no-jsx` mode switch.
|
|
154
158
|
|
|
155
|
-
|
|
159
|
+
The generator refuses to layer a scaffold over an existing non-empty destination. This protection is enforced by the shared `createProject()` engine for both CLI and programmatic use. An existing empty directory is allowed; a missing directory is created.
|
|
156
160
|
|
|
157
|
-
|
|
161
|
+
See [`CLI.md`](CLI.md), [`PACKAGE_MANAGERS.md`](PACKAGE_MANAGERS.md), and [`EXISTING_APPLICATIONS.md`](EXISTING_APPLICATIONS.md).
|
|
158
162
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
163
|
+
## Progressive enhancement and SEO
|
|
164
|
+
|
|
165
|
+
The generated project renders meaningful HTML during the build. Interactive task filters are real links first and are enhanced with History API navigation after initialization.
|
|
162
166
|
|
|
163
|
-
|
|
164
|
-
|
|
167
|
+
```text
|
|
168
|
+
HTML owns semantics.
|
|
169
|
+
JavaScript enhances behavior.
|
|
165
170
|
```
|
|
166
171
|
|
|
167
|
-
|
|
172
|
+
Public content should remain readable, navigable, and crawlable without executing the enhancement bundle. Generated pages include canonical URLs, page titles/descriptions, robots metadata, semantic navigation, JSON-LD, and sitemap-oriented output; deployment still needs to serve the generated files correctly.
|
|
173
|
+
|
|
174
|
+
Do not treat metadata in source code as proof of search-engine indexing. Verify deployed behavior and indexing with actual production/search-console evidence.
|
|
175
|
+
|
|
176
|
+
## A simple View click event
|
|
168
177
|
|
|
169
|
-
|
|
178
|
+
Use View lifecycle hooks to add and remove browser listeners with the same callback reference:
|
|
170
179
|
|
|
171
|
-
|
|
180
|
+
```ts
|
|
181
|
+
import View from 'white-label-view';
|
|
172
182
|
|
|
173
|
-
|
|
183
|
+
class ButtonView extends View {
|
|
184
|
+
handleClick = () => console.log('Clicked');
|
|
185
|
+
|
|
186
|
+
addListeners() {
|
|
187
|
+
this.element.addEventListener('click', this.handleClick);
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
removeListeners() {
|
|
192
|
+
this.element.removeEventListener('click', this.handleClick);
|
|
193
|
+
return this;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
`addListeners()` runs when the View mounts. `removeListeners()` runs before replacement or destruction, so listener ownership remains explicit.
|
|
174
199
|
|
|
175
200
|
## Programmatic API
|
|
176
201
|
|
|
@@ -179,92 +204,45 @@ Project creation has one implementation:
|
|
|
179
204
|
```js
|
|
180
205
|
import {createProject} from 'generator-white-label';
|
|
181
206
|
|
|
182
|
-
// Resolves after the scaffold files and package manifest have been written.
|
|
183
207
|
await createProject({
|
|
184
|
-
destination: new URL('./my-project', import.meta.url).pathname
|
|
185
|
-
jsx: false
|
|
208
|
+
destination: new URL('./my-project', import.meta.url).pathname
|
|
186
209
|
});
|
|
187
210
|
```
|
|
188
211
|
|
|
189
|
-
|
|
212
|
+
`createProject(options)` returns `Promise<void>`. A successful call resolves with `undefined`; file-system failures reject. If the destination exists and contains files, it rejects before copying or writing project content.
|
|
213
|
+
|
|
214
|
+
`createProject()` is the canonical creation API. The CLI and future integrations are adapters around it rather than separate generation systems. Keeping overwrite policy here prevents adapters from bypassing the same safety boundary.
|
|
215
|
+
|
|
216
|
+
## Serverless and function runtimes
|
|
217
|
+
|
|
218
|
+
Every generated project includes `server/handler.ts`, a provider-neutral example built around Web `Request` and `Response`. It composes Router, Mediator, Model, and `white-label-view/server` without Express or a provider SDK.
|
|
219
|
+
|
|
220
|
+
Mutable White Label instances are created inside `handleRequest()`. Serverless hosts can reuse a warm process for many requests, so mutable module-level state can leak request data or listeners. Immutable configuration can still live at module scope when its lifetime is intentionally process-wide.
|
|
190
221
|
|
|
191
|
-
|
|
222
|
+
Cloud-specific adapters should stay at the boundary: translate a provider request into a Web `Request` when needed, call `handleRequest()`, then translate the returned `Response` back only if required.
|
|
192
223
|
|
|
193
|
-
|
|
224
|
+
Generated-project tests exercise sequential warm invocations, concurrent requests, request-data escaping, execution without browser globals, and a browser/Web-target bundle smoke test. They also enforce serverless composition size/import regression budgets. These are regression guards, not universal latency guarantees.
|
|
194
225
|
|
|
195
|
-
|
|
226
|
+
The Web-target smoke test does **not** claim blanket Cloudflare/Deno/edge-provider compatibility. Published runtime packages document Node as their supported server runtime; verify a specific target before deployment.
|
|
196
227
|
|
|
197
228
|
## Source layout
|
|
198
229
|
|
|
199
230
|
| Path | Purpose |
|
|
200
231
|
| --- | --- |
|
|
201
232
|
| `scaffold/index.ts` | Canonical `createProject()` implementation and destination-safety boundary |
|
|
202
|
-
| `scaffold/no-jsx/` | Plain-TypeScript template equivalents |
|
|
203
233
|
| `cli/index.ts` | First-party command-line adapter |
|
|
204
|
-
| `app/*.
|
|
205
|
-
| `app/assets/view/` |
|
|
234
|
+
| `app/*.ts` | Static pages using tagged HTML templates |
|
|
235
|
+
| `app/assets/view/` | Reusable tagged-template views and sections |
|
|
206
236
|
| `app/assets/script/tasks/` | Model/View/Mediator/Router example |
|
|
207
|
-
| `server/handler.ts` | Provider-neutral serverless
|
|
208
|
-
| `scripts/build.ts` | Static rendering and asset build
|
|
237
|
+
| `server/handler.ts` | Provider-neutral serverless Request → Response example |
|
|
238
|
+
| `scripts/build.ts` | Static rendering and asset build |
|
|
209
239
|
| `test/` | Executable contracts and integration tests |
|
|
210
|
-
| `template-test/` | Generated-project validation
|
|
240
|
+
| `template-test/` | Generated-project validation |
|
|
211
241
|
|
|
212
|
-
No React, Bootstrap, Sass, Eta, Handlebars, Mustache, Nunjucks, Pug, web
|
|
213
|
-
|
|
214
|
-
## Template choice: JSX or no JSX
|
|
215
|
-
|
|
216
|
-
JSX is optional. When enabled, TypeScript uses the White Label JSX runtime:
|
|
217
|
-
|
|
218
|
-
```json
|
|
219
|
-
{
|
|
220
|
-
"compilerOptions": {
|
|
221
|
-
"jsx": "react-jsx",
|
|
222
|
-
"jsxImportSource": "white-label-view"
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
A page remains an ordinary function:
|
|
228
|
-
|
|
229
|
-
```tsx
|
|
230
|
-
export default function Page(data: Record<string, unknown>) {
|
|
231
|
-
return <main><h1>{String(data.title)}</h1></main>;
|
|
232
|
-
}
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
With `--no-jsx`, the equivalent page is ordinary TypeScript returning an HTML string instead. The application architecture and generated features remain the same. That no-JSX scaffold is also the intended starting point when Handlebars, Eta, EJS, Mustache, Nunjucks, Pug, or another renderer should remain the project's template convention.
|
|
236
|
-
|
|
237
|
-
For larger pages, compose focused views instead of growing one renderer indefinitely.
|
|
238
|
-
|
|
239
|
-
White Label JSX HTML-escapes ordinary child text and ordinary attribute values by default, rejects intrinsic `on*` event-handler attributes, and uses runtime-owned identity for trusted JSX/raw values. That escaping is not a general-purpose sanitizer for URL or CSS semantics. Plain-TypeScript templates and third-party engines likewise remain responsible for their own contextual escaping, sanitization, raw-output features, and configuration. See [Template engines and JSX options](https://whitelabeljs.org/docs/view/#template-engines) for the tested matrix and trust boundaries.
|
|
240
|
-
|
|
241
|
-
## Progressive enhancement
|
|
242
|
-
|
|
243
|
-
The generated project renders meaningful HTML during the build. Interactive task filters are real links first and are enhanced with History API navigation after initialization.
|
|
244
|
-
|
|
245
|
-
The principle is intentional:
|
|
246
|
-
|
|
247
|
-
```text
|
|
248
|
-
HTML owns semantics.
|
|
249
|
-
JavaScript enhances behavior.
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
## Serverless and function runtimes
|
|
253
|
-
|
|
254
|
-
Every generated project includes `server/handler.ts`, a provider-neutral example built around the Web `Request` and `Response` APIs. It composes Router, Mediator, Model, and `white-label-view/server` without Express or a provider SDK.
|
|
255
|
-
|
|
256
|
-
Mutable White Label instances are created inside `handleRequest()`. This is intentional: serverless hosts can reuse a warm process for many requests, so mutable module-level Model/View/Router/Mediator instances can leak request data or listeners. Immutable configuration can still be shared at module scope when its lifetime is intentionally process-wide.
|
|
257
|
-
|
|
258
|
-
Cloud-specific adapters should stay at the boundary. Translate an AWS/Vercel/Netlify/Azure/etc. request into a Web `Request` when necessary, call `handleRequest()`, and translate the returned `Response` back only if the provider requires it.
|
|
259
|
-
|
|
260
|
-
Generated-project tests exercise sequential warm invocations, concurrent requests, request-data escaping, execution without browser globals, and a browser/Web-target bundle smoke test. They also enforce a 100 kB minified serverless-composition bundle budget and a 750 ms fresh-process handler-import budget. These are regression guards, not universal latency guarantees.
|
|
261
|
-
|
|
262
|
-
The Web-target bundle smoke test catches unresolved Node built-ins, but it does **not** claim blanket Cloudflare/Deno/edge-provider compatibility. The published runtime packages document Node as their supported server runtime; verify a specific edge provider before deployment.
|
|
242
|
+
No React, Bootstrap, Sass, Eta, Handlebars, Mustache, Nunjucks, Pug, web font, cloud SDK, or browser-side template framework is required. Optional renderers/providers remain application dependencies.
|
|
263
243
|
|
|
264
244
|
## Build and verify
|
|
265
245
|
|
|
266
|
-
The repository keeps npm as its canonical maintenance/audit path and committed lockfile. Generated projects support npm, Yarn, and pnpm.
|
|
267
|
-
|
|
268
246
|
Development build:
|
|
269
247
|
|
|
270
248
|
```sh
|
|
@@ -291,13 +269,17 @@ npm run audit
|
|
|
291
269
|
npm pack --dry-run
|
|
292
270
|
```
|
|
293
271
|
|
|
294
|
-
Tests are part of the documentation.
|
|
272
|
+
Tests are part of the documentation. Executable project source is held to 100% statement, branch, function, and line coverage. CI checks supported Node versions, packed CLI/programmatic installation, production builds, and generated-project compatibility across npm, Yarn, and pnpm.
|
|
273
|
+
|
|
274
|
+
## Coordinated runtime release
|
|
275
|
+
|
|
276
|
+
Generator 10 targets the published White Label runtime line: `white-label-mediator@5.0.0`, `white-label-model@7.0.1`, `white-label-router@6.1.0`, and `white-label-view@7.0.0`. Release and CI verification use those registry packages directly.
|
|
295
277
|
|
|
296
278
|
## White Label ecosystem
|
|
297
279
|
|
|
298
280
|
- [`white-label-model`](https://github.com/bshack/white-label-model) — observable state.
|
|
299
|
-
- [`white-label-view`](https://github.com/bshack/white-label-view) — rendering, DOM lifecycle,
|
|
281
|
+
- [`white-label-view`](https://github.com/bshack/white-label-view) — rendering, existing-DOM lifecycle, tagged HTML templates, server rendering, and a template-engine-agnostic contract.
|
|
300
282
|
- [`white-label-mediator`](https://github.com/bshack/white-label-mediator) — application events.
|
|
301
|
-
- [`white-label-router`](https://github.com/bshack/white-label-router) — routing and URL state.
|
|
283
|
+
- [`white-label-router`](https://github.com/bshack/white-label-router) — progressive routing and URL state.
|
|
302
284
|
|
|
303
|
-
The generated project imports the real packages rather than reproducing their behavior locally. That makes it both an example and an ecosystem integration test.
|
|
285
|
+
The generated project imports the real packages rather than reproducing their behavior locally. That makes it both an example and an ecosystem integration test.
|
package/app/404.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {html} from 'white-label-view/html';
|
|
2
|
+
|
|
3
|
+
interface PageData {
|
|
4
|
+
cdn: string;
|
|
5
|
+
version: string;
|
|
6
|
+
www: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function NotFoundPage(data: Record<string, unknown>) {
|
|
10
|
+
const page = data as unknown as PageData;
|
|
11
|
+
return html`<html lang="en-US" dir="ltr">
|
|
12
|
+
<head>
|
|
13
|
+
<meta charset="utf-8">
|
|
14
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
15
|
+
<meta name="robots" content="noindex,follow">
|
|
16
|
+
<title>Page not found | White Label</title>
|
|
17
|
+
<link rel="stylesheet" href="${page.cdn}release/${page.version}/assets/style/global.css">
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
<a class="skip-link" href="#main">Skip to the message</a>
|
|
21
|
+
<main id="main" tabindex="-1" class="mx-auto max-w-5xl px-4 py-24 sm:px-6 lg:px-8">
|
|
22
|
+
<h1 class="text-5xl font-extrabold tracking-tight">Page not found</h1>
|
|
23
|
+
<p class="mt-6 text-lg">The requested page does not exist.</p>
|
|
24
|
+
<p class="mt-6"><a class="font-bold underline" href="${page.www}">Return to the White Label starter</a></p>
|
|
25
|
+
</main>
|
|
26
|
+
</body>
|
|
27
|
+
</html>`;
|
|
28
|
+
}
|
package/app/README.md
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
# White Label starter application
|
|
2
2
|
|
|
3
|
-
This static starter
|
|
3
|
+
This static starter uses ordinary TypeScript and White Label View's first-party tagged HTML templates, with Tailwind CSS 4 for styling. It makes no API or service calls.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The rendering path is intentionally small and explicit:
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```ts
|
|
8
|
+
import {html} from 'white-label-view/html';
|
|
9
|
+
|
|
10
|
+
export default function Page(data: {title: string}) {
|
|
11
|
+
return html`<main><h1>${data.title}</h1></main>`;
|
|
12
|
+
}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Dynamic text and quoted-attribute values are HTML-escaped by default. Use `attributes()` for conditional/opening-tag attributes. `unsafeHTML()` is an explicit trust boundary for application-owned content that is already trusted or sanitized; it is not a sanitizer.
|
|
16
|
+
|
|
17
|
+
White Label View remains template-engine agnostic. If the application prefers JSX, Handlebars, Eta, EJS, Mustache, Nunjucks, Pug, or another renderer, add it to the application and return its rendered output from View's `template` function. JSX is a tested third-party option rather than a White Label runtime requirement.
|
|
18
|
+
|
|
19
|
+
See [Template engines](https://whitelabeljs.org/docs/view/#template-engines) for tested integrations and rendering/security boundaries.
|
|
8
20
|
|
|
9
21
|
## Start developing
|
|
10
22
|
|
|
@@ -42,11 +54,11 @@ python3 -m http.server 8080 --directory _deploy
|
|
|
42
54
|
|
|
43
55
|
Open `http://localhost:8080/`.
|
|
44
56
|
|
|
45
|
-
Serve `_deploy` as the web server's document root. Do **not** browse to `_deploy/index.html` through a server rooted at the project directory
|
|
57
|
+
Serve `_deploy` as the web server's document root. Do **not** browse to `_deploy/index.html` through a server rooted at the project directory, because generated asset URLs such as `/release/local/assets/style/global.css` are intentionally rooted at the deployed site's origin.
|
|
46
58
|
|
|
47
|
-
The build output is `_deploy`. The test suite checks
|
|
59
|
+
The build output is `_deploy`. The test suite checks starter content, progressive interaction, production build behavior, accessibility/indexability signals, serverless request isolation, Web-standard bundling, and serverless size/startup budgets.
|
|
48
60
|
|
|
49
|
-
Pages live in `app/*.
|
|
61
|
+
Pages live in `app/*.ts`, page data lives in `app/assets/data/view`, browser code lives in `app/assets/script`, the provider-neutral serverless example lives in `server/handler.ts`, and shared styles live in `app/assets/style`.
|
|
50
62
|
|
|
51
63
|
## Simple View click event
|
|
52
64
|
|
|
@@ -100,4 +112,4 @@ npm run build -- --version=release-1 --production=true --site-url=https://www.ex
|
|
|
100
112
|
|
|
101
113
|
Build flags use `--key=value`. `--www` and `--cdn` default to `/`; `--version` selects `_deploy/release/<version>/assets`; without a version, the current timestamp is used. Production builds require a real HTTPS `--site-url` for canonical, Open Graph, robots, and sitemap output. Each build replaces `_deploy`.
|
|
102
114
|
|
|
103
|
-
Edit TypeScript
|
|
115
|
+
Edit TypeScript sources, not `dist`. Keep untrusted values in normal `html` interpolations; use `unsafeHTML()` only when the application has deliberately established trust or sanitization. Replace the starter copy with application content while retaining the tested accessibility and progressive-enhancement patterns.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** @module app/assets/script/index */
|
|
2
2
|
import {initializeTaskApplication} from './tasks/TaskApplication.js';
|
|
3
3
|
|
|
4
|
+
/** Start the feature composition for this document. */
|
|
4
5
|
export {initializeTaskApplication} from './tasks/TaskApplication.js';
|
|
5
6
|
export {normalizeTaskFilter} from './tasks/TaskRouter.js';
|
|
6
7
|
|
|
@@ -3,7 +3,7 @@ import TaskExample from '../../view/examples/tasks/TaskExample.js';
|
|
|
3
3
|
import type {TaskState} from '../../view/examples/tasks/task-state.js';
|
|
4
4
|
import type TaskModel from './TaskModel.js';
|
|
5
5
|
|
|
6
|
-
/**
|
|
6
|
+
/** Adopt the build-time task root and reuse the same tagged template for model updates. */
|
|
7
7
|
export function createTaskView(parentElement: HTMLElement, model: TaskModel): View {
|
|
8
8
|
const element = parentElement.querySelector<HTMLElement>('[data-task-app]');
|
|
9
9
|
if (!element) {throw new TypeError('Task example requires its static task-app root');}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {html, type HTMLMarkup} from 'white-label-view/html';
|
|
2
|
+
|
|
3
|
+
export const syntax = {
|
|
4
|
+
keyword: 'code-syntax-keyword',
|
|
5
|
+
type: 'code-syntax-type',
|
|
6
|
+
value: 'code-syntax-value',
|
|
7
|
+
muted: 'code-syntax-muted'
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/** Render a readable code example with stable line numbers and caller-supplied syntax spans. */
|
|
11
|
+
export default function CodeBlock({lines}: {lines: HTMLMarkup[]}) {
|
|
12
|
+
return html`<pre class="code-block"><code>${lines.map((line, index) => html`<span class="code-block__line">
|
|
13
|
+
<span aria-hidden="true" class="code-block__number">${index + 1}</span><span>${line}</span>
|
|
14
|
+
</span>`)}</code></pre>`;
|
|
15
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {attributes, html} from 'white-label-view/html';
|
|
2
|
+
import {getVisibleTasks, type TaskFilter, type TaskState} from './task-state.js';
|
|
3
|
+
|
|
4
|
+
const filters: TaskFilter[] = ['all', 'active', 'completed'];
|
|
5
|
+
|
|
6
|
+
/** Render the task example from state for both build-time HTML and browser updates. */
|
|
7
|
+
export default function TaskExample({state}: {state: TaskState}) {
|
|
8
|
+
const visibleTasks = getVisibleTasks(state);
|
|
9
|
+
const completed = state.tasks.filter(task => task.complete).length;
|
|
10
|
+
|
|
11
|
+
return html`<section class="task-app" data-task-app aria-label="Interactive task example">
|
|
12
|
+
<form class="task-form" data-task-form action="#example" method="get">
|
|
13
|
+
<label for="task-title">New task</label>
|
|
14
|
+
<div class="task-form__controls">
|
|
15
|
+
<input id="task-title" name="task" type="text" autocomplete="off" required>
|
|
16
|
+
<button type="submit">Add task</button>
|
|
17
|
+
</div>
|
|
18
|
+
</form>
|
|
19
|
+
|
|
20
|
+
<nav class="task-filters" aria-label="Filter tasks">
|
|
21
|
+
${filters.map(filter => html`<a ${attributes({
|
|
22
|
+
href: `/?tasks=${filter}#example`,
|
|
23
|
+
'data-task-filter': filter,
|
|
24
|
+
'data-pushstate': true,
|
|
25
|
+
'aria-current': state.filter === filter ? 'page' : 'false'
|
|
26
|
+
})}>${filter.charAt(0).toUpperCase()}${filter.slice(1)}</a>`)}
|
|
27
|
+
</nav>
|
|
28
|
+
|
|
29
|
+
<ul class="task-list">
|
|
30
|
+
${visibleTasks.map(task => html`<li>
|
|
31
|
+
<label>
|
|
32
|
+
<input ${attributes({
|
|
33
|
+
type: 'checkbox',
|
|
34
|
+
checked: task.complete,
|
|
35
|
+
'data-task-toggle': true,
|
|
36
|
+
'data-task-id': task.id
|
|
37
|
+
})}>
|
|
38
|
+
<span>${task.title}</span>
|
|
39
|
+
</label>
|
|
40
|
+
</li>`)}
|
|
41
|
+
</ul>
|
|
42
|
+
|
|
43
|
+
<dl class="demo__trace">
|
|
44
|
+
<div><dt>Router</dt><dd>/?tasks=${state.filter}</dd></div>
|
|
45
|
+
<div><dt>Mediator</dt><dd>task:* application events</dd></div>
|
|
46
|
+
<div><dt>Model</dt><dd>${state.tasks.length} tasks · ${completed} complete</dd></div>
|
|
47
|
+
<div><dt>View</dt><dd>${visibleTasks.length} tasks rendered</dd></div>
|
|
48
|
+
</dl>
|
|
49
|
+
</section>`;
|
|
50
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {html} from 'white-label-view/html';
|
|
2
|
+
|
|
3
|
+
/** Keep reusable static page chrome as a small tagged-template function. */
|
|
4
|
+
export default function SiteFooter() {
|
|
5
|
+
return html`<footer class="site-footer">
|
|
6
|
+
<div class="container site-footer__inner">
|
|
7
|
+
<p>White Label. Framework-independent TypeScript building blocks.</p>
|
|
8
|
+
<a href="#top">Back to top</a>
|
|
9
|
+
</div>
|
|
10
|
+
</footer>`;
|
|
11
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {html} from 'white-label-view/html';
|
|
2
|
+
|
|
3
|
+
/** Static navigation remains ordinary crawlable HTML; JavaScript enhances only opted-in links. */
|
|
4
|
+
export default function SiteHeader() {
|
|
5
|
+
return html`<header class="site-header">
|
|
6
|
+
<div class="container site-header__inner">
|
|
7
|
+
<a class="wordmark" href="#top">White Label</a>
|
|
8
|
+
<nav aria-label="Primary navigation">
|
|
9
|
+
<a href="#example">Example</a>
|
|
10
|
+
<a href="#packages">Packages</a>
|
|
11
|
+
<a href="#source">Source</a>
|
|
12
|
+
<a href="#start">Get started</a>
|
|
13
|
+
<a href="https://github.com/bshack/white-label">GitHub</a>
|
|
14
|
+
</nav>
|
|
15
|
+
</div>
|
|
16
|
+
</header>`;
|
|
17
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Build-time data available to every page template.
|
|
3
3
|
*
|
|
4
|
-
* Keeping this contract separate from the
|
|
5
|
-
*
|
|
4
|
+
* Keeping this contract separate from the renderer keeps top-level tagged
|
|
5
|
+
* HTML focused on application composition instead of deployment plumbing.
|
|
6
6
|
*/
|
|
7
7
|
export interface PageData {
|
|
8
8
|
cdn: string;
|