@stacksjs/stx 0.2.130 → 0.2.131

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/dist/utils.js CHANGED
@@ -22,8 +22,37 @@ export {
22
22
  extractVariables,
23
23
  hasVariables
24
24
  } from "./variable-extractor.js";
25
- const componentsCache = new LRUCache(500);
25
+ const componentsCache = new LRUCache(500), componentResolutionCache = new LRUCache(1000), componentRenderCache = new LRUCache(2000);
26
26
  let scopeIdCounter = 0;
27
+ function serializeComponentRenderInput(value) {
28
+ const seen = new WeakSet;
29
+ try {
30
+ const normalize = (input) => {
31
+ if (input === void 0)
32
+ return { __stxType: "undefined" };
33
+ if (typeof input === "bigint")
34
+ return { __stxType: "bigint", value: input.toString() };
35
+ if (typeof input === "function" || typeof input === "symbol")
36
+ throw TypeError("Non-serializable component input");
37
+ if (input === null || typeof input !== "object")
38
+ return input;
39
+ if (seen.has(input))
40
+ throw TypeError("Circular component input");
41
+ seen.add(input);
42
+ if (input instanceof Date)
43
+ return { __stxType: "date", value: input.toISOString() };
44
+ if (Array.isArray(input))
45
+ return input.map(normalize);
46
+ const normalized = {};
47
+ for (const key of Object.keys(input).sort())
48
+ normalized[key] = normalize(input[key]);
49
+ return normalized;
50
+ };
51
+ return JSON.stringify(normalize(value));
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
27
56
  function escapeAttr(str) {
28
57
  return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
29
58
  }
@@ -441,10 +470,16 @@ export async function renderComponentWithSlot(componentPath, props, slotContent,
441
470
  }))];
442
471
  searchDirs.length = 0;
443
472
  searchDirs.push(...normalizedSearchDirs);
444
- if (baseName.startsWith("./") || baseName.startsWith("../")) {
473
+ const resolutionCacheKey = `${baseName}\x00${parentFilePath}\x00${normalizedSearchDirs.join("\x00")}`, cachedResolution = componentResolutionCache.get(resolutionCacheKey);
474
+ if (cachedResolution)
475
+ if (await fileExists(cachedResolution))
476
+ componentFilePath = cachedResolution;
477
+ else
478
+ componentResolutionCache.delete(resolutionCacheKey);
479
+ if (!componentFilePath && (baseName.startsWith("./") || baseName.startsWith("../"))) {
445
480
  componentFilePath = path.resolve(path.dirname(parentFilePath), `${baseName}.stx`);
446
481
  triedPaths.push(componentFilePath);
447
- } else
482
+ } else if (!componentFilePath)
448
483
  for (const dir of searchDirs) {
449
484
  if (!dir)
450
485
  continue;
@@ -497,6 +532,8 @@ export async function renderComponentWithSlot(componentPath, props, slotContent,
497
532
  if (componentFilePath)
498
533
  break;
499
534
  }
535
+ if (componentFilePath)
536
+ componentResolutionCache.set(resolutionCacheKey, componentFilePath);
500
537
  }
501
538
  if (!componentFilePath || !await fileExists(componentFilePath)) {
502
539
  const searchInfo = triedPaths.length > 0 ? `
@@ -540,7 +577,26 @@ ${triedPaths.map((p) => ` - ${p}`).join(`
540
577
  const templateBlock = findSfcTemplateBlock(workingContent);
541
578
  if (templateBlock)
542
579
  workingContent = templateBlock.content.trim();
543
- const scriptRegex = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi, scriptMatches = [...componentContent.matchAll(scriptRegex)], clientScripts = [];
580
+ const scriptRegex = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi, scriptMatches = [...componentContent.matchAll(scriptRegex)], clientScripts = [], cacheableServerComponent = scriptMatches.some((match) => /\bserver\b/.test(match[1] || "") && /\bcache\b/.test(match[1] || "")) && scriptMatches.every((match) => /\bserver\b/.test(match[1] || ""));
581
+ let componentRenderCacheKey = null, dependencyBaseline = null;
582
+ if (cacheableServerComponent) {
583
+ const serializedInput = serializeComponentRenderInput({
584
+ props,
585
+ slot: slotContent,
586
+ buildMode: options.buildMode ?? "",
587
+ skipEventDirectives: options.skipEventDirectives === !0
588
+ });
589
+ if (serializedInput !== null) {
590
+ componentRenderCacheKey = `${componentFilePath}\x00${serializedInput}`;
591
+ const cachedRender = componentRenderCache.get(componentRenderCacheKey);
592
+ if (cachedRender) {
593
+ for (const dependency of cachedRender.dependencies)
594
+ dependencies.add(dependency);
595
+ return cachedRender.output;
596
+ }
597
+ dependencyBaseline = new Set(dependencies);
598
+ }
599
+ }
544
600
  for (const match of scriptMatches) {
545
601
  const attrs = match[1] || "";
546
602
  let content = match[2] || "";
@@ -701,6 +757,11 @@ finally {
701
757
  if (scopedStyleResult.hasScoped)
702
758
  output = scopedStyleResult.html;
703
759
  }
760
+ if (componentRenderCacheKey && dependencyBaseline)
761
+ componentRenderCache.set(componentRenderCacheKey, {
762
+ output,
763
+ dependencies: [...dependencies].filter((dependency) => !dependencyBaseline.has(dependency))
764
+ });
704
765
  return output;
705
766
  } catch (error) {
706
767
  return `[Error processing component: ${error instanceof Error ? error.message : String(error)}]`;
@@ -989,6 +1050,8 @@ ${colors.dim}${context}${colors.reset}
989
1050
  }
990
1051
  export function clearComponentCache() {
991
1052
  componentsCache.clear();
1053
+ componentResolutionCache.clear();
1054
+ componentRenderCache.clear();
992
1055
  }
993
1056
  export function getComponentCacheStats() {
994
1057
  return {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/stx",
3
3
  "type": "module",
4
- "version": "0.2.130",
4
+ "version": "0.2.131",
5
5
  "description": "A performant UI Framework. Powered by Bun.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",
@@ -117,12 +117,12 @@
117
117
  "dependencies": {
118
118
  "@cwcss/crosswind": "^0.2.12",
119
119
  "@stacksjs/clapp": "^0.2.0",
120
- "@stacksjs/desktop": "0.2.130",
121
- "@stacksjs/sanitizer": "0.2.130",
120
+ "@stacksjs/desktop": "0.2.131",
121
+ "@stacksjs/sanitizer": "0.2.131",
122
122
  "@stacksjs/ts-i18n": "^0.1.11",
123
- "bun-plugin-stx": "0.2.130",
123
+ "bun-plugin-stx": "0.2.131",
124
124
  "bunfig": "^0.15.15",
125
- "stx-router": "0.2.130",
125
+ "stx-router": "0.2.131",
126
126
  "ts-broadcasting": "^0.0.9",
127
127
  "ts-images": "^0.1.9",
128
128
  "ts-syntax-highlighter": "^0.2.1"