@proteus-vue/compiler 0.3.0-beta.11 → 0.3.0-beta.15

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 CHANGED
@@ -14677,7 +14677,87 @@ var TAG_MAP = {
14677
14677
  video: "video",
14678
14678
  canvas: "canvas",
14679
14679
  "scroll-view": "scroll-view",
14680
- slot: "slot"
14680
+ slot: "slot",
14681
+ // ★★2026-09-20(外部实战报告第二十三/二十四节):**补全常见 HTML 标签 → WXML 等价映射**。
14682
+ // 此前这些标签落到「未知标签逃生舱」**原样输出** → 微信 wxml 编译器直接拒绝(或渲染异常):
14683
+ // 外部工程实测因 <details>/<summary>/<pre>/<table>/<select>/<option>/<label>/<br> 等
14684
+ // 导致**模拟器整屏黑屏**(构建期零报错、产物看似齐全)。
14685
+ // 语义映射原则:块级 → view;行内/文本类 → text;表格/列表/折叠等无对等物 → view/text
14686
+ // (样式与交互由 CSS + 显式状态承担,见报告第二十四节的正当适配)。
14687
+ // 块级容器
14688
+ section: "view",
14689
+ article: "view",
14690
+ aside: "view",
14691
+ nav: "view",
14692
+ main: "view",
14693
+ header: "view",
14694
+ footer: "view",
14695
+ figure: "view",
14696
+ figcaption: "text",
14697
+ details: "view",
14698
+ summary: "text",
14699
+ dialog: "view",
14700
+ form: "form",
14701
+ fieldset: "view",
14702
+ legend: "text",
14703
+ // 列表
14704
+ ul: "view",
14705
+ ol: "view",
14706
+ li: "view",
14707
+ dl: "view",
14708
+ dt: "text",
14709
+ dd: "text",
14710
+ // 表格(微信无表格组件 → view 结构 + CSS)
14711
+ table: "view",
14712
+ thead: "view",
14713
+ tbody: "view",
14714
+ tfoot: "view",
14715
+ tr: "view",
14716
+ th: "text",
14717
+ td: "text",
14718
+ caption: "text",
14719
+ // 行内 / 语义文本
14720
+ strong: "text",
14721
+ b: "text",
14722
+ em: "text",
14723
+ i: "text",
14724
+ u: "text",
14725
+ s: "text",
14726
+ small: "text",
14727
+ mark: "text",
14728
+ sub: "text",
14729
+ sup: "text",
14730
+ code: "text",
14731
+ pre: "text",
14732
+ kbd: "text",
14733
+ samp: "text",
14734
+ var: "text",
14735
+ abbr: "text",
14736
+ cite: "text",
14737
+ q: "text",
14738
+ blockquote: "view",
14739
+ time: "text",
14740
+ address: "text",
14741
+ // 表单控件(无对等物者映射为 view/text,交互由 picker/state 承担)
14742
+ select: "view",
14743
+ option: "view",
14744
+ optgroup: "view",
14745
+ // ★注:`label` / `audio` **不映射**——它们是微信 wxml 原生组件(表单组件/媒体组件),
14746
+ // 映射会把可用能力降级(p-label 的 `for` 关联、p-media 的 audio 分支都会失效;
14747
+ // 实测被 tests/component-b6 与 tests/p-batch3-contract 抓住)。
14748
+ datalist: "view",
14749
+ output: "text",
14750
+ meter: "view",
14751
+ progress: "progress",
14752
+ // 其它常见
14753
+ hr: "view",
14754
+ br: "view",
14755
+ picture: "image",
14756
+ source: "view",
14757
+ track: "view",
14758
+ iframe: "web-view"
14759
+ // ★注:`template` **不映射**——它在 WXML 里是定义块(is=/data=),语义与 Vue 的片段容器不同,
14760
+ // 属「须改写法」而非「可映射」(见第二十四节:`<template v-if>` 应改为 `<view v-if>`)。
14681
14761
  };
14682
14762
  var EVENT_MAP = {
14683
14763
  click: "tap",
@@ -14716,6 +14796,29 @@ var SEMANTIC_CLASS = {
14716
14796
  a: "proteus-a"
14717
14797
  };
14718
14798
 
14799
+ // src/model-path.ts
14800
+ function modelHandlerSuffix(model) {
14801
+ const raw = String(model ?? "").trim();
14802
+ if (!raw) return "";
14803
+ const segs = raw.replace(/\[([^\]]*)\]/g, ".$1").split(".").map((s) => s.trim()).filter(Boolean);
14804
+ const cap = (s) => s ? s.charAt(0).toUpperCase() + s.slice(1) : "";
14805
+ return segs.map(cap).join("");
14806
+ }
14807
+ function isSimpleModel(model) {
14808
+ return /^[A-Za-z_$][\w$]*$/.test(String(model ?? "").trim());
14809
+ }
14810
+ function setDataEntry(model, valueExpr) {
14811
+ const m = String(model ?? "").trim();
14812
+ if (isSimpleModel(m)) return `${m}: ${valueExpr}`;
14813
+ return `'${m}': ${valueExpr}`;
14814
+ }
14815
+ function inputModelHandler(model) {
14816
+ return `proteusOn${modelHandlerSuffix(model)}Input`;
14817
+ }
14818
+ function componentModelHandler(model) {
14819
+ return `proteusUpdate${modelHandlerSuffix(model)}Model`;
14820
+ }
14821
+
14719
14822
  // src/transforms/template.ts
14720
14823
  function tagRule(id, title, tags, extra) {
14721
14824
  return {
@@ -15217,13 +15320,13 @@ var TEMPLATE_RULES = [
15217
15320
  kind: "component",
15218
15321
  model,
15219
15322
  propName,
15220
- updateHandler: `proteusUpdate${capitalize(model)}Model`
15323
+ updateHandler: componentModelHandler(model)
15221
15324
  };
15222
15325
  } else {
15223
15326
  ctx.output = {
15224
15327
  kind: "input",
15225
15328
  model,
15226
- inputHandler: `proteusOn${capitalize(model)}Input`
15329
+ inputHandler: inputModelHandler(model)
15227
15330
  };
15228
15331
  }
15229
15332
  }
@@ -17020,6 +17123,99 @@ function scanWxmlPlatformIssues(wxml) {
17020
17123
  seenKeys.add(key);
17021
17124
  }
17022
17125
  }
17126
+ for (const m of wxml.matchAll(/\{\{([\s\S]*?)\}\}/g)) {
17127
+ const expr = m[1] ?? "";
17128
+ if (expr.includes(String.fromCharCode(96)) || expr.includes("${")) {
17129
+ issues.push({
17130
+ code: "TemplateLiteralInExpression",
17131
+ message: `\u7ED1\u5B9A\u8868\u8FBE\u5F0F\u542B\u6A21\u677F\u5B57\u9762\u91CF\uFF08\u53CD\u5F15\u53F7 / \u7F8E\u5143\u5927\u62EC\u53F7\uFF09\uFF1A{{ ${expr.trim().slice(0, 60)} }}\u2014\u2014WXML \u8868\u8FBE\u5F0F\u4E0D\u652F\u6301\uFF0C\u5FAE\u4FE1\u62A5 "unexpected character inside expression"\u3002\u8BF7\u628A\u5B57\u7B26\u4E32\u62FC\u63A5\u79FB\u5230 script \u9884\u8BA1\u7B97\uFF08computed \u6216 data \u5B57\u6BB5\uFF09`,
17132
+ at: m.index ?? 0
17133
+ });
17134
+ }
17135
+ }
17136
+ for (const m of wxml.matchAll(/<template\b([^>]*?)>([\s\S]*?)<\/template>/g)) {
17137
+ const attrs = m[1] ?? "";
17138
+ const body = m[2] ?? "";
17139
+ const hasChildren = /<[a-zA-Z]/.test(body.trim());
17140
+ if (hasChildren && /wx:(if|elif|else|for)\b/.test(attrs)) {
17141
+ issues.push({
17142
+ code: "TemplateChildNodes",
17143
+ message: `<template${attrs.trim() ? " " + attrs.trim().slice(0, 40) : ""}> \u542B\u5B50\u5143\u7D20\u2014\u2014WXML \u7684 <template> \u662F\u5B9A\u4E49\u5757\uFF08is=/data=\uFF09\uFF0C\u4E0D\u662F Vue \u7684\u7247\u6BB5\u5BB9\u5668\uFF08\u5FAE\u4FE1\u62A5 "child nodes are not allowed"/"missing module name"\uFF09\u3002\u8BF7\u6539\u7528 <view wx:if>`,
17144
+ at: m.index ?? 0
17145
+ });
17146
+ }
17147
+ }
17148
+ const KNOWN_HTML_TAGS = /* @__PURE__ */ new Set([
17149
+ "section",
17150
+ "article",
17151
+ "aside",
17152
+ "nav",
17153
+ "main",
17154
+ "header",
17155
+ "footer",
17156
+ "figure",
17157
+ "figcaption",
17158
+ "details",
17159
+ "summary",
17160
+ "dialog",
17161
+ "fieldset",
17162
+ "legend",
17163
+ "ul",
17164
+ "ol",
17165
+ "li",
17166
+ "dl",
17167
+ "dt",
17168
+ "dd",
17169
+ "table",
17170
+ "thead",
17171
+ "tbody",
17172
+ "tfoot",
17173
+ "tr",
17174
+ "th",
17175
+ "td",
17176
+ "caption",
17177
+ "strong",
17178
+ "b",
17179
+ "em",
17180
+ "i",
17181
+ "u",
17182
+ "s",
17183
+ "small",
17184
+ "mark",
17185
+ "sub",
17186
+ "sup",
17187
+ "code",
17188
+ "pre",
17189
+ "kbd",
17190
+ "samp",
17191
+ "var",
17192
+ "abbr",
17193
+ "cite",
17194
+ "q",
17195
+ "blockquote",
17196
+ "time",
17197
+ "address",
17198
+ "select",
17199
+ "option",
17200
+ "optgroup",
17201
+ "datalist",
17202
+ "output",
17203
+ "meter",
17204
+ "hr",
17205
+ "br",
17206
+ "picture",
17207
+ "source",
17208
+ "track",
17209
+ "iframe"
17210
+ ]);
17211
+ for (const tag of scanOpenTags(wxml)) {
17212
+ if (!KNOWN_HTML_TAGS.has(tag.name)) continue;
17213
+ issues.push({
17214
+ code: "UnknownHtmlTag",
17215
+ message: `\u6807\u7B7E <${tag.name}> \u662F HTML \u6807\u7B7E\u3001\u5FAE\u4FE1 wxml \u65E0\u6B64\u539F\u751F\u6807\u7B7E\u2014\u2014\u672A\u88AB\u6620\u5C04\uFF08TAG_MAP \u9057\u6F0F\uFF09\u800C\u539F\u6837\u8FDB\u4EA7\u7269\uFF0C\u5FAE\u4FE1\u7F16\u8BD1\u4F1A\u5931\u8D25\u6216\u6E32\u67D3\u5F02\u5E38\u3002\u8BF7\u6539\u7528 WXML \u7B49\u4EF7\u7ED3\u6784\uFF08\u5757\u7EA7 \u2192 <view>\u3001\u6587\u672C\u7C7B \u2192 <text>\uFF09`,
17216
+ at: tag.at
17217
+ });
17218
+ }
17023
17219
  return issues;
17024
17220
  }
17025
17221
  function splitStyleDecls(styleVal) {
@@ -18879,6 +19075,25 @@ ${inner}
18879
19075
  }
18880
19076
  }
18881
19077
  if (staticStyle) attrs.push(`style="${escapeXml(staticStyle)}"`);
19078
+ {
19079
+ const bindIdx = attrs.map((a, i) => a.startsWith('bindinput="') ? i : -1).filter((i) => i >= 0);
19080
+ if (bindIdx.length > 1) {
19081
+ const names = bindIdx.map((i) => /bindinput="([^"]*)"/.exec(attrs[i])?.[1] ?? "").filter(Boolean);
19082
+ const [vmodelHandler, ...userHandlers] = names;
19083
+ const suffix2 = vmodelHandler.replace(/^proteusOn/, "").replace(/Input$/, "");
19084
+ const merged = `proteusMerge${suffix2}And${userHandlers.map((h) => h.charAt(0).toUpperCase() + h.slice(1)).join("And")}`;
19085
+ if (!ctx.vModelMergedHandlers.some((h) => h.name === merged)) {
19086
+ ctx.vModelMergedHandlers.push({ name: merged, calls: [vmodelHandler, ...userHandlers] });
19087
+ }
19088
+ for (const i of [...bindIdx].reverse()) attrs.splice(i, 1);
19089
+ attrs.push(`bindinput="${merged}"`);
19090
+ ctx.trace?.add("directive/v-model", {
19091
+ line: node.loc.start.line,
19092
+ before: `v-model + ${userHandlers.map((h) => "@input=" + h).join(" / ")}\uFF08\u540C\u5143\u7D20 \u2192 bindinput \u91CD\u590D\uFF09`,
19093
+ after: `bindinput="${merged}"\uFF08\u5408\u5E76\uFF1A\u5148\u56DE\u5199\u518D\u8C03\u7528\u6237 handler\uFF09`
19094
+ });
19095
+ }
19096
+ }
18882
19097
  const attrStr = attrs.length ? ` ${attrs.join(" ")}` : "";
18883
19098
  const lineNote = ctx.annotateLines ? `<!-- @${node.loc.start.line} ${node.tag} -->
18884
19099
  ` : "";
@@ -19038,6 +19253,7 @@ function transformTemplateToWxml(source, opts = { px2rpx: true, rpxRatio: 2, ann
19038
19253
  // ★2026-09-08 useTemplateRef/模板 ref 承接(ref="x" → id + 收集)
19039
19254
  templateRefNames: /* @__PURE__ */ new Set(),
19040
19255
  vModelComponentHandlers: [],
19256
+ vModelMergedHandlers: [],
19041
19257
  // ★#496 柔性语义编译:p-grid 收集
19042
19258
  semanticGrids: [],
19043
19259
  isPage: opts.isComponent !== true,
@@ -19113,6 +19329,7 @@ ${inner}
19113
19329
  dynamicSvgs: ctx.dynamicSvgs,
19114
19330
  // ★#500 自定义组件 v-model 回写处理器
19115
19331
  vModelComponentHandlers: ctx.vModelComponentHandlers,
19332
+ vModelMergedHandlers: ctx.vModelMergedHandlers,
19116
19333
  semanticGrids: ctx.semanticGrids,
19117
19334
  // ★15-page-scroll-container:已自动包滚动容器(compileVueSfc 据此注入高度样式)
19118
19335
  pageScrollWrapped: autoScroll && !alreadyScroll,
@@ -21924,7 +22141,16 @@ function transformScriptToPage(source, _opts = { px2rpx: true, rpxRatio: 2 }, ex
21924
22141
  }
21925
22142
  for (const ni of reactiveInits) dataExtra[ni.name] = null;
21926
22143
  const storeBindings = extra.storeBindings ?? [];
21927
- const storeVar = runtimeInits.find((i) => i.name === "store" && /^use\w*Store\(/.test(i.call))?.name;
22144
+ const storeCandidates = runtimeInits.filter((i) => /^use\w*Store\(/.test(i.call));
22145
+ const strict = storeCandidates.find((i) => i.name === "store");
22146
+ const storeVar = strict?.name ?? (storeCandidates.length === 1 ? storeCandidates[0].name : void 0);
22147
+ if (storeBindings.length && !storeVar) {
22148
+ warnings.push(
22149
+ `\u6A21\u677F\u5F15\u7528\u4E86 store \u5B57\u6BB5\uFF08${storeBindings.join(" / ")}\uFF09\uFF0C\u4F46\u672A\u80FD\u8BC6\u522B\u51FA store \u53D8\u91CF\u2014\u2014MP \u7AEF\u7ED1\u5B9A\u4E0D\u4F1A\u751F\u6548\uFF08\u6784\u5EFA\u901A\u8FC7\u4F46\u771F\u673A\u53D6\u4E0D\u5230\u503C\uFF09\u3002
22150
+ \u8BC6\u522B\u8981\u6C42\uFF1A\u2460 \u53D8\u91CF\u58F0\u660E\u4E3A \`const store = useXxxStore()\`\uFF08\u53D8\u91CF\u540D\u987B\u4E3A store\uFF09\uFF0C\u6216 \u2461 \u672C\u6587\u4EF6**\u53EA\u6709\u4E00\u4E2A** \`useXxxStore()\` \u8C03\u7528\u3002
22151
+ \u5F53\u524D\u672A\u6EE1\u8DB3${storeCandidates.length > 1 ? `\uFF08\u68C0\u6D4B\u5230\u591A\u4E2A useXxxStore()\uFF1A${storeCandidates.map((i) => i.name).join(" / ")}\u2014\u2014\u8BF7\u628A\u7528\u4E8E\u6A21\u677F\u7ED1\u5B9A\u7684\u90A3\u4E2A\u547D\u540D\u4E3A store\uFF09` : "\uFF08\u672A\u68C0\u6D4B\u5230\u4EFB\u4F55 useXxxStore() \u5F62\u6001\u7684\u8C03\u7528\uFF09"}\u3002`
22152
+ );
22153
+ }
21928
22154
  const storeBindingInit = storeVar && storeBindings.length ? storeBindingLine(storeBindings, storeVar) : "";
21929
22155
  if (storeBindingInit) {
21930
22156
  trace?.add("script/store-binding", {
@@ -22091,16 +22317,22 @@ function transformScriptToPage(source, _opts = { px2rpx: true, rpxRatio: 2 }, ex
22091
22317
  const vmodelDisabled = disabled.has("script/vmodel-handler");
22092
22318
  for (const name of vModelBindings) {
22093
22319
  if (!vmodelDisabled) {
22094
- methodNames.add(`proteusOn${capitalize2(name)}Input`);
22095
- pushMethod(` proteusOn${capitalize2(name)}Input(e) { this.setData({ ${name}: e.detail.value }) },`);
22320
+ methodNames.add(inputModelHandler(name));
22321
+ pushMethod(` ${inputModelHandler(name)}(e) { this.setData({ ${setDataEntry(name, "e.detail.value")} }) },`);
22096
22322
  }
22097
22323
  }
22098
22324
  for (const h of extra.vModelComponentHandlers ?? []) {
22099
22325
  if (!vmodelDisabled) {
22100
22326
  methodNames.add(h.name);
22101
- pushMethod(` ${h.name}(e) { this.setData({ ${h.model}: e.detail }) },`);
22327
+ pushMethod(` ${h.name}(e) { this.setData({ ${setDataEntry(h.model, "e.detail")} }) },`);
22102
22328
  }
22103
22329
  }
22330
+ for (const h of extra.vModelMergedHandlers ?? []) {
22331
+ if (vmodelDisabled) continue;
22332
+ methodNames.add(h.name);
22333
+ const body = h.calls.map((c) => `this.${c}(e)`).join("; ");
22334
+ pushMethod(` ${h.name}(e) { ${body} },`);
22335
+ }
22104
22336
  if (vModelBindings.length && !vmodelDisabled) {
22105
22337
  trace?.add("script/vmodel-handler", {
22106
22338
  before: `v-model="${vModelBindings.join('", "')}"`,
@@ -23188,6 +23420,8 @@ function compileVueSfc(source, options = {}) {
23188
23420
  dynamicSvgs: tplResult.dynamicSvgs,
23189
23421
  // ★#500 自定义组件 v-model 回写处理器
23190
23422
  vModelComponentHandlers: tplResult.vModelComponentHandlers,
23423
+ // ★2026-09-20(F-28/Bug E):v-model + @input 同元素 → 合并处理器(script 生成方法)
23424
+ vModelMergedHandlers: tplResult.vModelMergedHandlers,
23191
23425
  semanticGrids: tplResult.semanticGrids,
23192
23426
  moduleImports: options.moduleImports,
23193
23427
  modelRefs: sfcMacros.ok ? sfcMacros.modelRefs : void 0,
@@ -0,0 +1,14 @@
1
+ /** 路径 → 合法标识符片段(各段首字母大写并拼接;非标识符字符丢弃后按段切分) */
2
+ export declare function modelHandlerSuffix(model: string): string;
3
+ /** 绑定表达式是否为**简单标识符**(无路径)——简单标识符可走原来的简洁产物,路径才需要特殊处理 */
4
+ export declare function isSimpleModel(model: string): boolean;
5
+ /**
6
+ * setData 条目文本。
7
+ * · 简单标识符 → `name: expr`(与原产物一致,保持向后兼容)
8
+ * · 路径 → `'a.b': expr` / `'arr[0]': expr`(**键加引号**;小程序 setData 支持路径键写入嵌套字段)
9
+ */
10
+ export declare function setDataEntry(model: string, valueExpr: string): string;
11
+ /** v-model(原生/input 形态)的 handler 名——template 与 script 两侧共用 */
12
+ export declare function inputModelHandler(model: string): string;
13
+ /** v-model(组件形态,回写目标)的 handler 名——同上 */
14
+ export declare function componentModelHandler(model: string): string;
@@ -34,7 +34,7 @@ export declare function validateWxml(wxml: string): {
34
34
  export declare function assertValidResult(result: CompileResult, filename: string): void;
35
35
  /** 单条 wxml 平台违规(code = 官方 ParseErrorKind 蓝本) */
36
36
  export interface WxmlPlatformIssue {
37
- code: 'DataBindingNotAllowed' | 'DuplicatedAttribute' | 'AvoidUppercaseLetters' | 'UnsupportedSyntax' | 'InvalidAttribute' | 'DuplicatedStylePropertyNames';
37
+ code: 'DataBindingNotAllowed' | 'DuplicatedAttribute' | 'AvoidUppercaseLetters' | 'UnsupportedSyntax' | 'InvalidAttribute' | 'DuplicatedStylePropertyNames' | 'TemplateLiteralInExpression' | 'TemplateChildNodes' | 'UnknownHtmlTag';
38
38
  message: string;
39
39
  /** wxml 字符偏移(定位用) */
40
40
  at: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proteus-vue/compiler",
3
- "version": "0.3.0-beta.11",
3
+ "version": "0.3.0-beta.15",
4
4
  "description": "Proteus 编译引擎 —— 标准 Vue SFC → 小程序四件套(纯函数、零运行时依赖、规则注册表 + AI 说明书)",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -42,7 +42,7 @@
42
42
  "@babel/plugin-transform-optional-chaining": "^7.29.0",
43
43
  "@proteus-vue/component-ir": "0.3.0-beta.11",
44
44
  "@proteus-vue/contracts": "0.3.0-beta.11",
45
- "@proteus-vue/types": "0.3.0-beta.11"
45
+ "@proteus-vue/types": "0.3.0-beta.12"
46
46
  },
47
47
  "scripts": {
48
48
  "build": "tsc -p tsconfig.build.json --emitDeclarationOnly && esbuild src/index.ts src/style-safety/index.ts --bundle --format=esm --platform=node --outdir=dist --external:@vue/compiler-sfc --external:@vue/compiler-dom --external:@proteus-vue/contracts --external:@proteus-vue/component-ir --external:@babel/core --external:@babel/plugin-transform-nullish-coalescing-operator --external:@babel/plugin-transform-optional-chaining --external:@babel/plugin-transform-logical-assignment-operators --external:@babel/plugin-transform-object-rest-spread"