@jslop/server 0.1.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/LICENSE +21 -0
- package/README.md +55 -0
- package/dist/index.d.ts +95 -0
- package/dist/index.js +359 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 p-arndt
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# @jslop/server
|
|
2
|
+
|
|
3
|
+
SSR for JSlop components. Walks a compiled component's `buildView()` tree, emits HTML, and inlines the state capsule that `@jslop/client` resumes from.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pnpm add @jslop/server
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## API
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { renderPage, renderView } from "@jslop/server";
|
|
13
|
+
|
|
14
|
+
const html = renderPage({
|
|
15
|
+
title: "My page",
|
|
16
|
+
component, // a __jslop_component (default export of a compiled .jslop)
|
|
17
|
+
props: { slug: "hello-world" },
|
|
18
|
+
appScriptUrl: "/client.js", // <script type="module" src="...">
|
|
19
|
+
stylesheets: ["/src/app.css"], // optional <link rel="stylesheet">
|
|
20
|
+
head: "<meta name=\"x\" content=\"y\">", // optional extra <head> HTML
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`renderView(node)` is the lower-level helper that turns a single `ViewNode` into an HTML string.
|
|
25
|
+
|
|
26
|
+
## Output shape
|
|
27
|
+
|
|
28
|
+
```html
|
|
29
|
+
<!doctype html>
|
|
30
|
+
<html>
|
|
31
|
+
<head>
|
|
32
|
+
<title>My page</title>
|
|
33
|
+
<link rel="stylesheet" href="/src/app.css">
|
|
34
|
+
</head>
|
|
35
|
+
<body>
|
|
36
|
+
<div id="__jslop_root">
|
|
37
|
+
<!-- rendered component HTML -->
|
|
38
|
+
</div>
|
|
39
|
+
<script id="__jslop_state" type="application/json">
|
|
40
|
+
{ "count": 0, "children": [ ... ] }
|
|
41
|
+
</script>
|
|
42
|
+
<script type="module" src="/client.js"></script>
|
|
43
|
+
</body>
|
|
44
|
+
</html>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The state capsule is plain JSON — `@jslop/client` reads it via `JSON.parse`. There is no class revival or function deserialization.
|
|
48
|
+
|
|
49
|
+
## Known limitations
|
|
50
|
+
|
|
51
|
+
> [!CAUTION]
|
|
52
|
+
> - **Buffered.** The whole page is rendered to a string before responding. Streaming SSR isn't implemented.
|
|
53
|
+
> - **Whitespace.** Pure-whitespace text nodes still emit a space character — usually harmless but produces ugly HTML.
|
|
54
|
+
|
|
55
|
+
See [`TODO.md`](../../TODO.md) and [docs/ssr-and-resumability.md](../../docs/ssr-and-resumability.md).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export type RenderAttr = string | {
|
|
2
|
+
kind: "bind";
|
|
3
|
+
get: () => string;
|
|
4
|
+
} | {
|
|
5
|
+
kind: "prop";
|
|
6
|
+
get: () => unknown;
|
|
7
|
+
};
|
|
8
|
+
export type RenderNode = {
|
|
9
|
+
kind: "text";
|
|
10
|
+
value: string;
|
|
11
|
+
} | {
|
|
12
|
+
kind: "bind";
|
|
13
|
+
get: () => string;
|
|
14
|
+
} | {
|
|
15
|
+
kind: "component";
|
|
16
|
+
name: string;
|
|
17
|
+
instance: JSlopInstance;
|
|
18
|
+
view: RenderNode;
|
|
19
|
+
} | {
|
|
20
|
+
kind: "if";
|
|
21
|
+
test: () => unknown;
|
|
22
|
+
consequent: RenderNode[];
|
|
23
|
+
alternate: RenderNode[];
|
|
24
|
+
} | {
|
|
25
|
+
kind: "each";
|
|
26
|
+
each: () => Iterable<unknown>;
|
|
27
|
+
build: (item: unknown, index: number) => RenderNode[];
|
|
28
|
+
key?: (item: unknown, index: number) => unknown;
|
|
29
|
+
} | {
|
|
30
|
+
kind: "children";
|
|
31
|
+
} | {
|
|
32
|
+
kind: "element";
|
|
33
|
+
tag: string;
|
|
34
|
+
attrs: Record<string, RenderAttr>;
|
|
35
|
+
events: Record<string, (e: Event) => unknown>;
|
|
36
|
+
children: RenderNode[];
|
|
37
|
+
};
|
|
38
|
+
export interface JSlopInstance {
|
|
39
|
+
actions: Record<string, (...args: unknown[]) => unknown>;
|
|
40
|
+
buildView(): RenderNode;
|
|
41
|
+
buildHead?(): RenderNode[];
|
|
42
|
+
serializeState(): Record<string, unknown>;
|
|
43
|
+
restoreState(s: Record<string, unknown>): void;
|
|
44
|
+
children?: JSlopInstance[];
|
|
45
|
+
}
|
|
46
|
+
export interface JSlopComponent {
|
|
47
|
+
name: string;
|
|
48
|
+
create(props?: Record<string, unknown>): JSlopInstance;
|
|
49
|
+
}
|
|
50
|
+
export interface RenderResult {
|
|
51
|
+
html: string;
|
|
52
|
+
/** Rendered HTML for the page <head>, from each component's `head { … }` block. */
|
|
53
|
+
head: string;
|
|
54
|
+
/** Every component name rendered in this tree (used to inject scoped styles). */
|
|
55
|
+
nestedComponents: Set<string>;
|
|
56
|
+
capsule: {
|
|
57
|
+
components: Array<{
|
|
58
|
+
cid: string;
|
|
59
|
+
name: string;
|
|
60
|
+
props: Record<string, unknown>;
|
|
61
|
+
state: Record<string, unknown>;
|
|
62
|
+
}>;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export declare function renderComponent(component: JSlopComponent, props?: Record<string, unknown>, cid?: string): RenderResult;
|
|
66
|
+
/**
|
|
67
|
+
* Render a route nested inside zero or more layouts. Each layout must contain
|
|
68
|
+
* exactly one `<children/>` placeholder; the inner HTML is substituted in.
|
|
69
|
+
* Capsule entries are merged with unique cids so the client can boot each
|
|
70
|
+
* island independently.
|
|
71
|
+
*/
|
|
72
|
+
export declare function renderRouteChain(opts: {
|
|
73
|
+
route: JSlopComponent;
|
|
74
|
+
routeProps?: Record<string, unknown>;
|
|
75
|
+
/** Outermost layout first. Each layout must have a `<children/>`. */
|
|
76
|
+
layouts?: JSlopComponent[];
|
|
77
|
+
/**
|
|
78
|
+
* Props passed to every layout in the chain. Typically the merged result
|
|
79
|
+
* of each layout's `load()`, so a layout can declare `prop user = null`
|
|
80
|
+
* and have it injected by its own loader.
|
|
81
|
+
*/
|
|
82
|
+
layoutProps?: Record<string, unknown>;
|
|
83
|
+
}): RenderResult;
|
|
84
|
+
export declare function renderPage(opts: {
|
|
85
|
+
title: string;
|
|
86
|
+
component: JSlopComponent;
|
|
87
|
+
/** Layouts wrapping the route, outermost first. Each must have `<children/>`. */
|
|
88
|
+
layouts?: JSlopComponent[];
|
|
89
|
+
appScriptUrl: string;
|
|
90
|
+
props?: Record<string, unknown>;
|
|
91
|
+
/** Props passed to every layout (typically a merged layout-load result). */
|
|
92
|
+
layoutProps?: Record<string, unknown>;
|
|
93
|
+
stylesheets?: string[];
|
|
94
|
+
head?: string;
|
|
95
|
+
}): string;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { getRegisteredStyle } from "@jslop/runtime";
|
|
2
|
+
const VOID_ELEMENTS = new Set([
|
|
3
|
+
"area", "base", "br", "col", "embed", "hr", "img", "input",
|
|
4
|
+
"link", "meta", "param", "source", "track", "wbr",
|
|
5
|
+
]);
|
|
6
|
+
const CHILDREN_PLACEHOLDER = "<jslop-children></jslop-children>";
|
|
7
|
+
// HTML boolean attributes: presence implies true, absence implies false.
|
|
8
|
+
const BOOLEAN_ATTRS = new Set([
|
|
9
|
+
"checked", "disabled", "readonly", "required", "selected", "multiple",
|
|
10
|
+
"hidden", "autofocus", "autoplay", "controls", "loop", "muted",
|
|
11
|
+
"open", "reversed", "default",
|
|
12
|
+
]);
|
|
13
|
+
// Attributes whose values are URLs. Values rendered into these attributes are
|
|
14
|
+
// passed through `safeUrl()` so that `javascript:` / `data:` / `vbscript:` and
|
|
15
|
+
// other script-bearing schemes can never reach the browser's URL parser. The
|
|
16
|
+
// list is the set of standard URL-bearing HTML attributes; if a future feature
|
|
17
|
+
// introduces a new URL attribute, add it here.
|
|
18
|
+
const URL_ATTRS = new Set([
|
|
19
|
+
"href", "src", "action", "formaction", "poster", "data",
|
|
20
|
+
"background", "cite", "longdesc", "manifest", "ping", "srcdoc",
|
|
21
|
+
"usemap", "icon", "xlink:href",
|
|
22
|
+
]);
|
|
23
|
+
// Tags forbidden inside head fragments — these are raw-text / script-bearing
|
|
24
|
+
// elements where naïve string escaping is insufficient. Authors who need
|
|
25
|
+
// inline JS/CSS must inject it through opts.head with their own escaping.
|
|
26
|
+
const FORBIDDEN_HEAD_TAGS = new Set([
|
|
27
|
+
"script", "style", "noscript", "iframe", "object", "embed",
|
|
28
|
+
]);
|
|
29
|
+
// HTML attribute names must match this pattern. Used as a defense-in-depth
|
|
30
|
+
// guard against any future codepath that lets a template/spread produce an
|
|
31
|
+
// arbitrary attribute name. Permissive enough to cover real attributes
|
|
32
|
+
// (`data-*`, `aria-*`, `xlink:href`) but rejects whitespace, quotes, `=`, `/`,
|
|
33
|
+
// `>`, etc., that would let a name break out of the attribute syntax.
|
|
34
|
+
const SAFE_ATTR_NAME = /^[A-Za-z_:][A-Za-z0-9_:.\-]*$/;
|
|
35
|
+
function isSafeAttrName(name) {
|
|
36
|
+
return SAFE_ATTR_NAME.test(name);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Filter a URL destined for an HTML URL attribute (href/src/action/…). Any
|
|
40
|
+
* scheme that isn't a known-safe data scheme is replaced with `about:blank`
|
|
41
|
+
* so that an attacker-controlled value cannot execute script via
|
|
42
|
+
* `javascript:`, `vbscript:`, `data:text/html`, etc.
|
|
43
|
+
*
|
|
44
|
+
* Allowed:
|
|
45
|
+
* - Relative URLs (no scheme), including protocol-relative `//host/…`,
|
|
46
|
+
* fragment-only `#x`, query-only `?x`, absolute paths `/x`.
|
|
47
|
+
* - http: / https: / mailto: / tel: / ftp: / sms:
|
|
48
|
+
* - data:image/* (common safe use case for inline images/favicons).
|
|
49
|
+
* Everything else (including any `javascript:` form, leading whitespace
|
|
50
|
+
* tricks, control-character smuggling) is neutralized.
|
|
51
|
+
*/
|
|
52
|
+
function safeUrl(input) {
|
|
53
|
+
// Strip control characters (\x00-\x1F and \x7F) which browsers historically
|
|
54
|
+
// tolerate inside the scheme and which can be used to disguise `javascript:`
|
|
55
|
+
// as `java\tscript:` etc. Then trim surrounding whitespace.
|
|
56
|
+
// eslint-disable-next-line no-control-regex
|
|
57
|
+
const cleaned = input.replace(/[\x00-\x1F\x7F]/g, "").trim();
|
|
58
|
+
if (cleaned === "")
|
|
59
|
+
return "";
|
|
60
|
+
// Relative URL: no scheme. The first character is not an ASCII letter, or
|
|
61
|
+
// there is no `:` before the first `/`, `?`, or `#`.
|
|
62
|
+
const schemeMatch = /^([A-Za-z][A-Za-z0-9+.\-]*):/.exec(cleaned);
|
|
63
|
+
if (!schemeMatch)
|
|
64
|
+
return cleaned;
|
|
65
|
+
const scheme = schemeMatch[1].toLowerCase();
|
|
66
|
+
if (scheme === "http" || scheme === "https" || scheme === "mailto" ||
|
|
67
|
+
scheme === "tel" || scheme === "ftp" || scheme === "sms") {
|
|
68
|
+
return cleaned;
|
|
69
|
+
}
|
|
70
|
+
if (scheme === "data") {
|
|
71
|
+
// Only allow image data: URLs (common for favicons / inline thumbnails).
|
|
72
|
+
// Block `data:text/html`, `data:application/*`, etc., which can execute
|
|
73
|
+
// script via top-level navigation or iframe srcdoc-like contexts.
|
|
74
|
+
if (/^data:image\/(png|jpe?g|gif|webp|avif|svg\+xml|x-icon|vnd\.microsoft\.icon)[;,]/i.test(cleaned)) {
|
|
75
|
+
// SVG can contain script — refuse svg+xml unless explicitly base64-encoded
|
|
76
|
+
// and even then it's risky in some contexts, but base64-svg via <img>
|
|
77
|
+
// is generally safe. We allow it; authors using it in <object>/<iframe>
|
|
78
|
+
// are accepting the risk.
|
|
79
|
+
return cleaned;
|
|
80
|
+
}
|
|
81
|
+
return "about:blank";
|
|
82
|
+
}
|
|
83
|
+
return "about:blank";
|
|
84
|
+
}
|
|
85
|
+
export function renderComponent(component, props = {}, cid = "c0") {
|
|
86
|
+
const instance = component.create(props);
|
|
87
|
+
const view = instance.buildView();
|
|
88
|
+
if (view.kind !== "element") {
|
|
89
|
+
throw new Error(`component ${component.name}: root view must be an element`);
|
|
90
|
+
}
|
|
91
|
+
// Track every nested component name encountered while rendering this tree
|
|
92
|
+
// so renderPage can emit a <style> tag for each unique name. Without this,
|
|
93
|
+
// a route's <PresetCard/>s in an each block would inherit no styles.
|
|
94
|
+
const nestedComponents = new Set([component.name]);
|
|
95
|
+
const html = renderElement(view, { cid, componentName: component.name }, nestedComponents);
|
|
96
|
+
const head = renderHeadNodes(instance.buildHead?.() ?? []);
|
|
97
|
+
return {
|
|
98
|
+
html,
|
|
99
|
+
head,
|
|
100
|
+
nestedComponents,
|
|
101
|
+
capsule: {
|
|
102
|
+
components: [{ cid, name: component.name, props, state: instance.serializeState() }],
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function renderHeadNodes(nodes) {
|
|
107
|
+
return nodes.map((n) => renderHeadNode(n)).join("");
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Render a head fragment node WITHOUT the <jslop-b> bind wrapper that the body
|
|
111
|
+
* renderer emits — head text bindings (e.g. inside <title>) must produce raw
|
|
112
|
+
* text so the browser sees a clean tag. Head is server-only anyway; there is
|
|
113
|
+
* no client-side reactivity to hydrate against.
|
|
114
|
+
*/
|
|
115
|
+
function renderHeadNode(node) {
|
|
116
|
+
if (node.kind === "text")
|
|
117
|
+
return escapeHtml(node.value);
|
|
118
|
+
if (node.kind === "bind")
|
|
119
|
+
return escapeHtml(node.get());
|
|
120
|
+
if (node.kind === "element") {
|
|
121
|
+
// Raw-text / script-bearing elements need context-specific escaping
|
|
122
|
+
// that plain HTML escaping doesn't provide. Forbid them in head fragments;
|
|
123
|
+
// authors who need them can inject via opts.head with their own escaping.
|
|
124
|
+
if (FORBIDDEN_HEAD_TAGS.has(node.tag.toLowerCase())) {
|
|
125
|
+
throw new Error(`head fragments may not contain <${node.tag}> — inject it via opts.head with explicit escaping`);
|
|
126
|
+
}
|
|
127
|
+
const attrParts = [];
|
|
128
|
+
for (const [k, v] of Object.entries(node.attrs)) {
|
|
129
|
+
if (!isSafeAttrName(k))
|
|
130
|
+
continue;
|
|
131
|
+
const isUrl = URL_ATTRS.has(k.toLowerCase());
|
|
132
|
+
const filter = (raw) => escapeAttr(isUrl ? safeUrl(raw) : raw);
|
|
133
|
+
if (typeof v === "string")
|
|
134
|
+
attrParts.push(`${k}="${filter(v)}"`);
|
|
135
|
+
else if (v.kind === "bind")
|
|
136
|
+
attrParts.push(`${k}="${filter(v.get())}"`);
|
|
137
|
+
else if (v.kind === "prop")
|
|
138
|
+
attrParts.push(`${k}="${filter(String(v.get() ?? ""))}"`);
|
|
139
|
+
}
|
|
140
|
+
const open = `<${node.tag}${attrParts.length ? " " + attrParts.join(" ") : ""}>`;
|
|
141
|
+
if (VOID_ELEMENTS.has(node.tag))
|
|
142
|
+
return open;
|
|
143
|
+
const inner = node.children.map(renderHeadNode).join("");
|
|
144
|
+
return `${open}${inner}</${node.tag}>`;
|
|
145
|
+
}
|
|
146
|
+
throw new Error(`head fragments only support static elements, text, and {expr} — got '${node.kind}'`);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Render a route nested inside zero or more layouts. Each layout must contain
|
|
150
|
+
* exactly one `<children/>` placeholder; the inner HTML is substituted in.
|
|
151
|
+
* Capsule entries are merged with unique cids so the client can boot each
|
|
152
|
+
* island independently.
|
|
153
|
+
*/
|
|
154
|
+
export function renderRouteChain(opts) {
|
|
155
|
+
const layouts = opts.layouts ?? [];
|
|
156
|
+
let cidCounter = 0;
|
|
157
|
+
const nextCid = () => `c${cidCounter++}`;
|
|
158
|
+
const routeResult = renderComponent(opts.route, opts.routeProps ?? {}, nextCid());
|
|
159
|
+
let html = routeResult.html;
|
|
160
|
+
const components = [...routeResult.capsule.components];
|
|
161
|
+
const nestedComponents = new Set(routeResult.nestedComponents);
|
|
162
|
+
// Layouts contribute their head first (outermost → innermost), then the
|
|
163
|
+
// route's head — so the route's <title>/meta ends up last in the document
|
|
164
|
+
// head and wins for any duplicate tags the browser de-dupes by position.
|
|
165
|
+
const headParts = [];
|
|
166
|
+
// Wrap innermost-first so each replacement targets the *current* outermost
|
|
167
|
+
// placeholder. The CHILDREN_PLACEHOLDER constant is unique enough that a
|
|
168
|
+
// literal string replace is safe here (we never emit it for user content).
|
|
169
|
+
for (let i = layouts.length - 1; i >= 0; i--) {
|
|
170
|
+
const layout = layouts[i];
|
|
171
|
+
const layoutResult = renderComponent(layout, opts.layoutProps ?? {}, nextCid());
|
|
172
|
+
const idx = layoutResult.html.indexOf(CHILDREN_PLACEHOLDER);
|
|
173
|
+
if (idx === -1) {
|
|
174
|
+
throw new Error(`layout ${layout.name} has no <children/> element — required for routed content`);
|
|
175
|
+
}
|
|
176
|
+
html =
|
|
177
|
+
layoutResult.html.slice(0, idx) +
|
|
178
|
+
html +
|
|
179
|
+
layoutResult.html.slice(idx + CHILDREN_PLACEHOLDER.length);
|
|
180
|
+
components.push(...layoutResult.capsule.components);
|
|
181
|
+
for (const n of layoutResult.nestedComponents)
|
|
182
|
+
nestedComponents.add(n);
|
|
183
|
+
headParts.unshift(layoutResult.head);
|
|
184
|
+
}
|
|
185
|
+
headParts.push(routeResult.head);
|
|
186
|
+
return { html, head: headParts.join(""), nestedComponents, capsule: { components } };
|
|
187
|
+
}
|
|
188
|
+
function renderNode(node, registry) {
|
|
189
|
+
if (node.kind === "text")
|
|
190
|
+
return escapeHtml(node.value);
|
|
191
|
+
if (node.kind === "children")
|
|
192
|
+
return CHILDREN_PLACEHOLDER;
|
|
193
|
+
if (node.kind === "bind") {
|
|
194
|
+
return `<jslop-b>${escapeHtml(node.get())}</jslop-b>`;
|
|
195
|
+
}
|
|
196
|
+
if (node.kind === "if") {
|
|
197
|
+
const active = !!node.test();
|
|
198
|
+
const branch = active ? node.consequent : node.alternate;
|
|
199
|
+
const inner = branch.map((c) => renderNode(c, registry)).join("");
|
|
200
|
+
return `<jslop-if data-jslop-active="${active ? "t" : "f"}">${inner}</jslop-if>`;
|
|
201
|
+
}
|
|
202
|
+
if (node.kind === "each") {
|
|
203
|
+
const source = node.each();
|
|
204
|
+
let i = 0;
|
|
205
|
+
const itemsHtml = [];
|
|
206
|
+
const keyed = typeof node.key === "function";
|
|
207
|
+
for (const item of source) {
|
|
208
|
+
const itemChildren = node.build(item, i);
|
|
209
|
+
const keyAttr = keyed
|
|
210
|
+
? ` data-jslop-key="${escapeAttr(String(node.key(item, i)))}"`
|
|
211
|
+
: "";
|
|
212
|
+
itemsHtml.push(`<jslop-each-item${keyAttr} style="display:contents">${itemChildren.map((c) => renderNode(c, registry)).join("")}</jslop-each-item>`);
|
|
213
|
+
i++;
|
|
214
|
+
}
|
|
215
|
+
const keyedAttr = keyed ? ` data-jslop-keyed="t"` : "";
|
|
216
|
+
return `<jslop-each data-jslop-count="${i}"${keyedAttr} style="display:contents">${itemsHtml.join("")}</jslop-each>`;
|
|
217
|
+
}
|
|
218
|
+
if (node.kind === "component") {
|
|
219
|
+
if (node.view.kind !== "element") {
|
|
220
|
+
throw new Error(`component ${node.name}: root view must be an element`);
|
|
221
|
+
}
|
|
222
|
+
registry.add(node.name);
|
|
223
|
+
return renderElement(node.view, { componentName: node.name }, registry);
|
|
224
|
+
}
|
|
225
|
+
return renderElement(node, {}, registry);
|
|
226
|
+
}
|
|
227
|
+
function renderElement(node, marker, registry) {
|
|
228
|
+
const attrParts = [];
|
|
229
|
+
for (const [k, v] of Object.entries(node.attrs)) {
|
|
230
|
+
// Reject any attribute name that contains characters which could break
|
|
231
|
+
// out of the `name="value"` syntax. The compiler today only emits valid
|
|
232
|
+
// identifiers, but this is defense-in-depth against future spread/
|
|
233
|
+
// computed-attribute features.
|
|
234
|
+
if (!isSafeAttrName(k))
|
|
235
|
+
continue;
|
|
236
|
+
const isUrl = URL_ATTRS.has(k.toLowerCase());
|
|
237
|
+
const filter = (raw) => escapeAttr(isUrl ? safeUrl(raw) : raw);
|
|
238
|
+
if (typeof v === "string") {
|
|
239
|
+
attrParts.push(`${k}="${filter(v)}"`);
|
|
240
|
+
}
|
|
241
|
+
else if (v.kind === "bind") {
|
|
242
|
+
const val = v.get();
|
|
243
|
+
attrParts.push(`${k}="${filter(val)}" data-jslop-attr-${k}=""`);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
// kind: "prop" — boolean attributes render presence-based, others
|
|
247
|
+
// stringify their value into the attribute.
|
|
248
|
+
const val = v.get();
|
|
249
|
+
if (BOOLEAN_ATTRS.has(k)) {
|
|
250
|
+
if (val)
|
|
251
|
+
attrParts.push(`${k}="" data-jslop-prop-${k}=""`);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
attrParts.push(`${k}="${filter(String(val ?? ""))}" data-jslop-prop-${k}=""`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
for (const evt of Object.keys(node.events)) {
|
|
259
|
+
// Event names are emitted into the attribute *name* position
|
|
260
|
+
// (`data-jslop-on-<evt>`), so they must be strictly validated. Only
|
|
261
|
+
// lowercase alphanumeric plus the `:` namespace separator used by
|
|
262
|
+
// bind:value etc. is allowed.
|
|
263
|
+
if (!/^[a-z][a-z0-9]*(?::[a-z0-9]+)*$/.test(evt))
|
|
264
|
+
continue;
|
|
265
|
+
attrParts.push(`data-jslop-on-${evt}=""`);
|
|
266
|
+
}
|
|
267
|
+
if (marker.cid) {
|
|
268
|
+
attrParts.push(`data-jslop-cid="${escapeAttr(marker.cid)}"`);
|
|
269
|
+
}
|
|
270
|
+
if (marker.componentName) {
|
|
271
|
+
attrParts.push(`data-jslop-component="${escapeAttr(marker.componentName)}"`);
|
|
272
|
+
}
|
|
273
|
+
const open = `<${node.tag}${attrParts.length ? " " + attrParts.join(" ") : ""}>`;
|
|
274
|
+
if (VOID_ELEMENTS.has(node.tag))
|
|
275
|
+
return open;
|
|
276
|
+
const inner = node.children.map((c) => renderNode(c, registry)).join("");
|
|
277
|
+
return `${open}${inner}</${node.tag}>`;
|
|
278
|
+
}
|
|
279
|
+
export function renderPage(opts) {
|
|
280
|
+
const { html, head: componentHead, nestedComponents, capsule } = opts.layouts && opts.layouts.length > 0
|
|
281
|
+
? renderRouteChain({
|
|
282
|
+
route: opts.component,
|
|
283
|
+
routeProps: opts.props ?? {},
|
|
284
|
+
layouts: opts.layouts,
|
|
285
|
+
layoutProps: opts.layoutProps ?? {},
|
|
286
|
+
})
|
|
287
|
+
: renderComponent(opts.component, opts.props ?? {});
|
|
288
|
+
const capsuleJson = serializeForScript(capsule);
|
|
289
|
+
const linkTags = (opts.stylesheets ?? [])
|
|
290
|
+
.map((href) => `<link rel="stylesheet" href="${escapeAttr(safeUrl(href))}">`)
|
|
291
|
+
.join("");
|
|
292
|
+
const appScriptUrl = escapeAttr(safeUrl(opts.appScriptUrl));
|
|
293
|
+
const extraHead = opts.head ?? "";
|
|
294
|
+
// If the component emitted its own <title>, use it as the document title and
|
|
295
|
+
// suppress the fallback (browsers honor the *last* <title>, but emitting two
|
|
296
|
+
// is sloppy). Search the rendered head for a <title>…</title> tag.
|
|
297
|
+
const titleMatch = /<title[^>]*>([\s\S]*?)<\/title>/.exec(componentHead);
|
|
298
|
+
const fallbackTitle = titleMatch ? "" : `<title>${escapeHtml(opts.title)}</title>\n`;
|
|
299
|
+
// Collect one <style> per unique component name rendered on this page. The
|
|
300
|
+
// capsule lists every mounted component in order; dedup by name so a list of
|
|
301
|
+
// 100 <Card/>s only emits the Card style once.
|
|
302
|
+
const styleTags = [];
|
|
303
|
+
const emittedStyleScopes = new Set();
|
|
304
|
+
for (const name of nestedComponents) {
|
|
305
|
+
const reg = getRegisteredStyle(name);
|
|
306
|
+
if (!reg || emittedStyleScopes.has(reg.scope))
|
|
307
|
+
continue;
|
|
308
|
+
emittedStyleScopes.add(reg.scope);
|
|
309
|
+
styleTags.push(`<style data-jslop-style="${escapeAttr(reg.scope)}">${reg.css}</style>`);
|
|
310
|
+
}
|
|
311
|
+
const componentStyles = styleTags.join("");
|
|
312
|
+
return `<!doctype html>
|
|
313
|
+
<html lang="en">
|
|
314
|
+
<head>
|
|
315
|
+
<meta charset="utf-8">
|
|
316
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
317
|
+
${fallbackTitle}${linkTags}${componentStyles}${componentHead}${extraHead}</head>
|
|
318
|
+
<body>
|
|
319
|
+
<div id="app">${html}</div>
|
|
320
|
+
<script type="application/jslop+json" id="__jslop_capsule">${capsuleJson}</script>
|
|
321
|
+
<script type="module" src="${appScriptUrl}"></script>
|
|
322
|
+
</body>
|
|
323
|
+
</html>`;
|
|
324
|
+
}
|
|
325
|
+
function escapeHtml(s) {
|
|
326
|
+
return s
|
|
327
|
+
.replace(/&/g, "&")
|
|
328
|
+
.replace(/</g, "<")
|
|
329
|
+
.replace(/>/g, ">");
|
|
330
|
+
}
|
|
331
|
+
function escapeAttr(s) {
|
|
332
|
+
return s
|
|
333
|
+
.replace(/&/g, "&")
|
|
334
|
+
.replace(/"/g, """)
|
|
335
|
+
.replace(/'/g, "'")
|
|
336
|
+
.replace(/</g, "<")
|
|
337
|
+
.replace(/>/g, ">");
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Serialize a value for embedding inside a `<script>` tag. Escapes:
|
|
341
|
+
* - `<` → < so `</script>` cannot break out of the script element.
|
|
342
|
+
* - `>` → > for symmetry / belt-and-braces against parser quirks.
|
|
343
|
+
* - `&` → & so HTML entity decoding in attribute-like contexts is inert.
|
|
344
|
+
* - U+2028 / U+2029 → / so the JSON remains a valid JS string
|
|
345
|
+
* literal if the embedding context is ever changed to `text/javascript`.
|
|
346
|
+
*
|
|
347
|
+
* The capsule is embedded in `<script type="application/jslop+json">` today,
|
|
348
|
+
* which browsers treat as inert data — but this serializer is also safe for
|
|
349
|
+
* future call sites that embed JSON into JS contexts directly.
|
|
350
|
+
*/
|
|
351
|
+
function serializeForScript(value) {
|
|
352
|
+
return JSON.stringify(value)
|
|
353
|
+
.replace(/</g, "\\u003c")
|
|
354
|
+
.replace(/>/g, "\\u003e")
|
|
355
|
+
.replace(/&/g, "\\u0026")
|
|
356
|
+
.replace(/\u2028/g, "\\u2028")
|
|
357
|
+
.replace(/\u2029/g, "\\u2029");
|
|
358
|
+
}
|
|
359
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jslop/server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Server-side renderer for JSlop. Renders components to HTML and emits a serialized state capsule that @jslop/client consumes on hydration.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"jslop",
|
|
7
|
+
"ssr",
|
|
8
|
+
"server",
|
|
9
|
+
"framework"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"author": "p-arndt",
|
|
13
|
+
"homepage": "https://github.com/p-arndt/jslop#readme",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/p-arndt/jslop.git",
|
|
17
|
+
"directory": "packages/server"
|
|
18
|
+
},
|
|
19
|
+
"bugs": "https://github.com/p-arndt/jslop/issues",
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=20"
|
|
22
|
+
},
|
|
23
|
+
"type": "module",
|
|
24
|
+
"main": "./dist/index.js",
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"README.md"
|
|
35
|
+
],
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@jslop/runtime": "^0.1.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"typescript": "^5.6.3"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsc -p tsconfig.json"
|
|
47
|
+
}
|
|
48
|
+
}
|