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/tsconfig.site.json
CHANGED
|
@@ -11,8 +11,6 @@
|
|
|
11
11
|
"outDir": "dist",
|
|
12
12
|
"rootDir": ".",
|
|
13
13
|
"resolveJsonModule": true,
|
|
14
|
-
"jsx": "react-jsx",
|
|
15
|
-
"jsxImportSource": "white-label-view",
|
|
16
14
|
"types": [
|
|
17
15
|
"node"
|
|
18
16
|
],
|
|
@@ -21,7 +19,6 @@
|
|
|
21
19
|
"include": [
|
|
22
20
|
"scripts/**/*.ts",
|
|
23
21
|
"server/**/*.ts",
|
|
24
|
-
"app/**/*.ts"
|
|
25
|
-
"app/**/*.tsx"
|
|
22
|
+
"app/**/*.ts"
|
|
26
23
|
]
|
|
27
24
|
}
|
package/app/404.tsx
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
interface PageData {
|
|
2
|
-
cdn: string;
|
|
3
|
-
version: string;
|
|
4
|
-
www: string;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export default function NotFoundPage(data: Record<string, unknown>) {
|
|
8
|
-
const page = data as unknown as PageData;
|
|
9
|
-
return (
|
|
10
|
-
<html lang="en-US" dir="ltr">
|
|
11
|
-
<head>
|
|
12
|
-
<meta charset="utf-8" />
|
|
13
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
14
|
-
<meta name="robots" content="noindex,follow" />
|
|
15
|
-
<title>Page not found | White Label</title>
|
|
16
|
-
<link rel="stylesheet" href={`${page.cdn}release/${page.version}/assets/style/global.css`} />
|
|
17
|
-
</head>
|
|
18
|
-
<body>
|
|
19
|
-
<a className="skip-link" href="#main">Skip to the message</a>
|
|
20
|
-
<main id="main" tabindex="-1" className="mx-auto max-w-5xl px-4 py-24 sm:px-6 lg:px-8">
|
|
21
|
-
<h1 className="text-5xl font-extrabold tracking-tight">Page not found</h1>
|
|
22
|
-
<p className="mt-6 text-lg">The requested page does not exist.</p>
|
|
23
|
-
<p className="mt-6"><a className="font-bold underline" href={page.www}>Return to the White Label starter</a></p>
|
|
24
|
-
</main>
|
|
25
|
-
</body>
|
|
26
|
-
</html>
|
|
27
|
-
);
|
|
28
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/** @module app/assets/script/index */
|
|
2
|
-
import {initializeTaskApplication} from './tasks/TaskApplication.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Browser entry points should stay small.
|
|
6
|
-
*
|
|
7
|
-
* The task application owns its own composition and lifecycle, so the entry
|
|
8
|
-
* module has one obvious responsibility: start the feature for this document.
|
|
9
|
-
*/
|
|
10
|
-
export {initializeTaskApplication} from './tasks/TaskApplication.js';
|
|
11
|
-
export {normalizeTaskFilter} from './tasks/TaskRouter.js';
|
|
12
|
-
|
|
13
|
-
initializeTaskApplication(document);
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import View from 'white-label-view';
|
|
2
|
-
import TaskExample from '../../view/examples/tasks/TaskExample.js';
|
|
3
|
-
import type {TaskState} from '../../view/examples/tasks/task-state.js';
|
|
4
|
-
import type TaskModel from './TaskModel.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* View owns rendering, but not application decisions.
|
|
8
|
-
*
|
|
9
|
-
* Passing the already-rendered task root lets View adopt useful static HTML
|
|
10
|
-
* instead of replacing the document merely because JavaScript has started.
|
|
11
|
-
*/
|
|
12
|
-
export function createTaskView(parentElement: HTMLElement, model: TaskModel): View {
|
|
13
|
-
const element = parentElement.querySelector<HTMLElement>('[data-task-app]');
|
|
14
|
-
if (!element) {throw new TypeError('Task example requires its static task-app root');}
|
|
15
|
-
|
|
16
|
-
return new View({
|
|
17
|
-
element,
|
|
18
|
-
model,
|
|
19
|
-
parentElement,
|
|
20
|
-
template(data) {
|
|
21
|
-
return <TaskExample state={data as TaskState} />;
|
|
22
|
-
}
|
|
23
|
-
}).initialize();
|
|
24
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type {JSX} from 'white-label-view/jsx-runtime';
|
|
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: JSX.Element[]}) {
|
|
12
|
-
return (
|
|
13
|
-
<pre className="code-block"><code>{lines.map((line, index) => (
|
|
14
|
-
<span className="code-block__line"><span aria-hidden="true" className="code-block__number">{index + 1}</span><span>{line}</span></span>
|
|
15
|
-
))}</code></pre>
|
|
16
|
-
);
|
|
17
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import {getVisibleTasks, type TaskFilter, type TaskState} from './task-state.js';
|
|
2
|
-
|
|
3
|
-
const filters: TaskFilter[] = ['all', 'active', 'completed'];
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Render the task example from state only.
|
|
7
|
-
*
|
|
8
|
-
* The same JSX function is used twice: once while generating the initial HTML
|
|
9
|
-
* and again by white-label-view in the browser. Sharing one renderer keeps the
|
|
10
|
-
* static and interactive versions of the example from drifting apart.
|
|
11
|
-
*/
|
|
12
|
-
export default function TaskExample({state}: {state: TaskState}) {
|
|
13
|
-
const visibleTasks = getVisibleTasks(state);
|
|
14
|
-
const completed = state.tasks.filter(task => task.complete).length;
|
|
15
|
-
|
|
16
|
-
return (
|
|
17
|
-
<section className="task-app" data-task-app aria-label="Interactive task example">
|
|
18
|
-
<form className="task-form" data-task-form action="#example" method="get">
|
|
19
|
-
<label htmlFor="task-title">New task</label>
|
|
20
|
-
<div className="task-form__controls">
|
|
21
|
-
<input id="task-title" name="task" type="text" autocomplete="off" required />
|
|
22
|
-
<button type="submit">Add task</button>
|
|
23
|
-
</div>
|
|
24
|
-
</form>
|
|
25
|
-
|
|
26
|
-
<nav className="task-filters" aria-label="Filter tasks">
|
|
27
|
-
{filters.map(filter => (
|
|
28
|
-
<a
|
|
29
|
-
href={`/?tasks=${filter}#example`}
|
|
30
|
-
data-task-filter={filter}
|
|
31
|
-
data-pushstate
|
|
32
|
-
aria-current={state.filter === filter ? 'page' : 'false'}
|
|
33
|
-
>
|
|
34
|
-
{filter.charAt(0).toUpperCase()}{filter.slice(1)}
|
|
35
|
-
</a>
|
|
36
|
-
))}
|
|
37
|
-
</nav>
|
|
38
|
-
|
|
39
|
-
<ul className="task-list">
|
|
40
|
-
{visibleTasks.map(task => (
|
|
41
|
-
<li>
|
|
42
|
-
<label>
|
|
43
|
-
<input
|
|
44
|
-
type="checkbox"
|
|
45
|
-
checked={task.complete}
|
|
46
|
-
data-task-toggle
|
|
47
|
-
data-task-id={task.id}
|
|
48
|
-
/>
|
|
49
|
-
<span>{task.title}</span>
|
|
50
|
-
</label>
|
|
51
|
-
</li>
|
|
52
|
-
))}
|
|
53
|
-
</ul>
|
|
54
|
-
|
|
55
|
-
<dl className="demo__trace">
|
|
56
|
-
<div><dt>Router</dt><dd>/?tasks={state.filter}</dd></div>
|
|
57
|
-
<div><dt>Mediator</dt><dd>task:* application events</dd></div>
|
|
58
|
-
<div><dt>Model</dt><dd>{state.tasks.length} tasks · {completed} complete</dd></div>
|
|
59
|
-
<div><dt>View</dt><dd>{visibleTasks.length} tasks rendered</dd></div>
|
|
60
|
-
</dl>
|
|
61
|
-
</section>
|
|
62
|
-
);
|
|
63
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/** A small reusable footer keeps the page template focused on page structure. */
|
|
2
|
-
export default function SiteFooter() {
|
|
3
|
-
return (
|
|
4
|
-
<footer className="site-footer">
|
|
5
|
-
<div className="container site-footer__inner">
|
|
6
|
-
<p>White Label. Framework-independent TypeScript building blocks.</p>
|
|
7
|
-
<a href="#top">Back to top</a>
|
|
8
|
-
</div>
|
|
9
|
-
</footer>
|
|
10
|
-
);
|
|
11
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Static navigation is ordinary HTML first.
|
|
3
|
-
*
|
|
4
|
-
* White Label's router enhances only links that opt in with data-pushstate,
|
|
5
|
-
* so global navigation stays conventional, crawlable, and usable without
|
|
6
|
-
* client-side JavaScript.
|
|
7
|
-
*/
|
|
8
|
-
export default function SiteHeader() {
|
|
9
|
-
return (
|
|
10
|
-
<header className="site-header">
|
|
11
|
-
<div className="container site-header__inner">
|
|
12
|
-
<a className="wordmark" href="#top">White Label</a>
|
|
13
|
-
<nav aria-label="Primary navigation">
|
|
14
|
-
<a href="#example">Example</a>
|
|
15
|
-
<a href="#packages">Packages</a>
|
|
16
|
-
<a href="#source">Source</a>
|
|
17
|
-
<a href="#start">Get started</a>
|
|
18
|
-
<a href="https://github.com/bshack/white-label">GitHub</a>
|
|
19
|
-
</nav>
|
|
20
|
-
</div>
|
|
21
|
-
</header>
|
|
22
|
-
);
|
|
23
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import CodeBlock, {syntax} from '../CodeBlock.js';
|
|
2
|
-
|
|
3
|
-
/** Keep the shortest path from generator install to a verified local build visible. */
|
|
4
|
-
export default function GettingStartedSection() {
|
|
5
|
-
return (
|
|
6
|
-
<section className="section" id="start" aria-labelledby="start-title">
|
|
7
|
-
<div className="container start-grid">
|
|
8
|
-
<div className="section-intro">
|
|
9
|
-
<p className="eyebrow">Get started</p>
|
|
10
|
-
<h2 id="start-title">Create a project with the generator.</h2>
|
|
11
|
-
<p>The generated project is static-first, TypeScript-first, accessible by default, and ready for application-specific code.</p>
|
|
12
|
-
</div>
|
|
13
|
-
<div>
|
|
14
|
-
<p className="code-label">Create a project</p>
|
|
15
|
-
<CodeBlock lines={[<><span className={syntax.type}>npx</span> generator-white-label</>]} />
|
|
16
|
-
<p className="code-label">Then develop</p>
|
|
17
|
-
<CodeBlock lines={[
|
|
18
|
-
<><span className={syntax.type}>npm</span> ci</>,
|
|
19
|
-
<><span className={syntax.type}>npm</span> test</>,
|
|
20
|
-
<><span className={syntax.type}>npm</span> run build -- --version=<span className={syntax.value}>local</span></>
|
|
21
|
-
]} />
|
|
22
|
-
<p><a href="https://github.com/bshack/white-label#readme">Read the complete generator documentation</a></p>
|
|
23
|
-
</div>
|
|
24
|
-
</div>
|
|
25
|
-
</section>
|
|
26
|
-
);
|
|
27
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Marketing content can stay a plain JSX function.
|
|
3
|
-
*
|
|
4
|
-
* White Label does not require every piece of markup to become a stateful
|
|
5
|
-
* View instance. Use View where lifecycle or observable state is useful;
|
|
6
|
-
* keep static presentation simple.
|
|
7
|
-
*/
|
|
8
|
-
export default function HeroSection() {
|
|
9
|
-
return (
|
|
10
|
-
<section className="hero" id="top" aria-labelledby="hero-title">
|
|
11
|
-
<div className="container hero__inner">
|
|
12
|
-
<h1 id="hero-title">Small pieces.<br />Complete applications.</h1>
|
|
13
|
-
<p className="hero__lede">Framework-independent TypeScript packages for state, rendering, coordination, routing, and project generation.</p>
|
|
14
|
-
<div className="hero__links">
|
|
15
|
-
<a className="primary-link" href="#example">See the example</a>
|
|
16
|
-
<a href="#start">Install the generator</a>
|
|
17
|
-
</div>
|
|
18
|
-
</div>
|
|
19
|
-
</section>
|
|
20
|
-
);
|
|
21
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import TaskExample from '../examples/tasks/TaskExample.js';
|
|
2
|
-
import {createInitialTaskState, describeTaskStatus} from '../examples/tasks/task-state.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* The initial task UI is rendered at build time with the same JSX used later
|
|
6
|
-
* by white-label-view in the browser. JavaScript adds behavior; it does not
|
|
7
|
-
* need to invent the initial document.
|
|
8
|
-
*/
|
|
9
|
-
export default function LiveExampleSection() {
|
|
10
|
-
const state = createInitialTaskState();
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
<section className="section" id="example" aria-labelledby="example-title">
|
|
14
|
-
<div className="container">
|
|
15
|
-
<div className="section-intro">
|
|
16
|
-
<p className="eyebrow">Live example</p>
|
|
17
|
-
<h2 id="example-title">Four packages. One small application.</h2>
|
|
18
|
-
<p>Add a task, toggle its state, or change the filter. Router translates the URL into intent, Mediator coordinates actions, Model owns state, and View keeps the interface in sync.</p>
|
|
19
|
-
</div>
|
|
20
|
-
<div className="demo" data-task-example>
|
|
21
|
-
<TaskExample state={state} />
|
|
22
|
-
<p className="visually-hidden" role="status" aria-live="polite" aria-atomic="true" data-task-status>
|
|
23
|
-
{describeTaskStatus(state)}
|
|
24
|
-
</p>
|
|
25
|
-
</div>
|
|
26
|
-
</div>
|
|
27
|
-
</section>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import CodeBlock, {syntax} from '../CodeBlock.js';
|
|
2
|
-
|
|
3
|
-
const repositories = {
|
|
4
|
-
mediator: 'https://github.com/bshack/white-label-mediator',
|
|
5
|
-
model: 'https://github.com/bshack/white-label-model',
|
|
6
|
-
router: 'https://github.com/bshack/white-label-router',
|
|
7
|
-
view: 'https://github.com/bshack/white-label-view'
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
/** Package documentation stays close to the example it describes. */
|
|
11
|
-
export default function PackageDocsSection() {
|
|
12
|
-
return (
|
|
13
|
-
<section className="section" id="packages" aria-labelledby="packages-title">
|
|
14
|
-
<div className="container">
|
|
15
|
-
<div className="section-intro">
|
|
16
|
-
<p className="eyebrow">Documentation</p>
|
|
17
|
-
<h2 id="packages-title">The core flow stays explicit.</h2>
|
|
18
|
-
<p>Each package has one job, can be used independently, and stays easy to replace or test because application concerns are not hidden behind a framework.</p>
|
|
19
|
-
</div>
|
|
20
|
-
<div className="architecture"><ol>
|
|
21
|
-
<li><strong>Router</strong><span>turns a URL into application intent</span></li>
|
|
22
|
-
<li><strong>Mediator</strong><span>coordinates that intent between modules</span></li>
|
|
23
|
-
<li><strong>Model</strong><span>stores and publishes application state</span></li>
|
|
24
|
-
<li><strong>View</strong><span>renders the resulting interface</span></li>
|
|
25
|
-
</ol></div>
|
|
26
|
-
<div className="docs-grid">
|
|
27
|
-
<article>
|
|
28
|
-
<p className="eyebrow">Model</p><h3>State is observable, not magical.</h3>
|
|
29
|
-
<CodeBlock lines={[
|
|
30
|
-
<><span className={syntax.keyword}>const</span> model = <span className={syntax.keyword}>new</span> <span className={syntax.type}>Model</span>({'{'}count: 0{'}'});</>,
|
|
31
|
-
<>model.addEventListener(<span className={syntax.value}>'change'</span>, event => render(event.detail));</>,
|
|
32
|
-
<>model.update({'{'}count: 1{'}'});</>
|
|
33
|
-
]} />
|
|
34
|
-
<p><a href={repositories.model}>Model documentation</a></p>
|
|
35
|
-
</article>
|
|
36
|
-
<article>
|
|
37
|
-
<p className="eyebrow">View</p><h3>JSX renders through White Label.</h3>
|
|
38
|
-
<CodeBlock lines={[
|
|
39
|
-
<><span className={syntax.keyword}>const</span> view = <span className={syntax.keyword}>new</span> <span className={syntax.type}>View</span>({'{'}</>,
|
|
40
|
-
<> model,</>,
|
|
41
|
-
<> template: state => <p>{'{'}state.count{'}'}</p></>,
|
|
42
|
-
<>{'}'}).initialize();</>
|
|
43
|
-
]} />
|
|
44
|
-
<p><a href={repositories.view}>View documentation</a></p>
|
|
45
|
-
</article>
|
|
46
|
-
<article>
|
|
47
|
-
<p className="eyebrow">Mediator</p><h3>Modules communicate through events.</h3>
|
|
48
|
-
<CodeBlock lines={[
|
|
49
|
-
<>mediator.addEventListener(<span className={syntax.value}>'counter:increment'</span>, increment);</>,
|
|
50
|
-
<>mediator.dispatchEvent(<span className={syntax.keyword}>new</span> <span className={syntax.type}>CustomEvent</span>(<span className={syntax.value}>'counter:increment'</span>));</>
|
|
51
|
-
]} />
|
|
52
|
-
<p><a href={repositories.mediator}>Mediator documentation</a></p>
|
|
53
|
-
</article>
|
|
54
|
-
<article>
|
|
55
|
-
<p className="eyebrow">Router</p><h3>Routes describe intent.</h3>
|
|
56
|
-
<CodeBlock lines={[
|
|
57
|
-
<>router.routes = {'{'}</>,
|
|
58
|
-
<> <span className={syntax.value}>'/'</span>: (_scope, location) => {'{'}</>,
|
|
59
|
-
<> mediator.dispatchEvent(<span className={syntax.keyword}>new</span> <span className={syntax.type}>CustomEvent</span>(<span className={syntax.value}>'filter:set'</span>, {'{'}detail: location.data.query.filter{'}'}));</>,
|
|
60
|
-
<> {'}'}</>,
|
|
61
|
-
<>{'}'};</>
|
|
62
|
-
]} />
|
|
63
|
-
<p><a href={repositories.router}>Router documentation</a></p>
|
|
64
|
-
</article>
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
</section>
|
|
68
|
-
);
|
|
69
|
-
}
|
package/app/index.tsx
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import {raw} from 'white-label-view/jsx-runtime';
|
|
2
|
-
import SiteFooter from './assets/view/layout/SiteFooter.js';
|
|
3
|
-
import SiteHeader from './assets/view/layout/SiteHeader.js';
|
|
4
|
-
import type {PageData} from './assets/view/page-data.js';
|
|
5
|
-
import GettingStartedSection from './assets/view/sections/GettingStartedSection.js';
|
|
6
|
-
import HeroSection from './assets/view/sections/HeroSection.js';
|
|
7
|
-
import LiveExampleSection from './assets/view/sections/LiveExampleSection.js';
|
|
8
|
-
import PackageDocsSection from './assets/view/sections/PackageDocsSection.js';
|
|
9
|
-
import SourceGuideSection from './assets/view/sections/SourceGuideSection.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* The page template is intentionally thin.
|
|
13
|
-
*
|
|
14
|
-
* Developers learning White Label can start here to see the complete page at
|
|
15
|
-
* a glance, then follow each import into a focused example. Build-time data is
|
|
16
|
-
* kept at this boundary while individual views stay reusable and easy to read.
|
|
17
|
-
*/
|
|
18
|
-
export default function IndexPage(data: Record<string, unknown>) {
|
|
19
|
-
const page = data as unknown as PageData;
|
|
20
|
-
const structuredData = JSON.stringify(page.structuredData).replaceAll('<', '\\u003c');
|
|
21
|
-
|
|
22
|
-
return (
|
|
23
|
-
<html id="index" lang="en-US" dir="ltr">
|
|
24
|
-
<head>
|
|
25
|
-
<meta charset="utf-8" />
|
|
26
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
27
|
-
<title>{page.title}</title>
|
|
28
|
-
<meta name="description" content={page.meta.description} />
|
|
29
|
-
<meta name="robots" content="index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1" />
|
|
30
|
-
<link rel="canonical" href={`${page.siteUrl}/`} />
|
|
31
|
-
<meta property="og:title" content={page.title} />
|
|
32
|
-
<meta property="og:description" content={page.meta.description} />
|
|
33
|
-
<meta property="og:type" content="website" />
|
|
34
|
-
<meta property="og:url" content={`${page.siteUrl}/`} />
|
|
35
|
-
<link rel="stylesheet" href={`${page.cdn}release/${page.version}/assets/style/global.css`} media="all" />
|
|
36
|
-
<link rel="stylesheet" href={`${page.cdn}release/${page.version}/assets/style/print.css`} media="print" />
|
|
37
|
-
<script type="application/ld+json">{raw(structuredData)}</script>
|
|
38
|
-
</head>
|
|
39
|
-
<body itemscope itemtype="https://schema.org/WebPage">
|
|
40
|
-
<a className="skip-link" href="#main">Skip to content</a>
|
|
41
|
-
<SiteHeader />
|
|
42
|
-
<main id="main" tabindex="-1">
|
|
43
|
-
<HeroSection />
|
|
44
|
-
<LiveExampleSection />
|
|
45
|
-
<PackageDocsSection />
|
|
46
|
-
<SourceGuideSection />
|
|
47
|
-
<GettingStartedSection />
|
|
48
|
-
</main>
|
|
49
|
-
<SiteFooter />
|
|
50
|
-
<script src={`${page.cdn}release/${page.version}/assets/script/index.compiled.js`} defer />
|
|
51
|
-
</body>
|
|
52
|
-
</html>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
# White Label generated site
|
|
2
|
-
|
|
3
|
-
This project was generated with the **plain TypeScript / no-JSX** template option.
|
|
4
|
-
|
|
5
|
-
- Pages and views are `.ts` files.
|
|
6
|
-
- Templates return HTML strings instead of using JSX/TSX syntax.
|
|
7
|
-
- React is not required.
|
|
8
|
-
- White Label Model, View, Router, and Mediator are used the same way as in the JSX template.
|
|
9
|
-
- npm, Yarn, and pnpm are supported.
|
|
10
|
-
|
|
11
|
-
This scaffold is also the right starting point when you want a third-party template engine. Install the renderer your application uses, then call it from the View `template` function instead of returning a hand-built string. White Label does not require a renderer adapter or change the rest of the application architecture.
|
|
12
|
-
|
|
13
|
-
See [Template engines and JSX options](https://whitelabeljs.org/docs/view/#template-engines) for the tested third-party engines, examples, rendering contract, and escaping/security guidance.
|
|
14
|
-
|
|
15
|
-
## Commands
|
|
16
|
-
|
|
17
|
-
Use the package manager you prefer:
|
|
18
|
-
|
|
19
|
-
```sh
|
|
20
|
-
npm install
|
|
21
|
-
npm run build
|
|
22
|
-
npm test
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
```sh
|
|
26
|
-
yarn install
|
|
27
|
-
yarn build
|
|
28
|
-
yarn test
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
```sh
|
|
32
|
-
pnpm install
|
|
33
|
-
pnpm build
|
|
34
|
-
pnpm test
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## Simple View click event
|
|
38
|
-
|
|
39
|
-
View lifecycle hooks can own a browser listener directly:
|
|
40
|
-
|
|
41
|
-
```ts
|
|
42
|
-
import View from 'white-label-view';
|
|
43
|
-
|
|
44
|
-
class ButtonView extends View {
|
|
45
|
-
handleClick = () => {
|
|
46
|
-
console.log('Clicked');
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
addListeners() {
|
|
50
|
-
this.element.addEventListener('click', this.handleClick);
|
|
51
|
-
return this;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
removeListeners() {
|
|
55
|
-
this.element.removeEventListener('click', this.handleClick);
|
|
56
|
-
return this;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
The same callback reference is used for both registration and cleanup. View calls `removeListeners()` before replacement or destruction.
|
|
62
|
-
|
|
63
|
-
## Serverless / function runtimes
|
|
64
|
-
|
|
65
|
-
`server/handler.ts` is the same provider-neutral serverless example included in the JSX scaffold. It uses Web `Request` and `Response`, request-scoped Model/View/Router/Mediator instances, and the server View entrypoint; it does not require JSX or a provider SDK.
|
|
66
|
-
|
|
67
|
-
Keep immutable configuration at module scope when useful, but create mutable White Label instances per request unless shared state is explicitly intended. Warm serverless processes may handle many requests, so module-level mutable application state can leak data between invocations.
|
|
68
|
-
|
|
69
|
-
Use a thin cloud adapter around the handler when a platform does not expose Web `Request`/`Response` directly. The generated tests exercise repeated and concurrent requests, browser-global absence, Web-target bundling, and serverless bundle/startup budgets.
|
|
70
|
-
|
|
71
|
-
The Web-target bundle check is a portability smoke test, not a blanket claim of compatibility with every edge runtime. Verify the provider runtime you deploy to.
|
|
72
|
-
|
|
73
|
-
If you prefer JSX syntax for templates, generate a new project with `--jsx` or answer **Yes** to the generator's JSX question. JSX and no-JSX projects provide equivalent White Label functionality; the choice is about template syntax and renderer ownership.
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
interface PageData {cdn: string; siteUrl: string; title: string; version: string}
|
|
2
|
-
|
|
3
|
-
function escapeHtml(value: unknown): string {
|
|
4
|
-
return String(value).replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", ''');
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export default function NotFoundPage(data: Record<string, unknown>): string {
|
|
8
|
-
const page = data as unknown as PageData;
|
|
9
|
-
return `<html lang="en-US"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Page not found | ${escapeHtml(page.title)}</title><meta name="robots" content="noindex,follow"><link rel="stylesheet" href="${escapeHtml(page.cdn)}release/${escapeHtml(page.version)}/assets/style/global.css"></head><body><a class="skip-link" href="#main">Skip to the message</a><main id="main" tabindex="-1"><h1>Page not found</h1><p>The requested page could not be found.</p><p><a href="${escapeHtml(page.siteUrl)}/">Return home</a></p></main></body></html>`;
|
|
10
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import {getVisibleTasks, type TaskFilter, type TaskState} from './task-state.js';
|
|
2
|
-
|
|
3
|
-
const filters: TaskFilter[] = ['all', 'active', 'completed'];
|
|
4
|
-
|
|
5
|
-
function escapeHtml(value: unknown): string {
|
|
6
|
-
return String(value)
|
|
7
|
-
.replaceAll('&', '&')
|
|
8
|
-
.replaceAll('<', '<')
|
|
9
|
-
.replaceAll('>', '>')
|
|
10
|
-
.replaceAll('"', '"')
|
|
11
|
-
.replaceAll("'", ''');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** Render the task example with plain TypeScript and HTML strings. */
|
|
15
|
-
export default function TaskExample({state}: {state: TaskState}): string {
|
|
16
|
-
const visibleTasks = getVisibleTasks(state);
|
|
17
|
-
const completed = state.tasks.filter(task => task.complete).length;
|
|
18
|
-
const filterLinks = filters.map(filter => `
|
|
19
|
-
<a href="/?tasks=${filter}#example" data-task-filter="${filter}" data-pushstate aria-current="${state.filter === filter ? 'page' : 'false'}">${filter.charAt(0).toUpperCase()}${filter.slice(1)}</a>
|
|
20
|
-
`).join('');
|
|
21
|
-
const tasks = visibleTasks.map(task => `
|
|
22
|
-
<li><label><input type="checkbox" ${task.complete ? 'checked ' : ''}data-task-toggle data-task-id="${task.id}"><span>${escapeHtml(task.title)}</span></label></li>
|
|
23
|
-
`).join('');
|
|
24
|
-
|
|
25
|
-
return `<section class="task-app" data-task-app aria-label="Interactive task example">
|
|
26
|
-
<form class="task-form" data-task-form action="#example" method="get">
|
|
27
|
-
<label for="task-title">New task</label>
|
|
28
|
-
<div class="task-form__controls"><input id="task-title" name="task" type="text" autocomplete="off" required><button type="submit">Add task</button></div>
|
|
29
|
-
</form>
|
|
30
|
-
<nav class="task-filters" aria-label="Filter tasks">${filterLinks}</nav>
|
|
31
|
-
<ul class="task-list">${tasks}</ul>
|
|
32
|
-
<dl class="demo__trace">
|
|
33
|
-
<div><dt>Router</dt><dd>/?tasks=${state.filter}</dd></div>
|
|
34
|
-
<div><dt>Mediator</dt><dd>task:* application events</dd></div>
|
|
35
|
-
<div><dt>Model</dt><dd>${state.tasks.length} tasks · ${completed} complete</dd></div>
|
|
36
|
-
<div><dt>View</dt><dd>${visibleTasks.length} tasks rendered</dd></div>
|
|
37
|
-
</dl>
|
|
38
|
-
</section>`;
|
|
39
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import TaskExample from './assets/view/examples/tasks/TaskExample.js';
|
|
2
|
-
import {createInitialTaskState, describeTaskStatus} from './assets/view/examples/tasks/task-state.js';
|
|
3
|
-
|
|
4
|
-
interface PageData {
|
|
5
|
-
cdn: string;
|
|
6
|
-
meta: {description: string};
|
|
7
|
-
siteUrl: string;
|
|
8
|
-
structuredData: Record<string, unknown>;
|
|
9
|
-
title: string;
|
|
10
|
-
version: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function escapeHtml(value: unknown): string {
|
|
14
|
-
return String(value)
|
|
15
|
-
.replaceAll('&', '&')
|
|
16
|
-
.replaceAll('<', '<')
|
|
17
|
-
.replaceAll('>', '>')
|
|
18
|
-
.replaceAll('"', '"')
|
|
19
|
-
.replaceAll("'", ''');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** Render the generated starter using plain TypeScript and HTML strings. */
|
|
23
|
-
export default function IndexPage(data: Record<string, unknown>): string {
|
|
24
|
-
const page = data as unknown as PageData;
|
|
25
|
-
const taskState = createInitialTaskState();
|
|
26
|
-
const taskExample = TaskExample({state: taskState});
|
|
27
|
-
const taskStatus = describeTaskStatus(taskState);
|
|
28
|
-
const structuredData = JSON.stringify(page.structuredData).replaceAll('<', '\\u003c');
|
|
29
|
-
|
|
30
|
-
return `<html id="index" lang="en-US" dir="ltr">
|
|
31
|
-
<head>
|
|
32
|
-
<meta charset="utf-8">
|
|
33
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
34
|
-
<title>${escapeHtml(page.title)}</title>
|
|
35
|
-
<meta name="description" content="${escapeHtml(page.meta.description)}">
|
|
36
|
-
<meta name="robots" content="index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1">
|
|
37
|
-
<link rel="canonical" href="${escapeHtml(page.siteUrl)}/">
|
|
38
|
-
<meta property="og:title" content="${escapeHtml(page.title)}">
|
|
39
|
-
<meta property="og:description" content="${escapeHtml(page.meta.description)}">
|
|
40
|
-
<meta property="og:type" content="website">
|
|
41
|
-
<meta property="og:url" content="${escapeHtml(page.siteUrl)}/">
|
|
42
|
-
<link rel="stylesheet" href="${escapeHtml(page.cdn)}release/${escapeHtml(page.version)}/assets/style/global.css" media="all">
|
|
43
|
-
<link rel="stylesheet" href="${escapeHtml(page.cdn)}release/${escapeHtml(page.version)}/assets/style/print.css" media="print">
|
|
44
|
-
<script type="application/ld+json">${structuredData}</script>
|
|
45
|
-
</head>
|
|
46
|
-
<body itemscope itemtype="https://schema.org/WebPage">
|
|
47
|
-
<a class="skip-link" href="#main">Skip to content</a>
|
|
48
|
-
<header><nav aria-label="Primary"><a href="/">White Label</a></nav></header>
|
|
49
|
-
<main id="main" tabindex="-1">
|
|
50
|
-
<section class="hero"><p class="eyebrow">White Label starter</p><h1>Composable web primitives without a framework lock-in.</h1><p>State, rendering, routing, and application events stay small, explicit, and independently understandable.</p></section>
|
|
51
|
-
<section id="example" data-task-example><h2>Interactive task example</h2>${taskExample}<p class="visually-hidden" role="status" aria-live="polite" aria-atomic="true" data-task-status>${escapeHtml(taskStatus)}</p></section>
|
|
52
|
-
<section><h2>Plain TypeScript templates</h2><p>This project was generated without JSX. Pages and views return HTML strings from ordinary TypeScript functions.</p></section>
|
|
53
|
-
</main>
|
|
54
|
-
<footer><p>Built with White Label.</p></footer>
|
|
55
|
-
<script src="${escapeHtml(page.cdn)}release/${escapeHtml(page.version)}/assets/script/index.compiled.js" defer></script>
|
|
56
|
-
</body>
|
|
57
|
-
</html>`;
|
|
58
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
|
-
"strict": true,
|
|
7
|
-
"esModuleInterop": true,
|
|
8
|
-
"declaration": true,
|
|
9
|
-
"sourceMap": true,
|
|
10
|
-
"inlineSources": true,
|
|
11
|
-
"outDir": "dist",
|
|
12
|
-
"rootDir": ".",
|
|
13
|
-
"resolveJsonModule": true,
|
|
14
|
-
"types": [
|
|
15
|
-
"node"
|
|
16
|
-
],
|
|
17
|
-
"noEmitOnError": true
|
|
18
|
-
},
|
|
19
|
-
"include": [
|
|
20
|
-
"scripts/**/*.ts",
|
|
21
|
-
"server/**/*.ts",
|
|
22
|
-
"app/**/*.ts"
|
|
23
|
-
]
|
|
24
|
-
}
|