@nifrajs/web-vanilla 2.1.0 → 2.3.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/README.md +3 -3
- package/dist/compose.d.ts +2 -2
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +8 -1
- package/dist/compose.js.map +1 -1
- package/dist/html.d.ts +6 -6
- package/dist/html.js +9 -9
- package/dist/index.d.ts +6 -6
- package/dist/index.js +8 -8
- package/package.json +3 -3
- package/src/compose.ts +10 -3
- package/src/html.ts +9 -9
- package/src/index.ts +8 -8
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @nifrajs/web-vanilla
|
|
2
2
|
|
|
3
|
-
Zero-framework render adapter for @nifrajs/web
|
|
3
|
+
Zero-framework render adapter for @nifrajs/web - auto-escaping tagged-template HTML, ~0 KB client JS, interactivity via @nifrajs/web/islands.
|
|
4
4
|
|
|
5
|
-
Part of the **[nifra](https://nifra.dev)** full-stack TypeScript framework
|
|
5
|
+
Part of the **[nifra](https://nifra.dev)** full-stack TypeScript framework - one core, five UI libraries, every runtime. Scaffold a new app with `bun create nifra`.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -19,7 +19,7 @@ MIT
|
|
|
19
19
|
|
|
20
20
|
## For AI agents
|
|
21
21
|
|
|
22
|
-
Start with [`LLM.md`](./LLM.md)
|
|
22
|
+
Start with [`LLM.md`](./LLM.md) - this package's contract card (the exports you call + its footguns),
|
|
23
23
|
one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
24
24
|
[`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
|
|
25
25
|
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
package/dist/compose.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ import type { RenderProps } from "@nifrajs/web";
|
|
|
2
2
|
import type { Template } from "./html.js";
|
|
3
3
|
/**
|
|
4
4
|
* A vanilla "component": a plain function from props to a {@link Template}. The page (innermost
|
|
5
|
-
* chain element) receives the loader {@link RenderProps}; a layout receives `{ children }`
|
|
6
|
-
* already-rendered inner fragment
|
|
5
|
+
* chain element) receives the loader {@link RenderProps}; a layout receives `{ children }` - the
|
|
6
|
+
* already-rendered inner fragment - plus the same render props, mirroring the React/Preact
|
|
7
7
|
* adapters' `children` contract.
|
|
8
8
|
*/
|
|
9
9
|
export type VanillaComponent = (props: RenderProps & {
|
package/dist/compose.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAEzC;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,WAAW,GAAG;IAAE,QAAQ,CAAC,EAAE,QAAQ,CAAA;CAAE,KAAK,QAAQ,CAAA;AAEzF;;;GAGG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,QAAQ,
|
|
1
|
+
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAEzC;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,WAAW,GAAG;IAAE,QAAQ,CAAC,EAAE,QAAQ,CAAA;CAAE,KAAK,QAAQ,CAAA;AAEzF;;;GAGG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,QAAQ,CAc/E"}
|
package/dist/compose.js
CHANGED
|
@@ -6,7 +6,14 @@ export function compose(chain, props) {
|
|
|
6
6
|
const last = chain.length - 1;
|
|
7
7
|
let node = chain[last](props);
|
|
8
8
|
for (let i = last - 1; i >= 0; i--) {
|
|
9
|
-
|
|
9
|
+
// Each layout receives its own loader data at its own index. Layouts are the chain's leading
|
|
10
|
+
// prefix, so `layoutData[i]` belongs to `chain[i]`; anything past that end (a client-only `_error`
|
|
11
|
+
// boundary marker, the page) reads `undefined` and is unaffected.
|
|
12
|
+
node = chain[i]({
|
|
13
|
+
...props,
|
|
14
|
+
data: props.layoutData?.[i] ?? null,
|
|
15
|
+
children: node,
|
|
16
|
+
});
|
|
10
17
|
}
|
|
11
18
|
return node;
|
|
12
19
|
}
|
package/dist/compose.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAWA;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,KAAyB,EAAE,KAAkB;IACnE,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;IAC7B,IAAI,IAAI,GAAI,KAAK,CAAC,IAAI,CAAsB,CAAC,KAAK,CAAC,CAAA;IACnD,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,IAAI,GAAI,KAAK,CAAC,CAAC,CAAsB,CAAC,
|
|
1
|
+
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAWA;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,KAAyB,EAAE,KAAkB;IACnE,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;IAC7B,IAAI,IAAI,GAAI,KAAK,CAAC,IAAI,CAAsB,CAAC,KAAK,CAAC,CAAA;IACnD,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,6FAA6F;QAC7F,mGAAmG;QACnG,kEAAkE;QAClE,IAAI,GAAI,KAAK,CAAC,CAAC,CAAsB,CAAC;YACpC,GAAG,KAAK;YACR,IAAI,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI;YACnC,QAAQ,EAAE,IAAI;SACf,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
|
package/dist/html.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The tagged-template HTML engine
|
|
2
|
+
* The tagged-template HTML engine - `html` builds an escaped-by-construction {@link Template}.
|
|
3
3
|
*
|
|
4
4
|
* Security model (the part that matters): every interpolated value is HTML-escaped by default.
|
|
5
|
-
* The ONLY ways to emit unescaped markup are (1) nesting another `html` template
|
|
6
|
-
* by construction
|
|
5
|
+
* The ONLY ways to emit unescaped markup are (1) nesting another `html` template - already escaped
|
|
6
|
+
* by construction - and (2) an explicit {@link raw} wrapper, which is the audit-greppable opt-out.
|
|
7
7
|
* The escaper covers text and double-quoted attribute contexts (`& < > " '`); interpolations into
|
|
8
|
-
* unquoted attributes or inline scripts are NOT safe by design
|
|
8
|
+
* unquoted attributes or inline scripts are NOT safe by design - the docs say "always quote".
|
|
9
9
|
*/
|
|
10
10
|
/** Branded wrapper marking a string as pre-trusted markup. Construct only via {@link raw}. */
|
|
11
11
|
export declare class RawHtml {
|
|
@@ -13,12 +13,12 @@ export declare class RawHtml {
|
|
|
13
13
|
constructor(value: string);
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* Mark a string as trusted, pre-escaped markup
|
|
16
|
+
* Mark a string as trusted, pre-escaped markup - it is emitted verbatim. The deliberate escape
|
|
17
17
|
* hatch (CMS-sanitized HTML, pre-rendered markdown): every call site is greppable, exactly like
|
|
18
18
|
* React's dangerouslySetInnerHTML, without the JSX.
|
|
19
19
|
*/
|
|
20
20
|
export declare function raw(trusted: string): RawHtml;
|
|
21
|
-
/** A rendered HTML fragment
|
|
21
|
+
/** A rendered HTML fragment - what `html` returns and components produce. Stringified once. */
|
|
22
22
|
export declare class Template {
|
|
23
23
|
/** The final markup. Built eagerly at tag time (interpolations are already values by then). */
|
|
24
24
|
readonly html: string;
|
package/dist/html.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The tagged-template HTML engine
|
|
2
|
+
* The tagged-template HTML engine - `html` builds an escaped-by-construction {@link Template}.
|
|
3
3
|
*
|
|
4
4
|
* Security model (the part that matters): every interpolated value is HTML-escaped by default.
|
|
5
|
-
* The ONLY ways to emit unescaped markup are (1) nesting another `html` template
|
|
6
|
-
* by construction
|
|
5
|
+
* The ONLY ways to emit unescaped markup are (1) nesting another `html` template - already escaped
|
|
6
|
+
* by construction - and (2) an explicit {@link raw} wrapper, which is the audit-greppable opt-out.
|
|
7
7
|
* The escaper covers text and double-quoted attribute contexts (`& < > " '`); interpolations into
|
|
8
|
-
* unquoted attributes or inline scripts are NOT safe by design
|
|
8
|
+
* unquoted attributes or inline scripts are NOT safe by design - the docs say "always quote".
|
|
9
9
|
*/
|
|
10
10
|
/** Branded wrapper marking a string as pre-trusted markup. Construct only via {@link raw}. */
|
|
11
11
|
export class RawHtml {
|
|
@@ -15,7 +15,7 @@ export class RawHtml {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* Mark a string as trusted, pre-escaped markup
|
|
18
|
+
* Mark a string as trusted, pre-escaped markup - it is emitted verbatim. The deliberate escape
|
|
19
19
|
* hatch (CMS-sanitized HTML, pre-rendered markdown): every call site is greppable, exactly like
|
|
20
20
|
* React's dangerouslySetInnerHTML, without the JSX.
|
|
21
21
|
*/
|
|
@@ -24,7 +24,7 @@ export function raw(trusted) {
|
|
|
24
24
|
}
|
|
25
25
|
const ESCAPE_RE = /[&<>"']/;
|
|
26
26
|
const escapeHtml = (s) => {
|
|
27
|
-
// Fast path: most text has nothing to escape
|
|
27
|
+
// Fast path: most text has nothing to escape - one regex probe, zero allocation.
|
|
28
28
|
if (!ESCAPE_RE.test(s))
|
|
29
29
|
return s;
|
|
30
30
|
let out = "";
|
|
@@ -55,7 +55,7 @@ const escapeHtml = (s) => {
|
|
|
55
55
|
}
|
|
56
56
|
return out + s.slice(last);
|
|
57
57
|
};
|
|
58
|
-
/** A rendered HTML fragment
|
|
58
|
+
/** A rendered HTML fragment - what `html` returns and components produce. Stringified once. */
|
|
59
59
|
export class Template {
|
|
60
60
|
/** The final markup. Built eagerly at tag time (interpolations are already values by then). */
|
|
61
61
|
html;
|
|
@@ -85,8 +85,8 @@ function renderValue(value) {
|
|
|
85
85
|
out += renderValue(item);
|
|
86
86
|
return out;
|
|
87
87
|
}
|
|
88
|
-
// Objects/functions are a bug at the call site
|
|
89
|
-
throw new TypeError(`[nifra/web-vanilla] html: unsupported interpolation of type ${typeof value}
|
|
88
|
+
// Objects/functions are a bug at the call site - render loudly rather than "[object Object]".
|
|
89
|
+
throw new TypeError(`[nifra/web-vanilla] html: unsupported interpolation of type ${typeof value} - interpolate strings, numbers, nested html\`…\`, raw(), or arrays of those`);
|
|
90
90
|
}
|
|
91
91
|
/** The tag: `` html`<p>${user.name}</p>` `` → an escaped {@link Template}. */
|
|
92
92
|
export function html(strings, ...values) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nifrajs/web-vanilla
|
|
2
|
+
* @nifrajs/web-vanilla - the zero-framework render adapter for @nifrajs/web.
|
|
3
3
|
*
|
|
4
4
|
* Pages are plain functions returning auto-escaping `html` tagged templates; the client ships
|
|
5
5
|
* **no framework runtime at all**. Use it for content/SEO surfaces (landing pages, listings,
|
|
6
|
-
* docs, comparison tables) where HTML is the product
|
|
6
|
+
* docs, comparison tables) where HTML is the product - interactivity comes from
|
|
7
7
|
* `@nifrajs/web/islands` entries (`islandScripts`), each a hand-sized vanilla module.
|
|
8
8
|
*
|
|
9
|
-
* Routes using this adapter are server-rendered documents, not hydrated apps
|
|
9
|
+
* Routes using this adapter are server-rendered documents, not hydrated apps - set
|
|
10
10
|
* `export const hydrate = false` on the route (there is no client runtime to hydrate with);
|
|
11
11
|
* `renderPage` then omits the framework bootstrap entirely. Loaders, actions, ISR, SSG, head
|
|
12
|
-
* management, and streaming all work unchanged
|
|
12
|
+
* management, and streaming all work unchanged - they live in @nifrajs/web, not the view layer.
|
|
13
13
|
*
|
|
14
14
|
* import { html, vanillaAdapter } from "@nifrajs/web-vanilla"
|
|
15
15
|
*
|
|
16
16
|
* export default function Hotels({ data }) {
|
|
17
17
|
* const { hotels } = data as { hotels: Array<{ name: string; pricePaise: number }> }
|
|
18
|
-
* return html`<ul>${hotels.map((h) => html`<li>${h.name}
|
|
18
|
+
* return html`<ul>${hotels.map((h) => html`<li>${h.name} - ₹${h.pricePaise / 100}</li>`)}</ul>`
|
|
19
19
|
* }
|
|
20
20
|
*/
|
|
21
21
|
import type { RenderAdapter } from "@nifrajs/web";
|
|
22
22
|
export { compose, type VanillaComponent } from "./compose.js";
|
|
23
23
|
export { type HtmlValue, html, RawHtml, raw, Template } from "./html.js";
|
|
24
|
-
/** The zero-framework server render adapter
|
|
24
|
+
/** The zero-framework server render adapter - pass to @nifrajs/web's `renderPage`/`createWebApp`. */
|
|
25
25
|
export declare const vanillaAdapter: RenderAdapter;
|
|
26
26
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nifrajs/web-vanilla
|
|
2
|
+
* @nifrajs/web-vanilla - the zero-framework render adapter for @nifrajs/web.
|
|
3
3
|
*
|
|
4
4
|
* Pages are plain functions returning auto-escaping `html` tagged templates; the client ships
|
|
5
5
|
* **no framework runtime at all**. Use it for content/SEO surfaces (landing pages, listings,
|
|
6
|
-
* docs, comparison tables) where HTML is the product
|
|
6
|
+
* docs, comparison tables) where HTML is the product - interactivity comes from
|
|
7
7
|
* `@nifrajs/web/islands` entries (`islandScripts`), each a hand-sized vanilla module.
|
|
8
8
|
*
|
|
9
|
-
* Routes using this adapter are server-rendered documents, not hydrated apps
|
|
9
|
+
* Routes using this adapter are server-rendered documents, not hydrated apps - set
|
|
10
10
|
* `export const hydrate = false` on the route (there is no client runtime to hydrate with);
|
|
11
11
|
* `renderPage` then omits the framework bootstrap entirely. Loaders, actions, ISR, SSG, head
|
|
12
|
-
* management, and streaming all work unchanged
|
|
12
|
+
* management, and streaming all work unchanged - they live in @nifrajs/web, not the view layer.
|
|
13
13
|
*
|
|
14
14
|
* import { html, vanillaAdapter } from "@nifrajs/web-vanilla"
|
|
15
15
|
*
|
|
16
16
|
* export default function Hotels({ data }) {
|
|
17
17
|
* const { hotels } = data as { hotels: Array<{ name: string; pricePaise: number }> }
|
|
18
|
-
* return html`<ul>${hotels.map((h) => html`<li>${h.name}
|
|
18
|
+
* return html`<ul>${hotels.map((h) => html`<li>${h.name} - ₹${h.pricePaise / 100}</li>`)}</ul>`
|
|
19
19
|
* }
|
|
20
20
|
*/
|
|
21
21
|
import { compose } from "./compose.js";
|
|
22
22
|
export { compose } from "./compose.js";
|
|
23
23
|
export { html, RawHtml, raw, Template } from "./html.js";
|
|
24
24
|
const ENCODER = new TextEncoder();
|
|
25
|
-
/** The zero-framework server render adapter
|
|
25
|
+
/** The zero-framework server render adapter - pass to @nifrajs/web's `renderPage`/`createWebApp`. */
|
|
26
26
|
export const vanillaAdapter = {
|
|
27
|
-
// One-pass string render
|
|
27
|
+
// One-pass string render - renderPage's buffered fast path uses this for every non-deferred
|
|
28
28
|
// page, which for vanilla routes is the norm.
|
|
29
29
|
renderToString(chain, props) {
|
|
30
30
|
return compose(chain, props).html;
|
|
31
31
|
},
|
|
32
32
|
// Deferred pages need the streaming seam; a synchronous renderer satisfies it with a one-chunk
|
|
33
|
-
// stream (the shell IS the document
|
|
33
|
+
// stream (the shell IS the document - vanilla has no Suspense boundaries of its own; @nifrajs/web's
|
|
34
34
|
// defer()/<no-js fallback> machinery handles progressive data above this seam).
|
|
35
35
|
renderToStream(chain, props) {
|
|
36
36
|
const body = compose(chain, props).html;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/web-vanilla",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Zero-framework render adapter for @nifrajs/web
|
|
3
|
+
"version": "2.3.0",
|
|
4
|
+
"description": "Zero-framework render adapter for @nifrajs/web - auto-escaping tagged-template HTML, ~0 KB client JS, interactivity via @nifrajs/web/islands.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
7
7
|
"typescript",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"typecheck": "tsc --noEmit"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@nifrajs/web": "^2.
|
|
36
|
+
"@nifrajs/web": "^2.3.0"
|
|
37
37
|
},
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"publishConfig": {
|
package/src/compose.ts
CHANGED
|
@@ -3,8 +3,8 @@ import type { Template } from "./html.ts"
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A vanilla "component": a plain function from props to a {@link Template}. The page (innermost
|
|
6
|
-
* chain element) receives the loader {@link RenderProps}; a layout receives `{ children }`
|
|
7
|
-
* already-rendered inner fragment
|
|
6
|
+
* chain element) receives the loader {@link RenderProps}; a layout receives `{ children }` - the
|
|
7
|
+
* already-rendered inner fragment - plus the same render props, mirroring the React/Preact
|
|
8
8
|
* adapters' `children` contract.
|
|
9
9
|
*/
|
|
10
10
|
export type VanillaComponent = (props: RenderProps & { children?: Template }) => Template
|
|
@@ -17,7 +17,14 @@ export function compose(chain: readonly unknown[], props: RenderProps): Template
|
|
|
17
17
|
const last = chain.length - 1
|
|
18
18
|
let node = (chain[last] as VanillaComponent)(props)
|
|
19
19
|
for (let i = last - 1; i >= 0; i--) {
|
|
20
|
-
|
|
20
|
+
// Each layout receives its own loader data at its own index. Layouts are the chain's leading
|
|
21
|
+
// prefix, so `layoutData[i]` belongs to `chain[i]`; anything past that end (a client-only `_error`
|
|
22
|
+
// boundary marker, the page) reads `undefined` and is unaffected.
|
|
23
|
+
node = (chain[i] as VanillaComponent)({
|
|
24
|
+
...props,
|
|
25
|
+
data: props.layoutData?.[i] ?? null,
|
|
26
|
+
children: node,
|
|
27
|
+
})
|
|
21
28
|
}
|
|
22
29
|
return node
|
|
23
30
|
}
|
package/src/html.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The tagged-template HTML engine
|
|
2
|
+
* The tagged-template HTML engine - `html` builds an escaped-by-construction {@link Template}.
|
|
3
3
|
*
|
|
4
4
|
* Security model (the part that matters): every interpolated value is HTML-escaped by default.
|
|
5
|
-
* The ONLY ways to emit unescaped markup are (1) nesting another `html` template
|
|
6
|
-
* by construction
|
|
5
|
+
* The ONLY ways to emit unescaped markup are (1) nesting another `html` template - already escaped
|
|
6
|
+
* by construction - and (2) an explicit {@link raw} wrapper, which is the audit-greppable opt-out.
|
|
7
7
|
* The escaper covers text and double-quoted attribute contexts (`& < > " '`); interpolations into
|
|
8
|
-
* unquoted attributes or inline scripts are NOT safe by design
|
|
8
|
+
* unquoted attributes or inline scripts are NOT safe by design - the docs say "always quote".
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
/** Branded wrapper marking a string as pre-trusted markup. Construct only via {@link raw}. */
|
|
@@ -17,7 +17,7 @@ export class RawHtml {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* Mark a string as trusted, pre-escaped markup
|
|
20
|
+
* Mark a string as trusted, pre-escaped markup - it is emitted verbatim. The deliberate escape
|
|
21
21
|
* hatch (CMS-sanitized HTML, pre-rendered markdown): every call site is greppable, exactly like
|
|
22
22
|
* React's dangerouslySetInnerHTML, without the JSX.
|
|
23
23
|
*/
|
|
@@ -27,7 +27,7 @@ export function raw(trusted: string): RawHtml {
|
|
|
27
27
|
|
|
28
28
|
const ESCAPE_RE = /[&<>"']/
|
|
29
29
|
const escapeHtml = (s: string): string => {
|
|
30
|
-
// Fast path: most text has nothing to escape
|
|
30
|
+
// Fast path: most text has nothing to escape - one regex probe, zero allocation.
|
|
31
31
|
if (!ESCAPE_RE.test(s)) return s
|
|
32
32
|
let out = ""
|
|
33
33
|
let last = 0
|
|
@@ -58,7 +58,7 @@ const escapeHtml = (s: string): string => {
|
|
|
58
58
|
return out + s.slice(last)
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
/** A rendered HTML fragment
|
|
61
|
+
/** A rendered HTML fragment - what `html` returns and components produce. Stringified once. */
|
|
62
62
|
export class Template {
|
|
63
63
|
/** The final markup. Built eagerly at tag time (interpolations are already values by then). */
|
|
64
64
|
readonly html: string
|
|
@@ -95,9 +95,9 @@ function renderValue(value: HtmlValue): string {
|
|
|
95
95
|
for (const item of value) out += renderValue(item)
|
|
96
96
|
return out
|
|
97
97
|
}
|
|
98
|
-
// Objects/functions are a bug at the call site
|
|
98
|
+
// Objects/functions are a bug at the call site - render loudly rather than "[object Object]".
|
|
99
99
|
throw new TypeError(
|
|
100
|
-
`[nifra/web-vanilla] html: unsupported interpolation of type ${typeof value}
|
|
100
|
+
`[nifra/web-vanilla] html: unsupported interpolation of type ${typeof value} - interpolate strings, numbers, nested html\`…\`, raw(), or arrays of those`,
|
|
101
101
|
)
|
|
102
102
|
}
|
|
103
103
|
|
package/src/index.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nifrajs/web-vanilla
|
|
2
|
+
* @nifrajs/web-vanilla - the zero-framework render adapter for @nifrajs/web.
|
|
3
3
|
*
|
|
4
4
|
* Pages are plain functions returning auto-escaping `html` tagged templates; the client ships
|
|
5
5
|
* **no framework runtime at all**. Use it for content/SEO surfaces (landing pages, listings,
|
|
6
|
-
* docs, comparison tables) where HTML is the product
|
|
6
|
+
* docs, comparison tables) where HTML is the product - interactivity comes from
|
|
7
7
|
* `@nifrajs/web/islands` entries (`islandScripts`), each a hand-sized vanilla module.
|
|
8
8
|
*
|
|
9
|
-
* Routes using this adapter are server-rendered documents, not hydrated apps
|
|
9
|
+
* Routes using this adapter are server-rendered documents, not hydrated apps - set
|
|
10
10
|
* `export const hydrate = false` on the route (there is no client runtime to hydrate with);
|
|
11
11
|
* `renderPage` then omits the framework bootstrap entirely. Loaders, actions, ISR, SSG, head
|
|
12
|
-
* management, and streaming all work unchanged
|
|
12
|
+
* management, and streaming all work unchanged - they live in @nifrajs/web, not the view layer.
|
|
13
13
|
*
|
|
14
14
|
* import { html, vanillaAdapter } from "@nifrajs/web-vanilla"
|
|
15
15
|
*
|
|
16
16
|
* export default function Hotels({ data }) {
|
|
17
17
|
* const { hotels } = data as { hotels: Array<{ name: string; pricePaise: number }> }
|
|
18
|
-
* return html`<ul>${hotels.map((h) => html`<li>${h.name}
|
|
18
|
+
* return html`<ul>${hotels.map((h) => html`<li>${h.name} - ₹${h.pricePaise / 100}</li>`)}</ul>`
|
|
19
19
|
* }
|
|
20
20
|
*/
|
|
21
21
|
|
|
@@ -27,15 +27,15 @@ export { type HtmlValue, html, RawHtml, raw, Template } from "./html.ts"
|
|
|
27
27
|
|
|
28
28
|
const ENCODER = new TextEncoder()
|
|
29
29
|
|
|
30
|
-
/** The zero-framework server render adapter
|
|
30
|
+
/** The zero-framework server render adapter - pass to @nifrajs/web's `renderPage`/`createWebApp`. */
|
|
31
31
|
export const vanillaAdapter: RenderAdapter = {
|
|
32
|
-
// One-pass string render
|
|
32
|
+
// One-pass string render - renderPage's buffered fast path uses this for every non-deferred
|
|
33
33
|
// page, which for vanilla routes is the norm.
|
|
34
34
|
renderToString(chain: readonly unknown[], props: RenderProps): string {
|
|
35
35
|
return compose(chain, props).html
|
|
36
36
|
},
|
|
37
37
|
// Deferred pages need the streaming seam; a synchronous renderer satisfies it with a one-chunk
|
|
38
|
-
// stream (the shell IS the document
|
|
38
|
+
// stream (the shell IS the document - vanilla has no Suspense boundaries of its own; @nifrajs/web's
|
|
39
39
|
// defer()/<no-js fallback> machinery handles progressive data above this seam).
|
|
40
40
|
renderToStream(chain: readonly unknown[], props: RenderProps): ReadableStream<Uint8Array> {
|
|
41
41
|
const body = compose(chain, props).html
|