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.
Files changed (98) hide show
  1. package/CLI.md +13 -17
  2. package/EXISTING_APPLICATIONS.md +155 -0
  3. package/PACKAGE_MANAGERS.md +6 -4
  4. package/README.md +135 -153
  5. package/app/404.ts +28 -0
  6. package/app/README.md +19 -7
  7. package/{scaffold/no-jsx/app → app}/assets/script/index.ts +1 -0
  8. package/{scaffold/no-jsx/app → app}/assets/script/tasks/TaskView.ts +1 -1
  9. package/app/assets/view/CodeBlock.ts +15 -0
  10. package/app/assets/view/examples/tasks/TaskExample.ts +50 -0
  11. package/app/assets/view/examples/tasks/task-state.ts +1 -1
  12. package/app/assets/view/layout/SiteFooter.ts +11 -0
  13. package/app/assets/view/layout/SiteHeader.ts +17 -0
  14. package/app/assets/view/page-data.ts +2 -2
  15. package/app/assets/view/sections/GettingStartedSection.ts +26 -0
  16. package/app/assets/view/sections/HeroSection.ts +15 -0
  17. package/app/assets/view/sections/LiveExampleSection.ts +24 -0
  18. package/app/assets/view/sections/PackageDocsSection.ts +69 -0
  19. package/app/assets/view/sections/{SourceGuideSection.tsx → SourceGuideSection.ts} +11 -16
  20. package/app/index.ts +50 -0
  21. package/cli/index.ts +4 -41
  22. package/dist/app/404.d.ts +1 -1
  23. package/dist/app/404.js +18 -2
  24. package/dist/app/404.js.map +1 -1
  25. package/dist/app/assets/script/index.d.ts +1 -6
  26. package/dist/app/assets/script/index.js +1 -6
  27. package/dist/app/assets/script/index.js.map +1 -1
  28. package/dist/app/assets/script/tasks/TaskView.d.ts +1 -6
  29. package/dist/app/assets/script/tasks/TaskView.js +2 -8
  30. package/dist/app/assets/script/tasks/TaskView.js.map +1 -1
  31. package/dist/app/assets/view/CodeBlock.d.ts +3 -3
  32. package/dist/app/assets/view/CodeBlock.js +4 -2
  33. package/dist/app/assets/view/CodeBlock.js.map +1 -1
  34. package/dist/app/assets/view/examples/tasks/TaskExample.d.ts +2 -8
  35. package/dist/app/assets/view/examples/tasks/TaskExample.js +41 -9
  36. package/dist/app/assets/view/examples/tasks/TaskExample.js.map +1 -1
  37. package/dist/app/assets/view/examples/tasks/task-state.d.ts +1 -1
  38. package/dist/app/assets/view/examples/tasks/task-state.js.map +1 -1
  39. package/dist/app/assets/view/layout/SiteFooter.d.ts +2 -2
  40. package/dist/app/assets/view/layout/SiteFooter.js +8 -3
  41. package/dist/app/assets/view/layout/SiteFooter.js.map +1 -1
  42. package/dist/app/assets/view/layout/SiteHeader.d.ts +2 -8
  43. package/dist/app/assets/view/layout/SiteHeader.js +14 -9
  44. package/dist/app/assets/view/layout/SiteHeader.js.map +1 -1
  45. package/dist/app/assets/view/page-data.d.ts +2 -2
  46. package/dist/app/assets/view/page-data.js.map +1 -1
  47. package/dist/app/assets/view/sections/GettingStartedSection.d.ts +1 -1
  48. package/dist/app/assets/view/sections/GettingStartedSection.js +21 -6
  49. package/dist/app/assets/view/sections/GettingStartedSection.js.map +1 -1
  50. package/dist/app/assets/view/sections/HeroSection.d.ts +2 -8
  51. package/dist/app/assets/view/sections/HeroSection.js +12 -9
  52. package/dist/app/assets/view/sections/HeroSection.js.map +1 -1
  53. package/dist/app/assets/view/sections/LiveExampleSection.d.ts +2 -6
  54. package/dist/app/assets/view/sections/LiveExampleSection.js +17 -7
  55. package/dist/app/assets/view/sections/LiveExampleSection.js.map +1 -1
  56. package/dist/app/assets/view/sections/PackageDocsSection.d.ts +1 -1
  57. package/dist/app/assets/view/sections/PackageDocsSection.js +57 -20
  58. package/dist/app/assets/view/sections/PackageDocsSection.js.map +1 -1
  59. package/dist/app/assets/view/sections/SourceGuideSection.d.ts +2 -7
  60. package/dist/app/assets/view/sections/SourceGuideSection.js +20 -8
  61. package/dist/app/assets/view/sections/SourceGuideSection.js.map +1 -1
  62. package/dist/app/index.d.ts +2 -8
  63. package/dist/app/index.js +36 -11
  64. package/dist/app/index.js.map +1 -1
  65. package/dist/cli/index.d.ts +0 -3
  66. package/dist/cli/index.js +4 -36
  67. package/dist/cli/index.js.map +1 -1
  68. package/dist/scaffold/index.d.ts +2 -13
  69. package/dist/scaffold/index.js +17 -53
  70. package/dist/scaffold/index.js.map +1 -1
  71. package/dist/scripts/build.js +97 -28
  72. package/dist/scripts/build.js.map +1 -1
  73. package/dist/server/handler.js +2 -9
  74. package/dist/server/handler.js.map +1 -1
  75. package/package.json +22 -22
  76. package/pnpm-workspace.yaml +0 -1
  77. package/scaffold/AGENTS.md +13 -8
  78. package/scaffold/index.ts +17 -57
  79. package/scripts/build.ts +90 -30
  80. package/server/handler.ts +2 -10
  81. package/tsconfig.site.json +1 -4
  82. package/app/404.tsx +0 -28
  83. package/app/assets/script/index.tsx +0 -13
  84. package/app/assets/script/tasks/TaskView.tsx +0 -24
  85. package/app/assets/view/CodeBlock.tsx +0 -17
  86. package/app/assets/view/examples/tasks/TaskExample.tsx +0 -63
  87. package/app/assets/view/layout/SiteFooter.tsx +0 -11
  88. package/app/assets/view/layout/SiteHeader.tsx +0 -23
  89. package/app/assets/view/sections/GettingStartedSection.tsx +0 -27
  90. package/app/assets/view/sections/HeroSection.tsx +0 -21
  91. package/app/assets/view/sections/LiveExampleSection.tsx +0 -29
  92. package/app/assets/view/sections/PackageDocsSection.tsx +0 -69
  93. package/app/index.tsx +0 -54
  94. package/scaffold/no-jsx/README.md +0 -73
  95. package/scaffold/no-jsx/app/404.ts +0 -10
  96. package/scaffold/no-jsx/app/assets/view/examples/tasks/TaskExample.ts +0 -39
  97. package/scaffold/no-jsx/app/index.ts +0 -58
  98. 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
- The project is also its own teaching tool. The landing page, README, source, and tests are meant to be read together:
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
- ```text
10
- understand → see → build → verify
11
- ```
9
+ ## Where the generator fits
12
10
 
13
- White Label does not prescribe a framework. It provides focused pieces that can be composed where useful and omitted where they are not.
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 JSX or plain TypeScript HTML strings. A feature that does not need routing does not need a router.
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
- - [`white-label-view/jsx-runtime`](https://github.com/bshack/white-label-view) provides optional escaped JSX without React or another template engine.
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 it documents.
99
+ The project is also a teaching tool. The generated landing page demonstrates the same architecture its source and tests document.
46
100
 
47
- Its static sections use either JSX functions or equivalent plain TypeScript HTML-string functions, depending on the generator choice. The task example then shows all four packages working together:
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
- Both generated variants provide the same application behavior and progressive enhancement. The only difference is template syntax.
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
- For the default JSX scaffold, a useful reading order is:
92
-
93
- 1. [`app/index.tsx`](app/index.tsx) — page composition.
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` serverless composition.
99
- 7. [`test/app.test.js`](test/app.test.js) and [`template-test/site.test.js`](template-test/site.test.js) — browser and generated-project contracts.
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
- Install the CLI globally with your preferred package manager, or run the package directly.
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 npm, Yarn, or pnpm consistently within the project:
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
- Interactive creation asks whether templates should use JSX/TSX. JSX is optional: choose **Yes** for White Label's first-party JSX syntax such as `<section>...</section>`, or **No** for plain TypeScript functions that return HTML strings. Both choices generate the same functional starter application.
157
+ There is one canonical scaffold. The CLI has no renderer prompt and no `--jsx`/`--no-jsx` mode switch.
154
158
 
155
- Choose the no-JSX path when another template engine should own rendering. Install that engine in the generated application and call it from the View `template` function; no White Label adapter is required. White Label View currently tests Handlebars, Eta, EJS, Mustache, Nunjucks, and Pug in both browser and server rendering. See [Template engines and JSX options](https://whitelabeljs.org/docs/view/#template-engines) for tested versions, examples, the rendering contract, and security guidance.
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
- For explicit or non-interactive use:
161
+ See [`CLI.md`](CLI.md), [`PACKAGE_MANAGERS.md`](PACKAGE_MANAGERS.md), and [`EXISTING_APPLICATIONS.md`](EXISTING_APPLICATIONS.md).
158
162
 
159
- ```sh
160
- white-label create my-project --jsx
161
- white-label create my-project --no-jsx
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
- npx generator-white-label create my-project --jsx
164
- npx generator-white-label create my-project --no-jsx
167
+ ```text
168
+ HTML owns semantics.
169
+ JavaScript enhances behavior.
165
170
  ```
166
171
 
167
- The same `--jsx` and `--no-jsx` options work through `yarn dlx` and `pnpm dlx`.
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
- If no interactive answer is available and neither flag is supplied, JSX is the default.
178
+ Use View lifecycle hooks to add and remove browser listeners with the same callback reference:
170
179
 
171
- The generator refuses to layer a scaffold over an existing non-empty destination. This protection is enforced by the shared `createProject()` engine, so it applies to both the CLI and programmatic use. An existing empty directory is allowed; a missing directory is created as part of generation.
180
+ ```ts
181
+ import View from 'white-label-view';
172
182
 
173
- See [`CLI.md`](CLI.md) for the CLI contract and [`PACKAGE_MANAGERS.md`](PACKAGE_MANAGERS.md) for package-manager compatibility details.
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
- Set `jsx: true` for JSX/TSX templates or `jsx: false` for plain TypeScript and HTML strings. Use `jsx: false` as the starting point for a third-party template engine. Omitting `jsx` defaults to `true`.
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
- `createProject(options)` returns `Promise<void>`. A successful call resolves with `undefined`; file-system failures reject the promise instead of returning a status value. If the destination already exists and contains files, it rejects before copying or writing project content.
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
- `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 accidentally bypassing the same safety boundary.
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
- This is the same design principle used throughout White Label: one responsibility, one implementation, explicit adapters at environment boundaries.
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/*.tsx` | Default JSX top-level static pages |
205
- | `app/assets/view/` | Default JSX views and page sections |
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 `Request` → `Response` example |
208
- | `scripts/build.ts` | Static rendering and asset build for `.ts` and `.tsx` pages |
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, including serverless portability budgets |
240
+ | `template-test/` | Generated-project validation |
211
241
 
212
- No React, Bootstrap, Sass, Eta, Handlebars, Mustache, Nunjucks, Pug, web fonts, cloud SDK, or browser-side template framework is required. Third-party template engines and provider adapters remain optional application dependencies.
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. They demonstrate intended contracts while protecting behavior. Executable project source is held to 100% statement, branch, function, and line coverage. CI also checks the documented Node 22.18 minimum, the primary Node 24 line, packed CLI/programmatic API installation, and generated-project compatibility across npm, Yarn, and pnpm.
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, optional JSX, and a template-engine-agnostic rendering contract.
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 was generated with the **JSX/TSX** template option. It uses TypeScript and the White Label JSX runtime for page and client-side View rendering, with Tailwind CSS 4 for styling. It makes no API or service calls.
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
- JSX is optional in White Label. If you prefer plain TypeScript or want a third-party template engine to own rendering, generate with `--no-jsx` (or answer **No** to the generator's JSX question), then install and call that renderer from the View `template` function. The Model, View, Router, Mediator, progressive-enhancement behavior, and generated feature set remain equivalent.
5
+ The rendering path is intentionally small and explicit:
6
6
 
7
- See [Template engines and JSX options](https://whitelabeljs.org/docs/view/#template-engines) for the tested third-party engines, rendering contract, and no-JSX setup.
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 (for example, `/white-label-site/_deploy/index.html`), because generated asset URLs such as `/release/local/assets/style/global.css` are intentionally rooted at the deployed site's origin and will otherwise return 404 responses.
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 the starter content, progressive interaction, production build, accessibility/indexability signals, serverless request isolation, Web-standard bundling, and serverless size/startup budgets.
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/*.tsx`, 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`. TypeScript is configured with `jsx: react-jsx` and `jsxImportSource: white-label-view`, so this scaffold's JSX does not require React.
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/TSX sources, not `dist`. JSX expressions are escaped by the White Label runtime; reserve `raw()` for trusted application-authored markup. Replace the starter copy with application content while retaining the tested accessibility and progressive-enhancement patterns.
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
- /** Create the task view without JSX/TSX syntax. */
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
+ }
@@ -1,4 +1,4 @@
1
- /** Shared state shape used by build-time JSX and the browser application. */
1
+ /** Shared state shape used by build-time tagged HTML and the browser application. */
2
2
  export type TaskFilter = 'all' | 'active' | 'completed';
3
3
 
4
4
  export interface TaskItem {
@@ -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 page component makes the top-level
5
- * JSX read like application composition instead of deployment plumbing.
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;