@vesk/compiler 0.2.92 → 0.2.94
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/acorn-ts-plugin/index.js +1 -0
- package/dist/client-codegen.d.ts.map +1 -1
- package/dist/client-codegen.js +136 -29
- package/dist/errors.d.ts +28 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +133 -25
- package/dist/ir-generator.d.ts.map +1 -1
- package/dist/ir-generator.js +65 -1
- package/dist/module-imports.d.ts +9 -2
- package/dist/module-imports.d.ts.map +1 -1
- package/dist/module-imports.js +191 -44
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +306 -15
- package/dist/precompile.d.ts.map +1 -1
- package/dist/precompile.js +40 -2
- package/dist/server-render.d.ts.map +1 -1
- package/dist/server-render.js +36 -35
- package/package.json +2 -2
package/dist/parser.js
CHANGED
|
@@ -573,26 +573,317 @@ export function parse(source, options = {}) {
|
|
|
573
573
|
column = (err.loc.column ?? 0) + 1;
|
|
574
574
|
}
|
|
575
575
|
else {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
576
|
+
// Parse "(line:column)" from the message without regex.
|
|
577
|
+
const parenOpen = err.message.indexOf('(');
|
|
578
|
+
if (parenOpen >= 0) {
|
|
579
|
+
const parenClose = err.message.indexOf(')', parenOpen);
|
|
580
|
+
if (parenClose > parenOpen) {
|
|
581
|
+
const inside = err.message.slice(parenOpen + 1, parenClose);
|
|
582
|
+
const colon = inside.indexOf(':');
|
|
583
|
+
if (colon > 0) {
|
|
584
|
+
line = parseInt(inside.slice(0, colon), 10);
|
|
585
|
+
column = parseInt(inside.slice(colon + 1), 10) + 1;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
580
588
|
}
|
|
581
589
|
}
|
|
582
590
|
const filename = options.filename || '';
|
|
583
|
-
|
|
584
|
-
|
|
591
|
+
// Strip the trailing "(line:column)" acorn appends, without regex.
|
|
592
|
+
let cleanMessage = err.message;
|
|
593
|
+
const lastParen = cleanMessage.lastIndexOf('(');
|
|
594
|
+
if (lastParen >= 0) {
|
|
595
|
+
const closeParen = cleanMessage.indexOf(')', lastParen);
|
|
596
|
+
if (closeParen > lastParen) {
|
|
597
|
+
const inside = cleanMessage.slice(lastParen + 1, closeParen);
|
|
598
|
+
const colon = inside.indexOf(':');
|
|
599
|
+
if (colon > 0 &&
|
|
600
|
+
inside.slice(0, colon).split('').every((ch) => ch >= '0' && ch <= '9') &&
|
|
601
|
+
inside.slice(colon + 1).split('').every((ch) => ch >= '0' && ch <= '9')) {
|
|
602
|
+
cleanMessage = cleanMessage.slice(0, lastParen).trimEnd();
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
// --- Convert acorn's raw JSX errors into descriptive Vesk messages ---
|
|
607
|
+
let message = cleanMessage;
|
|
608
|
+
let errLine = line;
|
|
609
|
+
let errColumn = column;
|
|
610
|
+
let code;
|
|
611
|
+
const extraHelp = [];
|
|
612
|
+
const frame = errLine > 0 ? codeFrame(source, errLine, errColumn, 5, 5) : '';
|
|
585
613
|
const hint = filename ? ` in ${filename}` : '';
|
|
586
|
-
|
|
614
|
+
// "Unterminated JSX contents" — acorn hit EOF inside a JSX block.
|
|
615
|
+
if (cleanMessage.includes('Unterminated JSX contents')) {
|
|
616
|
+
throw VeskError.jsxUnterminated({
|
|
617
|
+
file: filename,
|
|
618
|
+
line: errLine,
|
|
619
|
+
column: errColumn,
|
|
620
|
+
frame,
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
// "JSX attributes must only be assigned a non-empty expression" — `{}`.
|
|
624
|
+
if (cleanMessage.includes('JSX attributes must only be assigned a non-empty expression')) {
|
|
625
|
+
throw VeskError.jsxEmptyExpression({
|
|
626
|
+
file: filename,
|
|
627
|
+
line: errLine,
|
|
628
|
+
column: errColumn,
|
|
629
|
+
frame,
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
// "Expected corresponding JSX closing tag for <X>" — acorn points at the
|
|
633
|
+
// opening tag, but it could mean either a mismatched closing tag
|
|
634
|
+
// (<div></span>) or a genuinely unclosed tag (<div><span>...</div>).
|
|
635
|
+
const jsxClosePrefix = 'Expected corresponding JSX closing tag for <';
|
|
636
|
+
if (cleanMessage.startsWith(jsxClosePrefix)) {
|
|
637
|
+
const mismatched = findMismatchedJsxTag(source, errLine);
|
|
638
|
+
if (mismatched) {
|
|
639
|
+
throw VeskError.jsxMismatchedTag(mismatched.openingTag, mismatched.closingTag, {
|
|
640
|
+
file: filename,
|
|
641
|
+
line: mismatched.line,
|
|
642
|
+
column: mismatched.column,
|
|
643
|
+
frame: codeFrame(source, mismatched.line, mismatched.column, 5, 5),
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
const rest = cleanMessage.slice(jsxClosePrefix.length);
|
|
647
|
+
const gt = rest.indexOf('>');
|
|
648
|
+
const tagName = gt >= 0 ? rest.slice(0, gt) : rest;
|
|
649
|
+
throw VeskError.jsxUnclosedTag(tagName, {
|
|
650
|
+
file: filename,
|
|
651
|
+
line: errLine,
|
|
652
|
+
column: errColumn,
|
|
653
|
+
frame,
|
|
654
|
+
suggestedFix: `Insert </${tagName}> before the tag that closes the parent element.`,
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
// "Unexpected token" inside a component body often means a JSX tag was
|
|
658
|
+
// opened but never closed — acorn reports it late, at the closing `}`.
|
|
659
|
+
if (cleanMessage.includes('Unexpected token') && errLine > 0) {
|
|
660
|
+
const srcLine = (source.split('\n')[errLine - 1] ?? '').trim();
|
|
661
|
+
const isBlockCloser = srcLine.startsWith('}') || srcLine.startsWith(')');
|
|
662
|
+
if (isBlockCloser) {
|
|
663
|
+
const unclosed = findUnclosedJsxTag(source, errLine);
|
|
664
|
+
if (unclosed) {
|
|
665
|
+
throw VeskError.jsxUnclosedTag(unclosed.tagName, {
|
|
666
|
+
file: filename,
|
|
667
|
+
line: unclosed.line,
|
|
668
|
+
column: unclosed.column,
|
|
669
|
+
frame: errLine > 0 ? codeFrame(source, unclosed.line, unclosed.column, 5, 5) : '',
|
|
670
|
+
suggestedFix: `Insert </${unclosed.tagName}> before the tag that closes the parent element.`,
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
// `{if (cond) { <jsx/> }}` — LLMs often wrap statements in braces in
|
|
675
|
+
// statement mode, but Vesk doesn't need them. Look for `{` immediately
|
|
676
|
+
// before the offending token (skipping whitespace) followed by a
|
|
677
|
+
// statement keyword (`if`, `for`, `while`, `switch`, `try`, `const`,
|
|
678
|
+
// `let`, `var`, `function`, `class`, `return`).
|
|
679
|
+
if (errColumn > 1) {
|
|
680
|
+
const lineText = source.split('\n')[errLine - 1] ?? '';
|
|
681
|
+
// Walk back from the error column to find the `{` that opened the
|
|
682
|
+
// block (or JSX expression container) that shouldn't be there.
|
|
683
|
+
let j = errColumn - 2; // 0-based index of char before error token
|
|
684
|
+
while (j >= 0 && (lineText[j] === ' ' || lineText[j] === '\t'))
|
|
685
|
+
j--;
|
|
686
|
+
if (j >= 0 && lineText[j] === '{') {
|
|
687
|
+
// Read the keyword after `{` (skipping whitespace)
|
|
688
|
+
let k = errColumn - 1; // 0-based index of error token start
|
|
689
|
+
while (k < lineText.length && (lineText[k] === ' ' || lineText[k] === '\t'))
|
|
690
|
+
k++;
|
|
691
|
+
let keyword = '';
|
|
692
|
+
while (k < lineText.length && ((lineText[k] >= 'a' && lineText[k] <= 'z') || (lineText[k] >= 'A' && lineText[k] <= 'Z') || lineText[k] === '_' || lineText[k] === '$')) {
|
|
693
|
+
keyword += lineText[k];
|
|
694
|
+
k++;
|
|
695
|
+
}
|
|
696
|
+
const statementKeywords = new Set(['if', 'for', 'while', 'switch', 'try', 'const', 'let', 'var', 'function', 'class', 'return', 'do', 'with']);
|
|
697
|
+
if (statementKeywords.has(keyword)) {
|
|
698
|
+
const startLine = errLine;
|
|
699
|
+
const startColumn = j + 1; // 1-based column of `{`
|
|
700
|
+
throw VeskError.blockStatementInStatementMode({
|
|
701
|
+
file: filename,
|
|
702
|
+
line: startLine,
|
|
703
|
+
column: startColumn,
|
|
704
|
+
frame: codeFrame(source, startLine, startColumn, 5, 5),
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
// Targeted help for a bare template literal used as a JSX attribute value,
|
|
711
|
+
// e.g. <Link href=`/admin/learners/${id}`>. JSX attributes must be a quoted
|
|
712
|
+
// string or a `{...}` expression; a backtick on its own is not valid JSX.
|
|
713
|
+
if (errLine > 0 &&
|
|
714
|
+
cleanMessage.includes('JSX value should be either an expression or a quoted JSX text')) {
|
|
715
|
+
const srcLine = (source.split('\n')[errLine - 1] ?? '');
|
|
716
|
+
if (srcLine.includes('`')) {
|
|
717
|
+
extraHelp.push('JSX attribute values must be a quoted string (`href="..."`) or a `{...}` expression (`href={expr}`).', 'A bare backtick template literal is not valid JSX — wrap it in braces:', ' href={`/admin/learners/${id}`}');
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
throw new VeskError(`${message}${hint}`, {
|
|
587
721
|
file: filename,
|
|
588
|
-
line,
|
|
589
|
-
column,
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
'If you wrote literal { or } inside JSX text, escape them as {\'{\'} and {\'}\'} or use { }.',
|
|
594
|
-
'Ensure all JSX tags are properly closed and component names start with an uppercase letter.',
|
|
595
|
-
],
|
|
722
|
+
line: errLine,
|
|
723
|
+
column: errColumn,
|
|
724
|
+
frame,
|
|
725
|
+
code,
|
|
726
|
+
nextSteps: extraHelp.length ? extraHelp : [],
|
|
596
727
|
});
|
|
597
728
|
}
|
|
598
729
|
}
|
|
730
|
+
/**
|
|
731
|
+
* Scan the source (character by character, no regex) tracking JSX opening and
|
|
732
|
+
* closing tags up to `upToLine`. Returns the most recently opened tag that
|
|
733
|
+
* was never closed, or `null` if all tags are balanced. Used only to produce
|
|
734
|
+
* a better error message when acorn reports a generic "Unexpected token"
|
|
735
|
+
* for an unclosed JSX tag.
|
|
736
|
+
*/
|
|
737
|
+
function findUnclosedJsxTag(source, upToLine) {
|
|
738
|
+
const stack = [];
|
|
739
|
+
let i = 0;
|
|
740
|
+
let currentLine = 1;
|
|
741
|
+
const isIdentStart = (code) => (code >= 97 && code <= 122) || (code >= 65 && code <= 90) || code === 95; // a-z A-Z _
|
|
742
|
+
while (i < source.length) {
|
|
743
|
+
if (source[i] === '\n') {
|
|
744
|
+
currentLine++;
|
|
745
|
+
i++;
|
|
746
|
+
if (currentLine > upToLine)
|
|
747
|
+
break;
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
if (currentLine > upToLine)
|
|
751
|
+
break;
|
|
752
|
+
if (source[i] === '<') {
|
|
753
|
+
// Closing tag: </tagName>
|
|
754
|
+
if (i + 1 < source.length && source[i + 1] === '/') {
|
|
755
|
+
let j = i + 2;
|
|
756
|
+
while (j < source.length && source[j] !== '>' && source[j] !== '\n')
|
|
757
|
+
j++;
|
|
758
|
+
const tagName = source.slice(i + 2, j);
|
|
759
|
+
// Only pop if it matches the most recently opened unclosed tag.
|
|
760
|
+
if (stack.length > 0 && stack[stack.length - 1].tagName === tagName) {
|
|
761
|
+
stack.pop();
|
|
762
|
+
}
|
|
763
|
+
// Skip past the closing >.
|
|
764
|
+
while (j < source.length && source[j] !== '>')
|
|
765
|
+
j++;
|
|
766
|
+
if (j < source.length)
|
|
767
|
+
j++;
|
|
768
|
+
i = j;
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
// Opening tag: <tagName ...> (skip namespace prefixes like ns.)
|
|
772
|
+
let j = i + 1;
|
|
773
|
+
while (j < source.length &&
|
|
774
|
+
(source[j] === '.' || source[j] === ':' ||
|
|
775
|
+
(source.charCodeAt(j) >= 97 && source.charCodeAt(j) <= 122) ||
|
|
776
|
+
(source.charCodeAt(j) >= 65 && source.charCodeAt(j) <= 90) ||
|
|
777
|
+
source.charCodeAt(j) === 95)) {
|
|
778
|
+
j++;
|
|
779
|
+
}
|
|
780
|
+
const tagName = source.slice(i + 1, j);
|
|
781
|
+
if (tagName.length > 0 && isIdentStart(tagName.charCodeAt(0))) {
|
|
782
|
+
// Check for self-closing: <tagName />
|
|
783
|
+
let k = j;
|
|
784
|
+
let selfClosing = false;
|
|
785
|
+
while (k < source.length && source[k] !== '>') {
|
|
786
|
+
if (source[k] === '/' && k + 1 < source.length && source[k + 1] === '>') {
|
|
787
|
+
selfClosing = true;
|
|
788
|
+
break;
|
|
789
|
+
}
|
|
790
|
+
k++;
|
|
791
|
+
}
|
|
792
|
+
if (!selfClosing) {
|
|
793
|
+
const loc = offsetToLineCol(source, i);
|
|
794
|
+
stack.push({ tagName, line: loc.line, column: loc.column });
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
// Skip past the closing >.
|
|
798
|
+
while (j < source.length && source[j] !== '>')
|
|
799
|
+
j++;
|
|
800
|
+
if (j < source.length)
|
|
801
|
+
j++;
|
|
802
|
+
i = j;
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
i++;
|
|
806
|
+
}
|
|
807
|
+
// The most recently opened tag still on the stack is the unclosed one.
|
|
808
|
+
if (stack.length > 0) {
|
|
809
|
+
return stack[stack.length - 1];
|
|
810
|
+
}
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Scan the source tracking JSX tags up to `upToLine`. Returns the first
|
|
815
|
+
* closing tag that does not match the most recently opened tag,
|
|
816
|
+
* e.g. `</span>` after `<div>`. Character scan only — no regex.
|
|
817
|
+
*/
|
|
818
|
+
function findMismatchedJsxTag(source, upToLine) {
|
|
819
|
+
const stack = [];
|
|
820
|
+
let i = 0;
|
|
821
|
+
let currentLine = 1;
|
|
822
|
+
const isIdentStart = (code) => (code >= 97 && code <= 122) || (code >= 65 && code <= 90) || code === 95;
|
|
823
|
+
while (i < source.length) {
|
|
824
|
+
if (source[i] === '\n') {
|
|
825
|
+
currentLine++;
|
|
826
|
+
i++;
|
|
827
|
+
if (currentLine > upToLine)
|
|
828
|
+
break;
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
if (currentLine > upToLine)
|
|
832
|
+
break;
|
|
833
|
+
if (source[i] === '<') {
|
|
834
|
+
// Closing tag: </tagName>
|
|
835
|
+
if (i + 1 < source.length && source[i + 1] === '/') {
|
|
836
|
+
let j = i + 2;
|
|
837
|
+
while (j < source.length && source[j] !== '>' && source[j] !== '\n')
|
|
838
|
+
j++;
|
|
839
|
+
const closingTag = source.slice(i + 2, j);
|
|
840
|
+
while (j < source.length && source[j] !== '>')
|
|
841
|
+
j++;
|
|
842
|
+
if (j < source.length)
|
|
843
|
+
j++;
|
|
844
|
+
const loc = offsetToLineCol(source, i);
|
|
845
|
+
// Mismatch: a closing tag that doesn't match the most recent opening tag.
|
|
846
|
+
if (stack.length > 0 && stack[stack.length - 1] !== closingTag) {
|
|
847
|
+
return { openingTag: stack[stack.length - 1], closingTag, line: loc.line, column: loc.column };
|
|
848
|
+
}
|
|
849
|
+
// Match: pop the stack.
|
|
850
|
+
if (stack.length > 0)
|
|
851
|
+
stack.pop();
|
|
852
|
+
i = j;
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
// Opening tag: <tagName ...>
|
|
856
|
+
let j = i + 1;
|
|
857
|
+
while (j < source.length &&
|
|
858
|
+
(source[j] === '.' || source[j] === ':' ||
|
|
859
|
+
(source.charCodeAt(j) >= 97 && source.charCodeAt(j) <= 122) ||
|
|
860
|
+
(source.charCodeAt(j) >= 65 && source.charCodeAt(j) <= 90) ||
|
|
861
|
+
source.charCodeAt(j) === 95)) {
|
|
862
|
+
j++;
|
|
863
|
+
}
|
|
864
|
+
const tagName = source.slice(i + 1, j);
|
|
865
|
+
if (tagName.length > 0 && isIdentStart(tagName.charCodeAt(0))) {
|
|
866
|
+
// Self-closing: <tagName />
|
|
867
|
+
let k = j;
|
|
868
|
+
let selfClosing = false;
|
|
869
|
+
while (k < source.length && source[k] !== '>') {
|
|
870
|
+
if (source[k] === '/' && k + 1 < source.length && source[k + 1] === '>') {
|
|
871
|
+
selfClosing = true;
|
|
872
|
+
break;
|
|
873
|
+
}
|
|
874
|
+
k++;
|
|
875
|
+
}
|
|
876
|
+
if (!selfClosing)
|
|
877
|
+
stack.push(tagName);
|
|
878
|
+
}
|
|
879
|
+
while (j < source.length && source[j] !== '>')
|
|
880
|
+
j++;
|
|
881
|
+
if (j < source.length)
|
|
882
|
+
j++;
|
|
883
|
+
i = j;
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
i++;
|
|
887
|
+
}
|
|
888
|
+
return null;
|
|
889
|
+
}
|
package/dist/precompile.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"precompile.d.ts","sourceRoot":"","sources":["../src/precompile.ts"],"names":[],"mappings":"AASA,OAAO,
|
|
1
|
+
{"version":3,"file":"precompile.d.ts","sourceRoot":"","sources":["../src/precompile.ts"],"names":[],"mappings":"AASA,OAAO,EAIL,cAAc,EACf,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EACV,kBAAkB,EAGnB,MAAM,uCAAuC,CAAC;AAG/C;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,cAAc,GACxB,kBAAkB,CAsBpB;AA8CD,iFAAiF;AACjF,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}
|
package/dist/precompile.js
CHANGED
|
@@ -26,16 +26,54 @@ export function precompileFile(source, sourcePath, registry) {
|
|
|
26
26
|
const hit = precompileCache.get(sourcePath);
|
|
27
27
|
// Compare the source, not just the path: the dev server rebuilds in-process
|
|
28
28
|
// and a stale plan would silently ship yesterday's component.
|
|
29
|
-
if (hit !== undefined && hit.source === source)
|
|
29
|
+
if (hit !== undefined && hit.source === source) {
|
|
30
|
+
// A plan is only half of the output: it references module keys that live
|
|
31
|
+
// in the caller's registry. A second build in the same process (prod then
|
|
32
|
+
// dev under `e2e-setup`) starts with a fresh registry, so replaying the
|
|
33
|
+
// memoized plan without its modules left `__veskModules` empty and every
|
|
34
|
+
// bare-package import (`vesk-qr`, …) resolved to `undefined`. Keys are
|
|
35
|
+
// path-derived, so registering the same absPaths here is idempotent.
|
|
36
|
+
for (const { absPath, entry } of hit.modules)
|
|
37
|
+
reg.register(absPath, entry);
|
|
30
38
|
return hit.plan;
|
|
39
|
+
}
|
|
31
40
|
const plan = precompileFileInternal(source, sourcePath, new Set(), reg);
|
|
32
41
|
// Bound it: plans are sizeable on a large app, and a watch session can
|
|
33
42
|
// outlive many builds.
|
|
34
43
|
if (precompileCache.size >= 512)
|
|
35
44
|
precompileCache.clear();
|
|
36
|
-
precompileCache.set(sourcePath, { source, plan });
|
|
45
|
+
precompileCache.set(sourcePath, { source, plan, modules: collectPlanModules(plan, reg) });
|
|
37
46
|
return plan;
|
|
38
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Every module entry a plan (and its nested `.vsk` sub-plans) resolves
|
|
50
|
+
* against, including transitive deps. The shared `__veskModules` registry
|
|
51
|
+
* must contain the whole closure, not just the keys named by `plan.modules`,
|
|
52
|
+
* or `evaluateModule` cannot walk `deps` at hydrate time.
|
|
53
|
+
*/
|
|
54
|
+
function collectPlanModules(plan, registry) {
|
|
55
|
+
const out = [];
|
|
56
|
+
const seen = new Set();
|
|
57
|
+
const visitKey = (key) => {
|
|
58
|
+
if (seen.has(key))
|
|
59
|
+
return;
|
|
60
|
+
seen.add(key);
|
|
61
|
+
const entry = registry.getModule(key);
|
|
62
|
+
if (!entry || !entry.absPath)
|
|
63
|
+
return;
|
|
64
|
+
out.push({ absPath: entry.absPath, entry });
|
|
65
|
+
for (const depKey of Object.values(entry.deps))
|
|
66
|
+
visitKey(depKey);
|
|
67
|
+
};
|
|
68
|
+
const visitPlan = (p) => {
|
|
69
|
+
for (const mod of p.modules)
|
|
70
|
+
visitKey(mod.key);
|
|
71
|
+
for (const sub of p.subFiles)
|
|
72
|
+
visitPlan(sub.plan);
|
|
73
|
+
};
|
|
74
|
+
visitPlan(plan);
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
39
77
|
/**
|
|
40
78
|
* Compiled plans, keyed by absolute path.
|
|
41
79
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-render.d.ts","sourceRoot":"","sources":["../src/server-render.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"server-render.d.ts","sourceRoot":"","sources":["../src/server-render.ts"],"names":[],"mappings":"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"}
|
package/dist/server-render.js
CHANGED
|
@@ -463,23 +463,24 @@ export function buildDataScripts(ssrProps, ssrData, external) {
|
|
|
463
463
|
return scripts;
|
|
464
464
|
}
|
|
465
465
|
export async function renderFullPage(source, componentName, props = {}, registry = new Map(), options = {}) {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
466
|
+
// Adopt the ambient request store when one is open: renderPage runs in the
|
|
467
|
+
// enclosing withSsrStore scope and attributes setSsrData writes to that
|
|
468
|
+
// store's token, so renderFullPage must read the same slot to hand off SSR
|
|
469
|
+
// data. Only mint a fresh store when called stand-alone (no ambient scope),
|
|
470
|
+
// which keeps concurrent requests isolated through their distinct tokens.
|
|
471
|
+
const store = adoptSsrStore();
|
|
472
|
+
const renderToken = store.token;
|
|
473
|
+
keepSsrSlot(renderToken);
|
|
474
|
+
pruneSsrDataSlots();
|
|
475
|
+
globalThis.__vsk_ssr = true;
|
|
476
|
+
// Start this render's handoff store fresh: setSsrData mirrors into
|
|
477
|
+
// globalThis.__vsk_ssr_data (see resource.ts) because the AsyncLocalStorage
|
|
478
|
+
// sink snapshot can be empty when the write runs in a forked async context.
|
|
479
|
+
// Resetting here also gives the /about-after-/posts no-leak behavior.
|
|
480
|
+
globalThis.__vsk_ssr_data = {};
|
|
481
|
+
let result;
|
|
482
|
+
try {
|
|
483
|
+
result = await withSsrStoreOf(store, async () => {
|
|
483
484
|
let ssrProps = { ...props };
|
|
484
485
|
let serializedProps = null;
|
|
485
486
|
const cached = options.cached || undefined;
|
|
@@ -500,18 +501,20 @@ export async function renderFullPage(source, componentName, props = {}, registry
|
|
|
500
501
|
// Defensive settle: page/layout renders already awaited their own SSR
|
|
501
502
|
// promises before cleanup; this covers any resource started here (loadFn).
|
|
502
503
|
await settleSsrPromises(renderToken);
|
|
503
|
-
// Merge the
|
|
504
|
-
//
|
|
505
|
-
//
|
|
506
|
-
//
|
|
507
|
-
//
|
|
504
|
+
// Merge the request's per-token data slot. The slot is the deterministic
|
|
505
|
+
// channel for writes that happened in a forked async context (native fetch
|
|
506
|
+
// .then() callbacks run outside the ALS scope) and is keyed per render
|
|
507
|
+
// token, so concurrent renders never mix data. The ALS sink (ssrSink) is
|
|
508
|
+
// NOT read here because withSsrStore reuses the ambient store when one is
|
|
509
|
+
// already open, so concurrent renderFullPage calls share a single sink and
|
|
510
|
+
// their writes would cross-contaminate. Every setSsrData write also lands
|
|
511
|
+
// in the slot, so the slot alone is a complete handoff source.
|
|
508
512
|
const slot = globalThis[`__vsk_ssr_data_${renderToken}`] || {};
|
|
509
513
|
const ssrData = {
|
|
510
|
-
...ssrSink.snapshot(),
|
|
511
514
|
...slot,
|
|
512
515
|
};
|
|
513
516
|
if (process.env.VESK_SSR_TRACE) {
|
|
514
|
-
console.error(`[render-trace] renderFullPage comp=${componentName} keys=${Object.keys(ssrData).join(',') || '∅'} slot=${Object.keys(slot).join(',') || '∅'}
|
|
517
|
+
console.error(`[render-trace] renderFullPage comp=${componentName} keys=${Object.keys(ssrData).join(',') || '∅'} slot=${Object.keys(slot).join(',') || '∅'}`);
|
|
515
518
|
}
|
|
516
519
|
let headHtml = rendered.head;
|
|
517
520
|
if (options.pageHead) {
|
|
@@ -565,17 +568,15 @@ ${bodyHtml}
|
|
|
565
568
|
${dataScriptBlock}${clientScript}</body>
|
|
566
569
|
</html>`;
|
|
567
570
|
return applyHtmlPlugins(docHtml, options.plugins, { sourcePath: options.sourcePath, url: options.__vesk?.url || undefined });
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
}
|
|
578
|
-
});
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
finally {
|
|
574
|
+
delete globalThis.__vsk_ssr;
|
|
575
|
+
delete globalThis[`__vsk_ssr_data_${renderToken}`];
|
|
576
|
+
dropSsrSlot(renderToken);
|
|
577
|
+
pruneSsrDataSlots();
|
|
578
|
+
}
|
|
579
|
+
return result;
|
|
579
580
|
}
|
|
580
581
|
export function renderPageStream(source, componentName, props = {}, registry = new Map(), options = {}) {
|
|
581
582
|
// One store for the whole stream. `next()` is driven by the consumer, so a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vesk/compiler",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.94",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Vesk compiler — lexer, parser, semantic analysis, IR, codegen",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@sveltejs/acorn-typescript": "^1.0.0",
|
|
23
|
-
"@vesk/types": "^0.2.
|
|
23
|
+
"@vesk/types": "^0.2.94",
|
|
24
24
|
"acorn": "^8.15.0",
|
|
25
25
|
"esrap": "^2.3.0",
|
|
26
26
|
"is-reference": "^3.0.0",
|