@symbo.ls/brender 3.5.0 → 3.6.1
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 +18 -2
- package/dist/cjs/env.js +23 -0
- package/dist/cjs/index.js +4 -1
- package/dist/cjs/load.js +62 -16
- package/dist/cjs/metadata.js +5 -81
- package/dist/cjs/render.js +434 -28
- package/dist/cjs/sitemap.js +41 -0
- package/dist/esm/env.js +23 -0
- package/dist/esm/index.js +4 -1
- package/dist/esm/load.js +52 -16
- package/dist/esm/metadata.js +4 -80
- package/dist/esm/render.js +434 -28
- package/dist/esm/sitemap.js +22 -0
- package/env.js +17 -0
- package/index.js +5 -2
- package/load.js +58 -19
- package/metadata.js +3 -115
- package/package.json +10 -9
- package/render.js +481 -29
- package/sitemap.js +28 -0
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ Browser DOM (static) DOMQL Tree (no nodes) After hydrate()
|
|
|
63
63
|
| `hydrate.js` | `collectBrNodes()` — scans DOM for data-br nodes; `hydrate()` — reconnects DOMQL tree to DOM |
|
|
64
64
|
| `env.js` | `createEnv()` — linkedom virtual DOM with browser API stubs (requestAnimationFrame, history, location, etc.) |
|
|
65
65
|
| `keys.js` | `resetKeys()`, `assignKeys()` — stamps data-br on DOM nodes; `mapKeysToElements()` — builds registry |
|
|
66
|
-
| `metadata.js` | `extractMetadata()`, `generateHeadHtml()`
|
|
66
|
+
| `metadata.js` | Re-exports from [`@symbo.ls/helmet`](../helmet/) — `extractMetadata()`, `generateHeadHtml()`, `resolveMetadata()`, `applyMetadata()` |
|
|
67
67
|
| `load.js` | `loadProject()` — imports a symbols/ directory; `loadAndRenderAll()` — renders every route |
|
|
68
68
|
| `index.js` | Re-exports everything |
|
|
69
69
|
|
|
@@ -168,13 +168,15 @@ Creates a linkedom virtual DOM environment with stubs for browser APIs that DOMQ
|
|
|
168
168
|
|
|
169
169
|
### `generateHeadHtml(metadata)`
|
|
170
170
|
|
|
171
|
-
Converts a metadata object into HTML head tags:
|
|
171
|
+
Converts a metadata object into HTML head tags. Provided by [`@symbo.ls/helmet`](../helmet/):
|
|
172
172
|
|
|
173
173
|
```js
|
|
174
174
|
generateHeadHtml({ title: 'My Page', description: 'About', 'og:image': '/img.png' })
|
|
175
175
|
// -> '<meta charset="UTF-8">\n<title>My Page</title>\n<meta name="description" content="About">\n<meta property="og:image" content="/img.png">'
|
|
176
176
|
```
|
|
177
177
|
|
|
178
|
+
Metadata values can also be functions — see the [helmet plugin](../helmet/) for details.
|
|
179
|
+
|
|
178
180
|
## Examples
|
|
179
181
|
|
|
180
182
|
The `examples/` directory contains runnable experiments. Copy a project's source into `examples/` first (gitignored), then run:
|
|
@@ -280,3 +282,17 @@ This means the server and client don't need to exchange the registry — as long
|
|
|
280
282
|
- `hydrate.js` is browser-only code (no linkedom dependency) — it's exported separately via `@symbo.ls/brender/hydrate`
|
|
281
283
|
- `createEnv()` sets `globalThis.window/document/Node/HTMLElement` because `@domql/utils` `isDOMNode` uses `instanceof` checks against global constructors
|
|
282
284
|
- `onRender` callbacks that do network requests or call `s.update()` will error during SSR — this is expected and harmless since the HTML is already produced before those callbacks fire
|
|
285
|
+
|
|
286
|
+
## Theme support
|
|
287
|
+
|
|
288
|
+
Brender defaults to `globalTheme: 'auto'`, generating CSS with both `prefers-color-scheme` media queries and `[data-theme]` selectors. The SSR output includes theme-switching CSS variables that work without JavaScript:
|
|
289
|
+
|
|
290
|
+
```css
|
|
291
|
+
@media (prefers-color-scheme: dark) {
|
|
292
|
+
:root:not([data-theme]) { --theme-document-background: #000; }
|
|
293
|
+
}
|
|
294
|
+
[data-theme="dark"] { --theme-document-background: #000; }
|
|
295
|
+
[data-theme="ocean"] { --theme-document-background: #0a2e4e; }
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Custom themes beyond dark/light are activated via `data-theme` attribute on the root element.
|
package/dist/cjs/env.js
CHANGED
|
@@ -48,6 +48,29 @@ const createEnv = (html = "<!DOCTYPE html><html><head></head><body></body></html
|
|
|
48
48
|
window.scrollTo = () => {
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
|
+
const createStorage = () => {
|
|
52
|
+
const store = {};
|
|
53
|
+
return {
|
|
54
|
+
getItem: (k) => store[k] ?? null,
|
|
55
|
+
setItem: (k, v) => {
|
|
56
|
+
store[k] = String(v);
|
|
57
|
+
},
|
|
58
|
+
removeItem: (k) => {
|
|
59
|
+
delete store[k];
|
|
60
|
+
},
|
|
61
|
+
clear: () => {
|
|
62
|
+
for (const k in store) delete store[k];
|
|
63
|
+
},
|
|
64
|
+
get length() {
|
|
65
|
+
return Object.keys(store).length;
|
|
66
|
+
},
|
|
67
|
+
key: (i) => Object.keys(store)[i] ?? null
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
if (!window.localStorage) window.localStorage = createStorage();
|
|
71
|
+
if (!window.sessionStorage) window.sessionStorage = createStorage();
|
|
72
|
+
if (!globalThis.localStorage) globalThis.localStorage = window.localStorage;
|
|
73
|
+
if (!globalThis.sessionStorage) globalThis.sessionStorage = window.sessionStorage;
|
|
51
74
|
globalThis.window = window;
|
|
52
75
|
globalThis.document = document;
|
|
53
76
|
globalThis.Node = window.Node || globalThis.Node;
|
package/dist/cjs/index.js
CHANGED
|
@@ -23,6 +23,7 @@ __export(index_exports, {
|
|
|
23
23
|
default: () => index_default,
|
|
24
24
|
extractMetadata: () => import_metadata.extractMetadata,
|
|
25
25
|
generateHeadHtml: () => import_metadata.generateHeadHtml,
|
|
26
|
+
generateSitemap: () => import_sitemap.generateSitemap,
|
|
26
27
|
hydrate: () => import_hydrate.hydrate,
|
|
27
28
|
loadAndRenderAll: () => import_load.loadAndRenderAll,
|
|
28
29
|
loadProject: () => import_load.loadProject,
|
|
@@ -40,6 +41,7 @@ var import_load = require("./load.js");
|
|
|
40
41
|
var import_render = require("./render.js");
|
|
41
42
|
var import_metadata = require("./metadata.js");
|
|
42
43
|
var import_hydrate = require("./hydrate.js");
|
|
44
|
+
var import_sitemap = require("./sitemap.js");
|
|
43
45
|
var index_default = {
|
|
44
46
|
createEnv: import_env.createEnv,
|
|
45
47
|
resetKeys: import_keys.resetKeys,
|
|
@@ -54,5 +56,6 @@ var index_default = {
|
|
|
54
56
|
extractMetadata: import_metadata.extractMetadata,
|
|
55
57
|
generateHeadHtml: import_metadata.generateHeadHtml,
|
|
56
58
|
collectBrNodes: import_hydrate.collectBrNodes,
|
|
57
|
-
hydrate: import_hydrate.hydrate
|
|
59
|
+
hydrate: import_hydrate.hydrate,
|
|
60
|
+
generateSitemap: import_sitemap.generateSitemap
|
|
58
61
|
};
|
package/dist/cjs/load.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
1
2
|
var __defProp = Object.defineProperty;
|
|
2
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
7
|
var __export = (target, all) => {
|
|
6
8
|
for (var name in all)
|
|
@@ -14,6 +16,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
14
16
|
}
|
|
15
17
|
return to;
|
|
16
18
|
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
17
27
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
28
|
var load_exports = {};
|
|
19
29
|
__export(load_exports, {
|
|
@@ -22,15 +32,51 @@ __export(load_exports, {
|
|
|
22
32
|
});
|
|
23
33
|
module.exports = __toCommonJS(load_exports);
|
|
24
34
|
var import_path = require("path");
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
var import_fs = require("fs");
|
|
36
|
+
var import_os = require("os");
|
|
37
|
+
var import_crypto = require("crypto");
|
|
38
|
+
const bundleAndImport = async (entryPath) => {
|
|
39
|
+
if (!(0, import_fs.existsSync)(entryPath)) return null;
|
|
40
|
+
let esbuild;
|
|
41
|
+
try {
|
|
42
|
+
esbuild = await import("esbuild");
|
|
43
|
+
} catch {
|
|
28
44
|
try {
|
|
29
|
-
return await import(
|
|
45
|
+
return await import(entryPath);
|
|
30
46
|
} catch {
|
|
31
47
|
return null;
|
|
32
48
|
}
|
|
33
|
-
}
|
|
49
|
+
}
|
|
50
|
+
const outFile = (0, import_path.join)((0, import_os.tmpdir)(), `brender_${(0, import_crypto.randomBytes)(8).toString("hex")}.mjs`);
|
|
51
|
+
try {
|
|
52
|
+
await esbuild.build({
|
|
53
|
+
entryPoints: [entryPath],
|
|
54
|
+
bundle: true,
|
|
55
|
+
format: "esm",
|
|
56
|
+
platform: "node",
|
|
57
|
+
outfile: outFile,
|
|
58
|
+
write: true,
|
|
59
|
+
logLevel: "silent",
|
|
60
|
+
// Mark node builtins as external
|
|
61
|
+
external: ["fs", "path", "os", "crypto", "url", "http", "https", "stream", "util", "events", "buffer", "child_process", "worker_threads", "net", "tls", "dns", "dgram", "zlib", "assert", "querystring", "string_decoder", "readline", "perf_hooks", "async_hooks", "v8", "vm", "cluster", "inspector", "module", "process", "tty"]
|
|
62
|
+
});
|
|
63
|
+
const mod = await import(`file://${outFile}`);
|
|
64
|
+
return mod;
|
|
65
|
+
} catch {
|
|
66
|
+
try {
|
|
67
|
+
return await import(entryPath);
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
} finally {
|
|
72
|
+
try {
|
|
73
|
+
(0, import_fs.unlinkSync)(outFile);
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const loadProject = async (projectPath) => {
|
|
79
|
+
const symbolsDir = (0, import_path.resolve)(projectPath, "symbols");
|
|
34
80
|
const [
|
|
35
81
|
appModule,
|
|
36
82
|
stateModule,
|
|
@@ -44,17 +90,17 @@ const loadProject = async (projectPath) => {
|
|
|
44
90
|
designSystemModule,
|
|
45
91
|
filesModule
|
|
46
92
|
] = await Promise.all([
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
93
|
+
bundleAndImport((0, import_path.join)(symbolsDir, "app.js")),
|
|
94
|
+
bundleAndImport((0, import_path.join)(symbolsDir, "state.js")),
|
|
95
|
+
bundleAndImport((0, import_path.join)(symbolsDir, "config.js")),
|
|
96
|
+
bundleAndImport((0, import_path.join)(symbolsDir, "dependencies.js")),
|
|
97
|
+
bundleAndImport((0, import_path.join)(symbolsDir, "components", "index.js")),
|
|
98
|
+
bundleAndImport((0, import_path.join)(symbolsDir, "snippets", "index.js")),
|
|
99
|
+
bundleAndImport((0, import_path.join)(symbolsDir, "pages", "index.js")),
|
|
100
|
+
bundleAndImport((0, import_path.join)(symbolsDir, "functions", "index.js")),
|
|
101
|
+
bundleAndImport((0, import_path.join)(symbolsDir, "methods", "index.js")),
|
|
102
|
+
bundleAndImport((0, import_path.join)(symbolsDir, "designSystem", "index.js")),
|
|
103
|
+
bundleAndImport((0, import_path.join)(symbolsDir, "files", "index.js"))
|
|
58
104
|
]);
|
|
59
105
|
return {
|
|
60
106
|
app: appModule?.default || {},
|
package/dist/cjs/metadata.js
CHANGED
|
@@ -17,86 +17,10 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
17
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
18
|
var metadata_exports = {};
|
|
19
19
|
__export(metadata_exports, {
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
applyMetadata: () => import_helmet.applyMetadata,
|
|
21
|
+
extractMetadata: () => import_helmet.extractMetadata,
|
|
22
|
+
generateHeadHtml: () => import_helmet.generateHeadHtml,
|
|
23
|
+
resolveMetadata: () => import_helmet.resolveMetadata
|
|
22
24
|
});
|
|
23
25
|
module.exports = __toCommonJS(metadata_exports);
|
|
24
|
-
|
|
25
|
-
const pages = data.pages || {};
|
|
26
|
-
const page = pages[route];
|
|
27
|
-
let metadata = {};
|
|
28
|
-
if (data.integrations?.seo) {
|
|
29
|
-
metadata = { ...data.integrations.seo };
|
|
30
|
-
}
|
|
31
|
-
if (page) {
|
|
32
|
-
const pageMeta = page.metadata || page.helmet || {};
|
|
33
|
-
metadata = { ...metadata, ...pageMeta };
|
|
34
|
-
if (!metadata.title && page.state?.title) {
|
|
35
|
-
metadata.title = page.state.title;
|
|
36
|
-
}
|
|
37
|
-
if (!metadata.description && page.state?.description) {
|
|
38
|
-
metadata.description = page.state.description;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
if (!metadata.title) {
|
|
42
|
-
metadata.title = data.name || "Symbols";
|
|
43
|
-
}
|
|
44
|
-
return metadata;
|
|
45
|
-
};
|
|
46
|
-
const generateHeadHtml = (metadata) => {
|
|
47
|
-
const esc = (text) => {
|
|
48
|
-
if (text === null || text === void 0) return "";
|
|
49
|
-
const map = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
50
|
-
return text.toString().replace(/[&<>"']/g, (m) => map[m]);
|
|
51
|
-
};
|
|
52
|
-
const tags = [
|
|
53
|
-
'<meta charset="UTF-8">',
|
|
54
|
-
'<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no">'
|
|
55
|
-
];
|
|
56
|
-
for (const [key, value] of Object.entries(metadata)) {
|
|
57
|
-
if (!value && value !== 0 && value !== false) continue;
|
|
58
|
-
if (key === "title") {
|
|
59
|
-
tags.push(`<title>${esc(value)}</title>`);
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
if (key === "canonical") {
|
|
63
|
-
tags.push(`<link rel="canonical" href="${esc(value)}">`);
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
if (key === "alternate" && Array.isArray(value)) {
|
|
67
|
-
value.forEach((alt) => {
|
|
68
|
-
if (typeof alt === "object") {
|
|
69
|
-
const attrs = Object.entries(alt).map(([k, v]) => `${k}="${esc(v)}"`).join(" ");
|
|
70
|
-
tags.push(`<link rel="alternate" ${attrs}>`);
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
const propertyPrefixes = ["og:", "article:", "product:", "fb:", "profile:", "book:", "business:", "music:", "video:"];
|
|
76
|
-
const namePrefixes = ["twitter:", "DC:", "DCTERMS:"];
|
|
77
|
-
const isProperty = propertyPrefixes.some((p) => key.startsWith(p));
|
|
78
|
-
const isName = namePrefixes.some((p) => key.startsWith(p));
|
|
79
|
-
if (key.startsWith("http-equiv:")) {
|
|
80
|
-
const httpKey = key.replace("http-equiv:", "");
|
|
81
|
-
tags.push(`<meta http-equiv="${esc(httpKey)}" content="${esc(value)}">`);
|
|
82
|
-
} else if (key.startsWith("itemprop:")) {
|
|
83
|
-
const itemKey = key.replace("itemprop:", "");
|
|
84
|
-
tags.push(`<meta itemprop="${esc(itemKey)}" content="${esc(value)}">`);
|
|
85
|
-
} else if (isProperty) {
|
|
86
|
-
if (Array.isArray(value)) {
|
|
87
|
-
value.forEach((v) => tags.push(`<meta property="${esc(key)}" content="${esc(v)}">`));
|
|
88
|
-
} else {
|
|
89
|
-
tags.push(`<meta property="${esc(key)}" content="${esc(value)}">`);
|
|
90
|
-
}
|
|
91
|
-
} else if (isName) {
|
|
92
|
-
tags.push(`<meta name="${esc(key)}" content="${esc(value)}">`);
|
|
93
|
-
} else if (key !== "favicon" && key !== "favicons") {
|
|
94
|
-
if (Array.isArray(value)) {
|
|
95
|
-
value.forEach((v) => tags.push(`<meta name="${esc(key)}" content="${esc(v)}">`));
|
|
96
|
-
} else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
97
|
-
tags.push(`<meta name="${esc(key)}" content="${esc(value)}">`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return tags.join("\n");
|
|
102
|
-
};
|
|
26
|
+
var import_helmet = require("@symbo.ls/helmet");
|