@stacksjs/stx 0.2.129 → 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/cli.js +665 -665
- package/dist/craft.js +328 -328
- package/dist/pwa.js +437 -437
- package/dist/ssg.js +453 -453
- package/dist/utils.js +67 -4
- package/dist/variable-extractor.d.ts +22 -0
- package/dist/variable-extractor.js +54 -1
- package/package.json +5 -5
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, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
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
|
-
|
|
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 {
|
|
@@ -26,6 +26,28 @@ export declare function extractVariables(scriptContent: string, context: Record<
|
|
|
26
26
|
*/
|
|
27
27
|
export declare function isCompleteImport(statement: string): boolean;
|
|
28
28
|
export declare function convertToCommonJS(scriptContent: string, filePath?: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Parse variable declarations (including multi-line objects and arrays)
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* Split `const name: Type = value` into its parts.
|
|
34
|
+
*
|
|
35
|
+
* The pattern this supports used to require the `=` to follow the name
|
|
36
|
+
* directly, so any TypeScript annotation made the declaration unparseable. That
|
|
37
|
+
* is not a small failure: an unparseable declaration throws out of
|
|
38
|
+
* `convertToCommonJS`, the whole server script falls back to static
|
|
39
|
+
* extraction, and the page renders with every variable undefined and no error.
|
|
40
|
+
* A single `const rows: any = await db...` was enough to blank a page.
|
|
41
|
+
*
|
|
42
|
+
* The annotation is erased at runtime, so it only has to be skipped. It is
|
|
43
|
+
* scanned rather than matched because it can contain almost anything: `=` in a
|
|
44
|
+
* generic default, braces in an object type, commas and angle brackets in a
|
|
45
|
+
* generic argument list.
|
|
46
|
+
*
|
|
47
|
+
* Returns null when the line is not a simple declaration, which leaves the
|
|
48
|
+
* destructuring and multi-line paths below to handle it as before.
|
|
49
|
+
*/
|
|
50
|
+
export declare function splitDeclaration(line: string): { type: string, name: string, value: string } | null;
|
|
29
51
|
/**
|
|
30
52
|
* Extract script content from an HTML template
|
|
31
53
|
*
|
|
@@ -877,8 +877,61 @@ function extractDestructuredNames(pattern) {
|
|
|
877
877
|
names.push(currentName.trim());
|
|
878
878
|
return names;
|
|
879
879
|
}
|
|
880
|
+
export function splitDeclaration(line) {
|
|
881
|
+
const head = /^(?:export\s+)?(const|let|var)\s+([A-Za-z_$][\w$]*)/.exec(line);
|
|
882
|
+
if (!head)
|
|
883
|
+
return null;
|
|
884
|
+
let index = head[0].length;
|
|
885
|
+
const isSpace = (character) => character === " " || character === "\t";
|
|
886
|
+
while (index < line.length && isSpace(line[index]))
|
|
887
|
+
index++;
|
|
888
|
+
if (line[index] === ":") {
|
|
889
|
+
let depth = 0, quote = null;
|
|
890
|
+
index++;
|
|
891
|
+
for (;index < line.length; index++) {
|
|
892
|
+
const character = line[index];
|
|
893
|
+
if (quote) {
|
|
894
|
+
if (character === "\\") {
|
|
895
|
+
index++;
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
if (character === quote)
|
|
899
|
+
quote = null;
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
if (character === "'" || character === '"' || character === "`") {
|
|
903
|
+
quote = character;
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
if (character === "(" || character === "[" || character === "{" || character === "<") {
|
|
907
|
+
depth++;
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
if (character === ")" || character === "]" || character === "}" || character === ">") {
|
|
911
|
+
depth--;
|
|
912
|
+
continue;
|
|
913
|
+
}
|
|
914
|
+
if (character === "=" && depth <= 0) {
|
|
915
|
+
const next = line[index + 1], previous = line[index - 1];
|
|
916
|
+
if (next !== "=" && next !== ">" && previous !== "=" && previous !== "!" && previous !== "<" && previous !== ">")
|
|
917
|
+
break;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
if (line[index] !== "=")
|
|
921
|
+
return null;
|
|
922
|
+
}
|
|
923
|
+
while (index < line.length && isSpace(line[index]))
|
|
924
|
+
index++;
|
|
925
|
+
if (line[index] !== "=")
|
|
926
|
+
return null;
|
|
927
|
+
return {
|
|
928
|
+
type: head[1],
|
|
929
|
+
name: head[2],
|
|
930
|
+
value: line.slice(index + 1).trim()
|
|
931
|
+
};
|
|
932
|
+
}
|
|
880
933
|
function parseVariableDeclaration(lines, startIndex) {
|
|
881
|
-
const firstLine = lines[startIndex].trim(), match = firstLine.
|
|
934
|
+
const firstLine = lines[startIndex].trim(), split = splitDeclaration(firstLine), match = split ? [firstLine, split.type, split.name, split.value] : null;
|
|
882
935
|
if (!match) {
|
|
883
936
|
const destructuringPrefix = firstLine.match(/^(?:export\s+)?(const|let|var)\s+/);
|
|
884
937
|
if (destructuringPrefix) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/stx",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
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.
|
|
121
|
-
"@stacksjs/sanitizer": "0.2.
|
|
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.
|
|
123
|
+
"bun-plugin-stx": "0.2.131",
|
|
124
124
|
"bunfig": "^0.15.15",
|
|
125
|
-
"stx-router": "0.2.
|
|
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"
|