@marko/runtime-tags 6.3.4 → 6.3.6
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 +30 -2
- package/dist/debug/dom.js +5 -0
- package/dist/debug/dom.mjs +5 -0
- package/dist/debug/html.js +5 -0
- package/dist/debug/html.mjs +5 -0
- package/dist/translator/index.d.ts +1 -0
- package/dist/translator/index.js +44 -2
- package/package.json +2 -2
package/cheatsheet.md
CHANGED
|
@@ -14,7 +14,8 @@ Marko 6 = HTML superset. NOT JSX, NOT old Marko 4/5. `.marko` files are componen
|
|
|
14
14
|
- object: `user = { ...user, name }`
|
|
15
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
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.
|
|
17
|
+
7. Transform in the handler when needed — number inputs give STRINGS: `<input type="number" value=n valueChange(v) { n = +v }>`, or `value:parseFloat:=n`.
|
|
18
|
+
8. Radio/checkbox groups: `checkedValue:=picked` on each input (shared var, distinct `value=`) — the match is checked; array var for multi-checkbox. Dropdown: `<select value:=picked>`.
|
|
18
19
|
|
|
19
20
|
## Canonical component (copy this shape)
|
|
20
21
|
|
|
@@ -109,7 +110,8 @@ Don't fetch while rendering: start data loads early, pass the PROMISE through th
|
|
|
109
110
|
|
|
110
111
|
- 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
112
|
- 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
|
+
- `class=` / `style=` accept strings, objects, arrays: `class=["btn", { active }]`, `style={ color }` (single braces). `style=` keys are kebab-case CSS names (`{ "background-color": c }`), not camelCase.
|
|
114
|
+
- `<id/x>` mints a collision-free id for label/input wiring (`<label for=x>`/`<input id=x>`) — don't hardcode ids in reusable tags; `<id/x=input.id>` reuses a caller's.
|
|
113
115
|
|
|
114
116
|
## Client-side effects (rare — prefer state/const)
|
|
115
117
|
|
|
@@ -125,6 +127,26 @@ Don't fetch while rendering: start data loads early, pass the PROMISE through th
|
|
|
125
127
|
|
|
126
128
|
`<style>` = real CSS, extracted & global. `<script>` = reactive effect, NOT an HTML script tag.
|
|
127
129
|
|
|
130
|
+
Imperative libs (charts, maps) needing mount/update/destroy: use `<lifecycle>`, not a hand-wired `<script>`. `this` persists across all three; return an object from `onMount` to stash the instance:
|
|
131
|
+
|
|
132
|
+
```marko
|
|
133
|
+
<canvas/canvas/>
|
|
134
|
+
<lifecycle
|
|
135
|
+
onMount() { return { chart: new Chart(canvas(), data) } }
|
|
136
|
+
onUpdate() { this.chart.setData(data) } // re-runs when `data` changes
|
|
137
|
+
onDestroy() { this.chart.destroy() }
|
|
138
|
+
/>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Lazy loading
|
|
142
|
+
|
|
143
|
+
Defer a tag's JS into its own bundle until a trigger fires: `render`, `visible#sel`, `idle`, `media(...)`, `on-click#sel` (combine with `|`). Server HTML renders immediately; `<try>` shows a `@placeholder` while loading. Don't hand-roll an `IntersectionObserver`.
|
|
144
|
+
|
|
145
|
+
```marko
|
|
146
|
+
import PriceChart from "<price-chart>" with { load: "visible#chart" }
|
|
147
|
+
<div#chart><PriceChart symbol=input.symbol/></div>
|
|
148
|
+
```
|
|
149
|
+
|
|
128
150
|
## DON'T (these are errors or silently wrong)
|
|
129
151
|
|
|
130
152
|
| Wrong (React/Vue/Marko5 habit) | Right |
|
|
@@ -144,3 +166,9 @@ Don't fetch while rendering: start data loads early, pass the PROMISE through th
|
|
|
144
166
|
| `by=item` using the loop variable | `by="propName"` or `by=(item) => key` — `by=` is evaluated outside the loop |
|
|
145
167
|
| `onInput(e) { q = e.target.value }` to sync an input | `value:=q` — the change handler owns the value |
|
|
146
168
|
| fetching inside the component that renders the data | start the promise early (route handler / top of template), pass it down to `<await>` |
|
|
169
|
+
| `style={ backgroundColor: c }` (camelCase keys) | `style={ "background-color": c }` (kebab-case) |
|
|
170
|
+
| `this.querySelector` / `this.getRootNode()` in `<script>` | element ref getter: `<div/el>` then `el()` (there is no `this`) |
|
|
171
|
+
| `<div/my-el>` / `<input/card-input>` (hyphen in tag var) | valid JS identifier: `<div/myEl>` |
|
|
172
|
+
| hand-rolled radios `checked=x checkedChange(v){…}` | `checkedValue:=picked` on each radio (shared var, distinct `value=`) |
|
|
173
|
+
| hand-rolled `IntersectionObserver` to defer a widget's JS | `import W from "<w>" with { load: "visible#sel" }` |
|
|
174
|
+
| imperative lib wired through `<script>` mount + cleanup | `<lifecycle onMount/onUpdate/onDestroy>` (keeps `this` across all three) |
|
package/dist/debug/dom.js
CHANGED
|
@@ -68,7 +68,12 @@ function _call(fn, v) {
|
|
|
68
68
|
function stringifyClassObject(name, value) {
|
|
69
69
|
return value ? name : "";
|
|
70
70
|
}
|
|
71
|
+
const warnedStyleKeys = /* @__PURE__ */ new Set();
|
|
71
72
|
function stringifyStyleObject(name, value) {
|
|
73
|
+
if (/[A-Z]/.test(name) && !name.includes("-") && !warnedStyleKeys.has(name)) {
|
|
74
|
+
warnedStyleKeys.add(name);
|
|
75
|
+
console.warn(`\`${name}\` is not a CSS property name; \`style\` object keys are written verbatim, so it renders as invalid CSS. Use \`${name.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase()).replace(/^ms-/, "-ms-")}\`.`);
|
|
76
|
+
}
|
|
72
77
|
return value || value === 0 ? name + ":" + value : "";
|
|
73
78
|
}
|
|
74
79
|
function escapeStyleValue(str) {
|
package/dist/debug/dom.mjs
CHANGED
|
@@ -66,7 +66,12 @@ function _call(fn, v) {
|
|
|
66
66
|
function stringifyClassObject(name, value) {
|
|
67
67
|
return value ? name : "";
|
|
68
68
|
}
|
|
69
|
+
const warnedStyleKeys = /* @__PURE__ */ new Set();
|
|
69
70
|
function stringifyStyleObject(name, value) {
|
|
71
|
+
if (/[A-Z]/.test(name) && !name.includes("-") && !warnedStyleKeys.has(name)) {
|
|
72
|
+
warnedStyleKeys.add(name);
|
|
73
|
+
console.warn(`\`${name}\` is not a CSS property name; \`style\` object keys are written verbatim, so it renders as invalid CSS. Use \`${name.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase()).replace(/^ms-/, "-ms-")}\`.`);
|
|
74
|
+
}
|
|
70
75
|
return value || value === 0 ? name + ":" + value : "";
|
|
71
76
|
}
|
|
72
77
|
function escapeStyleValue(str) {
|
package/dist/debug/html.js
CHANGED
|
@@ -64,7 +64,12 @@ function getWrongAttrSuggestion(name) {
|
|
|
64
64
|
function stringifyClassObject(name, value) {
|
|
65
65
|
return value ? name : "";
|
|
66
66
|
}
|
|
67
|
+
const warnedStyleKeys = /* @__PURE__ */ new Set();
|
|
67
68
|
function stringifyStyleObject(name, value) {
|
|
69
|
+
if (/[A-Z]/.test(name) && !name.includes("-") && !warnedStyleKeys.has(name)) {
|
|
70
|
+
warnedStyleKeys.add(name);
|
|
71
|
+
console.warn(`\`${name}\` is not a CSS property name; \`style\` object keys are written verbatim, so it renders as invalid CSS. Use \`${name.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase()).replace(/^ms-/, "-ms-")}\`.`);
|
|
72
|
+
}
|
|
68
73
|
return value || value === 0 ? name + ":" + value : "";
|
|
69
74
|
}
|
|
70
75
|
function escapeStyleValue(str) {
|
package/dist/debug/html.mjs
CHANGED
|
@@ -62,7 +62,12 @@ function getWrongAttrSuggestion(name) {
|
|
|
62
62
|
function stringifyClassObject(name, value) {
|
|
63
63
|
return value ? name : "";
|
|
64
64
|
}
|
|
65
|
+
const warnedStyleKeys = /* @__PURE__ */ new Set();
|
|
65
66
|
function stringifyStyleObject(name, value) {
|
|
67
|
+
if (/[A-Z]/.test(name) && !name.includes("-") && !warnedStyleKeys.has(name)) {
|
|
68
|
+
warnedStyleKeys.add(name);
|
|
69
|
+
console.warn(`\`${name}\` is not a CSS property name; \`style\` object keys are written verbatim, so it renders as invalid CSS. Use \`${name.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase()).replace(/^ms-/, "-ms-")}\`.`);
|
|
70
|
+
}
|
|
66
71
|
return value || value === 0 ? name + ":" + value : "";
|
|
67
72
|
}
|
|
68
73
|
function escapeStyleValue(str) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Config } from "@marko/compiler";
|
|
2
2
|
export { version } from "../../package.json";
|
|
3
|
+
export declare const cheatsheet = "../../cheatsheet.md";
|
|
3
4
|
export declare const tagDiscoveryDirs: string[];
|
|
4
5
|
export { default as internalEntryBuilder } from "./util/entry-builder";
|
|
5
6
|
export declare const preferAPI = "tags";
|
package/dist/translator/index.js
CHANGED
|
@@ -3802,6 +3802,8 @@ var for_default = {
|
|
|
3802
3802
|
default: throw tag.buildCodeFrameError("The [`<for>` tag](https://markojs.com/docs/reference/core-tag#for) requires an `of=`, `in=`, or `to=` attribute.");
|
|
3803
3803
|
}
|
|
3804
3804
|
if (!isAttrTag) allowAttrs.push("by");
|
|
3805
|
+
const keyAttr = tag.node.attributes.find((attr) => attr.type === "MarkoAttribute" && attr.name === "key");
|
|
3806
|
+
if (keyAttr) throw tag.hub.buildError(keyAttr, `The [\`<for>\` tag](https://markojs.com/docs/reference/core-tag#for) keys items with the \`by=\` attribute, not \`key=\`. ${forType === "of" ? "Use `by=\"propName\"` or `by=(item, index) => key`" : forType === "in" ? "Use `by=(key, value) => key`" : "Use `by=(num) => key`"}.`);
|
|
3805
3807
|
(0, _marko_compiler_babel_utils.assertAllowedAttributes)(tag, allowAttrs);
|
|
3806
3808
|
if (isAttrTag) return;
|
|
3807
3809
|
const byAttr = getKnownAttrValues(tag.node).by;
|
|
@@ -4359,6 +4361,7 @@ var native_tag_default = {
|
|
|
4359
4361
|
seen[attr.name] = attr;
|
|
4360
4362
|
assertValidNativeAttrName(tag, attr);
|
|
4361
4363
|
assertNativeAttrValueType(tag, attr);
|
|
4364
|
+
if (attr.name === "style") warnCamelCaseStyleKeys(tag, attr);
|
|
4362
4365
|
if (injectNonce && attr.name === "nonce") injectNonce = false;
|
|
4363
4366
|
if (isEventOrChangeHandler(attr.name)) assertNativeHandlerAttr(tag, attr);
|
|
4364
4367
|
if (isEventHandler(attr.name)) {
|
|
@@ -4810,6 +4813,19 @@ function assertNativeAttrValueType(tag, attr) {
|
|
|
4810
4813
|
if (_marko_compiler.types.isFunction(attr.value)) throw tag.hub.buildError(attr, `The \`${name}\` attribute cannot be a function.`, Error);
|
|
4811
4814
|
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);
|
|
4812
4815
|
}
|
|
4816
|
+
function warnCamelCaseStyleKeys(tag, attr) {
|
|
4817
|
+
const { value } = attr;
|
|
4818
|
+
const objects = value.type === "ObjectExpression" ? [value] : value.type === "ArrayExpression" ? value.elements.filter((el) => el?.type === "ObjectExpression") : [];
|
|
4819
|
+
for (const object of objects) for (const prop of object.properties) {
|
|
4820
|
+
if (prop.type !== "ObjectProperty" || prop.computed) continue;
|
|
4821
|
+
const { key } = prop;
|
|
4822
|
+
const name = key.type === "Identifier" ? key.name : key.type === "StringLiteral" ? key.value : void 0;
|
|
4823
|
+
if (name && !name.startsWith("--") && !name.includes("-") && /[A-Z]/.test(name)) (0, _marko_compiler_babel_utils.diagnosticWarn)(tag, {
|
|
4824
|
+
label: `\`${name}\` is not a CSS property name; the [\`style=\` object](https://markojs.com/docs/reference/native-tag#style) writes keys out verbatim (unlike the camelCased DOM style API), so this renders as invalid CSS the browser ignores. Use the kebab-case name \`${name.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase()).replace(/^ms-/, "-ms-")}\`.`,
|
|
4825
|
+
loc: key.loc ?? void 0
|
|
4826
|
+
});
|
|
4827
|
+
}
|
|
4828
|
+
}
|
|
4813
4829
|
function assertNativeHandlerAttr(tag, attr) {
|
|
4814
4830
|
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);
|
|
4815
4831
|
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);
|
|
@@ -9339,8 +9355,9 @@ function getTagRelativePath(tag) {
|
|
|
9339
9355
|
const { node, hub: { file } } = tag;
|
|
9340
9356
|
let relativePath;
|
|
9341
9357
|
if (_marko_compiler.types.isStringLiteral(node.name)) {
|
|
9342
|
-
const
|
|
9343
|
-
|
|
9358
|
+
const tagDef = (0, _marko_compiler_babel_utils.getTagDef)(tag);
|
|
9359
|
+
const template = node.extra?.featureType === "class" && tagDef?.renderer || (0, _marko_compiler_babel_utils.getTagTemplate)(tag);
|
|
9360
|
+
relativePath = template && (0, _marko_compiler_babel_utils.resolveRelativePath)(file, template, tagDef);
|
|
9344
9361
|
} else if (node.extra?.tagNameImported) relativePath = node.extra.tagNameImported;
|
|
9345
9362
|
if (!relativePath) throw tagNotFoundError(tag);
|
|
9346
9363
|
return relativePath;
|
|
@@ -9355,13 +9372,36 @@ function tagNotFoundError(tag) {
|
|
|
9355
9372
|
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)}\`.`);
|
|
9356
9373
|
let didYouMean = "";
|
|
9357
9374
|
const knownWrongTagHint = tagName && knownWrongTags.get(tagName);
|
|
9375
|
+
const proseText = tagName && getProseText(tag);
|
|
9358
9376
|
if (knownWrongTagHint) didYouMean = ` ${knownWrongTagHint}`;
|
|
9377
|
+
else if (proseText) didYouMean = ` If this line is meant to be text, prefix it with \`--\` (e.g. \`-- ${proseText}\`) or wrap it in an element such as \`<p>${proseText}</p>\`.`;
|
|
9359
9378
|
else if (tagName) {
|
|
9360
9379
|
const closestTag = (0, fastest_levenshtein.closest)(tagName, Object.keys((0, _marko_compiler_babel_utils.getTaglibLookup)(tag.hub.file).merged.tags));
|
|
9361
9380
|
if ((0, fastest_levenshtein.distance)(tagName, closestTag) < 4) didYouMean = ` Did you mean \`<${closestTag}>\`?`;
|
|
9362
9381
|
}
|
|
9363
9382
|
return tag.get("name").buildCodeFrameError(`Unable to find entry point for [custom tag](https://markojs.com/docs/reference/custom-tag#relative-custom-tags) \`<${tagName}>\`.${didYouMean}`);
|
|
9364
9383
|
}
|
|
9384
|
+
const wordReg = /^[A-Za-z]+$/;
|
|
9385
|
+
function getProseText(tag) {
|
|
9386
|
+
const { node } = tag;
|
|
9387
|
+
const tagName = getTagName(tag);
|
|
9388
|
+
if (!tagName || !wordReg.test(tagName) || node.var || node.body.params.length || node.body.body.length || !node.attributes.length || !isConciseModeTag(node)) return;
|
|
9389
|
+
return collectProseWords(node, tagName);
|
|
9390
|
+
}
|
|
9391
|
+
function isConciseModeTag(node) {
|
|
9392
|
+
const tagLoc = node.loc;
|
|
9393
|
+
const nameLoc = node.name.loc;
|
|
9394
|
+
if (!tagLoc || !nameLoc) return false;
|
|
9395
|
+
return tagLoc.start.line === nameLoc.start.line && tagLoc.start.column === nameLoc.start.column;
|
|
9396
|
+
}
|
|
9397
|
+
function collectProseWords(node, tagName) {
|
|
9398
|
+
let text = tagName;
|
|
9399
|
+
for (const attr of node.attributes) {
|
|
9400
|
+
if (attr.type !== "MarkoAttribute" || attr.default || !wordReg.test(attr.name) || attr.value.type !== "BooleanLiteral" || attr.value.value !== true) return;
|
|
9401
|
+
text += " " + attr.name;
|
|
9402
|
+
}
|
|
9403
|
+
return text;
|
|
9404
|
+
}
|
|
9365
9405
|
function importOrSelfReferenceName(file, request, name, nameHint) {
|
|
9366
9406
|
if (isCircularRequest(file, request)) return _marko_compiler.types.identifier(name);
|
|
9367
9407
|
return (0, _marko_compiler_babel_utils.importNamed)(file, request, name, nameHint);
|
|
@@ -9983,6 +10023,7 @@ function getVisitorExit(visit) {
|
|
|
9983
10023
|
}
|
|
9984
10024
|
//#endregion
|
|
9985
10025
|
//#region src/translator/index.ts
|
|
10026
|
+
const cheatsheet = "../../cheatsheet.md";
|
|
9986
10027
|
const visitors = extractVisitors({
|
|
9987
10028
|
Program: program_default,
|
|
9988
10029
|
Function: function_default,
|
|
@@ -10009,6 +10050,7 @@ function getRuntimeEntryFiles(output, optimize) {
|
|
|
10009
10050
|
}
|
|
10010
10051
|
//#endregion
|
|
10011
10052
|
exports.analyze = analyze;
|
|
10053
|
+
exports.cheatsheet = cheatsheet;
|
|
10012
10054
|
exports.createInteropTranslator = createInteropTranslator;
|
|
10013
10055
|
exports.getRuntimeEntryFiles = getRuntimeEntryFiles;
|
|
10014
10056
|
exports.internalEntryBuilder = entry_builder_default;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marko/runtime-tags",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.6",
|
|
4
4
|
"description": "Optimized runtime for Marko templates.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"build": "node -r ~ts ./scripts/bundle.mts"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@marko/compiler": "^5.
|
|
54
|
+
"@marko/compiler": "^5.41.1",
|
|
55
55
|
"csstype": "^3.2.3",
|
|
56
56
|
"fastest-levenshtein": "^1.0.16",
|
|
57
57
|
"magic-string": "^0.30.21"
|