@vesk/compiler 0.2.26 → 0.2.28
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 +233 -64
- 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 +296 -98
- 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 +51 -16
- package/dist/server-render.d.ts.map +1 -1
- package/dist/server-render.js +20 -15
- package/package.json +2 -2
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAoBxD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,WAAW,GAAE,MAAU,GAAG,MAAM,GAAG,IAAI,CAWrG;AAqCD,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,SAAI,EAAE,KAAK,SAAI,GAAG,MAAM,CAkBrG;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAgBD,qBAAa,SAAU,SAAQ,KAAK;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;gBAEF,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,gBAAqB;IAYxD,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,GAAE,MAAM,EAAO,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAoBxD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,WAAW,GAAE,MAAU,GAAG,MAAM,GAAG,IAAI,CAWrG;AAqCD,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,SAAI,EAAE,KAAK,SAAI,GAAG,MAAM,CAkBrG;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAgBD,qBAAa,SAAU,SAAQ,KAAK;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;gBAEF,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,gBAAqB;IAYxD,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,GAAE,MAAM,EAAO,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IA8BnG,MAAM,CAAC,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAoB3D,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAmBvF,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAmBvF,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,MAAM,EAAO,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAO3G,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,GAAE,MAAM,EAAO,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAYvG,MAAM,CAAC,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAmB/G,MAAM,CAAC,cAAc,CAAC,OAAO,GAAE,gBAAgB,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,SAAS;IAmBpF,QAAQ,IAAI,MAAM;IA0BlB,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAclC"}
|
package/dist/errors.js
CHANGED
|
@@ -142,6 +142,7 @@ export class VeskError extends Error {
|
|
|
142
142
|
}
|
|
143
143
|
return new VeskError(msg, {
|
|
144
144
|
...context,
|
|
145
|
+
code: context.code || 'V0401',
|
|
145
146
|
suggestions: [name, ...allCandidates.slice(0, 8)],
|
|
146
147
|
nextSteps,
|
|
147
148
|
tip: isBuiltin
|
|
@@ -152,6 +153,7 @@ export class VeskError extends Error {
|
|
|
152
153
|
static classDecl(context = {}) {
|
|
153
154
|
return new VeskError('class declarations are not supported inside Vesk components.', {
|
|
154
155
|
...context,
|
|
156
|
+
code: context.code || 'V0402',
|
|
155
157
|
suggestions: [
|
|
156
158
|
'Use a plain object: const obj = { ... };',
|
|
157
159
|
'Use a factory function: function create() { return { ... }; }',
|
|
@@ -167,6 +169,7 @@ export class VeskError extends Error {
|
|
|
167
169
|
static serverBlockInClient(compName, context = {}) {
|
|
168
170
|
return new VeskError(`{#server} block found in client island "${compName}". Client islands render on both server and client, so {#server} blocks have no effect.`, {
|
|
169
171
|
...context,
|
|
172
|
+
code: context.code || 'V0403',
|
|
170
173
|
suggestions: [
|
|
171
174
|
`Remove the {#server} block from "${compName}".`,
|
|
172
175
|
`Or remove the \`client\` keyword from "${compName}" declaration.`,
|
|
@@ -181,6 +184,7 @@ export class VeskError extends Error {
|
|
|
181
184
|
static clientBlockInServer(compName, context = {}) {
|
|
182
185
|
return new VeskError(`{#client} block found in component "${compName}", but this component is not a client island. {#client} blocks are only allowed inside components declared with the \`client\` keyword.`, {
|
|
183
186
|
...context,
|
|
187
|
+
code: context.code || 'V0404',
|
|
184
188
|
suggestions: [
|
|
185
189
|
`Add \`client\`: \`component ${compName} client { ... }\``,
|
|
186
190
|
`Or remove the {#client}...{/client} block.`,
|
|
@@ -201,6 +205,7 @@ export class VeskError extends Error {
|
|
|
201
205
|
static configError(msg, validOptions = [], context = {}) {
|
|
202
206
|
return new VeskError(msg, {
|
|
203
207
|
...context,
|
|
208
|
+
code: context.code || 'V0405',
|
|
204
209
|
suggestions: validOptions.length ? [`Valid options: ${validOptions.join(', ')}`] : [],
|
|
205
210
|
nextSteps: [
|
|
206
211
|
'Check your vesk.config file for typos.',
|
|
@@ -211,6 +216,7 @@ export class VeskError extends Error {
|
|
|
211
216
|
static asyncChildInSyncParent(parentName, childName, context = {}) {
|
|
212
217
|
return new VeskError(`Component "${parentName}" renders "<${childName} />", but "<${childName} />" is async and "${parentName}" is not declared async.`, {
|
|
213
218
|
...context,
|
|
219
|
+
code: context.code || 'V0406',
|
|
214
220
|
suggestions: [
|
|
215
221
|
`Declare the parent async: \`async component ${parentName} ...\``,
|
|
216
222
|
],
|
|
@@ -222,8 +228,23 @@ export class VeskError extends Error {
|
|
|
222
228
|
tip: `A component that renders an async component must itself be async so the renderer can await it before serializing the HTML. Async components also include components that call \`useFetch\`.`,
|
|
223
229
|
});
|
|
224
230
|
}
|
|
231
|
+
static attrJsxElement(context = {}) {
|
|
232
|
+
return new VeskError(`Attribute "${context.attr ?? ''}" on an HTML element is given a JSX element value. DOM attributes hold plain values — JSX elements belong to content or to a component prop (where they become a content slot).`, {
|
|
233
|
+
...context,
|
|
234
|
+
code: context.code || 'V0407',
|
|
235
|
+
suggestions: [
|
|
236
|
+
'Pass the element as a child instead: <div>{ <yourElement /> }</div>',
|
|
237
|
+
'If the component accepts it, pass the element as a component prop instead of an HTML attribute.',
|
|
238
|
+
],
|
|
239
|
+
nextSteps: [
|
|
240
|
+
'Move the JSX element into the element\'s content so it renders as a child node.',
|
|
241
|
+
'Component props can hold JSX elements — they are threaded as content slots and read back with {props.<name>}.',
|
|
242
|
+
],
|
|
243
|
+
tip: 'JSX elements are content. They render inside an element or travel as a component prop — never as an attribute value on an HTML tag.',
|
|
244
|
+
});
|
|
245
|
+
}
|
|
225
246
|
toString() {
|
|
226
|
-
let out = `[vesk] ${this.message}`;
|
|
247
|
+
let out = this.code ? `[vesk ${this.code}] ${this.message}` : `[vesk] ${this.message}`;
|
|
227
248
|
if (this.file) {
|
|
228
249
|
out += `\n File: ${this.file}`;
|
|
229
250
|
if (this.line) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ir-generator.d.ts","sourceRoot":"","sources":["../src/ir-generator.ts"],"names":[],"mappings":"AACA,OAAO,EACL,MAAM,
|
|
1
|
+
{"version":3,"file":"ir-generator.d.ts","sourceRoot":"","sources":["../src/ir-generator.ts"],"names":[],"mappings":"AACA,OAAO,EACL,MAAM,EAsBP,MAAM,uBAAuB,CAAC;AA0S/B,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,GAAG,CAAC;IAAC,cAAc,CAAC,EAAE,GAAG,CAAC;IAAC,UAAU,CAAC,EAAE,GAAG,EAAE,CAAA;CAAE,CAAC,GAAG,SAAS,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkCnL;AA07BD,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAwN9E"}
|
package/dist/ir-generator.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
import { IRRoot, ComponentIR, StaticNode, TextNode, DynamicBinding, OpaqueDynamicRegion, MapRegion, WhileLoop, SwitchBlock, TryCatch, RuntimeStatement, ForLoop, TrackDecl, ComponentRef, ComponentCall, ServerBlock, ClientBlock, HeadBlock, Expression, SlotNode, } from '@vesk/compiler/src/ir';
|
|
1
|
+
import { IRRoot, ComponentIR, StaticNode, TextNode, DynamicBinding, OpaqueDynamicRegion, MapRegion, WhileLoop, SwitchBlock, TryCatch, RuntimeStatement, ForLoop, TrackDecl, ComponentRef, ComponentCall, ServerBlock, ClientBlock, HeadBlock, Expression, SlotNode, PropSlot, PropSlotRender, } from '@vesk/compiler/src/ir';
|
|
2
2
|
import { VeskError, codeFrame } from '@vesk/compiler/src/errors';
|
|
3
3
|
import { createBaseParser } from '@vesk/compiler/src/parser';
|
|
4
4
|
import { skipWhitespace, findBalancedEnd, splitTopLevel, startsWithIdentifier, stripDeclKeyword, isWhitespaceChar, collapseNewlineWhitespace } from '@vesk/compiler/src/scan';
|
|
5
5
|
import { stripCodeTypes } from '@vesk/compiler/src/strip-ts';
|
|
6
6
|
import { stripTypeImport } from '@vesk/compiler/src/vsk-imports';
|
|
7
7
|
import { importBindingPairs } from '@vesk/compiler/src/module-imports';
|
|
8
|
-
import {
|
|
8
|
+
import { extractImportNames, importModuleTarget } from '@vesk/compiler/src/tokens';
|
|
9
9
|
let __vskAnnotations = [];
|
|
10
|
+
/**
|
|
11
|
+
* Prop names of the component currently being processed that are declared as
|
|
12
|
+
* renderable content (`*: Component`) and are therefore threaded through the
|
|
13
|
+
* slot channel. A content read `{props.<name>}` of such a prop compiles to
|
|
14
|
+
* {@link PropSlotRender} instead of a value binding, so the child can render a
|
|
15
|
+
* slot handed to it by a caller that wrote `name={<div/>}`.
|
|
16
|
+
*/
|
|
17
|
+
let __slotProps = null;
|
|
10
18
|
function parseExprNode(text) {
|
|
11
19
|
try {
|
|
12
20
|
const ParserClass = createBaseParser();
|
|
@@ -39,6 +47,34 @@ function getForClauseAnnotation(forStart) {
|
|
|
39
47
|
function getSource(source, node) {
|
|
40
48
|
return source.slice(node.start, node.end);
|
|
41
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Returns the source text for a loop/map binding pattern, preserving
|
|
52
|
+
* destructuring (`[a, b]`, `{x, y}`, defaults, rest) while stripping a
|
|
53
|
+
* top-level TS type annotation (`[a, b]: T` → `[a, b]`). Plain identifiers
|
|
54
|
+
* return their name directly so `const x: T` still yields `x`.
|
|
55
|
+
*/
|
|
56
|
+
function patternSource(source, node) {
|
|
57
|
+
if (!node)
|
|
58
|
+
return 'item';
|
|
59
|
+
if (node.type === 'Identifier')
|
|
60
|
+
return node.name ?? 'item';
|
|
61
|
+
const end = node.typeAnnotation ? node.typeAnnotation.start : node.end;
|
|
62
|
+
const text = source.slice(node.start, end).trim();
|
|
63
|
+
return text || 'item';
|
|
64
|
+
}
|
|
65
|
+
function mapParamSource(source, param) {
|
|
66
|
+
if (!param)
|
|
67
|
+
return 'item';
|
|
68
|
+
if (param.type === 'Identifier')
|
|
69
|
+
return param.name ?? 'item';
|
|
70
|
+
const end = param.typeAnnotation ? param.typeAnnotation.start : param.end;
|
|
71
|
+
if (typeof param.start === 'number' && typeof end === 'number') {
|
|
72
|
+
const text = source.slice(param.start, end).trim();
|
|
73
|
+
if (text)
|
|
74
|
+
return text;
|
|
75
|
+
}
|
|
76
|
+
return 'item';
|
|
77
|
+
}
|
|
42
78
|
function collectComponentCalls(nodes, out) {
|
|
43
79
|
for (const n of nodes) {
|
|
44
80
|
if (n instanceof ComponentCall) {
|
|
@@ -48,6 +84,9 @@ function collectComponentCalls(nodes, out) {
|
|
|
48
84
|
else if (n instanceof StaticNode || n instanceof ServerBlock || n instanceof ClientBlock || n instanceof HeadBlock) {
|
|
49
85
|
collectComponentCalls(n.children, out);
|
|
50
86
|
}
|
|
87
|
+
else if (n instanceof PropSlot) {
|
|
88
|
+
collectComponentCalls(n.body, out);
|
|
89
|
+
}
|
|
51
90
|
else if (n instanceof MapRegion) {
|
|
52
91
|
collectComponentCalls(n.bodyTemplate, out);
|
|
53
92
|
collectComponentCalls(n.alternateNodes, out);
|
|
@@ -129,6 +168,92 @@ function estreeCallsFetch(ast) {
|
|
|
129
168
|
}
|
|
130
169
|
return false;
|
|
131
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* The simple identifier a member chain *ends* in (`a.b.c` → `c`), or null when
|
|
173
|
+
* the object doesn't end in a bare name token — mirrors the tokenizer's "name
|
|
174
|
+
* two tokens before the call paren" rule so `a.b(` and `a?.b(` surface `a`,
|
|
175
|
+
* `a.b.c(` surfaces `b`, while `this.b(`, `a[0].b(` and `a(x).b(` surface
|
|
176
|
+
* nothing.
|
|
177
|
+
*/
|
|
178
|
+
function memberReceiverName(obj) {
|
|
179
|
+
if (!obj || typeof obj !== 'object')
|
|
180
|
+
return null;
|
|
181
|
+
const rec = obj;
|
|
182
|
+
if (rec.type === 'Identifier' && typeof rec.name === 'string')
|
|
183
|
+
return rec.name;
|
|
184
|
+
const prop = rec.type === 'MemberExpression' && !rec.computed ? rec.property : null;
|
|
185
|
+
if (prop && typeof prop === 'object' && prop.type === 'Identifier') {
|
|
186
|
+
const propName = prop.name;
|
|
187
|
+
if (typeof propName === 'string')
|
|
188
|
+
return propName;
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Single-pass ESTree walk collecting every simple call target in `ast` —
|
|
194
|
+
* identifiers invoked directly (`fn(` / `fn<T>(`, constructor calls included)
|
|
195
|
+
* and the receiver name of a bare member call (`a.b(` → `a`, `a.b?.c(` → `b`)
|
|
196
|
+
* — plus every JSX element tag name. Mirrors the semantics of
|
|
197
|
+
* `collectCalledIdentifiers` over the equivalent text, so one walk of the
|
|
198
|
+
* already-parsed tree replaces re-tokenizing each IR raw fragment.
|
|
199
|
+
*/
|
|
200
|
+
function collectCallAndJsxTargets(ast) {
|
|
201
|
+
const targets = new Set();
|
|
202
|
+
const stack = [ast];
|
|
203
|
+
while (stack.length > 0) {
|
|
204
|
+
const candidate = stack.pop();
|
|
205
|
+
if (!candidate || typeof candidate !== 'object')
|
|
206
|
+
continue;
|
|
207
|
+
const node = candidate;
|
|
208
|
+
if (typeof node.type === 'string') {
|
|
209
|
+
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
|
210
|
+
const callee = node.callee;
|
|
211
|
+
if (callee && typeof callee === 'object') {
|
|
212
|
+
const c = callee;
|
|
213
|
+
if (c.type === 'Identifier' && typeof c.name === 'string' && node.optional !== true) {
|
|
214
|
+
targets.add(c.name);
|
|
215
|
+
}
|
|
216
|
+
else if (c.type === 'MemberExpression' && !c.computed && node.optional !== true) {
|
|
217
|
+
const receiverName = memberReceiverName(c.object);
|
|
218
|
+
if (receiverName)
|
|
219
|
+
targets.add(receiverName);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else if (node.type === 'JSXElement') {
|
|
224
|
+
const opening = node.openingElement;
|
|
225
|
+
const tag = opening && typeof opening === 'object' ? opening.name : null;
|
|
226
|
+
if (tag && typeof tag === 'object') {
|
|
227
|
+
const t = tag;
|
|
228
|
+
if (t.type === 'JSXIdentifier' && typeof t.name === 'string') {
|
|
229
|
+
targets.add(t.name);
|
|
230
|
+
}
|
|
231
|
+
else if (t.type === 'JSXMemberExpression') {
|
|
232
|
+
const prop = t.property;
|
|
233
|
+
if (prop && typeof prop === 'object' && prop.type === 'JSXIdentifier') {
|
|
234
|
+
const propName = prop.name;
|
|
235
|
+
if (typeof propName === 'string')
|
|
236
|
+
targets.add(propName);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
for (const key of Object.keys(node)) {
|
|
242
|
+
if (key === 'parent' || key === 'loc' || key === 'start' || key === 'end' || key === 'range')
|
|
243
|
+
continue;
|
|
244
|
+
const value = node[key];
|
|
245
|
+
if (Array.isArray(value)) {
|
|
246
|
+
for (const item of value)
|
|
247
|
+
stack.push(item);
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
stack.push(value);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return targets;
|
|
256
|
+
}
|
|
132
257
|
function componentUsesFetch(nodes) {
|
|
133
258
|
for (const node of nodes) {
|
|
134
259
|
if (node instanceof ServerBlock || node instanceof ClientBlock) {
|
|
@@ -281,10 +406,99 @@ function processAttribute(source, attr) {
|
|
|
281
406
|
const expr = attr.value.expression;
|
|
282
407
|
if (expr.type === 'Literal')
|
|
283
408
|
return { name, value: String(expr.value) };
|
|
409
|
+
// A JSX element can never be a DOM attribute value — elements belong to
|
|
410
|
+
// content/slots, not to an attribute slot on an HTML tag. Surfacing a real
|
|
411
|
+
// error here beats a raw `(<div/>)` leaking into compiled JS (the previous
|
|
412
|
+
// behavior crashed with a runtime SyntaxError deep inside codegen).
|
|
413
|
+
if (containsJSX(expr)) {
|
|
414
|
+
throw VeskError.attrJsxElement({ attr: name });
|
|
415
|
+
}
|
|
284
416
|
return { name, value: toExpression(source, expr) };
|
|
285
417
|
}
|
|
286
418
|
return { name, value: '' };
|
|
287
419
|
}
|
|
420
|
+
/**
|
|
421
|
+
* True when `node` (an expression AST) contains a JSX element or fragment
|
|
422
|
+
* anywhere in its subtree — used to distinguish slot-bearing prop values
|
|
423
|
+
* (`trigger={<Button/>}`, `trigger={open ? <A/> : <B/>}`) from plain ones
|
|
424
|
+
* (`align={'end'}`, `count={n + 1}`).
|
|
425
|
+
*/
|
|
426
|
+
function containsJSX(node) {
|
|
427
|
+
if (!node || typeof node !== 'object')
|
|
428
|
+
return false;
|
|
429
|
+
if (node.type === 'JSXElement' || node.type === 'JSXFragment')
|
|
430
|
+
return true;
|
|
431
|
+
if (node.type === 'JSXExpressionContainer')
|
|
432
|
+
return containsJSX(node.expression);
|
|
433
|
+
// TS-only position holders (type annotations, parameter names) never contain
|
|
434
|
+
// JSX — skipping them avoids walking TS structure.
|
|
435
|
+
for (const key of [
|
|
436
|
+
'expression', 'object', 'property', 'callee', 'arguments', 'test',
|
|
437
|
+
'consequent', 'alternate', 'left', 'right', 'init', 'elements', 'params', 'body',
|
|
438
|
+
]) {
|
|
439
|
+
const child = node[key];
|
|
440
|
+
if (Array.isArray(child)) {
|
|
441
|
+
for (const c of child)
|
|
442
|
+
if (containsJSX(c))
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
else if (child && containsJSX(child)) {
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Returns the prop name when `expr` is a bare `props.<name>` read, else null.
|
|
453
|
+
*/
|
|
454
|
+
function slotReadPropName(expr) {
|
|
455
|
+
if (expr.type === 'MemberExpression' && !expr.computed &&
|
|
456
|
+
expr.object.type === 'Identifier' && expr.object.name === 'props' &&
|
|
457
|
+
expr.property.type === 'Identifier') {
|
|
458
|
+
return expr.property.name;
|
|
459
|
+
}
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Parses an inline component props type (`{ trigger: Component; ... }`) and
|
|
464
|
+
* returns the set of prop names declared as renderable content — any member
|
|
465
|
+
* whose type text mentions `Component`. Those props may be handed a JSX
|
|
466
|
+
* element at the call site and are read back with `{props.<name>}` as slots.
|
|
467
|
+
*/
|
|
468
|
+
function slotPropNamesFromType(propsType) {
|
|
469
|
+
const out = new Set();
|
|
470
|
+
if (!propsType)
|
|
471
|
+
return out;
|
|
472
|
+
const trimmed = propsType.trim();
|
|
473
|
+
if (trimmed.startsWith('{')) {
|
|
474
|
+
const inner = findBalancedEnd(trimmed, 0) === trimmed.length - 1
|
|
475
|
+
? trimmed.slice(1, -1)
|
|
476
|
+
: trimmed;
|
|
477
|
+
for (const part of splitTopLevel(inner, ';')) {
|
|
478
|
+
const colon = findTopLevelColon(part);
|
|
479
|
+
if (colon === -1)
|
|
480
|
+
continue;
|
|
481
|
+
const namePart = part.slice(0, colon).trim().replace(/^\$/, '').replace(/\?$/, '').trim();
|
|
482
|
+
const typePart = part.slice(colon + 1).trim();
|
|
483
|
+
if (namePart && typePart.includes('Component'))
|
|
484
|
+
out.add(namePart);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return out;
|
|
488
|
+
}
|
|
489
|
+
function findTopLevelColon(text) {
|
|
490
|
+
let depth = 0;
|
|
491
|
+
for (let i = 0; i < text.length; i++) {
|
|
492
|
+
const ch = text[i];
|
|
493
|
+
if (ch === '(' || ch === '[' || ch === '{' || ch === '<')
|
|
494
|
+
depth++;
|
|
495
|
+
else if (ch === ')' || ch === ']' || ch === '}')
|
|
496
|
+
depth = Math.max(0, depth - 1);
|
|
497
|
+
else if (ch === ':' && depth === 0)
|
|
498
|
+
return i;
|
|
499
|
+
}
|
|
500
|
+
return -1;
|
|
501
|
+
}
|
|
288
502
|
/**
|
|
289
503
|
* Recognizes a statement-mode `if (cond)` opening among JSX children.
|
|
290
504
|
* Returns the condition source, or null when `text` does not open an if
|
|
@@ -433,7 +647,7 @@ function processJSXChildren(source, children) {
|
|
|
433
647
|
const exprText = ofParts[1].trim();
|
|
434
648
|
const arrExpr = new Expression(exprText, [], parseExprNode(exprText), null);
|
|
435
649
|
const rawFor = hasForPrefix(child.value);
|
|
436
|
-
const ann = rawFor ? getForClauseAnnotation(child.start + rawFor) : null;
|
|
650
|
+
const ann = rawFor !== -1 ? getForClauseAnnotation(child.start + rawFor) : null;
|
|
437
651
|
const keyExpr = ann?.keyRange ? new Expression(source.slice(ann.keyRange[0], ann.keyRange[1])) : null;
|
|
438
652
|
const indexVar = ann?.indexName ?? null;
|
|
439
653
|
let alternate = [];
|
|
@@ -470,7 +684,7 @@ function processJSXChildren(source, children) {
|
|
|
470
684
|
}
|
|
471
685
|
if (isMapCall(expr)) {
|
|
472
686
|
const arrowFn = expr.arguments[0];
|
|
473
|
-
const itemVar = arrowFn.params[0]
|
|
687
|
+
const itemVar = mapParamSource(source, arrowFn.params[0]);
|
|
474
688
|
const indexVar = arrowFn.params[1]?.name ?? null;
|
|
475
689
|
const bodyNodes = processJSXCallbackBody(source, arrowFn.body);
|
|
476
690
|
const arrayExpr = toExpression(source, expr.callee.object);
|
|
@@ -502,6 +716,16 @@ function processJSXChildren(source, children) {
|
|
|
502
716
|
i++;
|
|
503
717
|
continue;
|
|
504
718
|
}
|
|
719
|
+
// A content read of a component-declared slot prop (`{props.trigger}`)
|
|
720
|
+
// must render the threaded content — not stringify a fragment.
|
|
721
|
+
{
|
|
722
|
+
const slotName = slotReadPropName(expr);
|
|
723
|
+
if (slotName !== null && __slotProps !== null && __slotProps.has(slotName)) {
|
|
724
|
+
result.push(new PropSlotRender(slotName));
|
|
725
|
+
i++;
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
505
729
|
result.push(new DynamicBinding(toExpression(source, expr)));
|
|
506
730
|
i++;
|
|
507
731
|
}
|
|
@@ -557,7 +781,7 @@ function exprToIR(source, expr) {
|
|
|
557
781
|
}
|
|
558
782
|
if (isMapCall(expr)) {
|
|
559
783
|
const arrowFn = expr.arguments[0];
|
|
560
|
-
const itemVar = arrowFn.params[0]
|
|
784
|
+
const itemVar = mapParamSource(source, arrowFn.params[0]);
|
|
561
785
|
const indexVar = arrowFn.params[1]?.name ?? null;
|
|
562
786
|
const bodyNodes = processJSXCallbackBody(source, arrowFn.body);
|
|
563
787
|
const arrayExpr = toExpression(source, expr.callee.object);
|
|
@@ -580,6 +804,19 @@ function exprToIR(source, expr) {
|
|
|
580
804
|
const consequent = exprToIR(source, expr.right);
|
|
581
805
|
return [new OpaqueDynamicRegion(condExpr, consequent)];
|
|
582
806
|
}
|
|
807
|
+
// `props.children` (or bare `children`) inside an expression branch must
|
|
808
|
+
// insert the slot nodes — not degrade to a text binding that stringifies
|
|
809
|
+
// the fragment (`String(props.children)` → "[object DocumentFragment]").
|
|
810
|
+
if ((expr.type === 'MemberExpression' && !expr.computed &&
|
|
811
|
+
expr.object.type === 'Identifier' && expr.object.name === 'props' &&
|
|
812
|
+
expr.property.type === 'Identifier' && expr.property.name === 'children')
|
|
813
|
+
|| (expr.type === 'Identifier' && expr.name === 'children')) {
|
|
814
|
+
return [new SlotNode()];
|
|
815
|
+
}
|
|
816
|
+
const slotName = slotReadPropName(expr);
|
|
817
|
+
if (slotName !== null && __slotProps !== null && __slotProps.has(slotName)) {
|
|
818
|
+
return [new PropSlotRender(slotName)];
|
|
819
|
+
}
|
|
583
820
|
return [new DynamicBinding(toExpression(source, expr))];
|
|
584
821
|
}
|
|
585
822
|
/**
|
|
@@ -633,18 +870,18 @@ function processJSXElement(source, element) {
|
|
|
633
870
|
// actual in-scope value instead of `document.createElement("it.icon")` or a
|
|
634
871
|
// registry lookup by dotted string.
|
|
635
872
|
if (nameNode && nameNode.type === 'JSXMemberExpression') {
|
|
636
|
-
const { props, spreadProps } = extractProps(source, element);
|
|
873
|
+
const { props, spreadProps, slots } = extractProps(source, element);
|
|
637
874
|
const children = selfClosing ? [] : processJSXChildren(source, element.children || []);
|
|
638
|
-
return [new ComponentCall(tagName, props, children, spreadProps, element.start, getSource(source, nameNode))];
|
|
875
|
+
return [new ComponentCall(tagName, props, [...children, ...slots.map((s) => new PropSlot(s.name, s.nodes))], spreadProps, element.start, getSource(source, nameNode))];
|
|
639
876
|
}
|
|
640
877
|
if (!isHTMLTag(tagName) && selfClosing) {
|
|
641
|
-
const { props, spreadProps } = extractProps(source, element);
|
|
642
|
-
return [new ComponentCall(tagName, props,
|
|
878
|
+
const { props, spreadProps, slots } = extractProps(source, element);
|
|
879
|
+
return [new ComponentCall(tagName, props, slots.map((s) => new PropSlot(s.name, s.nodes)), spreadProps, element.start)];
|
|
643
880
|
}
|
|
644
881
|
if (!isHTMLTag(tagName)) {
|
|
645
|
-
const { props, spreadProps } = extractProps(source, element);
|
|
882
|
+
const { props, spreadProps, slots } = extractProps(source, element);
|
|
646
883
|
const children = processJSXChildren(source, element.children || []);
|
|
647
|
-
return [new ComponentCall(tagName, props, children, spreadProps, element.start)];
|
|
884
|
+
return [new ComponentCall(tagName, props, [...children, ...slots.map((s) => new PropSlot(s.name, s.nodes))], spreadProps, element.start)];
|
|
648
885
|
}
|
|
649
886
|
const attributes = element.openingElement.attributes
|
|
650
887
|
.filter((attr) => attr.type !== 'JSXSpreadAttribute')
|
|
@@ -677,22 +914,34 @@ function processJSXElement(source, element) {
|
|
|
677
914
|
function extractProps(source, element) {
|
|
678
915
|
const props = [];
|
|
679
916
|
const spreadProps = [];
|
|
917
|
+
const slots = [];
|
|
680
918
|
for (const attr of element.openingElement.attributes) {
|
|
681
919
|
if (attr.type === 'JSXSpreadAttribute') {
|
|
682
920
|
spreadProps.push(toExpression(source, attr.argument));
|
|
683
921
|
}
|
|
684
922
|
else {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
923
|
+
const name = attr.name.type === 'JSXIdentifier' ? attr.name.name : getSource(source, attr.name);
|
|
924
|
+
let value;
|
|
925
|
+
if (attr.value === null) {
|
|
926
|
+
value = new Expression('true');
|
|
927
|
+
}
|
|
928
|
+
else if (attr.value.type === 'JSXExpressionContainer') {
|
|
929
|
+
const expr = attr.value.expression;
|
|
930
|
+
// JSX elements as prop values are named content slots (`trigger={<Button/>}`),
|
|
931
|
+
// not scalar values — the call site hoists them into the slot channel.
|
|
932
|
+
if (containsJSX(expr)) {
|
|
933
|
+
slots.push({ name, nodes: exprToIR(source, expr) });
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
value = toExpression(source, expr);
|
|
937
|
+
}
|
|
938
|
+
else {
|
|
939
|
+
value = new Expression(JSON.stringify(attr.value.value));
|
|
940
|
+
}
|
|
941
|
+
props.push({ name, value });
|
|
693
942
|
}
|
|
694
943
|
}
|
|
695
|
-
return { props, spreadProps };
|
|
944
|
+
return { props, spreadProps, slots };
|
|
696
945
|
}
|
|
697
946
|
function buildGuardChain(source, guardClauses, mainReturn) {
|
|
698
947
|
const mainBody = [];
|
|
@@ -842,9 +1091,16 @@ function processIfStatement(source, stmt) {
|
|
|
842
1091
|
function processForStatement(source, stmt, alternate = []) {
|
|
843
1092
|
if (stmt.type === 'ForOfStatement') {
|
|
844
1093
|
const left = stmt.left;
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
1094
|
+
let itemVar = 'item';
|
|
1095
|
+
if (left.type === 'VariableDeclaration') {
|
|
1096
|
+
itemVar = patternSource(source, left.declarations[0]?.id);
|
|
1097
|
+
}
|
|
1098
|
+
else if (left.type === 'Identifier') {
|
|
1099
|
+
itemVar = left.name ?? 'item';
|
|
1100
|
+
}
|
|
1101
|
+
else if (typeof left.start === 'number' && typeof left.end === 'number') {
|
|
1102
|
+
itemVar = source.slice(left.start, left.end).trim() || 'item';
|
|
1103
|
+
}
|
|
848
1104
|
const arrayExpr = toExpression(source, stmt.right);
|
|
849
1105
|
const bodyTemplate = processBlockBody(source, stmt.body);
|
|
850
1106
|
const ann = getForClauseAnnotation(stmt.start);
|
|
@@ -1112,6 +1368,7 @@ function idxOfQuote(s, from) {
|
|
|
1112
1368
|
}
|
|
1113
1369
|
export function generateIR(ast, source, filename) {
|
|
1114
1370
|
__vskAnnotations = ast.__vskAnnotations ?? [];
|
|
1371
|
+
__slotProps = null;
|
|
1115
1372
|
const file = filename || '';
|
|
1116
1373
|
const components = [];
|
|
1117
1374
|
const imports = [];
|
|
@@ -1206,6 +1463,11 @@ export function generateIR(ast, source, filename) {
|
|
|
1206
1463
|
const propsType = getPropsType(inner.params, source);
|
|
1207
1464
|
const bodyStmts = inner.body.body;
|
|
1208
1465
|
const isClientComp = !!inner.client;
|
|
1466
|
+
// Slot props are declared at the component boundary (`trigger: Component`),
|
|
1467
|
+
// so content reads of `props.<name>` compile to slot renders instead of
|
|
1468
|
+
// value bindings for THIS component only.
|
|
1469
|
+
const prevSlotProps = __slotProps;
|
|
1470
|
+
__slotProps = slotPropNamesFromType(propsType);
|
|
1209
1471
|
if (isStatementMode(bodyStmts)) {
|
|
1210
1472
|
const raw = processStatementModeBody(source, bodyStmts, file);
|
|
1211
1473
|
const { body, css } = extractStyle(raw);
|
|
@@ -1213,6 +1475,7 @@ export function generateIR(ast, source, filename) {
|
|
|
1213
1475
|
const comp = new ComponentIR(name, paramNames, body, { mode: 'statement', exported, defaultExport, isClient: inner.client, isAsync: inner.async, ssrAwait: componentUsesFetch(body), propsType });
|
|
1214
1476
|
comp.style = css;
|
|
1215
1477
|
components.push(comp);
|
|
1478
|
+
__slotProps = prevSlotProps;
|
|
1216
1479
|
}
|
|
1217
1480
|
else {
|
|
1218
1481
|
const guardClauses = [];
|
|
@@ -1261,6 +1524,7 @@ export function generateIR(ast, source, filename) {
|
|
|
1261
1524
|
const comp = new ComponentIR(name, paramNames, body, { exported, defaultExport, isClient: inner.client, isAsync: inner.async, ssrAwait: componentUsesFetch(body), propsType });
|
|
1262
1525
|
comp.style = css;
|
|
1263
1526
|
components.push(comp);
|
|
1527
|
+
__slotProps = prevSlotProps;
|
|
1264
1528
|
}
|
|
1265
1529
|
}
|
|
1266
1530
|
const asyncNames = new Set(components.filter((c) => c.isAsync || c.ssrAwait).map((c) => c.name));
|
|
@@ -1292,83 +1556,17 @@ export function generateIR(ast, source, filename) {
|
|
|
1292
1556
|
'createResource', 'getAction', 'validateActionInput', 'issuesToFieldMap', 'isFormAction',
|
|
1293
1557
|
'Show', 'For', 'Switch', 'Match',
|
|
1294
1558
|
];
|
|
1559
|
+
// One walk of the already-parsed tree finds every auto-importable call
|
|
1560
|
+
// target and JSX element name. Every IR raw the old per-fragment scan
|
|
1561
|
+
// re-tokenized ("addUsedFrom" over ~15 text slices) is a subtree of this
|
|
1562
|
+
// very same `ast` — nothing is lost, but the acorn tokenizer is invoked once
|
|
1563
|
+
// per file instead of once per fragment.
|
|
1564
|
+
const called = collectCallAndJsxTargets(ast);
|
|
1295
1565
|
const usedFunctions = new Set();
|
|
1296
|
-
const
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
if (called.has(fn))
|
|
1300
|
-
usedFunctions.add(fn);
|
|
1301
|
-
}
|
|
1302
|
-
};
|
|
1303
|
-
for (const code of topLevelCode)
|
|
1304
|
-
addUsedFrom(code);
|
|
1305
|
-
for (const code of [loadFn, staticProps]) {
|
|
1306
|
-
if (code)
|
|
1307
|
-
addUsedFrom(code);
|
|
1308
|
-
}
|
|
1309
|
-
function scanForAutoImport(nodes) {
|
|
1310
|
-
for (const node of nodes) {
|
|
1311
|
-
if (node instanceof RuntimeStatement && node.raw) {
|
|
1312
|
-
addUsedFrom(node.raw);
|
|
1313
|
-
}
|
|
1314
|
-
if (node instanceof TrackDecl && node.init) {
|
|
1315
|
-
addUsedFrom(node.init);
|
|
1316
|
-
}
|
|
1317
|
-
if (node instanceof ForLoop) {
|
|
1318
|
-
if (node.init)
|
|
1319
|
-
addUsedFrom(node.init);
|
|
1320
|
-
if (node.update)
|
|
1321
|
-
addUsedFrom(node.update);
|
|
1322
|
-
if (node.condition && node.condition.raw)
|
|
1323
|
-
addUsedFrom(node.condition.raw);
|
|
1324
|
-
scanForAutoImport(node.bodyTemplate);
|
|
1325
|
-
}
|
|
1326
|
-
if (node instanceof WhileLoop) {
|
|
1327
|
-
if (node.condition && node.condition.raw)
|
|
1328
|
-
addUsedFrom(node.condition.raw);
|
|
1329
|
-
scanForAutoImport(node.bodyTemplate);
|
|
1330
|
-
}
|
|
1331
|
-
if (node instanceof SwitchBlock) {
|
|
1332
|
-
if (node.discriminant && node.discriminant.raw)
|
|
1333
|
-
addUsedFrom(node.discriminant.raw);
|
|
1334
|
-
for (const c of node.cases) {
|
|
1335
|
-
if (c.test && c.test.raw)
|
|
1336
|
-
addUsedFrom(c.test.raw);
|
|
1337
|
-
scanForAutoImport(c.body);
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
if (node instanceof TryCatch) {
|
|
1341
|
-
scanForAutoImport(node.bodyTemplate);
|
|
1342
|
-
scanForAutoImport(node.catchBody);
|
|
1343
|
-
}
|
|
1344
|
-
if (node instanceof ComponentCall) {
|
|
1345
|
-
if (autoImportable.includes(node.componentName)) {
|
|
1346
|
-
usedFunctions.add(node.componentName);
|
|
1347
|
-
}
|
|
1348
|
-
for (const prop of node.props) {
|
|
1349
|
-
if (prop.value && prop.value.raw) {
|
|
1350
|
-
addUsedFrom(prop.value.raw);
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
scanForAutoImport(node.children);
|
|
1354
|
-
}
|
|
1355
|
-
if (node instanceof MapRegion) {
|
|
1356
|
-
scanForAutoImport(node.bodyTemplate);
|
|
1357
|
-
scanForAutoImport(node.alternateNodes);
|
|
1358
|
-
}
|
|
1359
|
-
if (node instanceof OpaqueDynamicRegion) {
|
|
1360
|
-
scanForAutoImport(node.consequentNodes);
|
|
1361
|
-
scanForAutoImport(node.alternateNodes);
|
|
1362
|
-
}
|
|
1363
|
-
if (node instanceof DynamicBinding && node.expression && node.expression.raw) {
|
|
1364
|
-
addUsedFrom(node.expression.raw);
|
|
1365
|
-
}
|
|
1366
|
-
if (node instanceof StaticNode || node instanceof ServerBlock || node instanceof ClientBlock || node instanceof HeadBlock) {
|
|
1367
|
-
scanForAutoImport(node.children);
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1566
|
+
for (const fn of autoImportable) {
|
|
1567
|
+
if (called.has(fn))
|
|
1568
|
+
usedFunctions.add(fn);
|
|
1370
1569
|
}
|
|
1371
|
-
scanForAutoImport(components.flatMap(c => c.body));
|
|
1372
1570
|
if (usedFunctions.size > 0) {
|
|
1373
1571
|
const existing = new Set();
|
|
1374
1572
|
for (const imp of imports) {
|
package/dist/ir.d.ts
CHANGED
|
@@ -104,6 +104,29 @@ export declare class ComponentCall {
|
|
|
104
104
|
export declare class SlotNode {
|
|
105
105
|
constructor();
|
|
106
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* A named content slot threaded to a component through a prop that was given a
|
|
109
|
+
* JSX element value at the call site (`<Popover trigger={<Button/>}>`). The
|
|
110
|
+
* prop is neither a scalar value nor JSON-able — it is renderable content, so
|
|
111
|
+
* codegen hoists the slot body and passes it under the prop name using the
|
|
112
|
+
* exact same channel as `children` (a serialized string IIFE on the server, a
|
|
113
|
+
* fresh DocumentFragment on the client). Child components draw it back with
|
|
114
|
+
* `{props.<name>}`, which compiles to a {@link PropSlotRender}.
|
|
115
|
+
*/
|
|
116
|
+
export declare class PropSlot {
|
|
117
|
+
propName: string;
|
|
118
|
+
body: IRNode[];
|
|
119
|
+
constructor(propName: string, body: IRNode[]);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* A content read of a slot prop (`{props.trigger}`). The runtime value behind
|
|
123
|
+
* it is threaded through the same channel as `children`, so emitting it on the
|
|
124
|
+
* server pushes the serialized string and on the client appends the fragment.
|
|
125
|
+
*/
|
|
126
|
+
export declare class PropSlotRender {
|
|
127
|
+
propName: string;
|
|
128
|
+
constructor(propName: string);
|
|
129
|
+
}
|
|
107
130
|
export declare class WhileLoop {
|
|
108
131
|
condition: Expression;
|
|
109
132
|
bodyTemplate: IRNode[];
|
|
@@ -163,5 +186,5 @@ export declare class HeadBlock {
|
|
|
163
186
|
children: IRNode[];
|
|
164
187
|
constructor(children: IRNode[]);
|
|
165
188
|
}
|
|
166
|
-
export type IRNode = StaticNode | TextNode | DynamicBinding | OpaqueDynamicRegion | MapRegion | WhileLoop | SwitchBlock | TryCatch | ForLoop | TrackDecl | RuntimeStatement | ComponentRef | ComponentCall | ServerBlock | ClientBlock | HeadBlock | SlotNode;
|
|
189
|
+
export type IRNode = StaticNode | TextNode | DynamicBinding | OpaqueDynamicRegion | MapRegion | WhileLoop | SwitchBlock | TryCatch | ForLoop | TrackDecl | RuntimeStatement | ComponentRef | ComponentCall | ServerBlock | ClientBlock | HeadBlock | SlotNode | PropSlot | PropSlotRender;
|
|
167
190
|
//# sourceMappingURL=ir.d.ts.map
|