@purestack/ts-render 1.1.2 → 1.1.3
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 +10 -2
- package/dist/ts-render.d.mts +12 -6
- package/dist/ts-render.d.mts.map +1 -1
- package/dist/ts-render.mjs +23 -30
- package/dist/ts-render.mjs.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
# @purestack/ts-render
|
|
2
2
|
|
|
3
3
|
Server-side rendering for PureStack's Regor components. `renderApp` takes HTML,
|
|
4
|
-
a component set, and a `TsSsgContext`,
|
|
5
|
-
required component scripts attached.
|
|
4
|
+
a component set, and a `TsSsgContext`, and resolves to the rendered HTML with
|
|
5
|
+
any required component scripts attached.
|
|
6
|
+
|
|
7
|
+
Each render gets a document of its own. With an async DOM scope installed, as
|
|
8
|
+
the site builder does, renders can run at the same time; see `useDomScope` in
|
|
9
|
+
`@purestack/ts-minidom`.
|
|
10
|
+
|
|
11
|
+
`onRendered(document)` runs on the rendered document before it becomes HTML.
|
|
12
|
+
It may be async, and the document stays that render's own while it runs.
|
|
6
13
|
|
|
7
14
|
`componentRegistry` lets custom build pipelines register components by name.
|
|
15
|
+
The components passed to `renderApp` apply on top of them, for that render only.
|
|
8
16
|
The site generator uses this package to turn page markup into static output.
|
package/dist/ts-render.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @purestack/ts-render v1.1.
|
|
1
|
+
/*! @purestack/ts-render v1.1.3 | (c) 2026 Ahmed Yasin Koculu | MIT License */
|
|
2
2
|
|
|
3
3
|
import { Component } from "regor";
|
|
4
4
|
import { TsSsgContext } from "@purestack/ts-common";
|
|
@@ -8,19 +8,25 @@ export declare const componentRegistry: {
|
|
|
8
8
|
registerMany<TComponents>(...components: (TComponents | object)[]): void;
|
|
9
9
|
getAll(): Record<string, Component>;
|
|
10
10
|
hasComponentName(name: string): boolean;
|
|
11
|
-
snapshot(): Map<string, Component<never>>;
|
|
12
|
-
restore(snapshot: Map<string, Component<never>>): void;
|
|
13
11
|
clear(): void;
|
|
14
12
|
};
|
|
15
13
|
//#endregion
|
|
16
14
|
//#region packages/ts-render/src/renderApp.d.ts
|
|
17
15
|
interface RenderAppOptions<TContext extends TsSsgContext> {
|
|
16
|
+
/** Components for this render, on top of the registered ones. */
|
|
18
17
|
components: unknown;
|
|
19
18
|
context: TContext;
|
|
20
|
-
/**
|
|
21
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Runs on the rendered document, before it becomes HTML. It may be async:
|
|
21
|
+
* the document stays this render's own until it finishes.
|
|
22
|
+
*/
|
|
23
|
+
onRendered?: (document: Document) => void | Promise<void>;
|
|
22
24
|
}
|
|
23
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Renders Regor markup to HTML in a document of its own, so renders can
|
|
27
|
+
* overlap, and `onRendered` can await, without seeing each other's pages.
|
|
28
|
+
*/
|
|
29
|
+
export declare const renderApp: <TContext extends TsSsgContext>(html: string, options: RenderAppOptions<TContext>) => Promise<string>;
|
|
24
30
|
//#endregion
|
|
25
31
|
export type { RenderAppOptions };
|
|
26
32
|
//# sourceMappingURL=ts-render.d.mts.map
|
package/dist/ts-render.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ts-render.d.mts","names":[],"sources":["../src/componentRegistry.ts","../src/renderApp.ts"],"mappings":";;;qBAKa;EACX,SAAQ,cAAa,WAAa;EAGlC,aAAa,gBAAW,aAAkB;EAQ1C,UAAU,eAAe;EAGzB,iBAAgB;EAGhB
|
|
1
|
+
{"version":3,"file":"ts-render.d.mts","names":[],"sources":["../src/componentRegistry.ts","../src/renderApp.ts"],"mappings":";;;qBAKa;EACX,SAAQ,cAAa,WAAa;EAGlC,aAAa,gBAAW,aAAkB;EAQ1C,UAAU,eAAe;EAGzB,iBAAgB;EAGhB;;;;UChBe,iBAAiB,iBAAiB;;EAEjD;EACA,SAAS;;;;;EAKT,cAAc,UAAU,oBAAoB;;;;;;qBAOjC,YAAa,iBAAiB,cAAY,cACzC,SACH,iBAAiB,cACzB"}
|
package/dist/ts-render.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
/*! @purestack/ts-render v1.1.
|
|
1
|
+
/*! @purestack/ts-render v1.1.3 | (c) 2026 Ahmed Yasin Koculu | MIT License */
|
|
2
2
|
|
|
3
3
|
import { isPlainObject } from "@purestack/ts-util";
|
|
4
|
-
import {
|
|
4
|
+
import { runInDom } from "@purestack/ts-minidom";
|
|
5
5
|
import { buildModalScript, buildTabsScript } from "@purestack/ts-page-scripts";
|
|
6
6
|
import { createApp } from "regor";
|
|
7
7
|
|
|
@@ -23,13 +23,6 @@ const componentRegistry = {
|
|
|
23
23
|
hasComponentName(name) {
|
|
24
24
|
return hasComponentName(name, registry);
|
|
25
25
|
},
|
|
26
|
-
snapshot() {
|
|
27
|
-
return new Map(registry);
|
|
28
|
-
},
|
|
29
|
-
restore(snapshot) {
|
|
30
|
-
registry.clear();
|
|
31
|
-
for (const [name, component] of snapshot) registry.set(name, component);
|
|
32
|
-
},
|
|
33
26
|
clear() {
|
|
34
27
|
registry.clear();
|
|
35
28
|
}
|
|
@@ -52,37 +45,37 @@ function normalizeComponentName(value) {
|
|
|
52
45
|
|
|
53
46
|
//#endregion
|
|
54
47
|
//#region packages/ts-render/src/renderApp.ts
|
|
48
|
+
/**
|
|
49
|
+
* Renders Regor markup to HTML in a document of its own, so renders can
|
|
50
|
+
* overlap, and `onRendered` can await, without seeing each other's pages.
|
|
51
|
+
*/
|
|
55
52
|
const renderApp = (html, options) => {
|
|
56
53
|
const normalizedHtml = html.trimStart();
|
|
57
54
|
const isDocument = normalizedHtml.startsWith("<!DOCTYPE html>") || normalizedHtml.startsWith("<!doctype html>") || normalizedHtml.startsWith("<html");
|
|
58
55
|
const htmlToParse = isDocument ? normalizedHtml : `<!DOCTYPE html><html><body>${html}</body></html>`;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
try {
|
|
72
|
-
if (options.components) componentRegistry.registerMany(options.components);
|
|
73
|
-
const components = { ...componentRegistry.getAll() };
|
|
56
|
+
return runInDom(htmlToParse, async (document) => {
|
|
57
|
+
const runtimeEmbeds = /* @__PURE__ */ new Map();
|
|
58
|
+
const baseContext = options.context;
|
|
59
|
+
const tsSsgContext = {
|
|
60
|
+
...baseContext,
|
|
61
|
+
recordRuntimeEmbed: (name, position) => {
|
|
62
|
+
const normalized = name.trim().toLowerCase();
|
|
63
|
+
if (normalized.length > 0) runtimeEmbeds.set(normalized, position);
|
|
64
|
+
baseContext.recordRuntimeEmbed(name, position);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
74
67
|
createApp({
|
|
75
|
-
components
|
|
68
|
+
components: {
|
|
69
|
+
...componentRegistry.getAll(),
|
|
70
|
+
...isPlainObject(options.components) ? options.components : {}
|
|
71
|
+
},
|
|
76
72
|
tsSsgContext
|
|
77
73
|
}, { element: document.body });
|
|
78
74
|
appendEmbeddedScriptsToDom(runtimeEmbeds, tsSsgContext);
|
|
79
|
-
options.onRendered?.(document);
|
|
75
|
+
await options.onRendered?.(document);
|
|
80
76
|
if (isDocument) return `<!DOCTYPE html>${document.documentElement?.outerHTML ?? ""}`;
|
|
81
77
|
return document.body?.innerHTML ?? "";
|
|
82
|
-
}
|
|
83
|
-
componentRegistry.restore(snapshot);
|
|
84
|
-
cleanup();
|
|
85
|
-
}
|
|
78
|
+
});
|
|
86
79
|
};
|
|
87
80
|
function appendEmbeddedScriptsToDom(runtimeEmbeds, context) {
|
|
88
81
|
appendRuntimeScript(resolveTabsEmbedPosition(runtimeEmbeds, context), buildTabsScript);
|
package/dist/ts-render.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ts-render.mjs","names":[],"sources":["../src/componentRegistry.ts","../src/renderApp.ts"],"sourcesContent":["import { isPlainObject } from '@purestack/ts-util'\nimport type { Component } from 'regor'\n\nconst registry = new Map<string, Component<never>>()\n\nexport const componentRegistry = {\n register(name: string, component: Component<never>) {\n registry.set(name, component)\n },\n registerMany<TComponents>(...components: (TComponents | object)[]) {\n for (const compSet of components) {\n if (!isPlainObject(compSet)) continue\n for (const [name, component] of Object.entries(compSet)) {\n registry.set(name, component as Component<never>)\n }\n }\n },\n getAll(): Record<string, Component> {\n return Object.fromEntries(registry.entries())\n },\n hasComponentName(name: string) {\n return hasComponentName(name, registry)\n },\n
|
|
1
|
+
{"version":3,"file":"ts-render.mjs","names":[],"sources":["../src/componentRegistry.ts","../src/renderApp.ts"],"sourcesContent":["import { isPlainObject } from '@purestack/ts-util'\nimport type { Component } from 'regor'\n\nconst registry = new Map<string, Component<never>>()\n\nexport const componentRegistry = {\n register(name: string, component: Component<never>) {\n registry.set(name, component)\n },\n registerMany<TComponents>(...components: (TComponents | object)[]) {\n for (const compSet of components) {\n if (!isPlainObject(compSet)) continue\n for (const [name, component] of Object.entries(compSet)) {\n registry.set(name, component as Component<never>)\n }\n }\n },\n getAll(): Record<string, Component> {\n return Object.fromEntries(registry.entries())\n },\n hasComponentName(name: string) {\n return hasComponentName(name, registry)\n },\n clear() {\n registry.clear()\n },\n}\n\nfunction hasComponentName(\n input: string,\n source: Map<string, Component<never>>,\n) {\n const normalizedInput = normalizeComponentName(input)\n if (!normalizedInput) return false\n for (const key of source.keys()) {\n const normalizedKey = normalizeComponentName(key)\n if (!normalizedKey) continue\n if (normalizedKey === normalizedInput) return true\n }\n return false\n}\n\nfunction normalizeComponentName(value: string) {\n const trimmed = value.trim()\n if (!trimmed) return ''\n return trimmed.toLowerCase().replace(/[^a-z0-9]/g, '')\n}\n","import type { TsSsgContext } from '@purestack/ts-common'\nimport { runInDom } from '@purestack/ts-minidom'\nimport { buildModalScript, buildTabsScript } from '@purestack/ts-page-scripts'\nimport { isPlainObject } from '@purestack/ts-util'\nimport { createApp } from 'regor'\nimport { componentRegistry } from './componentRegistry'\n\nexport interface RenderAppOptions<TContext extends TsSsgContext> {\n /** Components for this render, on top of the registered ones. */\n components: unknown\n context: TContext\n /**\n * Runs on the rendered document, before it becomes HTML. It may be async:\n * the document stays this render's own until it finishes.\n */\n onRendered?: (document: Document) => void | Promise<void>\n}\n\n/**\n * Renders Regor markup to HTML in a document of its own, so renders can\n * overlap, and `onRendered` can await, without seeing each other's pages.\n */\nexport const renderApp = <TContext extends TsSsgContext>(\n html: string,\n options: RenderAppOptions<TContext>,\n): Promise<string> => {\n const normalizedHtml = html.trimStart()\n const isDocument =\n normalizedHtml.startsWith('<!DOCTYPE html>') ||\n normalizedHtml.startsWith('<!doctype html>') ||\n normalizedHtml.startsWith('<html')\n const htmlToParse = isDocument\n ? normalizedHtml\n : `<!DOCTYPE html><html><body>${html}</body></html>`\n return runInDom(htmlToParse, async (document) => {\n const runtimeEmbeds = new Map<string, 'body' | 'head'>()\n const baseContext = options.context\n const tsSsgContext = {\n ...baseContext,\n recordRuntimeEmbed: (name: string, position: 'body' | 'head') => {\n const normalized = name.trim().toLowerCase()\n if (normalized.length > 0) {\n runtimeEmbeds.set(normalized, position)\n }\n baseContext.recordRuntimeEmbed(name, position)\n },\n }\n createApp(\n {\n components: {\n ...componentRegistry.getAll(),\n ...(isPlainObject(options.components) ? options.components : {}),\n },\n tsSsgContext,\n },\n {\n element: document.body,\n },\n )\n appendEmbeddedScriptsToDom(runtimeEmbeds, tsSsgContext)\n await options.onRendered?.(document)\n if (isDocument) {\n return `<!DOCTYPE html>${document.documentElement?.outerHTML ?? ''}`\n }\n return document.body?.innerHTML ?? ''\n })\n}\n\nfunction appendEmbeddedScriptsToDom(\n runtimeEmbeds: Map<string, 'body' | 'head'>,\n context: TsSsgContext,\n) {\n appendRuntimeScript(\n resolveTabsEmbedPosition(runtimeEmbeds, context),\n buildTabsScript,\n )\n appendRuntimeScript(\n resolveModalEmbedPosition(runtimeEmbeds, context),\n buildModalScript,\n )\n}\n\nfunction appendRuntimeScript(\n position: 'body' | 'head' | undefined,\n scriptBuilder: () => string,\n) {\n if (!position) return\n const targetParent = ensureScriptParent(position)\n if (!targetParent) return\n const script = document.createElement('script')\n script.textContent = scriptBuilder()\n targetParent.appendChild(script)\n}\n\nfunction ensureScriptParent(position: 'body' | 'head') {\n if (position === 'body') {\n return document.body\n }\n return document.head\n}\n\nfunction resolveTabsEmbedPosition(\n runtimeEmbeds: Map<string, 'body' | 'head'>,\n context: TsSsgContext,\n) {\n const runtimePosition = runtimeEmbeds.get('tabs')\n if (runtimePosition) return runtimePosition\n return context?.pageInfo?.frontmatter?.embed?.tabs\n}\n\nfunction resolveModalEmbedPosition(\n runtimeEmbeds: Map<string, 'body' | 'head'>,\n context: TsSsgContext,\n) {\n const runtimePosition = runtimeEmbeds.get('modal')\n if (runtimePosition) return runtimePosition\n return context?.pageInfo?.frontmatter?.embed?.modal\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@purestack/ts-render",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"packageManager": "yarn@4.9.2",
|
|
5
5
|
"module": "./dist/ts-render.mjs",
|
|
6
6
|
"types": "./dist/ts-render.d.mts",
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
"test": "vitest"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@purestack/ts-common": "^1.1.
|
|
43
|
-
"@purestack/ts-minidom": "^1.1.
|
|
44
|
-
"@purestack/ts-page-scripts": "^1.1.
|
|
45
|
-
"@purestack/ts-util": "^1.1.
|
|
42
|
+
"@purestack/ts-common": "^1.1.3",
|
|
43
|
+
"@purestack/ts-minidom": "^1.1.3",
|
|
44
|
+
"@purestack/ts-page-scripts": "^1.1.3",
|
|
45
|
+
"@purestack/ts-util": "^1.1.3",
|
|
46
46
|
"regor": "^1.7.3"
|
|
47
47
|
}
|
|
48
48
|
}
|