fix: optimize pass-through spreads to known tags

This commit is contained in:
Ryan Turnquist 2025-12-08 09:30:21 -08:00
parent 5c9eb8ea17
commit 217b6ee0c6
88 changed files with 925 additions and 106 deletions

View File

@ -0,0 +1,5 @@
---
"@marko/runtime-tags": patch
---
Optimize spreads through a wrapper tag to a known tag into direct calls

View File

@ -44,16 +44,16 @@
{ {
"name": "comments", "name": "comments",
"user": { "user": {
"min": 735, "min": 801,
"brotli": 398 "brotli": 426
}, },
"runtime": { "runtime": {
"min": 7140, "min": 7140,
"brotli": 3125 "brotli": 3125
}, },
"total": { "total": {
"min": 7875, "min": 7941,
"brotli": 3523 "brotli": 3551
} }
}, },
{ {

View File

@ -1,14 +1,14 @@
// size: 735 (min) 398 (brotli) // size: 801 (min) 426 (brotli)
const $if_content__setup = ($scope) => { const $if_content__setup = ($scope) => {
($scope.a, ($scope.a,
$if_content__comment_comments._($scope), $if_content__comment_comments._($scope),
$if_content__id._($scope)); $if_content__id._($scope));
}, },
$if_content__comment_comments = _if_closure(4, 0, ($scope) => $if_content__comment_comments = _if_closure(4, 0, ($scope) =>
$input_comments($scope.a, $scope._.i), $input_comments$1($scope.a, $scope._.i),
), ),
$if_content__id = _if_closure(4, 0, ($scope) => $if_content__id = _if_closure(4, 0, ($scope) =>
$input_path($scope.a, $scope._.l), $input_path$1($scope.a, $scope._.l),
), ),
$for_content__id = _const(11, ($scope) => { $for_content__id = _const(11, ($scope) => {
(_attr($scope.a, "id", $scope.l), $if_content__id($scope)); (_attr($scope.a, "id", $scope.l), $if_content__id($scope));
@ -52,18 +52,22 @@ const $if_content__setup = ($scope) => {
$for_content__setup, $for_content__setup,
$for_content__$params, $for_content__$params,
), ),
$input_comments = _const(3, ($scope) => $for($scope, [$scope.d])), $input_comments$1 = _const(3, ($scope) => $for($scope, [$scope.d])),
$input_path = _const(4, $for_content__input_path); $input_path$1 = _const(4, $for_content__input_path);
function $setup($scope) { function $setup($scope) {
$scope.a; ($scope.a, $input_comments$1($scope.a), $input_path$1($scope.a));
} }
const $input_comments = _const(3, ($scope) =>
$input_comments$1($scope.a, $scope.d),
),
$input_path = _const(4, ($scope) => $input_path$1($scope.a, $scope.e));
_template( _template(
"b", "b",
"<ul></ul>", "<ul></ul>",
"/ b&", "/ b&",
$setup, $setup,
_const(2, ($scope) => { _const(2, ($scope) => {
($input_comments($scope.a, $scope.c.comments), ($input_comments($scope, $scope.c.comments),
$input_path($scope.a, $scope.c.path)); $input_path($scope, $scope.c.path));
}), }),
).mount(); ).mount();

View File

@ -1 +1 @@
{"vars":{"props":{"$empty":"e","$rest":"t","$attrTag":"n","$attrTags":"r","$attrTagIterator":"i","$_assert_hoist":"o","$forIn":"l","$forOf":"u","$forTo":"f","$forUntil":"a","$_call":"c","$stringifyClassObject":"s","$stringifyStyleObject":"d","$toDelimitedString":"h","$isEventHandler":"p","$getEventHandlerName":"g","$normalizeDynamicRenderer":"v","$decodeAccessor":"b","$toArray":"m","$push":"S","$defaultDelegator":"y","$_on":"A","$createDelegator":"N","$handleDelegated":"k","$stripSpacesAndPunctuation":"C","$nextScopeId":"_","$createScope":"w","$skipScope":"E","$findBranchWithKey":"T","$destroyBranch":"I","$destroyNestedBranches":"F","$removeAndDestroyBranch":"M","$insertBranchBefore":"x","$tempDetachBranch":"L","$walker":"$","$walk":"O","$walkInternal":"D","$branchesEnabled":"K","$isResuming":"V","$registeredValues":"G","$enableBranches":"R","$init":"B","$runResumeEffects":"H","$_resume":"U","$_var_resume":"q","$_el":"P","$_attr_input_checked":"j","$_attr_input_checked_script":"W","$_attr_input_checkedValue":"J","$_attr_input_checkedValue_script":"Q","$_attr_input_value":"X","$_attr_input_value_script":"Z","$_attr_select_value":"z","$_attr_select_value_script":"Y","$setSelectOptions":"ee","$_attr_details_or_dialog_open":"te","$_attr_details_or_dialog_open_script":"ne","$inputType":"re","$setValueAndUpdateSelection":"ie","$setCheckboxValue":"oe","$controllableDelegate":"le","$syncControllable":"ue","$handleChange":"fe","$handleFormReset":"ae","$hasValueChanged":"ce","$hasCheckboxChanged":"se","$hasSelectChanged":"de","$hasFormElementChanged":"he","$normalizeStrProp":"pe","$normalizeBoolProp":"ge","$toValueProp":"ve","$isScheduled":"be","$channel":"me","$parsers":"Se","$parseHTML":"ye","$schedule":"Ae","$flushAndWaitFrame":"Ne","$triggerMacroTask":"ke","$_let":"Ce","$_const":"_e","$_or":"we","$_for_closure":"Ee","$_if_closure":"Te","$subscribeToScopeSet":"Ie","$_closure":"Fe","$_closure_get":"Me","$_child_setup":"xe","$_var":"Le","$_return":"$e","$_return_change":"Oe","$_var_change":"De","$tagIdsByGlobal":"Ke","$_id":"Ve","$_script":"Ge","$_el_read":"Re","$traverseAllHoisted":"Be","$_hoist":"He","$createBranch":"Ue","$createAndSetupBranch":"qe","$setupBranch":"Pe","$_content":"je","$_content_resume":"We","$_content_closures":"Je","$cloneCache":"Qe","$_to_text":"Xe","$_attr":"Ze","$setAttribute":"ze","$_attr_class":"Ye","$_attr_class_items":"et","$_attr_class_item":"tt","$_attr_style":"nt","$_attr_style_items":"rt","$_attr_style_item":"it","$_text":"ot","$_text_content":"lt","$_attrs":"ut","$_attrs_content":"ft","$hasAttrAlias":"at","$_attrs_partial":"ct","$_attrs_partial_content":"st","$attrsInternal":"dt","$_attr_content":"ht","$_attrs_script":"pt","$_html":"gt","$normalizeAttrValue":"vt","$normalizeString":"bt","$_lifecycle":"mt","$removeChildNodes":"St","$insertChildNodes":"yt","$toInsertNode":"At","$_await_promise":"Nt","$_await_content":"kt","$_try":"Ct","$renderCatch":"_t","$_if":"wt","$_dynamic_tag":"Et","$_resume_dynamic_tag":"Tt","$dynamicTagScript":"It","$setConditionalRenderer":"Ft","$_for_of":"Mt","$_for_in":"xt","$_for_to":"Lt","$_for_until":"$t","$loop":"Ot","$createBranchWithTagNameOrRenderer":"Dt","$bySecondArg":"Kt","$byFirstArg":"Vt","$asyncRendersLookup":"Gt","$rendering":"Rt","$pendingRenders":"Bt","$pendingRendersLookup":"Ht","$caughtError":"Ut","$placeholderShown":"qt","$pendingEffects":"Pt","$pendingScopes":"jt","$scopeKeyOffset":"Wt","$queueRender":"Jt","$queuePendingRender":"Qt","$queueEffect":"Xt","$run":"Zt","$prepareEffects":"zt","$runEffects":"Yt","$runRenders":"en","$runRender":"tn","$_enable_catch":"nn","$$signalReset":"rn","$$signal":"on","$abort":"ln","$classIdToBranch":"un","$compat":"fn","$_template":"an","$mount":"cn","$$clickCount__script":"ta","$$clickCount":"aa","$$setup":"na","$forEach":"sn","$$if_content__setup":"sa","$$if_content__comment_comments":"ia","$$if_content__id":"ca","$$for_content__id":"la","$$for_content__input_path__OR__i":"ma","$$for_content__input_path":"oa","$$for_content__i":"ua","$$for_content__open__script":"ba","$$for_content__open":"ea","$$for_content__setup":"_a","$$for_content__comment_text":"da","$$for_content__if":"fa","$$for_content__comment_comments":"ha","$$for_content__$params":"ja","$$for_content__comment":"pa","$$for":"ra","$$input_comments":"ga","$$input_path":"ka","$_attr_nonce":"dn","$setParentBranch":"hn"}}} {"vars":{"props":{"$empty":"e","$rest":"t","$attrTag":"n","$attrTags":"r","$attrTagIterator":"i","$_assert_hoist":"o","$forIn":"l","$forOf":"u","$forTo":"f","$forUntil":"a","$_call":"c","$stringifyClassObject":"s","$stringifyStyleObject":"d","$toDelimitedString":"h","$isEventHandler":"p","$getEventHandlerName":"g","$normalizeDynamicRenderer":"v","$decodeAccessor":"b","$toArray":"m","$push":"S","$defaultDelegator":"y","$_on":"A","$createDelegator":"N","$handleDelegated":"k","$stripSpacesAndPunctuation":"C","$nextScopeId":"_","$createScope":"w","$skipScope":"E","$findBranchWithKey":"T","$destroyBranch":"I","$destroyNestedBranches":"F","$removeAndDestroyBranch":"M","$insertBranchBefore":"x","$tempDetachBranch":"L","$walker":"$","$walk":"O","$walkInternal":"D","$branchesEnabled":"K","$isResuming":"V","$registeredValues":"G","$enableBranches":"R","$init":"B","$runResumeEffects":"H","$_resume":"U","$_var_resume":"q","$_el":"P","$_attr_input_checked":"j","$_attr_input_checked_script":"W","$_attr_input_checkedValue":"J","$_attr_input_checkedValue_script":"Q","$_attr_input_value":"X","$_attr_input_value_script":"Z","$_attr_select_value":"z","$_attr_select_value_script":"Y","$setSelectOptions":"ee","$_attr_details_or_dialog_open":"te","$_attr_details_or_dialog_open_script":"ne","$inputType":"re","$setValueAndUpdateSelection":"ie","$setCheckboxValue":"oe","$controllableDelegate":"le","$syncControllable":"ue","$handleChange":"fe","$handleFormReset":"ae","$hasValueChanged":"ce","$hasCheckboxChanged":"se","$hasSelectChanged":"de","$hasFormElementChanged":"he","$normalizeStrProp":"pe","$normalizeBoolProp":"ge","$toValueProp":"ve","$isScheduled":"be","$channel":"me","$parsers":"Se","$parseHTML":"ye","$schedule":"Ae","$flushAndWaitFrame":"Ne","$triggerMacroTask":"ke","$_let":"Ce","$_const":"_e","$_or":"we","$_for_closure":"Ee","$_if_closure":"Te","$subscribeToScopeSet":"Ie","$_closure":"Fe","$_closure_get":"Me","$_child_setup":"xe","$_var":"Le","$_return":"$e","$_return_change":"Oe","$_var_change":"De","$tagIdsByGlobal":"Ke","$_id":"Ve","$_script":"Ge","$_el_read":"Re","$traverseAllHoisted":"Be","$_hoist":"He","$createBranch":"Ue","$createAndSetupBranch":"qe","$setupBranch":"Pe","$_content":"je","$_content_resume":"We","$_content_closures":"Je","$cloneCache":"Qe","$_to_text":"Xe","$_attr":"Ze","$setAttribute":"ze","$_attr_class":"Ye","$_attr_class_items":"et","$_attr_class_item":"tt","$_attr_style":"nt","$_attr_style_items":"rt","$_attr_style_item":"it","$_text":"ot","$_text_content":"lt","$_attrs":"ut","$_attrs_content":"ft","$hasAttrAlias":"at","$_attrs_partial":"ct","$_attrs_partial_content":"st","$attrsInternal":"dt","$_attr_content":"ht","$_attrs_script":"pt","$_html":"gt","$normalizeAttrValue":"vt","$normalizeString":"bt","$_lifecycle":"mt","$removeChildNodes":"St","$insertChildNodes":"yt","$toInsertNode":"At","$_await_promise":"Nt","$_await_content":"kt","$_try":"Ct","$renderCatch":"_t","$_if":"wt","$_dynamic_tag":"Et","$_resume_dynamic_tag":"Tt","$dynamicTagScript":"It","$setConditionalRenderer":"Ft","$_for_of":"Mt","$_for_in":"xt","$_for_to":"Lt","$_for_until":"$t","$loop":"Ot","$createBranchWithTagNameOrRenderer":"Dt","$bySecondArg":"Kt","$byFirstArg":"Vt","$asyncRendersLookup":"Gt","$rendering":"Rt","$pendingRenders":"Bt","$pendingRendersLookup":"Ht","$caughtError":"Ut","$placeholderShown":"qt","$pendingEffects":"Pt","$pendingScopes":"jt","$scopeKeyOffset":"Wt","$queueRender":"Jt","$queuePendingRender":"Qt","$queueEffect":"Xt","$run":"Zt","$prepareEffects":"zt","$runEffects":"Yt","$runRenders":"en","$runRender":"tn","$_enable_catch":"nn","$$signalReset":"rn","$$signal":"on","$abort":"ln","$classIdToBranch":"un","$compat":"fn","$_template":"an","$mount":"cn","$$clickCount__script":"ta","$$clickCount":"aa","$$setup":"na","$forEach":"sn","$$if_content__setup":"sa","$$if_content__comment_comments":"ia","$$if_content__id":"ca","$$for_content__id":"la","$$for_content__input_path__OR__i":"ma","$$for_content__input_path":"oa","$$for_content__i":"ua","$$for_content__open__script":"ba","$$for_content__open":"ea","$$for_content__setup":"_a","$$for_content__comment_text":"da","$$for_content__if":"fa","$$for_content__comment_comments":"ha","$$for_content__$params":"ja","$$for_content__comment":"pa","$$for":"ra","$$input_comments":"ga","$$input_path":"ka","$_attr_nonce":"dn","$setParentBranch":"hn","$$input_comments$1":"$a","$$input_path$1":"qa"}}}

View File

@ -3,10 +3,14 @@ export const $walks = /* <comments> */`/${_comments_walks}&`;
import { $setup as _comments, $input_comments as _comments_input_comments, $input_path as _comments_input_path, $template as _comments_template, $walks as _comments_walks } from "./tags/comments.marko"; import { $setup as _comments, $input_comments as _comments_input_comments, $input_path as _comments_input_path, $template as _comments_template, $walks as _comments_walks } from "./tags/comments.marko";
export function $setup($scope) { export function $setup($scope) {
_comments($scope["#childScope/0"]); _comments($scope["#childScope/0"]);
_comments_input_comments($scope["#childScope/0"]);
_comments_input_path($scope["#childScope/0"]);
} }
import * as _ from "@marko/runtime-tags/debug/dom"; import * as _ from "@marko/runtime-tags/debug/dom";
export const $input_comments = /* @__PURE__ */_._const("input_comments", $scope => _comments_input_comments($scope["#childScope/0"], $scope.input_comments));
export const $input_path = /* @__PURE__ */_._const("input_path", $scope => _comments_input_path($scope["#childScope/0"], $scope.input_path));
export const $input = /* @__PURE__ */_._const("input", $scope => { export const $input = /* @__PURE__ */_._const("input", $scope => {
_comments_input_comments($scope["#childScope/0"], $scope.input.comments); $input_comments($scope, $scope.input.comments);
_comments_input_path($scope["#childScope/0"], $scope.input.path); $input_path($scope, $scope.input.path);
}); });
export default /* @__PURE__ */_._template("__tests__/template.marko", $template, $walks, $setup, $input); export default /* @__PURE__ */_._template("__tests__/template.marko", $template, $walks, $setup, $input);

View File

@ -5,12 +5,12 @@ export default _._template("__tests__/template.marko", input => {
const $scope0_id = _._scope_id(); const $scope0_id = _._scope_id();
const $childScope = _._peek_scope_id(); const $childScope = _._peek_scope_id();
_._set_serialize_reason({ _._set_serialize_reason({
/* input.comments, input.path */0: _._serialize_guard($scope0_reason, /* input */0), /* input.comments, input.path */0: _._serialize_guard($scope0_reason, /* input.comments, input.path */0),
/* input.comments */1: _._serialize_guard($scope0_reason, /* input */0), /* input.comments */1: _._serialize_guard($scope0_reason, /* input.comments */1),
/* input.path */2: _._serialize_guard($scope0_reason, /* input */0) /* input.path */2: _._serialize_guard($scope0_reason, /* input.path */2)
}); });
_comments(input); _comments(input);
_._serialize_if($scope0_reason, /* input */0) && _._scope($scope0_id, { _._serialize_if($scope0_reason, /* input.comments, input.path */0) && _._scope($scope0_id, {
"#childScope/0": _._serialize_if($scope0_reason, /* input */0) && _._existing_scope($childScope) "#childScope/0": _._serialize_if($scope0_reason, /* input.comments, input.path */0) && _._existing_scope($childScope)
}, "__tests__/template.marko", 0); }, "__tests__/template.marko", 0);
}); });

View File

@ -0,0 +1,8 @@
{
"vars": {
"props": {
"$_": "r",
"$init": "t"
}
}
}

View File

@ -0,0 +1,7 @@
# Render
```html
<input
class="foo"
value="pass"
/>
```

View File

@ -0,0 +1,12 @@
# Render
```html
<input
class="foo"
value="pass"
/>
```
# Mutations
```
INSERT input
```

View File

@ -0,0 +1,11 @@
export const $template = "<input>";
export const $walks = /* get, over(1) */" b";
export const $setup = () => {};
import * as _ from "@marko/runtime-tags/debug/dom";
export const $input_class = /* @__PURE__ */_._const("input_class", $scope => _._attr_class($scope["#input/0"], $scope.input_class));
export const $input_value = /* @__PURE__ */_._const("input_value", $scope => _._attr($scope["#input/0"], "value", $scope.input_value));
export const $input = /* @__PURE__ */_._const("input", $scope => {
$input_class($scope, $scope.input.class);
$input_value($scope, $scope.input.value);
});
export default /* @__PURE__ */_._template("__tests__/tags/child.marko", $template, $walks, $setup, $input);

View File

@ -0,0 +1,12 @@
export const $template = _child_template;
export const $walks = /* <child> */`/${_child_walks}&`;
import { $setup as _child, $input_value as _child_input_value, $input_class as _child_input_class, $template as _child_template, $walks as _child_walks } from "./child.marko";
export function $setup($scope) {
_child($scope["#childScope/0"]);
_child_input_value($scope["#childScope/0"], "pass");
_child_input_class($scope["#childScope/0"]);
}
import * as _ from "@marko/runtime-tags/debug/dom";
export const $input_class = /* @__PURE__ */_._const("input_class", $scope => _child_input_class($scope["#childScope/0"], $scope.input_class));
export const $input = /* @__PURE__ */_._const("input", $scope => $input_class($scope, $scope.input.class));
export default /* @__PURE__ */_._template("__tests__/tags/wrap.marko", $template, $walks, $setup, $input);

View File

@ -0,0 +1,9 @@
export const $template = _wrap_template;
export const $walks = /* <wrap> */`/${_wrap_walks}&`;
import { $setup as _wrap, $input_class as _wrap_input_class, $template as _wrap_template, $walks as _wrap_walks } from "./tags/wrap.marko";
export function $setup($scope) {
_wrap($scope["#childScope/0"]);
_wrap_input_class($scope["#childScope/0"], "foo");
}
import * as _ from "@marko/runtime-tags/debug/dom";
export default /* @__PURE__ */_._template("__tests__/template.marko", $template, $walks, $setup);

View File

@ -0,0 +1,7 @@
import * as _ from "@marko/runtime-tags/debug/html";
export default _._template("__tests__/tags/child.marko", input => {
const $scope0_reason = _._scope_reason();
const $scope0_id = _._scope_id();
_._html(`<input${_._attr_class(input.class)}${_._attr("value", input.value)}>${_._el_resume($scope0_id, "#input/0", _._serialize_guard($scope0_reason, /* input.class, input.value */0))}`);
_._serialize_if($scope0_reason, /* input.class, input.value */0) && _._scope($scope0_id, {}, "__tests__/tags/child.marko", 0);
});

View File

@ -0,0 +1,15 @@
import _child from "./child.marko";
import * as _ from "@marko/runtime-tags/debug/html";
export default _._template("__tests__/tags/wrap.marko", input => {
const $scope0_reason = _._scope_reason();
const $scope0_id = _._scope_id();
const $childScope = _._peek_scope_id();
_._set_serialize_reason(_._serialize_guard($scope0_reason, /* input.class */0));
_child({
...input,
value: "pass"
});
_._serialize_if($scope0_reason, /* input.class */0) && _._scope($scope0_id, {
"#childScope/0": _._serialize_if($scope0_reason, /* input.class */0) && _._existing_scope($childScope)
}, "__tests__/tags/wrap.marko", 0);
});

View File

@ -0,0 +1,8 @@
import _wrap from "./tags/wrap.marko";
import * as _ from "@marko/runtime-tags/debug/html";
export default _._template("__tests__/template.marko", input => {
const $scope0_id = _._scope_id();
_wrap({
class: "foo"
});
});

View File

@ -0,0 +1,7 @@
# Render
```html
<input
class="foo"
value="pass"
/>
```

View File

@ -0,0 +1,12 @@
# Render
```html
<html>
<head />
<body>
<input
class="foo"
value="pass"
/>
</body>
</html>
```

View File

@ -0,0 +1,7 @@
# Render End
```html
<input
class="foo"
value="pass"
/>
```

View File

@ -0,0 +1,25 @@
# Write
```html
<input class=foo value=pass>
```
# Render End
```html
<html>
<head />
<body>
<input
class="foo"
value="pass"
/>
</body>
</html>
```
# Mutations
```
INSERT html
INSERT html/head
INSERT html/body
INSERT html/body/input
```

View File

@ -0,0 +1 @@
<input class=input.class value=input.value>

View File

@ -0,0 +1 @@
<child ...input value="pass" />

View File

@ -0,0 +1 @@
<wrap class="foo" value="fail" />

View File

@ -0,0 +1 @@
export const steps = [{}];

View File

@ -0,0 +1,5 @@
{
"vars": {
"props": {}
}
}

View File

@ -0,0 +1,9 @@
# Render
```html
<input
class="foo"
/>
<input
class="bar"
/>
```

View File

@ -0,0 +1,14 @@
# Render
```html
<input
class="foo"
/>
<input
class="bar"
/>
```
# Mutations
```
INSERT input0, input1
```

View File

@ -0,0 +1,11 @@
export const $template = "<input>";
export const $walks = /* get, over(1) */" b";
export const $setup = () => {};
import * as _ from "@marko/runtime-tags/debug/dom";
export const $input_class = /* @__PURE__ */_._const("input_class", $scope => _._attr_class($scope["#input/0"], $scope.input_class));
export const $input_value = /* @__PURE__ */_._const("input_value", $scope => _._attr($scope["#input/0"], "value", $scope.input_value));
export const $input = /* @__PURE__ */_._const("input", $scope => {
$input_class($scope, $scope.input.class);
$input_value($scope, $scope.input.value);
});
export default /* @__PURE__ */_._template("__tests__/tags/child.marko", $template, $walks, $setup, $input);

View File

@ -0,0 +1,18 @@
export const $template = _child_template;
export const $walks = /* <child> */`/${_child_walks}&`;
import { $setup as _child, $input_class as _child_input_class, $input_value as _child_input_value, $template as _child_template, $walks as _child_walks } from "./child.marko";
export function $setup($scope) {
_child($scope["#childScope/0"]);
}
import * as _ from "@marko/runtime-tags/debug/dom";
export const $input = /* @__PURE__ */_._const("input", $scope => {
const $child_input_spread = {
...$scope.input,
...{
a: 1
}
};
_child_input_class($scope["#childScope/0"], $child_input_spread.class);
_child_input_value($scope["#childScope/0"], $child_input_spread.value);
});
export default /* @__PURE__ */_._template("__tests__/tags/wrap-many-spreads.marko", $template, $walks, $setup, $input);

View File

@ -0,0 +1,20 @@
export const $template = _child_template;
export const $walks = /* <child> */`/${_child_walks}&`;
import { $setup as _child, $input_class as _child_input_class, $input_value as _child_input_value, $template as _child_template, $walks as _child_walks } from "./child.marko";
export function $setup($scope) {
_child($scope["#childScope/0"]);
_child_input_class($scope["#childScope/0"]);
_child_input_value($scope["#childScope/0"]);
}
import * as _ from "@marko/runtime-tags/debug/dom";
export const $rest_class = /* @__PURE__ */_._const("rest_class", $scope => _child_input_class($scope["#childScope/0"], $scope.rest_class));
export const $rest_value = /* @__PURE__ */_._const("rest_value", $scope => _child_input_value($scope["#childScope/0"], $scope.rest_value));
export const $input = /* @__PURE__ */_._const("input", $scope => (({
value,
...rest
}) => $rest($scope, rest))($scope.input));
export const $rest = /* @__PURE__ */_._const("rest", $scope => {
$rest_class($scope, $scope.rest.class);
$rest_value($scope, $scope.rest.value);
});
export default /* @__PURE__ */_._template("__tests__/tags/wrap-rest.marko", $template, $walks, $setup, $input);

View File

@ -0,0 +1,18 @@
export const $template = _child_template;
export const $walks = /* <child> */`/${_child_walks}&`;
import { $setup as _child, $input_class as _child_input_class, $input_value as _child_input_value, $template as _child_template, $walks as _child_walks } from "./child.marko";
export function $setup($scope) {
_child($scope["#childScope/0"]);
}
import * as _ from "@marko/runtime-tags/debug/dom";
export const $input = /* @__PURE__ */_._const("input", $scope => {
const $child_input_spread = {
...$scope.input,
...{
a: 1
}
};
_child_input_class($scope["#childScope/0"], $child_input_spread.class);
_child_input_value($scope["#childScope/0"], $child_input_spread.value);
});
export default /* @__PURE__ */_._template("__tests__/tags/wrap.marko", $template, $walks, $setup, $input);

View File

@ -0,0 +1,16 @@
export const $template = `${_wrapManySpreads_template}${_wrapRest_template}`;
export const $walks = /* <wrap-many-spreads>, <wrap-rest> */`/${_wrapManySpreads_walks}&/${_wrapRest_walks}&`;
import { $setup as _wrapManySpreads, $input as _wrapManySpreads_input, $template as _wrapManySpreads_template, $walks as _wrapManySpreads_walks } from "./tags/wrap-many-spreads.marko";
import { $setup as _wrapRest, $rest as _wrapRest_input_$rest, $template as _wrapRest_template, $walks as _wrapRest_walks } from "./tags/wrap-rest.marko";
export function $setup($scope) {
_wrapManySpreads($scope["#childScope/0"]);
_wrapManySpreads_input($scope["#childScope/0"], {
class: "foo"
});
_wrapRest($scope["#childScope/1"]);
_wrapRest_input_$rest($scope["#childScope/1"], {
class: "bar"
});
}
import * as _ from "@marko/runtime-tags/debug/dom";
export default /* @__PURE__ */_._template("__tests__/template.marko", $template, $walks, $setup);

View File

@ -0,0 +1,7 @@
import * as _ from "@marko/runtime-tags/debug/html";
export default _._template("__tests__/tags/child.marko", input => {
const $scope0_reason = _._scope_reason();
const $scope0_id = _._scope_id();
_._html(`<input${_._attr_class(input.class)}${_._attr("value", input.value)}>${_._el_resume($scope0_id, "#input/0", _._serialize_guard($scope0_reason, /* input.class, input.value */0))}`);
_._serialize_if($scope0_reason, /* input.class, input.value */0) && _._scope($scope0_id, {}, "__tests__/tags/child.marko", 0);
});

View File

@ -0,0 +1,17 @@
import _child from "./child.marko";
import * as _ from "@marko/runtime-tags/debug/html";
export default _._template("__tests__/tags/wrap-many-spreads.marko", input => {
const $scope0_reason = _._scope_reason();
const $scope0_id = _._scope_id();
const $childScope = _._peek_scope_id();
_._set_serialize_reason(_._serialize_guard($scope0_reason, /* input */0));
_child({
...input,
...{
a: 1
}
});
_._serialize_if($scope0_reason, /* input */0) && _._scope($scope0_id, {
"#childScope/0": _._serialize_if($scope0_reason, /* input */0) && _._existing_scope($childScope)
}, "__tests__/tags/wrap-many-spreads.marko", 0);
});

View File

@ -0,0 +1,16 @@
import _child from "./child.marko";
import * as _ from "@marko/runtime-tags/debug/html";
export default _._template("__tests__/tags/wrap-rest.marko", input => {
const $scope0_reason = _._scope_reason();
const $scope0_id = _._scope_id();
const {
value,
...rest
} = input;
const $childScope = _._peek_scope_id();
_._set_serialize_reason(_._serialize_guard($scope0_reason, /* rest.class, rest.value */0));
_child(rest);
_._serialize_if($scope0_reason, /* rest.class, rest.value */0) && _._scope($scope0_id, {
"#childScope/0": _._serialize_if($scope0_reason, /* rest.class, rest.value */0) && _._existing_scope($childScope)
}, "__tests__/tags/wrap-rest.marko", 0);
});

View File

@ -0,0 +1,17 @@
import _child from "./child.marko";
import * as _ from "@marko/runtime-tags/debug/html";
export default _._template("__tests__/tags/wrap.marko", input => {
const $scope0_reason = _._scope_reason();
const $scope0_id = _._scope_id();
const $childScope = _._peek_scope_id();
_._set_serialize_reason(_._serialize_guard($scope0_reason, /* input */0));
_child({
...input,
...{
a: 1
}
});
_._serialize_if($scope0_reason, /* input */0) && _._scope($scope0_id, {
"#childScope/0": _._serialize_if($scope0_reason, /* input */0) && _._existing_scope($childScope)
}, "__tests__/tags/wrap.marko", 0);
});

View File

@ -0,0 +1,12 @@
import _wrapManySpreads from "./tags/wrap-many-spreads.marko";
import _wrapRest from "./tags/wrap-rest.marko";
import * as _ from "@marko/runtime-tags/debug/html";
export default _._template("__tests__/template.marko", input => {
const $scope0_id = _._scope_id();
_wrapManySpreads({
class: "foo"
});
_wrapRest({
class: "bar"
});
});

View File

@ -0,0 +1,9 @@
# Render
```html
<input
class="foo"
/>
<input
class="bar"
/>
```

View File

@ -0,0 +1,14 @@
# Render
```html
<html>
<head />
<body>
<input
class="foo"
/>
<input
class="bar"
/>
</body>
</html>
```

View File

@ -0,0 +1,9 @@
# Render End
```html
<input
class="foo"
/>
<input
class="bar"
/>
```

View File

@ -0,0 +1,28 @@
# Write
```html
<input class=foo><input class=bar>
```
# Render End
```html
<html>
<head />
<body>
<input
class="foo"
/>
<input
class="bar"
/>
</body>
</html>
```
# Mutations
```
INSERT html
INSERT html/head
INSERT html/body
INSERT html/body/input0
INSERT html/body/input1
```

View File

@ -0,0 +1 @@
<input class=input.class value=input.value>

View File

@ -0,0 +1 @@
<child ...input ...{ a: 1 }/>

View File

@ -0,0 +1,2 @@
<const/{ value, ...rest }=input>
<child ...rest />

View File

@ -0,0 +1,2 @@
<wrap-many-spreads class="foo" />
<wrap-rest class="bar" />

View File

@ -0,0 +1 @@
export const steps = [{}];

View File

@ -0,0 +1,8 @@
{
"vars": {
"props": {
"$_": "r",
"$init": "t"
}
}
}

View File

@ -0,0 +1,6 @@
# Render
```html
<input
class="foo"
/>
```

View File

@ -0,0 +1,11 @@
# Render
```html
<input
class="foo"
/>
```
# Mutations
```
INSERT input
```

View File

@ -0,0 +1,11 @@
export const $template = "<input>";
export const $walks = /* get, over(1) */" b";
export const $setup = () => {};
import * as _ from "@marko/runtime-tags/debug/dom";
export const $input_class = /* @__PURE__ */_._const("input_class", $scope => _._attr_class($scope["#input/0"], $scope.input_class));
export const $input_value = /* @__PURE__ */_._const("input_value", $scope => _._attr($scope["#input/0"], "value", $scope.input_value));
export const $input = /* @__PURE__ */_._const("input", $scope => {
$input_class($scope, $scope.input.class);
$input_value($scope, $scope.input.value);
});
export default /* @__PURE__ */_._template("__tests__/tags/child.marko", $template, $walks, $setup, $input);

View File

@ -0,0 +1,16 @@
export const $template = _child_template;
export const $walks = /* <child> */`/${_child_walks}&`;
import { $setup as _child, $input_class as _child_input_class, $input_value as _child_input_value, $template as _child_template, $walks as _child_walks } from "./child.marko";
export function $setup($scope) {
_child($scope["#childScope/0"]);
_child_input_class($scope["#childScope/0"]);
_child_input_value($scope["#childScope/0"]);
}
import * as _ from "@marko/runtime-tags/debug/dom";
export const $input_class = /* @__PURE__ */_._const("input_class", $scope => _child_input_class($scope["#childScope/0"], $scope.input_class));
export const $input_value = /* @__PURE__ */_._const("input_value", $scope => _child_input_value($scope["#childScope/0"], $scope.input_value));
export const $input = /* @__PURE__ */_._const("input", $scope => {
$input_class($scope, $scope.input.class);
$input_value($scope, $scope.input.value);
});
export default /* @__PURE__ */_._template("__tests__/tags/wrap.marko", $template, $walks, $setup, $input);

View File

@ -0,0 +1,10 @@
export const $template = _wrap_template;
export const $walks = /* <wrap> */`/${_wrap_walks}&`;
import { $setup as _wrap, $input_class as _wrap_input_class, $input_value as _wrap_input_value, $template as _wrap_template, $walks as _wrap_walks } from "./tags/wrap.marko";
export function $setup($scope) {
_wrap($scope["#childScope/0"]);
_wrap_input_class($scope["#childScope/0"], "foo");
_wrap_input_value($scope["#childScope/0"]);
}
import * as _ from "@marko/runtime-tags/debug/dom";
export default /* @__PURE__ */_._template("__tests__/template.marko", $template, $walks, $setup);

View File

@ -0,0 +1,7 @@
import * as _ from "@marko/runtime-tags/debug/html";
export default _._template("__tests__/tags/child.marko", input => {
const $scope0_reason = _._scope_reason();
const $scope0_id = _._scope_id();
_._html(`<input${_._attr_class(input.class)}${_._attr("value", input.value)}>${_._el_resume($scope0_id, "#input/0", _._serialize_guard($scope0_reason, /* input.class, input.value */0))}`);
_._serialize_if($scope0_reason, /* input.class, input.value */0) && _._scope($scope0_id, {}, "__tests__/tags/child.marko", 0);
});

View File

@ -0,0 +1,12 @@
import _child from "./child.marko";
import * as _ from "@marko/runtime-tags/debug/html";
export default _._template("__tests__/tags/wrap.marko", input => {
const $scope0_reason = _._scope_reason();
const $scope0_id = _._scope_id();
const $childScope = _._peek_scope_id();
_._set_serialize_reason(_._serialize_guard($scope0_reason, /* input.class, input.value */0));
_child(input);
_._serialize_if($scope0_reason, /* input.class, input.value */0) && _._scope($scope0_id, {
"#childScope/0": _._serialize_if($scope0_reason, /* input.class, input.value */0) && _._existing_scope($childScope)
}, "__tests__/tags/wrap.marko", 0);
});

View File

@ -0,0 +1,8 @@
import _wrap from "./tags/wrap.marko";
import * as _ from "@marko/runtime-tags/debug/html";
export default _._template("__tests__/template.marko", input => {
const $scope0_id = _._scope_id();
_wrap({
class: "foo"
});
});

View File

@ -0,0 +1,6 @@
# Render
```html
<input
class="foo"
/>
```

View File

@ -0,0 +1,11 @@
# Render
```html
<html>
<head />
<body>
<input
class="foo"
/>
</body>
</html>
```

View File

@ -0,0 +1,6 @@
# Render End
```html
<input
class="foo"
/>
```

View File

@ -0,0 +1,24 @@
# Write
```html
<input class=foo>
```
# Render End
```html
<html>
<head />
<body>
<input
class="foo"
/>
</body>
</html>
```
# Mutations
```
INSERT html
INSERT html/head
INSERT html/body
INSERT html/body/input
```

View File

@ -0,0 +1 @@
<input class=input.class value=input.value>

View File

@ -0,0 +1 @@
<child ...input/>

View File

@ -0,0 +1 @@
<wrap class="foo" />

View File

@ -0,0 +1 @@
export const steps = [{}];

View File

@ -0,0 +1,5 @@
{
"vars": {
"props": {}
}
}

View File

@ -0,0 +1,9 @@
# Render
```html
<input
class="foo"
/>
<input
class="foo"
/>
```

View File

@ -0,0 +1,14 @@
# Render
```html
<input
class="foo"
/>
<input
class="foo"
/>
```
# Mutations
```
INSERT input0, input1
```

View File

@ -0,0 +1,11 @@
export const $template = "<input>";
export const $walks = /* get, over(1) */" b";
export const $setup = () => {};
import * as _ from "@marko/runtime-tags/debug/dom";
export const $input_class = /* @__PURE__ */_._const("input_class", $scope => _._attr_class($scope["#input/0"], $scope.input_class));
export const $input_value = /* @__PURE__ */_._const("input_value", $scope => _._attr($scope["#input/0"], "value", $scope.input_value));
export const $input = /* @__PURE__ */_._const("input", $scope => {
$input_class($scope, $scope.input.class);
$input_value($scope, $scope.input.value);
});
export default /* @__PURE__ */_._template("__tests__/tags/child.marko", $template, $walks, $setup, $input);

View File

@ -0,0 +1,25 @@
export const $template = `${_child_template}${_child_template}`;
export const $walks = /* <child>, <child> */`/${_child_walks}&/${_child_walks}&`;
import { $setup as _child, $input_class as _child_input_class, $input_value as _child_input_value, $template as _child_template, $walks as _child_walks } from "./child.marko";
export function $setup($scope) {
_child($scope["#childScope/0"]);
_child_input_class($scope["#childScope/0"]);
_child_input_value($scope["#childScope/0"]);
_child($scope["#childScope/1"]);
_child_input_class($scope["#childScope/1"]);
_child_input_value($scope["#childScope/1"]);
}
import * as _ from "@marko/runtime-tags/debug/dom";
export const $input_class = /* @__PURE__ */_._const("input_class", $scope => {
_child_input_class($scope["#childScope/0"], $scope.input_class);
_child_input_class($scope["#childScope/1"], $scope.input_class);
});
export const $input_value = /* @__PURE__ */_._const("input_value", $scope => {
_child_input_value($scope["#childScope/0"], $scope.input_value);
_child_input_value($scope["#childScope/1"], $scope.input_value);
});
export const $input = /* @__PURE__ */_._const("input", $scope => {
$input_class($scope, $scope.input.class);
$input_value($scope, $scope.input.value);
});
export default /* @__PURE__ */_._template("__tests__/tags/wrap.marko", $template, $walks, $setup, $input);

View File

@ -0,0 +1,10 @@
export const $template = _wrap_template;
export const $walks = /* <wrap> */`/${_wrap_walks}&`;
import { $setup as _wrap, $input_class as _wrap_input_class, $input_value as _wrap_input_value, $template as _wrap_template, $walks as _wrap_walks } from "./tags/wrap.marko";
export function $setup($scope) {
_wrap($scope["#childScope/0"]);
_wrap_input_class($scope["#childScope/0"], "foo");
_wrap_input_value($scope["#childScope/0"]);
}
import * as _ from "@marko/runtime-tags/debug/dom";
export default /* @__PURE__ */_._template("__tests__/template.marko", $template, $walks, $setup);

View File

@ -0,0 +1,7 @@
import * as _ from "@marko/runtime-tags/debug/html";
export default _._template("__tests__/tags/child.marko", input => {
const $scope0_reason = _._scope_reason();
const $scope0_id = _._scope_id();
_._html(`<input${_._attr_class(input.class)}${_._attr("value", input.value)}>${_._el_resume($scope0_id, "#input/0", _._serialize_guard($scope0_reason, /* input.class, input.value */0))}`);
_._serialize_if($scope0_reason, /* input.class, input.value */0) && _._scope($scope0_id, {}, "__tests__/tags/child.marko", 0);
});

View File

@ -0,0 +1,16 @@
import _child from "./child.marko";
import * as _ from "@marko/runtime-tags/debug/html";
export default _._template("__tests__/tags/wrap.marko", input => {
const $scope0_reason = _._scope_reason();
const $scope0_id = _._scope_id();
const $childScope = _._peek_scope_id();
_._set_serialize_reason(_._serialize_guard($scope0_reason, /* input.class, input.value */0));
_child(input);
const $childScope2 = _._peek_scope_id();
_._set_serialize_reason(_._serialize_guard($scope0_reason, /* input.class, input.value */0));
_child(input);
_._serialize_if($scope0_reason, /* input.class, input.value */0) && _._scope($scope0_id, {
"#childScope/0": _._serialize_if($scope0_reason, /* input.class, input.value */0) && _._existing_scope($childScope),
"#childScope/1": _._serialize_if($scope0_reason, /* input.class, input.value */0) && _._existing_scope($childScope2)
}, "__tests__/tags/wrap.marko", 0);
});

View File

@ -0,0 +1,8 @@
import _wrap from "./tags/wrap.marko";
import * as _ from "@marko/runtime-tags/debug/html";
export default _._template("__tests__/template.marko", input => {
const $scope0_id = _._scope_id();
_wrap({
class: "foo"
});
});

View File

@ -0,0 +1,9 @@
# Render
```html
<input
class="foo"
/>
<input
class="foo"
/>
```

View File

@ -0,0 +1,14 @@
# Render
```html
<html>
<head />
<body>
<input
class="foo"
/>
<input
class="foo"
/>
</body>
</html>
```

View File

@ -0,0 +1,9 @@
# Render End
```html
<input
class="foo"
/>
<input
class="foo"
/>
```

View File

@ -0,0 +1,28 @@
# Write
```html
<input class=foo><input class=foo>
```
# Render End
```html
<html>
<head />
<body>
<input
class="foo"
/>
<input
class="foo"
/>
</body>
</html>
```
# Mutations
```
INSERT html
INSERT html/head
INSERT html/body
INSERT html/body/input0
INSERT html/body/input1
```

View File

@ -0,0 +1 @@
<input class=input.class value=input.value>

View File

@ -0,0 +1,2 @@
<child ...input/>
<child ...input/>

View File

@ -0,0 +1 @@
<wrap class="foo" />

View File

@ -0,0 +1 @@
export const steps = [{}];

View File

@ -7,7 +7,8 @@
"$$partialObj": "n", "$$partialObj": "n",
"$$partialObj_a": "r", "$$partialObj_a": "r",
"$$a": "a", "$$a": "a",
"$$partialObj_b": "e" "$$partialObj_b": "e",
"$$obj_b": "m"
} }
} }
} }

View File

@ -5,7 +5,7 @@ const $obj = _._let(6, ($scope) => {
$partialObj($scope, partialObj); $partialObj($scope, partialObj);
})($scope.g), })($scope.g),
$a($scope, $scope.g.a), $a($scope, $scope.g.a),
$partialObj_b($scope, $scope.g.b)); $obj_b($scope, $scope.g.b));
}), }),
$partialObj = _._const(8, ($scope) => { $partialObj = _._const(8, ($scope) => {
(_._text($scope.b, JSON.stringify($scope.i)), (_._text($scope.b, JSON.stringify($scope.i)),
@ -15,7 +15,7 @@ const $obj = _._let(6, ($scope) => {
_._text($scope.e, void 0 === $scope.k ? "removed a" : "didn't remove a"), _._text($scope.e, void 0 === $scope.k ? "removed a" : "didn't remove a"),
), ),
$a = _._const(7, ($scope) => _._text($scope.c, $scope.h)), $a = _._const(7, ($scope) => _._text($scope.c, $scope.h)),
$partialObj_b = _._const(9, ($scope) => _._text($scope.d, $scope.j)); $obj_b = _._const(9, ($scope) => _._text($scope.d, $scope.j));
(_._script("a0", ($scope) => (_._script("a0", ($scope) =>
_._on($scope.f, "click", function () { _._on($scope.f, "click", function () {
$obj($scope, { a: 4, b: 5, d: 6 }); $obj($scope, { a: 4, b: 5, d: 6 });

View File

@ -8,7 +8,7 @@ const $obj = /* @__PURE__ */_._let("obj/6", $scope => {
...partialObj ...partialObj
}) => $partialObj($scope, partialObj))($scope.obj); }) => $partialObj($scope, partialObj))($scope.obj);
$a($scope, $scope.obj.a); $a($scope, $scope.obj.a);
$partialObj_b($scope, $scope.obj.b); $obj_b($scope, $scope.obj.b);
}); });
const $partialObj = /* @__PURE__ */_._const("partialObj", $scope => { const $partialObj = /* @__PURE__ */_._const("partialObj", $scope => {
_._text($scope["#text/1"], JSON.stringify($scope.partialObj)); _._text($scope["#text/1"], JSON.stringify($scope.partialObj));
@ -16,7 +16,7 @@ const $partialObj = /* @__PURE__ */_._const("partialObj", $scope => {
}); });
const $partialObj_a = /* @__PURE__ */_._const("partialObj_a", $scope => _._text($scope["#text/4"], $scope.partialObj_a === undefined ? "removed a" : "didn't remove a")); const $partialObj_a = /* @__PURE__ */_._const("partialObj_a", $scope => _._text($scope["#text/4"], $scope.partialObj_a === undefined ? "removed a" : "didn't remove a"));
const $a = /* @__PURE__ */_._const("a", $scope => _._text($scope["#text/2"], $scope.a)); const $a = /* @__PURE__ */_._const("a", $scope => _._text($scope["#text/2"], $scope.a));
const $partialObj_b = /* @__PURE__ */_._const("partialObj_b", $scope => _._text($scope["#text/3"], $scope.partialObj_b)); const $obj_b = /* @__PURE__ */_._const("obj_b", $scope => _._text($scope["#text/3"], $scope.obj_b));
const $setup__script = _._script("__tests__/template.marko_0", $scope => _._on($scope["#button/5"], "click", function () { const $setup__script = _._script("__tests__/template.marko_0", $scope => _._on($scope["#button/5"], "click", function () {
$obj($scope, { $obj($scope, {
a: 4, a: 4,

View File

@ -17,17 +17,21 @@ import {
} from "./nested-attribute-tags"; } from "./nested-attribute-tags";
import { filterMap, forEach, fromIter, type Opt } from "./optional"; import { filterMap, forEach, fromIter, type Opt } from "./optional";
import { import {
addRead,
type Binding, type Binding,
BindingType, BindingType,
bindingUtil, bindingUtil,
createBinding, createBinding,
dropRead,
dropReferences, dropReferences,
getAllTagReferenceNodes, getAllTagReferenceNodes,
getDebugNames, getDebugNames,
getOrCreatePropertyAlias,
getScopeAccessorLiteral, getScopeAccessorLiteral,
type InputBinding, type InputBinding,
mergeReferences, mergeReferences,
type ParamBinding, type ParamBinding,
type ReferencedExtra,
setBindingDownstream, setBindingDownstream,
trackParamsReferences, trackParamsReferences,
trackVarReferences, trackVarReferences,
@ -584,11 +588,9 @@ function analyzeAttrs(
bindings = bindingUtil.add(bindings, templateExportAttr.binding); bindings = bindingUtil.add(bindings, templateExportAttr.binding);
} }
if (bindings) { forEach(bindings, (binding) => {
forEach(bindings, (binding) => { setBindingDownstream(binding, groupExtra);
setBindingDownstream(binding, groupExtra); });
});
}
for (const name of group) { for (const name of group) {
known[attrTagLookup[name].name] = groupKnownValue; known[attrTagLookup[name].name] = groupKnownValue;
@ -604,8 +606,9 @@ function analyzeAttrs(
} }
} }
const { attributes } = tag.node; let knownSpreadBinding: Binding | undefined;
let spreadReferenceNodes: t.Node[] | undefined; let spreadReferenceNodes: t.Node[] | undefined;
const { attributes } = tag.node;
for (let i = attributes.length; i--; ) { for (let i = attributes.length; i--; ) {
const attr = attributes[i]; const attr = attributes[i];
if (t.isMarkoAttribute(attr)) { if (t.isMarkoAttribute(attr)) {
@ -616,17 +619,29 @@ function analyzeAttrs(
continue; continue;
} }
const attrExtra = (attr.value.extra ??= {}) as ReferencedExtra;
seen.add(attr.name); seen.add(attr.name);
setBindingDownstream( setBindingDownstream(templateExportAttr.binding, attrExtra);
templateExportAttr.binding,
(attr.value.extra ??= {}), if (knownSpreadBinding) {
); addRead(
attrExtra,
undefined,
getOrCreatePropertyAlias(knownSpreadBinding, attr.name),
);
}
} }
if (spreadReferenceNodes) { if (spreadReferenceNodes) {
spreadReferenceNodes.push(attr.value); spreadReferenceNodes.push(attr.value);
} else if (t.isMarkoSpreadAttribute(attr)) { } else if (t.isMarkoSpreadAttribute(attr)) {
spreadReferenceNodes = [attr.value]; if (!propTree.rest) {
knownSpreadBinding = getSingleSpreadBinding(attributes);
dropRead(attr.value.extra as ReferencedExtra);
}
if (!knownSpreadBinding) {
spreadReferenceNodes = [attr.value];
}
} else { } else {
const attrValueExtra = (attr.value.extra ??= {}); const attrValueExtra = (attr.value.extra ??= {});
known[attr.name] = { value: attrValueExtra }; known[attr.name] = { value: attrValueExtra };
@ -634,36 +649,49 @@ function analyzeAttrs(
} }
} }
if (spreadReferenceNodes) { if (knownSpreadBinding) {
const extra = (inputExpr.value = mergeReferences( for (const prop in propTree.props) {
section, if (!seen.has(prop)) {
tag.node, const propBinding = getOrCreatePropertyAlias(knownSpreadBinding, prop);
spreadReferenceNodes, const propExtra: ReferencedExtra = { section };
));
let spreadBinding = propTree.binding;
if (seen.size) {
spreadBinding = createBinding(
generateUid(`${getTagName(tag)}_attrs`),
spreadBinding.type,
spreadBinding.section,
spreadBinding,
undefined,
fromIter(seen),
spreadReferenceNodes[0].loc,
true,
);
}
setBindingDownstream(spreadBinding, extra); known[prop] = { value: propExtra };
rootAttrExprs.add(propExtra);
addRead(propExtra, propExtra, propBinding);
setBindingDownstream(propBinding, propExtra);
}
}
} else if (spreadReferenceNodes) {
setBindingDownstream(
seen.size
? createBinding(
generateUid(`${getTagName(tag)}_attrs`),
propTree.binding.type,
propTree.binding.section,
propTree.binding,
undefined,
fromIter(seen),
spreadReferenceNodes[0].loc,
true,
)
: propTree.binding,
(inputExpr.value = mergeReferences(
section,
tag.node,
spreadReferenceNodes,
)),
);
} }
if (propTree.rest) { if (propTree.rest) {
const extra = (inputExpr.value = mergeReferences( setBindingDownstream(
section, propTree.binding,
tag.node, (inputExpr.value = mergeReferences(
unknownReferences.flat(), section,
)); tag.node,
setBindingDownstream(propTree.binding, extra); unknownReferences.flat(),
)),
);
} else { } else {
unknownReferences.forEach(dropReferences); unknownReferences.forEach(dropReferences);
} }
@ -671,6 +699,22 @@ function analyzeAttrs(
return inputExpr; return inputExpr;
} }
function getSingleSpreadBinding(
attributes: (t.MarkoAttribute | t.MarkoSpreadAttribute)[],
) {
let binding: Binding | undefined;
for (let i = attributes.length; i--; ) {
const attr = attributes[i];
if (
attr.type === "MarkoSpreadAttribute" &&
(binding || !(binding = attr.value.extra?.spreadFrom))
) {
return;
}
}
return binding;
}
function writeParamsToSignals( function writeParamsToSignals(
tag: t.NodePath<t.MarkoTag>, tag: t.NodePath<t.MarkoTag>,
propTree: BindingPropTree, propTree: BindingPropTree,
@ -927,26 +971,36 @@ function writeAttrsToSignals(
} }
} }
const { attributes } = tag.node; let knownSpreadBinding: Binding | undefined;
const staticAttrs: t.MarkoAttribute[] = [];
let spreadProps: t.ObjectExpression["properties"] | undefined; let spreadProps: t.ObjectExpression["properties"] | undefined;
const staticAttrs: t.MarkoAttribute[] = [];
const knownSpreadDefaultAttrs = new Set<t.MarkoAttribute>();
const { attributes } = tag.node;
for (let i = attributes.length; i--; ) { for (let i = attributes.length; i--; ) {
const attr = attributes[i]; const attr = attributes[i];
if (t.isMarkoAttribute(attr)) { if (t.isMarkoAttribute(attr)) {
const childAttrExports = propTree.props[attr.name]; if (
if (!childAttrExports || seen.has(attr.name)) continue; !propTree.props[attr.name] ||
seen.has(attr.name) ||
if (spreadProps) { spreadProps?.push(toObjectProperty(attr.name, attr.value))
spreadProps.push(toObjectProperty(attr.name, attr.value)); ) {
continue; continue;
} }
seen.add(attr.name); seen.add(attr.name);
staticAttrs.push(attr); staticAttrs.push(attr);
if (knownSpreadBinding) {
knownSpreadDefaultAttrs.add(attr);
}
} else if (spreadProps) { } else if (spreadProps) {
spreadProps.push(t.spreadElement(attr.value)); spreadProps.push(t.spreadElement(attr.value));
} else { } else {
spreadProps = [t.spreadElement(attr.value)]; if (!propTree.rest) {
knownSpreadBinding = getSingleSpreadBinding(attributes);
}
if (!knownSpreadBinding) {
spreadProps = [t.spreadElement(attr.value)];
}
} }
} }
@ -963,7 +1017,16 @@ function writeAttrsToSignals(
t.expressionStatement( t.expressionStatement(
t.callExpression(attrExportIdentifier, [ t.callExpression(attrExportIdentifier, [
createScopeReadExpression(info.childScopeBinding, info.tagSection), createScopeReadExpression(info.childScopeBinding, info.tagSection),
attr.value, knownSpreadDefaultAttrs.has(attr)
? t.logicalExpression(
"??",
createScopeReadExpression(
knownSpreadBinding!.propertyAliases.get(attr.name)!,
info.tagSection,
),
attr.value,
)
: attr.value,
]), ]),
), ),
); );
@ -975,7 +1038,30 @@ function writeAttrsToSignals(
if (missing.size) { if (missing.size) {
const referencedBindings = tag.node.extra?.referencedBindings; const referencedBindings = tag.node.extra?.referencedBindings;
if (spreadProps) { if (knownSpreadBinding) {
for (const prop of missing) {
const childAttrExports = propTree.props[prop];
const attrExportIdentifier = info.getBindingIdentifier(
childAttrExports.binding,
`${importAlias}_${prop}`,
);
const propBinding = knownSpreadBinding!.propertyAliases.get(prop)!;
addStatement(
"render",
info.tagSection,
propBinding,
t.expressionStatement(
t.callExpression(attrExportIdentifier, [
createScopeReadExpression(
info.childScopeBinding,
info.tagSection,
),
createScopeReadExpression(propBinding, info.tagSection),
]),
),
);
}
} else if (spreadProps) {
const spreadExpr = propsToExpression( const spreadExpr = propsToExpression(
propTree.rest propTree.rest
? (translatedAttrs = translateAttrs(tag, propTree, seen)).properties ? (translatedAttrs = translateAttrs(tag, propTree, seen)).properties

View File

@ -116,11 +116,7 @@ export type Intersection = Many<Binding>;
interface ReferencedFunctionExtra extends t.FunctionExtra, ReferencedExtra {} interface ReferencedFunctionExtra extends t.FunctionExtra, ReferencedExtra {}
interface Read { interface Read {
binding: Binding; binding: Binding;
node: extra: t.NodeExtra | undefined;
| undefined
| t.Identifier
| t.MemberExpression
| t.OptionalMemberExpression;
} }
declare module "@marko/compiler/dist/types" { declare module "@marko/compiler/dist/types" {
@ -134,6 +130,7 @@ declare module "@marko/compiler/dist/types" {
read?: { binding: Binding; props: Opt<string> }; read?: { binding: Binding; props: Opt<string> };
pruned?: true; pruned?: true;
isEffect?: true; isEffect?: true;
spreadFrom?: Binding;
[kIsInvoked]?: true; [kIsInvoked]?: true;
} }
@ -206,6 +203,19 @@ export function createBinding(
return binding; return binding;
} }
export function getOrCreatePropertyAlias(binding: Binding, property: string) {
return (
binding.propertyAliases.get(property) ||
createBinding(
`${binding.name}_${property.replace(/[^a-zA-Z0-9_$]/g, "_")}`,
binding.type,
binding.section,
binding,
property,
)
);
}
export function trackDomVarReferences( export function trackDomVarReferences(
tag: t.NodePath<t.MarkoTag>, tag: t.NodePath<t.MarkoTag>,
binding: Binding, binding: Binding,
@ -694,7 +704,6 @@ function trackReference(
| t.NodePath<t.MemberExpression> | t.NodePath<t.MemberExpression>
| t.NodePath<t.OptionalMemberExpression> = referencePath; | t.NodePath<t.OptionalMemberExpression> = referencePath;
let reference = binding; let reference = binding;
let propPath = binding.name;
while (true) { while (true) {
const { parent } = root; const { parent } = root;
@ -712,15 +721,6 @@ function trackReference(
reference = reference.upstreamAlias; reference = reference.upstreamAlias;
} }
if (reference.propertyAliases.has(prop)) {
root = root.parentPath as
| t.NodePath<t.MemberExpression>
| t.NodePath<t.OptionalMemberExpression>;
reference = reference.propertyAliases.get(prop)!;
propPath = reference.name;
continue;
}
if (isInvokedFunction(root.parentPath) && !isEventOrChangeHandler(prop)) { if (isInvokedFunction(root.parentPath) && !isEventOrChangeHandler(prop)) {
break; break;
} }
@ -728,13 +728,8 @@ function trackReference(
root = root.parentPath as root = root.parentPath as
| t.NodePath<t.MemberExpression> | t.NodePath<t.MemberExpression>
| t.NodePath<t.OptionalMemberExpression>; | t.NodePath<t.OptionalMemberExpression>;
reference = createBinding(
(propPath += `_${prop.replace(/[^a-zA-Z0-9_$]/g, "_")}`), reference = getOrCreatePropertyAlias(reference, prop);
reference.type,
reference.section,
reference,
prop,
);
} }
addReadToExpression(root, reference); addReadToExpression(root, reference);
@ -1366,6 +1361,27 @@ const [getFunctionReadsByExpression] = createProgramState(
() => new Map<ReferencedExtra, Map<ReferencedFunctionExtra, OneMany<Read>>>(), () => new Map<ReferencedExtra, Map<ReferencedFunctionExtra, OneMany<Read>>>(),
); );
export function addRead(
exprExtra: ReferencedExtra,
readExtra: t.NodeExtra | undefined,
binding: Binding,
) {
const readsByExpression = getReadsByExpression();
const read: Read = {
binding,
extra: readExtra,
};
readsByExpression.set(
exprExtra,
push(readsByExpression.get(exprExtra), read),
);
return read;
}
export function dropRead(exprExtra: ReferencedExtra) {
getReadsByExpression().delete(exprExtra);
}
function addReadToExpression( function addReadToExpression(
root: root:
| t.NodePath<t.Identifier> | t.NodePath<t.Identifier>
@ -1377,13 +1393,12 @@ function addReadToExpression(
const fnRoot = getFnRoot(root); const fnRoot = getFnRoot(root);
const exprRoot = getExprRoot(fnRoot || root); const exprRoot = getExprRoot(fnRoot || root);
const exprExtra = (exprRoot.node.extra ??= {}) as ReferencedExtra; const exprExtra = (exprRoot.node.extra ??= {}) as ReferencedExtra;
const readsByExpression = getReadsByExpression();
const section = (exprExtra.section = getOrCreateSection(exprRoot)); const section = (exprExtra.section = getOrCreateSection(exprRoot));
const read: Read = { binding, node }; const read = addRead(exprExtra, (node.extra ??= {}), binding);
readsByExpression.set(
exprExtra, if (root.parent.type === "MarkoSpreadAttribute") {
push(readsByExpression.get(exprExtra), read), exprExtra.spreadFrom = binding;
); }
if (fnRoot) { if (fnRoot) {
const fnReadsByExpr = getFunctionReadsByExpression(); const fnReadsByExpr = getFunctionReadsByExpression();
@ -1777,8 +1792,8 @@ function resolveReferencedBindings(
const rootBindings = getRootBindings(reads); const rootBindings = getRootBindings(reads);
for (const read of reads) { for (const read of reads) {
let { binding } = read; let { binding } = read;
if (read.node) { const readExtra = read.extra;
const readExtra = (read.node.extra ??= {}); if (readExtra) {
if (readExtra.assignmentTo !== binding) { if (readExtra.assignmentTo !== binding) {
readExtra.section = expr.section; readExtra.section = expr.section;
({ binding } = readExtra.read ??= resolveExpressionReference( ({ binding } = readExtra.read ??= resolveExpressionReference(
@ -1790,10 +1805,9 @@ function resolveReferencedBindings(
referencedBindings = bindingUtil.add(referencedBindings, binding); referencedBindings = bindingUtil.add(referencedBindings, binding);
} }
} else if (reads) { } else if (reads) {
if (reads.node) { if (reads.extra) {
const readExtra = (reads.node.extra ??= {}); reads.extra.section = expr.section;
readExtra.section = expr.section; reads.extra.read = createRead(reads.binding, undefined);
readExtra.read = createRead(reads.binding, undefined);
} }
referencedBindings = reads.binding; referencedBindings = reads.binding;
} }