@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 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()` SEO meta tags from page definitions |
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
- const loadProject = async (projectPath) => {
26
- const symbolsDir = (0, import_path.resolve)(projectPath, "symbols");
27
- const tryImport = async (modulePath) => {
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(modulePath);
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
- tryImport((0, import_path.join)(symbolsDir, "app.js")),
48
- tryImport((0, import_path.join)(symbolsDir, "state.js")),
49
- tryImport((0, import_path.join)(symbolsDir, "config.js")),
50
- tryImport((0, import_path.join)(symbolsDir, "dependencies.js")),
51
- tryImport((0, import_path.join)(symbolsDir, "components", "index.js")),
52
- tryImport((0, import_path.join)(symbolsDir, "snippets", "index.js")),
53
- tryImport((0, import_path.join)(symbolsDir, "pages", "index.js")),
54
- tryImport((0, import_path.join)(symbolsDir, "functions", "index.js")),
55
- tryImport((0, import_path.join)(symbolsDir, "methods", "index.js")),
56
- tryImport((0, import_path.join)(symbolsDir, "designSystem", "index.js")),
57
- tryImport((0, import_path.join)(symbolsDir, "files", "index.js"))
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 || {},
@@ -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
- extractMetadata: () => extractMetadata,
21
- generateHeadHtml: () => generateHeadHtml
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
- const extractMetadata = (data, route = "/") => {
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 = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#039;" };
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");