@symbo.ls/brender 3.8.8 → 3.14.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 CHANGED
@@ -113,14 +113,14 @@ const result = await render(data, { route: '/about' })
113
113
 
114
114
  // result.html -> full page HTML with data-br keys
115
115
  // result.metadata -> { title, description, og:image, ... }
116
- // result.emotionCSS -> array of CSS rule strings from emotion
116
+ // result.css -> array of CSS rule strings from the atomic CSS engine
117
117
  // result.registry -> { br-key: domqlElement }
118
118
  // result.element -> root DOMQL element
119
119
  ```
120
120
 
121
121
  ### `renderPage(data, route, options?)`
122
122
 
123
- Renders a complete, ready-to-serve HTML page. Combines `render()` output with metadata, CSS (emotion + global), fonts, and optional ISR client bundle.
123
+ Renders a complete, ready-to-serve HTML page. Combines `render()` output with metadata, CSS (atomic + global), fonts, and optional ISR client bundle.
124
124
 
125
125
  ```js
126
126
  import { renderPage, loadProject } from '@symbo.ls/brender'
@@ -345,11 +345,11 @@ This means the server and client don't need to exchange the registry — as long
345
345
 
346
346
  - `renderElement()` uses `@domql/element` create directly — lightweight, no smbls bootstrap. Good for individual components
347
347
  - `render()` uses the full `smbls/src/createDomql.js` pipeline — handles routing, designSystem initialization, uikit defaults, the works. Needed for complete apps
348
- - The smbls source is bundled with esbuild (cached after first call) because the monorepo uses extensionless/directory imports that Node.js ESM can't resolve natively. The esbuild plugin patches SSR-incompatible code (window references, createRequire, circular imports) and stubs out browser-only packages (@symbo.ls/sync)
348
+ - The smbls source is bundled with esbuild (cached after first call) because the monorepo uses extensionless/directory imports that Node.js ESM can't resolve natively. The esbuild plugin patches SSR-incompatible code (window references, createRequire, circular imports) and stubs out browser-only packages
349
349
  - `render()` sets `globalThis.document` and `globalThis.location` before each render to match the linkedom virtual env, then restores them after. This allows the bundled smbls code (which reads `window = globalThis`) to work in SSR
350
350
  - `hydrate.js` is browser-only code (no linkedom dependency) — it's exported separately via `@symbo.ls/brender/hydrate`
351
351
  - `createEnv()` sets `globalThis.window/document/Node/HTMLElement` because `@domql/utils` `isDOMNode` uses `instanceof` checks against global constructors
352
- - Emotion CSS is extracted from the CSSOM sheet rules (emotion uses `insertRule()` which doesn't populate `textContent` in linkedom)
352
+ - CSS is extracted from the atomic CSS engine's generated rules
353
353
  - `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
354
354
  - Data prefetching (`prefetch.js`) walks page definitions to find `fetch` declarations, then executes them against the DB adapter before rendering. Fetched data is injected into page state so components render with real content
355
355
 
package/dist/cjs/env.js CHANGED
@@ -24,7 +24,11 @@ var import_linkedom = require("linkedom");
24
24
  const createEnv = (html = "<!DOCTYPE html><html><head></head><body></body></html>") => {
25
25
  const { window, document } = (0, import_linkedom.parseHTML)(html);
26
26
  if (!window.requestAnimationFrame) {
27
- window.requestAnimationFrame = (fn) => setTimeout(fn, 0);
27
+ window.requestAnimationFrame = (fn) => {
28
+ const id = setTimeout(fn, 0);
29
+ if (id?.unref) id.unref();
30
+ return id;
31
+ };
28
32
  }
29
33
  if (!window.cancelAnimationFrame) {
30
34
  window.cancelAnimationFrame = (id) => clearTimeout(id);
@@ -87,6 +91,7 @@ const createEnv = (html = "<!DOCTYPE html><html><head></head><body></body></html
87
91
  globalThis.window = window;
88
92
  globalThis.document = document;
89
93
  globalThis.Node = window.Node || globalThis.Node;
94
+ globalThis.Element = window.Element || globalThis.Element;
90
95
  globalThis.HTMLElement = window.HTMLElement || globalThis.HTMLElement;
91
96
  globalThis.MutationObserver = window.MutationObserver;
92
97
  globalThis.Window = window.constructor;
@@ -76,12 +76,12 @@ const hydrate = (element, options = {}) => {
76
76
  return { element, linked, unlinked };
77
77
  };
78
78
  const renderCSS = (el, emotion, colorMap, mediaMap) => {
79
- const { node, props } = el;
80
- if (!node || !props) return;
79
+ const { node } = el;
80
+ if (!node) return;
81
81
  const css = {};
82
82
  let hasCss = false;
83
- for (const key in props) {
84
- const val = props[key];
83
+ for (const key in el) {
84
+ const val = el[key];
85
85
  if (key.charCodeAt(0) === 64) {
86
86
  const breakpoint = mediaMap[key.slice(1)];
87
87
  if (breakpoint && typeof val === "object") {
@@ -135,7 +135,7 @@ const renderCSS = (el, emotion, colorMap, mediaMap) => {
135
135
  if (typeof el.key === "string" && el.key.charCodeAt(0) === 95 && el.key.charCodeAt(1) !== 95) {
136
136
  classes.push(el.key.slice(1));
137
137
  }
138
- if (props.class) classes.push(props.class);
138
+ if (el.class) classes.push(el.class);
139
139
  if (el.attr?.class) classes.push(el.attr.class);
140
140
  const classlist = el.classlist;
141
141
  if (classlist) {
@@ -152,7 +152,7 @@ const renderCSS = (el, emotion, colorMap, mediaMap) => {
152
152
  if (classes.length) {
153
153
  node.setAttribute("class", classes.join(" "));
154
154
  }
155
- for (const key in props) {
155
+ for (const key in el) {
156
156
  if (isCSS(key) && node.hasAttribute(key)) {
157
157
  node.removeAttribute(key);
158
158
  }
@@ -503,47 +503,36 @@ const DOMQL_LIFECYCLE = /* @__PURE__ */ new Set([
503
503
  "error"
504
504
  ]);
505
505
  const bindEvents = (el) => {
506
- const { node, on, props } = el;
506
+ const { node } = el;
507
507
  if (!node) return;
508
- const handled = /* @__PURE__ */ new Set();
509
- if (on) {
510
- for (const param in on) {
511
- if (DOMQL_LIFECYCLE.has(param)) continue;
512
- if (typeof on[param] !== "function") continue;
513
- handled.add(param);
514
- addListener(node, param, on[param], el);
515
- }
516
- }
517
- if (props) {
518
- for (const key in props) {
519
- if (key.length <= 2 || key[0] !== "o" || key[1] !== "n") continue;
520
- if (typeof props[key] !== "function") continue;
521
- const third = key[2];
522
- if (third !== third.toUpperCase()) continue;
523
- const eventName = third.toLowerCase() + key.slice(3);
524
- if (handled.has(eventName) || DOMQL_LIFECYCLE.has(eventName)) continue;
525
- addListener(node, eventName, props[key], el);
526
- }
508
+ if (!el.__ref.__eventCleanup) el.__ref.__eventCleanup = [];
509
+ for (const key in el) {
510
+ if (key.length <= 2 || key[0] !== "o" || key[1] !== "n") continue;
511
+ if (typeof el[key] !== "function") continue;
512
+ const third = key[2];
513
+ if (third !== third.toUpperCase()) continue;
514
+ const eventName = third.toLowerCase() + key.slice(3);
515
+ if (DOMQL_LIFECYCLE.has(eventName)) continue;
516
+ addListener(node, eventName, el[key], el);
527
517
  }
528
518
  };
529
519
  const addListener = (node, eventName, handler, el) => {
530
- node.addEventListener(eventName, (event) => {
531
- const result = handler.call(el, event, el, el.state, el.context);
532
- if (result && typeof result.then === "function") {
533
- result.catch(() => {
534
- });
520
+ const listener = (event) => {
521
+ try {
522
+ handler.call(el, event, el, el.state, el.context);
523
+ } catch (e) {
524
+ console.warn("[brender hydrate]", eventName, e.message);
535
525
  }
536
- });
526
+ };
527
+ node.addEventListener(eventName, listener);
528
+ el.__ref.__eventCleanup.push(() => node.removeEventListener(eventName, listener));
537
529
  };
538
530
  const fireLifecycle = (el) => {
539
531
  if (!el || !el.__ref || !el.node) return;
540
- const on = el.on;
541
- if (on) {
542
- fireEvent(on.render, el);
543
- fireEvent(on.renderRouter, el);
544
- fireEvent(on.done, el);
545
- fireEvent(on.create, el);
546
- }
532
+ fireEvent(el.onRender, el);
533
+ fireEvent(el.onRenderRouter, el);
534
+ fireEvent(el.onDone, el);
535
+ fireEvent(el.onCreate, el);
547
536
  if (el.__ref.__children) {
548
537
  for (const childKey of el.__ref.__children) {
549
538
  const child = el[childKey];
package/dist/cjs/load.js CHANGED
@@ -88,7 +88,9 @@ const loadProject = async (projectPath) => {
88
88
  functionsModule,
89
89
  methodsModule,
90
90
  designSystemModule,
91
- filesModule
91
+ filesModule,
92
+ assetsModule,
93
+ sharedLibsModule
92
94
  ] = await Promise.all([
93
95
  bundleAndImport((0, import_path.join)(symbolsDir, "app.js")),
94
96
  bundleAndImport((0, import_path.join)(symbolsDir, "state.js")),
@@ -100,9 +102,11 @@ const loadProject = async (projectPath) => {
100
102
  bundleAndImport((0, import_path.join)(symbolsDir, "functions", "index.js")),
101
103
  bundleAndImport((0, import_path.join)(symbolsDir, "methods", "index.js")),
102
104
  bundleAndImport((0, import_path.join)(symbolsDir, "designSystem", "index.js")),
103
- bundleAndImport((0, import_path.join)(symbolsDir, "files", "index.js"))
105
+ bundleAndImport((0, import_path.join)(symbolsDir, "files", "index.js")),
106
+ bundleAndImport((0, import_path.join)(symbolsDir, "assets", "index.js")).catch(() => null),
107
+ bundleAndImport((0, import_path.join)(symbolsDir, "sharedLibraries.js")).catch(() => null)
104
108
  ]);
105
- return {
109
+ const result = {
106
110
  app: { ...appModule?.default || {} },
107
111
  state: { ...stateModule?.default || {} },
108
112
  dependencies: { ...depsModule?.default || {} },
@@ -113,8 +117,19 @@ const loadProject = async (projectPath) => {
113
117
  methods: { ...methodsModule || {} },
114
118
  designSystem: { ...designSystemModule?.default || {} },
115
119
  files: { ...filesModule?.default || {} },
116
- config: { ...configModule?.default || {} }
120
+ assets: { ...assetsModule?.default || {} },
121
+ config: { ...configModule?.default || {} },
122
+ sharedLibraries: sharedLibsModule?.default || []
117
123
  };
124
+ if (result.sharedLibraries.length) {
125
+ const { resolveSharedLibraries, mergeSharedLibraries } = await import("@symbo.ls/utils");
126
+ const hasStrings = result.sharedLibraries.some((lib) => typeof lib === "string");
127
+ if (hasStrings) {
128
+ result.sharedLibraries = await resolveSharedLibraries(result.sharedLibraries);
129
+ }
130
+ mergeSharedLibraries(result, result.sharedLibraries);
131
+ }
132
+ return result;
118
133
  };
119
134
  const loadAndRenderAll = async (projectPath, renderFn) => {
120
135
  const data = await loadProject(projectPath);
@@ -41,7 +41,6 @@ const resolveParams = (params, mockState) => {
41
41
  try {
42
42
  const mockEl = {
43
43
  state: mockState || {},
44
- props: {},
45
44
  call: () => void 0,
46
45
  __ref: {}
47
46
  };
@@ -225,7 +224,6 @@ const preEvaluateChildren = (def, inheritedState) => {
225
224
  try {
226
225
  const mockEl = {
227
226
  state: effectiveState,
228
- props: {},
229
227
  call: (fn) => {
230
228
  if (fn === "getActiveLang" || fn === "getLang") return effectiveState?.lang || "ka";
231
229
  if (fn === "polyglot") return arguments[1] || "";
@@ -47,13 +47,13 @@ var import_metadata = require("./metadata.js");
47
47
  var import_hydrate = require("./hydrate.js");
48
48
  var import_prefetch = require("./prefetch.js");
49
49
  var import_linkedom = require("linkedom");
50
- var import_create_instance = __toESM(require("@emotion/css/create-instance"), 1);
50
+ var import_css = require("@symbo.ls/css");
51
51
  const import_meta = {};
52
52
  let _funcqlPlugin = null;
53
53
  const getFuncqlPlugin = async () => {
54
54
  if (_funcqlPlugin) return _funcqlPlugin;
55
55
  try {
56
- const mod = await import("@domql/funcql");
56
+ const mod = await import("@symbo.ls/funcql");
57
57
  _funcqlPlugin = mod.funcqlPlugin;
58
58
  return _funcqlPlugin;
59
59
  } catch {
@@ -232,7 +232,7 @@ const resolveDomqlPackage = (ws, pkg, ...subpath) => {
232
232
  if (ws.isMonorepo) {
233
233
  return (0, import_path.resolve)(ws.monorepoRoot, "packages", "domql", "packages", pkg, ...subpath);
234
234
  }
235
- const pkgJson = tryRequireResolve(ws, `@domql/${pkg}/package.json`);
235
+ const pkgJson = tryRequireResolve(ws, `@symbo.ls/${pkg}/package.json`);
236
236
  if (pkgJson) return (0, import_path.resolve)((0, import_path.dirname)(pkgJson), ...subpath);
237
237
  return null;
238
238
  };
@@ -327,7 +327,7 @@ const bundleCreateDomql = async () => {
327
327
  }
328
328
  });
329
329
  build.onResolve({ filter: /^@domql\// }, (args) => {
330
- const pkg = args.path.replace("@domql/", "");
330
+ const pkg = args.path.replace("@symbo.ls/", "");
331
331
  if (ws.isMonorepo) {
332
332
  const src = (0, import_path.resolve)(ws.monorepoRoot, "packages", "domql", "packages", pkg, "src", "index.js");
333
333
  if ((0, import_fs.existsSync)(src)) return { path: src };
@@ -425,7 +425,7 @@ export const supabaseAdapter = () => ({name:'supabase'});
425
425
  let contents = (0, import_fs.readFileSync)(args.path, "utf8");
426
426
  contents = contents.replace(
427
427
  /import\s*\{\s*Link\s*\}\s*from\s*['"]smbls['"]/,
428
- `const Link = { tag: 'a', attr: { href: (el) => el.props?.href } }`
428
+ `const Link = { tag: 'a', attr: { href: (el) => el.href } }`
429
429
  );
430
430
  return { contents, loader: "js" };
431
431
  });
@@ -515,9 +515,9 @@ const UIKIT_STUBS = {
515
515
  Link: {
516
516
  tag: "a",
517
517
  attr: {
518
- href: (el) => el.props?.href,
519
- target: (el) => el.props?.target,
520
- rel: (el) => el.props?.rel
518
+ href: (el) => el.href,
519
+ target: (el) => el.target,
520
+ rel: (el) => el.rel
521
521
  }
522
522
  },
523
523
  A: { extends: "Link" },
@@ -526,14 +526,14 @@ const UIKIT_STUBS = {
526
526
  tag: "img",
527
527
  attr: {
528
528
  src: (el) => {
529
- let src = el.props?.src;
529
+ let src = el.src;
530
530
  if (typeof src === "string" && src.includes("{{")) {
531
531
  src = el.call("replaceLiteralsWithObjectFields", src, el.state);
532
532
  }
533
533
  return src;
534
534
  },
535
- alt: (el) => el.props?.alt,
536
- loading: (el) => el.props?.loading
535
+ alt: (el) => el.alt,
536
+ loading: (el) => el.loading
537
537
  }
538
538
  },
539
539
  Image: { extends: "Img" },
@@ -671,11 +671,7 @@ const render = async (data, options = {}) => {
671
671
  };
672
672
  }
673
673
  }
674
- const ssrEmotion = (0, import_create_instance.default)({
675
- key: "smbls",
676
- container: document.head,
677
- speedy: false
678
- });
674
+ (0, import_css.reset)();
679
675
  const ctx = {
680
676
  state: baseState,
681
677
  ...stateOverrides ? { state: { ...baseState, ...stateOverrides } } : {},
@@ -693,6 +689,7 @@ const render = async (data, options = {}) => {
693
689
  methods: data.methods || {},
694
690
  designSystem: structuredCloneDeep(data.designSystem || {}),
695
691
  files: data.files || {},
692
+ sharedLibraries: data.sharedLibraries || [],
696
693
  ...config,
697
694
  // Override polyglot with SSR-enriched version
698
695
  ...polyglotConfig ? { polyglot: polyglotConfig } : {},
@@ -700,8 +697,7 @@ const render = async (data, options = {}) => {
700
697
  document,
701
698
  window,
702
699
  parent: { node: body },
703
- // Use SSR emotion instance (non-speedy) for proper @media rule extraction
704
- initOptions: { emotion: ssrEmotion },
700
+ initOptions: {},
705
701
  // Disable sourcemap tracking in SSR — it causes stack overflows
706
702
  // when state contains large data arrays (articles, events, etc.)
707
703
  domqlOptions: { sourcemap: false },
@@ -724,38 +720,17 @@ const render = async (data, options = {}) => {
724
720
  const brRegistry = buildPathRegistry(element);
725
721
  const metadata = (0, import_metadata.extractMetadata)(data, route, element, element?.state);
726
722
  const emotionCSS = [];
727
- const emotionInstance = ctx.emotion || element && element.context && element.context.emotion;
728
- if (emotionInstance && emotionInstance.cache) {
729
- const cache = emotionInstance.cache;
730
- if (cache.inserted) {
731
- for (const key in cache.inserted) {
732
- const rule = cache.inserted[key];
733
- if (typeof rule === "string" && rule) emotionCSS.push(rule);
734
- }
735
- }
736
- if (cache.sheet && cache.sheet.tags) {
737
- for (const tag of cache.sheet.tags) {
738
- if (tag.sheet && tag.sheet.cssRules) {
739
- for (const rule of tag.sheet.cssRules) {
740
- if (rule.cssText) emotionCSS.push(rule.cssText);
741
- }
723
+ const head = document.head || document.querySelector("head");
724
+ if (head) {
725
+ for (const style of head.querySelectorAll("style")) {
726
+ if (style.sheet && style.sheet.cssRules) {
727
+ for (const rule of style.sheet.cssRules) {
728
+ if (rule.cssText) emotionCSS.push(rule.cssText);
742
729
  }
743
730
  }
744
- }
745
- }
746
- if (!emotionCSS.length) {
747
- const head = document.head || document.querySelector("head");
748
- if (head) {
749
- for (const style of head.querySelectorAll("style")) {
750
- if (style.sheet && style.sheet.cssRules) {
751
- for (const rule of style.sheet.cssRules) {
752
- if (rule.cssText) emotionCSS.push(rule.cssText);
753
- }
754
- }
755
- if (!emotionCSS.length) {
756
- const content = style.textContent || "";
757
- if (content) emotionCSS.push(content);
758
- }
731
+ if (!emotionCSS.length) {
732
+ const content = style.textContent || "";
733
+ if (content) emotionCSS.push(content);
759
734
  }
760
735
  }
761
736
  }
@@ -778,8 +753,8 @@ const renderElement = async (elementDef, options = {}) => {
778
753
  const { context = {} } = options;
779
754
  const { window, document } = (0, import_env.createEnv)();
780
755
  const body = document.body;
781
- const { create } = await import("@domql/element");
782
- const domqlUtils = await import("@domql/utils");
756
+ const { create } = await import("@symbo.ls/element");
757
+ const domqlUtils = await import("@symbo.ls/utils");
783
758
  const components = { ...UIKIT_STUBS, ...context.components || {} };
784
759
  const utils = {
785
760
  ...domqlUtils,
@@ -812,6 +787,36 @@ const fixSvgContent = (html) => {
812
787
  );
813
788
  };
814
789
  let _cachedGlobalCSS = null;
790
+ const SCRATCH_CONFIG_FLAGS = [
791
+ "globalTheme",
792
+ "themeStorageKey",
793
+ "themeRoot",
794
+ "useReset",
795
+ "useVariable",
796
+ "useFontImport",
797
+ "useIconSprite",
798
+ "useSvgSprite",
799
+ "useDocumentTheme",
800
+ "useDefaultConfig",
801
+ "useDefaultIcons",
802
+ "useThemeSuffixedVars",
803
+ "verbose",
804
+ "semanticIcons"
805
+ ];
806
+ const pickProjectConfig = (data) => {
807
+ if (!data || typeof data !== "object") return null;
808
+ if (data.config && typeof data.config === "object") return data.config;
809
+ const out = {};
810
+ let any = false;
811
+ for (const flag of SCRATCH_CONFIG_FLAGS) {
812
+ if (Object.prototype.hasOwnProperty.call(data, flag)) {
813
+ out[flag] = data[flag];
814
+ any = true;
815
+ }
816
+ }
817
+ if (any) return out;
818
+ return data.settings || null;
819
+ };
815
820
  const generateGlobalCSS = async (ds, config) => {
816
821
  if (_cachedGlobalCSS) return _cachedGlobalCSS;
817
822
  try {
@@ -894,7 +899,7 @@ const generateGlobalCSS = async (ds, config) => {
894
899
  }
895
900
  });
896
901
  build.onResolve({ filter: /^@domql\// }, (args) => {
897
- const pkg = args.path.replace("@domql/", "");
902
+ const pkg = args.path.replace("@symbo.ls/", "");
898
903
  if (ws.isMonorepo) {
899
904
  const src = (0, import_path.resolve)(ws.monorepoRoot, "packages", "domql", "packages", pkg, "src", "index.js");
900
905
  if (existsSync2(src)) return { path: src };
@@ -1002,7 +1007,7 @@ const renderRoute = async (data, options = {}) => {
1002
1007
  const result = await render(data, { route, pathname, prefetch: true });
1003
1008
  if (!result) return null;
1004
1009
  const ds = data.designSystem || {};
1005
- const globalCSS = await generateGlobalCSS(ds, data.config || data.settings);
1010
+ const globalCSS = await generateGlobalCSS(ds, pickProjectConfig(data));
1006
1011
  let prefetchedState = null;
1007
1012
  let activeLang = null;
1008
1013
  try {
@@ -1061,7 +1066,7 @@ const renderPage = async (data, route = "/", options = {}) => {
1061
1066
  }
1062
1067
  const emotionCSS = Array.from(_accumulatedEmotionCSS).join("\n");
1063
1068
  const ds = data.designSystem || {};
1064
- const globalCSS = await generateGlobalCSS(ds, data.config || data.settings);
1069
+ const globalCSS = await generateGlobalCSS(ds, pickProjectConfig(data));
1065
1070
  const fontLinks = generateFontLinks(ds);
1066
1071
  const brKeyCount = Object.keys(result.registry).length;
1067
1072
  let isrBody = "";
@@ -1479,32 +1484,29 @@ const getExtendsCSS = (el) => {
1479
1484
  return null;
1480
1485
  };
1481
1486
  const resolveElementProps = (el) => {
1482
- const { props } = el;
1483
- if (!props) return props;
1484
1487
  let resolved;
1485
- for (const key in props) {
1486
- if (typeof props[key] !== "function") continue;
1488
+ for (const key in el) {
1489
+ if (typeof el[key] !== "function") continue;
1487
1490
  if (NON_CSS_PROPS.has(key)) continue;
1488
1491
  if (key.charCodeAt(0) >= 65 && key.charCodeAt(0) <= 90) continue;
1489
1492
  if (key.startsWith("on")) continue;
1490
- if (!resolved) resolved = { ...props };
1493
+ if (key.startsWith("__")) continue;
1494
+ if (!resolved) resolved = {};
1491
1495
  let result;
1492
1496
  try {
1493
- result = props[key](el, el.state || {});
1497
+ result = el[key](el, el.state || {});
1494
1498
  } catch {
1495
1499
  try {
1496
1500
  const mockState = { root: {}, ...el.state || {} };
1497
- result = props[key](el, mockState);
1501
+ result = el[key](el, mockState);
1498
1502
  } catch {
1499
1503
  }
1500
1504
  }
1501
1505
  if (result !== void 0 && result !== null && result !== false) {
1502
1506
  resolved[key] = result;
1503
- } else {
1504
- delete resolved[key];
1505
1507
  }
1506
1508
  }
1507
- return resolved || props;
1509
+ return resolved || el;
1508
1510
  };
1509
1511
  const extractCSS = (element, ds) => {
1510
1512
  const mediaMap = ds?.media || {};
package/dist/esm/env.js CHANGED
@@ -2,7 +2,11 @@ import { parseHTML } from "linkedom";
2
2
  const createEnv = (html = "<!DOCTYPE html><html><head></head><body></body></html>") => {
3
3
  const { window, document } = parseHTML(html);
4
4
  if (!window.requestAnimationFrame) {
5
- window.requestAnimationFrame = (fn) => setTimeout(fn, 0);
5
+ window.requestAnimationFrame = (fn) => {
6
+ const id = setTimeout(fn, 0);
7
+ if (id?.unref) id.unref();
8
+ return id;
9
+ };
6
10
  }
7
11
  if (!window.cancelAnimationFrame) {
8
12
  window.cancelAnimationFrame = (id) => clearTimeout(id);
@@ -65,6 +69,7 @@ const createEnv = (html = "<!DOCTYPE html><html><head></head><body></body></html
65
69
  globalThis.window = window;
66
70
  globalThis.document = document;
67
71
  globalThis.Node = window.Node || globalThis.Node;
72
+ globalThis.Element = window.Element || globalThis.Element;
68
73
  globalThis.HTMLElement = window.HTMLElement || globalThis.HTMLElement;
69
74
  globalThis.MutationObserver = window.MutationObserver;
70
75
  globalThis.Window = window.constructor;
@@ -53,12 +53,12 @@ const hydrate = (element, options = {}) => {
53
53
  return { element, linked, unlinked };
54
54
  };
55
55
  const renderCSS = (el, emotion, colorMap, mediaMap) => {
56
- const { node, props } = el;
57
- if (!node || !props) return;
56
+ const { node } = el;
57
+ if (!node) return;
58
58
  const css = {};
59
59
  let hasCss = false;
60
- for (const key in props) {
61
- const val = props[key];
60
+ for (const key in el) {
61
+ const val = el[key];
62
62
  if (key.charCodeAt(0) === 64) {
63
63
  const breakpoint = mediaMap[key.slice(1)];
64
64
  if (breakpoint && typeof val === "object") {
@@ -112,7 +112,7 @@ const renderCSS = (el, emotion, colorMap, mediaMap) => {
112
112
  if (typeof el.key === "string" && el.key.charCodeAt(0) === 95 && el.key.charCodeAt(1) !== 95) {
113
113
  classes.push(el.key.slice(1));
114
114
  }
115
- if (props.class) classes.push(props.class);
115
+ if (el.class) classes.push(el.class);
116
116
  if (el.attr?.class) classes.push(el.attr.class);
117
117
  const classlist = el.classlist;
118
118
  if (classlist) {
@@ -129,7 +129,7 @@ const renderCSS = (el, emotion, colorMap, mediaMap) => {
129
129
  if (classes.length) {
130
130
  node.setAttribute("class", classes.join(" "));
131
131
  }
132
- for (const key in props) {
132
+ for (const key in el) {
133
133
  if (isCSS(key) && node.hasAttribute(key)) {
134
134
  node.removeAttribute(key);
135
135
  }
@@ -480,47 +480,36 @@ const DOMQL_LIFECYCLE = /* @__PURE__ */ new Set([
480
480
  "error"
481
481
  ]);
482
482
  const bindEvents = (el) => {
483
- const { node, on, props } = el;
483
+ const { node } = el;
484
484
  if (!node) return;
485
- const handled = /* @__PURE__ */ new Set();
486
- if (on) {
487
- for (const param in on) {
488
- if (DOMQL_LIFECYCLE.has(param)) continue;
489
- if (typeof on[param] !== "function") continue;
490
- handled.add(param);
491
- addListener(node, param, on[param], el);
492
- }
493
- }
494
- if (props) {
495
- for (const key in props) {
496
- if (key.length <= 2 || key[0] !== "o" || key[1] !== "n") continue;
497
- if (typeof props[key] !== "function") continue;
498
- const third = key[2];
499
- if (third !== third.toUpperCase()) continue;
500
- const eventName = third.toLowerCase() + key.slice(3);
501
- if (handled.has(eventName) || DOMQL_LIFECYCLE.has(eventName)) continue;
502
- addListener(node, eventName, props[key], el);
503
- }
485
+ if (!el.__ref.__eventCleanup) el.__ref.__eventCleanup = [];
486
+ for (const key in el) {
487
+ if (key.length <= 2 || key[0] !== "o" || key[1] !== "n") continue;
488
+ if (typeof el[key] !== "function") continue;
489
+ const third = key[2];
490
+ if (third !== third.toUpperCase()) continue;
491
+ const eventName = third.toLowerCase() + key.slice(3);
492
+ if (DOMQL_LIFECYCLE.has(eventName)) continue;
493
+ addListener(node, eventName, el[key], el);
504
494
  }
505
495
  };
506
496
  const addListener = (node, eventName, handler, el) => {
507
- node.addEventListener(eventName, (event) => {
508
- const result = handler.call(el, event, el, el.state, el.context);
509
- if (result && typeof result.then === "function") {
510
- result.catch(() => {
511
- });
497
+ const listener = (event) => {
498
+ try {
499
+ handler.call(el, event, el, el.state, el.context);
500
+ } catch (e) {
501
+ console.warn("[brender hydrate]", eventName, e.message);
512
502
  }
513
- });
503
+ };
504
+ node.addEventListener(eventName, listener);
505
+ el.__ref.__eventCleanup.push(() => node.removeEventListener(eventName, listener));
514
506
  };
515
507
  const fireLifecycle = (el) => {
516
508
  if (!el || !el.__ref || !el.node) return;
517
- const on = el.on;
518
- if (on) {
519
- fireEvent(on.render, el);
520
- fireEvent(on.renderRouter, el);
521
- fireEvent(on.done, el);
522
- fireEvent(on.create, el);
523
- }
509
+ fireEvent(el.onRender, el);
510
+ fireEvent(el.onRenderRouter, el);
511
+ fireEvent(el.onDone, el);
512
+ fireEvent(el.onCreate, el);
524
513
  if (el.__ref.__children) {
525
514
  for (const childKey of el.__ref.__children) {
526
515
  const child = el[childKey];
package/dist/esm/load.js CHANGED
@@ -55,7 +55,9 @@ const loadProject = async (projectPath) => {
55
55
  functionsModule,
56
56
  methodsModule,
57
57
  designSystemModule,
58
- filesModule
58
+ filesModule,
59
+ assetsModule,
60
+ sharedLibsModule
59
61
  ] = await Promise.all([
60
62
  bundleAndImport(join(symbolsDir, "app.js")),
61
63
  bundleAndImport(join(symbolsDir, "state.js")),
@@ -67,9 +69,11 @@ const loadProject = async (projectPath) => {
67
69
  bundleAndImport(join(symbolsDir, "functions", "index.js")),
68
70
  bundleAndImport(join(symbolsDir, "methods", "index.js")),
69
71
  bundleAndImport(join(symbolsDir, "designSystem", "index.js")),
70
- bundleAndImport(join(symbolsDir, "files", "index.js"))
72
+ bundleAndImport(join(symbolsDir, "files", "index.js")),
73
+ bundleAndImport(join(symbolsDir, "assets", "index.js")).catch(() => null),
74
+ bundleAndImport(join(symbolsDir, "sharedLibraries.js")).catch(() => null)
71
75
  ]);
72
- return {
76
+ const result = {
73
77
  app: { ...appModule?.default || {} },
74
78
  state: { ...stateModule?.default || {} },
75
79
  dependencies: { ...depsModule?.default || {} },
@@ -80,8 +84,19 @@ const loadProject = async (projectPath) => {
80
84
  methods: { ...methodsModule || {} },
81
85
  designSystem: { ...designSystemModule?.default || {} },
82
86
  files: { ...filesModule?.default || {} },
83
- config: { ...configModule?.default || {} }
87
+ assets: { ...assetsModule?.default || {} },
88
+ config: { ...configModule?.default || {} },
89
+ sharedLibraries: sharedLibsModule?.default || []
84
90
  };
91
+ if (result.sharedLibraries.length) {
92
+ const { resolveSharedLibraries, mergeSharedLibraries } = await import("@symbo.ls/utils");
93
+ const hasStrings = result.sharedLibraries.some((lib) => typeof lib === "string");
94
+ if (hasStrings) {
95
+ result.sharedLibraries = await resolveSharedLibraries(result.sharedLibraries);
96
+ }
97
+ mergeSharedLibraries(result, result.sharedLibraries);
98
+ }
99
+ return result;
85
100
  };
86
101
  const loadAndRenderAll = async (projectPath, renderFn) => {
87
102
  const data = await loadProject(projectPath);