@proteus-vue/compiler 0.3.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,3396 @@
1
+ // src/index.ts
2
+ import { parse as sfcParse2 } from "@vue/compiler-sfc";
3
+
4
+ // src/template.ts
5
+ import { parse as domParse, NodeTypes } from "@vue/compiler-dom";
6
+
7
+ // src/tags.ts
8
+ var TAG_MAP = {
9
+ div: "view",
10
+ span: "text",
11
+ p: "text",
12
+ h1: "text",
13
+ h2: "text",
14
+ h3: "text",
15
+ h4: "text",
16
+ h5: "text",
17
+ h6: "text",
18
+ img: "image",
19
+ a: "view",
20
+ button: "button",
21
+ input: "input",
22
+ textarea: "textarea",
23
+ video: "video",
24
+ canvas: "canvas",
25
+ "scroll-view": "scroll-view",
26
+ slot: "slot"
27
+ };
28
+ var EVENT_MAP = {
29
+ click: "tap",
30
+ input: "input",
31
+ change: "change",
32
+ submit: "submit",
33
+ focus: "focus",
34
+ blur: "blur",
35
+ touchstart: "touchstart",
36
+ touchmove: "touchmove",
37
+ touchend: "touchend",
38
+ longpress: "longpress",
39
+ confirm: "confirm",
40
+ scroll: "scroll"
41
+ // scroll-view 内置滚动事件(v0.4 虚拟列表)
42
+ };
43
+ var SEMANTIC_CLASS = {
44
+ h1: "proteus-h1",
45
+ h2: "proteus-h2",
46
+ h3: "proteus-h3",
47
+ h4: "proteus-h4",
48
+ h5: "proteus-h5",
49
+ h6: "proteus-h6",
50
+ p: "proteus-p",
51
+ a: "proteus-a"
52
+ };
53
+
54
+ // src/transforms/template.ts
55
+ function tagRule(id, title, tags, extra) {
56
+ return {
57
+ id,
58
+ phase: "template",
59
+ status: "implemented",
60
+ title,
61
+ mapping: Object.fromEntries(tags.map((t) => [t, TAG_MAP[t]]).filter(([, v]) => v !== void 0)),
62
+ ...extra
63
+ };
64
+ }
65
+ var TEMPLATE_RULES = [
66
+ // ============ 标签映射(TAG_MAP) ============
67
+ tagRule("tag/div-to-view", "div \u2192 view", ["div"], {
68
+ description: "\u5757\u7EA7\u5BB9\u5668 div \u2192 view\uFF08\u5C0F\u7A0B\u5E8F\u901A\u7528\u5BB9\u5668\uFF09",
69
+ why: "\u5C0F\u7A0B\u5E8F\u6CA1\u6709 div\uFF0Cview \u662F\u6700\u901A\u7528\u5BB9\u5668\u6807\u7B7E\uFF1B\u4E1A\u52A1\u4EE3\u7801\u7167\u5199\u6807\u51C6 HTML\uFF08\xA70.3 \u539F\u5219 1\uFF09",
70
+ when: "template \u4E2D\u51FA\u73B0 <div> \u65F6",
71
+ example: { before: '<div class="home">\u2026</div>', after: '<view class="home">\u2026</view>' },
72
+ verify: "tests/mp-transform.test.ts\u300C\u6807\u51C6\u6807\u7B7E\u6620\u5C04\u300D",
73
+ source: "src/compiler/template.ts \u2192 serializeElement\uFF08TAG_MAP \u67E5\u8868\uFF09",
74
+ decision: "#57\uFF08\u6837\u5F0F\u6807\u7B7E\u9009\u62E9\u5668\u6620\u5C04\uFF09"
75
+ }),
76
+ tagRule("tag/inline-to-text", "span \u2192 text", ["span"], {
77
+ description: "\u884C\u5185\u6587\u672C span \u2192 text\uFF08\u5C0F\u7A0B\u5E8F\u6700\u5C0F\u6587\u672C\u8282\u70B9\uFF09",
78
+ why: "\u5C0F\u7A0B\u5E8F\u65E0 span\uFF0Ctext \u662F\u884C\u5185\u6587\u672C\u5BB9\u5668\uFF1Btext \u9ED8\u8BA4\u4E0D\u6362\u884C\u3001\u53EF\u88AB text \u5D4C\u5957",
79
+ when: "template \u4E2D\u51FA\u73B0 <span> \u65F6",
80
+ example: { before: "<span>hi</span>", after: "<text>hi</text>" },
81
+ verify: "tests/mp-transform.test.ts\u300C\u6807\u51C6\u6807\u7B7E\u6620\u5C04\u300D",
82
+ source: "src/compiler/template.ts \u2192 serializeElement\uFF08TAG_MAP \u67E5\u8868\uFF09"
83
+ }),
84
+ tagRule("tag/heading-to-text", "h1\u2013h6 \u2192 text", ["h1", "h2", "h3", "h4", "h5", "h6"], {
85
+ description: "\u6807\u9898 h1-h6 \u2192 text\uFF0C\u5E76\u81EA\u52A8\u9644\u52A0 proteus-h1~h6 \u57FA\u7840\u7C7B\uFF08\u89C1 semantic/base-class\uFF09",
86
+ why: "\u5C0F\u7A0B\u5E8F\u65E0\u6807\u9898\u6807\u7B7E\uFF1B\u8BED\u4E49\uFF08\u5927\u5B57\u53F7/\u52A0\u7C97\uFF09\u7531\u57FA\u7840\u7C7B\u8FD8\u539F\uFF08#58\uFF09\uFF0C\u89C6\u89C9\u5BF9\u9F50 Web UA \u6837\u5F0F",
87
+ when: "template \u4E2D\u51FA\u73B0 <h1>~<h6> \u65F6",
88
+ example: { before: "<h1>\u6807\u9898</h1>", after: '<text class="proteus-h1">\u6807\u9898</text>' },
89
+ verify: "tests/mp-transform.test.ts\u300C\u8BED\u4E49\u6807\u7B7E\u81EA\u52A8\u9644\u52A0\u57FA\u7840\u7C7B\u300D",
90
+ source: "src/compiler/template.ts \u2192 serializeElement + src/compiler/style.ts BASE_SEMANTIC_WXSS",
91
+ decision: "#58 / #59"
92
+ }),
93
+ tagRule("tag/para-to-text", "p \u2192 text", ["p"], {
94
+ description: "\u6BB5\u843D p \u2192 text\uFF0C\u5E76\u81EA\u52A8\u9644\u52A0 proteus-p \u57FA\u7840\u7C7B\uFF08\u6BB5\u8DDD\u5BF9\u9F50 Web\uFF09",
95
+ why: "p \u6620\u5C04\u4E3A text \u540E\u65E0 UA \u9ED8\u8BA4\u6BB5\u8DDD\uFF0C\u57FA\u7840\u7C7B\u6CE8\u5165 margin: 0 0 1em \u8FD8\u539F Web \u6298\u53E0\u95F4\u8DDD\uFF08#59\uFF09",
96
+ when: "template \u4E2D\u51FA\u73B0 <p> \u65F6",
97
+ example: { before: "<p>\u6BB5\u843D</p>", after: '<text class="proteus-p">\u6BB5\u843D</text>' },
98
+ verify: "tests/mp-transform.test.ts\u300C\u6807\u51C6\u6807\u7B7E\u6620\u5C04\u300D",
99
+ source: "src/compiler/template.ts \u2192 serializeElement",
100
+ decision: "#59\uFF08margin \u5355\u8FB9 em\uFF09"
101
+ }),
102
+ tagRule("tag/link-to-view", "a \u2192 view", ["a"], {
103
+ description: "\u94FE\u63A5 a \u2192 view\uFF08\u5E26 href \u65F6\u5347\u7EA7\u4E3A\u5BFC\u822A\u94FE\u63A5\uFF0C\u89C1 nav/navigate-link\uFF09",
104
+ why: '\u5C0F\u7A0B\u5E8F\u65E0 a \u6807\u7B7E\uFF1B\u5BFC\u822A\u8BED\u4E49\u7531 data-url + bindtap="proteusNavigateTo" \u627F\u62C5\uFF0C\u6837\u5F0F\u8BED\u4E49\u7531 proteus-a \u57FA\u7840\u7C7B\u627F\u62C5',
105
+ when: "template \u4E2D\u51FA\u73B0 <a> \u65F6",
106
+ example: { before: '<a href="/pages/user/index">\u7528\u6237</a>', after: '<view class="proteus-a" data-url="/pages/user/index" bindtap="proteusNavigateTo">\u7528\u6237</view>' },
107
+ verify: "tests/mp-transform.test.ts \u5BFC\u822A\u94FE\u63A5\u7528\u4F8B",
108
+ source: "src/compiler/template.ts \u2192 serializeElement\uFF08isNavLink \u5206\u652F\uFF09",
109
+ decision: "#24 / #30\uFF08\u5BFC\u822A\u94FE\u63A5\uFF09/ #58\uFF08\u57FA\u7840\u7C7B\uFF09"
110
+ }),
111
+ tagRule("tag/image", "img \u2192 image", ["img"], {
112
+ description: '\u56FE\u7247 img \u2192 image\uFF1B:src \u7ED1\u5B9A\u7ECF directive/v-bind \u8F6C\u4E3A src="{{url}}"',
113
+ why: "\u5C0F\u7A0B\u5E8F\u56FE\u7247\u6807\u7B7E\u662F image",
114
+ when: "template \u4E2D\u51FA\u73B0 <img> \u65F6",
115
+ example: { before: '<img :src="url" />', after: '<image src="{{url}}" />' },
116
+ verify: "tests/mp-transform.test.ts\u300C\u6807\u51C6\u6807\u7B7E\u6620\u5C04\u300D",
117
+ source: "src/compiler/template.ts \u2192 serializeElement\uFF08TAG_MAP \u67E5\u8868\uFF09"
118
+ }),
119
+ tagRule("tag/passthrough", "\u540C\u540D\u6807\u7B7E\u4FDD\u7559", ["button", "input", "textarea", "video", "canvas", "scroll-view", "slot"], {
120
+ description: "button / input / textarea / video / canvas / scroll-view / slot \u540C\u540D\u4FDD\u7559\uFF08\u5C0F\u7A0B\u5E8F\u539F\u751F\u5373\u6709\uFF09",
121
+ why: "\u8FD9\u4E9B\u6807\u7B7E\u5728\u5C0F\u7A0B\u5E8F\u539F\u751F\u5B58\u5728\uFF0C\u65E0\u9700\u6620\u5C04\uFF1Binput/textarea \u540C\u65F6\u662F v-model \u7684\u76EE\u6807\uFF08directive/v-model\uFF09",
122
+ when: "template \u4E2D\u51FA\u73B0\u4EE5\u4E0A\u6807\u7B7E\u65F6",
123
+ example: { before: "<button>go</button>", after: "<button>go</button>" },
124
+ verify: "tests/mp-transform.test.ts \u4E8B\u4EF6/\u8868\u5355\u7528\u4F8B",
125
+ source: "src/compiler/template.ts \u2192 serializeElement\uFF08TAG_MAP \u67E5\u8868\uFF09"
126
+ }),
127
+ tagRule("tag/router-link", "router-link \u2192 view", ["router-link"], {
128
+ description: "Vue Router \u7684 router-link \u2192 view + \u5BFC\u822A\u94FE\u63A5\uFF08to \u5C5E\u6027 \u2192 data-url\uFF09",
129
+ why: "\u5C0F\u7A0B\u5E8F\u65E0 Vue Router \u7EC4\u4EF6\uFF1B\u7EDF\u4E00\u8F6C\u5BFC\u822A\u94FE\u63A5\uFF08\u51B3\u7B56 #24\uFF1A<a href> / <router-link> \u540C\u4E3A\u5BFC\u822A\u5165\u53E3\uFF09",
130
+ when: 'template \u4E2D\u51FA\u73B0 <router-link to="..."> \u4E14\u65E0 @click \u65F6',
131
+ example: { before: '<router-link to="/pages/user/index">\u7528\u6237</router-link>', after: '<view data-url="/pages/user/index" bindtap="proteusNavigateTo">\u7528\u6237</view>' },
132
+ verify: "tests/mp-transform.test.ts \u5BFC\u822A\u94FE\u63A5\u7528\u4F8B",
133
+ source: "src/compiler/template.ts \u2192 serializeElement\uFF08isNavLink \u5206\u652F\uFF09",
134
+ decision: "#24"
135
+ }),
136
+ tagRule("tag/rich-text", "v-html \u5BB9\u5668 \u2192 rich-text", [], {
137
+ description: '\u5E26 v-html \u7684\u5BB9\u5668\u5143\u7D20 \u2192 rich-text\uFF08nodes="{{expr}}"\uFF09',
138
+ why: "\u5C0F\u7A0B\u5E8F\u65E0 innerHTML\uFF0C\u5BCC\u6587\u672C\u7528 rich-text \u7EC4\u4EF6\u6E32\u67D3 HTML \u8282\u70B9",
139
+ when: "\u5143\u7D20\u4E0A\u6709 v-html \u6307\u4EE4\u65F6\uFF08v-html \u8986\u76D6\u6807\u7B7E\u6620\u5C04\uFF09",
140
+ example: { before: '<div v-html="html"></div>', after: '<rich-text nodes="{{html}}" />' },
141
+ verify: "tests/mp-transform.test.ts v-html \u7528\u4F8B",
142
+ source: "src/compiler/template.ts \u2192 serializeElement\uFF08hasVHtml \u5206\u652F\uFF09"
143
+ }),
144
+ tagRule("tag/unknown-kebab", "\u672A\u6CE8\u518C\u6807\u7B7E kebab-case \u539F\u6837\u8F93\u51FA", [], {
145
+ description: "TAG_MAP \u672A\u8986\u76D6\u7684\u6807\u7B7E\u6309 kebab-case \u539F\u6837\u8F93\u51FA\uFF08\u7EC4\u4EF6 / \u81EA\u5B9A\u4E49\u5143\u7D20\u9003\u751F\u8231\uFF09",
146
+ why: "\u767D\u540D\u5355\u6620\u5C04 + \u672A\u77E5\u6807\u7B7E\u4FDD\u5B88\u4FDD\u7559\uFF1A\u6807\u51C6 Vue \u7EC4\u4EF6\u4F53\u7CFB\uFF08\u539F\u5219 9\uFF09\u4E0E\u539F\u751F\u7EC4\u4EF6\u9003\u751F\u8231\uFF08\u75DB\u70B9 #11 \u5BF9\u7B56\uFF09\u4F9D\u8D56\u6B64\u901A\u9053",
147
+ when: "\u6807\u7B7E\u4E0D\u5728 TAG_MAP \u4E14\u975E router-link \u65F6",
148
+ example: { before: '<custom-comp foo="bar" />', after: '<custom-comp foo="bar" />' },
149
+ verify: "tests/mp-transform.test.ts \u672A\u6CE8\u518C\u6807\u7B7E\u7528\u4F8B",
150
+ source: "src/compiler/template.ts \u2192 serializeElement\uFF08TAG_MAP[node.tag] ?? kebabCase\uFF09"
151
+ }),
152
+ // ============ 语义基础类 ============
153
+ {
154
+ id: "semantic/base-class",
155
+ phase: "template",
156
+ status: "implemented",
157
+ title: "\u8BED\u4E49\u6807\u7B7E\u81EA\u52A8\u9644\u52A0 proteus-* \u57FA\u7840\u7C7B",
158
+ description: "h1-h6/p/a \u6620\u5C04\u65F6\u81EA\u52A8\u9644\u52A0 proteus-h1~h6 / proteus-p / proteus-a \u7C7B\uFF08\u4E0E\u7528\u6237 class \u5408\u5E76\u3001\u4E0E :class \u63D2\u503C\u62FC\u63A5\uFF09",
159
+ why: "Web \u7AEF h1-h6/p/a \u6709\u6D4F\u89C8\u5668 UA \u9ED8\u8BA4\u6837\u5F0F\uFF0C\u5C0F\u7A0B\u5E8F text/view \u6CA1\u6709\uFF1B\u57FA\u7840\u7C7B + \u57FA\u7840 WXSS\uFF08style/semantic-base-wxss\uFF09\u8FD8\u539F\u4E24\u7AEF\u89C6\u89C9\u4E00\u81F4\uFF08#58\uFF09",
160
+ when: "\u8BED\u4E49\u6807\u7B7E\u4E14\u975E v-html \u5BB9\u5668\u65F6",
161
+ example: {
162
+ before: '<h1 class="title">a</h1>',
163
+ after: '<text class="proteus-h1 title">a</text>'
164
+ },
165
+ verify: "tests/mp-transform.test.ts\u300C\u8BED\u4E49\u6807\u7B7E\u81EA\u52A8\u9644\u52A0\u57FA\u7840\u7C7B\u300D\uFF1Bgolden fixture showcase.wxml",
166
+ source: "src/compiler/template.ts \u2192 serializeElement\uFF08baseClass \u5206\u652F\uFF09+ src/compiler/tags.ts SEMANTIC_CLASS",
167
+ decision: "#58 / #59",
168
+ mapping: { ...SEMANTIC_CLASS }
169
+ },
170
+ // ============ 事件映射(EVENT_MAP) ============
171
+ {
172
+ id: "event/click-to-tap",
173
+ phase: "template",
174
+ status: "implemented",
175
+ title: "@click \u2192 bindtap\uFF08EVENT_MAP \u5168\u8868\uFF09",
176
+ description: "\u6807\u51C6\u4E8B\u4EF6 \u2192 \u5C0F\u7A0B\u5E8F\u4E8B\u4EF6\uFF1Aclick\u2192tap\u3001input/change/submit/focus/blur/touch*/longpress/confirm \u540C\u540D\u4FDD\u7559",
177
+ why: "\u5C0F\u7A0B\u5E8F\u65E0 click\uFF0C\u70B9\u51FB\u4E8B\u4EF6\u662F tap\uFF1B\u5176\u4F59\u4E8B\u4EF6\u547D\u540D\u4E00\u81F4\uFF08EVENT_MAP \u96C6\u4E2D\u5728 tags.ts \u4E0E\u6837\u5F0F\u4FA7\u5171\u7528\uFF09",
178
+ when: "\u5143\u7D20\u4E0A\u6709 @\u4E8B\u4EF6 \u6307\u4EE4\u65F6",
179
+ example: { before: '<button @click="handleTap">go</button>', after: '<button bindtap="handleTap">go</button>' },
180
+ verify: "tests/mp-transform.test.ts\u300C\u4E8B\u4EF6\u6620\u5C04\u300D",
181
+ source: "src/compiler/template.ts \u2192 serializeElement\uFF08on \u5206\u652F\uFF09+ src/compiler/tags.ts EVENT_MAP",
182
+ mapping: { ...EVENT_MAP }
183
+ },
184
+ {
185
+ id: "event/modifier-catch",
186
+ phase: "template",
187
+ title: ".stop / .prevent \u4FEE\u9970\u7B26 \u2192 catch \u524D\u7F00",
188
+ description: "@click.stop / @click.prevent \u2192 catchtap\uFF08\u963B\u6B62\u5192\u6CE1\uFF09\uFF1B\u5176\u4F59\u4FEE\u9970\u7B26\u5FFD\u7565",
189
+ why: "\u5C0F\u7A0B\u5E8F\u65E0\u4E8B\u4EF6\u4FEE\u9970\u7B26\u8BED\u6CD5\uFF0Ccatch* \u4E8B\u4EF6\u5929\u7136\u963B\u6B62\u5192\u6CE1\uFF0C\u7B49\u4EF7 .stop \u8BED\u4E49\uFF1B.prevent \u65E0\u5BF9\u7B49\u673A\u5236\uFF0C\u6620\u5C04\u4E3A catch \u515C\u5E95",
190
+ when: "\u4E8B\u4EF6\u6307\u4EE4\u5E26 stop \u6216 prevent \u4FEE\u9970\u7B26\u65F6",
191
+ example: { before: '<a @click.stop="stopFn">s</a>', after: '<a catchtap="stopFn">s</a>' },
192
+ verify: "tests/mp-transform.test.ts\u300C\u4E8B\u4EF6\u6620\u5C04\u300D",
193
+ source: "src/compiler/template.ts \u2192 serializeElement\uFF08on \u5206\u652F isCatch\uFF09",
194
+ status: "implemented"
195
+ },
196
+ {
197
+ id: "event/modifier-self-once",
198
+ phase: "template",
199
+ status: "implemented",
200
+ title: ".self / .once \u4FEE\u9970\u7B26 \u2192 \u5305\u88C5\u65B9\u6CD5\uFF08v0.3 \u5C3E\uFF09",
201
+ description: '@click.self="fn" \u2192 bindtap="proteusSelfFn"\uFF08e.target === e.currentTarget \u624D\u89E6\u53D1\uFF09\uFF1B@click.once="fn" \u2192 bindtap="proteusOnceFn"\uFF08data \u6807\u8BB0\u9996\u6B21\u89E6\u53D1\u540E\u4E0D\u518D\u89E6\u53D1\uFF09\uFF1B\u4EC5\u5BF9\u7B80\u5355\u65B9\u6CD5\u540D handler \u5305\u88C5',
202
+ why: "\u5C0F\u7A0B\u5E8F\u65E0 .self/.once \u539F\u751F\u8BED\u4E49\uFF0C\u7F16\u8BD1\u671F\u751F\u6210\u5305\u88C5\u65B9\u6CD5\uFF08script \u4FA7\uFF09\uFF1Aself \u7528\u4E8B\u4EF6\u6E90\u5224\u65AD\u3001once \u7528 data \u6807\u8BB0\uFF1B\u952E\u4F4D\u4FEE\u9970\u7B26\uFF08@keyup.enter\uFF09\u65E0\u5BF9\u7B49\u952E\u76D8\u4E8B\u4EF6 \u2192 \u7F16\u8BD1\u671F\u8B66\u544A\uFF08input \u8BF7\u7528 @confirm\uFF09",
203
+ when: "\u4E8B\u4EF6\u6307\u4EE4\u5E26 self \u6216 once \u4FEE\u9970\u7B26\uFF08\u4E14 handler \u662F\u7B80\u5355\u65B9\u6CD5\u540D\uFF09\u65F6",
204
+ example: {
205
+ before: '@click.self="handleTap" / @click.once="handleTap"',
206
+ after: 'bindtap="proteusSelfHandleTap" / bindtap="proteusOnceHandleTap"\uFF08\u5305\u88C5\u65B9\u6CD5\u751F\u6210\u4E8E Page methods\uFF09'
207
+ },
208
+ verify: "tests/mp-transform.test.ts \u4E8B\u4EF6\u4FEE\u9970\u7B26\u7528\u4F8B",
209
+ source: "packages/compiler/src/template.ts\uFF08on \u5206\u652F\uFF09+ script.ts\uFF08proteusSelf/Once \u751F\u6210\uFF09",
210
+ decision: "#88\uFF08v0.3 \u5C3E\u6307\u4EE4\u8865\u5168\uFF09"
211
+ },
212
+ {
213
+ id: "event/handler-simple-ref",
214
+ phase: "template",
215
+ title: "\u4E8B\u4EF6\u5904\u7406\u5668\u4EC5\u652F\u6301\u7B80\u5355\u65B9\u6CD5\u5F15\u7528",
216
+ description: "\u4EC5\u652F\u6301 handleTap / handleTap($event)\uFF1B\u590D\u6742\u8868\u8FBE\u5F0F\u7F16\u8BD1\u671F\u8B66\u544A\u5E76\u539F\u6837\u8F93\u51FA",
217
+ why: "MVP \u6536\u7F29\uFF08\u539F\u5219 10\uFF09\uFF1A\u5C0F\u7A0B\u5E8F\u4E8B\u4EF6\u5904\u7406\u5668\u5FC5\u987B\u662F Page methods \u4E2D\u7684\u65B9\u6CD5\u540D\uFF0C\u5185\u8054\u8868\u8FBE\u5F0F\u65E0\u6CD5\u9759\u6001\u7F16\u8BD1",
218
+ when: "\u4E8B\u4EF6\u6307\u4EE4\u7684\u8868\u8FBE\u5F0F\u4E0D\u662F\u7B80\u5355\u65B9\u6CD5\u5F15\u7528\u65F6",
219
+ example: {
220
+ before: '@click="count > 0 ? go() : back()"',
221
+ after: "\u7F16\u8BD1\u671F\u8B66\u544A\uFF1A\u4E0D\u662F\u7B80\u5355\u65B9\u6CD5\u5F15\u7528\uFF0C\u539F\u6837\u8F93\u51FA\uFF08\u4EA7\u7269\u9700\u4EBA\u5DE5\u5904\u7406\uFF09"
222
+ },
223
+ verify: "tests/mp-transform.test.ts \u4E8B\u4EF6\u8B66\u544A\u7528\u4F8B",
224
+ source: "src/compiler/template.ts \u2192 cleanHandler",
225
+ status: "limitation"
226
+ },
227
+ // ============ 指令映射 ============
228
+ {
229
+ id: "directive/v-if",
230
+ phase: "template",
231
+ status: "implemented",
232
+ title: "v-if \u2192 wx:if",
233
+ description: 'v-if="show" \u2192 wx:if="{{show}}"',
234
+ why: "\u5C0F\u7A0B\u5E8F\u6761\u4EF6\u6E32\u67D3\u6307\u4EE4\u662F wx:if",
235
+ when: "\u5143\u7D20\u4E0A\u6709 v-if \u6307\u4EE4\u65F6",
236
+ example: { before: '<p v-if="show">a</p>', after: '<p wx:if="{{show}}">a</p>' },
237
+ verify: "tests/mp-transform.test.ts\u300Cv-if / v-else\u300D",
238
+ source: "src/compiler/template.ts \u2192 serializeElement\uFF08if \u5206\u652F\uFF09"
239
+ },
240
+ {
241
+ id: "directive/v-else-if",
242
+ phase: "template",
243
+ status: "implemented",
244
+ title: "v-else-if \u2192 wx:elif",
245
+ description: 'v-else-if="cond" \u2192 wx:elif="{{cond}}"',
246
+ why: "\u5C0F\u7A0B\u5E8F\u6761\u4EF6\u94FE\u6307\u4EE4\u662F wx:elif",
247
+ when: "\u5143\u7D20\u4E0A\u6709 v-else-if \u6307\u4EE4\u65F6",
248
+ example: { before: '<p v-else-if="b">c</p>', after: '<p wx:elif="{{b}}">c</p>' },
249
+ verify: "tests/mp-transform.test.ts\u300Cv-if / v-else\u300D",
250
+ source: "src/compiler/template.ts \u2192 serializeElement\uFF08else-if \u5206\u652F\uFF09"
251
+ },
252
+ {
253
+ id: "directive/v-else",
254
+ phase: "template",
255
+ status: "implemented",
256
+ title: "v-else \u2192 wx:else",
257
+ description: "v-else \u2192 wx:else\uFF08\u65E0\u503C\uFF09",
258
+ why: "\u5C0F\u7A0B\u5E8F\u6761\u4EF6\u94FE\u6307\u4EE4\u662F wx:else",
259
+ when: "\u5143\u7D20\u4E0A\u6709 v-else \u6307\u4EE4\u65F6",
260
+ example: { before: "<p v-else>b</p>", after: "<p wx:else>b</p>" },
261
+ verify: "tests/mp-transform.test.ts\u300Cv-if / v-else\u300D",
262
+ source: "src/compiler/template.ts \u2192 serializeElement\uFF08else \u5206\u652F\uFF09"
263
+ },
264
+ {
265
+ id: "directive/v-for",
266
+ phase: "template",
267
+ status: "implemented",
268
+ title: "v-for \u2192 wx:for / wx:for-item / wx:for-index",
269
+ description: 'v-for="(item, idx) in list" \u2192 wx:for="{{list}}" + wx:for-item + wx:for-index\uFF08\u652F\u6301 in/of\uFF09',
270
+ why: "\u5C0F\u7A0B\u5E8F\u5FAA\u73AF\u6307\u4EE4\u662F wx:for\uFF0C\u4E14\u9700\u663E\u5F0F\u58F0\u660E item/index \u53D8\u91CF\u540D",
271
+ when: "\u5143\u7D20\u4E0A\u6709 v-for \u6307\u4EE4\u65F6",
272
+ example: {
273
+ before: '<div v-for="(item, idx) in list" :key="idx">{{ item }}</div>',
274
+ after: '<view wx:for="{{list}}" wx:for-item="item" wx:for-index="idx" wx:key="idx">{{ item }}</view>'
275
+ },
276
+ verify: "tests/mp-transform.test.ts\u300Cv-for\u300D",
277
+ source: "src/compiler/template.ts \u2192 serializeElement\uFF08for \u5206\u652F\uFF09+ parseForExpr"
278
+ },
279
+ {
280
+ id: "directive/v-bind",
281
+ phase: "template",
282
+ status: "implemented",
283
+ title: '\u666E\u901A :prop \u7ED1\u5B9A \u2192 prop="{{expr}}"',
284
+ description: ':src / :href / \u4EFB\u610F\u5C5E\u6027\u7ED1\u5B9A \u2192 \u5C5E\u6027="{{\u8868\u8FBE\u5F0F}}"\uFF0C\u9759\u6001\u5C5E\u6027\u539F\u6837\u4FDD\u7559',
285
+ why: '\u5C0F\u7A0B\u5E8F\u5C5E\u6027\u7ED1\u5B9A\u8BED\u6CD5\u662F {{expr}}\uFF1B\u9759\u6001\u5C5E\u6027\uFF08\u5982 placeholder="x"\uFF09\u76F4\u63A5\u900F\u4F20',
286
+ when: "\u5143\u7D20\u4E0A\u6709 v-bind \u4E14 arg \u4E0D\u662F class/style/key \u65F6",
287
+ example: { before: '<img :src="url" />', after: '<image src="{{url}}" />' },
288
+ verify: "tests/mp-transform.test.ts\u300C\u6807\u51C6\u6807\u7B7E\u6620\u5C04\u300D",
289
+ source: "src/compiler/template.ts \u2192 serializeElement\uFF08bind \u5206\u652F\uFF09"
290
+ },
291
+ {
292
+ id: "directive/v-bind-class",
293
+ phase: "template",
294
+ status: "implemented",
295
+ title: ":class \u7ED1\u5B9A\uFF08\u5BF9\u8C61/\u6570\u7EC4\u8BED\u6CD5 \u2192 \u4E09\u5143\u62FC\u63A5\uFF09",
296
+ description: `:class="{ active: on }" \u2192 {{(on?'active ':'')}}\uFF1B\u6570\u7EC4\u8BED\u6CD5\uFF08v0.3\uFF09\u2192 \u9010\u9879\u62FC\u63A5\uFF08\u5B57\u7B26\u4E32/\u5BF9\u8C61/\u7B80\u5355\u53D8\u91CF/\u4E09\u5143\uFF09`,
297
+ why: "\u5C0F\u7A0B\u5E8F\u65E0 class \u5BF9\u8C61/\u6570\u7EC4\u8BED\u6CD5\uFF0C\u7F16\u8BD1\u671F\u4E3A\u4E09\u5143\u8868\u8FBE\u5F0F\u62FC\u63A5\uFF1B\u6570\u7EC4\u8BED\u6CD5 v0.3 \u8865\u9F50\uFF08splitTopLevel \u9876\u5C42\u9017\u53F7\u5206\u5272\uFF0C\u8DF3\u8FC7\u5B57\u7B26\u4E32/\u62EC\u53F7\u5185\u9017\u53F7\uFF09",
298
+ when: "\u5143\u7D20\u4E0A\u6709 :class \u7ED1\u5B9A\u4E14\u5E26\u8BED\u4E49\u57FA\u7840\u7C7B\u6216\u5BF9\u8C61/\u6570\u7EC4\u8868\u8FBE\u5F0F\u65F6",
299
+ example: {
300
+ before: '<p :class="[activeClass, { active: on }]">b</p>',
301
+ after: `<text class="proteus-p {{((activeClass)?(activeClass)+' ':'')+(on?'active ':'')}}">b</text>`
302
+ },
303
+ verify: "tests/mp-transform.test.ts\u300C\u8BED\u4E49\u6807\u7B7E\u81EA\u52A8\u9644\u52A0\u57FA\u7840\u7C7B\u300D+ :class \u6570\u7EC4\u7528\u4F8B",
304
+ source: "packages/compiler/src/template.ts \u2192 formatClassBinding + splitTopLevel",
305
+ decision: "#61 / #77\uFF08v0.3 \u6570\u7EC4\u8BED\u6CD5\uFF09"
306
+ },
307
+ {
308
+ id: "directive/v-bind-style",
309
+ phase: "template",
310
+ status: "implemented",
311
+ title: ":style \u7ED1\u5B9A\uFF08\u5BF9\u8C61\u8BED\u6CD5 \u2192 prop:{{expr}} \u62FC\u63A5\uFF09",
312
+ description: ':style="{ color: c }" \u2192 style="color:{{c}}"\uFF1B\u5C5E\u6027\u540D camelCase \u2192 kebab-case',
313
+ why: "\u5C0F\u7A0B\u5E8F style \u5C5E\u6027\u652F\u6301\u5185\u8054\u63D2\u503C\uFF0C\u9010\u5C5E\u6027\u7F16\u8BD1\u53EF\u9759\u6001\u9A8C\u8BC1",
314
+ when: "\u5143\u7D20\u4E0A\u6709 :style \u7ED1\u5B9A\u4E14\u4E3A\u5BF9\u8C61\u8BED\u6CD5\u65F6",
315
+ example: { before: ':style="{ backgroundColor: bg }"', after: 'style="background-color:{{bg}}"' },
316
+ verify: "tests/mp-transform.test.ts :style \u7528\u4F8B",
317
+ source: "src/compiler/template.ts \u2192 formatStyleBinding"
318
+ },
319
+ {
320
+ id: "directive/v-bind-key",
321
+ phase: "template",
322
+ status: "implemented",
323
+ title: ":key \u2192 wx:key\uFF08\u4EC5\u7B80\u5355\u6807\u8BC6\u7B26\uFF09",
324
+ description: ':key="idx" \u2192 wx:key="idx"\uFF1B\u975E\u7B80\u5355\u6807\u8BC6\u7B26\u7F16\u8BD1\u671F\u8B66\u544A\u5E76\u5FFD\u7565',
325
+ why: "\u5C0F\u7A0B\u5E8F\u5217\u8868\u590D\u7528\u6807\u8BC6\u662F wx:key\uFF0C\u4EC5\u63A5\u53D7\u7B80\u5355\u6807\u8BC6\u7B26",
326
+ when: "\u5143\u7D20\u4E0A\u6709 :key \u7ED1\u5B9A\uFF08\u901A\u5E38\u5728 v-for \u5185\uFF09\u65F6",
327
+ example: { before: ':key="idx"', after: 'wx:key="idx"' },
328
+ verify: "tests/mp-transform.test.ts\u300Cv-for\u300D",
329
+ source: "src/compiler/template.ts \u2192 serializeElement\uFF08key \u5206\u652F\uFF09"
330
+ },
331
+ {
332
+ id: "directive/v-model",
333
+ phase: "template",
334
+ status: "implemented",
335
+ title: "v-model \u2192 value + bindinput \u81EA\u52A8 handler",
336
+ description: 'input/textarea \u7684 v-model="x" \u2192 value="{{x}}" + bindinput="proteusOnXInput"\uFF08handler \u7531 script \u9636\u6BB5\u6CE8\u5165 setData\uFF09',
337
+ why: "\u5C0F\u7A0B\u5E8F\u65E0 v-model \u8BED\u6CD5\uFF0C\u9700\u53CC\u5411\u7ED1\u5B9A\u7684\u4E24\u534A\uFF1Avalue \u7ED1\u5B9A + \u8F93\u5165\u4E8B\u4EF6\u56DE\u5199\uFF08script/vmodel-handler\uFF09",
338
+ when: "input \u6216 textarea \u4E0A\u6709 v-model \u6307\u4EE4\u65F6",
339
+ example: {
340
+ before: '<input v-model="name" />',
341
+ after: '<input value="{{name}}" bindinput="proteusOnNameInput" />'
342
+ },
343
+ verify: "tests/mp-transform.test.ts v-model \u7528\u4F8B",
344
+ source: "src/compiler/template.ts \u2192 serializeElement\uFF08model \u5206\u652F\uFF09",
345
+ decision: "#29\uFF08\u65B9\u6CD5\u540D\u907F\u514D __ \u524D\u7F00\uFF09"
346
+ },
347
+ {
348
+ id: "directive/v-html",
349
+ phase: "template",
350
+ status: "implemented",
351
+ title: "v-html \u2192 rich-text nodes",
352
+ description: 'v-html="html" \u2192 rich-text nodes="{{html}}"\uFF08\u5BB9\u5668\u6807\u7B7E\u6620\u5C04\u4E3A rich-text\uFF09',
353
+ why: "\u5C0F\u7A0B\u5E8F\u65E0 innerHTML\uFF0C\u5BCC\u6587\u672C\u7528 rich-text \u7EC4\u4EF6\uFF08\u539F\u751F\u80FD\u529B\u515C\u5E95\uFF0C\u75DB\u70B9 #11 \u5BF9\u7B56\uFF09",
354
+ when: "\u5143\u7D20\u4E0A\u6709 v-html \u6307\u4EE4\u65F6",
355
+ example: { before: '<div v-html="html"></div>', after: '<rich-text nodes="{{html}}" />' },
356
+ verify: "tests/mp-transform.test.ts v-html \u7528\u4F8B",
357
+ source: "src/compiler/template.ts \u2192 serializeElement\uFF08hasVHtml / html \u5206\u652F\uFF09"
358
+ },
359
+ {
360
+ id: "directive/v-show",
361
+ phase: "template",
362
+ status: "implemented",
363
+ title: "v-show \u2192 hidden \u5C5E\u6027",
364
+ description: 'v-show="show" \u2192 hidden="{{!show}}"\uFF08\u5C0F\u7A0B\u5E8F hidden \u5C5E\u6027 = display:none\uFF0C\u5143\u7D20\u59CB\u7EC8\u6E32\u67D3\uFF09',
365
+ why: "\u5C0F\u7A0B\u5E8F\u65E0 v-show \u6307\u4EE4\uFF0Chidden \u5C5E\u6027\u8BED\u4E49\u5BF9\u9F50\uFF08display:none \u5207\u6362\uFF09\uFF1B\u5143\u7D20\u4FDD\u7559\u5728\u6587\u6863\u6D41\u4E2D\uFF0C\u4E0E v-if \u7684\u79FB\u9664\u4E0D\u540C\uFF08v0.3 \u8865\u9F50\uFF0C\u539F\u4E3A MVP \u9650\u5236\uFF09",
366
+ when: "\u5143\u7D20\u4E0A\u6709 v-show \u6307\u4EE4\u65F6",
367
+ example: { before: '<p v-show="show">a</p>', after: '<p hidden="{{!show}}">a</p>' },
368
+ verify: "tests/mp-transform.test.ts v-show \u7528\u4F8B",
369
+ source: "packages/compiler/src/template.ts \u2192 serializeElement\uFF08show \u5206\u652F\uFF09",
370
+ decision: "#76\uFF08v0.3 \u6307\u4EE4\u8865\u5168\uFF09"
371
+ },
372
+ {
373
+ id: "directive/custom",
374
+ phase: "template",
375
+ status: "implemented",
376
+ title: "\u81EA\u5B9A\u4E49\u6307\u4EE4\u5265\u79BB\uFF08\u65E0\u5BF9\u7B49\u8B66\u544A\uFF09",
377
+ description: "v-focus \u7B49\u81EA\u5B9A\u4E49\u6307\u4EE4\u5728\u5C0F\u7A0B\u5E8F\u65E0\u5BF9\u7B49\u673A\u5236\u2014\u2014\u7F16\u8BD1\u671F\u8B66\u544A\u5E76\u5265\u79BB\uFF08\u903B\u8F91\u4E0D\u6267\u884C\uFF09\uFF0C\u4E0D\u518D\u9759\u9ED8",
378
+ why: "\u53CD\u9ED1\u76D2\uFF08vue-compat Batch A\uFF0C\u51B3\u7B56 #116\uFF09\uFF1A\u5E73\u53F0\u65E0\u5BF9\u7B49\u80FD\u529B\u5FC5\u987B\u7F16\u8BD1\u671F\u663E\u5F0F\u8B66\u544A",
379
+ when: "\u5143\u7D20\u4E0A\u6709\u975E\u5185\u7F6E v-xxx \u6307\u4EE4\uFF08\u4E0D\u5728 if/else/for/show/model/html/text/on/bind/slot/pre/once/cloak\uFF09",
380
+ example: { before: "<input v-focus />", after: "<input /> + \u8B66\u544A\uFF08\u5DF2\u5265\u79BB\uFF09" },
381
+ verify: "tests/vue-compat.test.ts \u81EA\u5B9A\u4E49\u6307\u4EE4\u7528\u4F8B",
382
+ source: "packages/compiler/src/template.ts \u2192 serializeElement\uFF08switch default \u5206\u652F\uFF09",
383
+ decision: "#116"
384
+ },
385
+ {
386
+ id: "template/is-component",
387
+ phase: "template",
388
+ status: "implemented",
389
+ title: "\u52A8\u6001\u7EC4\u4EF6 <component :is> \u65E0\u5BF9\u7B49\u8B66\u544A",
390
+ description: "<component :is> \u52A8\u6001\u7EC4\u4EF6\u5728\u5C0F\u7A0B\u5E8F\u65E0\u5BF9\u7B49\u673A\u5236\u2014\u2014\u8B66\u544A\uFF08\u4EA7\u7269\u4E3A\u65E0\u6548\u6807\u7B7E\uFF09\uFF0C\u5EFA\u8BAE v-if/v-else \u6761\u4EF6\u6E32\u67D3",
391
+ why: "\u53CD\u9ED1\u76D2\uFF08vue-compat Batch A\uFF0C\u51B3\u7B56 #116\uFF09\uFF1A\u4E0D\u518D\u9759\u9ED8\u8F93\u51FA\u65E0\u6548\u4EA7\u7269",
392
+ when: "\u6A21\u677F\u51FA\u73B0 <component> \u6807\u7B7E",
393
+ example: { before: '<component :is="which" />', after: "\u8B66\u544A + \u539F\u6837\u8F93\u51FA\uFF08\u65E0\u6548\u6807\u7B7E\uFF09" },
394
+ verify: "tests/vue-compat.test.ts \u52A8\u6001\u7EC4\u4EF6\u7528\u4F8B",
395
+ source: "packages/compiler/src/template.ts \u2192 serializeElement\uFF08component \u5206\u652F\uFF09",
396
+ decision: "#116"
397
+ },
398
+ {
399
+ id: "event/inline-expression",
400
+ phase: "template",
401
+ status: "implemented",
402
+ title: "\u5185\u8054\u4E8B\u4EF6\u8868\u8FBE\u5F0F \u2192 \u5305\u88C5\u65B9\u6CD5\uFF08vue-compat Batch B\uFF09",
403
+ description: '@click="count++"\uFF08\u81EA\u589E/\u81EA\u51CF\uFF09\u4E0E @click="fn(1)"\uFF08\u7B80\u5355\u65B9\u6CD5\u8C03\u7528\uFF09\u2192 \u751F\u6210 proteusInlineXxx \u5305\u88C5\u65B9\u6CD5\uFF08setData \u66F4\u65B0 / this.fn(1)\uFF09\uFF0C\u4EA7\u7269\u53EF\u8FD0\u884C\uFF1B\u590D\u6742\u8868\u8FBE\u5F0F\u4ECD\u8B66\u544A',
404
+ why: "Vue \u5E38\u89C1\u5199\u6CD5\u652F\u6301\uFF08\u51B3\u7B56 #116 Batch B\uFF09\uFF1A\u4E0D\u518D\u539F\u6837\u8F93\u51FA\u65E0\u6548 bindtap\uFF1B\u5BF9\u9F50 ref \u91CD\u5199\uFF08this.data.x \xB1 1\uFF0C\u51B3\u7B56 #36\uFF09",
405
+ when: "\u4E8B\u4EF6\u5904\u7406\u5668\u4E3A\u81EA\u589E/\u81EA\u51CF\u6216\u7B80\u5355\u65B9\u6CD5\u8C03\u7528\uFF08\u65E0 . \u94FE\uFF09\u65F6",
406
+ example: { before: '@click="count++"', after: 'bindtap="proteusInlineIncCount" + \u65B9\u6CD5 setData({ count: this.data.count + 1 })' },
407
+ verify: "tests/vue-compat.test.ts \u5185\u8054\u8868\u8FBE\u5F0F\u7528\u4F8B",
408
+ source: "packages/compiler/src/template.ts \u2192 tryInlineHandler + script.ts \u2192 inlineHandlers \u6CE8\u5165",
409
+ decision: "#116"
410
+ },
411
+ {
412
+ id: "slot/scoped-slot",
413
+ phase: "template",
414
+ status: "implemented",
415
+ title: "\u4F5C\u7528\u57DF\u63D2\u69FD\u8B66\u544A\uFF08\u2605Batch 7\uFF1AMP/Skyline \u5E73\u53F0\u9650\u5236\u786E\u8BA4 + \u66FF\u4EE3\u6A21\u5F0F\uFF09",
416
+ description: '<slot :item="x"> \u4F5C\u7528\u57DF\u63D2\u69FD\u5728\u5C0F\u7A0B\u5E8F\u65E0\u5BF9\u7B49\u673A\u5236\uFF08\u7236\u4FA7\u62FF\u4E0D\u5230\u5B50\u6570\u636E\uFF09\u2014\u2014\u2605\u5E73\u53F0\u9650\u5236\uFF1A\u5FAE\u4FE1\u65E0\u6A21\u677F\u4F20\u53C2\u673A\u5236\uFF08webview \u7684 template import \u65E0\u6CD5\u52A8\u6001\u9009\u62E9\u591A\u7236\u6A21\u677F\uFF0CSkyline \u4E0D\u652F\u6301\u8DE8\u6587\u4EF6\u6A21\u677F\uFF09\uFF0Cuni-app/Taro MP \u7AEF\u540C\u6837\u4E0D\u5B8C\u6574\u2014\u2014\u7F16\u8BD1\u671F\u8B66\u544A + \u66FF\u4EE3\u6A21\u5F0F\uFF1A\u5B50\u7EC4\u4EF6 props \u63A5\u6536\u6570\u636E + \u81EA\u5B9A\u4E49\u4E8B\u4EF6\u56DE\u8C03\u4F20\u6570\u636E\uFF08<MyList :items :item-tap>\uFF09',
417
+ why: "\u53CD\u9ED1\u76D2\uFF08vue-compat-advance Batch 1/7\uFF0C\u51B3\u7B56 #117\uFF09\uFF1A\u4E0D\u518D\u9759\u9ED8\u8F93\u51FA\u65E0\u6548\u5C5E\u6027\uFF1B\u8FD0\u884C\u65F6\u7B49\u4EF7\u53D7 MP \u5E73\u53F0\u80FD\u529B\u9650\u5236\uFF08\u975E\u5F85\u529E\uFF0C\u66FF\u4EE3\u6A21\u5F0F props+\u4E8B\u4EF6\u5DF2\u7531\u7EC4\u4EF6\u7CFB\u7EDF\u5B8C\u6574\u652F\u6301\uFF09",
418
+ when: "<slot> \u5143\u7D20\u4E0A\u6709 v-bind \u5C5E\u6027\uFF08\u975E class/style/name\uFF09",
419
+ example: { before: '<slot :item="item" />', after: "<slot /> + \u8B66\u544A\uFF08\u66FF\u4EE3\uFF1Aprops \u4F20\u5B50 + triggerEvent \u4E8B\u4EF6\u56DE\u8C03\uFF09" },
420
+ verify: "tests/vue-compat-advance.test.ts \u4F5C\u7528\u57DF\u63D2\u69FD\u7528\u4F8B",
421
+ source: "packages/compiler/src/template.ts \u2192 serializeElement\uFF08bind \u5206\u652F slot \u68C0\u6D4B\uFF09",
422
+ decision: "#117"
423
+ },
424
+ {
425
+ id: "transition/component",
426
+ phase: "template",
427
+ status: "implemented",
428
+ title: "<transition> \u2192 \u88C5\u9970\u5F0F\u8FDB\u5165\u52A8\u753B\uFF08\u5B50\u5143\u7D20\u6CE8\u5165\u52A8\u753B class\uFF09",
429
+ description: '<transition name="fade"> \u88C5\u9970\u5F0F\u5904\u7406\uFF1A\u8FC7\u6E21\u6807\u7B7E\u4E0D\u8F93\u51FA\uFF0C\u5B50\u5143\u7D20\u6CE8\u5165 class="proteus-transition-{name}"\uFF08\u8FDB\u5165\u52A8\u753B\u7531\u91CD\u5EFA\u81EA\u52A8\u64AD\u653E\uFF09\uFF1Bwxss \u6309 usesTransition \u6309\u9700\u6CE8\u5165 keyframes\uFF08fade/slide-up/scale\uFF09',
430
+ why: "Vue <transition> \u8FD0\u884C\u65F6\u7B49\u4EF7\uFF08vue-compat-advance Batch 2\uFF0C\u51B3\u7B56 #117\uFF09\uFF1AMP \u65E0\u539F\u751F Transition\uFF0C\u7F16\u8BD1\u6CE8\u5165\u52A8\u753B class + keyframes \u8865\u4F4D\uFF1B\u4E0E\u8DEF\u7531 routeType \u8F6C\u573A\u4E92\u8865\uFF08\u5143\u7D20\u7EA7 vs \u9875\u9762\u7EA7\uFF09",
431
+ when: "\u6A21\u677F\u51FA\u73B0 <transition> \u6807\u7B7E",
432
+ example: { before: '<transition name="fade"><view v-if="on">X</view></transition>', after: '<view class="proteus-transition-fade" wx:if="{{__tv0}}">X</view>' },
433
+ verify: "tests/vue-compat-advance.test.ts Transition \u7528\u4F8B",
434
+ source: "packages/compiler/src/template.ts \u2192 serializeElement\uFF08transition \u5206\u652F\uFF09+ style.ts TRANSITION_WXSS",
435
+ decision: "#117"
436
+ },
437
+ {
438
+ id: "transition/leave-state",
439
+ phase: "template",
440
+ status: "implemented",
441
+ title: "<transition> \u79BB\u5F00\u52A8\u753B\u72B6\u6001\u673A\uFF08\u88F8 ref v-if \u5EF6\u8FDF\u79FB\u9664\uFF09",
442
+ description: 'transition \u76F4\u63A5\u5B50\u5143\u7D20 v-if \u4E3A\u88F8 ref \u540D\u65F6\u542F\u7528\u79BB\u5F00\u52A8\u753B\uFF1Av-if \u6539\u5199 wx:if="{{__tv{i}}}"\uFF08\u663E\u793A\u72B6\u6001\uFF0C\u521D\u59CB = ref \u521D\u59CB\u503C\uFF09+ class \u63D2\u503C {{__tl{i} ? "...-leave" : ""}}\uFF08\u79BB\u5F00\u4E2D\u5207\u6362 leave \u52A8\u753B\uFF09\uFF1Bscript \u751F\u6210 proteusTransitionToggle{i}()\uFF08ref \u5199\u5165\u70B9\u6CE8\u5165\uFF1Aon \u53D8 false \u2192 __tl{i}=true \u64AD\u79BB\u5F00\u52A8\u753B + setTimeout \u65F6\u957F\u540E __tv{i}=false \u5EF6\u8FDF\u79FB\u9664\uFF1Bon \u53D8 true \u2192 \u53D6\u6D88\u5B9A\u65F6\u5668\u6062\u590D\u8FDB\u5165\u52A8\u753B\uFF09\uFF1Bwxss \u6309\u9700\u6CE8\u5165 leave class + keyframes\uFF08forwards \u4FDD\u6301\u672B\u5E27\uFF09',
443
+ why: "Vue transition \u79BB\u5F00\u8BED\u4E49\uFF1Av-if \u53D8 false \u5148\u64AD\u79BB\u5F00\u52A8\u753B\u518D\u79FB\u9664\uFF08vue-compat-advance Batch 5\uFF09\uFF1BMP wx:if \u7ACB\u5373\u79FB\u9664\u65E0\u52A8\u753B\u2014\u2014\u7F16\u8BD1\u671F\u72B6\u6001\u673A\u8865\u4F4D\uFF1B\u4EC5\u88F8 ref v-if \u652F\u6301\uFF08\u590D\u6742\u8868\u8FBE\u5F0F\u4FDD\u6301 Batch 2 \u73B0\u72B6\uFF09",
444
+ when: "<transition> \u76F4\u63A5\u5B50\u5143\u7D20\u5E26 v-if \u4E14\u8868\u8FBE\u5F0F\u4E3A\u88F8 ref \u540D",
445
+ example: { before: '<transition name="fade"><view v-if="on">X</view></transition>', after: `<view wx:if="{{__tv0}}" class="proteus-transition-fade {{__tl0 ? 'proteus-transition-fade-leave' : ''}}">X</view> + data __tv0/__tl0 + proteusTransitionToggle0()` },
446
+ verify: "tests/vue-compat-advance.test.ts Batch 5 \u7528\u4F8B",
447
+ source: "packages/compiler/src/template.ts \u2192 serializeElement\uFF08v-if \u5206\u652F + class \u6CE8\u5165\uFF09+ script.ts\uFF08data/\u65B9\u6CD5/\u5199\u5165\u70B9\u6CE8\u5165\uFF09+ style.ts TRANSITION_WXSS",
448
+ decision: "#117"
449
+ },
450
+ {
451
+ id: "template/template-ref",
452
+ phase: "template",
453
+ status: "implemented",
454
+ title: "\u6A21\u677F ref \u65E0\u5BF9\u7B49\u8B66\u544A",
455
+ description: 'ref="el" \u6A21\u677F ref \u5728\u5C0F\u7A0B\u5E8F\u65E0\u5BF9\u7B49\u7ED1\u5B9A\uFF08\u6C38\u4E0D\u8D4B\u503C\uFF09\u2014\u2014\u8B66\u544A\uFF0C\u5EFA\u8BAE this.selectComponent("#id")',
456
+ why: "\u53CD\u9ED1\u76D2\uFF08vue-compat Batch A\uFF0C\u51B3\u7B56 #116\uFF09\uFF1A\u4E0D\u518D\u9759\u9ED8\u65E0\u6548",
457
+ when: "\u5143\u7D20\u4E0A\u6709 ref \u5C5E\u6027",
458
+ example: { before: '<input ref="el" />', after: "<input /> + \u8B66\u544A" },
459
+ verify: "tests/vue-compat.test.ts \u6A21\u677F ref \u7528\u4F8B",
460
+ source: "packages/compiler/src/template.ts \u2192 serializeElement\uFF08ref \u5C5E\u6027\u5206\u652F\uFF09",
461
+ decision: "#116"
462
+ },
463
+ {
464
+ id: "template/no-peer",
465
+ phase: "template",
466
+ status: "implemented",
467
+ title: "\u5E73\u53F0\u65E0\u5BF9\u7B49\u7EC4\u4EF6\u8B66\u544A\uFF08Transition/Teleport \u7B49\uFF09",
468
+ description: "transition/transition-group/teleport/suspense/keep-alive \u5728\u5C0F\u7A0B\u5E8F\u65E0\u5BF9\u7B49\u7EC4\u4EF6\u2014\u2014\u8B66\u544A\uFF08\u539F\u6837\u8F93\u51FA\u4E0D\u751F\u6548\uFF09",
469
+ why: "\u53CD\u9ED1\u76D2\uFF08vue-compat Batch A\uFF0C\u51B3\u7B56 #116\uFF09\uFF1A\u8F6C\u573A\u8BF7\u7528\u8DEF\u7531 routeType\uFF0C\u7F13\u5B58/\u4F20\u9001\u8BF7\u79FB\u9664",
470
+ when: "\u6A21\u677F\u51FA\u73B0\u8FD9\u4E9B\u6807\u7B7E",
471
+ example: { before: '<transition name="fade">\u2026</transition>', after: "\u8B66\u544A + \u539F\u6837\u8F93\u51FA" },
472
+ verify: "tests/vue-compat.test.ts \u65E0\u5BF9\u7B49\u7EC4\u4EF6\u7528\u4F8B",
473
+ source: "packages/compiler/src/template.ts \u2192 serializeElement\uFF08no-peer \u5206\u652F\uFF09",
474
+ decision: "#116"
475
+ },
476
+ // ============ 导航链接 ============
477
+ {
478
+ id: "nav/navigate-link",
479
+ phase: "template",
480
+ status: "implemented",
481
+ title: "<a href> / <router-link to> \u2192 \u5BFC\u822A\u94FE\u63A5",
482
+ description: '\u5E26 href/to \u7684\u5BFC\u822A\u94FE\u63A5 \u2192 data-url + data-route-type + bindtap="proteusNavigateTo"\uFF08handler \u7531 script/nav-handler \u6CE8\u5165\uFF09',
483
+ why: "Web \u7AEF <a> \u8D70\u6D4F\u89C8\u5668\u5BFC\u822A\uFF0C\u5C0F\u7A0B\u5E8F\u7AEF\u9700\u8F6C data-url + \u70B9\u51FB\u8DF3\u8F6C\uFF08\u51B3\u7B56 #24\uFF09\uFF1B\u4FDD\u7559\u524D\u5BFC / \u4E3A\u7EDD\u5BF9\u8DEF\u5F84\uFF08#30 \u771F\u673A\u6559\u8BAD\uFF1A\u65E0\u524D\u5BFC / \u4F1A\u76F8\u5BF9\u5F53\u524D\u9875\u76EE\u5F55\u89E3\u6790\u5BFC\u81F4\u53CC\u91CD\u524D\u7F00\uFF09",
484
+ when: "a \u6216 router-link \u4E14\u65E0 @click\u3001\u542B href/to\uFF08\u9759\u6001\u6216 :bind\uFF09\u65F6",
485
+ example: {
486
+ before: '<a href="/pages/user/index">\u7528\u6237</a>',
487
+ after: '<view class="proteus-a" data-url="/pages/user/index" bindtap="proteusNavigateTo">\u7528\u6237</view>'
488
+ },
489
+ verify: "tests/mp-transform.test.ts \u5BFC\u822A\u94FE\u63A5\u7528\u4F8B\uFF1B\u771F\u673A\u9A8C\u8BC1\u5F52\u6863\u51B3\u7B56 #30",
490
+ source: "src/compiler/template.ts \u2192 serializeElement\uFF08isNavLink / hasNavTarget \u5206\u652F\uFF09",
491
+ decision: "#24 / #30 / #29"
492
+ },
493
+ {
494
+ id: "nav/route-type",
495
+ phase: "template",
496
+ status: "implemented",
497
+ title: "\u5BFC\u822A\u94FE\u63A5 route-type \u5C5E\u6027 \u2192 data-route-type",
498
+ description: '<a route-type="halfScreen"> \u2192 data-route-type="halfScreen"\uFF08proteusNavigateTo \u900F\u4F20\u4E3A wx.navigateTo \u7684 routeType\uFF09',
499
+ why: "Skyline \u81EA\u5B9A\u4E49\u8DEF\u7531\u8F6C\u573A\u4ECE\u5BFC\u822A\u94FE\u63A5\u53D1\u8D77\u65F6\uFF0C\u9700\u628A routeType \u4F20\u5230\u8FD0\u884C\u671F\uFF08\u51B3\u7B56 #44\uFF1ArouteType \u53CC\u7AEF\u540C API\uFF09",
500
+ when: "\u5BFC\u822A\u94FE\u63A5\u5143\u7D20\u4E0A\u6709 route-type \u5C5E\u6027\u65F6",
501
+ example: {
502
+ before: '<a href="/pages/user/profile" route-type="halfScreen">\u8D44\u6599</a>',
503
+ after: '<view data-url="/pages/user/profile" data-route-type="halfScreen" bindtap="proteusNavigateTo">\u8D44\u6599</view>'
504
+ },
505
+ verify: "tests/mp-transform.test.ts \u5BFC\u822A\u94FE\u63A5\u7528\u4F8B",
506
+ source: "src/compiler/template.ts \u2192 serializeElement\uFF08route-type \u5206\u652F\uFF09",
507
+ decision: "#28 / #44"
508
+ },
509
+ // ============ 通用节点 ============
510
+ {
511
+ id: "node/interpolation",
512
+ phase: "template",
513
+ status: "implemented",
514
+ title: "\u63D2\u503C {{ expr }} \u4FDD\u7559",
515
+ description: "{{ title }} \u2192 {{ title }}\uFF08\u8868\u8FBE\u5F0F\u539F\u6837\u900F\u4F20\uFF0C\u6587\u672C\u8282\u70B9\u7D27\u51D1\u5355\u884C\u8F93\u51FA\uFF09",
516
+ why: "\u5C0F\u7A0B\u5E8F\u63D2\u503C\u8BED\u6CD5\u540C\u4E3A {{expr}}\uFF0C\u65E0\u9700\u8F6C\u6362\uFF1B\u7EAF\u6587\u672C\u5B50\u8282\u70B9\u7D27\u51D1\u5355\u884C\u4FDD\u8BC1\u4EA7\u7269\u53EF\u8BFB\uFF08\u51B3\u7B56 #15\uFF09",
517
+ when: "\u6A21\u677F\u51FA\u73B0 {{ }} \u63D2\u503C\u6216\u7EAF\u6587\u672C\u5B50\u8282\u70B9\u65F6",
518
+ example: { before: "<p>tapped {{ count }} times</p>", after: '<text class="proteus-p">tapped {{ count }} times</text>' },
519
+ verify: "golden fixture basic.wxml",
520
+ source: "src/compiler/template.ts \u2192 serializeNode\uFF08TEXT / INTERPOLATION \u5206\u652F\uFF09",
521
+ decision: "#15"
522
+ },
523
+ {
524
+ id: "annotation/line-note",
525
+ phase: "template",
526
+ status: "implemented",
527
+ title: "PROTEUS_DEBUG \u6E90\u7801\u884C\u53F7\u6CE8\u91CA",
528
+ description: "annotateLines=true \u65F6 WXML \u6BCF\u4E2A\u5143\u7D20\u524D\u6CE8\u5165 <!-- @\u884C\u53F7 \u6807\u7B7E -->\uFF0C\u4EA7\u7269\u53EF\u53CD\u67E5\u6E90\u7801\u4F4D\u7F6E",
529
+ why: "\u53CD\u7F16\u8BD1\u9ED1\u76D2\u673A\u5236\uFF08#17\uFF09\uFF1A\u9ED8\u8BA4\u5173\u95ED\uFF0Cnpm run debug:mp \u5F00\u542F\u2014\u2014AI/\u4EBA\u62FF\u5230\u4EA7\u7269\u5373\u53EF\u5B9A\u4F4D\u5230\u6E90\u7801\u884C",
530
+ when: "\u7F16\u8BD1\u9009\u9879 annotateLines=true\uFF08PROTEUS_DEBUG=1 \u6784\u5EFA\uFF09\u65F6",
531
+ example: { before: "\u7B2C 26 \u884C <h1>{{ title }}</h1>", after: '<!-- @26 h1 -->\n<text class="proteus-h1">{{ title }}</text>' },
532
+ verify: "tests/mp-transform.test.ts annotateLines \u7528\u4F8B\uFF1BPROJECT_MEMORY #17",
533
+ source: "src/compiler/template.ts \u2192 serializeElement\uFF08lineNote \u5206\u652F\uFF09",
534
+ decision: "#17"
535
+ },
536
+ {
537
+ id: "template/scope-attr",
538
+ phase: "template",
539
+ status: "implemented",
540
+ title: "scoped CSS\uFF1A\u7528\u6237 class \u4E0E scopeId \u62FC\u63A5\u4E3A\u5355\u4E00\u7C7B\uFF08\u26052026-08 \u771F\u673A\u91CD\u6784\uFF1A\u7C7B\u540D\u540E\u7F00\uFF09",
541
+ description: "<style scoped> \u5B58\u5728\u65F6\uFF0C\u6A21\u677F\u5143\u7D20 class \u503C token \u8FFD\u52A0 -scopeId\uFF08.box \u2192 box-data-v-xxx\uFF09\uFF1B:class \u5B57\u7B26\u4E32\u5B57\u9762\u91CF/\u5BF9\u8C61\u952E\u540C\u6837\u540E\u7F00\uFF08\u52A8\u6001\u53D8\u91CF\u7C7B\u540D\u7F16\u8BD1\u671F\u8B66\u544A\uFF09\uFF1B\u6837\u5F0F\u4FA7\u9009\u62E9\u5668 .box-data-v-xxx \u5339\u914D",
542
+ why: "\u5C0F\u7A0B\u5E8F\u65E0 scoped CSS \u539F\u751F\u673A\u5236\uFF0C\u7F16\u8BD1\u671F\u7C7B\u540D\u540E\u7F00\u7B49\u4EF7\uFF08v0.3\uFF0C\u51B3\u7B56 #77\uFF09\uFF1B\u26052026-08 \u771F\u673A\u91CD\u6784\uFF1ASkyline \u4E0D\u652F\u6301\u5C5E\u6027\u9009\u62E9\u5668/\u590D\u5408\u7C7B\u9009\u62E9\u5668 \u2192 \u7C7B\u540D\u62FC\u63A5\u4E3A\u552F\u4E00\u5355\u7C7B\u9009\u62E9\u5668\u8DEF\u5F84",
543
+ when: "SFC \u542B <style scoped>\uFF08compileVueSfc \u751F\u6210 scopeId \u5E76\u6CE8\u5165\uFF09\u65F6",
544
+ example: { before: '<div class="card">\u2026</div>', after: '<view class="card-data-v-abc123">\u2026</view>' },
545
+ verify: "tests/mp-transform.test.ts scoped CSS \u7528\u4F8B",
546
+ source: "packages/compiler/src/template.ts \u2192 serializeElement\uFF08scopeSuffix \u5206\u652F\uFF09",
547
+ decision: "#77\uFF08v0.3 scoped CSS\uFF09+ 2026-08 Skyline \u771F\u673A\u91CD\u6784\uFF08\u7C7B\u540D\u540E\u7F00\uFF09",
548
+ // ★分派层示范:输入 { tag, scopeId } → 输出作用域后缀片段(AI 覆盖可改后缀名)
549
+ apply: (ctx) => {
550
+ const input = ctx.input;
551
+ ctx.output = input.scopeId;
552
+ }
553
+ },
554
+ {
555
+ id: "component/root-class",
556
+ phase: "template",
557
+ status: "implemented",
558
+ title: "\u7EC4\u4EF6\u6807\u7B7E class \u900F\u4F20\uFF1Aroot-class \u5C5E\u6027 \u2192 \u7EC4\u4EF6\u6839\u8282\u70B9 {{rootClass}}\uFF08Vue class \u7EE7\u627F\u8BED\u4E49\uFF09",
559
+ description: '\u81EA\u5B9A\u4E49\u7EC4\u4EF6\u6807\u7B7E\uFF08\u975E\u539F\u751F\u57FA\u7840\u6807\u7B7E\uFF09\u7684 class\uFF08scope class + \u7528\u6237 class + :class \u7ED1\u5B9A\uFF09\u5408\u5E76\u4E3A\u5355\u4E2A root-class \u5C5E\u6027\u53D1\u5C04\uFF1B\u7EC4\u4EF6\u6A21\u677F\u6839\u8282\u70B9 class \u8FFD\u52A0 {{rootClass}}\uFF1Bscript \u4FA7\u7EC4\u4EF6\u6CE8\u5165 rootClass property\uFF08value: ""\uFF09',
560
+ why: "Vue \u7684 class \u7EE7\u627F\u8BED\u4E49\uFF08\u7236\u7EC4\u4EF6 class \u4F5C\u7528\u4E8E\u5B50\u7EC4\u4EF6\u6839\u8282\u70B9\uFF09\u5728\u5FAE\u4FE1\u65E0\u539F\u751F\u5BF9\u7B49\u2014\u2014\u7EC4\u4EF6 host \u8282\u70B9 class \u5408\u5E76\u540E\uFF0C\u9875\u9762 wxss \u65E0\u6CD5\u53EF\u9760\u4F5C\u7528\uFF08\u771F\u673A\u5B9E\u6D4B\uFF1Ap-view \u5916\u5C42\u5BB9\u5668 box \u6837\u5F0F\u4E0D\u751F\u6548\uFF0C\u5373\u4F7F styleIsolation: apply-shared\uFF09\u2014\u2014\u7F16\u8BD1\u671F\u7B49\u4EF7\uFF1Aclass \u7ECF root-class \u5C5E\u6027\u4F20\u5165\u7EC4\u4EF6\uFF0C\u6839\u8282\u70B9\u7ED1\u5B9A {{rootClass}}\uFF0C\u914D\u5408 apply-shared \u8BA9\u9875\u9762\u6837\u5F0F\u4F5C\u7528\u7EC4\u4EF6\u6839\u8282\u70B9",
561
+ when: "\u6A21\u677F\u51FA\u73B0\u975E\u5C0F\u7A0B\u5E8F\u539F\u751F\u6807\u7B7E\uFF08\u7EC4\u4EF6\u6807\u7B7E\uFF0C\u5982 p-view/counter\uFF09\u4E14\u89C4\u5219\u672A\u7981\u7528\u65F6\uFF1B\u7EC4\u4EF6\u6A21\u5F0F\u6839\u8282\u70B9\u7ED1\u5B9A",
562
+ example: { before: '<p-view class="box">\u2026</p-view>', after: '<p-view root-class="data-v-abc123 box" />\uFF08\u7EC4\u4EF6\u6839\u8282\u70B9 class="\u2026 {{rootClass}}"\uFF09' },
563
+ verify: "tests/mp-transform.test.ts \u7EC4\u4EF6 class \u900F\u4F20\u7528\u4F8B",
564
+ source: "packages/compiler/src/template.ts \u2192 serializeElement\uFF08isComponentTag \u5206\u652F\uFF09+ transformTemplateToWxml\uFF08isComponentRoot\uFF09\uFF1Bpackages/compiler/src/script.ts \u2192 transformScriptToPage\uFF08properties.rootClass\uFF09",
565
+ decision: "2026-08 \u771F\u673A\u5B9E\u6D4B\uFF08Skyline\uFF09"
566
+ },
567
+ {
568
+ id: "layout/auto-flex-row",
569
+ phase: "template",
570
+ status: "implemented",
571
+ title: "\u884C\u5185\u573A\u666F\u81EA\u52A8 flex row\uFF08Skyline \u65E0 inline \u5E03\u5C40\uFF09",
572
+ description: "\u5BB9\u5668\u76F4\u63A5\u5B50\u5143\u7D20\u540C\u65F6\u542B text \u4E0E\u884C\u5185\u63A7\u4EF6\uFF08switch/slider/icon/image/button/input/textarea/checkbox/radio/label/navigator/progress\uFF09\u2192 \u81EA\u52A8\u9644\u52A0 proteus-flex-row \u7C7B\uFF08display:flex;row;align-items:center\uFF09\uFF1BBASE \u6CE8\u5165\u5BF9\u5E94\u89C4\u5219",
573
+ why: "Skyline \u5F15\u64CE\u4E0D\u652F\u6301 inline \u5E03\u5C40\uFF08\u5B98\u65B9 Inline \xD7 \u5F00\u53D1\u4E2D\uFF09\u2014\u2014text \u5929\u751F block \u5360\u6EE1\u4E00\u884C\uFF0C\u884C\u5185\u6392\u5E03\uFF08text + switch \u540C\u884C\uFF09\u552F\u4E00\u8DEF\u5F84\u662F flex row\uFF08\u7528\u6237\u5B9E\u6D4B\uFF09\uFF1B\u81EA\u52A8\u68C0\u6D4B\u514D\u5F00\u53D1\u8005\u624B\u52A8\u5305 flex\uFF08\u53CC\u7AEF\u4E00\u81F4\uFF09",
574
+ when: "\u5BB9\u5668\u5B50\u5143\u7D20\u542B text \u4E14\u542B\u884C\u5185\u63A7\u4EF6\uFF08rules.disabled \u53EF\u5173\uFF09",
575
+ example: { before: "<view><switch/><text>\u5F00\u5173</text></view>", after: '<view class="proteus-flex-row"><switch/><text>\u5F00\u5173</text></view>' },
576
+ verify: "tests/mp-transform.test.ts \u884C\u5185 flex \u7528\u4F8B",
577
+ source: "packages/compiler/src/template.ts \u2192 serializeElement\uFF08autoFlexRow \u5224\u5B9A\uFF09+ style.ts \u2192 BASE_SEMANTIC_WXSS",
578
+ decision: "2026-08 \u7528\u6237\u51B3\u7B56\uFF08Skyline inline \u9650\u5236\uFF09"
579
+ },
580
+ {
581
+ id: "component/progress-degrade",
582
+ phase: "template",
583
+ status: "implemented",
584
+ title: "progress \u964D\u7EA7\u81EA\u5B9A\u4E49 view \u8FDB\u5EA6\u6761\uFF08Skyline \u5B98\u65B9\u4E0D\u652F\u6301 progress\uFF09",
585
+ description: "\u5C0F\u7A0B\u5E8F\u8BED\u4E49 <progress> \u7F16\u8BD1\u4E3A\u81EA\u5B9A\u4E49 view \u7ED3\u6784\uFF08track/inner/info \u4E09\u8282\u70B9\uFF09\u2014\u2014percent\u2192\u5BBD\u5EA6\u3001active-color/color\u2192\u586B\u5145\u8272\u3001stroke-width\u2192\u9AD8\u5EA6\u3001show-info\uFF08\u65E0\u503C=\u771F\uFF09\u2192\u767E\u5206\u6BD4\u6587\u5B57\uFF1B\u9759\u6001\u5C5E\u6027\u76F4\u51FA\u3001\u7ED1\u5B9A\u63D2\u503C\uFF1BBASE \u6CE8\u5165 .proteus-progress \u6837\u5F0F",
586
+ why: "Skyline \u7EC4\u4EF6\u652F\u6301\u8868 progress \u6682\u4E0D\u8003\u8651\uFF08\u771F\u673A\u5B9E\u6D4B\u4E0D\u6E32\u67D3\uFF09\u2014\u2014\u964D\u7EA7\u81EA\u5B9A\u4E49\u7ED3\u6784\u53CC\u7AEF\u4E00\u81F4 + Skyline \u53EF\u7528\uFF0816-progress-skyline-degrade\uFF09",
587
+ when: "\u6A21\u677F\u51FA\u73B0 <progress> \u4E14\u89C4\u5219\u672A\u7981\u7528\u65F6",
588
+ example: { before: '<progress :percent="70" show-info />', after: '<view class="proteus-progress"><view class="proteus-progress-track"><view class="proteus-progress-inner" style="width:{{70}}%;background-color:#07c160"></view></view><text wx:if="{{true}}" class="proteus-progress-info">{{70}}%</text></view>' },
589
+ verify: "tests/mp-transform.test.ts progress \u964D\u7EA7\u7528\u4F8B",
590
+ source: "packages/compiler/src/template.ts \u2192 serializeProgress + style.ts \u2192 BASE_SEMANTIC_WXSS",
591
+ decision: "2026-08 \u771F\u673A\u5B9E\u6D4B\uFF08Skyline \u4E0D\u652F\u6301 progress\uFF09"
592
+ }
593
+ ];
594
+ var TAG_RULE_BY_TAG = Object.fromEntries(
595
+ TEMPLATE_RULES.filter((r) => r.id.startsWith("tag/")).flatMap(
596
+ (r) => r.mapping ? Object.keys(r.mapping).map((t) => [t, r.id]) : []
597
+ )
598
+ );
599
+
600
+ // src/transforms/script.ts
601
+ var SCRIPT_RULES = [
602
+ {
603
+ id: "script/const-to-data",
604
+ phase: "script",
605
+ status: "implemented",
606
+ title: "\u9876\u5C42 const\uFF08ref/reactive/\u5B57\u9762\u91CF\uFF09\u2192 data",
607
+ description: "\u96F6\u7F29\u8FDB\u9876\u5C42 const \u2192 data \u5B57\u6BB5\uFF1Bref(0)/reactive({...})/\u5B57\u9762\u91CF\u5728\u6784\u5EFA\u671F\u9759\u6001\u6C42\u503C\uFF0C\u591A\u884C\u6570\u7EC4/\u5BF9\u8C61\u5B57\u9762\u91CF\u5B8C\u6574\u63D0\u53D6",
608
+ why: "\u5C0F\u7A0B\u5E8F\u9875\u9762\u72B6\u6001\u5728 data \u4E2D\uFF0C\u54CD\u5E94\u5F0F\u58F0\u660E\uFF08ref/reactive\uFF09\u7F16\u8BD1\u671F\u4E3A\u521D\u59CB\u503C\u6C42\u503C\uFF08\u51B3\u7B56 #60\uFF1A\u62EC\u53F7\u5E73\u8861\u626B\u63CF\u652F\u6301\u591A\u884C\u5B57\u9762\u91CF\uFF1B\u53EA\u63D0\u53D6\u96F6\u7F29\u8FDB\u9876\u5C42 const\uFF0C\u4E0D\u8BEF\u53D6\u51FD\u6570\u4F53\u5185\u5C40\u90E8 const\uFF09",
609
+ when: "script \u9876\u5C42\u51FA\u73B0 const \u58F0\u660E\u4E14\u521D\u59CB\u503C\u4E3A\u5B57\u9762\u91CF/ref/reactive/shallowRef/readonly \u65F6",
610
+ example: {
611
+ before: 'const count = ref(0)\nconst cards = ref([{ title: "a" }])',
612
+ after: 'data: {\n count: 0,\n cards: [{ "title": "a" }],\n}'
613
+ },
614
+ verify: "tests/mp-transform.test.ts data \u63D0\u53D6\u7528\u4F8B\uFF1Bgolden fixture showcase.js\uFF08\u591A\u884C\u6570\u7EC4\uFF09",
615
+ source: "packages/compiler/src/script.ts \u2192 extractData + extractInitializer",
616
+ decision: "#60 / #22"
617
+ },
618
+ {
619
+ id: "script/computed-to-data",
620
+ phase: "script",
621
+ status: "implemented",
622
+ title: "computed \u8BFB\u8DEF\u5F84 \u2192 data \u6D3E\u751F\u5B57\u6BB5\uFF08v0.3\uFF09",
623
+ description: "\u9876\u5C42 const x = computed(() => expr) \u2192 data \u4E0D\u5B58\uFF1BonLoad \u521D\u59CB\u5316 + \u4F9D\u8D56 ref \u5199\u5165 setData \u65F6\u540C\u6B65\u91CD\u7B97\uFF08expr \u4E2D x.value \u2192 this.data.x\uFF09",
624
+ why: "\u5C0F\u7A0B\u5E8F\u65E0 computed \u6982\u5FF5\uFF0C\u7F16\u8BD1\u671F\u628A getter \u8F6C data \u6D3E\u751F\uFF1A\u9759\u6001\u63D0\u53D6 getter \u4E2D\u7684 ref \u4F9D\u8D56 \u2192 \u4F9D\u8D56\u5199\u5165\u65F6\u628A\u91CD\u7B97\u8868\u8FBE\u5F0F\u5408\u5E76\u8FDB\u540C\u4E00 setData\uFF08v0.3 \u5148\u505A\u8BFB\u8DEF\u5F84\uFF0Cwatch/\u5199\u8DEF\u5F84\u540E\u7EED\uFF09",
625
+ when: "script \u9876\u5C42\u51FA\u73B0 computed(() => \u8868\u8FBE\u5F0F)\uFF08\u4EC5\u652F\u6301\u7BAD\u5934\u7B80\u5199 + \u8868\u8FBE\u5F0F\u4F53\uFF1B\u5757\u4F53/function \u5F62\u5F0F\u7F16\u8BD1\u671F\u8B66\u544A\uFF09",
626
+ example: {
627
+ before: "const double = computed(() => count.value * 2)",
628
+ after: "data \u4E0D\u542B double\uFF1Bthis.setData({ count: ..., double: this.data.count * 2 })\uFF08count \u5199\u5165\u65F6\u5408\u5E76\uFF0ConLoad \u521D\u59CB\u5316\u4E00\u6B21\uFF09"
629
+ },
630
+ verify: "tests/mp-transform.test.ts computed \u7528\u4F8B",
631
+ source: "packages/compiler/src/script.ts \u2192 extractComputedFromInit + computedPatch + computedInitLine",
632
+ decision: "#76\uFF08v0.3\uFF09"
633
+ },
634
+ {
635
+ id: "script/watch-to-methods",
636
+ phase: "script",
637
+ status: "implemented",
638
+ title: "watch \u2192 proteusWatchX \u65B9\u6CD5\uFF08v0.3\uFF09",
639
+ description: "watch(ref, (newVal, oldVal) => { body }) \u2192 methods \u751F\u6210 proteusWatchX\uFF1B\u4F9D\u8D56 ref \u5199\u5165 setData \u540E\u81EA\u52A8\u8C03\u7528\uFF08\u65E7\u503C\u5728\u5199\u5165\u524D\u4FDD\u5B58\uFF09\uFF1Bimmediate: true \u2192 onLoad \u521D\u59CB\u5316\u8C03\u7528\u4E00\u6B21",
640
+ why: "\u5C0F\u7A0B\u5E8F\u65E0 watch \u6982\u5FF5\uFF0C\u7F16\u8BD1\u671F\u6A21\u62DF\uFF1A\u4F9D\u8D56\u9759\u6001\u63D0\u53D6\uFF08\u5355 ref\uFF09+ \u5199\u5165\u70B9\u8054\u52A8\uFF08setData \u540E\u8C03\u7528\u56DE\u8C03\uFF0CnewVal/oldVal \u7531\u7F16\u8BD1\u671F\u4FDD\u5B58\uFF09\uFF1BMVP\uFF1A\u4EC5\u5355 ref \u76F4\u63A5\u5F15\u7528 + \u7BAD\u5934\u51FD\u6570\u56DE\u8C03\uFF08\u6570\u7EC4\u6E90/\u51FD\u6570\u6E90/function \u56DE\u8C03\u8B66\u544A\uFF09",
641
+ when: "script \u9876\u5C42\u51FA\u73B0 watch(refName, callback) \u65F6",
642
+ example: {
643
+ before: "watch(count, (n, o) => {\n log(n, o)\n})",
644
+ after: "proteusWatchCount(n, o) {\n log(n, o)\n},\n// count \u5199\u5165\uFF1Aconst oldCount = this.data.count; ...setData(...); this.proteusWatchCount(this.data.count, oldCount)"
645
+ },
646
+ verify: "tests/mp-transform.test.ts watch \u7528\u4F8B",
647
+ source: "packages/compiler/src/script.ts \u2192 extractWatch + watchTail + immediateWatchLine",
648
+ decision: "#78\uFF08v0.3\uFF09"
649
+ },
650
+ {
651
+ id: "script/watch-props",
652
+ phase: "script",
653
+ status: "implemented",
654
+ title: "watch props \u6E90 \u2192 WeChat observers\uFF08\u7EC4\u4EF6\u5C5E\u6027\u76D1\u542C\uFF09",
655
+ description: "watch(props.x, (n, o) => { body }) \u6216 watch(() => props.x, ...) \u2192 Component observers: { x(n, o) { body } }\uFF08\u5C5E\u6027\u53D8\u5316\u89E6\u53D1\u56DE\u8C03\uFF1BWeb \u7AEF\u5373\u6807\u51C6 Vue watch\uFF0C\u5168\u54CD\u5E94\u5F0F\uFF09\uFF1Bimmediate: true \u2192 \u53E6\u751F\u6210 proteusWatchPropX \u65B9\u6CD5 + attached \u521D\u59CB\u5316\u8C03\u7528\u4E00\u6B21",
656
+ why: "\u7EC4\u4EF6\u9700\u8981\u54CD\u5E94\u81EA\u8EAB\u5C5E\u6027\u53D8\u5316\uFF08\u5217\u8868 items \u5206\u9875/\u52A0\u8F7D\u66F4\u591A\u3001\u5F39\u5C42 visible v-model\u3001\u8868\u5355 value \u540C\u6B65\u7B49\uFF09\uFF1B\u5C0F\u7A0B\u5E8F\u65E0\u54CD\u5E94\u5F0F\u7CFB\u7EDF\uFF0C\u5C5E\u6027\u53D8\u5316\u552F\u4E00\u901A\u9053\u662F observers",
657
+ when: "\u7EC4\u4EF6 script \u9876\u5C42\u51FA\u73B0 watch(props.x, callback) / watch(() => props.x, callback) \u65F6\uFF08MVP\uFF1A\u5355\u5C5E\u6027\u6E90\uFF09",
658
+ example: {
659
+ before: "watch(() => props.items, () => {\n calc()\n})",
660
+ after: "observers: {\n items(n, o) {\n calc()\n },\n}"
661
+ },
662
+ verify: "tests/mp-transform.test.ts props \u6E90 watch \u7528\u4F8B",
663
+ source: "packages/compiler/src/script.ts \u2192 extractWatch\uFF08propField \u5206\u652F\uFF09+ observers \u53D1\u5C04",
664
+ decision: "\u7EC4\u4EF6\u5E93 B3\uFF08p-list-view items \u54CD\u5E94\u5F0F\uFF09"
665
+ },
666
+ {
667
+ id: "script/module-import",
668
+ phase: "script",
669
+ status: "implemented",
670
+ title: "\u8DE8\u6A21\u5757\u5F15\u7528\uFF1Aimport \u2192 require \u8F6C\u6362\uFF08module-plan B0\uFF09/ \u5265\u79BB\u8B66\u544A",
671
+ description: 'setup \u9876\u5C42 import\uFF1A\u76F8\u5BF9\u8DEF\u5F84\u5171\u4EAB\u6A21\u5757\uFF08moduleImports \u547D\u4E2D\uFF09\u2192 \u4EA7\u7269\u9876\u90E8 const { x } = require("<\u76F8\u5BF9\u4EA7\u7269\u8DEF\u5F84>.js")\uFF08named/default/namespace/side \u56DB\u5F62\u6001\uFF09\uFF1Bvue/@proteus-vue/*/type/.vue \u8DF3\u8FC7\uFF08\u7F16\u8BD1\u5668\u9759\u6001/usingComponents/\u7EAF\u7C7B\u578B\uFF09\uFF1B\u65E0\u6CD5\u89E3\u6790\u7684\u8DEF\u5F84 \u2192 \u5265\u79BB + \u8B66\u544A',
672
+ why: "\u53CD\u9ED1\u76D2\uFF08vue-compat Batch A\uFF0C\u51B3\u7B56 #116\uFF09\uFF1Aimport \u5265\u79BB\u4E0D\u518D\u9759\u9ED8\uFF0C\u4EA7\u7269\u672A\u5B9A\u4E49\u5F15\u7528\u663E\u5F0F\u63D0\u793A\uFF1B\u2605module-plan B0\uFF1A\u76F8\u5BF9\u8DEF\u5F84\u5171\u4EAB\u6A21\u5757\u7F16\u8BD1\u4E3A\u72EC\u7ACB\u4EA7\u7269 + require \u8F6C\u6362\uFF0C\u8DE8\u6A21\u5757\u5F15\u7528\u771F\u6B63\u53EF\u7528\uFF08\u540E\u7EED Pinia/API/Component \u57FA\u5EFA\u7684\u5730\u57FA\uFF09",
673
+ when: "<script setup> \u9876\u5C42\u51FA\u73B0 import \u8BED\u53E5",
674
+ example: { before: 'import { formatTime } from "../utils/format"', after: 'const { formatTime } = require("../utils/format.js")\uFF08moduleImports \u547D\u4E2D\uFF09\uFF1B\u672A\u6536\u5F55\u8DEF\u5F84 \u2192 \u8B66\u544A + \u5265\u79BB' },
675
+ verify: "tests/module-import.test.ts B0 \u7528\u4F8B + tests/vue-compat.test.ts import \u7528\u4F8B",
676
+ source: "packages/compiler/src/script.ts \u2192 extractImports + transformScriptToPage\uFF08require \u751F\u6210\uFF09",
677
+ decision: "#116"
678
+ },
679
+ {
680
+ id: "script/runtime-init",
681
+ phase: "script",
682
+ status: "implemented",
683
+ title: "\u51FD\u6570\u8C03\u7528\u521D\u59CB\u5316 \u2192 \u8FD0\u884C\u65F6\u521D\u59CB\u5316\u5B9E\u4F8B\u5C5E\u6027\uFF08module-plan B0\uFF09",
684
+ description: "\u9876\u5C42 const x = fn()\uFF08\u9759\u6001\u6C42\u503C\u5931\u8D25\uFF09\u2192 data \u4E0D\u542B\u8BE5\u5B57\u6BB5\uFF1BonLoad/attached \u6CE8\u5165 this.x = fn()\uFF08\u5B9E\u4F8B\u5C5E\u6027\uFF0CES5 \u5B89\u5168\uFF09\uFF1B\u6A21\u677F\u7ED1\u5B9A\u4E0D\u652F\u6301\uFF08\u8BFB this.data.x undefined\uFF09\u2014\u2014\u5171\u4EAB\u903B\u8F91\u8BF7\u7528\u6A21\u5757 import",
685
+ why: "\u65E7\u884C\u4E3A\uFF1A\u51FD\u6570\u8C03\u7528\u521D\u59CB\u5316\u9759\u6001\u6C42\u503C\u5931\u8D25 \u2192 data.x = undefined \u4E14\u8C03\u7528\u4E22\u5931\uFF08\u9759\u9ED8\u574F\uFF09\uFF1BB0 \u914D\u5408\u8DE8\u6A21\u5757 require \u8BA9 useStore()/createX() \u771F\u5B9E\u6267\u884C",
686
+ when: "\u9876\u5C42 const \u521D\u59CB\u503C\u4E3A\u51FD\u6570\u8C03\u7528\u4E14\u65E0\u6CD5\u9759\u6001\u6C42\u503C\uFF08\u975E inject/ref/computed\uFF09",
687
+ example: { before: "const store = usePlayerStore()", after: "onLoad: this.store = usePlayerStore()\uFF08data \u4E0D\u542B store\uFF09" },
688
+ verify: "tests/module-import.test.ts + tests/vue-compat.test.ts \u8FD0\u884C\u65F6\u521D\u59CB\u5316\u7528\u4F8B",
689
+ source: "packages/compiler/src/script.ts \u2192 extractData\uFF08runtimeInits\uFF09+ runtimeInitLine",
690
+ decision: "#116"
691
+ },
692
+ {
693
+ id: "script/store-binding",
694
+ phase: "script",
695
+ status: "implemented",
696
+ title: "Pinia store \u6A21\u677F\u7ED1\u5B9A\uFF08pinia-plan 12 P1\uFF1A$subscribe \u2192 setData \u540C\u6B65\uFF09",
697
+ description: "\u6A21\u677F {{ store.<field> }} \u5F15\u7528\uFF08template \u6536\u96C6 storeBindings\uFF09\u2192 onLoad \u6CE8\u5165\uFF1A\u521D\u59CB setData({ field: this.store.field }) + store.$subscribe \u8BA2\u9605\uFF08\u53D8\u66F4 \u2192 setData\uFF0C\u6A21\u677F\u5B57\u6BB5\u5B9E\u65F6\u540C\u6B65\uFF09\uFF1Bstore \u53D8\u91CF = useXxxStore() \u7684 runtimeInit \u5B9E\u4F8B\u5C5E\u6027\uFF1B\u5D4C\u5957 store.current.title \u2192 current.title\uFF08store \u524D\u7F00\u5265\u79BB\uFF09",
698
+ why: "MP \u6A21\u677F\u7ED1\u5B9A\u8BFB this.data\u2014\u2014store \u662F\u5B9E\u4F8B\u5C5E\u6027\u8BFB\u4E0D\u5230\uFF1BPinia $subscribe \u8BA2\u9605 + setData \u540C\u6B65\uFF08\u5BF9\u9F50 store \u6865 connectPageStore \u6A21\u5F0F\uFF09\uFF0C\u8BA9 pinia-demo MP \u7AEF\u72B6\u6001\u663E\u793A\u53EF\u7528\uFF08pinia-plan 12\uFF09",
699
+ when: "\u6A21\u677F\u51FA\u73B0 store.<field> \u5F15\u7528\u4E14 script \u6709 const store = useXxxStore()",
700
+ example: { before: "{{ store.current.title }}", after: "{{ current.title }} + onLoad: this.store.$subscribe(() => this.setData({ current: this.store.current }))" },
701
+ verify: "tests/pinia-mp-compile.test.ts\uFF08P1 \u7528\u4F8B\uFF09",
702
+ source: "packages/compiler/src/template.ts\uFF08rewriteStoreRefs\uFF09+ script.ts\uFF08storeBindingLine\uFF09",
703
+ decision: "#116"
704
+ },
705
+ {
706
+ id: "script/define-props",
707
+ phase: "script",
708
+ status: "implemented",
709
+ title: "defineProps \u2192 Component properties\uFF08v0.3 \u7EC4\u4EF6\u7CFB\u7EDF + v0.3 \u5C3E\u6CDB\u578B\uFF09",
710
+ description: "\u7EC4\u4EF6\u6A21\u5F0F\uFF1AdefineProps({ label: String, initial: { type: Number, default: 0 } }) \u6216 TS \u6CDB\u578B defineProps<{ label: string; count?: number }>() \u2192 properties\uFF08type + \u9ED8\u8BA4\u503C value\uFF09\uFF1Bprops.xxx \u8BBF\u95EE\u91CD\u5199\u4E3A this.data.xxx",
711
+ why: "\u5C0F\u7A0B\u5E8F\u7EC4\u4EF6\u7528 Component({ properties }) \u58F0\u660E\u5916\u90E8\u5C5E\u6027\uFF1BVue \u7EC4\u4EF6 props \u7F16\u8BD1\u671F\u6620\u5C04\uFF08v0.3\uFF0C\u51B3\u7B56 #79\uFF09\uFF1B\u5BF9\u8C61\u5F62\u5F0F\uFF08type/default\uFF09+ TS \u6CDB\u578B\u5F62\u5F0F\uFF08string/number/boolean/object/Array/\u8054\u5408\u6620\u5C04\uFF09",
712
+ when: "\u7EC4\u4EF6\u6587\u4EF6\uFF08isComponent\uFF09\u542B defineProps \u65F6",
713
+ example: { before: "const props = defineProps<{ initial: number; label: string }>()", after: 'properties: {\n initial: { type: Number, value: 0 },\n label: { type: String, value: "" },\n}' },
714
+ verify: "tests/mp-transform.test.ts \u7EC4\u4EF6\u7CFB\u7EDF\u7528\u4F8B",
715
+ source: "packages/compiler/src/script.ts \u2192 extractProps + mapTsType",
716
+ decision: "#79 / #91\uFF08v0.3 \u5C3E\u6CDB\u578B\uFF09"
717
+ },
718
+ {
719
+ id: "script/define-emits",
720
+ phase: "script",
721
+ status: "implemented",
722
+ title: "defineEmits + emit() \u2192 triggerEvent\uFF08v0.3 \u7EC4\u4EF6\u7CFB\u7EDF\uFF09",
723
+ description: '\u7EC4\u4EF6\u6A21\u5F0F\uFF1Aemit("xxx", payload) \u2192 this.triggerEvent("xxx", payload)\uFF1B\u7236\u7EC4\u4EF6 @xxx\uFF08\u975E EVENT_MAP\uFF09\u2192 bind:xxx',
724
+ why: "\u5C0F\u7A0B\u5E8F\u7EC4\u4EF6\u5411\u7236\u7EC4\u4EF6\u901A\u4FE1\u7528 triggerEvent\uFF1BVue emit \u7F16\u8BD1\u671F\u6620\u5C04\uFF08v0.3\uFF0C\u51B3\u7B56 #79\uFF09\uFF1BMVP\uFF1A\u7EA6\u5B9A emit \u53D8\u91CF\u540D",
725
+ when: "\u7EC4\u4EF6\u6587\u4EF6\u542B defineEmits \u58F0\u660E\u4E14\u65B9\u6CD5\u4F53\u8C03\u7528 emit(...) \u65F6",
726
+ example: { before: "emit('change', count.value)", after: "this.triggerEvent('change', this.data.count)" },
727
+ verify: "tests/mp-transform.test.ts \u7EC4\u4EF6\u7CFB\u7EDF\u7528\u4F8B",
728
+ source: "packages/compiler/src/script.ts \u2192 rewriteRefAccess\uFF08emit \u91CD\u5199\uFF09+ template.ts\uFF08bind: \u5192\u53F7\u5F62\u5F0F\uFF09",
729
+ decision: "#79\uFF08v0.3 \u7EC4\u4EF6\u7CFB\u7EDF\uFF09"
730
+ },
731
+ {
732
+ id: "script/define-expose",
733
+ phase: "script",
734
+ status: "implemented",
735
+ title: "defineExpose \u2192 no-op + \u6821\u9A8C\uFF08v0.3 \u5C3E\uFF09",
736
+ description: "\u7EC4\u4EF6\u6A21\u5F0F\uFF1AdefineExpose({ ... }) \u7F16\u8BD1\u671F no-op\u2014\u2014\u5C0F\u7A0B\u5E8F\u7EC4\u4EF6 methods \u5929\u7136\u53EF\u88AB selectComponent \u8BBF\u95EE\uFF1B\u6821\u9A8C\u58F0\u660E\u6210\u5458\uFF1Aref \u503C\u66B4\u9732\u65E0\u5BF9\u7B49\u673A\u5236 \u2192 \u8B66\u544A",
737
+ why: "\u5C0F\u7A0B\u5E8F\u7EC4\u4EF6\u5916\u90E8\u8BBF\u95EE\uFF08selectComponent + \u65B9\u6CD5\u8C03\u7528\uFF09\u5929\u7136\u8986\u76D6 Vue \u7684 defineExpose \u65B9\u6CD5\u66B4\u9732\u8BED\u4E49\uFF1Bref \u503C\u66B4\u9732\u9700\u65B9\u6CD5\u5305\u88C5\uFF08v0.3 \u5C3E\uFF0C\u51B3\u7B56 #91\uFF09",
738
+ when: "\u7EC4\u4EF6\u6587\u4EF6\u542B defineExpose \u65F6",
739
+ example: { before: "defineExpose({ reset })", after: "no-op\uFF08reset \u5DF2\u5728 methods\uFF0C\u5916\u90E8 selectComponent \u53EF\u8C03\uFF09" },
740
+ verify: "tests/mp-transform.test.ts defineExpose \u7528\u4F8B",
741
+ source: "packages/compiler/src/script.ts \u2192 checkDefineExpose",
742
+ decision: "#91\uFF08v0.3 \u5C3E\u7EC4\u4EF6\u6536\u5C3E\uFF09"
743
+ },
744
+ {
745
+ id: "script/function-to-methods",
746
+ phase: "script",
747
+ status: "implemented",
748
+ title: "\u9876\u5C42 function \u58F0\u660E \u2192 methods",
749
+ description: "\u9876\u5C42 function handleTap() {...} \u2192 methods \u4E2D\u7684\u5BF9\u8C61\u7B80\u5199 handleTap() {...}",
750
+ why: "\u5C0F\u7A0B\u5E8F\u9875\u9762\u903B\u8F91\u5728 methods \u4E2D\uFF1B\u5BF9\u8C61\u5B57\u9762\u91CF\u5185\u4E0D\u80FD\u8F93\u51FA\u88F8 function \u58F0\u660E\uFF08\u51B3\u7B56 #14\uFF1A\u4EA7\u7269\u65B9\u6CD5\u7528\u5BF9\u8C61\u7B80\u5199\uFF09",
751
+ when: "script \u9876\u5C42\u51FA\u73B0 function \u58F0\u660E\u65F6",
752
+ example: {
753
+ before: "function handleTap() {\n count.value++\n}",
754
+ after: "handleTap() {\n this.setData({ count: (this.data.count === undefined || this.data.count === null ? 0 : this.data.count) + 1 })\n},"
755
+ },
756
+ verify: "tests/mp-transform.test.ts methods \u63D0\u53D6\u7528\u4F8B",
757
+ source: "src/compiler/script.ts \u2192 extractMethods",
758
+ decision: "#14"
759
+ },
760
+ {
761
+ id: "script/arrow-to-methods",
762
+ phase: "script",
763
+ status: "implemented",
764
+ title: "const \u7BAD\u5934\u51FD\u6570 \u2192 methods",
765
+ description: "const fn = (params) => {...} \u2192 methods \u4E2D\u7684 fn(params) {...}\uFF08\u652F\u6301 async\uFF09",
766
+ why: "\u5C0F\u7A0B\u5E8F\u9875\u9762\u903B\u8F91\u5728 methods \u4E2D\uFF1Bconst \u7BAD\u5934\u51FD\u6570\u540C\u6837\u63D0\u53D6\u4E3A\u65B9\u6CD5",
767
+ when: "script \u9876\u5C42\u51FA\u73B0 const \u7BAD\u5934\u51FD\u6570\uFF08\u542B async\uFF09\u65F6",
768
+ example: {
769
+ before: "const load = async () => {\n const r = await fetchData()\n}",
770
+ after: "load() {\n const r = await fetchData()\n},"
771
+ },
772
+ verify: "tests/mp-transform.test.ts methods \u63D0\u53D6\u7528\u4F8B",
773
+ source: "src/compiler/script.ts \u2192 extractMethods\uFF08arrowRe \u5206\u652F\uFF09"
774
+ },
775
+ {
776
+ id: "script/lifecycle-map",
777
+ phase: "script",
778
+ status: "implemented",
779
+ title: "\u751F\u547D\u5468\u671F\u6620\u5C04 onMounted \u2192 onReady / onUnmounted \u2192 onUnload",
780
+ description: "onMounted(() => {...}) \u2192 onReady() {...}\uFF1BonUnmounted \u2192 onUnload\uFF1BonLoad \u900F\u4F20",
781
+ why: "Vue \u7EC4\u4EF6\u751F\u547D\u5468\u671F\u4E0E\u5C0F\u7A0B\u5E8F\u9875\u9762\u751F\u547D\u5468\u671F\u4E0D\u540C\u540D\uFF0C\u7F16\u8BD1\u671F\u6620\u5C04\u5230\u5C0F\u7A0B\u5E8F\u94A9\u5B50",
782
+ when: "script \u51FA\u73B0 onMounted / onUnmounted / onLoad \u8C03\u7528\u65F6",
783
+ example: { before: "onMounted(() => { doInit() })", after: "onReady() {\n doInit()\n}," },
784
+ verify: "tests/runtime.test.ts + mp-transform.test.ts \u751F\u547D\u5468\u671F\u7528\u4F8B",
785
+ source: "src/compiler/script.ts \u2192 extractLifecycles"
786
+ },
787
+ {
788
+ id: "script/ref-read",
789
+ phase: "script",
790
+ status: "implemented",
791
+ title: "\u65B9\u6CD5\u5185 ref \u8BFB\u53D6 \u2192 this.data.name",
792
+ description: "\u65B9\u6CD5/\u751F\u547D\u5468\u671F\u4F53\u5185\u7684 name.value \u8BFB\u53D6 \u2192 this.data.name",
793
+ why: "\u5C0F\u7A0B\u5E8F\u8FD0\u884C\u671F\u72B6\u6001\u5728 this.data \u4E2D\uFF0C\u7F16\u8BD1\u671F\u628A setup ref \u8BBF\u95EE\u91CD\u5199\u4E3A data \u8BBF\u95EE\uFF08\u51B3\u7B56 #22\uFF09",
794
+ when: "\u65B9\u6CD5\u6216\u751F\u547D\u5468\u671F\u4F53\u5185\u51FA\u73B0 ref.value \u8BFB\u53D6\u65F6",
795
+ example: { before: "const v = count.value", after: "const v = this.data.count" },
796
+ verify: "tests/mp-transform.test.ts ref \u91CD\u5199\u7528\u4F8B",
797
+ source: "src/compiler/script.ts \u2192 rewriteRefAccess",
798
+ decision: "#22"
799
+ },
800
+ {
801
+ id: "script/ref-write",
802
+ phase: "script",
803
+ status: "implemented",
804
+ title: "\u65B9\u6CD5\u5185 ref \u8D4B\u503C \u2192 this.setData",
805
+ description: "\u65B9\u6CD5/\u751F\u547D\u5468\u671F\u4F53\u5185\u7684 name.value = expr \u2192 this.setData({ name: expr })",
806
+ why: "\u5C0F\u7A0B\u5E8F\u66F4\u65B0\u89C6\u56FE\u7684\u552F\u4E00\u901A\u9053\u662F setData\uFF0C\u7F16\u8BD1\u671F\u628A\u8D4B\u503C\u91CD\u5199\u4E3A setData \u8C03\u7528\uFF08\u51B3\u7B56 #22\uFF1B\u6392\u9664 ==/===/\u590D\u5408\u8D4B\u503C\uFF09",
807
+ when: "\u65B9\u6CD5\u6216\u751F\u547D\u5468\u671F\u4F53\u5185\u51FA\u73B0 name.value = expr \u65F6",
808
+ example: { before: "count.value = count.value + 1", after: "this.setData({ count: this.data.count + 1 })" },
809
+ verify: "tests/mp-transform.test.ts ref \u91CD\u5199\u7528\u4F8B",
810
+ source: "src/compiler/script.ts \u2192 rewriteRefAccess",
811
+ decision: "#22"
812
+ },
813
+ {
814
+ id: "script/ref-incdec",
815
+ phase: "script",
816
+ status: "implemented",
817
+ title: "\u65B9\u6CD5\u5185 ref \u81EA\u589E/\u81EA\u51CF \u2192 this.setData",
818
+ description: "name.value++ / -- / ++name.value \u2192 this.setData({ name: (null \u68C0\u67E5 ? 0 : this.data.name) + 1 })",
819
+ why: "\u81EA\u589E/\u81EA\u51CF\u65E0\u8868\u8FBE\u5F0F\u53EF\u63D0\u53D6\uFF0C\u7F16\u8BD1\u671F\u4E3A\u663E\u5F0F setData \u5E76\u505A null \u515C\u5E95\uFF08\u51B3\u7B56 #22\uFF1B\u4E0D\u7528 ?? \u8FD0\u7B97\u7B26\u2014\u2014\u771F\u673A\u9884\u89C8\u62A5 SyntaxError\uFF0C\u51B3\u7B56 #36\uFF09",
820
+ when: "\u65B9\u6CD5\u6216\u751F\u547D\u5468\u671F\u4F53\u5185\u51FA\u73B0 name.value \u7684\u81EA\u589E/\u81EA\u51CF\u65F6",
821
+ example: {
822
+ before: "count.value++",
823
+ after: "this.setData({ count: (this.data.count === undefined || this.data.count === null ? 0 : this.data.count) + 1 })"
824
+ },
825
+ verify: "tests/mp-transform.test.ts ref \u91CD\u5199\u7528\u4F8B\uFF1BWeb E2E tap \u8BA1\u6570\u7528\u4F8B",
826
+ source: "src/compiler/script.ts \u2192 rewriteRefAccess + numOrZero",
827
+ decision: "#22 / #36"
828
+ },
829
+ {
830
+ id: "script/vmodel-handler",
831
+ phase: "script",
832
+ status: "implemented",
833
+ title: "v-model \u81EA\u52A8 handler \u6CE8\u5165",
834
+ description: '\u6A21\u677F\u51FA\u73B0 v-model="x" \u65F6\u6CE8\u5165 proteusOnXInput(e) { this.setData({ x: e.detail.value }) }',
835
+ why: "\u5C0F\u7A0B\u5E8F\u65E0 v-model\uFF0C\u56DE\u5199\u65B9\u5411\u7531\u81EA\u52A8 handler \u627F\u62C5\uFF08\u51B3\u7B56 #29\uFF1A\u65B9\u6CD5\u540D\u907F\u514D __ \u524D\u7F00\uFF0C\u5FAE\u4FE1\u4FDD\u7559 _ \u524D\u7F00\u53EF\u80FD\u5BFC\u81F4\u7ED1\u5B9A\u5931\u6548\uFF09",
836
+ when: "template \u8F6C\u6362\u8FD4\u56DE\u7684 vModelBindings \u975E\u7A7A\u65F6",
837
+ example: {
838
+ before: '\uFF08\u6A21\u677F\uFF09<input v-model="name" />',
839
+ after: "proteusOnNameInput(e) { this.setData({ name: e.detail.value }) },"
840
+ },
841
+ verify: "tests/mp-transform.test.ts v-model \u7528\u4F8B",
842
+ source: "src/compiler/script.ts \u2192 transformScriptToPage\uFF08vModelBindings \u5FAA\u73AF\uFF09",
843
+ decision: "#29"
844
+ },
845
+ {
846
+ id: "script/nav-handler",
847
+ phase: "script",
848
+ status: "implemented",
849
+ title: "\u5BFC\u822A\u94FE\u63A5\u81EA\u52A8 handler \u6CE8\u5165\uFF08proteusNavigateTo\uFF09",
850
+ description: "\u6A21\u677F\u51FA\u73B0\u5BFC\u822A\u94FE\u63A5\u65F6\u6CE8\u5165 proteusNavigateTo(e)\uFF1A\u8BFB data-url \u2192 wx.navigateTo\uFF08routeType \u900F\u4F20\uFF0Cfail \u964D\u7EA7\u666E\u901A\u8DF3\u8F6C\uFF09",
851
+ why: "\u5C0F\u7A0B\u5E8F\u5BFC\u822A\u7EDF\u4E00\u8D70 wx.navigateTo\uFF1B\u4FDD\u7559\u524D\u5BFC / \u4E3A\u7EDD\u5BF9\u8DEF\u5F84\uFF08\u51B3\u7B56 #30 \u771F\u673A\u6839\u56E0\uFF09\uFF1Bfail \u964D\u7EA7\u4FDD\u8BC1\u81EA\u5B9A\u4E49\u8DEF\u7531\u5931\u8D25\u4ECD\u53EF\u8DF3\u8F6C\uFF08#28\uFF09\uFF1B\u65B9\u6CD5\u540D\u907F\u5F00 __ \u524D\u7F00\uFF08#29\uFF09",
852
+ when: "template \u8F6C\u6362\u8FD4\u56DE usesNavigate=true \u65F6",
853
+ example: {
854
+ before: '\uFF08\u6A21\u677F\uFF09<a href="/pages/user/index">\u7528\u6237</a>',
855
+ after: 'proteusNavigateTo(e) {\n const ds = e.currentTarget.dataset\n const url = String(ds.url || "")\n if (!url) return\n const nav = { url: url, fail: ... }\n if (ds.routeType) nav.routeType = ds.routeType\n wx.navigateTo(nav)\n},'
856
+ },
857
+ verify: "tests/mp-transform.test.ts \u5BFC\u822A\u94FE\u63A5\u7528\u4F8B\uFF1B\u771F\u673A\u9A8C\u8BC1\u5F52\u6863 #30",
858
+ source: "src/compiler/script.ts \u2192 transformScriptToPage\uFF08usesNavigate \u5206\u652F\uFF09",
859
+ decision: "#24 / #28 / #29 / #30"
860
+ },
861
+ {
862
+ id: "page/scroll-bridge",
863
+ phase: "script",
864
+ status: "implemented",
865
+ title: "\u9875\u9762\u6EDA\u52A8 API \u6865\u63A5\uFF0815-page-scroll-container\uFF1ASkyline \u9875\u9762\u4E0D\u6EDA\u52A8\uFF0C\u9875\u9762\u94A9\u5B50\u9760 scroll-view \u4E8B\u4EF6\u89E6\u53D1\uFF09",
866
+ description: "\u9875\u9762\u58F0\u660E onPageScroll/onReachBottom/onPullDownRefresh \u65F6\uFF1A\u81EA\u52A8\u5305\u88C5 scroll-view \u7ED1\u5B9A\u5BF9\u5E94\u4E8B\u4EF6\uFF08template \u4FA7\uFF09+ \u751F\u6210\u6865\u63A5\u65B9\u6CD5\uFF08\u8F7D\u8377\u5F52\u4E00\uFF1Ascroll-view e.detail.scrollTop \u2192 onPageScroll { scrollTop }\uFF09\uFF1B\u624B\u52A8 scroll-view \u573A\u666F\u7F16\u8BD1\u671F\u6B67\u4E49\u8B66\u544A",
867
+ why: "Skyline \u9875\u9762\u672C\u8EAB\u4E0D\u6EDA\u52A8\uFF08\u6EDA\u52A8\u5FC5\u987B scroll-view\uFF09\u2014\u2014\u9875\u9762\u7EA7\u6EDA\u52A8\u94A9\u5B50\u4E0D\u4F1A\u88AB\u89E6\u53D1\uFF1B\u6865\u63A5\u540E onPageScroll/onReachBottom/onPullDownRefresh \u8BED\u4E49\u4FDD\u7559\uFF08\u4E0E Web \u7AEF\u4E00\u81F4\uFF09",
868
+ when: "\u9875\u9762\uFF08\u975E\u7EC4\u4EF6\uFF09\u6A21\u5F0F + \u58F0\u660E\u6EDA\u52A8\u751F\u547D\u5468\u671F + \u81EA\u52A8\u5305\u6EDA\u52A8\u5BB9\u5668\uFF08rules.disabled \u53EF\u5173\uFF09",
869
+ example: { before: "function onPageScroll(e) { \u2026 }", after: '\u81EA\u52A8\u5305\u88C5 scroll-view bindscroll="proteusPageScroll" + proteusPageScroll(e) \u8F7D\u8377\u5F52\u4E00\u8C03\u7528 onPageScroll' },
870
+ verify: "tests/mp-transform.test.ts \u9875\u9762\u6EDA\u52A8\u6865\u63A5\u7528\u4F8B",
871
+ source: "packages/compiler/src/{template,script}.ts\uFF08pageScrollHooks + proteusPageScroll \u7B49\uFF09",
872
+ decision: "2026-08 \u771F\u673A\u5B9E\u6D4B\uFF0815-page-scroll-container\uFF09"
873
+ },
874
+ {
875
+ id: "script/onload-params",
876
+ phase: "script",
877
+ status: "implemented",
878
+ title: "\u9ED8\u8BA4 onLoad \u53C2\u6570\u81EA\u52A8 decode",
879
+ description: "\u65E0\u663E\u5F0F onLoad \u65F6\u6CE8\u5165\u9ED8\u8BA4\u5B9E\u73B0\uFF1A\u904D\u5386 options \u2192 decodeURIComponent \u2192 \u7ED3\u6784\u5316\u503C\uFF08{/[ \u5F00\u5934\uFF09JSON.parse \u2192 setData",
880
+ why: '\u8DEF\u7531\u53C2\u6570\u7ECF query \u4F20\u9012\uFF0C\u9875\u9762\u9700\u8FD8\u539F\u4E3A\u539F\u59CB\u7C7B\u578B\uFF08\u51B3\u7B56 #19\uFF1A\u4EC5\u5BF9\u7ED3\u6784\u5316\u503C JSON.parse\uFF0C\u666E\u901A\u6807\u91CF\u4FDD\u6301\u5B57\u7B26\u4E32\u2014\u2014\u5BF9\u9F50 P3 \u5951\u7EA6 options.id === "1"\uFF09',
881
+ when: "script \u65E0\u663E\u5F0F onLoad \u65F6\uFF08\u6709\u663E\u5F0F onLoad \u5219\u900F\u4F20\uFF09",
882
+ example: {
883
+ before: "\uFF08\u65E0 onLoad\uFF09",
884
+ after: "onLoad(options) {\n const params = {}\n const keys = Object.keys(options || {})\n for (let i = 0; i < keys.length; i++) {\n ...\n }\n this.setData(params)\n},"
885
+ },
886
+ verify: "tests/runtime.test.ts \u53C2\u6570 decode \u7528\u4F8B",
887
+ source: "src/compiler/script.ts \u2192 transformScriptToPage\uFF08onLoad \u5206\u652F\uFF09",
888
+ decision: "#19 / #32\uFF08ES5 \u5B89\u5168\uFF1A\u7D22\u5F15\u5FAA\u73AF Object.keys\uFF09"
889
+ },
890
+ {
891
+ id: "script/component-mode",
892
+ phase: "script",
893
+ status: "implemented",
894
+ title: "\u7EC4\u4EF6\u6A21\u5F0F \u2192 Component() \u6784\u9020\u5668",
895
+ description: "isComponent=true\uFF08components \u76EE\u5F55\u4E0B\uFF09\u2192 Component({ data, methods, ... })\uFF0C\u9875\u9762 \u2192 Page({ ... })",
896
+ why: "\u5C0F\u7A0B\u5E8F\u9875\u9762\u7528 Page()\uFF0C\u7EC4\u4EF6\u7528 Component()\uFF08\u4E0D\u540C\u6784\u9020\u5668\u5F62\u6001\uFF0CisComponent \u5206\u652F\u5DF2\u5728 compiler/index.ts \u900F\u4F20\uFF09",
897
+ when: "\u6587\u4EF6\u8DEF\u5F84\u5305\u542B components/ \u76EE\u5F55\u65F6",
898
+ example: { before: "\uFF08\u7EC4\u4EF6 SFC\uFF09", after: "Component({ data: {...}, ... })" },
899
+ verify: "tests/mp-transform.test.ts \u7EC4\u4EF6\u5F62\u6001\u7528\u4F8B",
900
+ source: "src/compiler/script.ts \u2192 transformScriptToPage\uFF08isComponent \u5206\u652F\uFF09"
901
+ },
902
+ {
903
+ id: "script/es5-safe",
904
+ phase: "script",
905
+ status: "implemented",
906
+ title: "\u751F\u6210\u4EE3\u7801 ES5 \u5B89\u5168",
907
+ description: "\u4EA7\u7269\u89C4\u907F ?? / ?. / \u6570\u7EC4\u89E3\u6784 / \u5BF9\u8C61\u5C55\u5F00\uFF08\u663E\u5F0F null \u68C0\u67E5\u4E09\u5143\u3001\u7D22\u5F15\u5FAA\u73AF Object.keys\u3001\u76F4\u63A5\u5C5E\u6027\u8D4B\u503C\uFF09",
908
+ why: "\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u5C06\u9875\u9762 JS \u8F6C ES5 \u65F6\u4F9D\u8D56 babel helper \u6A21\u5757\uFF0Chelper \u4E0D\u5728\u5305\u5185\u4F1A\u62A5\u9519\uFF08#32\uFF1AarrayWithHoles \u672A\u5B9A\u4E49\uFF09\uFF1B\u771F\u673A\u9884\u89C8\u76F4\u63A5\u62A5 ?? \u8BED\u6CD5\u9519\u8BEF\uFF08#36\uFF09",
909
+ when: "\u6240\u6709\u751F\u6210\u7684 JS \u4EA7\u7269\uFF08\u65B9\u6CD5\u4F53 / onLoad / \u81EA\u52A8 handler\uFF09",
910
+ example: {
911
+ before: "// \u7981\u6B62\u51FA\u73B0\u5728\u4EA7\u7269\u4E2D\nconst [a, b] = arr\nconst v = x ?? 0",
912
+ after: "// \u4EA7\u7269\u5B9E\u9645\u5F62\u6001\nconst a = arr[0], b = arr[1]\nconst v = (x === undefined || x === null ? 0 : x)"
913
+ },
914
+ verify: "grep \u626B\u63CF\u4EA7\u7269\u96F6\u6B8B\u7559\uFF08\u51B3\u7B56 #36 \u9A8C\u8BC1\u65B9\u5F0F\uFF09",
915
+ source: "src/compiler/script.ts \u5404\u5904\uFF08numOrZero / onLoad \u5B9E\u73B0 / nav handler\uFF09",
916
+ decision: "#32 / #36"
917
+ },
918
+ {
919
+ id: "script/provide-inject",
920
+ phase: "script",
921
+ status: "implemented",
922
+ title: "provide/inject \u2192 getApp().__proteusProvides \u5168\u5C40\u6CE8\u518C\u8868\u6865\uFF08vue-compat-advance Batch 3/4\uFF09",
923
+ description: '\u9876\u5C42 provide("key", expr) / const x = inject("key"[, default]) \u7F16\u8BD1\u4E3A\u6CE8\u518C\u8868\u8BFB\u5199\uFF1A\u9875\u9762 onLoad \u5355\u51FD\u6570\u5408\u5E76\u5757\uFF08pageId \u547D\u540D\u7A7A\u95F4\u89E3\u6790\u4E00\u6B21 + provide \u6CE8\u518C + inject setData\uFF09\uFF1B\u7EC4\u4EF6 provide \u653E created\uFF08\u5148\u4E8E\u5B50\u7EC4\u4EF6 attached \u6CE8\u5165\uFF09\u3001inject \u653E attached\uFF1Bprovide \u503C\u8868\u8FBE\u5F0F\u91CD\u5199\u88F8 ref \u540D / ref.value \u2192 this.data.<name>\uFF1Binject \u652F\u6301\u9ED8\u8BA4\u503C\uFF08undefined \u65F6\u53D6\u9ED8\u8BA4\uFF09\uFF1B\u2605Batch 4 \u54CD\u5E94\u5F0F\u8054\u52A8\uFF1A\u88F8 ref \u63D0\u4F9B\uFF08provide("key", refName)\uFF09\u2192 ref \u5199\u5165\u70B9\u6CE8\u5165 proteusSyncProvide\uFF08\u540C\u6B65\u6CE8\u518C\u8868\u503C + \u901A\u77E5\u8BA2\u9605\u8005\uFF09\uFF0Cinject \u4FA7\u8BA2\u9605 __subs[key]\uFF08\u503C\u53D8\u5316 setData \u5237\u65B0\uFF09+ detached/onUnload \u53D6\u6D88\uFF1B\u2605Batch 6 \u9875\u9762\u7EA7\u9694\u79BB\uFF1A\u6CE8\u518C\u8868\u6309 pageId \u547D\u540D\u7A7A\u95F4\uFF08\u9875\u9762 onLoad __seq \u751F\u6210 + \u7EC4\u4EF6 getCurrentPages \u6808\u9876\u89E3\u6790\uFF0ConUnload \u5220\u9664\u547D\u540D\u7A7A\u95F4\u9632\u6CC4\u6F0F\uFF09\uFF1B.value/\u5B57\u9762\u91CF\u63D0\u4F9B\u4FDD\u6301\u9759\u6001\u5FEB\u7167\uFF08\u5BF9\u9F50 Vue \u8BED\u4E49\uFF1A\u4F20 ref \u8054\u52A8\u3001\u4F20\u503C\u5FEB\u7167\uFF09',
924
+ why: "\u5C0F\u7A0B\u5E8F\u7EC4\u4EF6\u6811\u65E0 provide/inject \u673A\u5236\uFF08\u51B3\u7B56 #117\uFF09\uFF1A\u5168\u5C40\u6CE8\u518C\u8868\u6865\u8BA9\u9875\u9762\u5411\u7EC4\u4EF6\u4F20\u503C\uFF08\u542B\u6DF1\u5C42\u5D4C\u5957\u7EC4\u4EF6\uFF09\uFF1BMVP \u503C\u5FEB\u7167\uFF08\u975E\u54CD\u5E94\u5F0F\u8054\u52A8\uFF09+ \u5168\u5C40\u6CE8\u518C\u8868\uFF08\u91CD\u540D key \u540E\u5199\u8986\u76D6\uFF09\uFF0C\u9875\u9762\u7EA7\u9694\u79BB/\u54CD\u5E94\u5F0F\u4E3A\u540E\u7EED",
925
+ when: 'script \u9876\u5C42\u51FA\u73B0 provide("key", value) \u8C03\u7528\u6216 const x = inject("key")\uFF08\u4EC5\u96F6\u7F29\u8FDB\u5355\u884C\uFF1Bprovide \u503C\u652F\u6301\u5B57\u9762\u91CF/\u88F8 ref/ref.value\uFF09',
926
+ example: {
927
+ before: 'provide("user", userInfo)\nconst theme = inject("theme", "light")',
928
+ after: 'onLoad: const provides = (getApp().__proteusProvides || (getApp().__proteusProvides = {})); provides["user"] = this.data.userInfo; if (!provides.__subs) provides.__subs = {}; ...; this.setData({ theme: (provides["theme"] === undefined ? "light" : provides["theme"]) })\uFF1BuserInfo \u5199\u5165\u70B9\u8FFD\u52A0 this.proteusSyncProvide("user", "userInfo")\uFF08\u901A\u77E5\u8BA2\u9605\u8005 setData \u5237\u65B0\uFF09'
929
+ },
930
+ verify: "tests/vue-compat-advance.test.ts Batch 3/4 \u7528\u4F8B",
931
+ source: "packages/compiler/src/script.ts \u2192 extractProvideInject + buildProvideInject + rewriteRefAccess\uFF08\u5199\u5165\u70B9\u6CE8\u5165\uFF09+ transformScriptToPage\uFF08created/attached/onLoad \u6CE8\u5165\u70B9 + proteusSyncProvide/proteusUnsubscribeProvide \u751F\u6210\uFF09",
932
+ decision: "#117"
933
+ }
934
+ ];
935
+
936
+ // src/transforms/style.ts
937
+ var STYLE_RULES = [
938
+ {
939
+ id: "style/px-to-rpx",
940
+ phase: "style",
941
+ status: "implemented",
942
+ title: "px \u2192 rpx\uFF08\u4EC5 MP \u7AEF\u7F16\u8BD1\u671F\u751F\u6548\uFF09",
943
+ description: "CSS \u6570\u503C px \u2192 rpx\uFF08rpxRatio \u9ED8\u8BA4 2\uFF1A48px \u2192 96rpx\uFF09\uFF1BWeb \u7AEF\u6C38\u4E0D\u8F6C\u6362\uFF08Vite \u539F\u751F\u5904\u7406\uFF09",
944
+ why: "\u5C0F\u7A0B\u5E8F rpx \u662F\u5C4F\u5E55\u7B49\u6BD4\u5355\u4F4D\uFF08750 \u8BBE\u8BA1\u7A3F\uFF09\uFF0C\u8DE8\u7AEF CSS \u4E00\u81F4\u6027\u7684\u7F16\u8BD1\u671F\u5438\u6536\uFF08\u51B3\u7B56 #9\uFF1AMP \u7AEF px\u2192rpx\uFF0CWeb \u7AEF\u4FDD\u6301\u6807\u51C6 CSS\uFF09",
945
+ when: "style \u4E2D\u51FA\u73B0\u6570\u503C px\uFF08style.px2rpx=true\uFF09\u65F6",
946
+ example: { before: "padding: 48px;", after: "padding: 96rpx;" },
947
+ verify: "tests/mp-transform.test.ts px\u2192rpx \u7528\u4F8B",
948
+ source: "src/compiler/style.ts \u2192 transformStyleToWxss\uFF08px2rpx \u5206\u652F\uFF09",
949
+ decision: "#9",
950
+ // ★分派层:AI 覆盖此实现(如改换算公式/单位映射)→ 编译输出即时变化,无需改 style.ts
951
+ apply: (ctx) => {
952
+ const input = ctx.input;
953
+ const ratio = Number(ctx.options?.rpxRatio ?? 2);
954
+ ctx.output = input.replace(/(\d+(?:\.\d+)?)px\b/g, (_m, n) => `${Number(n) * ratio}rpx`);
955
+ }
956
+ },
957
+ {
958
+ id: "style/selector-tag",
959
+ phase: "style",
960
+ status: "implemented",
961
+ title: "\u9009\u62E9\u5668 HTML \u6807\u7B7E \u2192 \u5C0F\u7A0B\u5E8F\u6807\u7B7E",
962
+ description: "\u9009\u62E9\u5668\u4E2D\u7684\u6807\u7B7E\u540D\u91CD\u5199\u4E3A\u5C0F\u7A0B\u5E8F\u6807\u7B7E\uFF08.links a \u2192 .links view\u3001div > p \u2192 view > .proteus-p\uFF09\uFF1B\u5C5E\u6027\u9009\u62E9\u5668/\u7C7B\u540D/ID/\u957F\u6807\u8BC6\u7B26\u4E0D\u8BEF\u4F24",
963
+ why: "\u6A21\u677F\u5DF2\u628A div/a/h1 \u7B49\u6620\u5C04\u4E3A view/text\uFF0C\u82E5\u6837\u5F0F\u9009\u62E9\u5668\u4E0D\u91CD\u5199\u5219\u5339\u914D\u4E0D\u5230\u5143\u7D20\uFF08\u51B3\u7B56 #57 \u4FEE\u590D\u7684 bug\uFF09\uFF1B\u547D\u4E2D\u6761\u4EF6=\u6807\u7B7E\u4F4D\u4E8E\u9009\u62E9\u5668\u8D77\u59CB\u6216\u7EC4\u5408\u5668\u4E4B\u540E\uFF0C\u907F\u514D .a/#input/tag-a \u8BEF\u4F24",
964
+ when: "style \u9009\u62E9\u5668\u4E2D\u542B TAG_MAP \u4E2D\u7684\u6807\u7B7E\u540D\u65F6",
965
+ example: { before: ".links a { color: #1a7af8; }", after: ".links .proteus-a { color: #1a7af8; }" },
966
+ verify: "tests/mp-transform.test.ts \u9009\u62E9\u5668\u91CD\u5199\u7528\u4F8B\uFF1B\u4EA7\u7269\u9A8C\u8BC1 .links a \u2192 .links view \u5BF9\u9F50 wxml\uFF08\u51B3\u7B56 #57\uFF09",
967
+ source: "src/compiler/style.ts \u2192 rewriteTagSelectors + TAG_SELECTOR_RE",
968
+ decision: "#57",
969
+ mapping: { ...TAG_MAP }
970
+ },
971
+ {
972
+ id: "style/selector-semantic",
973
+ phase: "style",
974
+ status: "implemented",
975
+ title: "\u8BED\u4E49\u6807\u7B7E\u9009\u62E9\u5668 \u2192 proteus-* \u7C7B\u9009\u62E9\u5668",
976
+ description: "h1-h6/p/a \u9009\u62E9\u5668\u6620\u5C04\u4E3A\u57FA\u7840\u7C7B\u9009\u62E9\u5668\uFF08.card h3 \u2192 .card .proteus-h3\u3001.links a \u2192 .links .proteus-a\uFF09\uFF0C\u800C\u975E\u6807\u7B7E",
977
+ why: "h3 \u4E0E p \u90FD\u6620\u5C04\u4E3A text\uFF0C\u82E5\u90FD\u6620\u5C04\u4E3A\u6807\u7B7E\uFF0C\u540C\u7279\u5F02\u6027\u89C4\u5219\u540E\u5199\u8986\u76D6\u5148\u5199\uFF08\u51B3\u7B56 #61 \u4FEE\u590D\uFF1A.card p \u7684 color \u66FE\u6C61\u67D3 h3\uFF09\uFF1B\u6A21\u677F\u4FA7\u5DF2\u9644\u52A0 proteus-* \u7C7B\u6545\u7CBE\u786E\u5339\u914D",
978
+ when: "\u9009\u62E9\u5668\u4E2D\u542B h1-h6/p/a \u6807\u7B7E\u65F6",
979
+ example: { before: ".card h3 { font-weight: 700; }", after: ".card .proteus-h3 { font-weight: 700; }" },
980
+ verify: "tests/mp-transform.test.ts \u9009\u62E9\u5668\u91CD\u5199\u7528\u4F8B\uFF08showcase.wxss \u4E24\u6761\u89C4\u5219\u72EC\u7ACB\uFF09",
981
+ source: "src/compiler/style.ts \u2192 rewriteTagSelectors\uFF08semantic \u5206\u652F\uFF09+ src/compiler/tags.ts SEMANTIC_CLASS",
982
+ decision: "#61",
983
+ mapping: { ...SEMANTIC_CLASS }
984
+ },
985
+ {
986
+ id: "style/semantic-base-wxss",
987
+ phase: "style",
988
+ status: "implemented",
989
+ title: "\u6CE8\u5165\u8BED\u4E49\u57FA\u7840 WXSS\uFF08h1-h6/p/a \u89C6\u89C9\u8FD8\u539F\uFF09",
990
+ description: "\u4EA7\u7269 WXSS \u5934\u90E8\u6CE8\u5165 .proteus-h1~h6/.proteus-p/.proteus-a \u57FA\u7840\u6837\u5F0F\uFF08\u5BF9\u9F50 HTML \u6807\u51C6\u9644\u5F55 D\uFF1A\u5B57\u53F7/\u5B57\u91CD/\u5355\u8FB9 em \u6BB5\u8DDD/\u94FE\u63A5\u8272\uFF09\uFF0C\u4F4D\u4E8E\u7528\u6237\u6837\u5F0F\u4E4B\u524D",
991
+ why: "Web \u6D4F\u89C8\u5668\u6709 UA \u9ED8\u8BA4\u6837\u5F0F\uFF0C\u5C0F\u7A0B\u5E8F text/view \u6CA1\u6709\uFF1B\u6CE8\u5165\u57FA\u7840\u7C7B\u8FD8\u539F\u4E24\u7AEF\u89C6\u89C9\u4E00\u81F4\uFF08\u51B3\u7B56 #58\uFF09\uFF1Bmargin \u7528\u5355\u8FB9 bottom + em \u76F8\u5BF9\u81EA\u8EAB\u5B57\u53F7\u2014\u2014Skyline \u81EA\u7814\u5F15\u64CE\u4E0D\u6298\u53E0 margin\uFF0C\u5355\u8FB9 em \u4E0E Web \u6298\u53E0\u5728\u4E3B\u6D41\u7A0B\u7EC4\u5408\u4E0B\u89C6\u89C9\u4E00\u81F4\uFF08\u51B3\u7B56 #59\uFF09",
992
+ when: "\u6BCF\u6B21 style \u8F6C\u6362\uFF08\u4F5C\u4E3A\u4EA7\u7269\u524D\u7F00\uFF09",
993
+ example: {
994
+ before: "// \u6E90\u7801\u65E0\u6837\u5F0F\u65F6\u4EA7\u7269\u4ECD\u542B",
995
+ after: ".proteus-h1 { display: block; font-size: 64rpx; font-weight: 700; margin: 0 0 0.67em; }\n.proteus-p { display: block; margin: 0 0 1em; }\n.proteus-a { color: #1a7af8; text-decoration: underline; }"
996
+ },
997
+ verify: 'golden fixture showcase.wxss\uFF1B\u4EA7\u7269\u9A8C\u8BC1 <text class="proteus-h1"> + .proteus-h1\uFF08\u51B3\u7B56 #58\uFF09',
998
+ source: "src/compiler/style.ts \u2192 BASE_SEMANTIC_WXSS",
999
+ decision: "#58 / #59"
1000
+ },
1001
+ {
1002
+ id: "style/default-scoped",
1003
+ phase: "style",
1004
+ status: "implemented",
1005
+ title: "\u9ED8\u8BA4 scoped\uFF1A<style> \u65E0\u6807\u8BB0\u6309\u5C40\u90E8\u4F5C\u7528\u57DF\u5904\u7406\uFF08\u26052026-08 \u7528\u6237\u51B3\u7B56\uFF09",
1006
+ description: "\u975E <style global> \u7684 style \u5757\u5747\u6309 scoped \u5904\u7406\uFF08\u7C7B\u540D\u540E\u7F00\u62FC\u63A5\uFF09\uFF1B<style> \u65E0 scoped/global \u6807\u8BB0\u65F6\u7F16\u8BD1\u671F\u8B66\u544A\uFF1B<style global>\uFF08Proteus \u6269\u5C55\uFF09\u663E\u5F0F\u5168\u5C40\uFF08\u4E0D\u4F5C\u7528\u57DF\u5316\uFF09\uFF1B\u540C\u6587\u4EF6 scoped+global \u5206\u7EC4\u8F93\u51FA\uFF08global \u5728\u524D\uFF09",
1007
+ why: "Vue \u6807\u51C6 <style> \u4E3A\u5168\u5C40\u2014\u2014Web \u7AEF\u6CC4\u6F0F\u5230\u6240\u6709\u9875\u9762\uFF08\u771F\u673A\u5B9E\u6D4B\uFF1Aconfig-demo \u65E0 scoped \u7684\u7070\u8272\u80CC\u666F\u4E32\u5230\u5185\u7F6E\u7EC4\u4EF6\u6F14\u793A\u9875\uFF09\uFF1B\u7528\u6237\u51B3\u7B56\u9ED8\u8BA4\u5C40\u90E8\u66F4\u5B89\u5168\uFF08\u8DE8\u7AEF\u4E00\u81F4\uFF1AWeb \u7AEF vite \u63D2\u4EF6\u540C\u6B65\u6539\u5199 <style> \u2192 <style scoped>\uFF09",
1008
+ when: "\u6BCF\u6B21 SFC \u7F16\u8BD1\uFF08style \u5757\u5904\u7406\uFF09",
1009
+ example: { before: "<style>.box { color: red; }</style>", after: "\u6309 scoped \u5904\u7406\uFF08.box-data-v-x\uFF09+ \u8B66\u544A\u63D0\u793A\u5168\u5C40\u51FA\u53E3 <style global>" },
1010
+ verify: "tests/mp-transform.test.ts \u9ED8\u8BA4 scoped \u7528\u4F8B",
1011
+ source: "packages/compiler/src/index.ts \u2192 compileVueSfc\uFF08scopedStyles/globalStyles \u5206\u7EC4\uFF09",
1012
+ decision: "2026-08 \u7528\u6237\u51B3\u7B56\uFF08\u9ED8\u8BA4 scoped\uFF09"
1013
+ },
1014
+ {
1015
+ id: "style/skyline-unsupported",
1016
+ phase: "style",
1017
+ status: "implemented",
1018
+ title: "Skyline \u4E0D\u652F\u6301\u5C5E\u6027\u7F16\u8BD1\u671F\u8B66\u544A",
1019
+ description: "float\u3001position: fixed \u51FA\u73B0\u65F6\u7F16\u8BD1\u671F\u8B66\u544A\uFF08\u4E0D\u963B\u65AD\u6784\u5EFA\uFF09",
1020
+ why: "Skyline \u81EA\u7814\u6E32\u67D3\u5F15\u64CE\u4E0D\u652F\u6301\u8FD9\u4E9B\u5E03\u5C40\u5C5E\u6027\uFF0C\u7F16\u8BD1\u671F\u8B66\u544A\u8BA9\u5F00\u53D1\u8005\u63D0\u524D\u77E5\u9053\uFF08\u53CD\u9ED1\u76D2\u539F\u5219\uFF1A\u8B66\u544A\u53EF\u89C1\u3001\u53EF\u7EDF\u8BA1\uFF09",
1021
+ when: "WXSS \u4E2D\u51FA\u73B0 float: \u6216 position: fixed \u65F6",
1022
+ example: { before: ".banner { position: fixed; }", after: "\u8B66\u544A\uFF1AWXSS \u68C0\u6D4B\u5230 Skyline \u4E0D\u652F\u6301\u7684\u5C5E\u6027\uFF1Aposition: fixed\uFF08\u7F16\u8BD1\u671F\u8B66\u544A\uFF09" },
1023
+ verify: "tests/mp-transform.test.ts \u8B66\u544A\u7528\u4F8B",
1024
+ source: "src/compiler/style.ts \u2192 transformStyleToWxss\uFF08unsupported \u5206\u652F\uFF09"
1025
+ },
1026
+ {
1027
+ id: "style/scoped-css",
1028
+ phase: "style",
1029
+ status: "implemented",
1030
+ title: "scoped CSS\uFF1A\u9009\u62E9\u5668\u7C7B\u540D\u540E\u7F00\u62FC\u63A5\uFF08\u26052026-08 \u771F\u673A\u91CD\u6784\uFF1A\u5355\u7C7B\u9009\u62E9\u5668\uFF09",
1031
+ description: "<style scoped> \u5B58\u5728\u65F6\uFF1A\u9009\u62E9\u5668\u5185\u6BCF\u4E2A\u7C7B token \u8FFD\u52A0 -scopeId\uFF08.box \u2192 .box-data-v-xxx \u5355\u4E00\u7C7B\u9009\u62E9\u5668\uFF09\uFF1B:deep(X) \u53BB\u5305\u88C5\u540E\u7EDF\u4E00\u540E\u7F00\uFF1B@keyframes \u5E27/\u4F2A\u7C7B\u4F2A\u5143\u7D20/@\u89C4\u5219\u4E0D\u5904\u7406\uFF1B\u9017\u53F7\u5217\u8868\u9010\u6761",
1032
+ why: "\u5C0F\u7A0B\u5E8F\u65E0 scoped CSS \u539F\u751F\u673A\u5236\uFF0C\u7F16\u8BD1\u671F\u7C7B\u540D\u540E\u7F00\u7B49\u4EF7\uFF08v0.3\uFF0C\u51B3\u7B56 #77\uFF09\uFF1B\u26052026-08 \u771F\u673A\u91CD\u6784\uFF1ASkyline glass-easel \u4E0D\u652F\u6301\u5C5E\u6027\u9009\u62E9\u5668 [data-v]\uFF08f48460c \u6539 class \u590D\u5408\u4ECD\u4E0D\u5339\u914D\u2014\u2014.a.b \u590D\u5408\u7C7B\u9009\u62E9\u5668\u4E5F\u9759\u9ED8\u4E22\u5F03\uFF0C\u7EC4\u4EF6\u81EA\u8EAB wxss \u5339\u914D\u81EA\u8EAB\u6839\u8282\u70B9\u90FD\u5931\u6548\uFF09\u2192 \u7C7B\u540D\u540E\u7F00\u662F\u552F\u4E00 Skyline \u786E\u5B9A\u652F\u6301\u7684\u8DEF\u5F84\uFF08\u5355\u7C7B\u9009\u62E9\u5668 \u2713\uFF09",
1033
+ when: "SFC \u542B <style scoped> \u4E14\u89C4\u5219\u672A\u88AB\u7981\u7528\u65F6",
1034
+ example: { before: ".card { color: red; }", after: ".card-data-v-abc123 { color: red; }" },
1035
+ verify: "tests/mp-transform.test.ts scoped CSS \u7528\u4F8B",
1036
+ source: "packages/compiler/src/style.ts \u2192 transformStyleToWxss\uFF08scopeId \u5206\u652F\uFF09",
1037
+ decision: "#77\uFF08v0.3 scoped CSS\uFF09+ 2026-08 Skyline \u771F\u673A\u91CD\u6784\uFF08\u7C7B\u540D\u540E\u7F00\uFF09"
1038
+ },
1039
+ {
1040
+ id: "transition/animation-wxss",
1041
+ phase: "style",
1042
+ status: "implemented",
1043
+ title: "<transition> \u8FDB\u5165\u52A8\u753B keyframes \u6CE8\u5165",
1044
+ description: "wxss \u5C3E\u90E8\u6CE8\u5165 proteus-transition-* \u8FDB\u5165\u52A8\u753B\uFF08fade/slide-up/scale keyframes\uFF09\u2014\u2014\u914D\u5408\u6A21\u677F\u4FA7 transition \u88C5\u9970 class",
1045
+ why: "vue-compat-advance Batch 2\uFF08\u51B3\u7B56 #117\uFF09\uFF1A<transition> \u8FDB\u5165\u52A8\u753B\u8FD0\u884C\u65F6\u7B49\u4EF7\uFF08\u5143\u7D20 wx:if \u91CD\u5EFA\u65F6 animation \u81EA\u52A8\u64AD\u653E\uFF09\uFF1B\u79BB\u5F00\u52A8\u753B MP \u65E0\u94A9\u5B50",
1046
+ when: "\u4EFB\u4E00\u9875\u9762\u4F7F\u7528 <transition>\uFF08\u9ED8\u8BA4\u6CE8\u5165\uFF1Brules.disabled \u53EF\u5173\uFF09",
1047
+ example: { before: '<transition name="fade">\u2026', after: "wxss \u542B .proteus-transition-fade + @keyframes proteus-fade-in" },
1048
+ verify: "tests/vue-compat-advance.test.ts Transition \u7528\u4F8B",
1049
+ source: "packages/compiler/src/style.ts \u2192 TRANSITION_WXSS \u6CE8\u5165",
1050
+ decision: "#117"
1051
+ }
1052
+ ];
1053
+
1054
+ // src/transforms/validate.ts
1055
+ var VALIDATE_RULES = [
1056
+ {
1057
+ id: "validate/js-syntax",
1058
+ phase: "validate",
1059
+ status: "implemented",
1060
+ title: "JS \u4EA7\u7269\u8BED\u6CD5\u81EA\u6821\u9A8C",
1061
+ description: "new Function(js) \u4EC5\u89E3\u6790\u4E0D\u6267\u884C\uFF0C\u8BED\u6CD5\u9519\u8BEF \u2192 \u6821\u9A8C\u5931\u8D25\u5E76\u643A\u5E26\u9519\u8BEF\u4FE1\u606F",
1062
+ why: "\u53CD\u7F16\u8BD1\u9ED1\u76D2\u673A\u5236\uFF08\u51B3\u7B56 #17\uFF09\uFF1A\u574F\u4EA7\u7269\u5F53\u573A\u62A5\u9519\u6307\u660E\u6587\u4EF6\uFF0C\u7EDD\u4E0D\u9759\u9ED8\u8F93\u51FA\u4E0D\u53EF\u7528\u7684\u4EA7\u7269\uFF08\u5BF9\u6BD4 uni-app \u4EA7\u7269\u65E0\u6CD5\u5B9A\u4F4D\u95EE\u9898\uFF09",
1063
+ when: "\u6BCF\u6B21\u6574\u5305\u7F16\u8BD1\u540E\uFF08assertValidResult \u5185\uFF09",
1064
+ example: {
1065
+ before: "// \u82E5\u4EA7\u7269 js \u542B\u8BED\u6CD5\u9519\u8BEF",
1066
+ after: "CompilerError: [proteus-compiler] xxx.vue: js \u4EA7\u7269\u8BED\u6CD5\u9519\u8BEF\uFF1AUnexpected token ..."
1067
+ },
1068
+ verify: "tests/mp-transform.test.ts \u6821\u9A8C\u7528\u4F8B\uFF08\u6784\u9020\u574F\u4EA7\u7269\u65AD\u8A00\u629B\u9519\uFF09",
1069
+ source: "src/compiler/validate.ts \u2192 validateJs",
1070
+ decision: "#17"
1071
+ },
1072
+ {
1073
+ id: "validate/wxml-pairing",
1074
+ phase: "validate",
1075
+ status: "implemented",
1076
+ title: "WXML \u6807\u7B7E\u914D\u5BF9\u81EA\u6821\u9A8C",
1077
+ description: "\u6808\u5F0F\u626B\u63CF WXML \u6807\u7B7E\u914D\u5BF9\uFF08\u5148\u5265\u79BB\u6CE8\u91CA\uFF0C\u907F\u514D\u884C\u53F7\u6CE8\u91CA\u5E72\u6270\uFF09\uFF0C\u672A\u95ED\u5408/\u9519\u914D \u2192 \u6821\u9A8C\u5931\u8D25",
1078
+ why: "\u53CD\u7F16\u8BD1\u9ED1\u76D2\u673A\u5236\uFF08\u51B3\u7B56 #17\uFF09\uFF1A\u6A21\u677F\u8F6C\u6362\u51FA\u9519\u7684\u5E38\u89C1\u5F62\u6001\u5C31\u662F\u6807\u7B7E\u4E0D\u914D\u5BF9\uFF0C\u7F16\u8BD1\u671F\u62E6\u622A\u6BD4\u771F\u673A\u62A5\u9519\u597D\u5B9A\u4F4D",
1079
+ when: "\u6BCF\u6B21\u6574\u5305\u7F16\u8BD1\u540E\uFF08assertValidResult \u5185\uFF09",
1080
+ example: {
1081
+ before: "// \u82E5\u4EA7\u7269 wxml \u6807\u7B7E\u4E0D\u914D\u5BF9",
1082
+ after: "CompilerError: [proteus-compiler] xxx.vue: wxml \u4EA7\u7269\u7ED3\u6784\u9519\u8BEF\uFF1A</view> \u4E0E <text> \u4E0D\u5339\u914D\uFF08\u4F4D\u7F6E N\uFF09"
1083
+ },
1084
+ verify: "tests/mp-transform.test.ts \u6821\u9A8C\u7528\u4F8B",
1085
+ source: "src/compiler/validate.ts \u2192 validateWxml",
1086
+ decision: "#17"
1087
+ },
1088
+ {
1089
+ id: "validate/compiler-error",
1090
+ phase: "validate",
1091
+ status: "implemented",
1092
+ title: "\u574F\u4EA7\u7269\u629B CompilerError \u6307\u660E\u6587\u4EF6",
1093
+ description: "\u6821\u9A8C\u5931\u8D25 \u2192 \u629B CompilerError\uFF08\u643A\u5E26\u6E90\u6587\u4EF6\u540D\uFF0C\u6D88\u606F\u542B [proteus-compiler] \u524D\u7F00\uFF09",
1094
+ why: "\u53CD\u9ED1\u76D2\u673A\u5236\u7684\u7EDF\u4E00\u9519\u8BEF\u901A\u9053\uFF1AAI/\u5F00\u53D1\u8005\u62FF\u5230\u9519\u8BEF\u5373\u53EF\u5B9A\u4F4D\u5230\u5177\u4F53\u6587\u4EF6\uFF08\u9519\u8BEF = \u53EF\u64CD\u4F5C\u7684\u53CD\u9988\uFF0C\u800C\u975E\u9ED1\u76D2\u5931\u8D25\uFF09",
1095
+ when: "js \u6216 wxml \u6821\u9A8C\u4E0D\u901A\u8FC7\u65F6",
1096
+ example: {
1097
+ before: "// \u9759\u9ED8\u8F93\u51FA\u574F\u4EA7\u7269\uFF08\u53CD\u6A21\u5F0F\uFF09",
1098
+ after: "throw new CompilerError(filename, message)"
1099
+ },
1100
+ verify: "tests/mp-transform.test.ts \u6821\u9A8C\u7528\u4F8B",
1101
+ source: "src/compiler/validate.ts \u2192 CompilerError + assertValidResult",
1102
+ decision: "#17"
1103
+ }
1104
+ ];
1105
+
1106
+ // src/transforms/registry.ts
1107
+ var TRANSFORM_RULES = [
1108
+ ...TEMPLATE_RULES,
1109
+ ...SCRIPT_RULES,
1110
+ ...STYLE_RULES,
1111
+ ...VALIDATE_RULES
1112
+ ];
1113
+ var byId = new Map(TRANSFORM_RULES.map((r) => [r.id, r]));
1114
+ function listTransformRules(phase) {
1115
+ return phase ? TRANSFORM_RULES.filter((r) => r.phase === phase) : [...TRANSFORM_RULES];
1116
+ }
1117
+ function getTransformRule(id) {
1118
+ return byId.get(id);
1119
+ }
1120
+ function formatTransformRule(rule) {
1121
+ const lines = [
1122
+ `## ${rule.id}\uFF08${rule.phase}\uFF09`,
1123
+ `**${rule.title}** \`[${rule.status}]\``,
1124
+ `- \u8F93\u5165 \u2192 \u8F93\u51FA\uFF1A${rule.description}`,
1125
+ `- \u4E3A\u4EC0\u4E48\uFF1A${rule.why}`,
1126
+ `- \u89E6\u53D1\u6761\u4EF6\uFF1A${rule.when}`,
1127
+ `- \u793A\u4F8B\uFF1A`,
1128
+ ` - \u6E90\u7801\uFF1A\`${rule.example.before}\``,
1129
+ ` - \u4EA7\u7269\uFF1A\`${rule.example.after}\``,
1130
+ `- \u5982\u4F55\u9A8C\u8BC1\uFF1A${rule.verify}`,
1131
+ `- \u5B9E\u73B0\u4F4D\u7F6E\uFF1A${rule.source}`
1132
+ ];
1133
+ if (rule.decision) lines.push(`- \u51B3\u7B56\uFF1A${rule.decision}`);
1134
+ return lines.join("\n");
1135
+ }
1136
+ function formatTransformCatalog() {
1137
+ const phases = ["template", "script", "style", "validate"];
1138
+ const blocks = phases.map((phase) => {
1139
+ const rules = listTransformRules(phase);
1140
+ const items = rules.map((r) => `- \`${r.id}\` \`[${r.status}]\` ${r.title}${r.apply ? "\uFF08\u5DF2\u5206\u6D3E apply\uFF09" : ""}`).join("\n");
1141
+ return `### ${phase} \u9636\u6BB5\uFF08${rules.length} \u6761\u89C4\u5219\uFF09
1142
+
1143
+ ${items}`;
1144
+ });
1145
+ return blocks.join("\n\n");
1146
+ }
1147
+ function executeRule(id, ctx) {
1148
+ const rule = byId.get(id);
1149
+ if (!rule) {
1150
+ throw new Error(`[proteus] executeRule \u672A\u77E5\u89C4\u5219 ID: ${id}`);
1151
+ }
1152
+ if (!rule.apply) {
1153
+ throw new Error(`[proteus] \u89C4\u5219 ${id} \u672A\u767B\u8BB0 apply()\uFF08\u63CF\u8FF0\u5C42\u89C4\u5219\uFF1B\u5B9E\u73B0\u89C1 ${rule.source}\u2014\u2014AI \u53EF\u767B\u8BB0 apply \u540E\u7ECF\u5206\u6D3E\u5C42\u751F\u6548\uFF09`);
1154
+ }
1155
+ rule.apply(ctx);
1156
+ }
1157
+
1158
+ // src/overrides.ts
1159
+ function resolveOverrides(options) {
1160
+ const tagMap = { ...TAG_MAP };
1161
+ const eventMap = { ...EVENT_MAP };
1162
+ const semanticClass = { ...SEMANTIC_CLASS };
1163
+ const disabled = new Set(options?.disabled ?? []);
1164
+ for (const [ruleId, patch] of Object.entries(options?.mapping ?? {})) {
1165
+ const rule = getTransformRule(ruleId);
1166
+ if (!rule) {
1167
+ console.warn(`[proteus] \u89C4\u5219\u8986\u76D6\u5F15\u7528\u4E86\u672A\u6CE8\u518C\u7684\u89C4\u5219 ID\uFF1A${ruleId}\uFF08\u5DF2\u5FFD\u7565\uFF0C\u53EF\u7528 listTransformRules() \u67E5\u770B\u5408\u6CD5 ID\uFF09`);
1168
+ continue;
1169
+ }
1170
+ if (rule.id.startsWith("tag/")) Object.assign(tagMap, patch);
1171
+ else if (rule.id === "event/click-to-tap") Object.assign(eventMap, patch);
1172
+ else if (rule.id === "semantic/base-class") Object.assign(semanticClass, patch);
1173
+ else console.warn(`[proteus] \u89C4\u5219 ${ruleId} \u4E0D\u652F\u6301 mapping \u8986\u76D6\uFF08\u4EC5 tag/* / event/click-to-tap / semantic/base-class\uFF09`);
1174
+ }
1175
+ Object.assign(tagMap, options?.customTags);
1176
+ for (const id of disabled) {
1177
+ if (!getTransformRule(id)) console.warn(`[proteus] \u89C4\u5219\u8986\u76D6\u7981\u7528\u4E86\u672A\u6CE8\u518C\u7684\u89C4\u5219 ID\uFF1A${id}\uFF08\u5DF2\u5FFD\u7565\uFF09`);
1178
+ }
1179
+ return { tagMap, eventMap, semanticClass, disabled };
1180
+ }
1181
+
1182
+ // src/template.ts
1183
+ function escapeXml(s) {
1184
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1185
+ }
1186
+ function kebabCase(s) {
1187
+ return s.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
1188
+ }
1189
+ function camelToKebab(s) {
1190
+ return s.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
1191
+ }
1192
+ function suffixClassValue(value, scopeId) {
1193
+ return value.split(/\s+/).filter(Boolean).map((n) => n.endsWith(`-${scopeId}`) ? n : `${n}-${scopeId}`).join(" ");
1194
+ }
1195
+ function suffixClassName(name, scopeId) {
1196
+ if (!scopeId) return name;
1197
+ return name.endsWith(`-${scopeId}`) ? name : `${name}-${scopeId}`;
1198
+ }
1199
+ var NATIVE_TAGS = /* @__PURE__ */ new Set([
1200
+ "view",
1201
+ "scroll-view",
1202
+ "swiper",
1203
+ "swiper-item",
1204
+ "movable-view",
1205
+ "movable-area",
1206
+ "cover-view",
1207
+ "cover-image",
1208
+ "icon",
1209
+ "text",
1210
+ "rich-text",
1211
+ "progress",
1212
+ "button",
1213
+ "checkbox",
1214
+ "checkbox-group",
1215
+ "form",
1216
+ "input",
1217
+ "label",
1218
+ "picker",
1219
+ "picker-view",
1220
+ "picker-view-column",
1221
+ "radio",
1222
+ "radio-group",
1223
+ "slider",
1224
+ "switch",
1225
+ "textarea",
1226
+ "navigator",
1227
+ "image",
1228
+ "video",
1229
+ "camera",
1230
+ "live-player",
1231
+ "live-pusher",
1232
+ "map",
1233
+ "canvas",
1234
+ "web-view",
1235
+ "ad",
1236
+ "official-account",
1237
+ "open-data",
1238
+ "slot",
1239
+ "block",
1240
+ "template",
1241
+ "match-media",
1242
+ "page-container",
1243
+ "share-element",
1244
+ "sticky-header",
1245
+ "sticky-section",
1246
+ "root-portal",
1247
+ "channel-live",
1248
+ "channel-video",
1249
+ "list",
1250
+ "grid",
1251
+ "cell",
1252
+ "cell-group",
1253
+ "waterflow"
1254
+ ]);
1255
+ var INLINE_CONTROL_TAGS = /* @__PURE__ */ new Set([
1256
+ "switch",
1257
+ "slider",
1258
+ "icon",
1259
+ "image",
1260
+ "button",
1261
+ "input",
1262
+ "textarea",
1263
+ "checkbox",
1264
+ "radio",
1265
+ "label",
1266
+ "navigator",
1267
+ "progress"
1268
+ ]);
1269
+ function stripTypeAssertions(expr) {
1270
+ let out = expr.trim();
1271
+ for (let i = 0; i < 5; i++) {
1272
+ let changed = false;
1273
+ out = out.replace(/\(\s*([^()]*?)\s+as\s+[A-Za-z_$][\w$.]*(\[\])?\s*\)/g, (_m, inner) => {
1274
+ changed = true;
1275
+ return inner;
1276
+ });
1277
+ out = out.replace(/\s+as\s+[A-Za-z_$][\w$.]*(\[\])?\s*$/g, () => {
1278
+ changed = true;
1279
+ return "";
1280
+ });
1281
+ if (!changed) break;
1282
+ }
1283
+ return out.trim();
1284
+ }
1285
+ function exprContent(exp) {
1286
+ let out = "";
1287
+ if (exp == null) return "";
1288
+ if (typeof exp === "string") {
1289
+ out = exp;
1290
+ } else {
1291
+ const node = exp;
1292
+ if (node.type === NodeTypes.SIMPLE_EXPRESSION) {
1293
+ out = typeof node.content === "string" ? node.content : "";
1294
+ } else if (node.type === NodeTypes.INTERPOLATION) {
1295
+ out = exprContent(node.content);
1296
+ } else if (node.type === NodeTypes.COMPOUND_EXPRESSION || Array.isArray(node.children)) {
1297
+ out = node.children.map((c) => exprContent(c)).join("");
1298
+ } else if (node.type === NodeTypes.TEXT) {
1299
+ out = typeof node.content === "string" ? node.content : "";
1300
+ }
1301
+ }
1302
+ return stripTypeAssertions(out);
1303
+ }
1304
+ function parseForExpr(exp) {
1305
+ const m = exp.trim().match(/^\(?\s*([\w$]+)\s*(?:,\s*([\w$]+))?\s*\)?\s+(?:in|of)\s+(.+)$/);
1306
+ if (!m) return { list: exp };
1307
+ return { list: m[3].trim(), item: m[1], index: m[2] };
1308
+ }
1309
+ function cleanHandler(exp, warnings) {
1310
+ const t = exp.trim();
1311
+ if (/^[\w$]+$/.test(t)) return t;
1312
+ const m = t.match(/^([\w$]+)\(\$event\)$/);
1313
+ if (m) return m[1];
1314
+ warnings.push(`\u4E8B\u4EF6\u5904\u7406\u5668 "${t}" \u4E0D\u662F\u7B80\u5355\u65B9\u6CD5\u5F15\u7528\uFF08MVP \u4EC5\u652F\u6301\u65B9\u6CD5\u540D\uFF09\uFF0C\u5DF2\u539F\u6837\u8F93\u51FA`);
1315
+ return t;
1316
+ }
1317
+ function formatClassBinding(exp, warnings, scopeId = "") {
1318
+ const t = exp.trim();
1319
+ const sfx = (name) => scopeId && !name.endsWith(`-${scopeId}`) ? `${name}-${scopeId}` : name;
1320
+ const sfxExpr = (e) => scopeId ? e.replace(/'([^']*)'/g, (_m, s) => `'${sfx(s)}'`) : e;
1321
+ const dynWarn = (name) => {
1322
+ if (scopeId) warnings.push(`:class \u52A8\u6001\u7C7B\u540D "${name}" \u65E0\u6CD5 scoped \u540E\u7F00\uFF08MP \u5355\u7C7B\u9009\u62E9\u5668\u673A\u5236\uFF09\uFF0C\u8BE5\u52A8\u6001\u7C7B\u5728\u5C0F\u7A0B\u5E8F\u65E0 scoped \u6837\u5F0F\u5339\u914D\uFF08Web \u7AEF\u6B63\u5E38\uFF09`);
1323
+ };
1324
+ if (t.startsWith("{")) {
1325
+ const parts = [];
1326
+ const re = /(['"]?)([\w-]+)\1\s*:\s*([^,}]+)/g;
1327
+ let m;
1328
+ while (m = re.exec(t)) parts.push(`(${m[3].trim()}?'${sfx(m[2])} ':'')`);
1329
+ if (parts.length) return `{{${parts.join("+")}}}`;
1330
+ }
1331
+ if (t.startsWith("[")) {
1332
+ const items = splitTopLevel(t.slice(1, -1));
1333
+ const parts = [];
1334
+ for (const item of items) {
1335
+ const i = item.trim();
1336
+ if (!i) continue;
1337
+ const str = i.match(/^(['"])([^'"]*)\1$/);
1338
+ if (str) {
1339
+ parts.push(`'${sfx(str[2])} '`);
1340
+ continue;
1341
+ }
1342
+ if (i.startsWith("{")) {
1343
+ const inner = i.slice(1, -1);
1344
+ const shorthand = inner.trim().match(/^([\w$]+)$/);
1345
+ if (shorthand) {
1346
+ parts.push(`(${shorthand[1]}?'${sfx(shorthand[1])} ':'')`);
1347
+ continue;
1348
+ }
1349
+ const re = /(['"]?)([\w-]+)\1\s*:\s*([^,}]+)/g;
1350
+ let m;
1351
+ let ok = false;
1352
+ while (m = re.exec(inner)) {
1353
+ parts.push(`(${m[3].trim()}?'${sfx(m[2])} ':'')`);
1354
+ ok = true;
1355
+ }
1356
+ if (ok) continue;
1357
+ }
1358
+ if (/^[\w$.]+(?:\s*\?\s*[^:]+:.+)?$/.test(i)) {
1359
+ if (scopeId && /^[\w$.]+$/.test(i)) dynWarn(i);
1360
+ parts.push(`((${sfxExpr(i)})?(${sfxExpr(i)})+' ':'')`);
1361
+ continue;
1362
+ }
1363
+ warnings.push(`:class \u6570\u7EC4\u9879 "${i}" \u6682\u4E0D\u652F\u6301\uFF08MVP\uFF1A\u4EC5\u5B57\u7B26\u4E32/\u5BF9\u8C61/\u7B80\u5355\u53D8\u91CF/\u4E09\u5143\uFF09\uFF0C\u5DF2\u8DF3\u8FC7`);
1364
+ }
1365
+ if (parts.length) return `{{${parts.join("+")}}}`;
1366
+ }
1367
+ if (/^[\w$.]+$/.test(t)) dynWarn(t);
1368
+ return `{{${sfxExpr(t)}}}`;
1369
+ }
1370
+ function splitTopLevel(expr) {
1371
+ const parts = [];
1372
+ let depth = 0;
1373
+ let quote = null;
1374
+ let cur = "";
1375
+ for (let i = 0; i < expr.length; i++) {
1376
+ const ch = expr[i];
1377
+ if (quote) {
1378
+ cur += ch;
1379
+ if (ch === quote && expr[i - 1] !== "\\") quote = null;
1380
+ continue;
1381
+ }
1382
+ if (ch === "'" || ch === '"' || ch === "`") {
1383
+ quote = ch;
1384
+ cur += ch;
1385
+ continue;
1386
+ }
1387
+ if (ch === "(" || ch === "{" || ch === "[") {
1388
+ depth++;
1389
+ cur += ch;
1390
+ continue;
1391
+ }
1392
+ if (ch === ")" || ch === "}" || ch === "]") {
1393
+ depth--;
1394
+ cur += ch;
1395
+ continue;
1396
+ }
1397
+ if (ch === "," && depth === 0) {
1398
+ parts.push(cur);
1399
+ cur = "";
1400
+ continue;
1401
+ }
1402
+ cur += ch;
1403
+ }
1404
+ if (cur.trim()) parts.push(cur);
1405
+ return parts;
1406
+ }
1407
+ function formatStyleBinding(exp) {
1408
+ const t = exp.trim();
1409
+ if (t.startsWith("{")) {
1410
+ const parts = [];
1411
+ const re = /(['"]?)([\w-]+)\1\s*:\s*([^,}]+)/g;
1412
+ let m;
1413
+ while (m = re.exec(t)) parts.push(`${camelToKebab(m[2])}:{{${m[3].trim()}}}`);
1414
+ if (parts.length) return parts.join(";");
1415
+ }
1416
+ return `{{${t}}}`;
1417
+ }
1418
+ function rewriteStoreRefs(expr, ctx) {
1419
+ return expr.replace(/\bstore\.([A-Za-z_$][\w$]*)/g, (m, field) => {
1420
+ ctx.storeBindings.add(field);
1421
+ return field;
1422
+ });
1423
+ }
1424
+ function tryInlineHandler(exp) {
1425
+ const t = exp.trim();
1426
+ let m = t.match(/^([\w$]+)\+\+$/) ?? t.match(/^\+\+([\w$]+)$/);
1427
+ if (m) {
1428
+ return {
1429
+ name: `proteusInlineInc${capitalize(m[1])}`,
1430
+ code: `this.setData({ ${m[1]}: this.data.${m[1]} + 1 })`
1431
+ };
1432
+ }
1433
+ m = t.match(/^([\w$]+)--$/) ?? t.match(/^--([\w$]+)$/);
1434
+ if (m) {
1435
+ return {
1436
+ name: `proteusInlineDec${capitalize(m[1])}`,
1437
+ code: `this.setData({ ${m[1]}: this.data.${m[1]} - 1 })`
1438
+ };
1439
+ }
1440
+ m = t.match(/^([\w$]+)\(([^()]*)\)$/);
1441
+ if (m && /^[\w$,'"\s]*$/.test(m[2])) {
1442
+ const key = m[2].replace(/\W/g, "") || "NoArgs";
1443
+ return {
1444
+ name: `proteusInline${capitalize(m[1])}${key}`,
1445
+ code: `this.${m[1]}(${m[2]})`
1446
+ };
1447
+ }
1448
+ m = t.match(/^store\.([A-Za-z_$][\w$]*)\s*\(([^()]*)\)$/);
1449
+ if (m) {
1450
+ const method = m[1];
1451
+ const args = m[2].trim();
1452
+ const key = args.replace(/[^A-Za-z0-9_$+-]/g, "").replace(/-/g, "Minus").replace(/\+/g, "Plus") || "NoArgs";
1453
+ return {
1454
+ name: `proteusStore${capitalize(method)}${key}`,
1455
+ code: `this.store.${method}(${args.replace(/\bstore\./g, "this.store.")})`
1456
+ };
1457
+ }
1458
+ return null;
1459
+ }
1460
+ function serializeProgress(node, ctx) {
1461
+ const find = (name) => node.props.find(
1462
+ (p2) => p2.type === NodeTypes.ATTRIBUTE ? p2.name === name : exprContent(p2.arg) === name
1463
+ );
1464
+ const staticVal = (name) => {
1465
+ const a = find(name);
1466
+ return a && a.type === NodeTypes.ATTRIBUTE && a.value ? a.value.content : void 0;
1467
+ };
1468
+ const bindVal = (name) => {
1469
+ const d = find(name);
1470
+ return d && d.type === NodeTypes.DIRECTIVE && d.exp ? exprContent(d.exp) : void 0;
1471
+ };
1472
+ const attr = (name) => ({ static: staticVal(name), bind: bindVal(name) });
1473
+ const p = attr("percent");
1474
+ const c = attr("active-color") || attr("color");
1475
+ const sw = attr("stroke-width");
1476
+ const percentInner = p.bind ?? p.static ?? "0";
1477
+ const percentExpr = p.bind ? `{{${p.bind}}}` : p.static ?? "0";
1478
+ const colorExpr = c.bind ? `{{${c.bind}}}` : c.static ?? "#07c160";
1479
+ const swExpr = sw.bind ? `{{${sw.bind}}}` : sw.static ?? "6";
1480
+ const widthStyle = p.bind ? `width:{{${p.bind}}}%` : `width:${p.static ?? 0}%`;
1481
+ const colorStyle = c.bind ? `background-color:{{${c.bind}}}` : `background-color:${c.static ?? "#07c160"}`;
1482
+ const si = find("show-info");
1483
+ const showInfo = si ? si.type === NodeTypes.DIRECTIVE ? exprContent(si.exp) || "true" : "true" : "false";
1484
+ ctx.trace?.add("component/progress-degrade", {
1485
+ line: node.loc.start.line,
1486
+ before: '<progress percent="70" show-info>',
1487
+ after: "\u81EA\u5B9A\u4E49 view \u8FDB\u5EA6\u6761\uFF08Skyline \u4E0D\u652F\u6301\u539F\u751F progress\uFF0C16-progress-skyline-degrade\uFF09"
1488
+ });
1489
+ return `<view class="proteus-progress">
1490
+ <view class="proteus-progress-track" style="height:${swExpr}px">
1491
+ <view class="proteus-progress-inner" style="${widthStyle};${colorStyle}"></view>
1492
+ </view>
1493
+ ${showInfo === "false" ? "" : `<text wx:if="{{${showInfo}}}" class="proteus-progress-info">{{${percentInner}}}%</text>
1494
+ `}</view>`;
1495
+ }
1496
+ function serializeElement(node, ctx) {
1497
+ if (node.tag === "component") {
1498
+ ctx.warnings.push(
1499
+ `<component :is> \u52A8\u6001\u7EC4\u4EF6\u5728\u5C0F\u7A0B\u5E8F\u65E0\u5BF9\u7B49\u673A\u5236\uFF08\u4EA7\u7269\u4E3A\u65E0\u6548\u6807\u7B7E\uFF09\u2014\u2014\u8BF7\u7528 v-if/v-else \u6761\u4EF6\u6E32\u67D3\uFF08vue-compat Batch A\uFF09`
1500
+ );
1501
+ ctx.trace?.add("template/is-component", { line: node.loc.start.line, before: "<component :is>", after: "\uFF08\u65E0\u6548\u6807\u7B7E\uFF0C\u8BF7\u6761\u4EF6\u6E32\u67D3\uFF09" });
1502
+ }
1503
+ const isTransition = node.tag === "transition";
1504
+ if (isTransition) {
1505
+ const nameAttr = node.props.find((p) => p.type === NodeTypes.ATTRIBUTE && p.name === "name");
1506
+ const tName = (nameAttr && nameAttr.value ? nameAttr.value.content : "fade") || "fade";
1507
+ ctx.transitionClassName = `proteus-transition-${tName}`;
1508
+ ctx.usesTransition = true;
1509
+ ctx.transitionCtx = { ref: void 0, tName, index: -1 };
1510
+ ctx.trace?.add("transition/component", {
1511
+ line: node.loc.start.line,
1512
+ before: `<transition name="${tName}">`,
1513
+ after: `\u5B50\u5143\u7D20\u6CE8\u5165 class="proteus-transition-${tName}"\uFF08\u8FDB\u5165\u52A8\u753B\u81EA\u52A8\u64AD\u653E\uFF1B\u88F8 ref v-if \u5B50\u5143\u7D20\u542F\u7528\u79BB\u5F00\u52A8\u753B\u72B6\u6001\u673A Batch 5\uFF09`
1514
+ });
1515
+ return node.children.map((c) => serializeNode(c, ctx)).join("\n");
1516
+ }
1517
+ if ((node.tag === "transition-group" || node.tag === "teleport" || node.tag === "suspense" || node.tag === "keep-alive") && !isTransition) {
1518
+ ctx.warnings.push(
1519
+ `<${node.tag}> \u5728\u5C0F\u7A0B\u5E8F\u65E0\u5BF9\u7B49\u7EC4\u4EF6\uFF08\u5DF2\u539F\u6837\u8F93\u51FA\uFF0C\u4E0D\u751F\u6548\uFF09\u2014\u2014\u7F13\u5B58/\u4F20\u9001\u8BF7\u79FB\u9664\uFF1B\u591A\u5143\u7D20\u8F6C\u573A\u8BF7\u7528\u8DEF\u7531 routeType\uFF08vue-compat Batch A\uFF09`
1520
+ );
1521
+ ctx.trace?.add("template/no-peer", { line: node.loc.start.line, before: `<${node.tag}>`, after: "\uFF08\u65E0\u5BF9\u7B49\uFF0C\u539F\u6837\u8F93\u51FA\uFF09" });
1522
+ }
1523
+ const hasVHtml = node.props.some((p) => p.type === NodeTypes.DIRECTIVE && p.name === "html");
1524
+ const hasClick = node.props.some((p) => p.type === NodeTypes.DIRECTIVE && p.name === "on");
1525
+ const isNavLink = (node.tag === "a" || node.tag === "router-link") && !hasClick;
1526
+ const tagRuleId = TAG_RULE_BY_TAG[node.tag];
1527
+ let tag = hasVHtml ? "rich-text" : ctx.tagMap[node.tag] ?? kebabCase(node.tag);
1528
+ if (node.tag === "router-link") tag = "view";
1529
+ if (tagRuleId && ctx.disabled.has(tagRuleId)) {
1530
+ tag = kebabCase(node.tag);
1531
+ ctx.warnings.push(`\u89C4\u5219 ${tagRuleId} \u5DF2\u88AB\u7981\u7528\uFF08rules.disabled\uFF09\uFF0C<${node.tag}> \u6309\u672A\u6CE8\u518C\u6807\u7B7E\u539F\u6837\u8F93\u51FA`);
1532
+ }
1533
+ if (tag === "progress" && !ctx.disabled.has("component/progress-degrade")) {
1534
+ return serializeProgress(node, ctx);
1535
+ }
1536
+ if (!ctx.disabled.has("tag/unknown-kebab") && !(tagRuleId && ctx.disabled.has(tagRuleId))) {
1537
+ ctx.trace?.add(
1538
+ hasVHtml ? "tag/rich-text" : node.tag === "router-link" ? "tag/router-link" : TAG_RULE_BY_TAG[node.tag] ?? "tag/unknown-kebab",
1539
+ { line: node.loc.start.line, before: `<${node.tag}>`, after: `<${tag}>` }
1540
+ );
1541
+ }
1542
+ const baseClass = hasVHtml ? "" : ctx.semanticClass[node.tag] ?? "";
1543
+ const tagDisabled = Boolean(tagRuleId) && ctx.disabled.has(tagRuleId);
1544
+ if (baseClass && (ctx.disabled.has("semantic/base-class") || tagDisabled)) {
1545
+ ctx.warnings.push(`\u8BED\u4E49\u57FA\u7840\u7C7B\u5DF2\u88AB\u7981\u7528\uFF08${tagDisabled ? `${tagRuleId} \u88AB\u7981\u7528` : "semantic/base-class \u88AB\u7981\u7528"}\uFF0Crules.disabled\uFF09\uFF0C\u4E0D\u518D\u9644\u52A0`);
1546
+ }
1547
+ const effectiveBaseClass = baseClass && !ctx.disabled.has("semantic/base-class") && !tagDisabled ? baseClass : "";
1548
+ if (effectiveBaseClass) {
1549
+ ctx.trace?.add("semantic/base-class", { line: node.loc.start.line, before: node.tag, after: effectiveBaseClass });
1550
+ }
1551
+ const isInputLike = tag === "input" || tag === "textarea";
1552
+ const attrs = [];
1553
+ let hasNavTarget = false;
1554
+ let scopeClass = "";
1555
+ if (ctx.scopeId) {
1556
+ const scopeCtx = { input: { tag: node.tag, scopeId: ctx.scopeId } };
1557
+ executeRule("template/scope-attr", scopeCtx);
1558
+ scopeClass = scopeCtx.output ?? ctx.scopeId;
1559
+ ctx.trace?.add("template/scope-attr", { line: node.loc.start.line, before: `<${node.tag}>`, after: `<${node.tag} class="${scopeClass}">` });
1560
+ }
1561
+ const scopeSuffix = scopeClass || "";
1562
+ let staticClass;
1563
+ let bindingClass;
1564
+ for (const prop of node.props) {
1565
+ if (prop.type === NodeTypes.ATTRIBUTE) {
1566
+ const attr = prop;
1567
+ if (isNavLink && (attr.name === "href" || attr.name === "to" || attr.name === "route-type")) {
1568
+ if (ctx.disabled.has("nav/navigate-link")) {
1569
+ ctx.warnings.push("\u89C4\u5219 nav/navigate-link \u5DF2\u88AB\u7981\u7528\uFF08rules.disabled\uFF09\uFF0C<a> \u6309\u666E\u901A view \u8F93\u51FA\uFF08\u65E0\u5BFC\u822A\u8BED\u4E49\uFF09");
1570
+ break;
1571
+ }
1572
+ if (attr.name === "route-type" && attr.value) {
1573
+ attrs.push(`data-route-type="${escapeXml(attr.value.content)}"`);
1574
+ ctx.trace?.add("nav/route-type", { line: node.loc.start.line, before: `route-type="${attr.value.content}"`, after: `data-route-type="${attr.value.content}"` });
1575
+ } else if (attr.value) {
1576
+ attrs.push(`data-url="${escapeXml(attr.value.content)}"`);
1577
+ hasNavTarget = true;
1578
+ }
1579
+ continue;
1580
+ }
1581
+ if (attr.name === "class") {
1582
+ staticClass = attr.value ? escapeXml(attr.value.content) : "";
1583
+ continue;
1584
+ }
1585
+ if (attr.name === "ref") {
1586
+ ctx.warnings.push(
1587
+ `\u6A21\u677F ref="${attr.value ? attr.value.content : ""}" \u5728\u5C0F\u7A0B\u5E8F\u65E0\u5BF9\u7B49\u7ED1\u5B9A\uFF08\u6C38\u4E0D\u8D4B\u503C\uFF09\u2014\u2014\u8BF7\u7528 this.selectComponent('#id')\uFF08vue-compat Batch A\uFF09`
1588
+ );
1589
+ ctx.trace?.add("template/template-ref", { line: node.loc.start.line, before: `ref="${attr.value ? attr.value.content : ""}"`, after: "\uFF08\u5265\u79BB\uFF0C\u65E0\u5BF9\u7B49\uFF09" });
1590
+ continue;
1591
+ }
1592
+ attrs.push(attr.value ? `${attr.name}="${escapeXml(attr.value.content)}"` : attr.name);
1593
+ continue;
1594
+ }
1595
+ const dir = prop;
1596
+ if (isNavLink && dir.name === "bind" && (exprContent(dir.arg) === "href" || exprContent(dir.arg) === "to")) {
1597
+ const exp = exprContent(dir.exp);
1598
+ if (exp.trim().startsWith("{")) {
1599
+ ctx.warnings.push("\u8DEF\u7531\u94FE\u63A5 :to/:href \u5BF9\u8C61\u5F62\u5F0F\u6682\u4E0D\u652F\u6301\uFF08MVP\uFF09\uFF0C\u8BF7\u6539\u7528\u5B57\u7B26\u4E32\u8DEF\u5F84");
1600
+ } else if (!ctx.disabled.has("nav/navigate-link")) {
1601
+ attrs.push(`data-url="{{${exp}}}"`);
1602
+ hasNavTarget = true;
1603
+ } else {
1604
+ ctx.warnings.push("\u89C4\u5219 nav/navigate-link \u5DF2\u88AB\u7981\u7528\uFF08rules.disabled\uFF09\uFF0C<a> \u6309\u666E\u901A view \u8F93\u51FA\uFF08\u65E0\u5BFC\u822A\u8BED\u4E49\uFF09");
1605
+ }
1606
+ continue;
1607
+ }
1608
+ switch (dir.name) {
1609
+ case "if":
1610
+ if (ctx.disabled.has("directive/v-if")) {
1611
+ ctx.warnings.push("\u89C4\u5219 directive/v-if \u5DF2\u88AB\u7981\u7528\uFF08rules.disabled\uFF09\uFF0Cv-if \u5DF2\u5FFD\u7565");
1612
+ break;
1613
+ }
1614
+ if (ctx.transitionCtx && /^[A-Za-z_$][\w$]*$/.test(exprContent(dir.exp))) {
1615
+ const ref = exprContent(dir.exp);
1616
+ ctx.transitionCtx.ref = ref;
1617
+ ctx.transitionCtx.index = ctx.transitions.length;
1618
+ ctx.transitions.push({ ref, tName: ctx.transitionCtx.tName, index: ctx.transitionCtx.index });
1619
+ attrs.push(`wx:if="{{__tv${ctx.transitionCtx.index}}}"`);
1620
+ ctx.trace?.add("transition/leave-state", {
1621
+ line: node.loc.start.line,
1622
+ before: `v-if="${ref}"`,
1623
+ after: `wx:if="{{__tv${ctx.transitionCtx.index}}}"\uFF08\u79BB\u5F00\u52A8\u753B\u72B6\u6001\u673A\uFF1A__tl${ctx.transitionCtx.index} \u64AD\u79BB\u5F00\u52A8\u753B + \u5EF6\u8FDF\u79FB\u9664\uFF09`
1624
+ });
1625
+ break;
1626
+ }
1627
+ if (ctx.transitionCtx) ctx.transitionCtx.ref = void 0;
1628
+ attrs.push(`wx:if="{{${exprContent(dir.exp)}}}"`);
1629
+ ctx.trace?.add("directive/v-if", { line: node.loc.start.line, before: `v-if="${exprContent(dir.exp)}"`, after: `wx:if="{{${exprContent(dir.exp)}}}"` });
1630
+ break;
1631
+ case "else-if":
1632
+ if (ctx.disabled.has("directive/v-else-if")) break;
1633
+ attrs.push(`wx:elif="{{${exprContent(dir.exp)}}}"`);
1634
+ ctx.trace?.add("directive/v-else-if", { line: node.loc.start.line, before: "v-else-if", after: "wx:elif" });
1635
+ break;
1636
+ case "else":
1637
+ if (ctx.disabled.has("directive/v-else")) break;
1638
+ attrs.push("wx:else");
1639
+ ctx.trace?.add("directive/v-else", { line: node.loc.start.line, before: "v-else", after: "wx:else" });
1640
+ break;
1641
+ case "for": {
1642
+ if (ctx.disabled.has("directive/v-for")) {
1643
+ ctx.warnings.push("\u89C4\u5219 directive/v-for \u5DF2\u88AB\u7981\u7528\uFF08rules.disabled\uFF09\uFF0Cv-for \u5DF2\u5FFD\u7565");
1644
+ break;
1645
+ }
1646
+ const f = parseForExpr(exprContent(dir.exp));
1647
+ attrs.push(`wx:for="{{${f.list}}}"`);
1648
+ if (f.item) attrs.push(`wx:for-item="${f.item}"`);
1649
+ if (f.index) attrs.push(`wx:for-index="${f.index}"`);
1650
+ ctx.trace?.add("directive/v-for", { line: node.loc.start.line, before: exprContent(dir.exp), after: `wx:for="{{${f.list}}}"` });
1651
+ break;
1652
+ }
1653
+ case "on": {
1654
+ if (ctx.disabled.has("event/click-to-tap") && ctx.disabled.has("event/modifier-catch")) {
1655
+ ctx.warnings.push("\u4E8B\u4EF6\u6620\u5C04\u89C4\u5219\u5DF2\u5168\u90E8\u7981\u7528\uFF08rules.disabled\uFF09\uFF0C@\u4E8B\u4EF6 \u539F\u6837\u8F93\u51FA");
1656
+ const handler2 = cleanHandler(exprContent(dir.exp), ctx.warnings);
1657
+ attrs.push(`bind${exprContent(dir.arg)}="${handler2}"`);
1658
+ break;
1659
+ }
1660
+ const raw = exprContent(dir.arg);
1661
+ const mapped = ctx.eventMap[raw] ?? raw;
1662
+ const mods = dir.modifiers.map(
1663
+ (m) => typeof m === "string" ? m : m?.content ?? ""
1664
+ );
1665
+ const isCatch = (mods.includes("stop") || mods.includes("prevent")) && !ctx.disabled.has("event/modifier-catch");
1666
+ const isSelf = mods.includes("self") && !isCatch;
1667
+ const isOnce = mods.includes("once") && !isCatch;
1668
+ const rawHandler = exprContent(dir.exp);
1669
+ const inline2 = tryInlineHandler(rawHandler);
1670
+ let handler;
1671
+ if (inline2 && !isSelf && !isOnce) {
1672
+ handler = inline2.name;
1673
+ if (!ctx.inlineHandlers.some((h) => h.name === inline2.name)) ctx.inlineHandlers.push(inline2);
1674
+ ctx.trace?.add("event/inline-expression", { line: node.loc.start.line, before: `@${exprContent(dir.arg)}="${rawHandler}"`, after: `${inline2.name}\uFF08\u5305\u88C5\u65B9\u6CD5\uFF09` });
1675
+ } else {
1676
+ handler = cleanHandler(rawHandler, ctx.warnings);
1677
+ }
1678
+ if (raw === "keyup" || raw === "keydown" || raw === "keypress") {
1679
+ const keyMods = mods.filter((m) => !["stop", "prevent", "self", "once"].includes(m));
1680
+ ctx.warnings.push(`@${raw}${keyMods.length ? "." + keyMods.join(".") : ""} \u5728\u5C0F\u7A0B\u5E8F\u65E0\u5BF9\u7B49\u952E\u76D8\u4E8B\u4EF6\uFF08input \u952E\u76D8\u884C\u4E3A\u8BF7\u7528 @confirm\uFF09\uFF0C\u5DF2\u539F\u6837\u8F93\u51FA`);
1681
+ }
1682
+ const isCustomEvent = !(raw in ctx.eventMap);
1683
+ const prefix = `${isCatch ? "catch" : "bind"}${isCustomEvent ? ":" : ""}`;
1684
+ if ((isSelf || isOnce) && /^[\w$]+$/.test(handler)) {
1685
+ if (isSelf) ctx.selfHandlers.add(handler);
1686
+ if (isOnce) ctx.onceHandlers.add(handler);
1687
+ const wrap = `${isSelf ? "proteusSelf" : "proteusOnce"}${capitalize(handler)}`;
1688
+ attrs.push(`${prefix}${mapped}="${wrap}"`);
1689
+ } else {
1690
+ attrs.push(`${prefix}${mapped}="${handler}"`);
1691
+ }
1692
+ ctx.trace?.add(
1693
+ isCatch ? "event/modifier-catch" : isSelf || isOnce ? "event/modifier-self-once" : "event/click-to-tap",
1694
+ { line: node.loc.start.line, before: `@${raw}`, after: `${prefix}${mapped}` }
1695
+ );
1696
+ break;
1697
+ }
1698
+ case "bind": {
1699
+ const arg = exprContent(dir.arg);
1700
+ const exp = exprContent(dir.exp);
1701
+ if (node.tag === "slot" && arg !== "class" && arg !== "style" && arg !== "name") {
1702
+ ctx.warnings.push(
1703
+ `\u4F5C\u7528\u57DF\u63D2\u69FD <slot :${arg}> \u5728\u5C0F\u7A0B\u5E8F\u65E0\u5BF9\u7B49\u673A\u5236\uFF08\u7236\u4FA7\u62FF\u4E0D\u5230\u5B50\u7EC4\u4EF6\u6570\u636E\uFF1BMP/Skyline \u65E0\u6A21\u677F\u4F20\u53C2\u2014\u2014vue-compat-advance Batch 7 \u5E73\u53F0\u9650\u5236\uFF09\u2014\u2014\u66FF\u4EE3\u6A21\u5F0F\uFF1A\u5B50\u7EC4\u4EF6 props \u63A5\u6536\u6570\u636E + \u81EA\u5B9A\u4E49\u4E8B\u4EF6\u56DE\u8C03\u4F20\u6570\u636E\uFF0C\u5982 <MyList :items="items" @item-tap="onItemTap" />\uFF08props.items \u5185\u6E32\u67D3 + triggerEvent \u56DE\u4F20\uFF09`
1704
+ );
1705
+ ctx.trace?.add("slot/scoped-slot", { line: node.loc.start.line, before: `<slot :${arg}="${exp}">`, after: "\uFF08\u65E0\u6548\uFF0CMP \u4E0D\u4F20\u6570\u636E\uFF09\u2192 \u66FF\u4EE3\uFF1Aprops \u4F20\u5B50 + \u4E8B\u4EF6\u56DE\u8C03" });
1706
+ }
1707
+ if (arg === "class") {
1708
+ if (ctx.disabled.has("directive/v-bind-class")) break;
1709
+ bindingClass = formatClassBinding(exp, ctx.warnings, scopeSuffix);
1710
+ ctx.trace?.add("directive/v-bind-class", { line: node.loc.start.line, before: `:class="${exp}"`, after: bindingClass });
1711
+ } else if (arg === "style") {
1712
+ if (ctx.disabled.has("directive/v-bind-style")) break;
1713
+ attrs.push(`style="${formatStyleBinding(exp)}"`);
1714
+ ctx.trace?.add("directive/v-bind-style", { line: node.loc.start.line, before: `:style="${exp}"`, after: formatStyleBinding(exp) });
1715
+ } else if (arg === "key") {
1716
+ if (ctx.disabled.has("directive/v-bind-key")) break;
1717
+ if (/^[\w$]+$/.test(exp)) attrs.push(`wx:key="${exp}"`);
1718
+ else ctx.warnings.push(`:key="${exp}" \u4E0D\u662F\u7B80\u5355\u6807\u8BC6\u7B26\uFF08MVP\uFF09\uFF0Cwx:key \u5DF2\u5FFD\u7565`);
1719
+ ctx.trace?.add("directive/v-bind-key", { line: node.loc.start.line, before: `:key="${exp}"`, after: `wx:key="${exp}"` });
1720
+ } else {
1721
+ if (ctx.disabled.has("directive/v-bind")) break;
1722
+ attrs.push(`${arg}="{{${rewriteStoreRefs(exp, ctx)}}}"`);
1723
+ ctx.trace?.add("directive/v-bind", { line: node.loc.start.line, before: `:${arg}`, after: `${arg}="{{${rewriteStoreRefs(exp, ctx)}}}"` });
1724
+ }
1725
+ break;
1726
+ }
1727
+ case "model": {
1728
+ if (ctx.disabled.has("directive/v-model")) {
1729
+ ctx.warnings.push("\u89C4\u5219 directive/v-model \u5DF2\u88AB\u7981\u7528\uFF08rules.disabled\uFF09\uFF0Cv-model \u5DF2\u5FFD\u7565");
1730
+ break;
1731
+ }
1732
+ const model = exprContent(dir.exp);
1733
+ if (model && !ctx.vModelBindings.includes(model)) ctx.vModelBindings.push(model);
1734
+ if (isInputLike) attrs.push(`value="{{${model}}}"`);
1735
+ attrs.push(`bindinput="proteusOn${capitalize(model)}Input"`);
1736
+ ctx.trace?.add("directive/v-model", { line: node.loc.start.line, before: `v-model="${model}"`, after: `bindinput="proteusOn${capitalize(model)}Input"` });
1737
+ break;
1738
+ }
1739
+ case "html":
1740
+ if (ctx.disabled.has("directive/v-html")) {
1741
+ ctx.warnings.push("\u89C4\u5219 directive/v-html \u5DF2\u88AB\u7981\u7528\uFF08rules.disabled\uFF09\uFF0Cv-html \u5DF2\u5FFD\u7565");
1742
+ break;
1743
+ }
1744
+ attrs.push(`nodes="{{${exprContent(dir.exp)}}}"`);
1745
+ ctx.trace?.add("directive/v-html", { line: node.loc.start.line, before: "v-html", after: "rich-text nodes" });
1746
+ break;
1747
+ case "show":
1748
+ if (ctx.disabled.has("directive/v-show")) break;
1749
+ attrs.push(`hidden="{{!${exprContent(dir.exp)}}}"`);
1750
+ ctx.trace?.add("directive/v-show", { line: node.loc.start.line, before: `v-show="${exprContent(dir.exp)}"`, after: `hidden="{{!${exprContent(dir.exp)}}}"` });
1751
+ break;
1752
+ default:
1753
+ if (dir.name === "slot" || dir.name === "pre" || dir.name === "cloak") {
1754
+ break;
1755
+ }
1756
+ ctx.warnings.push(
1757
+ `\u81EA\u5B9A\u4E49\u6307\u4EE4 v-${dir.name} \u5728\u5C0F\u7A0B\u5E8F\u65E0\u5BF9\u7B49\u673A\u5236\uFF08\u5DF2\u5265\u79BB\u4E14\u4E0D\u6267\u884C\uFF09\u2014\u2014\u8BF7\u6539\u7528\u65B9\u6CD5\u8C03\u7528\u6216\u6761\u4EF6\u6E32\u67D3\uFF08vue-compat Batch A\uFF09`
1758
+ );
1759
+ ctx.trace?.add("directive/custom", { line: node.loc.start.line, before: `v-${dir.name}`, after: "\uFF08\u5265\u79BB\uFF0C\u65E0\u5BF9\u7B49\uFF09" });
1760
+ break;
1761
+ }
1762
+ }
1763
+ if (hasNavTarget && !ctx.disabled.has("nav/navigate-link")) {
1764
+ attrs.push('bindtap="proteusNavigateTo"');
1765
+ ctx.usesNavigate = true;
1766
+ ctx.trace?.add("nav/navigate-link", { line: node.loc.start.line, before: `<${node.tag}>` + (node.tag === "a" ? " href/to" : " to"), after: 'data-url + bindtap="proteusNavigateTo"' });
1767
+ }
1768
+ let transitionAnimCls = "";
1769
+ let transitionLeaveExpr = "";
1770
+ if (ctx.transitionClassName) {
1771
+ transitionAnimCls = ctx.transitionClassName;
1772
+ ctx.transitionClassName = void 0;
1773
+ const tctx = ctx.transitionCtx;
1774
+ ctx.transitionCtx = void 0;
1775
+ if (tctx && tctx.ref !== void 0) {
1776
+ transitionLeaveExpr = `{{__tl${tctx.index} ? '${suffixClassName(`${transitionAnimCls}-leave`, scopeSuffix)}' : ''}}`;
1777
+ }
1778
+ }
1779
+ const suffix = (v) => scopeSuffix ? suffixClassValue(v, scopeSuffix) : v;
1780
+ let autoFlexRow = false;
1781
+ if (!ctx.disabled.has("layout/auto-flex-row")) {
1782
+ const childTags = node.children.filter((c) => c.type === NodeTypes.ELEMENT).map((c) => ctx.tagMap[c.tag] ?? kebabCase(c.tag));
1783
+ const controls = childTags.filter((t) => INLINE_CONTROL_TAGS.has(t));
1784
+ const textCount = childTags.filter((t) => t === "text").length;
1785
+ autoFlexRow = controls.length === 1 && textCount === 1;
1786
+ if (autoFlexRow) {
1787
+ ctx.trace?.add("layout/auto-flex-row", {
1788
+ line: node.loc.start.line,
1789
+ before: `<${node.tag}>\uFF081 text + 1 \u884C\u5185\u63A7\u4EF6\uFF09`,
1790
+ after: `\u81EA\u52A8\u9644\u52A0 proteus-flex-row\uFF08display:flex;row;align-items:center\u2014\u2014Skyline \u65E0 inline\uFF0C\u884C\u5185\u6392\u5E03\u5FC5\u987B flex row\uFF09`
1791
+ });
1792
+ }
1793
+ }
1794
+ const classStatic = [transitionAnimCls, autoFlexRow ? "proteus-flex-row" : "", effectiveBaseClass, staticClass].filter((c) => Boolean(c)).map(suffix);
1795
+ const classInterp = [bindingClass, transitionLeaveExpr].filter((c) => Boolean(c));
1796
+ if (ctx.isComponentRoot) classInterp.push("{{rootClass}}");
1797
+ const isComponentTag = !NATIVE_TAGS.has(tag);
1798
+ const rootClassEnabled = !ctx.disabled.has("component/root-class");
1799
+ if (classStatic.length || classInterp.length) {
1800
+ const clsValue = `${classStatic.join(" ")}${classInterp.length ? `${classStatic.length ? " " : ""}${classInterp.join(" ")}` : ""}`;
1801
+ attrs.push(isComponentTag && rootClassEnabled ? `root-class="${clsValue}"` : `class="${clsValue}"`);
1802
+ if (isComponentTag && rootClassEnabled) {
1803
+ ctx.trace?.add("component/root-class", { line: node.loc.start.line, before: `<${node.tag} class="${clsValue}">`, after: `<${node.tag} root-class="${clsValue}">` });
1804
+ }
1805
+ }
1806
+ const attrStr = attrs.length ? ` ${attrs.join(" ")}` : "";
1807
+ const lineNote = ctx.annotateLines ? `<!-- @${node.loc.start.line} ${node.tag} -->
1808
+ ` : "";
1809
+ if (lineNote && !ctx.lineNoteTraced) {
1810
+ ctx.lineNoteTraced = true;
1811
+ ctx.trace?.add("annotation/line-note", { line: node.loc.start.line, before: `<${node.tag}>`, after: `<!-- @${node.loc.start.line} ${node.tag} -->` });
1812
+ }
1813
+ if (!node.children.length) return `${lineNote}<${tag}${attrStr} />`;
1814
+ const hasElementChild = node.children.some((c) => c.type === NodeTypes.ELEMENT);
1815
+ if (hasElementChild) {
1816
+ const inner = node.children.map((c) => serializeNode(c, ctx)).join("\n");
1817
+ return `${lineNote}<${tag}${attrStr}>
1818
+ ${inner}
1819
+ </${tag}>`;
1820
+ }
1821
+ const inline = node.children.map((c) => serializeNode(c, ctx)).join("");
1822
+ return `${lineNote}<${tag}${attrStr}>${inline}</${tag}>`;
1823
+ }
1824
+ function serializeNode(node, ctx) {
1825
+ switch (node.type) {
1826
+ case NodeTypes.ELEMENT:
1827
+ return serializeElement(node, ctx);
1828
+ case NodeTypes.TEXT:
1829
+ return escapeXml(node.content);
1830
+ case NodeTypes.INTERPOLATION:
1831
+ ctx.trace?.add("node/interpolation", { line: node.loc.start.line, before: "{{ expr }}", after: "{{ expr }}\uFF08\u539F\u6837\u4FDD\u7559\uFF09" });
1832
+ return `{{ ${rewriteStoreRefs(node.content.content, ctx)} }}`;
1833
+ case NodeTypes.COMMENT:
1834
+ return `<!-- ${node.content} -->`;
1835
+ default:
1836
+ return "";
1837
+ }
1838
+ }
1839
+ function capitalize(s) {
1840
+ return s.charAt(0).toUpperCase() + s.slice(1);
1841
+ }
1842
+ function transformTemplateToWxml(source, opts = { px2rpx: true, rpxRatio: 2, annotateLines: false }) {
1843
+ const ctx = {
1844
+ vModelBindings: [],
1845
+ warnings: [],
1846
+ annotateLines: opts.annotateLines ?? false,
1847
+ filename: opts.filename,
1848
+ usesNavigate: false,
1849
+ trace: opts.trace,
1850
+ // ★底线循环 ①③:生效配置 = tags.ts 常量 + config 覆盖(规则改写/禁用即时生效)
1851
+ ...resolveOverrides(opts.rules),
1852
+ scopeId: opts.scopeId,
1853
+ selfHandlers: /* @__PURE__ */ new Set(),
1854
+ onceHandlers: /* @__PURE__ */ new Set(),
1855
+ inlineHandlers: [],
1856
+ usesTransition: false,
1857
+ transitions: [],
1858
+ storeBindings: /* @__PURE__ */ new Set()
1859
+ };
1860
+ const root = domParse(source, { onError: () => void 0 });
1861
+ const isComp = opts.isComponent === true;
1862
+ const autoScroll = opts.autoScrollContainer !== false && !isComp;
1863
+ const topTags = root.children.filter((c) => c.type === NodeTypes.ELEMENT).map((c) => c.tag);
1864
+ const alreadyScroll = topTags.length === 1 && (topTags[0] === "scroll-view" || topTags[0] === "p-scroll-view");
1865
+ let firstEl = isComp;
1866
+ const inner = root.children.map((c) => {
1867
+ if (firstEl && c.type === NodeTypes.ELEMENT) {
1868
+ firstEl = false;
1869
+ return serializeNode(c, { ...ctx, isComponentRoot: true });
1870
+ }
1871
+ return serializeNode(c, ctx);
1872
+ }).join("\n");
1873
+ let wxml = inner;
1874
+ if (autoScroll && !alreadyScroll) {
1875
+ const hooks = opts.pageScrollHooks ?? {};
1876
+ const bindAttrs = ["scroll-y", 'class="proteus-page-scroll"'];
1877
+ if (hooks.hasOnPageScroll) bindAttrs.push('bindscroll="proteusPageScroll"');
1878
+ if (hooks.hasOnReachBottom) bindAttrs.push('bindscrolltolower="proteusReachBottom"');
1879
+ if (hooks.hasOnPullDownRefresh) {
1880
+ bindAttrs.push('refresher-enabled="{{true}}" refresher-triggered="{{__proteusRefreshing}}" bindrefresherrefresh="proteusPullDownRefresh"');
1881
+ }
1882
+ if (hooks.hasPageScrollTo) {
1883
+ bindAttrs.push('scroll-top="{{__proteusPageScrollTop}}" scroll-with-animation');
1884
+ }
1885
+ wxml = `<scroll-view ${bindAttrs.join(" ")}>
1886
+ ${inner}
1887
+ </scroll-view>`;
1888
+ } else if (!isComp && (opts.pageScrollHooks?.hasOnPageScroll || opts.pageScrollHooks?.hasOnReachBottom)) {
1889
+ ctx.warnings.push(
1890
+ "\u6B67\u4E49\u8B66\u544A\uFF1A\u9875\u9762\u58F0\u660E onPageScroll/onReachBottom \u4E14\u9876\u5C42\u662F scroll-view\uFF08\u7528\u6237\u663E\u5F0F\u6EDA\u52A8\u5BB9\u5668\uFF09\u2014\u2014\u9875\u9762\u7EA7\u6EDA\u52A8\u94A9\u5B50\u4E0D\u4F1A\u88AB scroll-view \u89E6\u53D1\uFF0C\u8BF7\u7528 bindscroll/bindscrolltolower \u6216\u4EA4\u7531\u81EA\u52A8\u5305\u88C5\uFF0815-page-scroll-container\uFF09"
1891
+ );
1892
+ }
1893
+ for (const w of ctx.warnings) console.warn(`[mp-transform] ${w}`);
1894
+ return {
1895
+ wxml,
1896
+ vModelBindings: ctx.vModelBindings,
1897
+ usesNavigate: ctx.usesNavigate,
1898
+ selfHandlers: [...ctx.selfHandlers],
1899
+ onceHandlers: [...ctx.onceHandlers],
1900
+ inlineHandlers: ctx.inlineHandlers,
1901
+ usesTransition: ctx.usesTransition,
1902
+ // ★Batch 5:离开动画状态机列表(裸 ref v-if 的 transition 子元素)
1903
+ transitions: ctx.transitions,
1904
+ // ★pinia-plan 12 P1:模板 store 引用字段(script 生成绑定)
1905
+ storeBindings: [...ctx.storeBindings],
1906
+ // ★15-page-scroll-container:已自动包滚动容器(compileVueSfc 据此注入高度样式)
1907
+ pageScrollWrapped: autoScroll && !alreadyScroll,
1908
+ warnings: ctx.warnings
1909
+ };
1910
+ }
1911
+
1912
+ // src/trace.ts
1913
+ function createTrace(phase = "template") {
1914
+ return {
1915
+ events: [],
1916
+ add(ruleId, opts) {
1917
+ this.events.push({ ruleId, phase, ...opts });
1918
+ }
1919
+ };
1920
+ }
1921
+ function lineAt(source, index) {
1922
+ let line = 1;
1923
+ for (let i = 0; i < index && i < source.length; i++) {
1924
+ if (source[i] === "\n") line++;
1925
+ }
1926
+ return line;
1927
+ }
1928
+
1929
+ // src/script.ts
1930
+ function evalLiteral(expr) {
1931
+ try {
1932
+ return Function(`"use strict"; return (${expr})`)();
1933
+ } catch {
1934
+ return void 0;
1935
+ }
1936
+ }
1937
+ function extractProvideInject(source) {
1938
+ const provides = [];
1939
+ const injects = [];
1940
+ const pRe = /^provide\s*\(\s*['"]([^'"]+)['"]\s*,\s*([^)]+)\)/gm;
1941
+ let m;
1942
+ while (m = pRe.exec(source)) {
1943
+ const lineStart = source.lastIndexOf("\n", m.index) + 1;
1944
+ if (source.slice(lineStart, m.index) !== "") continue;
1945
+ provides.push({ key: m[1], expr: m[2].trim(), line: lineAt(source, m.index) });
1946
+ }
1947
+ const iRe = /^const\s+([A-Za-z_$][\w$]*)\s*=\s*inject\s*\(\s*['"]([^'"]+)['"]\s*(?:,\s*([^)]+))?\)/gm;
1948
+ while (m = iRe.exec(source)) {
1949
+ const lineStart = source.lastIndexOf("\n", m.index) + 1;
1950
+ if (source.slice(lineStart, m.index) !== "") continue;
1951
+ const inj = { name: m[1], key: m[2], line: lineAt(source, m.index) };
1952
+ if (m[3] !== void 0) inj.def = m[3].trim();
1953
+ injects.push(inj);
1954
+ }
1955
+ return { provides, injects };
1956
+ }
1957
+ function buildProvideInject(provides, injects, data, computeds) {
1958
+ const pageOpen = [
1959
+ "const __reg = getApp().__proteusProvides || (getApp().__proteusProvides = {})",
1960
+ "__reg.__seq = (__reg.__seq || 0) + 1",
1961
+ "this.__proteusPageId = 'p' + __reg.__seq",
1962
+ "const provides = (__reg[this.__proteusPageId] || (__reg[this.__proteusPageId] = {}))"
1963
+ ].join("\n");
1964
+ const compOpen = [
1965
+ "const __pages = (typeof getCurrentPages === 'function' ? getCurrentPages() : [])",
1966
+ "const __pid = __pages.length ? __pages[__pages.length - 1].__proteusPageId : ''",
1967
+ "this.__proteusPageId = __pid || 'global'",
1968
+ "const __reg = getApp().__proteusProvides || (getApp().__proteusProvides = {})",
1969
+ "const provides = (__reg[this.__proteusPageId] || (__reg[this.__proteusPageId] = {}))"
1970
+ ].join("\n");
1971
+ const provideRefs = /* @__PURE__ */ new Map();
1972
+ const pLines = [];
1973
+ for (const p of provides) {
1974
+ let expr = p.expr;
1975
+ const vm = expr.match(/^([A-Za-z_$][\w$]*)\.value$/);
1976
+ if (vm && (vm[1] in data || vm[1] in computeds)) expr = `this.data.${vm[1]}`;
1977
+ else if (expr in data) {
1978
+ expr = `this.data.${expr}`;
1979
+ provideRefs.set(p.expr, p.key);
1980
+ }
1981
+ pLines.push(`provides[${JSON.stringify(p.key)}] = ${expr}`);
1982
+ if (provideRefs.has(p.expr)) {
1983
+ pLines.push(`if (!provides.__subs) provides.__subs = {}; if (!provides.__subs[${JSON.stringify(p.key)}]) provides.__subs[${JSON.stringify(p.key)}] = []`);
1984
+ }
1985
+ }
1986
+ const iLines = [];
1987
+ for (const inj of injects) {
1988
+ const read = `provides[${JSON.stringify(inj.key)}]`;
1989
+ const value = inj.def === void 0 ? read : `(${read} === undefined ? ${inj.def} : ${read})`;
1990
+ iLines.push(`this.setData({ ${inj.name}: ${value} })`);
1991
+ }
1992
+ if (injects.length) {
1993
+ iLines.push("const __self = this");
1994
+ for (const inj of injects) {
1995
+ const read = `provides[${JSON.stringify(inj.key)}]`;
1996
+ iLines.push(`if (provides.__subs && provides.__subs[${JSON.stringify(inj.key)}]) {`);
1997
+ iLines.push(` const __sub = { k: ${JSON.stringify(inj.key)}, fn: function () { __self.setData({ ${inj.name}: ${read} }) } }`);
1998
+ iLines.push(" if (!this.__proteusSubs) this.__proteusSubs = []");
1999
+ iLines.push(" this.__proteusSubs.push(__sub)");
2000
+ iLines.push(` provides.__subs[${JSON.stringify(inj.key)}].push(__sub)`);
2001
+ iLines.push("}");
2002
+ }
2003
+ }
2004
+ return {
2005
+ page: pLines.length || iLines.length ? `${pageOpen}
2006
+ ${[...pLines, ...iLines].join("\n")}` : "",
2007
+ provide: pLines.length ? `${compOpen}
2008
+ ${pLines.join("\n")}` : "",
2009
+ inject: iLines.length ? `${compOpen}
2010
+ ${iLines.join("\n")}` : "",
2011
+ provideRefs
2012
+ };
2013
+ }
2014
+ function extractImports(source) {
2015
+ const out = [];
2016
+ const lines = source.split("\n");
2017
+ for (let i = 0; i < lines.length; i++) {
2018
+ const line = lines[i];
2019
+ const t = line.trim();
2020
+ if (!t.startsWith("import ")) continue;
2021
+ const lineNo = i + 1;
2022
+ let m = t.match(/^import\s+['"]([^'"]+)['"];?$/);
2023
+ if (m) {
2024
+ out.push({ source: m[1], kind: "side", names: [], line: lineNo, typeOnly: false });
2025
+ continue;
2026
+ }
2027
+ m = t.match(/^import\s+type\s+([\s\S]*?)\s+from\s+['"]([^'"]+)['"];?$/);
2028
+ if (m) {
2029
+ out.push({ source: m[2], kind: "named", names: [], line: lineNo, typeOnly: true });
2030
+ continue;
2031
+ }
2032
+ m = t.match(/^import\s+([A-Za-z_$][\w$]*)\s*,\s*\{([^}]*)\}\.*\s+from\s+['"]([^'"]+)['"];?$/);
2033
+ if (m) {
2034
+ const names = m[2].split(",").map((n) => n.trim()).filter(Boolean).map((n) => n.replace(/\s+as\s+[\w$]+$/, "").trim());
2035
+ out.push({ source: m[3], kind: "default", names: [m[1]], line: lineNo, typeOnly: false });
2036
+ if (names.length) out.push({ source: m[3], kind: "named", names, line: lineNo, typeOnly: false });
2037
+ continue;
2038
+ }
2039
+ m = t.match(/^import\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\s+['"]([^'"]+)['"];?$/);
2040
+ if (m) {
2041
+ out.push({ source: m[2], kind: "namespace", names: [m[1]], line: lineNo, typeOnly: false });
2042
+ continue;
2043
+ }
2044
+ m = t.match(/^import\s+\{([^}]*)\}\s+from\s+['"]([^'"]+)['"];?$/);
2045
+ if (m) {
2046
+ const names = m[1].split(",").map((n) => n.trim()).filter(Boolean).map((n) => n.replace(/\s+as\s+[\w$]+$/, "").trim());
2047
+ out.push({ source: m[2], kind: "named", names, line: lineNo, typeOnly: false });
2048
+ continue;
2049
+ }
2050
+ m = t.match(/^import\s+([A-Za-z_$][\w$]*)\s+from\s+['"]([^'"]+)['"];?$/);
2051
+ if (m) {
2052
+ out.push({ source: m[2], kind: "default", names: [m[1]], line: lineNo, typeOnly: false });
2053
+ continue;
2054
+ }
2055
+ }
2056
+ return out;
2057
+ }
2058
+ function extractBracedBody(source, openBraceIndex) {
2059
+ let depth = 0;
2060
+ for (let i = openBraceIndex; i < source.length; i++) {
2061
+ const ch = source[i];
2062
+ if (ch === "{") depth++;
2063
+ else if (ch === "}") {
2064
+ depth--;
2065
+ if (depth === 0) return source.slice(openBraceIndex + 1, i);
2066
+ }
2067
+ }
2068
+ return null;
2069
+ }
2070
+ function extractInitializer(source, valueStart) {
2071
+ let depth = 0;
2072
+ let quote = null;
2073
+ let escaped = false;
2074
+ let inBlockComment = false;
2075
+ let i = valueStart;
2076
+ const len = source.length;
2077
+ for (; i < len; i++) {
2078
+ const ch = source[i];
2079
+ const next = source[i + 1];
2080
+ if (inBlockComment) {
2081
+ if (ch === "*" && next === "/") {
2082
+ inBlockComment = false;
2083
+ i++;
2084
+ }
2085
+ continue;
2086
+ }
2087
+ if (quote) {
2088
+ if (escaped) {
2089
+ escaped = false;
2090
+ continue;
2091
+ }
2092
+ if (ch === "\\") {
2093
+ escaped = true;
2094
+ continue;
2095
+ }
2096
+ if (ch === quote) quote = null;
2097
+ continue;
2098
+ }
2099
+ if (ch === "/" && next === "*") {
2100
+ inBlockComment = true;
2101
+ i++;
2102
+ continue;
2103
+ }
2104
+ if (ch === "/" && next === "/") {
2105
+ while (i < len && source[i] !== "\n") i++;
2106
+ if (depth === 0) break;
2107
+ continue;
2108
+ }
2109
+ if (ch === '"' || ch === "'" || ch === "`") {
2110
+ quote = ch;
2111
+ continue;
2112
+ }
2113
+ if (ch === "(" || ch === "[" || ch === "{") {
2114
+ depth++;
2115
+ continue;
2116
+ }
2117
+ if (ch === ")" || ch === "]" || ch === "}") {
2118
+ depth--;
2119
+ continue;
2120
+ }
2121
+ if (depth === 0 && (ch === ";" || ch === "\n")) break;
2122
+ }
2123
+ return source.slice(valueStart, i).trim();
2124
+ }
2125
+ function mapTsType(t, warnings, name) {
2126
+ const s = t.trim();
2127
+ if (s === "string") return { type: "String", value: "" };
2128
+ if (s === "number") return { type: "Number", value: 0 };
2129
+ if (s === "boolean") return { type: "Boolean", value: false };
2130
+ if (s === "object") return { type: "Object" };
2131
+ if (s.endsWith("[]") || s === "Array") return { type: "Array" };
2132
+ if (s.includes("|") || s.startsWith("'")) return { type: "String", value: "" };
2133
+ warnings.push(`prop ${name} \u7684\u7C7B\u578B ${s} \u65E0\u6CD5\u6620\u5C04\uFF08\u6CDB\u578B\u5F62\u5F0F\u652F\u6301 string/number/boolean/object/Array/\u8054\u5408\uFF09\uFF0C\u5DF2\u6309 String \u5904\u7406`);
2134
+ return { type: "String", value: "" };
2135
+ }
2136
+ function extractProps(source, warnings, trace) {
2137
+ const out = {};
2138
+ const add = (name, info, before) => {
2139
+ out[name] = info;
2140
+ trace?.add("script/define-props", { before, after: `properties.${name}\uFF08type: ${info.type}\uFF09` });
2141
+ };
2142
+ const m = source.match(/\bdefineProps\s*\(\s*\{/);
2143
+ if (m) {
2144
+ const body = extractBracedBody(source, (m.index ?? 0) + m[0].length - 1);
2145
+ if (body === null) {
2146
+ warnings.push("defineProps \u89E3\u6790\u5931\u8D25\uFF08MVP \u4EC5\u652F\u6301\u5BF9\u8C61\u5F62\u5F0F defineProps({...})\uFF09\uFF0C\u5DF2\u5FFD\u7565");
2147
+ return out;
2148
+ }
2149
+ const re2 = /(['"]?)([A-Za-z_$][\w$]*)\1\s*:\s*(\{[^}]*\}|[A-Za-z_$][\w$]*)/g;
2150
+ let pm2;
2151
+ while (pm2 = re2.exec(body)) {
2152
+ const name = pm2[2];
2153
+ const spec = pm2[3].trim();
2154
+ let type = "String";
2155
+ let value;
2156
+ if (spec.startsWith("{")) {
2157
+ const typeM = spec.match(/type\s*:\s*([A-Za-z_$][\w$]*)/);
2158
+ if (typeM) type = typeM[1];
2159
+ const defM = spec.match(/default\s*:\s*([^,}]+)/);
2160
+ if (defM) value = evalLiteral(defM[1].trim());
2161
+ } else {
2162
+ type = spec;
2163
+ }
2164
+ if (!["String", "Number", "Boolean", "Object", "Array", "Function"].includes(type)) {
2165
+ warnings.push(`prop ${name} \u7684\u7C7B\u578B ${type} \u65E0\u6CD5\u6620\u5C04\u5230\u5FAE\u4FE1 properties\uFF08MVP \u652F\u6301 String/Number/Boolean/Object/Array\uFF09\uFF0C\u5DF2\u6309 String \u5904\u7406`);
2166
+ type = "String";
2167
+ }
2168
+ if (typeof value === "function") {
2169
+ warnings.push(`prop ${name} \u7684 default \u662F\u51FD\u6570\uFF08\u5FAE\u4FE1 properties.value \u4EC5\u652F\u6301\u5B57\u9762\u91CF\uFF09\uFF0C\u5DF2\u5FFD\u7565\u9ED8\u8BA4\u503C`);
2170
+ value = void 0;
2171
+ }
2172
+ if (value === void 0) {
2173
+ if (type === "String") value = "";
2174
+ else if (type === "Number") value = 0;
2175
+ else if (type === "Boolean") value = false;
2176
+ }
2177
+ add(name, { type, value }, `defineProps({ ${name}: ... })`);
2178
+ }
2179
+ return out;
2180
+ }
2181
+ const tsM = source.match(/\bdefineProps\s*<\{([\s\S]*?)\}\s*>/);
2182
+ if (!tsM) return out;
2183
+ const re = /([A-Za-z_$][\w$]*)\s*(\?)?\s*:\s*([^;]+)/g;
2184
+ let pm;
2185
+ while (pm = re.exec(tsM[1])) {
2186
+ const name = pm[1];
2187
+ const info = mapTsType(pm[3], warnings, name);
2188
+ add(name, info, `defineProps<{ ${name}${pm[2] ?? ""}: ${pm[3].trim()} }>()`);
2189
+ }
2190
+ return out;
2191
+ }
2192
+ function extractWatch(source, data, warnings, trace, allowPropWatch = true) {
2193
+ const out = {};
2194
+ const re = /^watch\s*\(\s*([\s\S]*?)\s*,\s*(?:\(([^)]*)\)\s*=>|function\s*\(([^)]*)\)\s*)\s*\{/gm;
2195
+ let m;
2196
+ while (m = re.exec(source)) {
2197
+ const lineStart = source.lastIndexOf("\n", m.index) + 1;
2198
+ if (source.slice(lineStart, m.index) !== "") continue;
2199
+ const rawSrc = m[1].trim();
2200
+ const params = (m[2] ?? m[3] ?? "").split(",").map((s) => s.trim()).filter(Boolean);
2201
+ let propField;
2202
+ if (allowPropWatch && rawSrc.startsWith("props.")) {
2203
+ propField = rawSrc.slice("props.".length).trim().replace(/\.value$/, "");
2204
+ } else if (allowPropWatch && rawSrc.startsWith("()")) {
2205
+ const getter = rawSrc.replace(/^\(\)\s*=>\s*/, "").trim();
2206
+ const pm = getter.match(/^props\.([A-Za-z_$][\w$]*)$/);
2207
+ if (pm) propField = pm[1];
2208
+ }
2209
+ if (propField) {
2210
+ const braceIdx2 = m.index + m[0].length - 1;
2211
+ const body2 = extractBracedBody(source, braceIdx2);
2212
+ if (body2 === null) {
2213
+ warnings.push(`watch ${rawSrc} \u56DE\u8C03\u4F53\u89E3\u6790\u5931\u8D25\uFF0C\u5DF2\u8DF3\u8FC7`);
2214
+ continue;
2215
+ }
2216
+ const after2 = source.slice(braceIdx2 + body2.length + 1, braceIdx2 + body2.length + 120);
2217
+ const immediate2 = /immediate\s*:\s*true/.test(after2);
2218
+ const id2 = `Prop${capitalize2(propField)}`;
2219
+ if (out[id2]) {
2220
+ warnings.push(`watch ${rawSrc} \u4E0E\u5DF2\u6709 watch \u91CD\u540D\uFF08${id2}\uFF09\uFF0C\u540E\u8005\u8986\u76D6\u524D\u8005`);
2221
+ }
2222
+ trace?.add("script/watch-props", {
2223
+ line: lineAt(source, m.index),
2224
+ before: `watch(${rawSrc}, (${params.join(", ")}) => ...)`,
2225
+ after: `observers: { ${propField}(n, o) { ... }${immediate2 ? " + proteusWatchPropX \u65B9\u6CD5\uFF08attached \u521D\u59CB\u5316\u8C03\u7528\u4E00\u6B21\uFF09" : ""} }`
2226
+ });
2227
+ out[id2] = { id: id2, deps: [], params, body: body2, immediate: immediate2, line: lineAt(source, m.index), propField };
2228
+ continue;
2229
+ }
2230
+ let deps = [];
2231
+ let expr;
2232
+ if (rawSrc.startsWith("[")) {
2233
+ deps = Array.from(rawSrc.matchAll(/\b([A-Za-z_$][\w$]*)\b/g), (mm) => mm[1]);
2234
+ } else if (rawSrc.startsWith("()")) {
2235
+ const getter = rawSrc.replace(/^\(\)\s*=>\s*/, "");
2236
+ deps = [...new Set(Array.from(getter.matchAll(/\b([A-Za-z_$][\w$]*)\.value\b/g), (mm) => mm[1]))];
2237
+ expr = getter.replace(/\b([A-Za-z_$][\w$]*)\.value\b/g, "this.data.$1");
2238
+ } else {
2239
+ deps = [rawSrc];
2240
+ }
2241
+ const missing = deps.filter((d) => !(d in data));
2242
+ if (missing.length) {
2243
+ warnings.push(`watch \u4F9D\u8D56 ${missing.join("/")} \u672A\u5728\u9876\u5C42 data \u4E2D\u5B9A\u4E49\uFF08watch \u7684\u6E90\u5FC5\u987B\u662F\u672C\u6587\u4EF6\u9876\u5C42 ref/reactive\uFF09`);
2244
+ continue;
2245
+ }
2246
+ if (!deps.length) {
2247
+ warnings.push(`watch \u6E90\u65E0\u6CD5\u89E3\u6790\u4F9D\u8D56\uFF08${rawSrc.slice(0, 40)}\uFF09\uFF0C\u5DF2\u8DF3\u8FC7`);
2248
+ continue;
2249
+ }
2250
+ const id = deps.map((d) => capitalize2(d)).join("And");
2251
+ const braceIdx = m.index + m[0].length - 1;
2252
+ const body = extractBracedBody(source, braceIdx);
2253
+ if (body === null) {
2254
+ warnings.push(`watch ${rawSrc} \u56DE\u8C03\u4F53\u89E3\u6790\u5931\u8D25\uFF0C\u5DF2\u8DF3\u8FC7`);
2255
+ continue;
2256
+ }
2257
+ const after = source.slice(braceIdx + body.length + 1, braceIdx + body.length + 120);
2258
+ const immediate = /immediate\s*:\s*true/.test(after);
2259
+ if (out[id]) {
2260
+ warnings.push(`watch ${rawSrc} \u4E0E\u5DF2\u6709 watch \u91CD\u540D\uFF08${id}\uFF09\uFF0C\u540E\u8005\u8986\u76D6\u524D\u8005`);
2261
+ }
2262
+ trace?.add("script/watch-to-methods", {
2263
+ line: lineAt(source, m.index),
2264
+ before: `watch(${rawSrc}, (${params.join(", ")}) => ...)`,
2265
+ after: `proteusWatch${id}\uFF08${deps.join("/")} \u5199\u5165 setData \u540E\u81EA\u52A8\u8C03\u7528${immediate ? "\uFF0Cimmediate \u521D\u59CB\u5316\u4E00\u6B21" : ""}\uFF09`
2266
+ });
2267
+ out[id] = { id, deps, params, body, immediate, line: lineAt(source, m.index), expr };
2268
+ }
2269
+ return out;
2270
+ }
2271
+ function extractComputedFromInit(name, init, data, warnings) {
2272
+ const arrow = init.match(/^computed\s*\(\s*\(\)\s*=>\s*([\s\S]*?)\s*\)\s*;?$/);
2273
+ let rawExpr;
2274
+ let setter;
2275
+ if (arrow) {
2276
+ rawExpr = arrow[1];
2277
+ if (rawExpr.trim().startsWith("{")) return null;
2278
+ } else {
2279
+ const objM = init.match(/^computed\s*\(\s*\{/);
2280
+ if (!objM) return null;
2281
+ const body = extractBracedBody(init, (objM.index ?? 0) + objM[0].length - 1);
2282
+ if (body === null) return null;
2283
+ const getM = body.match(/\bget\s*:\s*\(\)\s*=>\s*([\s\S]*?)(?=,\s*\bset\s*:|$)/);
2284
+ rawExpr = getM?.[1]?.trim();
2285
+ if (!rawExpr || rawExpr.startsWith("{")) return null;
2286
+ const setM = body.match(/\bset\s*:\s*\(([^)]*)\)\s*=>\s*\{([\s\S]*?)\}/);
2287
+ if (setM) setter = { param: setM[1].trim(), body: setM[2] };
2288
+ }
2289
+ const deps = [...new Set(Array.from(rawExpr.matchAll(/\b([A-Za-z_$][\w$]*)\.value\b/g), (mm) => mm[1]))];
2290
+ const missing = deps.filter((d) => !(d in data));
2291
+ if (missing.length) {
2292
+ warnings.push(
2293
+ `computed ${name} \u4F9D\u8D56 ${missing.join("/")} \u672A\u5728\u9876\u5C42 data \u4E2D\u5B9A\u4E49\uFF08${name} \u7684\u4F9D\u8D56\u5FC5\u987B\u662F\u672C\u6587\u4EF6\u9876\u5C42 ref/reactive\uFF09`
2294
+ );
2295
+ }
2296
+ const expr = rawExpr.replace(/\b([A-Za-z_$][\w$]*)\.value\b/g, "this.data.$1");
2297
+ return { name, deps, expr, setter };
2298
+ }
2299
+ function extractData(source, warnings, trace) {
2300
+ const data = {};
2301
+ const runtimeInits = [];
2302
+ const rawComputed = [];
2303
+ const re = /const\s+([A-Za-z_$][\w$]*)\s*=\s*/gm;
2304
+ let m;
2305
+ while (m = re.exec(source)) {
2306
+ const lineStart = source.lastIndexOf("\n", m.index) + 1;
2307
+ if (source.slice(lineStart, m.index) !== "") continue;
2308
+ const name = m[1];
2309
+ const init = extractInitializer(source, m.index + m[0].length);
2310
+ if (!init) continue;
2311
+ const line = lineAt(source, m.index);
2312
+ if (/^(?:defineProps\s*[<(]|defineEmits\s*\(|defineExpose\s*\()/.test(init)) continue;
2313
+ if (/^(?:async\s+)?(?:function\b|(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>)/.test(init)) continue;
2314
+ if (init.startsWith("computed(")) {
2315
+ rawComputed.push({ name, init, line });
2316
+ continue;
2317
+ }
2318
+ trace?.add("script/const-to-data", {
2319
+ line,
2320
+ before: `const ${name} = ${init.slice(0, 40)}${init.length > 40 ? "\u2026" : ""}`,
2321
+ after: `data.${name}`
2322
+ });
2323
+ const inner = init.match(/^(?:ref|reactive|shallowRef|readonly)\s*\(\s*([\s\S]*?)\s*\);?\s*$/);
2324
+ const raw = inner ? inner[1] : init;
2325
+ const value = evalLiteral(raw);
2326
+ const isCall = /^[\w$.]+\(/.test(raw.trim());
2327
+ if (isCall && value === void 0 && !/^inject\s*\(/.test(raw.trim())) {
2328
+ runtimeInits.push({ name, call: raw.trim() });
2329
+ trace?.add("script/runtime-init", {
2330
+ line,
2331
+ before: `const ${name} = ${raw.slice(0, 40)}`,
2332
+ after: `this.${name} = ${raw.trim()}\uFF08onLoad/attached \u8FD0\u884C\u65F6\u521D\u59CB\u5316\uFF0C\u5B9E\u4F8B\u5C5E\u6027\uFF1B\u6A21\u677F\u7ED1\u5B9A\u4E0D\u652F\u6301\uFF09`
2333
+ });
2334
+ warnings.push(
2335
+ `const ${name} \u7684\u521D\u59CB\u503C "${raw.slice(0, 40)}" \u662F\u51FD\u6570\u8C03\u7528\u2014\u2014\u5DF2\u7F16\u8BD1\u4E3A\u8FD0\u884C\u65F6\u521D\u59CB\u5316 this.${name}\uFF08onLoad/attached \u6267\u884C\uFF0C\u5B9E\u4F8B\u5C5E\u6027\uFF1A\u6A21\u677F\u7ED1\u5B9A\u4E0D\u652F\u6301\uFF0C\u903B\u8F91\u5C42\u53EF\u7528\uFF1B\u5171\u4EAB\u903B\u8F91\u8BF7\u7528\u6A21\u5757 import\uFF0C\u89C1 docs/proteus-module-plan/\uFF09`
2336
+ );
2337
+ continue;
2338
+ }
2339
+ if (value === void 0 && raw !== "undefined" && !/^inject\s*\(/.test(raw.trim())) {
2340
+ warnings.push(`const ${name} \u7684\u521D\u59CB\u503C "${raw.slice(0, 40)}" \u65E0\u6CD5\u9759\u6001\u6C42\u503C\uFF0Cdata.${name} \u5C06\u8BBE\u4E3A undefined\uFF08MVP \u9650\u5236\uFF1A\u4EC5\u652F\u6301\u5B57\u9762\u91CF\uFF09`);
2341
+ }
2342
+ data[name] = value;
2343
+ }
2344
+ const computed = {};
2345
+ for (const c of rawComputed) {
2346
+ const info = extractComputedFromInit(c.name, c.init, data, warnings);
2347
+ if (info) {
2348
+ computed[c.name] = info;
2349
+ trace?.add("script/computed-to-data", {
2350
+ line: c.line,
2351
+ before: `const ${c.name} = computed(() => ...)`,
2352
+ after: `\u6D3E\u751F\u5B57\u6BB5\uFF08\u4F9D\u8D56 ${info.deps.join("/") || "\u65E0"}\uFF0C\u5199\u5165\u65F6\u5408\u5E76\u91CD\u7B97\uFF09`
2353
+ });
2354
+ } else {
2355
+ warnings.push(`computed ${c.name} \u4EC5\u652F\u6301\u7BAD\u5934\u7B80\u5199 + \u8868\u8FBE\u5F0F\u4F53\uFF08computed(() => expr)\uFF09\uFF0C\u5DF2\u5FFD\u7565`);
2356
+ }
2357
+ }
2358
+ return { data, computed, runtimeInits };
2359
+ }
2360
+ function stripParamTypes(params) {
2361
+ return params.replace(/:\s*[^,)]+/g, "").trim();
2362
+ }
2363
+ function checkDefineExpose(source, data, warnings, trace) {
2364
+ const m = source.match(/\bdefineExpose\s*\(\s*\{([\s\S]*?)\}\s*\)/);
2365
+ if (!m) return;
2366
+ const names = Array.from(m[1].matchAll(/([A-Za-z_$][\w$]*)/g), (mm) => mm[1]);
2367
+ trace?.add("script/define-expose", {
2368
+ before: `defineExpose({ ${names.join(", ")} })`,
2369
+ after: "no-op\uFF08\u7EC4\u4EF6 methods \u5929\u7136\u53EF\u88AB selectComponent \u8BBF\u95EE\uFF09"
2370
+ });
2371
+ for (const n of names) {
2372
+ if (n in data) {
2373
+ warnings.push(`defineExpose \u66B4\u9732\u7684 ${n} \u662F ref \u503C\uFF08\u5C0F\u7A0B\u5E8F\u65E0\u5BF9\u7B49\u673A\u5236\uFF0C\u5916\u90E8\u4EC5\u53EF\u8BBF\u95EE methods\uFF09\uFF0C\u5DF2\u5FFD\u7565\u2014\u2014\u8BF7\u7528\u65B9\u6CD5\u5305\u88C5`);
2374
+ }
2375
+ }
2376
+ }
2377
+ function extractMethods(source, warnings, trace, disabled) {
2378
+ const methods = {};
2379
+ const fnRe = /(async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(([^)]*)\)\s*\{/g;
2380
+ let m;
2381
+ if (!disabled?.has("script/function-to-methods")) {
2382
+ while (m = fnRe.exec(source)) {
2383
+ const name = m[2];
2384
+ const isAsync = Boolean(m[1]);
2385
+ const params = stripParamTypes(m[3]);
2386
+ const body = extractBracedBody(source, m.index + m[0].length - 1);
2387
+ const line = lineAt(source, m.index);
2388
+ trace?.add("script/function-to-methods", { line, before: `function ${name}(${m[3]})`, after: `${name}(${params})` });
2389
+ if (body !== null) methods[name] = { src: `${isAsync ? "async " : ""}${name}(${params}) {
2390
+ ${body}
2391
+ }`, line };
2392
+ else warnings.push(`\u51FD\u6570 ${name} \u4F53\u89E3\u6790\u5931\u8D25\uFF0C\u5DF2\u8DF3\u8FC7`);
2393
+ }
2394
+ }
2395
+ const arrowRe = /const\s+([A-Za-z_$][\w$]*)\s*=\s*(async\s*)?\(([^)]*)\)\s*=>\s*\{/g;
2396
+ if (!disabled?.has("script/arrow-to-methods")) {
2397
+ while (m = arrowRe.exec(source)) {
2398
+ const name = m[1];
2399
+ const isAsync = Boolean(m[2]);
2400
+ const params = stripParamTypes(m[3]);
2401
+ const braceIdx = source.indexOf("{", m.index + m[0].length - 1);
2402
+ const body = extractBracedBody(source, braceIdx);
2403
+ const line = lineAt(source, m.index);
2404
+ trace?.add("script/arrow-to-methods", { line, before: `const ${name} = (...) =>`, after: `${name}(...)` });
2405
+ if (body !== null) methods[name] = { src: `${isAsync ? "async " : ""}${name}(${params}) {
2406
+ ${body}
2407
+ }`, line };
2408
+ }
2409
+ }
2410
+ return methods;
2411
+ }
2412
+ function numOrZero(expr) {
2413
+ return `(${expr} === undefined || ${expr} === null ? 0 : ${expr})`;
2414
+ }
2415
+ function computedPatch(writtenRef, computeds) {
2416
+ const patches = Object.entries(computeds).filter(([, c]) => c.deps.includes(writtenRef)).map(([n, c]) => `${n}: ${c.expr}`);
2417
+ return patches.length ? `, ${patches.join(", ")}` : "";
2418
+ }
2419
+ function computedInitLine(computeds) {
2420
+ const entries = Object.entries(computeds);
2421
+ if (!entries.length) return "";
2422
+ return `this.setData({ ${entries.map(([n, c]) => `${n}: ${c.expr}`).join(", ")} })`;
2423
+ }
2424
+ function runtimeInitLine(inits) {
2425
+ return inits.map((i) => `this.${i.name} = ${i.call}`).join("\n");
2426
+ }
2427
+ function storeBindingLine(fields, storeVar) {
2428
+ const map = fields.map((f) => `${f}: __self.${storeVar}.${f}`).join(", ");
2429
+ return [
2430
+ "const __self = this",
2431
+ `this.setData({ ${map} })`,
2432
+ `if (this.${storeVar} && this.${storeVar}.$subscribe) {`,
2433
+ ` this.${storeVar}.$subscribe(function () { __self.setData({ ${map} }) })`,
2434
+ `}`
2435
+ ].join("\n");
2436
+ }
2437
+ function immediateWatchLine(watches) {
2438
+ const lines = Object.entries(watches).filter(([, w]) => w.immediate).map(([, w]) => {
2439
+ const single = w.deps.length === 1;
2440
+ const newVals = w.propField ? `this.data.${w.propField}` : single ? w.expr ?? `this.data.${w.deps[0]}` : `[${w.expr ?? w.deps.map((d) => `this.data.${d}`).join(", ")}]`;
2441
+ const oldVals = w.propField ? "undefined" : single ? "undefined" : `[${w.deps.map(() => "undefined").join(", ")}]`;
2442
+ return `this.proteusWatch${w.id}(${newVals}, ${oldVals})`;
2443
+ });
2444
+ return lines.join("\n");
2445
+ }
2446
+ function writeSetData(name, valueExpr, patch, hasWatch = false, forceWrite = false) {
2447
+ const needWrite = forceWrite || Boolean(patch) || hasWatch;
2448
+ if (!needWrite) return `this.setData({ ${name}: ${valueExpr} })`;
2449
+ return `this.data.${name} = ${valueExpr}; this.setData({ ${name}: this.data.${name}${patch} })`;
2450
+ }
2451
+ function watchTail(w) {
2452
+ if (!w) return "";
2453
+ const single = w.deps.length === 1;
2454
+ const newVals = single ? w.expr ?? `this.data.${w.deps[0]}` : `[${w.expr ?? w.deps.map((d) => `this.data.${d}`).join(", ")}]`;
2455
+ const oldVals = single ? `old${capitalize2(w.deps[0])}` : `[${w.deps.map((d) => `old${capitalize2(d)}`).join(", ")}]`;
2456
+ return `; this.proteusWatch${w.id}(${newVals}, ${oldVals})`;
2457
+ }
2458
+ function rewriteBareMethodCalls(body, methodNames) {
2459
+ let out = body;
2460
+ for (const name of methodNames) {
2461
+ out = out.replace(new RegExp(`(?<![\\w$.])${name}\\s*\\(`, "g"), `this.${name}(`);
2462
+ }
2463
+ return out;
2464
+ }
2465
+ function rewriteRefAccess(body, refNames, trace, disabled, computeds = {}, watches = {}, emitEnabled = false, propsVar, providedRefs, transitionToggle) {
2466
+ const skip = (id) => disabled?.has(id);
2467
+ let out = body;
2468
+ out = out.replace(/\s+as\s+(?:unknown|any|never)\b/g, "");
2469
+ out = out.replace(/\s+as\s+[A-Za-z_$][\w$]*(?:<[^;\n]*?>)?/g, "");
2470
+ if (emitEnabled) out = out.replace(/\bemit\s*\(/g, "this.triggerEvent(");
2471
+ if (propsVar) out = out.replace(new RegExp(`\\b${propsVar}\\.([A-Za-z_$][\\w$]*)`, "g"), "this.data.$1");
2472
+ for (const [cname, c] of Object.entries(computeds)) {
2473
+ if (!new RegExp(`\\b${cname}\\.value\\s*=`).test(out)) continue;
2474
+ if (c.setter) {
2475
+ out = out.replace(
2476
+ new RegExp(`\\b${cname}\\.value\\s*=\\s*(?!=)([^;\\n]+)`),
2477
+ (_m, expr) => `this.proteusSet${capitalize2(cname)}(${expr.trim()})`
2478
+ );
2479
+ } else {
2480
+ out = out.replace(
2481
+ new RegExp(`\\b${cname}\\.value\\s*=\\s*(?!=)([^;\\n]+)`),
2482
+ () => `/* computed ${cname} \u53EA\u8BFB\uFF08\u65E0 setter\uFF09\uFF0C\u8D4B\u503C\u5DF2\u5FFD\u7565 */`
2483
+ );
2484
+ }
2485
+ }
2486
+ for (const name of refNames) {
2487
+ const prop = `this.data.${name}`;
2488
+ const line = lineAt(body, Math.max(0, body.indexOf(name)));
2489
+ const patch = computedPatch(name, computeds);
2490
+ const w = Object.values(watches).find((ww) => ww.deps.includes(name) && !skip("script/watch-to-methods"));
2491
+ const oldSave = w ? `const ${w.deps.map((d) => `old${capitalize2(d)} = this.data.${d}`).join(", ")}; ` : "";
2492
+ const tail = watchTail(w);
2493
+ const sync = providedRefs && providedRefs.get(name) ? `; this.proteusSyncProvide(${JSON.stringify(providedRefs.get(name))}, ${JSON.stringify(name)})` : "";
2494
+ const tToggle = transitionToggle && transitionToggle.get(name) ? `; this.${transitionToggle.get(name)}()` : "";
2495
+ if (!skip("script/ref-incdec")) {
2496
+ if (new RegExp(`(\\+\\+|--)\\s*${name}\\.value`).test(body) || new RegExp(`\\b${name}\\.value\\s*(\\+\\+|--)`).test(body)) {
2497
+ trace?.add("script/ref-incdec", { line, before: `${name}.value++/--`, after: `this.setData({ ${name}: ...${patch || w ? " + \u6D3E\u751F/\u8054\u52A8" : ""} })` });
2498
+ }
2499
+ out = out.replace(new RegExp(`\\+\\+\\s*${name}\\.value`, "g"), `${oldSave}${writeSetData(name, `${numOrZero(prop)} + 1`, patch, Boolean(w), true)}${tail}${sync}${tToggle}`);
2500
+ out = out.replace(new RegExp(`--\\s*${name}\\.value`, "g"), `${oldSave}${writeSetData(name, `${numOrZero(prop)} - 1`, patch, Boolean(w), true)}${tail}${sync}${tToggle}`);
2501
+ out = out.replace(new RegExp(`\\b${name}\\.value\\s*\\+\\+`, "g"), `${oldSave}${writeSetData(name, `${numOrZero(prop)} + 1`, patch, Boolean(w))}${tail}${sync}${tToggle}`);
2502
+ out = out.replace(new RegExp(`\\b${name}\\.value\\s*--`, "g"), `${oldSave}${writeSetData(name, `${numOrZero(prop)} - 1`, patch, Boolean(w))}${tail}${sync}${tToggle}`);
2503
+ }
2504
+ if (!skip("script/ref-write")) {
2505
+ if (new RegExp(`\\b${name}\\.value\\s*=\\s*(?!=)`).test(out)) {
2506
+ trace?.add("script/ref-write", { line, before: `${name}.value = expr`, after: `this.setData({ ${name}: expr${patch || w ? " + \u6D3E\u751F/\u8054\u52A8" : ""} })` });
2507
+ }
2508
+ const assignRe = new RegExp(`\\b${name}\\.value\\s*=\\s*(?!=)`);
2509
+ const chunks = [];
2510
+ let rest = out;
2511
+ while (true) {
2512
+ const am = assignRe.exec(rest);
2513
+ if (!am) {
2514
+ chunks.push(rest);
2515
+ break;
2516
+ }
2517
+ chunks.push(rest.slice(0, am.index));
2518
+ let i = am.index + am[0].length;
2519
+ let depth = 0;
2520
+ let inStr = null;
2521
+ while (i < rest.length) {
2522
+ const ch = rest[i];
2523
+ if (inStr) {
2524
+ if (ch === "\\") {
2525
+ i += 2;
2526
+ continue;
2527
+ }
2528
+ if (ch === inStr) inStr = null;
2529
+ } else if (ch === '"' || ch === "'") {
2530
+ inStr = ch;
2531
+ } else if (ch === "{" || ch === "(" || ch === "[") {
2532
+ depth++;
2533
+ } else if (ch === "}" || ch === ")" || ch === "]") {
2534
+ depth--;
2535
+ } else if (depth === 0 && (ch === ";" || ch === "\n")) {
2536
+ break;
2537
+ }
2538
+ i++;
2539
+ }
2540
+ const expr = rest.slice(am.index + am[0].length, i).trim();
2541
+ chunks.push(`${oldSave}${writeSetData(name, expr, patch, Boolean(w))}${tail}${sync}${tToggle}`);
2542
+ rest = rest.slice(i);
2543
+ }
2544
+ out = chunks.join("");
2545
+ }
2546
+ if (!skip("script/ref-read")) {
2547
+ if (new RegExp(`\\b${name}\\.value\\b`).test(out)) {
2548
+ trace?.add("script/ref-read", { line, before: `${name}.value`, after: `this.data.${name}` });
2549
+ }
2550
+ out = out.replace(new RegExp(`\\b${name}\\.value\\b`, "g"), prop);
2551
+ }
2552
+ }
2553
+ return out;
2554
+ }
2555
+ function extractLifecycles(source, trace, disabled, warnings = []) {
2556
+ const out = {};
2557
+ if (disabled?.has("script/lifecycle-map")) return out;
2558
+ const hooks = [
2559
+ { re: /onMounted\s*\(/g, key: "onReady" },
2560
+ { re: /onUnmounted\s*\(/g, key: "onUnload" },
2561
+ { re: /onLoad\s*\(/g, key: "onLoad" }
2562
+ ];
2563
+ const mapped = /* @__PURE__ */ new Set(["onMounted", "onUnmounted", "onLoad"]);
2564
+ for (const hm of source.matchAll(/\bon([A-Z][A-Za-z0-9_]*)\s*\(\s*(?:(?:\([^)]*\)\s*=>)|function)/g)) {
2565
+ const full = `on${hm[1]}`;
2566
+ if (!mapped.has(full)) {
2567
+ warnings.push(`\u672A\u6620\u5C04\u7684\u751F\u547D\u5468\u671F\u94A9\u5B50 ${full}() \u5DF2\u5265\u79BB\uFF08\u5C0F\u7A0B\u5E8F\u65E0\u5BF9\u7B49\u94A9\u5B50\uFF1BWeb \u7AEF\u4FDD\u7559\u539F\u751F\u8BED\u4E49\uFF09\u2014\u2014\u5982\u7EC4\u4EF6\u5185\u9700\u8981\u964D\u7EA7\u8BF4\u660E\u8BF7\u6CE8\u91CA\u6807\u6CE8`);
2568
+ }
2569
+ }
2570
+ for (const h of hooks) {
2571
+ h.re.lastIndex = 0;
2572
+ const m = h.re.exec(source);
2573
+ if (!m) continue;
2574
+ const braceIdx = source.indexOf("{", m.index);
2575
+ const body = extractBracedBody(source, braceIdx);
2576
+ trace?.add("script/lifecycle-map", { line: lineAt(source, m.index), before: m[0].replace(/\s*\(/, "()"), after: h.key });
2577
+ if (body !== null) out[h.key] = body;
2578
+ }
2579
+ return out;
2580
+ }
2581
+ function indentBody(body) {
2582
+ return body.split("\n").map((l) => ` ${l}`).join("\n");
2583
+ }
2584
+ function capitalize2(s) {
2585
+ return s.charAt(0).toUpperCase() + s.slice(1);
2586
+ }
2587
+ var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2588
+ function vlqEncode(value) {
2589
+ let vlq = value < 0 ? -value << 1 | 1 : value << 1;
2590
+ let out = "";
2591
+ do {
2592
+ let digit = vlq & 31;
2593
+ vlq >>>= 5;
2594
+ if (vlq > 0) digit |= 32;
2595
+ out += B64[digit];
2596
+ } while (vlq > 0);
2597
+ return out;
2598
+ }
2599
+ function buildSourceMap(js, file, source, lineMappings) {
2600
+ const byOut = new Map(lineMappings.map((m) => [m.out, m.src]));
2601
+ const lineCount = js.split("\n").length;
2602
+ let prevSrc = 0;
2603
+ const segs = [];
2604
+ for (let i = 0; i < lineCount; i++) {
2605
+ const srcLine = byOut.get(i);
2606
+ if (srcLine == null) {
2607
+ segs.push("");
2608
+ continue;
2609
+ }
2610
+ const src0 = srcLine - 1;
2611
+ segs.push(`AA${vlqEncode(src0 - prevSrc)}A`);
2612
+ prevSrc = src0;
2613
+ }
2614
+ return JSON.stringify({
2615
+ version: 3,
2616
+ file: file ? `${file}.js` : "page.js",
2617
+ sources: [file ?? "anonymous.vue"],
2618
+ sourcesContent: [source],
2619
+ names: [],
2620
+ mappings: segs.join(";")
2621
+ });
2622
+ }
2623
+ function transformScriptToPage(source, _opts = { px2rpx: true, rpxRatio: 2 }, extra = {}) {
2624
+ const warnings = [];
2625
+ const trace = extra.trace;
2626
+ const moduleImports = /* @__PURE__ */ new Map();
2627
+ for (const mi of extra.moduleImports ?? []) moduleImports.set(mi.source, mi.requirePath);
2628
+ const requireLines = [];
2629
+ const importWarnings = [];
2630
+ for (const imp of extractImports(source)) {
2631
+ if (imp.source === "vue") continue;
2632
+ if (imp.typeOnly) continue;
2633
+ if (imp.source.endsWith(".vue")) continue;
2634
+ const reqPath = moduleImports.get(imp.source);
2635
+ if (reqPath) {
2636
+ if (imp.kind === "named") {
2637
+ requireLines.push(`const { ${imp.names.join(", ")} } = require('${reqPath}')`);
2638
+ } else if (imp.kind === "namespace") {
2639
+ requireLines.push(`const ${imp.names[0]} = require('${reqPath}')`);
2640
+ } else if (imp.kind === "default") {
2641
+ requireLines.push(`const ${imp.names[0]} = require('${reqPath}').default !== undefined ? require('${reqPath}').default : require('${reqPath}')`);
2642
+ } else {
2643
+ requireLines.push(`require('${reqPath}')`);
2644
+ }
2645
+ trace?.add("script/module-import", {
2646
+ line: imp.line,
2647
+ before: `import { ${imp.names.join(", ")} } from '${imp.source}'`,
2648
+ after: `const { ${imp.names.join(", ")} } = require('${reqPath}')\uFF08\u8DE8\u6A21\u5757\u5F15\u7528\uFF0C\u5171\u4EAB\u6A21\u5757\u72EC\u7ACB\u4EA7\u7269\uFF09`
2649
+ });
2650
+ } else if (!extra.isComponent) {
2651
+ importWarnings.push(imp.source);
2652
+ trace?.add("script/module-import", {
2653
+ line: imp.line,
2654
+ before: `import ... from '${imp.source}'`,
2655
+ after: "\uFF08\u5265\u79BB\uFF1A\u65E0\u6CD5\u89E3\u6790\u7684\u8DE8\u6A21\u5757\u5F15\u7528\uFF0C\u7B26\u53F7\u5C06 undefined\uFF09"
2656
+ });
2657
+ }
2658
+ }
2659
+ if (importWarnings.length) {
2660
+ warnings.push(
2661
+ `\u68C0\u6D4B\u5230 ${importWarnings.length} \u6761\u65E0\u6CD5\u89E3\u6790\u7684 import\uFF08${importWarnings.slice(0, 3).join(", ")}${importWarnings.length > 3 ? "\u2026" : ""}\uFF09\u2014\u2014\u5C0F\u7A0B\u5E8F\u4EA7\u7269\u65E0\u6A21\u5757\u7CFB\u7EDF\uFF0C\u8DE8\u6A21\u5757\u5F15\u7528\u5C06 undefined\uFF1A\u8BF7\u6539\u7528\u672C\u5730\u6A21\u5757\u8DEF\u5F84\uFF08module-plan B0\uFF1A\u76F8\u5BF9\u8DEF\u5F84\u5171\u4EAB\u6A21\u5757\u81EA\u52A8\u7F16\u8BD1 + require\uFF09\u6216\u6846\u67B6 store \u6865`
2662
+ );
2663
+ }
2664
+ const disabled = resolveOverrides(extra.rules).disabled;
2665
+ const { data, computed, runtimeInits } = disabled.has("script/const-to-data") ? { data: {}, computed: {}, runtimeInits: [] } : extractData(source, warnings, trace);
2666
+ const computeds = disabled.has("script/computed-to-data") ? {} : computed;
2667
+ const watches = disabled.has("script/watch-to-methods") ? {} : extractWatch(source, data, warnings, trace, !disabled.has("script/watch-props"));
2668
+ const props = extra.isComponent && !disabled.has("script/define-props") ? extractProps(source, warnings, trace) : {};
2669
+ const propsVar = source.match(/const\s+([A-Za-z_$][\w$]*)\s*=\s*defineProps\s*[<(]/)?.[1];
2670
+ const emitEnabled = extra.isComponent && /defineEmits\s*\(/.test(source) && !disabled.has("script/define-emits");
2671
+ const methods = extractMethods(source, warnings, trace, disabled);
2672
+ if (extra.isComponent) checkDefineExpose(source, data, warnings, trace);
2673
+ const lifecycles = extractLifecycles(source, trace, disabled, warnings);
2674
+ const vModelBindings = extra.vModelBindings ?? [];
2675
+ const refNames = new Set(Object.keys(data));
2676
+ const piEnabled = !disabled.has("script/provide-inject");
2677
+ const { provides, injects } = piEnabled ? extractProvideInject(source) : { provides: [], injects: [] };
2678
+ const piBlocks = buildProvideInject(provides, injects, data, computeds);
2679
+ if (piEnabled && (provides.length || injects.length)) {
2680
+ trace?.add("script/provide-inject", {
2681
+ before: `${provides.map((p) => `provide(${JSON.stringify(p.key)}, \u2026)`).join("\u3001")}${provides.length && injects.length ? "\uFF1B" : ""}${injects.map((i) => `const ${i.name} = inject(${JSON.stringify(i.key)})`).join("\u3001")}`,
2682
+ after: "\u9875\u9762 onLoad / \u7EC4\u4EF6 created+attached \u6CE8\u5165 getApp().__proteusProvides \u5168\u5C40\u6CE8\u518C\u8868\u8BFB\u5199\uFF08MVP \u503C\u5FEB\u7167\uFF0C\u975E\u54CD\u5E94\u5F0F\uFF09"
2683
+ });
2684
+ }
2685
+ const providedRefs = piEnabled ? piBlocks.provideRefs : /* @__PURE__ */ new Map();
2686
+ const hasInjects = piEnabled && injects.length > 0;
2687
+ const transitions = extra.transitions ?? [];
2688
+ const transitionToggle = /* @__PURE__ */ new Map();
2689
+ for (const t of transitions) transitionToggle.set(t.ref, `proteusTransitionToggle${t.index}`);
2690
+ const dataExtra = {};
2691
+ for (const t of transitions) {
2692
+ dataExtra[`__tv${t.index}`] = data[t.ref];
2693
+ dataExtra[`__tl${t.index}`] = false;
2694
+ }
2695
+ const storeBindings = extra.storeBindings ?? [];
2696
+ const storeVar = runtimeInits.find((i) => i.name === "store" && /^use\w*Store\(/.test(i.call))?.name;
2697
+ const storeBindingInit = storeVar && storeBindings.length ? storeBindingLine(storeBindings, storeVar) : "";
2698
+ if (storeBindingInit) {
2699
+ trace?.add("script/store-binding", {
2700
+ before: `\u6A21\u677F {{ store.${storeBindings.join(" }} / {{ store.")} }}`,
2701
+ after: `onLoad\uFF1Athis.setData(\u6620\u5C04) + store.$subscribe \u2192 setData\uFF08Pinia MP \u7ED1\u5B9A\uFF0Cpinia-plan 12 P1\uFF09`
2702
+ });
2703
+ }
2704
+ const lines = [];
2705
+ if (extra.file) lines.push(`// ${extra.file}\uFF08Proteus mp-transform \u7F16\u8BD1\u4EA7\u7269\uFF09`);
2706
+ lines.push("// AUTO-GENERATED by vite-plugin-mp-transform.ts. DO NOT EDIT.", "");
2707
+ if (requireLines.length) lines.push(...requireLines, "");
2708
+ lines.push(extra.isComponent ? "Component({" : "Page({");
2709
+ const methodLines = [];
2710
+ const methodMappings = [];
2711
+ const pushMethod = (line, srcLine) => {
2712
+ const start = methodLines.length;
2713
+ methodLines.push(line);
2714
+ if (srcLine !== void 0) {
2715
+ const sub = line.split("\n");
2716
+ for (let i = 0; i < sub.length; i++) methodMappings.push({ out: start + i, src: srcLine + i });
2717
+ }
2718
+ };
2719
+ const methodNames = new Set(Object.keys(methods));
2720
+ const bridgeHooks = {
2721
+ hasOnPageScroll: /onPageScroll\s*\(/.test(source),
2722
+ hasOnReachBottom: /onReachBottom\s*\(/.test(source),
2723
+ hasOnPullDownRefresh: /onPullDownRefresh\s*\(/.test(source),
2724
+ hasPageScrollTo: /wx\.pageScrollTo\s*\(/.test(source)
2725
+ };
2726
+ if (!extra.isComponent && !disabled.has("page/scroll-bridge")) {
2727
+ if (bridgeHooks.hasOnPullDownRefresh) dataExtra.__proteusRefreshing = false;
2728
+ if (bridgeHooks.hasPageScrollTo) dataExtra.__proteusPageScrollTop = 0;
2729
+ }
2730
+ const dataEntries = [...Object.entries(data), ...Object.entries(dataExtra)];
2731
+ if (dataEntries.length) {
2732
+ lines.push(" data: {");
2733
+ for (const [k, v] of dataEntries) lines.push(` ${k}: ${JSON.stringify(v)},`);
2734
+ lines.push(" },");
2735
+ }
2736
+ const propEntries = Object.entries(props);
2737
+ if (extra.isComponent) {
2738
+ const allProps = { ...props };
2739
+ if (!disabled.has("component/root-class")) {
2740
+ allProps.rootClass = { type: "String", value: "" };
2741
+ }
2742
+ const entries = Object.entries(allProps);
2743
+ if (entries.length) {
2744
+ lines.push(" properties: {");
2745
+ for (const [k, p] of entries) {
2746
+ lines.push(` ${k}: { type: ${p.type}${p.value !== void 0 ? `, value: ${JSON.stringify(p.value)}` : ""} },`);
2747
+ }
2748
+ lines.push(" },");
2749
+ }
2750
+ } else if (propEntries.length) {
2751
+ lines.push(" properties: {");
2752
+ for (const [k, p] of propEntries) {
2753
+ lines.push(` ${k}: { type: ${p.type}${p.value !== void 0 ? `, value: ${JSON.stringify(p.value)}` : ""} },`);
2754
+ }
2755
+ lines.push(" },");
2756
+ }
2757
+ const vmodelDisabled = disabled.has("script/vmodel-handler");
2758
+ for (const name of vModelBindings) {
2759
+ if (!vmodelDisabled) {
2760
+ methodNames.add(`proteusOn${capitalize2(name)}Input`);
2761
+ pushMethod(` proteusOn${capitalize2(name)}Input(e) { this.setData({ ${name}: e.detail.value }) },`);
2762
+ }
2763
+ }
2764
+ if (vModelBindings.length && !vmodelDisabled) {
2765
+ trace?.add("script/vmodel-handler", {
2766
+ before: `v-model="${vModelBindings.join('", "')}"`,
2767
+ after: `proteusOn${vModelBindings.map(capitalize2).join(" / proteusOn")}Input\uFF08setData \u56DE\u5199\uFF09`
2768
+ });
2769
+ }
2770
+ const propWatches = Object.values(watches).filter((w) => w.propField);
2771
+ if (extra.isComponent && propWatches.length) {
2772
+ lines.push(" observers: {");
2773
+ for (const w of propWatches) {
2774
+ const observerBody = rewriteBareMethodCalls(rewriteRefAccess(w.body, refNames, trace, disabled, computeds, watches, emitEnabled, propsVar, providedRefs, transitionToggle), methodNames);
2775
+ const bodyLines = observerBody.split("\n");
2776
+ lines.push(` ${w.propField}(n, o) {`);
2777
+ for (const bl of bodyLines) lines.push(` ${bl}`);
2778
+ lines.push(" },");
2779
+ }
2780
+ lines.push(" },");
2781
+ }
2782
+ if (extra.usesNavigate && !disabled.has("script/nav-handler")) {
2783
+ trace?.add("script/nav-handler", { before: "<a href> / <router-link>", after: "proteusNavigateTo(e)\uFF08data-url \u2192 wx.navigateTo\uFF09" });
2784
+ methodNames.add("proteusNavigateTo");
2785
+ methodLines.push(
2786
+ " proteusNavigateTo(e) {",
2787
+ " const ds = e.currentTarget.dataset",
2788
+ ' const url = String(ds.url || "")',
2789
+ ...extra.debug ? [` console.log('[proteus][nav] tap', JSON.stringify(ds), Date.now())`] : [],
2790
+ " if (!url) return",
2791
+ ...extra.debug ? [` console.log('[proteus][nav] navigateTo', url, Date.now())`] : [],
2792
+ " const nav = {",
2793
+ " url: url,",
2794
+ ...extra.debug ? [` success: function () { console.log('[proteus][nav] navigateTo success', url, Date.now()) },`] : [],
2795
+ " fail: function (err) {",
2796
+ ...extra.debug ? [` console.warn('[proteus][nav] navigateTo fail', JSON.stringify(err), Date.now())`] : [],
2797
+ " if (ds.routeType) wx.navigateTo({ url: url })",
2798
+ " }",
2799
+ " }",
2800
+ " if (ds.routeType) nav.routeType = ds.routeType",
2801
+ " wx.navigateTo(nav)",
2802
+ " },"
2803
+ );
2804
+ }
2805
+ if (lifecycles.onReady) {
2806
+ lines.push(` onReady() {
2807
+ ${indentBody(rewriteBareMethodCalls(rewriteRefAccess(lifecycles.onReady, refNames, trace, disabled, computeds, watches, emitEnabled, propsVar, providedRefs, transitionToggle), methodNames))}
2808
+ },`);
2809
+ } else if (extra.debug) {
2810
+ lines.push(` onReady() {
2811
+ console.log('[proteus][page] onReady ${extra.file ?? ""}', Date.now())
2812
+ },`);
2813
+ }
2814
+ const storeDisposeLine = storeVar && !extra.isComponent ? `if (this.${storeVar} && this.${storeVar}.$dispose) { this.${storeVar}.$dispose(); this.${storeVar} = null }` : "";
2815
+ const needsPageCleanup = hasInjects || providedRefs.size > 0 || Boolean(storeDisposeLine);
2816
+ const pageCleanupLine = "const __reg = getApp().__proteusProvides; if (__reg && this.__proteusPageId) delete __reg[this.__proteusPageId]";
2817
+ const unsubLine = hasInjects ? "this.proteusUnsubscribeProvide()" : "";
2818
+ if (lifecycles.onUnload) {
2819
+ const unloadBody = rewriteBareMethodCalls(rewriteRefAccess(lifecycles.onUnload, refNames, trace, disabled, computeds, watches, emitEnabled, propsVar, providedRefs, transitionToggle), methodNames);
2820
+ const isComp = extra.isComponent;
2821
+ const pre = isComp ? [unsubLine].filter(Boolean).join("\n") : [unsubLine, storeDisposeLine, pageCleanupLine].filter(Boolean).join("\n");
2822
+ const hook = isComp ? "detached" : "onUnload";
2823
+ lines.push(` ${hook}() {
2824
+ ${indentBody(pre ? `${pre}
2825
+ ${unloadBody}` : unloadBody)}
2826
+ },`);
2827
+ } else if (needsPageCleanup && !extra.isComponent) {
2828
+ lines.push(` onUnload() {
2829
+ ${indentBody([unsubLine, storeDisposeLine, pageCleanupLine].filter(Boolean).join("\n"))}
2830
+ },`);
2831
+ }
2832
+ if (extra.isComponent) {
2833
+ if (piBlocks.provide) {
2834
+ lines.push(` created() {
2835
+ ${indentBody(piBlocks.provide)}
2836
+ },`);
2837
+ }
2838
+ const initLines = [computedInitLine(computeds), runtimeInitLine(runtimeInits), storeBindingInit, immediateWatchLine(watches), piBlocks.inject].filter(Boolean);
2839
+ if (initLines.length) {
2840
+ lines.push(` attached() {
2841
+ ${indentBody(initLines.join("\n"))}
2842
+ },`);
2843
+ }
2844
+ if (hasInjects && !lifecycles.onUnload) {
2845
+ lines.push(` detached() {
2846
+ this.proteusUnsubscribeProvide()
2847
+ },`);
2848
+ }
2849
+ } else if (lifecycles.onLoad) {
2850
+ const initLines = [computedInitLine(computeds), runtimeInitLine(runtimeInits), storeBindingInit, immediateWatchLine(watches), piBlocks.page].filter(Boolean);
2851
+ const body = rewriteBareMethodCalls(rewriteRefAccess(lifecycles.onLoad, refNames, trace, disabled, computeds, watches, emitEnabled, propsVar, providedRefs, transitionToggle), methodNames);
2852
+ lines.push(` onLoad(options) {
2853
+ ${indentBody(initLines.length ? `${initLines.join("\n")}
2854
+ ${body}` : body)}
2855
+ },`);
2856
+ } else {
2857
+ if (!disabled.has("script/onload-params")) {
2858
+ trace?.add("script/onload-params", { before: "\uFF08\u65E0\u663E\u5F0F onLoad\uFF09", after: "onLoad(options) \u2192 decodeURIComponent + JSON.parse + setData" });
2859
+ const initLines = [computedInitLine(computeds), runtimeInitLine(runtimeInits), storeBindingInit, immediateWatchLine(watches), piBlocks.page].filter(Boolean);
2860
+ lines.push(
2861
+ [
2862
+ " onLoad(options) {",
2863
+ ...extra.debug ? [` console.log('[proteus][page] onLoad ${extra.file ?? ""}', JSON.stringify(options), Date.now())`] : [],
2864
+ ...initLines.map((l) => l.split("\n").map((sl) => ` ${sl}`).join("\n")),
2865
+ " const params = {}",
2866
+ " const keys = Object.keys(options || {})",
2867
+ " for (let i = 0; i < keys.length; i++) {",
2868
+ " const k = keys[i]",
2869
+ " const v = options[k]",
2870
+ " const s = decodeURIComponent(v)",
2871
+ ' try { params[k] = (s.startsWith("{") || s.startsWith("[")) ? JSON.parse(s) : s } catch { params[k] = s }',
2872
+ " }",
2873
+ " this.setData(params)",
2874
+ " },"
2875
+ ].join("\n")
2876
+ );
2877
+ } else if (piBlocks.page) {
2878
+ lines.push(` onLoad(options) {
2879
+ ${indentBody(piBlocks.page)}
2880
+ },`);
2881
+ }
2882
+ }
2883
+ const lineMappings = [];
2884
+ const pushMapped = (line, srcLine) => {
2885
+ const start = lines.length;
2886
+ lines.push(line);
2887
+ const sub = line.split("\n");
2888
+ for (let i = 0; i < sub.length; i++) lineMappings.push({ out: start + i, src: srcLine + i });
2889
+ };
2890
+ for (const [name, m] of Object.entries(methods)) {
2891
+ if (extra.debug) methodLines.push(` // @${m.line} ${name}()`);
2892
+ const braceIdx = m.src.indexOf("{");
2893
+ const sig = m.src.slice(0, braceIdx + 1);
2894
+ const body = m.src.slice(braceIdx + 1);
2895
+ pushMethod(` ${sig + rewriteBareMethodCalls(rewriteRefAccess(body, refNames, trace, disabled, computeds, watches, emitEnabled, propsVar, providedRefs, transitionToggle), methodNames)},`, m.line);
2896
+ }
2897
+ if (!extra.isComponent && !disabled.has("page/scroll-bridge")) {
2898
+ if (bridgeHooks.hasOnPageScroll) {
2899
+ methodNames.add("proteusPageScroll");
2900
+ methodLines.push(' proteusPageScroll(e) { if (typeof this.onPageScroll === "function") this.onPageScroll({ scrollTop: e.detail.scrollTop, scrollLeft: e.detail.scrollLeft }) },');
2901
+ }
2902
+ if (bridgeHooks.hasOnReachBottom) {
2903
+ methodNames.add("proteusReachBottom");
2904
+ methodLines.push(' proteusReachBottom() { if (typeof this.onReachBottom === "function") this.onReachBottom() },');
2905
+ }
2906
+ if (bridgeHooks.hasOnPullDownRefresh) {
2907
+ methodNames.add("proteusPullDownRefresh");
2908
+ methodLines.push(
2909
+ ' proteusPullDownRefresh() { const __r = typeof this.onPullDownRefresh === "function" ? this.onPullDownRefresh() : undefined; this.setData({ __proteusRefreshing: false }); return __r },'
2910
+ );
2911
+ }
2912
+ if (bridgeHooks.hasPageScrollTo) {
2913
+ methodNames.add("proteusPageScrollTo");
2914
+ methodLines.push(" proteusPageScrollTo(opts) { this.setData({ __proteusPageScrollTop: opts.scrollTop }) },");
2915
+ }
2916
+ }
2917
+ for (const w of Object.values(watches)) {
2918
+ if (w.propField && !w.immediate) continue;
2919
+ methodNames.add(`proteusWatch${w.id}`);
2920
+ const src = `proteusWatch${w.id}(${w.params.join(", ")}) {
2921
+ ${indentBody(rewriteBareMethodCalls(rewriteRefAccess(w.body, refNames, trace, disabled, computeds, watches, emitEnabled, propsVar, providedRefs, transitionToggle), methodNames))}
2922
+ },`;
2923
+ pushMethod(` ${src}`, w.line);
2924
+ }
2925
+ for (const [cname, c] of Object.entries(computeds)) {
2926
+ if (!c.setter) continue;
2927
+ methodNames.add(`proteusSet${capitalize2(cname)}`);
2928
+ const src = `proteusSet${capitalize2(cname)}(${c.setter.param}) {
2929
+ ${indentBody(rewriteBareMethodCalls(rewriteRefAccess(c.setter.body, refNames, trace, disabled, computeds, watches, emitEnabled, propsVar, providedRefs, transitionToggle), methodNames))}
2930
+ },`;
2931
+ pushMethod(` ${src}`, 1);
2932
+ }
2933
+ for (const h of extra.selfHandlers ?? []) {
2934
+ methodNames.add(`proteusSelf${capitalize2(h)}`);
2935
+ methodLines.push(` proteusSelf${capitalize2(h)}(e) {`);
2936
+ methodLines.push(` if (e.target === e.currentTarget) {`);
2937
+ methodLines.push(` this.${h}(e)`);
2938
+ methodLines.push(` }`);
2939
+ methodLines.push(` },`);
2940
+ }
2941
+ for (const h of extra.onceHandlers ?? []) {
2942
+ methodNames.add(`proteusOnce${capitalize2(h)}`);
2943
+ methodLines.push(` proteusOnce${capitalize2(h)}(e) {`);
2944
+ methodLines.push(` if (!this.data.__once${capitalize2(h)}) {`);
2945
+ methodLines.push(` this.data.__once${capitalize2(h)} = true`);
2946
+ methodLines.push(` this.${h}(e)`);
2947
+ methodLines.push(` }`);
2948
+ methodLines.push(` },`);
2949
+ }
2950
+ for (const ih of extra.inlineHandlers ?? []) {
2951
+ if (disabled.has("event/inline-expression")) continue;
2952
+ methodNames.add(ih.name);
2953
+ pushMethod(` ${ih.name}(e) {`, 1);
2954
+ pushMethod(` ${ih.code}`, 1);
2955
+ pushMethod(` },`, 1);
2956
+ }
2957
+ if (providedRefs.size > 0) {
2958
+ methodNames.add("proteusSyncProvide");
2959
+ methodLines.push(
2960
+ " proteusSyncProvide(key, ref) {",
2961
+ " const reg = getApp().__proteusProvides",
2962
+ " const p = reg && this.__proteusPageId ? reg[this.__proteusPageId] : (reg || {})",
2963
+ " if (!p) return",
2964
+ " p[key] = this.data[ref]",
2965
+ " const subs = p.__subs && p.__subs[key]",
2966
+ " if (!subs) return",
2967
+ " for (let i = 0; i < subs.length; i++) subs[i]()",
2968
+ " },"
2969
+ );
2970
+ }
2971
+ if (hasInjects) {
2972
+ methodNames.add("proteusUnsubscribeProvide");
2973
+ methodLines.push(
2974
+ " proteusUnsubscribeProvide() {",
2975
+ " const reg = getApp().__proteusProvides",
2976
+ " const p = reg && this.__proteusPageId ? reg[this.__proteusPageId] : (reg || {})",
2977
+ " if (!p || !p.__subs || !this.__proteusSubs) return",
2978
+ " for (let i = 0; i < this.__proteusSubs.length; i++) {",
2979
+ " const s = this.__proteusSubs[i]",
2980
+ " const list = p.__subs[s.k]",
2981
+ " if (list) {",
2982
+ " const idx = list.indexOf(s.fn)",
2983
+ " if (idx >= 0) list.splice(idx, 1)",
2984
+ " }",
2985
+ " }",
2986
+ " this.__proteusSubs = null",
2987
+ " },"
2988
+ );
2989
+ }
2990
+ for (const t of transitions) {
2991
+ const dur = { fade: 250, "slide-up": 320, scale: 400 }[t.tName] ?? 250;
2992
+ methodNames.add(`proteusTransitionToggle${t.index}`);
2993
+ methodLines.push(
2994
+ ` proteusTransitionToggle${t.index}() {`,
2995
+ ` if (this.data.${t.ref}) {`,
2996
+ ` clearTimeout(this.__tlTimer${t.index})`,
2997
+ ` this.__tlTimer${t.index} = null`,
2998
+ ` this.setData({ __tv${t.index}: true, __tl${t.index}: false })`,
2999
+ ` return`,
3000
+ ` }`,
3001
+ ` if (this.__tlTimer${t.index}) return`,
3002
+ ` this.setData({ __tl${t.index}: true })`,
3003
+ ` this.__tlTimer${t.index} = setTimeout(() => {`,
3004
+ ` this.__tlTimer${t.index} = null`,
3005
+ ` this.setData({ __tv${t.index}: false })`,
3006
+ ` }, ${dur})`,
3007
+ ` },`
3008
+ );
3009
+ }
3010
+ if (extra.isComponent) {
3011
+ lines.push(" methods: {");
3012
+ for (const ml of methodLines) {
3013
+ for (const sl of ml.split("\n")) lines.push(` ${sl}`);
3014
+ }
3015
+ lines.push(" },");
3016
+ } else {
3017
+ lines.push(...methodLines);
3018
+ }
3019
+ const methodsBase = lines.length;
3020
+ for (const mm of methodMappings) lineMappings.push({ out: methodsBase + mm.out, src: mm.src });
3021
+ lines.push("})");
3022
+ trace?.add("script/component-mode", {
3023
+ before: "SFC",
3024
+ after: extra.isComponent ? "Component({ ... })" : "Page({ ... })"
3025
+ });
3026
+ trace?.add("script/es5-safe", { before: "?? / ?. / \u89E3\u6784 / \u5C55\u5F00", after: "\u663E\u5F0F null \u4E09\u5143 / \u7D22\u5F15\u5FAA\u73AF / \u76F4\u63A5\u8D4B\u503C" });
3027
+ for (const w of warnings) console.warn(`[mp-transform] ${w}`);
3028
+ const js = lines.join("\n") + "\n";
3029
+ const jsFinal = js.replace(/\bwx\.pageScrollTo\s*\(/g, "this.proteusPageScrollTo(");
3030
+ const sourcemap = buildSourceMap(jsFinal, extra.file, source, lineMappings);
3031
+ return { js: jsFinal, warnings, sourcemap };
3032
+ }
3033
+
3034
+ // src/style.ts
3035
+ function makeTagSelectorRe(tagMap, semanticClass) {
3036
+ const names = [...Object.keys(tagMap), ...Object.keys(semanticClass)].sort((a, b) => b.length - a.length);
3037
+ return new RegExp(
3038
+ `(?<![\\w-.#:\\[*])(?:${names.join("|")})(?=[\\s.#:\\[>+,~)(\\u0000]|$)`,
3039
+ "g"
3040
+ );
3041
+ }
3042
+ function rewriteTagSelectors(selector, res, tagRe) {
3043
+ const attrs = [];
3044
+ const masked = selector.replace(/\[[^\]]*\]/g, (m) => {
3045
+ attrs.push(m);
3046
+ return `\0${attrs.length - 1}\0`;
3047
+ });
3048
+ const rewritten = masked.replace(tagRe, (tag) => {
3049
+ const semantic = res.semanticClass[tag];
3050
+ if (semantic) return `.${semantic}`;
3051
+ return res.tagMap[tag] ?? tag;
3052
+ });
3053
+ return rewritten.replace(/\u0000(\d+)\u0000/g, (_m, i) => attrs[Number(i)]);
3054
+ }
3055
+ function splitTopLevelSelectors(s) {
3056
+ const out = [];
3057
+ let depth = 0;
3058
+ let cur = "";
3059
+ for (const ch of s) {
3060
+ if (ch === "(" || ch === "[") depth++;
3061
+ else if (ch === ")" || ch === "]") depth = Math.max(0, depth - 1);
3062
+ if (ch === "," && depth === 0) {
3063
+ out.push(cur);
3064
+ cur = "";
3065
+ continue;
3066
+ }
3067
+ cur += ch;
3068
+ }
3069
+ out.push(cur);
3070
+ return out;
3071
+ }
3072
+ function rewriteSelectorTags(css, res, tagRe) {
3073
+ return css.replace(/([^{}]+)\{/g, (_m, sel) => `${rewriteTagSelectors(sel, res, tagRe)}{`);
3074
+ }
3075
+ var BASE_SEMANTIC_WXSS = [
3076
+ // ★16-progress-skyline-degrade:<progress> 降级自定义进度条(Skyline 不支持原生 progress)
3077
+ ".proteus-progress { display: flex; align-items: center; gap: 8px; }",
3078
+ ".proteus-progress-track { flex: 1; background: #ebedf0; border-radius: 2px; overflow: hidden; }",
3079
+ ".proteus-progress-inner { height: 100%; border-radius: 2px; background: #07c160; }",
3080
+ ".proteus-progress-info { font-size: 13px; color: #666; min-width: 34px; text-align: right; }",
3081
+ // ★2026-08:行内场景自动 flex row(layout/auto-flex-row 模板注入 proteus-flex-row 类)——
3082
+ // Skyline 不支持 inline 布局,text + 行内控件同行必须 flex row(双端一致)
3083
+ ".proteus-flex-row { display: flex; flex-direction: row; align-items: center; }",
3084
+ ".proteus-h1 { display: block; font-size: 64rpx; font-weight: 700; margin: 0 0 0.67em; }",
3085
+ ".proteus-h2 { display: block; font-size: 48rpx; font-weight: 700; margin: 0 0 0.83em; }",
3086
+ ".proteus-h3 { display: block; font-size: 36rpx; font-weight: 700; margin: 0 0 1em; }",
3087
+ ".proteus-h4 { display: block; font-size: 32rpx; font-weight: 700; margin: 0 0 1.33em; }",
3088
+ ".proteus-h5 { display: block; font-size: 28rpx; font-weight: 700; margin: 0 0 1.67em; }",
3089
+ ".proteus-h6 { display: block; font-size: 24rpx; font-weight: 700; margin: 0 0 2.33em; }",
3090
+ ".proteus-p { display: block; margin: 0 0 1em; }",
3091
+ ".proteus-a { color: #1a7af8; text-decoration: underline; }"
3092
+ ].join("\n");
3093
+ var TRANSITION_WXSS = [
3094
+ // ★Batch 5:离开动画(__tl{i} 插值 class 切换;forwards 保持末帧直到延迟移除)
3095
+ ".proteus-transition-fade-leave { animation: proteus-fade-out 0.25s ease forwards; }",
3096
+ ".proteus-transition-slide-up-leave { animation: proteus-slide-up-out 0.32s cubic-bezier(0.35, 0.91, 0.33, 0.97) forwards; }",
3097
+ ".proteus-transition-scale-leave { animation: proteus-scale-out 0.4s cubic-bezier(0.35, 0.91, 0.33, 0.97) forwards; }",
3098
+ ".proteus-transition-fade { animation: proteus-fade-in 0.25s ease; }",
3099
+ ".proteus-transition-slide-up { animation: proteus-slide-up-in 0.32s cubic-bezier(0.35, 0.91, 0.33, 0.97); }",
3100
+ ".proteus-transition-scale { animation: proteus-scale-in 0.4s cubic-bezier(0.35, 0.91, 0.33, 0.97); }",
3101
+ "@keyframes proteus-fade-in { from { opacity: 0; } to { opacity: 1; } }",
3102
+ "@keyframes proteus-fade-out { from { opacity: 1; } to { opacity: 0; } }",
3103
+ "@keyframes proteus-slide-up-in { from { transform: translateY(20%); opacity: 0; } to { transform: translateY(0); opacity: 1; } }",
3104
+ "@keyframes proteus-slide-up-out { from { transform: translateY(0); opacity: 1; } to { transform: translateY(20%); opacity: 0; } }",
3105
+ "@keyframes proteus-scale-in { from { transform: scale(0.92) translateY(4%); opacity: 0.8; } to { transform: scale(1) translateY(0); opacity: 1; } }",
3106
+ "@keyframes proteus-scale-out { from { transform: scale(1) translateY(0); opacity: 1; } to { transform: scale(0.92) translateY(4%); opacity: 0.8; } }"
3107
+ ].join("\n");
3108
+ function countSelectorRewrites(css, res) {
3109
+ const semanticKeys = Object.keys(res.semanticClass);
3110
+ const tagKeys = Object.keys(res.tagMap).filter((k) => !semanticKeys.includes(k));
3111
+ const lookahead = "[\\s.#:\\[>+,~)(\\u0000]|$";
3112
+ const semanticRe = new RegExp(`(?<![\\w-.\\#:\\[*])(?:${semanticKeys.join("|")})(?=${lookahead})`, "g");
3113
+ const tagRePlain = new RegExp(`(?<![\\w-.\\#:\\[*])(?:${tagKeys.join("|")})(?=${lookahead})`, "g");
3114
+ return { tag: (css.match(tagRePlain) ?? []).length, semantic: (css.match(semanticRe) ?? []).length };
3115
+ }
3116
+ function transformStyleToWxss(source, opts = { px2rpx: true, rpxRatio: 2 }) {
3117
+ const trace = opts.trace;
3118
+ const res = resolveOverrides(opts.rules);
3119
+ const tagRe = makeTagSelectorRe(res.tagMap, res.semanticClass);
3120
+ const scopeId = opts.scopeId;
3121
+ const doScope = Boolean(scopeId) && !res.disabled.has("style/scoped-css");
3122
+ if (doScope && scopeId) trace?.add("style/scoped-css", { before: "<style scoped>", after: `\u9009\u62E9\u5668\u672B\u5C3E\u8FFD\u52A0 [${scopeId}]\uFF08:deep() \u53BB\u5305\u88C5\uFF09` });
3123
+ const injectBase = !res.disabled.has("style/semantic-base-wxss");
3124
+ let css = injectBase ? `${BASE_SEMANTIC_WXSS}
3125
+ ${source}` : source;
3126
+ if (injectBase) trace?.add("style/semantic-base-wxss", { before: "h1-h6/p/a \u65E0 UA \u6837\u5F0F", after: ".proteus-h1~h6/.proteus-p/.proteus-a \u57FA\u7840 WXSS\uFF08\u6CE8\u5165\u5728\u7528\u6237\u6837\u5F0F\u4E4B\u524D\uFF09" });
3127
+ const injectTransition = opts.usesTransition === true && !res.disabled.has("transition/animation-wxss");
3128
+ if (injectTransition) css = `${css}
3129
+
3130
+ ${TRANSITION_WXSS}`;
3131
+ const doSelectorRewrite = !res.disabled.has("style/selector-tag") && !res.disabled.has("style/selector-semantic");
3132
+ const counts = doSelectorRewrite ? countSelectorRewrites(css, res) : { tag: 0, semantic: 0 };
3133
+ if (doSelectorRewrite) css = rewriteSelectorTags(css, res, tagRe);
3134
+ if (counts.tag > 0) trace?.add("style/selector-tag", { before: `\u9009\u62E9\u5668\u542B HTML \u6807\u7B7E\uFF08${counts.tag} \u5904\uFF09`, after: "\u6620\u5C04\u4E3A\u5C0F\u7A0B\u5E8F\u6807\u7B7E\uFF08div \u2192 view\uFF09" });
3135
+ if (counts.semantic > 0) trace?.add("style/selector-semantic", { before: `h1-h6/p/a \u9009\u62E9\u5668\uFF08${counts.semantic} \u5904\uFF09`, after: ".proteus-* \u7C7B\u9009\u62E9\u5668\uFF08\u907F\u514D\u540C\u7279\u5F02\u6027\u8986\u76D6\uFF09" });
3136
+ const doPx2rpx = opts.px2rpx && !res.disabled.has("style/px-to-rpx");
3137
+ const pxCount = (css.match(/(\d+(?:\.\d+)?)px\b/g) ?? []).length;
3138
+ if (doPx2rpx) {
3139
+ const pxCtx = {
3140
+ input: css,
3141
+ options: { rpxRatio: opts.rpxRatio }
3142
+ };
3143
+ executeRule("style/px-to-rpx", pxCtx);
3144
+ css = pxCtx.output ?? css;
3145
+ if (pxCount > 0) trace?.add("style/px-to-rpx", { before: `${pxCount} \u5904 px`, after: `${pxCount} \u5904 rpx\uFF08rpxRatio=${opts.rpxRatio}\uFF09` });
3146
+ }
3147
+ const unsupported = [];
3148
+ if (!res.disabled.has("style/skyline-unsupported")) {
3149
+ if (/float\s*:/.test(css)) unsupported.push("float");
3150
+ if (/position\s*:\s*fixed\b/.test(css)) unsupported.push("position: fixed");
3151
+ }
3152
+ for (const u of unsupported) {
3153
+ console.warn(`[mp-transform] WXSS \u68C0\u6D4B\u5230 Skyline \u4E0D\u652F\u6301\u7684\u5C5E\u6027\uFF1A${u}\uFF08\u7F16\u8BD1\u671F\u8B66\u544A\uFF09`);
3154
+ trace?.add("style/skyline-unsupported", { before: u, after: "\u7F16\u8BD1\u671F\u8B66\u544A\uFF08\u4E0D\u963B\u65AD\u6784\u5EFA\uFF09" });
3155
+ }
3156
+ if (doScope && scopeId) {
3157
+ css = css.replace(/:deep\(([^)]*)\)/g, "$1");
3158
+ css = css.replace(/([^{}]+)\{/g, (m, sel) => {
3159
+ const s = sel.trim();
3160
+ if (s.startsWith("@")) return m;
3161
+ const parts = splitTopLevelSelectors(s);
3162
+ const scoped = parts.map((p) => {
3163
+ const t = p.trim();
3164
+ if (/^(from|to|\d+(?:\.\d+)?%)$/i.test(t)) return t;
3165
+ return suffixClassTokens(t, scopeId);
3166
+ }).join(", ");
3167
+ return `${scoped} {`;
3168
+ });
3169
+ }
3170
+ return css;
3171
+ }
3172
+ function suffixClassTokens(sel, scopeId) {
3173
+ const attrs = [];
3174
+ const masked = sel.replace(/\[[^\]]*\]/g, (m) => {
3175
+ attrs.push(m);
3176
+ return `\0${attrs.length - 1}\0`;
3177
+ });
3178
+ const out = masked.replace(
3179
+ /\.(-?[_a-zA-Z][-_a-zA-Z0-9]*)/g,
3180
+ (m, name) => name.endsWith(`-${scopeId}`) ? m : `.${name}-${scopeId}`
3181
+ );
3182
+ return out.replace(/\u0000(\d+)\u0000/g, (_m, i) => attrs[Number(i)]);
3183
+ }
3184
+
3185
+ // src/validate.ts
3186
+ var CompilerError = class extends Error {
3187
+ constructor(filename, message) {
3188
+ super(`[proteus-compiler] ${filename}: ${message}`);
3189
+ this.filename = filename;
3190
+ this.name = "CompilerError";
3191
+ }
3192
+ };
3193
+ function validateJs(js) {
3194
+ try {
3195
+ new Function(js);
3196
+ return { ok: true };
3197
+ } catch (err) {
3198
+ return { ok: false, error: err.message };
3199
+ }
3200
+ }
3201
+ function validateWxml(wxml) {
3202
+ const withoutComments = wxml.replace(/<!--[\s\S]*?-->/g, "");
3203
+ const tagRe = /<\/?([a-zA-Z][\w-]*)(?:"[^"]*"|'[^']*'|[^>"'])*\/?>/g;
3204
+ const stack = [];
3205
+ let m;
3206
+ while (m = tagRe.exec(withoutComments)) {
3207
+ const full = m[0];
3208
+ const name = m[1];
3209
+ if (full.startsWith("</")) {
3210
+ const top = stack.pop();
3211
+ if (top !== name) {
3212
+ return { ok: false, error: `</${name}> \u4E0E <${top ?? "(\u65E0)"}> \u4E0D\u5339\u914D\uFF08\u4F4D\u7F6E ${m.index}\uFF09` };
3213
+ }
3214
+ } else if (!full.endsWith("/>")) {
3215
+ stack.push(name);
3216
+ }
3217
+ }
3218
+ if (stack.length) {
3219
+ return { ok: false, error: `<${stack[stack.length - 1]}> \u672A\u95ED\u5408` };
3220
+ }
3221
+ return { ok: true };
3222
+ }
3223
+ function assertValidResult(result, filename) {
3224
+ const jsCheck = validateJs(result.js);
3225
+ if (!jsCheck.ok) {
3226
+ throw new CompilerError(filename, `js \u4EA7\u7269\u8BED\u6CD5\u9519\u8BEF\uFF1A${jsCheck.error}`);
3227
+ }
3228
+ const wxmlCheck = validateWxml(result.wxml);
3229
+ if (!wxmlCheck.ok) {
3230
+ throw new CompilerError(filename, `wxml \u4EA7\u7269\u7ED3\u6784\u9519\u8BEF\uFF1A${wxmlCheck.error}`);
3231
+ }
3232
+ }
3233
+
3234
+ // src/explain.ts
3235
+ import { parse as sfcParse } from "@vue/compiler-sfc";
3236
+ function explainTransform(source, options = {}) {
3237
+ const { descriptor } = sfcParse(source, { filename: options.filename ?? "anonymous.vue" });
3238
+ const styleOpts = { px2rpx: options.px2rpx ?? true, rpxRatio: options.rpxRatio ?? 2 };
3239
+ const events = [];
3240
+ const tplTrace = createTrace("template");
3241
+ const tpl = descriptor.template?.content ?? "";
3242
+ const tplResult = transformTemplateToWxml(tpl, {
3243
+ ...styleOpts,
3244
+ filename: options.filename,
3245
+ annotateLines: options.annotateLines,
3246
+ rules: options.rules,
3247
+ trace: tplTrace
3248
+ });
3249
+ events.push(...tplTrace.events);
3250
+ const setup = descriptor.scriptSetup?.content ?? descriptor.script?.content ?? "";
3251
+ const scriptTrace = createTrace("script");
3252
+ transformScriptToPage(setup, styleOpts, {
3253
+ file: options.filename,
3254
+ isComponent: options.isComponent,
3255
+ vModelBindings: tplResult.vModelBindings,
3256
+ usesNavigate: tplResult.usesNavigate,
3257
+ rules: options.rules,
3258
+ trace: scriptTrace
3259
+ });
3260
+ events.push(...scriptTrace.events);
3261
+ const styleTrace = createTrace("style");
3262
+ transformStyleToWxss(
3263
+ descriptor.styles.map((s) => s.content).join("\n"),
3264
+ { ...styleOpts, rules: options.rules, trace: styleTrace }
3265
+ );
3266
+ events.push(...styleTrace.events);
3267
+ return { filename: options.filename, events };
3268
+ }
3269
+ function formatTransformTrace(result) {
3270
+ const phases = ["template", "script", "style", "validate"];
3271
+ const blocks = [];
3272
+ for (const phase of phases) {
3273
+ const evts = result.events.filter((e) => e.phase === phase);
3274
+ if (!evts.length) continue;
3275
+ const lines = evts.map((e) => {
3276
+ const line = e.line != null ? `L${e.line} ` : "";
3277
+ const detail = [e.before, e.after].filter(Boolean).join(" \u2192 ");
3278
+ return ` ${line}${e.ruleId}${detail ? `\uFF1A${detail}` : ""}`;
3279
+ });
3280
+ blocks.push(`### ${phase} \u9636\u6BB5\uFF08${evts.length} \u4E2A\u51B3\u7B56\uFF09
3281
+ ${lines.join("\n")}`);
3282
+ }
3283
+ return `# explainTransform ${result.filename ?? ""}
3284
+
3285
+ ${blocks.join("\n\n")}`;
3286
+ }
3287
+
3288
+ // src/index.ts
3289
+ function scopedIdFrom(filename) {
3290
+ let hash = 5381;
3291
+ for (let i = 0; i < filename.length; i++) {
3292
+ hash = (hash << 5) + hash + filename.charCodeAt(i) >>> 0;
3293
+ }
3294
+ return `data-v-${hash.toString(16).slice(0, 6)}`;
3295
+ }
3296
+ function compileVueSfc(source, options = {}) {
3297
+ const { descriptor } = sfcParse2(source, { filename: options.filename ?? "anonymous.vue" });
3298
+ const styleOpts = {
3299
+ px2rpx: options.px2rpx ?? true,
3300
+ rpxRatio: options.rpxRatio ?? 2,
3301
+ rules: options.rules
3302
+ };
3303
+ const globalStyles = descriptor.styles.filter((s) => s.attrs?.global !== void 0);
3304
+ const scopedStyles = descriptor.styles.filter((s) => s.attrs?.global === void 0);
3305
+ const hasScoped = scopedStyles.length > 0;
3306
+ const scopeId = hasScoped ? scopedIdFrom(options.filename ?? "anonymous.vue") : void 0;
3307
+ const tplTrace = createTrace("template");
3308
+ const tpl = descriptor.template?.content ?? "";
3309
+ const setup = descriptor.scriptSetup?.content ?? descriptor.script?.content ?? "";
3310
+ const pageScrollHooks = {
3311
+ hasOnPageScroll: /onPageScroll\s*\(/.test(setup),
3312
+ hasOnReachBottom: /onReachBottom\s*\(/.test(setup),
3313
+ hasOnPullDownRefresh: /onPullDownRefresh\s*\(/.test(setup),
3314
+ hasPageScrollTo: /wx\.pageScrollTo\s*\(/.test(setup)
3315
+ };
3316
+ const tplResult = transformTemplateToWxml(tpl, {
3317
+ ...styleOpts,
3318
+ filename: options.filename,
3319
+ annotateLines: options.annotateLines,
3320
+ scopeId,
3321
+ isComponent: options.isComponent,
3322
+ autoScrollContainer: options.autoScrollContainer,
3323
+ pageScrollHooks,
3324
+ trace: tplTrace
3325
+ });
3326
+ const scriptTrace = createTrace("script");
3327
+ const scriptResult = transformScriptToPage(setup, styleOpts, {
3328
+ file: options.filename,
3329
+ isComponent: options.isComponent,
3330
+ vModelBindings: tplResult.vModelBindings,
3331
+ usesNavigate: tplResult.usesNavigate,
3332
+ selfHandlers: tplResult.selfHandlers,
3333
+ onceHandlers: tplResult.onceHandlers,
3334
+ inlineHandlers: tplResult.inlineHandlers,
3335
+ debug: options.debug,
3336
+ rules: options.rules,
3337
+ transitions: tplResult.transitions,
3338
+ storeBindings: tplResult.storeBindings,
3339
+ moduleImports: options.moduleImports,
3340
+ trace: scriptTrace
3341
+ });
3342
+ const styleTrace = createTrace("style");
3343
+ const preprocess = (s) => s.lang && options.preprocessStyle ? options.preprocessStyle(s.lang, s.content) : s.content;
3344
+ if (!options.rules?.disabled?.includes("style/default-scoped")) {
3345
+ for (const s of scopedStyles) {
3346
+ if (!s.scoped) {
3347
+ const msg = `<style> \u5DF2\u6309 scoped \u5904\u7406\uFF08Proteus \u9ED8\u8BA4\u5C40\u90E8\u4F5C\u7528\u57DF\uFF1BVue \u6807\u51C6 <style> \u4E3A\u5168\u5C40\uFF0CWeb \u7AEF\u4F1A\u6CC4\u6F0F\u5230\u6240\u6709\u9875\u9762\uFF09\u2014\u2014\u5982\u9700\u5168\u5C40\u6837\u5F0F\u8BF7\u6539\u7528 <style global>`;
3348
+ tplResult.warnings.push(msg);
3349
+ console.warn(`[mp-transform] ${msg}`);
3350
+ break;
3351
+ }
3352
+ }
3353
+ }
3354
+ const globalWxss = transformStyleToWxss(globalStyles.map(preprocess).join("\n"), {
3355
+ ...styleOpts,
3356
+ usesTransition: tplResult.usesTransition,
3357
+ trace: styleTrace
3358
+ });
3359
+ const scopedWxss = transformStyleToWxss(scopedStyles.map(preprocess).join("\n"), {
3360
+ ...styleOpts,
3361
+ scopeId,
3362
+ usesTransition: tplResult.usesTransition,
3363
+ trace: styleTrace
3364
+ });
3365
+ const wxss = [globalWxss, scopedWxss].filter(Boolean).join("\n");
3366
+ const pageScrollCss = tplResult.pageScrollWrapped ? "\n.proteus-page-scroll { height: 100vh; }\n" : "";
3367
+ const finalWxss = `${wxss}${pageScrollCss}`;
3368
+ const result = {
3369
+ wxml: tplResult.wxml,
3370
+ js: scriptResult.js,
3371
+ wxss: finalWxss,
3372
+ warnings: [...tplResult.warnings, ...scriptResult.warnings],
3373
+ trace: [...tplTrace.events, ...scriptTrace.events, ...styleTrace.events],
3374
+ sourcemap: scriptResult.sourcemap
3375
+ };
3376
+ assertValidResult(result, options.filename ?? "anonymous.vue");
3377
+ return result;
3378
+ }
3379
+ export {
3380
+ CompilerError,
3381
+ compileVueSfc,
3382
+ createTrace,
3383
+ explainTransform,
3384
+ formatTransformCatalog,
3385
+ formatTransformRule,
3386
+ formatTransformTrace,
3387
+ getTransformRule,
3388
+ lineAt,
3389
+ listTransformRules,
3390
+ scopedIdFrom,
3391
+ transformScriptToPage,
3392
+ transformStyleToWxss,
3393
+ transformTemplateToWxml,
3394
+ validateJs,
3395
+ validateWxml
3396
+ };