diff --git a/.changeset/ts-optimizer-captured-prop-tags.md b/.changeset/ts-optimizer-captured-prop-tags.md
new file mode 100644
index 00000000000..1080a42e29b
--- /dev/null
+++ b/.changeset/ts-optimizer-captured-prop-tags.md
@@ -0,0 +1,5 @@
+---
+'@qwik.dev/core': patch
+---
+
+fix: the TypeScript optimizer now resolves destructured props used as JSX tags inside `$` callbacks, like `onClick$={() => render( )}`
diff --git a/.changeset/ts-optimizer-destructured-prop-tags.md b/.changeset/ts-optimizer-destructured-prop-tags.md
new file mode 100644
index 00000000000..8633b45da5f
--- /dev/null
+++ b/.changeset/ts-optimizer-destructured-prop-tags.md
@@ -0,0 +1,5 @@
+---
+'@qwik.dev/core': patch
+---
+
+fix: the TypeScript optimizer no longer fails when a destructured prop with a default or a string key is used as a JSX tag
diff --git a/.ruler/skills/qwik-optimizer-development/SKILL.md b/.ruler/skills/qwik-optimizer-development/SKILL.md
index 2270051f407..434191aaced 100644
--- a/.ruler/skills/qwik-optimizer-development/SKILL.md
+++ b/.ruler/skills/qwik-optimizer-development/SKILL.md
@@ -87,6 +87,10 @@ starting with `[a-z]` is an intrinsic element, while ``, `<_Foo>` and the o
(or `isNonReferenceIdentifier`) whether a `JSXIdentifier` reads a binding, and `isHtmlElement`
whether a tag is intrinsic.
+A pass that rewrites reads of a binding must rewrite its JSX tag reads too. A tag holds only a
+member chain, so a read with a default, a quoted key or a conditional waits for the JSX transform:
+return it as `DeferredTagReads` and apply `resolveDeferredTagReads` (`rewrite/raw-props.ts`) after it.
+
## Worker pool changes
The transform worker pool (`packages/ts-optimizer/src/worker-pool.ts`) never starts real workers
diff --git a/packages/optimizer/core/src/snapshots/qwik_core__test__captured_props_as_jsx_tags.snap b/packages/optimizer/core/src/snapshots/qwik_core__test__captured_props_as_jsx_tags.snap
new file mode 100644
index 00000000000..5311448754d
--- /dev/null
+++ b/packages/optimizer/core/src/snapshots/qwik_core__test__captured_props_as_jsx_tags.snap
@@ -0,0 +1,200 @@
+---
+source: packages/optimizer/core/src/test.rs
+expression: output
+---
+==INPUT==
+
+
+import { component$ } from '@qwik.dev/core';
+import { Fallback, fallback, render } from './x';
+
+export const Cmp = component$(({ Model = Fallback }: { Model?: any }) => {
+ return render( )} />;
+});
+
+export const Cmp2 = component$(
+ ({ Model, ui = fallback, icons, 'my-icon': Icon }: { Model: any; ui?: any; icons: any; 'my-icon': any }) => {
+ return (
+
+ render(
+
+
+
+
+
+ )
+ }
+ />
+ );
+ }
+);
+
+============================= test.js ==
+
+import { componentQrl } from "@qwik.dev/core";
+import { qrl } from "@qwik.dev/core";
+//
+const q_Cmp2_component_FnOz26iMYaM = /*#__PURE__*/ qrl(()=>import("./test.tsx_Cmp2_component_FnOz26iMYaM"), "Cmp2_component_FnOz26iMYaM");
+const q_Cmp_component_4ryKJTOKjWE = /*#__PURE__*/ qrl(()=>import("./test.tsx_Cmp_component_4ryKJTOKjWE"), "Cmp_component_4ryKJTOKjWE");
+//
+export const Cmp = /*#__PURE__*/ componentQrl(q_Cmp_component_4ryKJTOKjWE);
+export const Cmp2 = /*#__PURE__*/ componentQrl(q_Cmp2_component_FnOz26iMYaM);
+
+
+Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;;;;;AAIA,OAAO,MAAM,oBAAM,0CAEhB;AAEH,OAAO,MAAM,qBAAO,2CAgBlB\"}")
+============================= test.tsx_Cmp_component_button_q_e_click_0O0tA6ahzDQ.js (ENTRY POINT)==
+
+import { Fallback } from "./x";
+import { _jsxSorted } from "@qwik.dev/core";
+import { render } from "./x";
+//
+export const Cmp_component_button_q_e_click_0O0tA6ahzDQ = (_, _1, _rawProps)=>render(/*#__PURE__*/ _jsxSorted(_rawProps.Model ?? Fallback, null, null, null, 3, "u6_0"));
+
+
+Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;;;0DAK0B,oBAAM,qBAAO,qBADN,SAAQ\"}")
+/*
+{
+ "origin": "test.tsx",
+ "name": "Cmp_component_button_q_e_click_0O0tA6ahzDQ",
+ "entry": null,
+ "displayName": "test.tsx_Cmp_component_button_q_e_click",
+ "hash": "0O0tA6ahzDQ",
+ "canonicalFilename": "test.tsx_Cmp_component_button_q_e_click_0O0tA6ahzDQ",
+ "path": "",
+ "extension": "js",
+ "parent": "Cmp_component_4ryKJTOKjWE",
+ "ctxKind": "eventHandler",
+ "ctxName": "onClick$",
+ "captures": false,
+ "loc": [
+ 199,
+ 222
+ ],
+ "paramNames": [
+ "_",
+ "_1",
+ "_rawProps"
+ ]
+}
+*/
+============================= test.tsx_Cmp2_component_button_q_e_click_lRw2YGQT0z8.js (ENTRY POINT)==
+
+import { _jsxSorted } from "@qwik.dev/core";
+import { fallback } from "./x";
+import { render } from "./x";
+//
+export const Cmp2_component_button_q_e_click_lRw2YGQT0z8 = (_, _1, _rawProps)=>render(/*#__PURE__*/ _jsxSorted(_rawProps.Model, null, null, [
+ /*#__PURE__*/ _jsxSorted((_rawProps.ui ?? fallback).Home, null, null, null, 3, "u6_2"),
+ /*#__PURE__*/ _jsxSorted(_rawProps.icons.Star, null, null, null, 3, "u6_3"),
+ /*#__PURE__*/ _jsxSorted(_rawProps["my-icon"], null, null, null, 3, "u6_4")
+ ], 1, "u6_5"));
+
+
+Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;;;2DAYc,oBACT,qBACC,qBALF;sBAMG,WAAC,WANG,MAAK,UAML,IAAI;sBACR,WAAC,UAPkB,MAOZ,IAAI;sBACX\"}")
+/*
+{
+ "origin": "test.tsx",
+ "name": "Cmp2_component_button_q_e_click_lRw2YGQT0z8",
+ "entry": null,
+ "displayName": "test.tsx_Cmp2_component_button_q_e_click",
+ "hash": "lRw2YGQT0z8",
+ "canonicalFilename": "test.tsx_Cmp2_component_button_q_e_click_lRw2YGQT0z8",
+ "path": "",
+ "extension": "js",
+ "parent": "Cmp2_component_FnOz26iMYaM",
+ "ctxKind": "eventHandler",
+ "ctxName": "onClick$",
+ "captures": false,
+ "loc": [
+ 412,
+ 523
+ ],
+ "paramNames": [
+ "_",
+ "_1",
+ "_rawProps"
+ ]
+}
+*/
+============================= test.tsx_Cmp_component_4ryKJTOKjWE.js (ENTRY POINT)==
+
+import { _jsxSorted } from "@qwik.dev/core";
+import { qrl } from "@qwik.dev/core";
+//
+const q_Cmp_component_button_q_e_click_0O0tA6ahzDQ = /*#__PURE__*/ qrl(()=>import("./test.tsx_Cmp_component_button_q_e_click_0O0tA6ahzDQ"), "Cmp_component_button_q_e_click_0O0tA6ahzDQ").m();
+//
+export const Cmp_component_4ryKJTOKjWE = (_rawProps)=>{
+ return /*#__PURE__*/ _jsxSorted("button", {
+ "q-e:click": q_Cmp_component_button_q_e_click_0O0tA6ahzDQ,
+ "q:p": _rawProps
+ }, null, null, 6, "u6_1");
+};
+
+
+Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;;;;yCAI8B;IAC7B,qBAAO,WAAC;QAAO,WAAQ;;;AACxB\"}")
+/*
+{
+ "origin": "test.tsx",
+ "name": "Cmp_component_4ryKJTOKjWE",
+ "entry": null,
+ "displayName": "test.tsx_Cmp_component",
+ "hash": "4ryKJTOKjWE",
+ "canonicalFilename": "test.tsx_Cmp_component_4ryKJTOKjWE",
+ "path": "",
+ "extension": "js",
+ "parent": null,
+ "ctxKind": "function",
+ "ctxName": "component$",
+ "captures": false,
+ "loc": [
+ 128,
+ 229
+ ],
+ "paramNames": [
+ "_rawProps"
+ ]
+}
+*/
+============================= test.tsx_Cmp2_component_FnOz26iMYaM.js (ENTRY POINT)==
+
+import { _jsxSorted } from "@qwik.dev/core";
+import { qrl } from "@qwik.dev/core";
+//
+const q_Cmp2_component_button_q_e_click_lRw2YGQT0z8 = /*#__PURE__*/ qrl(()=>import("./test.tsx_Cmp2_component_button_q_e_click_lRw2YGQT0z8"), "Cmp2_component_button_q_e_click_lRw2YGQT0z8").m();
+//
+export const Cmp2_component_FnOz26iMYaM = (_rawProps)=>{
+ return /*#__PURE__*/ _jsxSorted("button", {
+ "q-e:click": q_Cmp2_component_button_q_e_click_lRw2YGQT0z8,
+ "q:p": _rawProps
+ }, null, null, 6, "u6_6");
+};
+
+
+Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;;;;0CASC;IACC,qBACC,WAAC;QACA,WAAQ;;;AAWX\"}")
+/*
+{
+ "origin": "test.tsx",
+ "name": "Cmp2_component_FnOz26iMYaM",
+ "entry": null,
+ "displayName": "test.tsx_Cmp2_component",
+ "hash": "FnOz26iMYaM",
+ "canonicalFilename": "test.tsx_Cmp2_component_FnOz26iMYaM",
+ "path": "",
+ "extension": "js",
+ "parent": null,
+ "ctxKind": "function",
+ "ctxName": "component$",
+ "captures": false,
+ "loc": [
+ 266,
+ 543
+ ],
+ "paramNames": [
+ "_rawProps"
+ ]
+}
+*/
+== DIAGNOSTICS ==
+
+[]
diff --git a/packages/optimizer/core/src/snapshots/qwik_core__test__captured_props_as_jsx_tags_inline.snap b/packages/optimizer/core/src/snapshots/qwik_core__test__captured_props_as_jsx_tags_inline.snap
new file mode 100644
index 00000000000..9ca78e0c8e3
--- /dev/null
+++ b/packages/optimizer/core/src/snapshots/qwik_core__test__captured_props_as_jsx_tags_inline.snap
@@ -0,0 +1,73 @@
+---
+source: packages/optimizer/core/src/test.rs
+expression: output
+---
+==INPUT==
+
+
+import { $, component$ } from '@qwik.dev/core';
+import { Fallback, fallback, render } from './x';
+
+export const Cmp = component$(
+ ({
+ Model = Fallback,
+ Layout,
+ ui = fallback,
+ icons,
+ 'my-icon': Icon,
+ }: {
+ Model?: any;
+ Layout: any;
+ ui?: any;
+ icons: any;
+ 'my-icon': any;
+ }) => {
+ const view = $(() =>
+ render(
+
+
+
+
+
+
+ )
+ );
+ return ;
+ }
+);
+
+============================= test.js ==
+
+import { componentQrl } from "@qwik.dev/core";
+import { _jsxSorted } from "@qwik.dev/core";
+import { _capturesObj } from "@qwik.dev/core";
+import { _noopQrl } from "@qwik.dev/core";
+import { Fallback, fallback, render } from './x';
+//
+const q_Cmp_component_4ryKJTOKjWE = /*#__PURE__*/ _noopQrl("Cmp_component_4ryKJTOKjWE");
+const q_Cmp_component_view_ITAz7Hgj10g = /*#__PURE__*/ _noopQrl("Cmp_component_view_ITAz7Hgj10g");
+//
+q_Cmp_component_view_ITAz7Hgj10g.s(()=>{
+ let _rawProps = _capturesObj._[0];
+ return render(/*#__PURE__*/ _jsxSorted(_rawProps.Layout, null, null, [
+ /*#__PURE__*/ _jsxSorted(_rawProps.Model ?? Fallback, null, null, null, 3, "u6_0"),
+ /*#__PURE__*/ _jsxSorted((_rawProps.ui ?? fallback).Home, null, null, null, 3, "u6_1"),
+ /*#__PURE__*/ _jsxSorted(_rawProps.icons.Star, null, null, null, 3, "u6_2"),
+ /*#__PURE__*/ _jsxSorted(_rawProps["my-icon"], null, null, null, 3, "u6_3")
+ ], 1, "u6_4"));
+});
+q_Cmp_component_4ryKJTOKjWE.s((_rawProps)=>{
+ const view = q_Cmp_component_view_ITAz7Hgj10g.w([
+ _rawProps
+ ]);
+ return /*#__PURE__*/ _jsxSorted("button", null, {
+ "q-e:click": view
+ }, null, 3, "u6_5");
+});
+export const Cmp = /*#__PURE__*/ componentQrl(q_Cmp_component_4ryKJTOKjWE);
+
+
+Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;;;AAEA,SAAS,QAAQ,EAAE,QAAQ,EAAE,MAAM,QAAQ,MAAM;;;;;mCAgBhC;;WACd,qBACC,qBAbF;sBAcG,qBAfH,SAAQ;sBAgBL,WAAC,WAdJ,MAAK,UAcE,IAAI;sBACR,WAAC,UAdJ,MAcU,IAAI;sBACX;;;8BAnBJ;IAaC,MAAM;;;IAUN,qBAAO,WAAC;QAAO,aAAU;;AAC1B;AAzBD,OAAO,MAAM,oBAAM,0CA0BjB\"}")
+== DIAGNOSTICS ==
+
+[]
diff --git a/packages/optimizer/core/src/snapshots/qwik_core__test__destructured_props_as_jsx_tags.snap b/packages/optimizer/core/src/snapshots/qwik_core__test__destructured_props_as_jsx_tags.snap
new file mode 100644
index 00000000000..8a51caa9a0d
--- /dev/null
+++ b/packages/optimizer/core/src/snapshots/qwik_core__test__destructured_props_as_jsx_tags.snap
@@ -0,0 +1,116 @@
+---
+source: packages/optimizer/core/src/test.rs
+expression: output
+---
+==INPUT==
+
+
+import { component$ } from '@qwik.dev/core';
+import { Fallback, fallback, getFallback } from './x';
+
+export const Cmp = component$((props: { Model?: any; ui?: any; icons: any; 'my-icon': any }) => {
+ const { Model = Fallback, ui = fallback, icons, 'my-icon': Icon } = props;
+ return (
+
+
+
+
+
+ );
+});
+
+export const Cmp2 = component$(({ Model = getFallback() }: { Model?: any }) => {
+ return ;
+});
+
+============================= test.js ==
+
+import { componentQrl } from "@qwik.dev/core";
+import { qrl } from "@qwik.dev/core";
+//
+const q_Cmp2_component_FnOz26iMYaM = /*#__PURE__*/ qrl(()=>import("./test.tsx_Cmp2_component_FnOz26iMYaM"), "Cmp2_component_FnOz26iMYaM");
+const q_Cmp_component_4ryKJTOKjWE = /*#__PURE__*/ qrl(()=>import("./test.tsx_Cmp_component_4ryKJTOKjWE"), "Cmp_component_4ryKJTOKjWE");
+//
+export const Cmp = /*#__PURE__*/ componentQrl(q_Cmp_component_4ryKJTOKjWE);
+export const Cmp2 = /*#__PURE__*/ componentQrl(q_Cmp2_component_FnOz26iMYaM);
+
+
+Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;;;;;AAIA,OAAO,MAAM,oBAAM,0CAShB;AAEH,OAAO,MAAM,qBAAO,2CAEjB\"}")
+============================= test.tsx_Cmp_component_4ryKJTOKjWE.js (ENTRY POINT)==
+
+import { Fallback } from "./x";
+import { _jsxSorted } from "@qwik.dev/core";
+import { fallback } from "./x";
+//
+export const Cmp_component_4ryKJTOKjWE = (props)=>{
+ return /*#__PURE__*/ _jsxSorted(props.Model ?? Fallback, null, null, [
+ /*#__PURE__*/ _jsxSorted((props.ui ?? fallback).Home, null, null, null, 3, "u6_0"),
+ /*#__PURE__*/ _jsxSorted(props.icons.Star, null, null, null, 3, "u6_1"),
+ /*#__PURE__*/ _jsxSorted(props["my-icon"], null, null, null, 3, "u6_2")
+ ], 1, "u6_3");
+};
+
+
+Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;;;yCAI8B,CAAC;IAE9B,qBACC,WAFmE,MAA5D,SAAQ;sBAGd,WAAC,CAHiE,MAA1C,MAAK,UAGzB,IAAI;sBACR,WAAC,AAJiE,MAA3B,MAIhC,IAAI;sBACX,WALkE;;AAQrE\"}")
+/*
+{
+ "origin": "test.tsx",
+ "name": "Cmp_component_4ryKJTOKjWE",
+ "entry": null,
+ "displayName": "test.tsx_Cmp_component",
+ "hash": "4ryKJTOKjWE",
+ "canonicalFilename": "test.tsx_Cmp_component_4ryKJTOKjWE",
+ "path": "",
+ "extension": "js",
+ "parent": null,
+ "ctxKind": "function",
+ "ctxName": "component$",
+ "captures": false,
+ "loc": [
+ 133,
+ 358
+ ],
+ "paramNames": [
+ "props"
+ ]
+}
+*/
+============================= test.tsx_Cmp2_component_FnOz26iMYaM.js (ENTRY POINT)==
+
+import { _jsxSorted } from "@qwik.dev/core";
+import { getFallback } from "./x";
+import { untrack } from "@qwik.dev/core";
+//
+export const Cmp2_component_FnOz26iMYaM = (_rawProps)=>{
+ const _defaultValue = untrack(()=>_rawProps.Model) === void 0 ? getFallback() : void 0;
+ return /*#__PURE__*/ _jsxSorted(_rawProps.Model === void 0 ? _defaultValue : _rawProps.Model, null, null, null, 3, "u6_4");
+};
+
+
+Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;;;0CAe+B;gDAAG,oBAAQ;IACzC,qBAAO,qBAD0B,6CAAA;AAElC\"}")
+/*
+{
+ "origin": "test.tsx",
+ "name": "Cmp2_component_FnOz26iMYaM",
+ "entry": null,
+ "displayName": "test.tsx_Cmp2_component",
+ "hash": "FnOz26iMYaM",
+ "canonicalFilename": "test.tsx_Cmp2_component_FnOz26iMYaM",
+ "path": "",
+ "extension": "js",
+ "parent": null,
+ "ctxKind": "function",
+ "ctxName": "component$",
+ "captures": false,
+ "loc": [
+ 393,
+ 463
+ ],
+ "paramNames": [
+ "_rawProps"
+ ]
+}
+*/
+== DIAGNOSTICS ==
+
+[]
diff --git a/packages/optimizer/core/src/snapshots/qwik_core__test__destructured_props_as_jsx_tags_inline.snap b/packages/optimizer/core/src/snapshots/qwik_core__test__destructured_props_as_jsx_tags_inline.snap
new file mode 100644
index 00000000000..02196664bbf
--- /dev/null
+++ b/packages/optimizer/core/src/snapshots/qwik_core__test__destructured_props_as_jsx_tags_inline.snap
@@ -0,0 +1,55 @@
+---
+source: packages/optimizer/core/src/test.rs
+expression: output
+---
+==INPUT==
+
+
+import { component$ } from '@qwik.dev/core';
+import { Fallback, fallback, getFallback } from './x';
+
+export const Cmp = component$((props: { Model?: any; ui?: any; icons: any; 'my-icon': any }) => {
+ const { Model = Fallback, ui = fallback, icons, 'my-icon': Icon } = props;
+ return (
+
+
+
+
+
+ );
+});
+
+export const Cmp2 = component$(({ Model = getFallback() }: { Model?: any }) => {
+ return ;
+});
+
+============================= test.js ==
+
+import { untrack } from "@qwik.dev/core";
+import { componentQrl } from "@qwik.dev/core";
+import { _jsxSorted } from "@qwik.dev/core";
+import { _noopQrl } from "@qwik.dev/core";
+import { Fallback, fallback, getFallback } from './x';
+//
+const q_Cmp2_component_FnOz26iMYaM = /*#__PURE__*/ _noopQrl("Cmp2_component_FnOz26iMYaM");
+const q_Cmp_component_4ryKJTOKjWE = /*#__PURE__*/ _noopQrl("Cmp_component_4ryKJTOKjWE");
+//
+q_Cmp_component_4ryKJTOKjWE.s((props)=>{
+ return /*#__PURE__*/ _jsxSorted(props.Model ?? Fallback, null, null, [
+ /*#__PURE__*/ _jsxSorted((props.ui ?? fallback).Home, null, null, null, 3, "u6_0"),
+ /*#__PURE__*/ _jsxSorted(props.icons.Star, null, null, null, 3, "u6_1"),
+ /*#__PURE__*/ _jsxSorted(props["my-icon"], null, null, null, 3, "u6_2")
+ ], 1, "u6_3");
+});
+q_Cmp2_component_FnOz26iMYaM.s((_rawProps)=>{
+ const _defaultValue = untrack(()=>_rawProps.Model) === void 0 ? getFallback() : void 0;
+ return /*#__PURE__*/ _jsxSorted(_rawProps.Model === void 0 ? _defaultValue : _rawProps.Model, null, null, null, 3, "u6_4");
+});
+export const Cmp = /*#__PURE__*/ componentQrl(q_Cmp_component_4ryKJTOKjWE);
+export const Cmp2 = /*#__PURE__*/ componentQrl(q_Cmp2_component_FnOz26iMYaM);
+
+
+Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;;;AAEA,SAAS,QAAQ,EAAE,QAAQ,EAAE,WAAW,QAAQ,MAAM;;;;;8BAExB,CAAC;IAE9B,qBACC,WAFmE,MAA5D,SAAQ;sBAGd,WAAC,CAHiE,MAA1C,MAAK,UAGzB,IAAI;sBACR,WAAC,AAJiE,MAA3B,MAIhC,IAAI;sBACX,WALkE;;AAQrE;+BAE+B;gDAAG,oBAAQ;IACzC,qBAAO,qBAD0B,6CAAA;AAElC;AAbA,OAAO,MAAM,oBAAM,0CAShB;AAEH,OAAO,MAAM,qBAAO,2CAEjB\"}")
+== DIAGNOSTICS ==
+
+[]
diff --git a/packages/optimizer/core/src/test.rs b/packages/optimizer/core/src/test.rs
index 02e7da989e7..e397f1b557f 100644
--- a/packages/optimizer/core/src/test.rs
+++ b/packages/optimizer/core/src/test.rs
@@ -724,6 +724,144 @@ export const Cmp = component$((props: { flag?: boolean; className?: string }) =>
});
}
+#[test]
+fn destructured_props_as_jsx_tags() {
+ test_input!(TestInput {
+ code: r#"
+import { component$ } from '@qwik.dev/core';
+import { Fallback, fallback, getFallback } from './x';
+
+export const Cmp = component$((props: { Model?: any; ui?: any; icons: any; 'my-icon': any }) => {
+ const { Model = Fallback, ui = fallback, icons, 'my-icon': Icon } = props;
+ return (
+
+
+
+
+
+ );
+});
+
+export const Cmp2 = component$(({ Model = getFallback() }: { Model?: any }) => {
+ return ;
+});
+"#
+ .to_string(),
+ transpile_jsx: true,
+ transpile_ts: true,
+ ..TestInput::default()
+ });
+}
+
+#[test]
+fn destructured_props_as_jsx_tags_inline() {
+ test_input!(TestInput {
+ code: r#"
+import { component$ } from '@qwik.dev/core';
+import { Fallback, fallback, getFallback } from './x';
+
+export const Cmp = component$((props: { Model?: any; ui?: any; icons: any; 'my-icon': any }) => {
+ const { Model = Fallback, ui = fallback, icons, 'my-icon': Icon } = props;
+ return (
+
+
+
+
+
+ );
+});
+
+export const Cmp2 = component$(({ Model = getFallback() }: { Model?: any }) => {
+ return ;
+});
+"#
+ .to_string(),
+ transpile_jsx: true,
+ transpile_ts: true,
+ entry_strategy: EntryStrategy::Inline,
+ ..TestInput::default()
+ });
+}
+
+#[test]
+fn captured_props_as_jsx_tags() {
+ test_input!(TestInput {
+ code: r#"
+import { component$ } from '@qwik.dev/core';
+import { Fallback, fallback, render } from './x';
+
+export const Cmp = component$(({ Model = Fallback }: { Model?: any }) => {
+ return render( )} />;
+});
+
+export const Cmp2 = component$(
+ ({ Model, ui = fallback, icons, 'my-icon': Icon }: { Model: any; ui?: any; icons: any; 'my-icon': any }) => {
+ return (
+
+ render(
+
+
+
+
+
+ )
+ }
+ />
+ );
+ }
+);
+"#
+ .to_string(),
+ transpile_jsx: true,
+ transpile_ts: true,
+ ..TestInput::default()
+ });
+}
+
+#[test]
+fn captured_props_as_jsx_tags_inline() {
+ test_input!(TestInput {
+ code: r#"
+import { $, component$ } from '@qwik.dev/core';
+import { Fallback, fallback, render } from './x';
+
+export const Cmp = component$(
+ ({
+ Model = Fallback,
+ Layout,
+ ui = fallback,
+ icons,
+ 'my-icon': Icon,
+ }: {
+ Model?: any;
+ Layout: any;
+ ui?: any;
+ icons: any;
+ 'my-icon': any;
+ }) => {
+ const view = $(() =>
+ render(
+
+
+
+
+
+
+ )
+ );
+ return ;
+ }
+);
+"#
+ .to_string(),
+ transpile_jsx: true,
+ transpile_ts: true,
+ entry_strategy: EntryStrategy::Inline,
+ ..TestInput::default()
+ });
+}
+
#[test]
fn local_shadowing_destructured_prop() {
test_input!(TestInput {
diff --git a/packages/ts-optimizer/src/optimizer/rewrite/index.ts b/packages/ts-optimizer/src/optimizer/rewrite/index.ts
index 9eacfe92012..e171fb4b773 100644
--- a/packages/ts-optimizer/src/optimizer/rewrite/index.ts
+++ b/packages/ts-optimizer/src/optimizer/rewrite/index.ts
@@ -85,9 +85,12 @@ export {
consolidateRawPropsCaptures,
groupPropsFieldsByBinding,
rawPropsBindingNames,
+ resolveDeferredTagReads,
resolveRawPropsSlots,
+ type DeferredTagReads,
type RawPropsConsolidation,
type RawPropsSource,
+ type RawPropsTransformResult,
} from './raw-props.js';
import {
diff --git a/packages/ts-optimizer/src/optimizer/rewrite/inline-body.ts b/packages/ts-optimizer/src/optimizer/rewrite/inline-body.ts
index ca44f040492..066a7ef771c 100644
--- a/packages/ts-optimizer/src/optimizer/rewrite/inline-body.ts
+++ b/packages/ts-optimizer/src/optimizer/rewrite/inline-body.ts
@@ -34,9 +34,11 @@ import {
bodyConsolidatesToRawProps,
groupPropsFieldsByBinding,
rawPropsBindingNames,
+ resolveDeferredTagReads,
resolveRawPropsSlots,
consolidateQpCaptureValues,
extractDestructuredFieldInfo,
+ type DeferredTagReads,
type InlineSegmentJsxOptions,
} from './raw-props.js';
import {
@@ -372,6 +374,7 @@ export function transformInlineSegmentBody(
additionalImports.set('_capturesObj', '@qwik.dev/core');
}
}
+ const deferredTagReads: DeferredTagReads[] = [];
{
// Consolidate any destructured first param (component props AND hook
// contexts like useComputed$'s { cleanup }) — a bare destructured method
@@ -382,9 +385,12 @@ export function transformInlineSegmentBody(
rawPropsInfo?.fieldDynamicDefaults,
bindingNames.get(ext.symbolName)
)
- : body;
- if (rawPropsResult !== body) {
- body = rawPropsResult;
+ : { code: body };
+ if (rawPropsResult.code !== body) {
+ body = rawPropsResult.code;
+ if (rawPropsResult.deferredTagReads) {
+ deferredTagReads.push(rawPropsResult.deferredTagReads);
+ }
if (body.includes('_restProps(')) {
additionalImports.set('_restProps', '@qwik.dev/core');
}
@@ -402,13 +408,17 @@ export function transformInlineSegmentBody(
bindingNames
);
for (const [propsName, fields] of groups) {
- body = replacePropsFieldReferencesInBody(
+ const rewritten = replacePropsFieldReferencesInBody(
body,
fields,
propsName,
ext.propsFieldDefaults,
ext.propsFieldDynamicDefaults
);
+ body = rewritten.code;
+ if (rewritten.deferredTagReads) {
+ deferredTagReads.push(rewritten.deferredTagReads);
+ }
}
}
@@ -618,6 +628,7 @@ export function transformInlineSegmentBody(
if (body.endsWith(';') && !ext.bodyText.endsWith(';')) {
body = body.slice(0, -1);
}
+ body = resolveDeferredTagReads(body, deferredTagReads);
for (const sym of bodyJsxResult.neededImports) {
additionalImports.set(sym, '@qwik.dev/core');
diff --git a/packages/ts-optimizer/src/optimizer/rewrite/props-field-rewrite.ts b/packages/ts-optimizer/src/optimizer/rewrite/props-field-rewrite.ts
index 4e48f4001ee..b82c296f09f 100644
--- a/packages/ts-optimizer/src/optimizer/rewrite/props-field-rewrite.ts
+++ b/packages/ts-optimizer/src/optimizer/rewrite/props-field-rewrite.ts
@@ -1,4 +1,5 @@
-import { buildPropertyAccessor } from '../ast/identifier-name.js';
+import { isReferenceIdentifier } from 'oxc-walker';
+import { buildPropertyAccessor, isSimpleIdentifierName } from '../ast/identifier-name.js';
import {
applyReplacements,
collectRangeReplacements,
@@ -7,6 +8,7 @@ import {
type RangeReplacementCollector,
} from '../edit/range-replace.js';
import { createTransformSession } from '../edit/transform-session.js';
+import type { DeferredTagReads, RawPropsTransformResult } from './raw-props.js';
interface RewritePropsFieldReferencesOptions {
memberPropertyMode?: 'all' | 'nonComputed';
@@ -29,9 +31,33 @@ function propsFieldIdentifierCollector(
defaultValues: ReadonlyMap | undefined,
dynamicDefaults: ReadonlyMap | undefined,
memberPropertyMode: 'all' | 'nonComputed' | undefined,
- propsName: string
+ propsName: string,
+ deferredTagFields: Map
): RangeReplacementCollector {
return (node, ctx) => {
+ if (node.type === 'JSXIdentifier') {
+ const tagKey = fieldMap.get(node.name);
+ if (tagKey === undefined || !isReferenceIdentifier(node, ctx.parentNode ?? null)) {
+ return null;
+ }
+ const isMemberChain =
+ !defaultValues?.has(node.name) &&
+ !dynamicDefaults?.has(node.name) &&
+ isSimpleIdentifierName(tagKey);
+ if (!isMemberChain) {
+ deferredTagFields.set(node.name, tagKey);
+ return null;
+ }
+ return {
+ replacements: [
+ {
+ start: node.start - ctx.exprStart,
+ end: node.end - ctx.exprStart,
+ replacement: buildPropertyAccessor(propsName, tagKey),
+ },
+ ],
+ };
+ }
if (node.type !== 'Identifier') {
return null;
}
@@ -95,38 +121,53 @@ export function rewritePropsFieldReferences(
bodyText: string,
fieldMap: Map,
options: RewritePropsFieldReferencesOptions
-): string {
+): RawPropsTransformResult {
if (fieldMap.size === 0) {
- return bodyText;
+ return { code: bodyText };
}
let session;
try {
session = createTransformSession(bodyText);
} catch {
- return bodyText;
+ return { code: bodyText };
}
if (!session) {
- return bodyText;
+ return { code: bodyText };
}
const { offset, program, wrappedSource } = session;
+ const propsName = options.propsName ?? '_rawProps';
+ const deferredTagFields = new Map();
const collector = propsFieldIdentifierCollector(
fieldMap,
options.defaultValues,
options.dynamicDefaults,
options.memberPropertyMode,
- options.propsName ?? '_rawProps'
+ propsName,
+ deferredTagFields
);
// Ranges are relative to `wrappedSource`; slicing off the wrapper prefix
// yields the original body's edited form.
const replacements = collectRangeReplacements(program, 0, wrappedSource, [collector]);
+ const deferredTagReads: DeferredTagReads | undefined =
+ deferredTagFields.size > 0
+ ? {
+ baseName: propsName,
+ fieldLocalToKey: deferredTagFields,
+ fieldLocalToDefault: options.defaultValues ?? new Map(),
+ fieldLocalToDynamicDefault: options.dynamicDefaults ?? new Map(),
+ }
+ : undefined;
if (replacements.length === 0) {
- return bodyText;
+ return { code: bodyText, deferredTagReads };
}
const edited = applyReplacements(wrappedSource, replacements);
- return edited.slice(offset, edited.length - session.wrapperSuffix.length);
+ return {
+ code: edited.slice(offset, edited.length - session.wrapperSuffix.length),
+ deferredTagReads,
+ };
}
diff --git a/packages/ts-optimizer/src/optimizer/rewrite/raw-props.ts b/packages/ts-optimizer/src/optimizer/rewrite/raw-props.ts
index dcbfd865be3..dc969e09ce8 100644
--- a/packages/ts-optimizer/src/optimizer/rewrite/raw-props.ts
+++ b/packages/ts-optimizer/src/optimizer/rewrite/raw-props.ts
@@ -10,7 +10,7 @@ import type {
AstMaybeNode,
AstNode,
} from '../../ast-types.js';
-import { buildPropertyAccessor } from '../ast/identifier-name.js';
+import { buildPropertyAccessor, isSimpleIdentifierName } from '../ast/identifier-name.js';
import { rewritePropsFieldReferences } from './props-field-rewrite.js';
import {
forEachAstChild,
@@ -94,6 +94,19 @@ interface IdentifierReplacement {
local: string;
isShorthand?: boolean;
needsParens?: boolean;
+ isJsxTagName?: boolean;
+}
+
+export interface DeferredTagReads {
+ readonly baseName: string;
+ readonly fieldLocalToKey: Map;
+ readonly fieldLocalToDefault: ReadonlyMap;
+ readonly fieldLocalToDynamicDefault: ReadonlyMap;
+}
+
+export interface RawPropsTransformResult {
+ readonly code: string;
+ readonly deferredTagReads?: DeferredTagReads;
}
interface RawPropsField {
@@ -717,6 +730,7 @@ function buildIdentifierReplacementsCollector(
local: node.name,
// JSX tag position does not need parens around a MemberExpression.
needsParens: false,
+ isJsxTagName: true,
});
}
return { replacements: [] };
@@ -812,11 +826,20 @@ function applyIdentifierReplacements(
baseName: string,
defaultValues: ReadonlyMap,
dynamicDefaults: ReadonlyMap
-): void {
+): Map {
+ const deferredTagFields = new Map();
for (const replacement of replacements) {
const baseAccessor = buildPropertyAccessor(baseName, replacement.key);
const defaultValue = defaultValues?.get(replacement.local);
const dynamicDefaultName = dynamicDefaults.get(replacement.local);
+ const isMemberChain =
+ defaultValue === undefined &&
+ dynamicDefaultName === undefined &&
+ isSimpleIdentifierName(replacement.key);
+ if (replacement.isJsxTagName && !isMemberChain) {
+ deferredTagFields.set(replacement.local, replacement.key);
+ continue;
+ }
// Parens only when parent precedence requires them (via `replacement.needsParens`).
let accessor: string;
if (dynamicDefaultName !== undefined) {
@@ -838,6 +861,30 @@ function applyIdentifierReplacements(
session.edits.overwrite(start, end, accessor);
}
}
+ return deferredTagFields;
+}
+
+export function resolveDeferredTagReads(
+ body: string,
+ deferredReads: readonly DeferredTagReads[]
+): string {
+ let resolved = body;
+ for (const reads of deferredReads) {
+ const session = createFunctionTransformSession(resolved);
+ if (!session) {
+ return resolved;
+ }
+ const replacements = collectIdentifierReplacements(session, reads.fieldLocalToKey);
+ applyIdentifierReplacements(
+ session,
+ replacements,
+ reads.baseName,
+ reads.fieldLocalToDefault,
+ reads.fieldLocalToDynamicDefault
+ );
+ resolved = session.toSource();
+ }
+ return resolved;
}
function formatConsolidatedWItems(items: string[]): string {
@@ -935,15 +982,15 @@ export function applyRawPropsTransform(
body: string,
preferredDynamicDefaultNames?: ReadonlyMap,
propsName = '_rawProps'
-): string {
+): RawPropsTransformResult {
const session = createFunctionTransformSession(body);
if (!session) {
- return body;
+ return { code: body };
}
const plan = analyzeRawPropsTransform(session, body, preferredDynamicDefaultNames, propsName);
if (!plan) {
- return body;
+ return { code: body };
}
if (plan.replacementParamRange) {
@@ -964,31 +1011,40 @@ export function applyRawPropsTransform(
} else if (plan.restLine) {
prologueLines.push(plan.restLine);
}
+ let deferredTagReads: DeferredTagReads | undefined;
if (plan.fieldLocalToKey.size > 0) {
const replacements = collectIdentifierReplacements(
session,
plan.fieldLocalToKey,
plan.excludedRanges
);
- applyIdentifierReplacements(
+ const deferredTagFields = applyIdentifierReplacements(
session,
replacements,
plan.replacementBaseName,
plan.fieldLocalToDefault,
plan.fieldLocalToDynamicDefault
);
+ if (deferredTagFields.size > 0) {
+ deferredTagReads = {
+ baseName: plan.replacementBaseName,
+ fieldLocalToKey: deferredTagFields,
+ fieldLocalToDefault: plan.fieldLocalToDefault,
+ fieldLocalToDynamicDefault: plan.fieldLocalToDynamicDefault,
+ };
+ }
}
const transformed = session.toSource();
if (prologueLines.length === 0) {
- return transformed;
+ return { code: transformed, deferredTagReads };
}
const prologueSession = createFunctionTransformSession(transformed);
if (!prologueSession) {
- return transformed;
+ return { code: transformed, deferredTagReads };
}
insertFunctionBodyPrologue(prologueSession, prologueSession.fn, prologueLines.join('\n'));
- return prologueSession.toSource();
+ return { code: prologueSession.toSource(), deferredTagReads };
}
export function bodyConsolidatesToRawProps(body: string): boolean {
@@ -1184,7 +1240,7 @@ export function replacePropsFieldReferencesInBody(
propsName: string,
defaultValues?: ReadonlyMap,
dynamicDefaults?: ReadonlyMap
-): string {
+): RawPropsTransformResult {
return rewritePropsFieldReferences(body, fieldMap, {
memberPropertyMode: 'nonComputed',
propsName,
diff --git a/packages/ts-optimizer/src/optimizer/segment/body-transforms.ts b/packages/ts-optimizer/src/optimizer/segment/body-transforms.ts
index 8968277e481..b69b27c39b0 100644
--- a/packages/ts-optimizer/src/optimizer/segment/body-transforms.ts
+++ b/packages/ts-optimizer/src/optimizer/segment/body-transforms.ts
@@ -8,7 +8,11 @@ import {
} from '../edit/transform-session.js';
import { buildSyncTransform, needsPureAnnotation } from '../rewrite/rewrite-calls.js';
import { formatWCall, parseArrayItems } from '../qwik/w-call.js';
-import { applyRawPropsTransform, consolidateRawPropsInWCalls } from '../rewrite/index.js';
+import {
+ applyRawPropsTransform,
+ consolidateRawPropsInWCalls,
+ type RawPropsTransformResult,
+} from '../rewrite/index.js';
import type { NestedCallSiteInfo } from './segment-codegen.js';
import {
blankNonCode,
@@ -575,20 +579,20 @@ export function applyRawPropsToSegmentBody(
bodyText: string,
parts: string[],
preferredDynamicDefaultNames?: ReadonlyMap
-): string {
+): RawPropsTransformResult {
const result = applyRawPropsTransform(bodyText, preferredDynamicDefaultNames);
- if (result === bodyText) {
- return bodyText;
+ if (result.code === bodyText) {
+ return result;
}
- bodyText = consolidateRawPropsInWCalls(result);
+ bodyText = consolidateRawPropsInWCalls(result.code);
if (bodyText.includes('_restProps(') && !parts.some((p) => p.includes('_restProps'))) {
insertImportBeforeSeparator(parts, `import { _restProps } from "@qwik.dev/core";`);
}
if (bodyText.includes('untrack(') && !parts.some((p) => p.includes('untrack'))) {
insertImportBeforeSeparator(parts, `import { untrack } from "@qwik.dev/core";`);
}
- return bodyText;
+ return { code: bodyText, deferredTagReads: result.deferredTagReads };
}
/** Must run AFTER nested call site rewriting, which uses original positions. */
diff --git a/packages/ts-optimizer/src/optimizer/segment/segment-codegen.ts b/packages/ts-optimizer/src/optimizer/segment/segment-codegen.ts
index 490a308f8a8..2b72fd0f0a1 100644
--- a/packages/ts-optimizer/src/optimizer/segment/segment-codegen.ts
+++ b/packages/ts-optimizer/src/optimizer/segment/segment-codegen.ts
@@ -9,7 +9,10 @@ import {
groupPropsFieldsByBinding,
inlineConstCaptures,
rawPropsBindingNames,
+ resolveDeferredTagReads,
resolveRawPropsSlots,
+ type DeferredTagReads,
+ type RawPropsTransformResult,
} from '../rewrite/index.js';
import { hasUnderscorePlaceholderParams } from '../rewrite/predicates.js';
import type { ConsolidatedSegment } from '../extraction/extract.js';
@@ -146,7 +149,7 @@ function replacePropsFieldReferences(
propsName: string,
defaultValues?: ReadonlyMap,
dynamicDefaults?: ReadonlyMap
-): string {
+): RawPropsTransformResult {
return rewritePropsFieldReferences(bodyText, fieldMap, {
memberPropertyMode: 'all',
propsName,
@@ -669,7 +672,11 @@ function applyBodyTransforms(
nestedCallSites: NestedCallSiteInfo[] | undefined,
enumValueMap: Map> | undefined,
preserveOffsets: boolean
-): { bodyText: string; captureInfo: SegmentCaptureInfo | undefined } {
+): {
+ bodyText: string;
+ captureInfo: SegmentCaptureInfo | undefined;
+ deferredTagReads: DeferredTagReads[];
+} {
// Internal helpers work on plain string; the BodyText brand applies only at
// the ExtractionResult boundary.
let bodyText: string = extraction.bodyText;
@@ -699,8 +706,17 @@ function applyBodyTransforms(
// `_rawProps`. Doing so renamed the param while the body still referenced the
// original binding and dropped the closing brace, producing an unbalanced,
// unparseable segment.
+ const deferredTagReads: DeferredTagReads[] = [];
if (!extraction.isInlinedQrl) {
- bodyText = applyRawPropsToSegmentBody(bodyText, parts, rawPropsInfo?.fieldDynamicDefaults);
+ const rawProps = applyRawPropsToSegmentBody(
+ bodyText,
+ parts,
+ rawPropsInfo?.fieldDynamicDefaults
+ );
+ bodyText = rawProps.code;
+ if (rawProps.deferredTagReads) {
+ deferredTagReads.push(rawProps.deferredTagReads);
+ }
}
bodyText = stripDiagnosticsAndDirectives(bodyText);
@@ -712,13 +728,17 @@ function applyBodyTransforms(
bindingNames
);
for (const [propsName, fields] of groups) {
- bodyText = replacePropsFieldReferences(
+ const rewritten = replacePropsFieldReferences(
bodyText,
fields,
propsName,
captureInfo?.propsFieldDefaults,
captureInfo?.propsFieldDynamicDefaults
);
+ bodyText = rewritten.code;
+ if (rewritten.deferredTagReads) {
+ deferredTagReads.push(rewritten.deferredTagReads);
+ }
}
}
@@ -746,7 +766,7 @@ function applyBodyTransforms(
bodyText = injectCapturesUnpacking(bodyText, liveCaptureInfo.captureNames);
}
- return { bodyText, captureInfo: liveCaptureInfo };
+ return { bodyText, captureInfo: liveCaptureInfo, deferredTagReads };
}
export function generateSegmentCode(
@@ -789,7 +809,7 @@ export function generateSegmentCode(
nestedCallSites,
liveCaptureInfo
);
- bodyText = jsxResult.bodyText;
+ bodyText = resolveDeferredTagReads(jsxResult.bodyText, transformed.deferredTagReads);
segmentKeyCounterValue = jsxResult.keyCounterValue;
}
diff --git a/packages/ts-optimizer/tests/optimizer/rewrite/props-field-rewrite.test.ts b/packages/ts-optimizer/tests/optimizer/rewrite/props-field-rewrite.test.ts
index 05ab36088cd..6ad0f7e73c8 100644
--- a/packages/ts-optimizer/tests/optimizer/rewrite/props-field-rewrite.test.ts
+++ b/packages/ts-optimizer/tests/optimizer/rewrite/props-field-rewrite.test.ts
@@ -9,7 +9,7 @@ describe('props-field-rewrite', () => {
{
memberPropertyMode: 'all',
}
- );
+ ).code;
expect(result).toBe('obj[count] + _rawProps.count');
});
@@ -21,7 +21,7 @@ describe('props-field-rewrite', () => {
{
memberPropertyMode: 'nonComputed',
}
- );
+ ).code;
expect(result).toBe('obj[_rawProps.count] + _rawProps.count');
});
@@ -29,7 +29,7 @@ describe('props-field-rewrite', () => {
it('rewrites shorthand object properties with explicit keys', () => {
const result = rewritePropsFieldReferences('({ count })', new Map([['count', 'count']]), {
memberPropertyMode: 'all',
- });
+ }).code;
expect(result).toBe('({ count: _rawProps.count })');
});
diff --git a/packages/ts-optimizer/tests/optimizer/rewrite/raw-props.test.ts b/packages/ts-optimizer/tests/optimizer/rewrite/raw-props.test.ts
index 36ed334a5ea..10d3ab32bbf 100644
--- a/packages/ts-optimizer/tests/optimizer/rewrite/raw-props.test.ts
+++ b/packages/ts-optimizer/tests/optimizer/rewrite/raw-props.test.ts
@@ -11,7 +11,7 @@ describe('raw-props', () => {
it('rewrites destructured params without reparsing the edited body', () => {
const body = '({ count, label = "x" }) => ({ count, label, total: count + 1 })';
- const result = applyRawPropsTransform(body);
+ const result = applyRawPropsTransform(body).code;
expect(result).toBe(
'(_rawProps) => ({ count: _rawProps.count, label: _rawProps.label ?? "x", total: _rawProps.count + 1 })'
@@ -21,7 +21,7 @@ describe('raw-props', () => {
it('evaluates call-expression defaults once without tracking props', () => {
const body = '({ count, label = getLabel() }) => ({ count, label, total: count + 1 })';
- const result = applyRawPropsTransform(body);
+ const result = applyRawPropsTransform(body).code;
expect(result).toBe(
'(_rawProps) => {\nconst _defaultValue = untrack(() => _rawProps.label) === void 0 ? getLabel() : void 0;\nreturn ({ count: _rawProps.count, label: _rawProps.label === void 0 ? _defaultValue : _rawProps.label, total: _rawProps.count + 1 });\n}'
@@ -31,7 +31,7 @@ describe('raw-props', () => {
it('aborts consolidation for nested ObjectPattern field (parity gate)', () => {
const body = '({ count, stuff: { hey } }) => ({ count, hey })';
- const result = applyRawPropsTransform(body);
+ const result = applyRawPropsTransform(body).code;
expect(result).toBe(body);
});
@@ -39,7 +39,7 @@ describe('raw-props', () => {
it('rewrites body-level destructuring and keeps the original param name', () => {
const body = '(props) => {\n const { count, ...rest } = props;\n return { count, rest };\n}';
- const result = applyRawPropsTransform(body);
+ const result = applyRawPropsTransform(body).code;
expect(result).toBe(
'(props) => {\n const rest = _restProps(props, [\n "count"\n]);\n return { count: props.count, rest };\n}'
@@ -50,7 +50,7 @@ describe('raw-props', () => {
const body =
'(props) => {\n const { withDefault = true, plain, ...rest } = props;\n return { withDefault, plain, rest };\n}';
- const result = applyRawPropsTransform(body);
+ const result = applyRawPropsTransform(body).code;
expect(result).toContain('withDefault: props.withDefault ?? true');
expect(result).toContain('plain: props.plain');
@@ -61,7 +61,7 @@ describe('raw-props', () => {
const body =
'(rawProps) => {\n const props = usePlayground(rawProps, "x");\n const { value: givenValue, ...rest } = props;\n return givenValue ?? rest;\n}';
- const result = applyRawPropsTransform(body);
+ const result = applyRawPropsTransform(body).code;
expect(result).toContain('const props = usePlayground(rawProps, "x");');
expect(result).toMatch(/const rest = _restProps\(props, \[\s*"value"\s*\]\);/);
@@ -73,7 +73,7 @@ describe('raw-props', () => {
it('rewrites rest-only destructuring in expression bodies', () => {
const body = '({ ...rest }) => rest';
- const result = applyRawPropsTransform(body);
+ const result = applyRawPropsTransform(body).code;
expect(result).toBe('(_rawProps) => {\nconst rest = _restProps(_rawProps);\nreturn rest;\n}');
});
diff --git a/packages/ts-optimizer/tests/optimizer/snapshot-options.ts b/packages/ts-optimizer/tests/optimizer/snapshot-options.ts
index 34061ad426c..e66698c452d 100644
--- a/packages/ts-optimizer/tests/optimizer/snapshot-options.ts
+++ b/packages/ts-optimizer/tests/optimizer/snapshot-options.ts
@@ -403,6 +403,24 @@ export const SNAPSHOT_OPTIONS: Record = {
transpileTs: true,
transpileJsx: true,
},
+ destructured_props_as_jsx_tags: {
+ transpileTs: true,
+ transpileJsx: true,
+ },
+ destructured_props_as_jsx_tags_inline: {
+ transpileTs: true,
+ transpileJsx: true,
+ entryStrategy: { type: 'inline' },
+ },
+ captured_props_as_jsx_tags: {
+ transpileTs: true,
+ transpileJsx: true,
+ },
+ captured_props_as_jsx_tags_inline: {
+ transpileTs: true,
+ transpileJsx: true,
+ entryStrategy: { type: 'inline' },
+ },
nested_segments_number_captured_raw_props: {
transpileTs: true,
transpileJsx: true,