@marko/runtime-tags 6.3.2 → 6.3.3

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/cheatsheet.md ADDED
@@ -0,0 +1,146 @@
1
+ # Marko 6 cheat sheet
2
+
3
+ Marko 6 = HTML superset. NOT JSX, NOT old Marko 4/5. `.marko` files are components; the filename is the tag name.
4
+
5
+ ## Golden rules
6
+
7
+ 1. Text interpolation: `${expr}` inside tag bodies. A bare line like `Welcome aboard` at the root of the template parses as a TAG named `Welcome` (concise mode) and fails to compile. Wrap it in an element (`<p>Welcome aboard</p>`) or prefix the line with `-- ` to mark it as text (`-- Welcome ${name}` works at the top level). Attributes take raw JS after `=` with NO braces/quotes needed: `<div title=user.name data-n=1 + 1>` (parenthesize if the value contains `>`).
8
+ 2. State: `<let/name=initial>` (slash then var name!). Update by plain assignment in an event handler: `count++`, `text = "hi"`. No setState, no hooks.
9
+ 3. Derived values: `<const/total=items.length * price>` — auto-recomputes. Never use an effect to derive state.
10
+ 4. NEVER mutate state in place. `items.push(x)` will NOT update the UI. Always reassign:
11
+ - add: `items = items.concat(x)`
12
+ - remove: `items = items.toSpliced(i, 1)`
13
+ - update: `items = items.toSpliced(i, 1, { ...item, done: true })`
14
+ - object: `user = { ...user, name }`
15
+ 5. Events: method shorthand `onClick() { ... }` or `onClick=fn`. Handler gets the DOM event: `onSubmit(e) { e.preventDefault(); save() }`. Don't sync input values through `onInput`/`onChange` listeners — that's what change handlers (next rule) are for, and they make the data's owner explicit.
16
+ 6. Native inputs are UNCONTROLLED by default: `value=` only sets the initial value. Adding the matching `*Change` handler is what makes them controlled — `valueChange` on `<input>`/`<textarea>`/`<select>`, `checkedChange` on checkboxes/radios, `openChange` on `<details>`/`<dialog>`. `value:=text` is the shorthand for `value=text valueChange(v) { text = v }`. (`<textarea value:=text/>` — value attribute, not body.)
17
+ 7. Write the change handler yourself when updates need transforming — number inputs give STRINGS: `<input type="number" value=n valueChange(v) { n = +v }>`.
18
+
19
+ ## Canonical component (copy this shape)
20
+
21
+ ```marko
22
+ <let/items=[]>
23
+ <let/draft="">
24
+ <const/remaining=items.filter(t => !t.done).length>
25
+
26
+ <input value:=draft placeholder="What next?">
27
+ <button onClick() {
28
+ const text = draft.trim();
29
+ if (text) {
30
+ items = items.concat({ id: items.length + 1, text, done: false });
31
+ draft = "";
32
+ }
33
+ }>Add</button>
34
+
35
+ <if=items.length>
36
+ <ul>
37
+ <for|item, i| of=items by="id">
38
+ <li class={ done: item.done }>
39
+ <input
40
+ type="checkbox"
41
+ checked=item.done
42
+ checkedChange(v) { items = items.toSpliced(i, 1, { ...item, done: v }) }
43
+ >
44
+ ${item.text}
45
+ </li>
46
+ </for>
47
+ </ul>
48
+ <p>${remaining} left</p>
49
+ </if>
50
+ <else>
51
+ <p>Nothing yet</p>
52
+ </else>
53
+ ```
54
+
55
+ ## Control flow
56
+
57
+ ```marko
58
+ <if=cond> A </if>
59
+ <else if=other> B </else>
60
+ <else> C </else>
61
+
62
+ <for|item, index| of=list by="id"> ${item.name} </for> // by keys the loop (no key= attr!)
63
+ <for|city| of=cities by=(city) => city> ${city} </for> // primitives: by takes a FUNCTION — by=city would be an undefined variable (the loop param is not in scope in by=)
64
+ <for|i| from=0 until=5> ${i} </for> // 0..4
65
+
66
+ <show=open> stays mounted, keeps state (form drafts) when hidden </show>
67
+ ```
68
+
69
+ `<if>` destroys/rebuilds its content; `<show>` just hides it (use for toggles that must keep state).
70
+
71
+ ## Async (`<await>`)
72
+
73
+ ```marko
74
+ import { getUser } from "../data.js";
75
+
76
+ <try>
77
+ <await|user|=getUser()>
78
+ <h2>${user.name}</h2>
79
+ </await>
80
+
81
+ <@placeholder>Loading...</@placeholder>
82
+ <@catch|err|>${err.message}</@catch>
83
+ </try>
84
+ ```
85
+
86
+ `@placeholder`/`@catch` go on `<try>`, never on `<await>`. On the server this streams (placeholder flushes first, content follows). It works in the browser too: hand `<await>` a new promise (e.g. a `<const>` derived from state) and it shows the placeholder again, then the new result.
87
+
88
+ Don't fetch while rendering: start data loads early, pass the PROMISE through the template, and `<await>` it where the data is rendered. Fetching inside each component that renders the data serializes the requests (waterfalls). Under @marko/run, load in the route handler — `return next({ user: getUser() })`, no await — and render with `<await|user|=$global.data.user>`.
89
+
90
+ ## Components
91
+
92
+ - File `src/tags/product-card.marko` is auto-discovered as `<product-card>` from any template (no import needed). Attributes arrive as `input`: `${input.title}`.
93
+ - Body content passed by the parent renders with `<${input.content}/>`.
94
+ - Named sections use attribute tags:
95
+
96
+ ```marko
97
+ /* parent */
98
+ <my-card>
99
+ <@header>Hello</@header>
100
+ body text
101
+ </my-card>
102
+
103
+ /* src/tags/my-card.marko */
104
+ <div class="card">
105
+ <header><${input.header.content}/></header>
106
+ <${input.content}/>
107
+ </div>
108
+ ```
109
+
110
+ - Repeated attr tags (many `<@tab ...>`) arrive as the SINGULAR prop `input.tab`, which is iterable but NOT an array: `input.tab[i]` and `input.tab.length` are undefined. To index or count, spread first: `<const/tabs=[...input.tab ?? []]>` then `tabs[active]`/`tabs.length`. Looping directly is fine: `<for|tab| of=input.tab>`.
111
+ - Conditional attrs: `false`/`null` attrs are omitted from HTML. `aria-selected` etc. want strings: `aria-selected=(i === active && "true")`.
112
+ - `class=` / `style=` accept strings, objects, arrays: `class=["btn", { active }]`, `style={ color }` (single braces).
113
+
114
+ ## Client-side effects (rare — prefer state/const)
115
+
116
+ ```marko
117
+ <div/el/>
118
+ <script>
119
+ // Browser-only. Runs after mount and re-runs when referenced state changes.
120
+ el().focus(); // element refs are getter FUNCTIONS
121
+ const id = setInterval(tick, 1000);
122
+ $signal.onabort = () => clearInterval(id); // cleanup
123
+ </script>
124
+ ```
125
+
126
+ `<style>` = real CSS, extracted & global. `<script>` = reactive effect, NOT an HTML script tag.
127
+
128
+ ## DON'T (these are errors or silently wrong)
129
+
130
+ | Wrong (React/Vue/Marko5 habit) | Right |
131
+ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------ |
132
+ | `{expr}` in markup, `className`, `key=`, `style={{...}}` | `${expr}`, `class`, `by=` on `<for>`, `style={...}` |
133
+ | `onClick={() => ...}` / `@click` / `on-click("name")` | `onClick() { ... }` |
134
+ | `const [x, setX] = useState()` / `state` / `class {}` block | `<let/x=0>` then `x = 1` |
135
+ | `$ const y = x * 2;` (scriptlets are removed) | `<const/y=x * 2>` |
136
+ | `<let x=0>` | `<let/x=0>` |
137
+ | `<if(cond)>` | `<if=cond>` |
138
+ | `items.push(x)` | `items = items.concat(x)` |
139
+ | `input.renderBody` | `input.content` |
140
+ | `<await>` with `@placeholder`/`@catch` | wrap in `<try>` |
141
+ | `el.focus()` on a ref | `el().focus()` inside `<script>`/handler |
142
+ | `input.tab[0]` / `input.tab.length` | `[...input.tab ?? []]` first (attr tags are iterables, not arrays) |
143
+ | bare text on its own line at template root | wrap in an element (`<p>...`), or prefix the line with `-- ` |
144
+ | `by=item` using the loop variable | `by="propName"` or `by=(item) => key` — `by=` is evaluated outside the loop |
145
+ | `onInput(e) { q = e.target.value }` to sync an input | `value:=q` — the change handler owns the value |
146
+ | fetching inside the component that renders the data | start the promise early (route handler / top of template), pass it down to `<await>` |
@@ -1249,6 +1249,7 @@ function startSection(path) {
1249
1249
  upstreamExpression: void 0,
1250
1250
  downstreamBinding: void 0,
1251
1251
  hasAbortSignal: false,
1252
+ abortSignalExprs: 0,
1252
1253
  readsOwner: false,
1253
1254
  isBranch: false
1254
1255
  };
@@ -1539,6 +1540,21 @@ function isStaticRoot(path) {
1539
1540
  }
1540
1541
  }
1541
1542
  //#endregion
1543
+ //#region src/translator/util/analyze-errors.ts
1544
+ const [getHasAnalyzeErrors, setHasAnalyzeErrors] = createProgramState(() => false);
1545
+ function reportAnalyzeError(path, error) {
1546
+ if (!(error instanceof Error)) throw error;
1547
+ const { label = error.message, loc } = error;
1548
+ setHasAnalyzeErrors(true);
1549
+ (0, _marko_compiler_babel_utils.diagnosticError)(path, {
1550
+ label,
1551
+ loc: loc ?? path.node.loc ?? void 0
1552
+ });
1553
+ }
1554
+ function hasAnalyzeErrors() {
1555
+ return getHasAnalyzeErrors();
1556
+ }
1557
+ //#endregion
1542
1558
  //#region src/translator/util/asset-imports.ts
1543
1559
  function addAssetImport(file, request) {
1544
1560
  (file.metadata.marko.assetImports ??= /* @__PURE__ */ new Set()).add(request);
@@ -3766,9 +3782,9 @@ var for_default = {
3766
3782
  if (!isAttrTag) allowAttrs.push("by");
3767
3783
  (0, _marko_compiler_babel_utils.assertAllowedAttributes)(tag, allowAttrs);
3768
3784
  if (isAttrTag) return;
3769
- if (forType !== "of") {
3770
- if (getKnownAttrValues(tag.node).by?.type === "StringLiteral") throw (tag.get("attributes").find((attr) => attr.isMarkoAttribute() && attr.node.name === "by") || tag).buildCodeFrameError(`The [\`<for>\` tag](https://markojs.com/docs/reference/core-tag#for) only supports a string \`by\` key with \`of\`; use a \`by=(${forType === "in" ? "key, value" : "index"}) => ...\` function for \`<for ${forType}>\`.`);
3771
- }
3785
+ const byAttr = getKnownAttrValues(tag.node).by;
3786
+ if (forType !== "of" && byAttr?.type === "StringLiteral") throw tag.hub.buildError(byAttr, `The [\`<for>\` tag](https://markojs.com/docs/reference/core-tag#for) only supports a string \`by\` key with \`of\`; use a \`by=(${forType === "in" ? "key, value" : "index"}) => ...\` function for \`<for ${forType}>\`.`);
3787
+ if (byAttr?.type === "Identifier" && !tag.scope.getBinding(byAttr.name) && tag.node.body.params.some((param) => Object.hasOwn(_marko_compiler.types.getBindingIdentifiers(param), byAttr.name))) throw tag.hub.buildError(byAttr, `The \`by=\` attribute is evaluated before the loop runs, so \`${byAttr.name}\` is not in scope. Key with a property name string (\`by="id"\`) or a function (\`by=(${byAttr.name}) => key\`).`);
3772
3788
  const bodySection = startSection(tagBody);
3773
3789
  if (!bodySection) {
3774
3790
  dropNodes(getAllTagReferenceNodes(tag.node));
@@ -4773,6 +4789,7 @@ function assertNativeAttrValueType(tag, attr) {
4773
4789
  if (_marko_compiler.types.isObjectExpression(attr.value)) throw tag.hub.buildError(attr, `The \`${name}\` attribute cannot be a plain object (it would render as \`[object Object]\`).`, Error);
4774
4790
  }
4775
4791
  function assertNativeHandlerAttr(tag, attr) {
4792
+ if (_marko_compiler.types.isObjectExpression(attr.value)) throw tag.hub.buildError(attr.value, `The \`${attr.name}\` ${isEventHandler(attr.name) ? "event handler" : "change handler"} on a [native tag](https://markojs.com/docs/reference/native-tag) must be a function. Attribute values in Marko are plain JavaScript expressions, not JSX; remove the wrapping \`{ }\` (e.g. \`${attr.name}=myHandler\` or \`${attr.name}() { ... }\`).`, Error);
4776
4793
  if ((0, _marko_compiler_babel_utils.computeNode)(attr.value)?.value) throw tag.hub.buildError(attr.value, `The \`${attr.name}\` ${isEventHandler(attr.name) ? "event handler" : "change handler"} on a [native tag](https://markojs.com/docs/reference/native-tag) must be a function or a falsey value (\`null\`, \`undefined\`, \`false\`, \`0\`, …).`, Error);
4777
4794
  }
4778
4795
  const lowercaseEventHandlerReg = /^on[a-z]/;
@@ -5197,8 +5214,9 @@ function flattenTextOnlyConditional(rootTag) {
5197
5214
  rootTag.replaceWith(_marko_compiler.types.markoPlaceholder(expr, true));
5198
5215
  }
5199
5216
  function assertValidCondition(tag) {
5217
+ const conditionTagName = getTagName(tag);
5200
5218
  (0, _marko_compiler_babel_utils.assertNoVar)(tag);
5201
- (0, _marko_compiler_babel_utils.assertNoArgs)(tag);
5219
+ (0, _marko_compiler_babel_utils.assertNoArgs)(tag, conditionTagName === "else" ? "Write the condition as an attribute instead: `<else if=condition>`." : `Write the condition as a value attribute instead: \`<${conditionTagName}=condition>\`.`);
5202
5220
  (0, _marko_compiler_babel_utils.assertNoParams)(tag);
5203
5221
  assertHasBody$1(tag);
5204
5222
  assertNoSpreadAttrs(tag);
@@ -5550,6 +5568,7 @@ var program_default = {
5550
5568
  }
5551
5569
  },
5552
5570
  exit(program) {
5571
+ if (hasAnalyzeErrors()) return;
5553
5572
  finalizeReferences();
5554
5573
  const programExtra = program.node.extra;
5555
5574
  const paramsBinding = programExtra.binding;
@@ -7011,7 +7030,7 @@ function addReadToExpression(root, binding, getter) {
7011
7030
  const fnRoot = getFnRoot(root);
7012
7031
  const exprRoot = getExprRoot(fnRoot || root);
7013
7032
  const section = getOrCreateSection(exprRoot);
7014
- const exprExtra = exprRoot.node.extra ??= { section };
7033
+ const exprExtra = getCanonicalExtra(exprRoot.node.extra ??= { section });
7015
7034
  const read = addRead(exprExtra, node.extra ??= {}, binding, section, getter);
7016
7035
  const { parent } = root;
7017
7036
  if (parent.type === "BinaryExpression" && (parent.operator === "===" || parent.operator === "!==")) read.comparedTo = parent.left === node ? parent.right : parent.left;
@@ -7488,9 +7507,9 @@ const kDOMBinding$2 = Symbol("await tag dom binding");
7488
7507
  var await_default = {
7489
7508
  analyze(tag) {
7490
7509
  (0, _marko_compiler_babel_utils.assertNoVar)(tag);
7491
- (0, _marko_compiler_babel_utils.assertNoArgs)(tag);
7510
+ (0, _marko_compiler_babel_utils.assertNoArgs)(tag, "Write the promise as a value attribute and receive the result as a tag parameter instead: `<await|result|=promise>`.");
7492
7511
  assertNoSpreadAttrs(tag);
7493
- (0, _marko_compiler_babel_utils.assertNoAttributeTags)(tag);
7512
+ (0, _marko_compiler_babel_utils.assertNoAttributeTags)(tag, "For pending and error UI, wrap the `<await>` in a [`<try>` tag](https://markojs.com/docs/reference/core-tag#try) with `<@placeholder>` and `<@catch|err|>` attribute tags.");
7494
7513
  const { node } = tag;
7495
7514
  const tagBody = tag.get("body");
7496
7515
  const section = getOrCreateSection(tag);
@@ -7599,7 +7618,10 @@ var const_default = {
7599
7618
  assertNoBodyContent(tag);
7600
7619
  const { node } = tag;
7601
7620
  const [valueAttr] = node.attributes;
7602
- if (!node.var) throw tag.get("name").buildCodeFrameError("The [`<const>` tag](https://markojs.com/docs/reference/core-tag#const) requires a [tag variable](https://markojs.com/docs/reference/language#tag-variables).");
7621
+ if (!node.var) {
7622
+ const looksLikeDeclaration = !node.attributes.some((attr) => _marko_compiler.types.isMarkoAttribute(attr) && (attr.default || attr.name === "value")) && node.attributes.length === 1 && _marko_compiler.types.isMarkoAttribute(node.attributes[0]) && /^[A-Za-z_$][\w$]*$/.test(node.attributes[0].name);
7623
+ throw tag.get("name").buildCodeFrameError(`The [\`<const>\` tag](https://markojs.com/docs/reference/core-tag#const) requires a [tag variable](https://markojs.com/docs/reference/language#tag-variables)${looksLikeDeclaration ? `; the variable goes after a slash: \`<const/${node.attributes[0].name}=...>\`. For a one time module level value, prefix a plain JavaScript statement with \`static\`.` : ", e.g. `<const/doubled=count * 2>`."}`);
7624
+ }
7603
7625
  if (!valueAttr) throw tag.get("name").buildCodeFrameError("The [`<const>` tag](https://markojs.com/docs/reference/core-tag#const) requires a [`value=` attribute](https://markojs.com/docs/reference/language#shorthand-value).");
7604
7626
  if (node.attributes.length > 1 || !_marko_compiler.types.isMarkoAttribute(valueAttr) || !valueAttr.default && valueAttr.name !== "value") throw tag.get("name").buildCodeFrameError("The [`<const>` tag](https://markojs.com/docs/reference/core-tag#const) only supports the [`value=` attribute](https://markojs.com/docs/reference/language#shorthand-value).");
7605
7627
  const valueExtra = evaluate(valueAttr.value);
@@ -7964,7 +7986,8 @@ var let_default = {
7964
7986
  } else {
7965
7987
  const start = attr.loc?.start;
7966
7988
  const end = attr.loc?.end;
7967
- const msg = "The [`<let>` tag](https://markojs.com/docs/reference/core-tag#let) only supports the [`value=` attribute](https://markojs.com/docs/reference/language#shorthand-value) and its change handler.";
7989
+ let msg = "The [`<let>` tag](https://markojs.com/docs/reference/core-tag#let) only supports the [`value=` attribute](https://markojs.com/docs/reference/language#shorthand-value) and its change handler.";
7990
+ if (!tagVar && /^[A-Za-z_$][\w$]*$/.test(attr.name)) msg += ` To declare reactive state, the variable goes after a slash: \`<let/${attr.name}=...>\`. For a one time module level value, prefix a plain JavaScript statement with \`static\`.`;
7968
7991
  if (start == null || end == null) throw tag.get("name").buildCodeFrameError(msg);
7969
7992
  else throw tag.hub.buildError({ loc: {
7970
7993
  start,
@@ -7975,7 +7998,7 @@ var let_default = {
7975
7998
  (0, _marko_compiler_babel_utils.assertNoParams)(tag);
7976
7999
  assertNoBodyContent(tag);
7977
8000
  assertNoSpreadAttrs(tag);
7978
- if (!tagVar) throw tag.get("name").buildCodeFrameError("The [`<let>` tag](https://markojs.com/docs/reference/core-tag#let) requires a [tag variable](https://markojs.com/docs/reference/language#tag-variables).");
8001
+ if (!tagVar) throw tag.get("name").buildCodeFrameError("The [`<let>` tag](https://markojs.com/docs/reference/core-tag#let) requires a [tag variable](https://markojs.com/docs/reference/language#tag-variables), e.g. `<let/count=0>`.");
7979
8002
  if (!_marko_compiler.types.isIdentifier(tagVar)) throw tag.get("var").buildCodeFrameError("The [`<let>` tag](https://markojs.com/docs/reference/core-tag#let) variable cannot be destructured.");
7980
8003
  if (valueChangeAttr && (0, _marko_compiler_babel_utils.computeNode)(valueChangeAttr.value)?.value) throw tag.get("attributes").find((attr) => attr.node === valueChangeAttr).get("value").buildCodeFrameError("The [`<let>` tag](https://markojs.com/docs/reference/core-tag#let) [`valueChange=` attribute](https://markojs.com/docs/reference/core-tag#controllable-let) must be a function.");
7981
8004
  const tagSection = getOrCreateSection(tag);
@@ -8339,7 +8362,7 @@ function isLiteral(expr) {
8339
8362
  }
8340
8363
  function assertValidShow(tag) {
8341
8364
  (0, _marko_compiler_babel_utils.assertNoVar)(tag);
8342
- (0, _marko_compiler_babel_utils.assertNoArgs)(tag);
8365
+ (0, _marko_compiler_babel_utils.assertNoArgs)(tag, "Write the condition as a value attribute instead: `<show=condition>`.");
8343
8366
  (0, _marko_compiler_babel_utils.assertNoParams)(tag);
8344
8367
  assertNoSpreadAttrs(tag);
8345
8368
  assertHasBody(tag);
@@ -9080,7 +9103,7 @@ function getInlinedBodyTag(parent) {
9080
9103
  }
9081
9104
  //#endregion
9082
9105
  //#region src/translator/visitors/referenced-identifier.ts
9083
- const abortIdsByExpressionForSection = /* @__PURE__ */ new WeakMap();
9106
+ const [getAbortResetEmitted] = createSectionState("abortResetEmitted", () => /* @__PURE__ */ new Set());
9084
9107
  var referenced_identifier_default = {
9085
9108
  migrate(identifier) {
9086
9109
  const { name } = identifier.node;
@@ -9100,6 +9123,9 @@ var referenced_identifier_default = {
9100
9123
  const section = getOrCreateSection(identifier);
9101
9124
  section.hasAbortSignal = true;
9102
9125
  setReferencesScope(identifier);
9126
+ const exprRoot = getExprRoot(identifier);
9127
+ const rootExtra = exprRoot.node.extra ??= { section };
9128
+ if (rootExtra.abortId === void 0) rootExtra.abortId = section.abortSignalExprs++;
9103
9129
  }
9104
9130
  },
9105
9131
  translate(identifier) {
@@ -9114,16 +9140,10 @@ var referenced_identifier_default = {
9114
9140
  else {
9115
9141
  const section = getSection(identifier);
9116
9142
  const exprRoot = getExprRoot(identifier);
9117
- let abortIdsByExpression = abortIdsByExpressionForSection.get(section);
9118
- let exprId;
9119
- if (abortIdsByExpression) exprId = abortIdsByExpression.get(exprRoot);
9120
- else {
9121
- abortIdsByExpression = /* @__PURE__ */ new Map();
9122
- abortIdsByExpressionForSection.set(section, abortIdsByExpression);
9123
- }
9124
- if (exprId === void 0) {
9125
- exprId = abortIdsByExpression.size;
9126
- abortIdsByExpression.set(exprRoot, exprId);
9143
+ const exprId = exprRoot.node.extra.abortId;
9144
+ const resetEmitted = getAbortResetEmitted(section);
9145
+ if (!resetEmitted.has(exprRoot)) {
9146
+ resetEmitted.add(exprRoot);
9127
9147
  addStatement("render", section, exprRoot.node.extra?.referencedBindings, _marko_compiler.types.expressionStatement(_marko_compiler.types.callExpression(importRuntime("$signalReset"), [scopeIdentifier, _marko_compiler.types.numericLiteral(exprId)])), false);
9128
9148
  }
9129
9149
  identifier.replaceWith(_marko_compiler.types.callExpression(importRuntime("$signal"), [scopeIdentifier, _marko_compiler.types.numericLiteral(exprId)]));
@@ -9135,7 +9155,11 @@ var referenced_identifier_default = {
9135
9155
  //#region src/translator/visitors/scriptlet.ts
9136
9156
  var scriptlet_default = {
9137
9157
  analyze(scriptlet) {
9138
- if (!scriptlet.node.static) throw scriptlet.buildCodeFrameError("Scriptlets are not supported when using the tags api.");
9158
+ if (!scriptlet.node.static) {
9159
+ reportAnalyzeError(scriptlet, scriptlet.buildCodeFrameError("Scriptlets are not supported when using the tags api."));
9160
+ scriptlet.skip();
9161
+ return;
9162
+ }
9139
9163
  mergeReferences(getOrCreateSection(scriptlet), scriptlet.node, scriptlet.node.body);
9140
9164
  if (scriptlet.node.target === "client") (0, _marko_compiler_babel_utils.getProgram)().node.extra.isInteractive = true;
9141
9165
  },
@@ -9294,11 +9318,18 @@ function getTagRelativePath(tag) {
9294
9318
  if (!relativePath) throw tagNotFoundError(tag);
9295
9319
  return relativePath;
9296
9320
  }
9321
+ const knownWrongTags = /* @__PURE__ */ new Map([
9322
+ ["slot", "To render content passed to this tag, use a [dynamic tag](https://markojs.com/docs/reference/language#dynamic-tags): `<${input.content}/>`."],
9323
+ ["state", "Reactive state is declared with the [`<let>` tag](https://markojs.com/docs/reference/core-tag#let): `<let/name=initialValue>`."],
9324
+ ["fragment", "Marko templates and tag bodies may have multiple root nodes; no fragment wrapper is needed."]
9325
+ ]);
9297
9326
  function tagNotFoundError(tag) {
9298
9327
  const tagName = getTagName(tag);
9299
9328
  if (tagName && tag.scope.hasBinding(tagName)) return tag.get("name").buildCodeFrameError(`Local variables must be in a [dynamic tag](https://markojs.com/docs/reference/language#dynamic-tags) unless they are PascalCase. Use \`<\${${tagName}}/>\` or rename to \`${tagName.charAt(0).toUpperCase() + tagName.slice(1)}\`.`);
9300
9329
  let didYouMean = "";
9301
- if (tagName) {
9330
+ const knownWrongTagHint = tagName && knownWrongTags.get(tagName);
9331
+ if (knownWrongTagHint) didYouMean = ` ${knownWrongTagHint}`;
9332
+ else if (tagName) {
9302
9333
  const closestTag = (0, fastest_levenshtein.closest)(tagName, Object.keys((0, _marko_compiler_babel_utils.getTaglibLookup)(tag.hub.file).merged.tags));
9303
9334
  if ((0, fastest_levenshtein.distance)(tagName, closestTag) < 4) didYouMean = ` Did you mean \`<${closestTag}>\`?`;
9304
9335
  }
@@ -9505,33 +9536,46 @@ function enableDynamicTagResume(tag) {
9505
9536
  var tag_default = {
9506
9537
  analyze: {
9507
9538
  enter(tag) {
9508
- const tagDef = (0, _marko_compiler_babel_utils.getTagDef)(tag);
9509
- const type = analyzeTagNameType(tag);
9510
- const hook = tagDef?.analyzer?.hook;
9511
- if (hook) {
9512
- enter$1(hook, tag);
9513
- return;
9514
- }
9515
- if (type === 0) {
9516
- native_tag_default.analyze.enter(tag);
9517
- return;
9518
- }
9519
- switch (type) {
9520
- case 1:
9521
- custom_tag_default.analyze.enter(tag);
9522
- break;
9523
- case 3:
9524
- attribute_tag_default.analyze.enter(tag);
9525
- break;
9526
- case 2:
9527
- dynamic_tag_default.analyze.enter(tag);
9528
- break;
9539
+ try {
9540
+ const tagDef = (0, _marko_compiler_babel_utils.getTagDef)(tag);
9541
+ const type = analyzeTagNameType(tag);
9542
+ const hook = tagDef?.analyzer?.hook;
9543
+ if (hook) {
9544
+ enter$1(hook, tag);
9545
+ return;
9546
+ }
9547
+ if (type === 0) {
9548
+ native_tag_default.analyze.enter(tag);
9549
+ return;
9550
+ }
9551
+ switch (type) {
9552
+ case 1:
9553
+ custom_tag_default.analyze.enter(tag);
9554
+ break;
9555
+ case 3:
9556
+ attribute_tag_default.analyze.enter(tag);
9557
+ break;
9558
+ case 2:
9559
+ dynamic_tag_default.analyze.enter(tag);
9560
+ break;
9561
+ }
9562
+ } catch (err) {
9563
+ (tag.node.extra ??= {}).analyzeFailed = true;
9564
+ reportAnalyzeError(tag, err);
9565
+ tag.skip();
9529
9566
  }
9530
9567
  },
9531
9568
  exit(tag) {
9532
- const hook = (0, _marko_compiler_babel_utils.getTagDef)(tag)?.analyzer?.hook;
9533
- if (hook) {
9534
- exit$1(hook, tag);
9569
+ if (tag.node.extra?.analyzeFailed) return;
9570
+ try {
9571
+ const hook = (0, _marko_compiler_babel_utils.getTagDef)(tag)?.analyzer?.hook;
9572
+ if (hook) {
9573
+ exit$1(hook, tag);
9574
+ return;
9575
+ }
9576
+ } catch (err) {
9577
+ (tag.node.extra ??= {}).analyzeFailed = true;
9578
+ reportAnalyzeError(tag, err);
9535
9579
  return;
9536
9580
  }
9537
9581
  }
@@ -0,0 +1,3 @@
1
+ import type { types as t } from "@marko/compiler";
2
+ export declare function reportAnalyzeError(path: t.NodePath<t.Node>, error: unknown): void;
3
+ export declare function hasAnalyzeErrors(): boolean;
@@ -47,6 +47,9 @@ export interface Section {
47
47
  properties: Opt<string>;
48
48
  } | false | undefined;
49
49
  hasAbortSignal: boolean;
50
+ /** Count of distinct `$signal` expression roots; analyze allocates each
51
+ * root's `abortId` from this so translates read, never re-derive. */
52
+ abortSignalExprs: number;
50
53
  readsOwner: boolean;
51
54
  isBranch: boolean;
52
55
  content: null | {
@@ -1,4 +1,11 @@
1
1
  import { types as t } from "@marko/compiler";
2
+ declare module "@marko/compiler/dist/types" {
3
+ interface NodeExtra {
4
+ /** `$signal` abort id for this expression root, allocated in analyze
5
+ * (see below) so every translate reads the same id. */
6
+ abortId?: number;
7
+ }
8
+ }
2
9
  declare const _default: {
3
10
  migrate(this: unknown, identifier: t.NodePath<t.Identifier>): void;
4
11
  analyze(this: unknown, identifier: t.NodePath<t.Identifier>): void;
@@ -1,4 +1,9 @@
1
1
  import { types as t } from "@marko/compiler";
2
+ declare module "@marko/compiler/dist/types" {
3
+ interface MarkoTagExtra {
4
+ analyzeFailed?: boolean;
5
+ }
6
+ }
2
7
  declare const _default: {
3
8
  analyze: {
4
9
  enter(this: unknown, tag: t.NodePath<t.MarkoTag>): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marko/runtime-tags",
3
- "version": "6.3.2",
3
+ "version": "6.3.3",
4
4
  "description": "Optimized runtime for Marko templates.",
5
5
  "keywords": [
6
6
  "api",
@@ -20,6 +20,7 @@
20
20
  ".": {
21
21
  "types": "./index.d.ts"
22
22
  },
23
+ "./cheatsheet.md": "./cheatsheet.md",
23
24
  "./package.json": "./package.json",
24
25
  "./translator": "./dist/translator/index.js",
25
26
  "./tags/*": "./tags/*",
@@ -43,13 +44,14 @@
43
44
  "tags-html.d.ts",
44
45
  "!**/meta.*.json",
45
46
  "!**/__tests__",
46
- "!**/*.tsbuildinfo"
47
+ "!**/*.tsbuildinfo",
48
+ "cheatsheet.md"
47
49
  ],
48
50
  "scripts": {
49
51
  "build": "node -r ~ts ./scripts/bundle.mts"
50
52
  },
51
53
  "dependencies": {
52
- "@marko/compiler": "^5.39.66",
54
+ "@marko/compiler": "^5.40.2",
53
55
  "csstype": "^3.2.3",
54
56
  "fastest-levenshtein": "^1.0.16",
55
57
  "magic-string": "^0.30.21"
@@ -61,6 +63,7 @@
61
63
  ".": {
62
64
  "types": "./index.d.ts"
63
65
  },
66
+ "./cheatsheet.md": "./cheatsheet.md",
64
67
  "./translator": "./src/translator/index.ts",
65
68
  "./tags/*": "./tags/*",
66
69
  "./debug/*": "./src/*.ts",