@vesk/compiler 0.2.25 → 0.2.27
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/client-codegen.d.ts +15 -0
- package/dist/client-codegen.d.ts.map +1 -1
- package/dist/client-codegen.js +343 -73
- package/dist/errors.d.ts +3 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +22 -1
- package/dist/ir-generator.d.ts.map +1 -1
- package/dist/ir-generator.js +200 -22
- package/dist/ir.d.ts +24 -1
- package/dist/ir.d.ts.map +1 -1
- package/dist/ir.js +28 -0
- package/dist/module-imports.d.ts +22 -1
- package/dist/module-imports.d.ts.map +1 -1
- package/dist/module-imports.js +177 -2
- package/dist/server-jsgen.d.ts.map +1 -1
- package/dist/server-jsgen.js +57 -24
- package/dist/server-render.d.ts.map +1 -1
- package/dist/server-render.js +20 -15
- package/package.json +2 -2
package/dist/module-imports.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync, realpathSync, statSync } from 'node:fs';
|
|
1
|
+
import { readFileSync, existsSync, realpathSync, statSync } from 'node:fs';
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import { dirname, extname, isAbsolute, join, resolve } from 'node:path';
|
|
4
4
|
import { print } from 'esrap';
|
|
@@ -644,10 +644,180 @@ function createModuleRequire(fromDir) {
|
|
|
644
644
|
return loaded;
|
|
645
645
|
};
|
|
646
646
|
}
|
|
647
|
+
const EMPTY_ALIASES = { exact: new Map(), wildcard: [] };
|
|
648
|
+
const ALIAS_CACHE = new Map();
|
|
649
|
+
/** Strips `//` + block comments and trailing commas so a tsconfig.json parses. */
|
|
650
|
+
function parseJsonc(raw) {
|
|
651
|
+
let out = '';
|
|
652
|
+
let inString = false;
|
|
653
|
+
let quote = '';
|
|
654
|
+
for (let i = 0; i < raw.length; i++) {
|
|
655
|
+
const ch = raw[i];
|
|
656
|
+
const next = raw[i + 1];
|
|
657
|
+
if (inString) {
|
|
658
|
+
out += ch;
|
|
659
|
+
if (ch === '\\') {
|
|
660
|
+
out += next ?? '';
|
|
661
|
+
i++;
|
|
662
|
+
}
|
|
663
|
+
else if (ch === quote) {
|
|
664
|
+
inString = false;
|
|
665
|
+
}
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
if (ch === '"' || ch === "'") {
|
|
669
|
+
inString = true;
|
|
670
|
+
quote = ch;
|
|
671
|
+
out += ch;
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
if (ch === '/' && next === '/') {
|
|
675
|
+
while (i < raw.length && raw[i] !== '\n')
|
|
676
|
+
i++;
|
|
677
|
+
out += '\n';
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
if (ch === '/' && next === '*') {
|
|
681
|
+
i += 2;
|
|
682
|
+
while (i < raw.length && !(raw[i] === '*' && raw[i + 1] === '/'))
|
|
683
|
+
i++;
|
|
684
|
+
i++;
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
out += ch;
|
|
688
|
+
}
|
|
689
|
+
return JSON.parse(out.replace(/,\s*([}\]])/g, '$1'));
|
|
690
|
+
}
|
|
691
|
+
/** Nearest `tsconfig.json` at or above `fromDir`, or null. */
|
|
692
|
+
export function findTsconfigPath(fromDir) {
|
|
693
|
+
let dir = fromDir;
|
|
694
|
+
for (let depth = 0; depth < 64; depth++) {
|
|
695
|
+
const st = statOrNull(join(dir, 'tsconfig.json'));
|
|
696
|
+
if (st && st.isFile())
|
|
697
|
+
return join(dir, 'tsconfig.json');
|
|
698
|
+
const parent = dirname(dir);
|
|
699
|
+
if (parent === dir)
|
|
700
|
+
break;
|
|
701
|
+
dir = parent;
|
|
702
|
+
}
|
|
703
|
+
return null;
|
|
704
|
+
}
|
|
705
|
+
function buildAliases(tsconfig) {
|
|
706
|
+
let raw;
|
|
707
|
+
try {
|
|
708
|
+
raw = readFileSync(tsconfig, 'utf-8');
|
|
709
|
+
}
|
|
710
|
+
catch {
|
|
711
|
+
return EMPTY_ALIASES;
|
|
712
|
+
}
|
|
713
|
+
let parsed = null;
|
|
714
|
+
try {
|
|
715
|
+
parsed = parseJsonc(raw);
|
|
716
|
+
}
|
|
717
|
+
catch {
|
|
718
|
+
return EMPTY_ALIASES;
|
|
719
|
+
}
|
|
720
|
+
const co = parsed?.compilerOptions;
|
|
721
|
+
if (!co || typeof co.paths !== 'object' || co.paths === null)
|
|
722
|
+
return EMPTY_ALIASES;
|
|
723
|
+
const configDir = dirname(tsconfig);
|
|
724
|
+
const baseUrl = typeof co.baseUrl === 'string' && co.baseUrl.length > 0 ? resolve(configDir, co.baseUrl) : configDir;
|
|
725
|
+
const exact = new Map();
|
|
726
|
+
const wildcard = [];
|
|
727
|
+
for (const [key, value] of Object.entries(co.paths)) {
|
|
728
|
+
if (!Array.isArray(value))
|
|
729
|
+
continue;
|
|
730
|
+
const targets = value.filter((t) => typeof t === 'string').map((t) => resolve(baseUrl, t));
|
|
731
|
+
if (targets.length === 0)
|
|
732
|
+
continue;
|
|
733
|
+
const star = key.indexOf('*');
|
|
734
|
+
if (star === -1)
|
|
735
|
+
exact.set(key, targets);
|
|
736
|
+
else
|
|
737
|
+
wildcard.push({ prefix: key.slice(0, star), suffix: key.slice(star + 1), targets });
|
|
738
|
+
}
|
|
739
|
+
// Longest prefix first so a specific alias (`@app/`) beats a broad one (`@/`).
|
|
740
|
+
wildcard.sort((a, b) => b.prefix.length - a.prefix.length);
|
|
741
|
+
return { exact, wildcard };
|
|
742
|
+
}
|
|
743
|
+
function loadAliases(fromDir) {
|
|
744
|
+
const tsconfig = findTsconfigPath(fromDir);
|
|
745
|
+
if (!tsconfig)
|
|
746
|
+
return EMPTY_ALIASES;
|
|
747
|
+
const st = statOrNull(tsconfig);
|
|
748
|
+
if (!st)
|
|
749
|
+
return EMPTY_ALIASES;
|
|
750
|
+
const cached = ALIAS_CACHE.get(tsconfig);
|
|
751
|
+
if (cached && cached.mtimeMs === st.mtimeMs)
|
|
752
|
+
return cached.aliases;
|
|
753
|
+
const aliases = buildAliases(tsconfig);
|
|
754
|
+
if (ALIAS_CACHE.size >= 64)
|
|
755
|
+
ALIAS_CACHE.clear();
|
|
756
|
+
ALIAS_CACHE.set(tsconfig, { mtimeMs: st.mtimeMs, aliases });
|
|
757
|
+
return aliases;
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Resolves a tsconfig-alias specifier (`@/x`, `@app/x`) to an existing file via
|
|
761
|
+
* the nearest tsconfig's `baseUrl` + `paths`, or null when the specifier is not
|
|
762
|
+
* aliased or no target exists (caller then falls through to normal resolution).
|
|
763
|
+
*/
|
|
764
|
+
export function resolveAliasModule(spec, fromDir) {
|
|
765
|
+
const aliases = loadAliases(fromDir);
|
|
766
|
+
if (aliases.exact.size === 0 && aliases.wildcard.length === 0)
|
|
767
|
+
return null;
|
|
768
|
+
const candidates = [];
|
|
769
|
+
const exact = aliases.exact.get(spec);
|
|
770
|
+
if (exact)
|
|
771
|
+
candidates.push(...exact);
|
|
772
|
+
for (const w of aliases.wildcard) {
|
|
773
|
+
if (spec.startsWith(w.prefix) && spec.endsWith(w.suffix) && spec.length >= w.prefix.length + w.suffix.length) {
|
|
774
|
+
const middle = spec.slice(w.prefix.length, spec.length - w.suffix.length);
|
|
775
|
+
candidates.push(...w.targets.map((t) => t.replace('*', middle)));
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
for (const candidate of candidates) {
|
|
779
|
+
const found = probeFile(candidate);
|
|
780
|
+
if (found)
|
|
781
|
+
return found;
|
|
782
|
+
}
|
|
783
|
+
return null;
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Resolves a relative import specifier to an absolute file path for a bundler
|
|
787
|
+
* (esbuild/rollup) to consume. An explicit extension is preserved — `./x.ts`
|
|
788
|
+
* stays `./x.ts` and must never become `./x.ts.ts`. An extensionless specifier
|
|
789
|
+
* is probed against `EXTENSIONS` (then `.vsk`, so component imports resolve),
|
|
790
|
+
* and falls back to `.ts` so the bundler reports the missing file by name.
|
|
791
|
+
* tsconfig `paths` aliases (`@/x`, `@app/x`) resolve against the app tsconfig;
|
|
792
|
+
* other bare/absolute specifiers are returned unchanged. This is the single
|
|
793
|
+
* source of truth for every relative-import rewrite (SSR loader and client
|
|
794
|
+
* chunk bundler).
|
|
795
|
+
*/
|
|
796
|
+
export function resolveImportPath(spec, fromDir) {
|
|
797
|
+
if (spec.startsWith('./') || spec.startsWith('../')) {
|
|
798
|
+
const base = resolve(fromDir, spec);
|
|
799
|
+
if (extname(base))
|
|
800
|
+
return base;
|
|
801
|
+
for (const suffix of EXTENSIONS) {
|
|
802
|
+
if (existsSync(base + suffix))
|
|
803
|
+
return base + suffix;
|
|
804
|
+
}
|
|
805
|
+
if (existsSync(`${base}.vsk`))
|
|
806
|
+
return `${base}.vsk`;
|
|
807
|
+
return `${base}.ts`;
|
|
808
|
+
}
|
|
809
|
+
if (!isAbsolute(spec)) {
|
|
810
|
+
const aliased = resolveAliasModule(spec, fromDir);
|
|
811
|
+
if (aliased)
|
|
812
|
+
return aliased;
|
|
813
|
+
}
|
|
814
|
+
return spec;
|
|
815
|
+
}
|
|
647
816
|
/**
|
|
648
817
|
* Resolves a specifier to an absolute file path (or a `BUILTIN_PREFIX` marker
|
|
649
818
|
* for Node builtins). Relative/absolute localities use extension probing;
|
|
650
|
-
*
|
|
819
|
+
* tsconfig `paths` aliases (`@/x`, `@app/x`) resolve against the app tsconfig;
|
|
820
|
+
* other bare specifiers (`npm-package`, `pkg/subpath`, `node:fs`, `fs`) prefer
|
|
651
821
|
* Node's own resolver — which understands `exports` maps, conditions, scoped
|
|
652
822
|
* packages and builtins — and fall back to a `node_modules` walk-up.
|
|
653
823
|
*
|
|
@@ -670,6 +840,11 @@ export function resolveSsrModule(specifier, fromDir) {
|
|
|
670
840
|
resolved = probeFile(resolve(fromDir, specifier));
|
|
671
841
|
}
|
|
672
842
|
else {
|
|
843
|
+
// tsconfig `paths` alias (`@/x`, `@app/x`) — resolved before Node's
|
|
844
|
+
// resolver so app-local aliases win over any same-named package.
|
|
845
|
+
const aliased = resolveAliasModule(specifier, fromDir);
|
|
846
|
+
if (aliased)
|
|
847
|
+
return toRealPath(aliased);
|
|
673
848
|
// Bare specifier — prefer the native resolver (exports map, conditions,
|
|
674
849
|
// builtins, symlinks), then fall back to a node_modules walk-up.
|
|
675
850
|
const native = nativeResolve(specifier, fromDir);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-jsgen.d.ts","sourceRoot":"","sources":["../src/server-jsgen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAOzE,OAAO,EAAkG,KAAK,WAAW,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"server-jsgen.d.ts","sourceRoot":"","sources":["../src/server-jsgen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAOzE,OAAO,EAAkG,KAAK,WAAW,EAAE,MAAM,mCAAmC,CAAC;AAmDrK,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,EAAE,OAAO,GAAE,OAAe,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAqDjJ;AAkXD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAqD1F;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,EAAE,CA4BzE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAYhG"}
|
package/dist/server-jsgen.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { StaticNode, TextNode, DynamicBinding, OpaqueDynamicRegion, MapRegion, WhileLoop, SwitchBlock, TryCatch, ForLoop, TrackDecl, RuntimeStatement, ComponentRef, ComponentCall, ServerBlock, ClientBlock, HeadBlock, SlotNode, } from '@vesk/compiler/src/ir';
|
|
1
|
+
import { StaticNode, TextNode, DynamicBinding, OpaqueDynamicRegion, MapRegion, WhileLoop, SwitchBlock, TryCatch, ForLoop, TrackDecl, RuntimeStatement, ComponentRef, ComponentCall, ServerBlock, ClientBlock, HeadBlock, SlotNode, PropSlot, PropSlotRender, } from '@vesk/compiler/src/ir';
|
|
2
2
|
import { isStaticIR, collectTrackedNames, transformTracked, transformTrackedInit, semicolonizeStatement } from '@vesk/compiler/src/client-codegen';
|
|
3
3
|
import { unwrapTrackCall, stripTrackGeneric, hasTopLevelComma, skipWhitespace, findBalancedEnd, startsWithIdentifier } from '@vesk/compiler/src/scan';
|
|
4
4
|
import { escapeHtml, indent, exprJS, extractTopLevelNames, extractRuntimeNames, buildParamInit, __vskHydrate, __vskImportedNames, setVskImportedNames, setVskForceClaim, takeVskForceClaim, nextVskId, } from '@vesk/compiler/src/server-utils';
|
|
@@ -19,14 +19,28 @@ import { localValueImportNames } from '@vesk/compiler/src/module-imports';
|
|
|
19
19
|
// the client's for every property, not just sticky.
|
|
20
20
|
//
|
|
21
21
|
// The wrapper tag must be `<span>`, not `<div>`. The HTML parser implicitly
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
22
|
+
// Marker-ONLY component boundary. The client's shared walker claims a
|
|
23
|
+
// component call-site by reading the component's own root element off this
|
|
24
|
+
// marker — the SSR output of a component call is exactly the marker followed
|
|
25
|
+
// by the callee's content. A `<span style="display:contents">` box here would
|
|
26
|
+
// sit between the marker and that root, so a `nextElement(tag)` claim (e.g.
|
|
27
|
+
// LoadingIndicator claiming `div`) hits a tag mismatch, never advances the
|
|
28
|
+
// walker cursor, and every later claim miss wipes the SSR content.
|
|
29
|
+
//
|
|
30
|
+
// Every marker is keyed, no bare markers (Hydrate-Todo: keyed markers):
|
|
31
|
+
// component boundaries carry the callee name (`<!--vsk:c:Name-->`),
|
|
32
|
+
// static-subtree boundaries carry the tag (`<!--vsk:t:tag-->`). The walker
|
|
33
|
+
// asserts the tag identity on adopt, reports skew deterministically, and names
|
|
34
|
+
// every miss/orphan — divergence is pinpointed at the exact boundary instead
|
|
35
|
+
// of cascading anonymously.
|
|
36
|
+
function sanitizeMarkerName(name) {
|
|
37
|
+
// No regex (compiler rule): identifiers cannot contain comment-breaking
|
|
38
|
+
// sequences, but guard anyway — `--` would terminate the HTML comment.
|
|
39
|
+
return String(name).split('--').join('-').split('<').join('').split('>').join('');
|
|
40
|
+
}
|
|
41
|
+
function componentMarker(compName) {
|
|
42
|
+
return `<!--vsk:c:${sanitizeMarkerName(compName)}-->`;
|
|
43
|
+
}
|
|
30
44
|
export function irNodeToJS(node, importedNames, isAsync = false, tracked) {
|
|
31
45
|
importedNames = importedNames || __vskImportedNames;
|
|
32
46
|
if (node instanceof StaticNode)
|
|
@@ -93,6 +107,10 @@ export function irNodeToJS(node, importedNames, isAsync = false, tracked) {
|
|
|
93
107
|
return semicolonizeStatement(transformTracked(node, tracked || new Map()));
|
|
94
108
|
if (node instanceof SlotNode)
|
|
95
109
|
return `__out.push(props.children || '');`;
|
|
110
|
+
if (node instanceof PropSlotRender)
|
|
111
|
+
return `__out.push(props.${node.propName} || '');`;
|
|
112
|
+
if (node instanceof PropSlot)
|
|
113
|
+
return '';
|
|
96
114
|
return '';
|
|
97
115
|
}
|
|
98
116
|
let __currentCompName = '';
|
|
@@ -108,13 +126,11 @@ function exprJSX(node, tracked) {
|
|
|
108
126
|
function staticNodeToJS(node, isAsync = false, tracked) {
|
|
109
127
|
const lines = [];
|
|
110
128
|
const dynAttrTargets = new Set();
|
|
111
|
-
const dynAttrOrder = [];
|
|
112
129
|
for (const child of node.children) {
|
|
113
130
|
if (child instanceof DynamicBinding && child.kind === 'attribute' && child.target !== null && child.target !== 'ref') {
|
|
114
131
|
if (isEvent(child.target))
|
|
115
132
|
continue;
|
|
116
133
|
dynAttrTargets.add(child.target);
|
|
117
|
-
dynAttrOrder.push(child.target);
|
|
118
134
|
}
|
|
119
135
|
}
|
|
120
136
|
const hasDynamicAttrs = dynAttrTargets.size > 0;
|
|
@@ -122,7 +138,9 @@ function staticNodeToJS(node, isAsync = false, tracked) {
|
|
|
122
138
|
const forceClaim = takeVskForceClaim();
|
|
123
139
|
const subtreeNeedsJS = __vskHydrate && (forceClaim || !isStaticIR(node.children));
|
|
124
140
|
if (subtreeNeedsJS) {
|
|
125
|
-
|
|
141
|
+
// Keyed static boundary (Hydrate-Todo: no bare markers): the walker
|
|
142
|
+
// asserts this tag on adopt and names it in every miss/orphan report.
|
|
143
|
+
lines.push('__out.push(' + JSON.stringify(`<!--vsk:t:${sanitizeMarkerName(node.tag)}-->`) + ');');
|
|
126
144
|
}
|
|
127
145
|
for (const attr of node.attributes) {
|
|
128
146
|
if (isEvent(attr.name))
|
|
@@ -136,17 +154,13 @@ function staticNodeToJS(node, isAsync = false, tracked) {
|
|
|
136
154
|
openTag += ` ${attr.name}="${escapeHtml(attr.value)}"`;
|
|
137
155
|
}
|
|
138
156
|
}
|
|
139
|
-
for (const target of dynAttrOrder) {
|
|
140
|
-
openTag += ` ${target}=""`;
|
|
141
|
-
}
|
|
142
157
|
if (node.selfClosing) {
|
|
143
158
|
let tag = openTag + ' />';
|
|
144
159
|
if (hasDynamicAttrs) {
|
|
145
160
|
let expr = JSON.stringify(tag);
|
|
146
161
|
for (const child of node.children) {
|
|
147
162
|
if (child instanceof DynamicBinding && child.kind === 'attribute' && child.target !== 'ref' && !isEvent(child.target)) {
|
|
148
|
-
|
|
149
|
-
expr = `${expr}.replace(${JSON.stringify(' ' + child.target + '=""')}, ' ' + ${JSON.stringify(child.target)} + '=\"' + ${val} + '\"')`;
|
|
163
|
+
expr += ` + __attr(${JSON.stringify(child.target)}, ${exprJSX(child.expression, tracked)})`;
|
|
150
164
|
}
|
|
151
165
|
}
|
|
152
166
|
lines.push(`__out.push(${expr});`);
|
|
@@ -161,8 +175,7 @@ function staticNodeToJS(node, isAsync = false, tracked) {
|
|
|
161
175
|
let expr = JSON.stringify(openTag);
|
|
162
176
|
for (const child of node.children) {
|
|
163
177
|
if (child instanceof DynamicBinding && child.kind === 'attribute' && child.target !== 'ref' && !isEvent(child.target)) {
|
|
164
|
-
|
|
165
|
-
expr = `${expr}.replace(${JSON.stringify(' ' + child.target + '=""')}, ' ' + ${JSON.stringify(child.target)} + '=\"' + ${val} + '\"')`;
|
|
178
|
+
expr += ` + __attr(${JSON.stringify(child.target)}, ${exprJSX(child.expression, tracked)})`;
|
|
166
179
|
}
|
|
167
180
|
}
|
|
168
181
|
lines.push(`__out.push(${expr});`);
|
|
@@ -413,9 +426,29 @@ function componentCallToJS(node, importedNames, isAsync = false, tracked) {
|
|
|
413
426
|
propsEntries.push(`...${exprJSX(sp, tracked)}`);
|
|
414
427
|
}
|
|
415
428
|
const lines = [];
|
|
416
|
-
|
|
429
|
+
// Named content slots (`trigger={<Button/>}`) thread through the same channel
|
|
430
|
+
// as `children`: a closed-over IIFE that serializes the slot body to a string.
|
|
431
|
+
const slotChildren = node.children.filter((c) => c instanceof PropSlot);
|
|
432
|
+
const regularChildren = node.children.filter((c) => !(c instanceof PropSlot));
|
|
433
|
+
for (const slot of slotChildren) {
|
|
434
|
+
const slotLines = [];
|
|
435
|
+
for (const child of slot.body) {
|
|
436
|
+
const code = irNodeToJS(child, importedNames, isAsync, tracked);
|
|
437
|
+
if (code)
|
|
438
|
+
slotLines.push(code);
|
|
439
|
+
}
|
|
440
|
+
if (slotLines.length > 0) {
|
|
441
|
+
const slotVar = `__sl${nextVskId()}`;
|
|
442
|
+
propsEntries.push(`${JSON.stringify(slot.propName)}: ${slotVar}`);
|
|
443
|
+
lines.push(`const ${slotVar} = ${isAsync ? 'await (async ' : '('}() => {`);
|
|
444
|
+
lines.push(`const __out = [];`);
|
|
445
|
+
lines.push(indent(slotLines.join('\n')));
|
|
446
|
+
lines.push(`return __out.join(''); })();`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (regularChildren.length > 0) {
|
|
417
450
|
const childLines = [];
|
|
418
|
-
for (const child of
|
|
451
|
+
for (const child of regularChildren) {
|
|
419
452
|
const code = irNodeToJS(child, importedNames, isAsync, tracked);
|
|
420
453
|
if (code)
|
|
421
454
|
childLines.push(code);
|
|
@@ -448,9 +481,7 @@ function componentCallToJS(node, importedNames, isAsync = false, tracked) {
|
|
|
448
481
|
lines.push(`const ${calleeVar} = ${callee};`);
|
|
449
482
|
const callExpr = `${awaitKw}${calleeVar}(${propsObj}, __registry, (${calleeVar}.__veskScope || __vesk))`;
|
|
450
483
|
if (__vskHydrate) {
|
|
451
|
-
|
|
452
|
-
// become a layout box (see the constant's comment for why).
|
|
453
|
-
lines.push(`__out.push(${JSON.stringify(HYDRATE_COMPONENT_WRAPPER)} + (${callExpr} || '') + '</span>');`);
|
|
484
|
+
lines.push(`__out.push(${JSON.stringify(componentMarker(compName))} + (${callExpr} || ''));`);
|
|
454
485
|
}
|
|
455
486
|
else {
|
|
456
487
|
lines.push(`__out.push(${callExpr} || '');`);
|
|
@@ -468,6 +499,8 @@ export function generateFunctionBody(comp, importedNames) {
|
|
|
468
499
|
lines.push(`__sa({ c: null, p: __prev });`);
|
|
469
500
|
lines.push(`try {`);
|
|
470
501
|
lines.push(`const __escape = (s) => { s = String(s); return s.split('&').join('&').split('<').join('<').split('>').join('>').split(${JSON.stringify('"')}).join('"'); };`);
|
|
502
|
+
lines.push(`const __styleText = (v) => { if (typeof v === 'string') return v; if (v && typeof v === 'object') { let s = ''; for (const k in v) { const x = v[k]; if (x == null || x === false) continue; s += k.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase()) + ':' + x + ';'; } return s; } return String(v); };`);
|
|
503
|
+
lines.push(`const __attr = (n, v) => (v == null || v === false) ? '' : ' ' + n + '="' + __escape(n === 'style' ? __styleText(v) : String(v)) + '"';`);
|
|
471
504
|
lines.push(`const raw = (s) => s == null ? '' : String(s);`);
|
|
472
505
|
lines.push(`const __tk = globalThis.__vsk_ssr_token || '';`);
|
|
473
506
|
if (asyncMode) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-render.d.ts","sourceRoot":"","sources":["../src/server-render.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,SAAS,EAAE,eAAe,EAAqB,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AA6BvJ,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,iBAAiB,CAEhG;
|
|
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
|
@@ -40,24 +40,29 @@ function compileFileInternal(source, sourcePath, seenImportFiles) {
|
|
|
40
40
|
if (seenImportFiles.has(importPath))
|
|
41
41
|
continue;
|
|
42
42
|
seenImportFiles.add(importPath);
|
|
43
|
+
// Only truly unresolvable imports are skipped. A `.vsk` file that EXISTS
|
|
44
|
+
// but fails to compile must surface its error — silently dropping it here
|
|
45
|
+
// corrupts the component registry (the caller later crashes on
|
|
46
|
+
// `undefined.__veskScope` with no pointer back to the real cause).
|
|
47
|
+
let importedSrc;
|
|
43
48
|
try {
|
|
44
|
-
|
|
45
|
-
const sub = compileFileInternal(importedSrc, importPath, seenImportFiles);
|
|
46
|
-
for (const [name, fn] of sub.componentMap) {
|
|
47
|
-
if (!componentMap.has(name))
|
|
48
|
-
componentMap.set(name, fn);
|
|
49
|
-
}
|
|
50
|
-
// Hoist the sub-`.vsk`'s scope into this file's so that
|
|
51
|
-
// registry-hoisted sub components (called with this `__vesk`) can
|
|
52
|
-
// destructure runtime imports, module values and top-level helpers.
|
|
53
|
-
for (const key of Object.keys(sub.__vesk)) {
|
|
54
|
-
if (key in __vesk)
|
|
55
|
-
continue;
|
|
56
|
-
__vesk[key] = sub.__vesk[key];
|
|
57
|
-
}
|
|
49
|
+
importedSrc = readFileSync(importPath, 'utf-8');
|
|
58
50
|
}
|
|
59
51
|
catch {
|
|
60
|
-
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const sub = compileFileInternal(importedSrc, importPath, seenImportFiles);
|
|
55
|
+
for (const [name, fn] of sub.componentMap) {
|
|
56
|
+
if (!componentMap.has(name))
|
|
57
|
+
componentMap.set(name, fn);
|
|
58
|
+
}
|
|
59
|
+
// Hoist the sub-`.vsk`'s scope into this file's so that
|
|
60
|
+
// registry-hoisted sub components (called with this `__vesk`) can
|
|
61
|
+
// destructure runtime imports, module values and top-level helpers.
|
|
62
|
+
for (const key of Object.keys(sub.__vesk)) {
|
|
63
|
+
if (key in __vesk)
|
|
64
|
+
continue;
|
|
65
|
+
__vesk[key] = sub.__vesk[key];
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
68
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vesk/compiler",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.27",
|
|
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.27",
|
|
24
24
|
"acorn": "^8.15.0",
|
|
25
25
|
"esrap": "^2.3.0",
|
|
26
26
|
"is-reference": "^3.0.0",
|