@meekojs/meeko 0.0.0-stage → 0.0.2

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.
@@ -0,0 +1,404 @@
1
+ import { createHash } from "node:crypto";
2
+ import { basename } from "node:path";
3
+ import ts from "@typescript/typescript6";
4
+ import postcss from "postcss";
5
+ import selectorParser from "postcss-selector-parser";
6
+ import { extractSourceMappings, markSource } from "./compiler/mapping.js";
7
+ import { compileError, locate, parseSfc } from "./compiler/sfc.js";
8
+ import { parseTemplate } from "./compiler/template.js";
9
+ function emptyBindings() {
10
+ return { texts: [], events: [], attributes: [], components: [], regions: [] };
11
+ }
12
+ function quote(value) {
13
+ return JSON.stringify(value);
14
+ }
15
+ function validateExpression(expression, source, filename, offset) {
16
+ const prefix = "const __check = (";
17
+ const parsed = ts.createSourceFile(filename, `${prefix}${expression});`, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
18
+ const error = parsed.parseDiagnostics[0];
19
+ if (error) {
20
+ const position = Math.max(0, Math.min(expression.length, (error.start ?? prefix.length) - prefix.length));
21
+ throw compileError(source, filename, "MEEK020", ts.flattenDiagnosticMessageText(error.messageText, "\n"), offset + position);
22
+ }
23
+ }
24
+ function attributeValueOffset(node, name, context) {
25
+ const position = node.sourceCodeLocation?.attrs?.[name];
26
+ if (!position)
27
+ return context.templateOffset + (node.sourceCodeLocation?.startOffset ?? 0);
28
+ const raw = context.source.slice(context.templateOffset + position.startOffset, context.templateOffset + position.endOffset);
29
+ const equals = raw.indexOf("=");
30
+ if (equals < 0)
31
+ return context.templateOffset + position.startOffset;
32
+ const after = raw.slice(equals + 1);
33
+ const whitespace = after.length - after.trimStart().length;
34
+ const quoteStart = equals + 1 + whitespace;
35
+ const quoted = raw[quoteStart] === '"' || raw[quoteStart] === "'";
36
+ return context.templateOffset + position.startOffset + quoteStart + (quoted ? 1 : 0);
37
+ }
38
+ function rewriteLocals(expression, locals) {
39
+ if (locals.size === 0)
40
+ return expression;
41
+ const prefix = "const __value = (";
42
+ const file = ts.createSourceFile("template-expression.ts", `${prefix}${expression});`, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
43
+ const changes = [];
44
+ function isShadowed(node) {
45
+ for (let parent = node.parent; parent; parent = parent.parent) {
46
+ if ((ts.isArrowFunction(parent) || ts.isFunctionExpression(parent)) && parent.parameters.some((parameter) => ts.isIdentifier(parameter.name) && parameter.name.text === node.text))
47
+ return true;
48
+ if (ts.isSourceFile(parent))
49
+ break;
50
+ }
51
+ return false;
52
+ }
53
+ function visit(node) {
54
+ if (ts.isIdentifier(node)) {
55
+ const replacement = locals.get(node.text);
56
+ const parent = node.parent;
57
+ if (replacement && parent && !isShadowed(node)
58
+ && !(ts.isPropertyAccessExpression(parent) && parent.name === node)
59
+ && !(ts.isPropertyAssignment(parent) && parent.name === node)
60
+ && !(ts.isParameter(parent) && parent.name === node)
61
+ && !(ts.isVariableDeclaration(parent) && parent.name === node)) {
62
+ const shorthand = ts.isShorthandPropertyAssignment(parent) && parent.name === node;
63
+ changes.push({ start: node.getStart(file) - prefix.length, end: node.getEnd() - prefix.length, text: shorthand ? `${node.text}: (${replacement})` : `(${replacement})` });
64
+ }
65
+ }
66
+ ts.forEachChild(node, visit);
67
+ }
68
+ visit(file);
69
+ let result = expression;
70
+ for (const change of changes.reverse()) {
71
+ if (change.start >= 0 && change.end <= expression.length)
72
+ result = result.slice(0, change.start) + change.text + result.slice(change.end);
73
+ }
74
+ return result;
75
+ }
76
+ function textParts(value, bindings, context, offset) {
77
+ const parts = [];
78
+ const pattern = /\{([^{}]+)\}/g;
79
+ let cursor = 0;
80
+ for (const match of value.matchAll(pattern)) {
81
+ const index = match.index;
82
+ const staticPart = value.slice(cursor, index);
83
+ if (staticPart)
84
+ parts.push(`__meeko.escapeHtml(${quote(staticPart)})`);
85
+ const rawExpression = match[1] ?? "";
86
+ const expression = rawExpression.trim();
87
+ if (!expression)
88
+ throw compileError(context.source, context.filename, "MEEK009", "Empty template expression", offset + index);
89
+ validateExpression(expression, context.source, context.filename, offset + index + 1 + rawExpression.indexOf(expression));
90
+ const id = context.nextText++;
91
+ const expressionOffset = offset + index + 1 + rawExpression.indexOf(expression);
92
+ bindings.texts.push({ id, expression, offset: expressionOffset });
93
+ parts.push(`${quote(`<!--meeko:text:${id}-->`)} + __meeko.escapeHtml(${markSource(expression, expressionOffset)}) + ${quote(`<!--/meeko:text:${id}-->`)}`);
94
+ cursor = index + match[0].length;
95
+ }
96
+ const tail = value.slice(cursor);
97
+ if (tail.includes("{"))
98
+ throw compileError(context.source, context.filename, "MEEK010", "Unclosed template expression", offset + cursor + tail.indexOf("{"));
99
+ if (tail)
100
+ parts.push(`__meeko.escapeHtml(${quote(tail)})`);
101
+ return parts;
102
+ }
103
+ function renderNode(node, bindings, context) {
104
+ if (node.nodeName === "#text") {
105
+ return textParts(node.value ?? "", bindings, context, context.templateOffset + (node.sourceCodeLocation?.startOffset ?? 0)).join(" + ") || quote("");
106
+ }
107
+ if (node.nodeName === "#comment")
108
+ return quote(`<!--${node.value ?? ""}-->`);
109
+ if (!node.tagName)
110
+ return (node.childNodes ?? []).map((child) => renderNode(child, bindings, context)).join(" + ") || quote("");
111
+ const attrsList = node.attrs ?? [];
112
+ const position = context.templateOffset + (node.sourceCodeLocation?.startOffset ?? 0);
113
+ const condition = attrsList.find((attribute) => attribute.name === "if:when");
114
+ const list = attrsList.find((attribute) => attribute.name === "each:of");
115
+ if (condition && list)
116
+ throw compileError(context.source, context.filename, "MEEK021", "A node cannot have both if:when and each:of", position);
117
+ if (condition || list) {
118
+ const inner = emptyBindings();
119
+ const excluded = new Set(condition ? ["if:when"] : ["each:of", "each:as", "each:key", "each:index"]);
120
+ const clone = { ...node, attrs: attrsList.filter((attribute) => !excluded.has(attribute.name)) };
121
+ const html = renderNodeContent(clone, inner, context);
122
+ const id = context.nextRegion++;
123
+ if (condition) {
124
+ const offset = attributeValueOffset(node, "if:when", context);
125
+ validateExpression(condition.value, context.source, context.filename, offset);
126
+ bindings.regions.push({ kind: "condition", id, expression: condition.value, offset, render: html, bindings: inner });
127
+ return `__meeko.renderIf(${id}, ${markSource(condition.value, offset)}, () => ${html})`;
128
+ }
129
+ const itemName = attrsList.find((attribute) => attribute.name === "each:as")?.value;
130
+ const key = attrsList.find((attribute) => attribute.name === "each:key")?.value;
131
+ const indexName = attrsList.find((attribute) => attribute.name === "each:index")?.value ?? "index";
132
+ if (!itemName || !/^[a-zA-Z_$][\w$]*$/.test(itemName) || itemName.startsWith("__") || !/^[a-zA-Z_$][\w$]*$/.test(indexName) || indexName.startsWith("__") || itemName === indexName) {
133
+ throw compileError(context.source, context.filename, "MEEK022", "A list needs valid, distinct each:as and each:index identifiers", position);
134
+ }
135
+ if (!key)
136
+ throw compileError(context.source, context.filename, "MEEK023", "A list requires each:key", position);
137
+ const offset = attributeValueOffset(node, "each:of", context);
138
+ const keyOffset = attributeValueOffset(node, "each:key", context);
139
+ validateExpression(list.value, context.source, context.filename, offset);
140
+ validateExpression(key, context.source, context.filename, keyOffset);
141
+ bindings.regions.push({ kind: "list", id, expression: list.value, offset, key, keyOffset, itemName, indexName, render: html, bindings: inner });
142
+ return `__meeko.renderFor(${id}, ${markSource(list.value, offset)}, (${itemName}, ${indexName}) => ${markSource(key, keyOffset)}, (${itemName}, ${indexName}) => ${html})`;
143
+ }
144
+ if (attrsList.some((attribute) => attribute.name.startsWith("each:") || attribute.name.startsWith("if:"))) {
145
+ throw compileError(context.source, context.filename, "MEEK024", "Incomplete template directive", position);
146
+ }
147
+ return renderNodeContent(node, bindings, context);
148
+ }
149
+ function renderNodeContent(node, bindings, context) {
150
+ if (node.tagName === "template") {
151
+ return (node.content?.childNodes ?? []).map((child) => renderNode(child, bindings, context)).join(" + ") || quote("");
152
+ }
153
+ if (node.tagName === "component") {
154
+ const position = node.start;
155
+ const referenceAttribute = node.attrs?.find((attribute) => attribute.name === "is");
156
+ const reference = referenceAttribute?.value ?? "";
157
+ if (!/^[a-zA-Z_$][\w$]*$/.test(reference) || reference.startsWith("__")) {
158
+ throw compileError(context.source, context.filename, "MEEK030", "A component requires is=\"ImportedComponent\"", position);
159
+ }
160
+ if ((node.childNodes ?? []).some((child) => child.nodeName !== "#text" || Boolean(child.value?.trim()))) {
161
+ throw compileError(context.source, context.filename, "MEEK031", "Component children are not supported; close the tag without content", position);
162
+ }
163
+ const props = [];
164
+ for (const attribute of node.attrs ?? []) {
165
+ if (attribute.name === "is")
166
+ continue;
167
+ const rawName = /^[^\s=]+/.exec(context.source.slice(attribute.start, attribute.end))?.[0] ?? attribute.name;
168
+ if (!rawName.startsWith("bind:") || !/^[a-zA-Z_$][\w$]*$/.test(rawName.slice(5))) {
169
+ throw compileError(context.source, context.filename, "MEEK032", "Component props must use bind:prop=\"expression\"", attribute.start);
170
+ }
171
+ const name = rawName.slice(5);
172
+ const offset = attributeValueOffset(node, attribute.name, context);
173
+ validateExpression(attribute.value, context.source, context.filename, offset);
174
+ props.push({ name, nameOffset: attribute.start + 5, expression: attribute.value, offset });
175
+ }
176
+ const id = context.nextRegion++;
177
+ const referenceOffset = attributeValueOffset(node, "is", context);
178
+ bindings.components.push({ id, reference, referenceOffset, props, offset: position });
179
+ const mappedProps = props.map((prop) => `${markSource(quote(prop.name), prop.nameOffset, prop.name.length)}: ${markSource(prop.expression, prop.offset)}`);
180
+ const object = markSource(`{ ${mappedProps.join(", ")} }`, position, node.end - position);
181
+ return `__meeko.renderComponent(${id}, ${markSource(reference, referenceOffset)}, ${object}, __meeko_transfer)`;
182
+ }
183
+ const tag = node.tagName;
184
+ const attrs = [];
185
+ for (const attr of node.attrs ?? []) {
186
+ if (attr.name.startsWith("on:")) {
187
+ const name = attr.name.slice(3);
188
+ if (!/^[a-z]+$/.test(name))
189
+ throw compileError(context.source, context.filename, "MEEK011", `Invalid event name: ${name}`, context.templateOffset + (node.sourceCodeLocation?.startOffset ?? 0));
190
+ const id = context.nextEvent++;
191
+ const offset = attributeValueOffset(node, attr.name, context);
192
+ validateExpression(attr.value, context.source, context.filename, offset);
193
+ bindings.events.push({ id, name, expression: attr.value, offset });
194
+ attrs.push({ kind: "static", value: ` data-meeko-e-${id}=""` });
195
+ }
196
+ else if (attr.name.startsWith("bind:")) {
197
+ const name = attr.name.slice(5);
198
+ if (!/^[a-z][a-z0-9:.-]*$/.test(name) || name.startsWith("on")) {
199
+ throw compileError(context.source, context.filename, "MEEK012", `Invalid dynamic attribute: ${name}`, context.templateOffset + (node.sourceCodeLocation?.startOffset ?? 0));
200
+ }
201
+ const offset = attributeValueOffset(node, attr.name, context);
202
+ validateExpression(attr.value, context.source, context.filename, offset);
203
+ const id = context.nextAttribute++;
204
+ bindings.attributes.push({ id, name, expression: attr.value, offset });
205
+ attrs.push({ kind: "static", value: ` data-meeko-a-${id}=""` });
206
+ attrs.push({ kind: "dynamic", expression: `__meeko.renderAttribute(${quote(name)}, ${markSource(attr.value, offset)})` });
207
+ }
208
+ else if (attr.name.startsWith(":")) {
209
+ throw compileError(context.source, context.filename, "MEEK012", `Unsupported template directive: ${attr.name}`, context.templateOffset + (node.sourceCodeLocation?.startOffset ?? 0));
210
+ }
211
+ else {
212
+ attrs.push({ kind: "static", value: ` ${attr.name}="${escapeAttribute(attr.value)}"` });
213
+ }
214
+ }
215
+ if (context.scopeAttribute)
216
+ attrs.push({ kind: "static", value: ` ${context.scopeAttribute}=""` });
217
+ const openingParts = [quote(`<${tag}`)];
218
+ for (const attr of attrs) {
219
+ if (attr.kind === "dynamic")
220
+ openingParts.push(attr.expression);
221
+ else
222
+ openingParts.push(quote(attr.value));
223
+ }
224
+ openingParts.push(quote(">"));
225
+ const opening = openingParts.join(" + ");
226
+ const voidElements = new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"]);
227
+ if (voidElements.has(tag))
228
+ return opening;
229
+ const children = (node.childNodes ?? []).map((child) => renderNode(child, bindings, context));
230
+ return [opening, ...children, quote(`</${tag}>`)].join(" + ");
231
+ }
232
+ function escapeAttribute(value) {
233
+ return value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("<", "&lt;");
234
+ }
235
+ function scopeCss(css, scopeAttribute) {
236
+ const root = postcss.parse(css);
237
+ root.walkRules((rule) => {
238
+ let parent = rule.parent;
239
+ while (parent) {
240
+ if (parent.type === "atrule" && /keyframes$/i.test(parent.name))
241
+ return;
242
+ parent = parent.parent;
243
+ }
244
+ if (rule.selector.includes(":global"))
245
+ throw rule.error(":global is not supported in scoped styles yet", { index: rule.selector.indexOf(":global") });
246
+ rule.selector = selectorParser((selectors) => {
247
+ selectors.each((selector) => {
248
+ const pseudoElement = selector.nodes.find((node) => node.type === "pseudo" && node.value.startsWith("::"));
249
+ const attribute = selectorParser.attribute({ attribute: scopeAttribute, value: undefined, raws: {} });
250
+ if (pseudoElement)
251
+ selector.insertBefore(pseudoElement, attribute);
252
+ else
253
+ selector.append(attribute);
254
+ });
255
+ }).processSync(rule.selector);
256
+ });
257
+ return root.toString();
258
+ }
259
+ function compileScript(section, source, filename) {
260
+ const file = ts.createSourceFile(filename, section.content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
261
+ const syntaxError = file.parseDiagnostics[0];
262
+ if (syntaxError) {
263
+ const message = ts.flattenDiagnosticMessageText(syntaxError.messageText, "\n");
264
+ throw compileError(source, filename, "MEEK013", message, section.contentStart + (syntaxError.start ?? 0));
265
+ }
266
+ const imports = [];
267
+ const types = [];
268
+ const setup = [];
269
+ const names = [];
270
+ let propsType;
271
+ let propsTypeOffset;
272
+ for (const statement of file.statements) {
273
+ if (ts.isImportDeclaration(statement)) {
274
+ imports.push(markSource(statement.getText(file), section.contentStart + statement.getStart(file)));
275
+ continue;
276
+ }
277
+ if (ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement)) {
278
+ types.push(markSource(statement.getText(file), section.contentStart + statement.getStart(file)));
279
+ continue;
280
+ }
281
+ if (ts.isVariableStatement(statement)) {
282
+ if (statement.declarationList.declarations.length !== 1)
283
+ throw compileError(source, filename, "MEEK014", "Declare one variable per statement in .meeko scripts", section.contentStart + statement.getStart(file));
284
+ for (const declaration of statement.declarationList.declarations) {
285
+ if (!ts.isIdentifier(declaration.name))
286
+ throw compileError(source, filename, "MEEK015", "Only simple variable declarations are supported in .meeko scripts", section.contentStart + declaration.getStart(file));
287
+ const name = declaration.name.text;
288
+ names.push(name);
289
+ if (declaration.initializer && ts.isCallExpression(declaration.initializer) && declaration.initializer.expression.getText(file) === "defineProps") {
290
+ if (propsType)
291
+ throw compileError(source, filename, "MEEK016", "defineProps must appear once in its own declaration", section.contentStart + declaration.getStart(file));
292
+ const typeArgument = declaration.initializer.typeArguments?.[0];
293
+ propsType = typeArgument?.getText(file);
294
+ if (!propsType)
295
+ throw compileError(source, filename, "MEEK017", "defineProps requires a type argument", section.contentStart + declaration.getStart(file));
296
+ propsTypeOffset = section.contentStart + typeArgument.getStart(file);
297
+ setup.push(`const ${name}: ${markSource(propsType, propsTypeOffset)} = __props;`);
298
+ }
299
+ else {
300
+ setup.push(markSource(statement.getText(file), section.contentStart + statement.getStart(file)));
301
+ }
302
+ }
303
+ continue;
304
+ }
305
+ if (ts.isFunctionDeclaration(statement) && statement.name) {
306
+ names.push(statement.name.text);
307
+ setup.push(markSource(statement.getText(file), section.contentStart + statement.getStart(file)));
308
+ continue;
309
+ }
310
+ throw compileError(source, filename, "MEEK018", `Unsupported top-level script statement: ${ts.SyntaxKind[statement.kind]}`, section.contentStart + statement.getStart(file));
311
+ }
312
+ return { imports, types, setup, names, propsType: propsType ?? "Record<string, never>", propsTypeOffset };
313
+ }
314
+ function hydrationBody(bindings, context, locals = new Map()) {
315
+ const statements = [];
316
+ const mappedExpression = (expression, offset, expressionLocals = locals) => markSource(rewriteLocals(expression, expressionLocals), offset, expression.length);
317
+ for (const { id, expression, offset } of bindings.texts) {
318
+ const point = locate(context.source, offset);
319
+ const location = `${basename(context.filename)}:${point.line}:${point.column} (text ${id})`;
320
+ statements.push(`cleanups.push(__meeko.bindText(__scope, ${id}, () => ${mappedExpression(expression, offset)}, ${quote(location)}));`);
321
+ }
322
+ for (const { id, name, expression, offset } of bindings.attributes) {
323
+ const point = locate(context.source, offset);
324
+ const location = `${basename(context.filename)}:${point.line}:${point.column} (attribute ${name})`;
325
+ statements.push(`cleanups.push(__meeko.bindAttribute(__scope, ${id}, ${quote(name)}, () => ${mappedExpression(expression, offset)}, ${quote(location)}));`);
326
+ }
327
+ for (const { id, name, expression, offset } of bindings.events) {
328
+ const eventType = ["click", "input", "change", "submit", "keydown", "keyup", "focus", "blur"].includes(name)
329
+ ? `GlobalEventHandlersEventMap[${quote(name)}]` : "Event";
330
+ const handler = markSource(`((__meeko_handler: (event: ${eventType}) => void) => __meeko_handler(event as ${eventType}))(${mappedExpression(expression, offset)});`, offset, expression.length);
331
+ statements.push(`{
332
+ cleanups.push(__meeko.bindEvent(__scope, ${id}, ${quote(name)}, (event: Event) => {
333
+ ${handler}
334
+ }));
335
+ }`);
336
+ }
337
+ const componentStatements = [];
338
+ for (const entry of bindings.components) {
339
+ const point = locate(context.source, entry.offset);
340
+ const location = `${basename(context.filename)}:${point.line}:${point.column} (component ${entry.reference})`;
341
+ const props = entry.props.map((prop) => `${markSource(quote(prop.name), prop.nameOffset, prop.name.length)}: ${mappedExpression(prop.expression, prop.offset)}`);
342
+ const object = markSource(`{ ${props.join(", ")} }`, entry.offset, 1);
343
+ componentStatements.push(`cleanups.push(__meeko.bindComponent(__scope, ${entry.id}, ${mappedExpression(entry.reference, entry.referenceOffset)}, () => (${object}), ${quote(location)}));`);
344
+ }
345
+ statements.unshift(...componentStatements);
346
+ for (const entry of bindings.regions) {
347
+ if (entry.kind === "condition") {
348
+ statements.push(`cleanups.push(__meeko.bindIf(__scope, ${entry.id}, () => ${mappedExpression(entry.expression, entry.offset)}, () => ${rewriteLocals(entry.render, locals)}, (__scope) => { ${hydrationBody(entry.bindings, context, locals)} }));`);
349
+ }
350
+ else {
351
+ const outerLocals = new Map(locals);
352
+ outerLocals.delete(entry.itemName);
353
+ outerLocals.delete(entry.indexName);
354
+ const innerLocals = new Map(locals);
355
+ innerLocals.set(entry.itemName, "__meeko_item.value");
356
+ innerLocals.set(entry.indexName, "__meeko_index.value");
357
+ statements.push(`cleanups.push(__meeko.bindFor(__scope, ${entry.id}, () => ${mappedExpression(entry.expression, entry.offset)}, (${entry.itemName}, ${entry.indexName}) => ${mappedExpression(entry.key, entry.keyOffset, outerLocals)}, (${entry.itemName}, ${entry.indexName}) => ${rewriteLocals(entry.render, outerLocals)}, (__meeko_item, __meeko_index, __scope) => { ${hydrationBody(entry.bindings, context, innerLocals)} }));`);
358
+ }
359
+ }
360
+ return `const cleanups: Array<() => void> = [];
361
+ try {
362
+ ${statements.join("\n")}
363
+ return () => { for (const cleanup of cleanups.reverse()) cleanup(); };
364
+ } catch (error) { for (const cleanup of cleanups.reverse()) cleanup(); throw error; }`;
365
+ }
366
+ /** Compila una parte tipada de .meeko para SSR e hidratación del HTML existente. */
367
+ export function compile(source, filename = "anonymous.meeko", options = {}) {
368
+ const { script, template, style } = parseSfc(source, filename);
369
+ const componentId = createHash("sha256").update(filename).digest("hex").slice(0, 12);
370
+ const scopeAttribute = style?.attributes.some((attribute) => attribute.name === "scoped")
371
+ ? `data-meeko-s-${createHash("sha256").update(filename).digest("hex").slice(0, 8)}`
372
+ : undefined;
373
+ let css;
374
+ try {
375
+ css = scopeAttribute ? scopeCss(style?.content ?? "", scopeAttribute) : style?.content ?? "";
376
+ }
377
+ catch (error) {
378
+ if (error instanceof postcss.CssSyntaxError && style && error.line !== undefined && error.column !== undefined) {
379
+ const lines = style.content.split(/\r\n|\r|\n/);
380
+ const prior = lines.slice(0, error.line - 1).reduce((total, line) => total + line.length + 1, 0);
381
+ throw compileError(source, filename, "MEEK019", error.reason, style.contentStart + prior + error.column - 1);
382
+ }
383
+ throw error;
384
+ }
385
+ const parsed = parseTemplate(source, template, filename);
386
+ const bindingsForTemplate = emptyBindings();
387
+ const context = { source, filename, templateOffset: template.contentStart, scopeAttribute, nextText: 0, nextEvent: 0, nextAttribute: 0, nextRegion: 0 };
388
+ const html = renderNode(parsed, bindingsForTemplate, context);
389
+ const scriptParts = compileScript(script, source, filename);
390
+ const bindings = scriptParts.names.join(", ");
391
+ const hot = options.hot === true;
392
+ const setupCode = `const __bindings = __setup(__inputProps);\n__meeko.restoreSignals(__bindings, __state);\nconst { ${bindings} } = __bindings;`;
393
+ const releaseCode = hot
394
+ ? `const __cleanup = () => { __release(); dispose(); };\nconst __component = { renderToString, hydrate, hydrateScope, css };\nconst __snapshot = () => __meeko.snapshotSignals(__bindings);\nconst __devtoolsStop = __meeko.registerDevtoolsInstance({ componentId: __hotId, name: ${quote(basename(filename, ".meeko"))}, file: ${quote(filename)}, scope: __scope, props: __inputProps, bindings: __bindings });\nconst __dispose = () => { try { __cleanup(); } finally { __devtoolsStop(); } };\nconst __stop = __hotRoot ? __meeko.registerHotInstance(__hotId, __hotRoot, __inputProps, __component, __snapshot, __dispose) : __scope.start ? __meeko.registerHotScope(__hotId, __scope, __inputProps, __component, __snapshot, __dispose) : __dispose;\nreturn __meeko.withStateSnapshot(__stop, __snapshot);`
395
+ : `return __meeko.withStateSnapshot(() => { __release(); dispose(); }, () => __meeko.snapshotSignals(__bindings));`;
396
+ const hotIdExport = hot ? `export const __hotId = ${quote(componentId)};\n` : "";
397
+ const stateParam = ", __state?: __meeko.HotState";
398
+ const scopeHotRootParam = hot ? ", __hotRoot?: Element" : "";
399
+ const scopeForward = hot ? "__scope, __inputProps, __state, root" : "__scope, __inputProps, __state";
400
+ const propsType = scriptParts.propsTypeOffset === undefined ? scriptParts.propsType : markSource(scriptParts.propsType, scriptParts.propsTypeOffset);
401
+ const markedCode = `// Generated by Meeko. Edit the .meeko source instead.\nimport * as __meeko from "@meekojs/meeko/internal";\n${scriptParts.imports.join("\n")}\n${scriptParts.types.join("\n")}\nfunction __setup(__props: ${propsType}) {\n${scriptParts.setup.join("\n")}\nreturn { ${bindings} };\n}\nexport type ComponentProps = ${propsType};\nexport const css = ${quote(css)};\n${hotIdExport}export function renderWithState(__inputProps: ComponentProps): { html: string; state: __meeko.HotState } {\nreturn __meeko.createRoot((dispose) => {\nconst __meeko_transfer = true;\nconst __bindings = __setup(__inputProps);\nconst { ${bindings} } = __bindings;\ntry {\nconst html = ${html};\nconst state = __meeko.snapshotSignals(__bindings);\n__meeko.serializeJson(state);\nreturn { html, state };\n} finally { dispose(); }\n});\n}\nexport function renderToString(__inputProps: ComponentProps${stateParam}): string {\nreturn __meeko.createRoot((dispose) => {\nconst __meeko_transfer = false;\n${setupCode}\ntry { return ${html}; } finally { dispose(); }\n});\n}\nexport function hydrate(root: Element, __inputProps: ComponentProps${stateParam}): () => void {\nconst __scope: __meeko.DomScope = { root };\nreturn hydrateScope(${scopeForward});\n}\nexport function hydrateScope(__scope: __meeko.DomScope, __inputProps: ComponentProps${stateParam}${scopeHotRootParam}): () => void {\nreturn __meeko.createRoot((dispose) => {\nconst __meeko_transfer = false;\n${setupCode}\nconst __release = (() => { ${hydrationBody(bindingsForTemplate, context)} })();\n${releaseCode}\n});\n}\n`;
402
+ const { code, mappings } = extractSourceMappings(markedCode);
403
+ return { code, css, mappings };
404
+ }
@@ -0,0 +1,14 @@
1
+ /** Opciones de montaje del panel integrado de desarrollo. */
2
+ export interface DevtoolsPanelOptions {
3
+ /** Elemento que recibirá el host del Shadow DOM. El valor predeterminado es `document.body`. */
4
+ container?: Element;
5
+ }
6
+ /** Control del ciclo de vida del panel. */
7
+ export interface DevtoolsPanel {
8
+ /** Reintenta encontrar el hook y actualiza los datos visibles. */
9
+ refresh(): void;
10
+ /** Quita la interfaz y libera las suscripciones y eventos instalados. */
11
+ unmount(): void;
12
+ }
13
+ /** Monta una interfaz aislada en Shadow DOM para inspeccionar instancias Meeko. */
14
+ export declare function mountDevtoolsPanel(options?: DevtoolsPanelOptions): DevtoolsPanel;