@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/README.md +74 -0
- package/dist/explain.d.ts +25 -0
- package/dist/explain.js +59 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +3396 -0
- package/dist/overrides.d.ts +14 -0
- package/dist/overrides.js +34 -0
- package/dist/script.d.ts +15 -0
- package/dist/script.js +318 -0
- package/dist/style-safety/constants.d.ts +6 -0
- package/dist/style-safety/index.d.ts +25 -0
- package/dist/style-safety/index.js +276 -0
- package/dist/style-safety/reachability.d.ts +8 -0
- package/dist/style.d.ts +3 -0
- package/dist/style.js +96 -0
- package/dist/tags.d.ts +3 -0
- package/dist/tags.js +49 -0
- package/dist/template.d.ts +3 -0
- package/dist/template.js +337 -0
- package/dist/trace.d.ts +6 -0
- package/dist/trace.js +18 -0
- package/dist/transforms/registry.d.ts +18 -0
- package/dist/transforms/registry.js +48 -0
- package/dist/transforms/script.d.ts +2 -0
- package/dist/transforms/script.js +179 -0
- package/dist/transforms/style.d.ts +2 -0
- package/dist/transforms/style.js +74 -0
- package/dist/transforms/template.d.ts +3 -0
- package/dist/transforms/template.js +371 -0
- package/dist/transforms/types.d.ts +50 -0
- package/dist/transforms/types.js +7 -0
- package/dist/transforms/validate.d.ts +2 -0
- package/dist/transforms/validate.js +50 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.js +4 -0
- package/dist/validate.d.ts +18 -0
- package/dist/validate.js +54 -0
- package/package.json +42 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TransformRuleOverrides } from './types';
|
|
2
|
+
/** 解析后的生效配置(供转换函数查询) */
|
|
3
|
+
export interface ResolvedOverrides {
|
|
4
|
+
/** 生效的标签映射(TAG_MAP + customTags + tag/* 规则覆盖) */
|
|
5
|
+
tagMap: Record<string, string>;
|
|
6
|
+
/** 生效的事件映射(EVENT_MAP + event/click-to-tap 覆盖) */
|
|
7
|
+
eventMap: Record<string, string>;
|
|
8
|
+
/** 生效的语义基础类(SEMANTIC_CLASS + semantic/base-class 覆盖) */
|
|
9
|
+
semanticClass: Record<string, string>;
|
|
10
|
+
/** 被禁用的规则 ID 集合 */
|
|
11
|
+
disabled: Set<string>;
|
|
12
|
+
}
|
|
13
|
+
/** 解析规则覆盖:合并 tags.ts 常量 + 覆盖补丁;未知规则 ID 编译期警告(防配置笔误) */
|
|
14
|
+
export declare function resolveOverrides(options?: TransformRuleOverrides): ResolvedOverrides;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// src/compiler/overrides.ts
|
|
2
|
+
// 规则覆盖解析 —— ★底线循环 ①③:AI / proteus.config.ts 改写或禁用编译规则,编译器即时生效
|
|
3
|
+
// 所有转换函数(template/style/script)从本模块取"生效的映射表 + 禁用集",不再直接读 tags.ts 常量
|
|
4
|
+
import { TAG_MAP, EVENT_MAP, SEMANTIC_CLASS } from './tags';
|
|
5
|
+
import { getTransformRule } from './transforms/registry';
|
|
6
|
+
/** 解析规则覆盖:合并 tags.ts 常量 + 覆盖补丁;未知规则 ID 编译期警告(防配置笔误) */
|
|
7
|
+
export function resolveOverrides(options) {
|
|
8
|
+
const tagMap = { ...TAG_MAP };
|
|
9
|
+
const eventMap = { ...EVENT_MAP };
|
|
10
|
+
const semanticClass = { ...SEMANTIC_CLASS };
|
|
11
|
+
const disabled = new Set(options?.disabled ?? []);
|
|
12
|
+
for (const [ruleId, patch] of Object.entries(options?.mapping ?? {})) {
|
|
13
|
+
const rule = getTransformRule(ruleId);
|
|
14
|
+
if (!rule) {
|
|
15
|
+
console.warn(`[proteus] 规则覆盖引用了未注册的规则 ID:${ruleId}(已忽略,可用 listTransformRules() 查看合法 ID)`);
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (rule.id.startsWith('tag/'))
|
|
19
|
+
Object.assign(tagMap, patch);
|
|
20
|
+
else if (rule.id === 'event/click-to-tap')
|
|
21
|
+
Object.assign(eventMap, patch);
|
|
22
|
+
else if (rule.id === 'semantic/base-class')
|
|
23
|
+
Object.assign(semanticClass, patch);
|
|
24
|
+
else
|
|
25
|
+
console.warn(`[proteus] 规则 ${ruleId} 不支持 mapping 覆盖(仅 tag/* / event/click-to-tap / semantic/base-class)`);
|
|
26
|
+
}
|
|
27
|
+
// 自定义标签映射:AI 扩展新标签的入口(最高优先级)
|
|
28
|
+
Object.assign(tagMap, options?.customTags);
|
|
29
|
+
for (const id of disabled) {
|
|
30
|
+
if (!getTransformRule(id))
|
|
31
|
+
console.warn(`[proteus] 规则覆盖禁用了未注册的规则 ID:${id}(已忽略)`);
|
|
32
|
+
}
|
|
33
|
+
return { tagMap, eventMap, semanticClass, disabled };
|
|
34
|
+
}
|
package/dist/script.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ScriptTransformOptions, ScriptTransformResult, StyleTransformOptions } from './types';
|
|
2
|
+
/** base64 VLQ 编码(sourcemap mappings 字段) */
|
|
3
|
+
export declare function vlqEncode(value: number): string;
|
|
4
|
+
/** base64 VLQ 解码(测试验证用) */
|
|
5
|
+
export declare function vlqDecode(str: string): number[];
|
|
6
|
+
/**
|
|
7
|
+
* 生成 sourcemap v3 JSON:产物每行 → 源码行(lineMappings: 产物 0-based 行 → 源码 1-based 行)
|
|
8
|
+
* segment = [genCol=0, srcIdx=0, srcLine(delta), srcCol=0];无映射行 → 空 segment
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildSourceMap(js: string, file: string | undefined, source: string, lineMappings: Array<{
|
|
11
|
+
out: number;
|
|
12
|
+
src: number;
|
|
13
|
+
}>): string;
|
|
14
|
+
/** script 源码 → Page/Component 构造器 JS(纯函数,独立可测) */
|
|
15
|
+
export declare function transformScriptToPage(source: string, _opts?: StyleTransformOptions, extra?: ScriptTransformOptions): ScriptTransformResult;
|
package/dist/script.js
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { lineAt } from './trace';
|
|
2
|
+
import { resolveOverrides } from './overrides';
|
|
3
|
+
/** 构建期求值开发者自身源码中的字面量表达式(与 babel 插件同信任域) */
|
|
4
|
+
function evalLiteral(expr) {
|
|
5
|
+
try {
|
|
6
|
+
return Function(`"use strict"; return (${expr})`)();
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** 从 openBraceIndex 的 { 开始匹配闭合大括号,返回内部内容 */
|
|
13
|
+
function extractBracedBody(source, openBraceIndex) {
|
|
14
|
+
let depth = 0;
|
|
15
|
+
for (let i = openBraceIndex; i < source.length; i++) {
|
|
16
|
+
const ch = source[i];
|
|
17
|
+
if (ch === '{')
|
|
18
|
+
depth++;
|
|
19
|
+
else if (ch === '}') {
|
|
20
|
+
depth--;
|
|
21
|
+
if (depth === 0)
|
|
22
|
+
return source.slice(openBraceIndex + 1, i);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 从 valueStart 扫描 const 初始值:追踪 ()[]{} 平衡并跳过字符串/注释(字符串里的括号不影响深度),
|
|
29
|
+
* 深度归零后遇 ; 或行尾结束——支持多行数组/对象字面量(如 ref([\n { a: 1 },\n]))
|
|
30
|
+
*/
|
|
31
|
+
function extractInitializer(source, valueStart) {
|
|
32
|
+
let depth = 0;
|
|
33
|
+
let quote = null;
|
|
34
|
+
let escaped = false;
|
|
35
|
+
let inBlockComment = false;
|
|
36
|
+
let i = valueStart;
|
|
37
|
+
const len = source.length;
|
|
38
|
+
for (; i < len; i++) {
|
|
39
|
+
const ch = source[i];
|
|
40
|
+
const next = source[i + 1];
|
|
41
|
+
if (inBlockComment) {
|
|
42
|
+
if (ch === '*' && next === '/') {
|
|
43
|
+
inBlockComment = false;
|
|
44
|
+
i++;
|
|
45
|
+
}
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (quote) {
|
|
49
|
+
if (escaped) {
|
|
50
|
+
escaped = false;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (ch === '\\') {
|
|
54
|
+
escaped = true;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (ch === quote)
|
|
58
|
+
quote = null;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (ch === '/' && next === '*') {
|
|
62
|
+
inBlockComment = true;
|
|
63
|
+
i++;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (ch === '/' && next === '/') {
|
|
67
|
+
// 行注释:跳过至行尾;深度 0 时注释后无语句内容,行尾即结束
|
|
68
|
+
while (i < len && source[i] !== '\n')
|
|
69
|
+
i++;
|
|
70
|
+
if (depth === 0)
|
|
71
|
+
break;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
75
|
+
quote = ch;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (ch === '(' || ch === '[' || ch === '{') {
|
|
79
|
+
depth++;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (ch === ')' || ch === ']' || ch === '}') {
|
|
83
|
+
depth--;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (depth === 0 && (ch === ';' || ch === '\n'))
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
return source.slice(valueStart, i).trim();
|
|
90
|
+
}
|
|
91
|
+
/** 顶层 const(ref/reactive/字面量)→ data 初始值 */
|
|
92
|
+
function extractData(source, warnings, trace) {
|
|
93
|
+
const data = {};
|
|
94
|
+
// 只提取行首(缩进 0)的顶层 const:函数体/生命周期体/块内的局部 const 天然跳过
|
|
95
|
+
const re = /const\s+([A-Za-z_$][\w$]*)\s*=\s*/gm;
|
|
96
|
+
let m;
|
|
97
|
+
while ((m = re.exec(source))) {
|
|
98
|
+
// 只提取行首(零缩进)的顶层 const:函数体/生命周期体/块内的局部 const 天然跳过
|
|
99
|
+
const lineStart = source.lastIndexOf('\n', m.index) + 1;
|
|
100
|
+
if (source.slice(lineStart, m.index) !== '')
|
|
101
|
+
continue;
|
|
102
|
+
const name = m[1];
|
|
103
|
+
const init = extractInitializer(source, m.index + m[0].length);
|
|
104
|
+
if (!init)
|
|
105
|
+
continue;
|
|
106
|
+
trace?.add('script/const-to-data', {
|
|
107
|
+
line: lineAt(source, m.index),
|
|
108
|
+
before: `const ${name} = ${init.slice(0, 40)}${init.length > 40 ? '…' : ''}`,
|
|
109
|
+
after: `data.${name}`,
|
|
110
|
+
});
|
|
111
|
+
// 跳过函数/箭头函数(属于 methods)
|
|
112
|
+
if (/^(?:async\s+)?(?:function\b|(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>)/.test(init))
|
|
113
|
+
continue;
|
|
114
|
+
// 解构/嵌套 const(如 const { a } = ...)在 regex 上已天然跳过
|
|
115
|
+
const inner = init.match(/^(?:ref|reactive|shallowRef|readonly)\s*\(\s*([\s\S]*?)\s*\);?\s*$/);
|
|
116
|
+
const raw = inner ? inner[1] : init;
|
|
117
|
+
const value = evalLiteral(raw);
|
|
118
|
+
if (value === undefined && raw !== 'undefined') {
|
|
119
|
+
warnings.push(`const ${name} 的初始值 "${raw.slice(0, 40)}" 无法静态求值,data.${name} 将设为 undefined(MVP 限制:仅支持字面量)`);
|
|
120
|
+
}
|
|
121
|
+
data[name] = value;
|
|
122
|
+
}
|
|
123
|
+
return data;
|
|
124
|
+
}
|
|
125
|
+
/** 顶层函数(function 声明 / const 箭头)→ methods 源码 */
|
|
126
|
+
function extractMethods(source, warnings, trace, disabled) {
|
|
127
|
+
const methods = {};
|
|
128
|
+
const fnRe = /function\s+([A-Za-z_$][\w$]*)\s*\(([^)]*)\)\s*\{/g;
|
|
129
|
+
let m;
|
|
130
|
+
if (!disabled?.has('script/function-to-methods')) {
|
|
131
|
+
while ((m = fnRe.exec(source))) {
|
|
132
|
+
const name = m[1];
|
|
133
|
+
const params = m[2];
|
|
134
|
+
const body = extractBracedBody(source, m.index + m[0].length - 1);
|
|
135
|
+
trace?.add('script/function-to-methods', { line: lineAt(source, m.index), before: `function ${name}(${params})`, after: `${name}(${params})` });
|
|
136
|
+
// 对象字面量方法简写:handleTap() {...}(不能输出裸 function 声明)
|
|
137
|
+
if (body !== null)
|
|
138
|
+
methods[name] = `${name}(${params}) {\n${body}\n}`;
|
|
139
|
+
else
|
|
140
|
+
warnings.push(`函数 ${name} 体解析失败,已跳过`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const arrowRe = /const\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?\(([^)]*)\)\s*=>\s*\{/g;
|
|
144
|
+
if (!disabled?.has('script/arrow-to-methods')) {
|
|
145
|
+
while ((m = arrowRe.exec(source))) {
|
|
146
|
+
const name = m[1];
|
|
147
|
+
const params = m[2];
|
|
148
|
+
const braceIdx = source.indexOf('{', m.index + m[0].length - 1);
|
|
149
|
+
const body = extractBracedBody(source, braceIdx);
|
|
150
|
+
trace?.add('script/arrow-to-methods', { line: lineAt(source, m.index), before: `const ${name} = (...) =>`, after: `${name}(...)` });
|
|
151
|
+
if (body !== null)
|
|
152
|
+
methods[name] = `${name}(${params}) {\n${body}\n}`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return methods;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* 方法/生命周期体中的 setup ref 访问重写(MVP 能力)
|
|
159
|
+
* - name.value = expr → this.setData({ name: expr })
|
|
160
|
+
* - name.value++ / -- → this.setData({ name: (null 检查 ? 0 : this.data.name) + 1 })(含前置 ++ 形式)
|
|
161
|
+
* - name.value(读取) → this.data.name
|
|
162
|
+
* 未覆盖:复合赋值(+= 等)降级为 this.data.name 读写(不触发 setData)
|
|
163
|
+
* 注意:不能用 `??`(真机预览报 SyntaxError: Unexpected token ?),统一显式 null 检查
|
|
164
|
+
*/
|
|
165
|
+
function numOrZero(expr) {
|
|
166
|
+
return `(${expr} === undefined || ${expr} === null ? 0 : ${expr})`;
|
|
167
|
+
}
|
|
168
|
+
function rewriteRefAccess(body, refNames, trace, disabled) {
|
|
169
|
+
const skip = (id) => disabled?.has(id);
|
|
170
|
+
let out = body;
|
|
171
|
+
for (const name of refNames) {
|
|
172
|
+
const prop = `this.data.${name}`;
|
|
173
|
+
const line = lineAt(body, Math.max(0, body.indexOf(name)));
|
|
174
|
+
// 自增/自减(含前置 ++name.value / --name.value)
|
|
175
|
+
if (!skip('script/ref-incdec')) {
|
|
176
|
+
if (new RegExp(`(\\+\\+|--)\\s*${name}\\.value`).test(body) || new RegExp(`\\b${name}\\.value\\s*(\\+\\+|--)`).test(body)) {
|
|
177
|
+
trace?.add('script/ref-incdec', { line, before: `${name}.value++/--`, after: `this.setData({ ${name}: ... })` });
|
|
178
|
+
}
|
|
179
|
+
out = out.replace(new RegExp(`\\+\\+\\s*${name}\\.value`, 'g'), `${prop} = ${numOrZero(prop)} + 1; this.setData({ ${name}: ${prop} })`);
|
|
180
|
+
out = out.replace(new RegExp(`--\\s*${name}\\.value`, 'g'), `${prop} = ${numOrZero(prop)} - 1; this.setData({ ${name}: ${prop} })`);
|
|
181
|
+
out = out.replace(new RegExp(`\\b${name}\\.value\\s*\\+\\+`, 'g'), `this.setData({ ${name}: ${numOrZero(prop)} + 1 })`);
|
|
182
|
+
out = out.replace(new RegExp(`\\b${name}\\.value\\s*--`, 'g'), `this.setData({ ${name}: ${numOrZero(prop)} - 1 })`);
|
|
183
|
+
}
|
|
184
|
+
// 赋值:name.value = expr(排除 == / === / 复合赋值)
|
|
185
|
+
if (!skip('script/ref-write')) {
|
|
186
|
+
if (new RegExp(`\\b${name}\\.value\\s*=\\s*(?!=)`).test(out)) {
|
|
187
|
+
trace?.add('script/ref-write', { line, before: `${name}.value = expr`, after: `this.setData({ ${name}: expr })` });
|
|
188
|
+
}
|
|
189
|
+
out = out.replace(new RegExp(`\\b${name}\\.value\\s*=\\s*(?!=)([^;\\n]+)`), (_m, expr) => `this.setData({ ${name}: ${expr.trim()} })`);
|
|
190
|
+
}
|
|
191
|
+
// 读取:name.value → this.data.name
|
|
192
|
+
if (!skip('script/ref-read')) {
|
|
193
|
+
if (new RegExp(`\\b${name}\\.value\\b`).test(out)) {
|
|
194
|
+
trace?.add('script/ref-read', { line, before: `${name}.value`, after: `this.data.${name}` });
|
|
195
|
+
}
|
|
196
|
+
out = out.replace(new RegExp(`\\b${name}\\.value\\b`, 'g'), prop);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return out;
|
|
200
|
+
}
|
|
201
|
+
/** 生命周期映射:onMounted→onReady / onUnmounted→onUnload / onLoad→onLoad */
|
|
202
|
+
function extractLifecycles(source, trace, disabled) {
|
|
203
|
+
const out = {};
|
|
204
|
+
if (disabled?.has('script/lifecycle-map'))
|
|
205
|
+
return out;
|
|
206
|
+
const hooks = [
|
|
207
|
+
{ re: /onMounted\s*\(/g, key: 'onReady' },
|
|
208
|
+
{ re: /onUnmounted\s*\(/g, key: 'onUnload' },
|
|
209
|
+
{ re: /onLoad\s*\(/g, key: 'onLoad' },
|
|
210
|
+
];
|
|
211
|
+
for (const h of hooks) {
|
|
212
|
+
h.re.lastIndex = 0;
|
|
213
|
+
const m = h.re.exec(source);
|
|
214
|
+
if (!m)
|
|
215
|
+
continue;
|
|
216
|
+
const braceIdx = source.indexOf('{', m.index);
|
|
217
|
+
const body = extractBracedBody(source, braceIdx);
|
|
218
|
+
trace?.add('script/lifecycle-map', { line: lineAt(source, m.index), before: m[0].replace(/\s*\(/, '()'), after: h.key });
|
|
219
|
+
if (body !== null)
|
|
220
|
+
out[h.key] = body;
|
|
221
|
+
}
|
|
222
|
+
return out;
|
|
223
|
+
}
|
|
224
|
+
function indentBody(body) {
|
|
225
|
+
return body.split('\n').map((l) => ` ${l}`).join('\n');
|
|
226
|
+
}
|
|
227
|
+
function capitalize(s) {
|
|
228
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
229
|
+
}
|
|
230
|
+
/** script 源码 → Page/Component 构造器 JS(纯函数,独立可测) */
|
|
231
|
+
export function transformScriptToPage(source, _opts = { px2rpx: true, rpxRatio: 2 }, extra = {}) {
|
|
232
|
+
const warnings = [];
|
|
233
|
+
const trace = extra.trace;
|
|
234
|
+
// ★底线循环 ①③:禁用集(config rules.disabled 即时生效)
|
|
235
|
+
const disabled = resolveOverrides(extra.rules).disabled;
|
|
236
|
+
const data = disabled.has('script/const-to-data') ? {} : extractData(source, warnings, trace);
|
|
237
|
+
const methods = extractMethods(source, warnings, trace, disabled);
|
|
238
|
+
const lifecycles = extractLifecycles(source, trace, disabled);
|
|
239
|
+
const vModelBindings = extra.vModelBindings ?? [];
|
|
240
|
+
const refNames = new Set(Object.keys(data));
|
|
241
|
+
const lines = [];
|
|
242
|
+
if (extra.file)
|
|
243
|
+
lines.push(`// ${extra.file}(Proteus mp-transform 编译产物)`);
|
|
244
|
+
lines.push('// AUTO-GENERATED by vite-plugin-mp-transform.ts. DO NOT EDIT.', '');
|
|
245
|
+
lines.push(extra.isComponent ? 'Component({' : 'Page({');
|
|
246
|
+
const dataEntries = Object.entries(data);
|
|
247
|
+
if (dataEntries.length) {
|
|
248
|
+
lines.push(' data: {');
|
|
249
|
+
for (const [k, v] of dataEntries)
|
|
250
|
+
lines.push(` ${k}: ${JSON.stringify(v)},`);
|
|
251
|
+
lines.push(' },');
|
|
252
|
+
}
|
|
253
|
+
// v-model 自动 handler:proteusOnXxxInput(e) { this.setData({ xxx: e.detail.value }) }
|
|
254
|
+
const vmodelDisabled = disabled.has('script/vmodel-handler');
|
|
255
|
+
for (const name of vModelBindings) {
|
|
256
|
+
if (!vmodelDisabled)
|
|
257
|
+
lines.push(` proteusOn${capitalize(name)}Input(e) { this.setData({ ${name}: e.detail.value }) },`);
|
|
258
|
+
}
|
|
259
|
+
if (vModelBindings.length && !vmodelDisabled) {
|
|
260
|
+
trace?.add('script/vmodel-handler', {
|
|
261
|
+
before: `v-model="${vModelBindings.join('", "')}"`,
|
|
262
|
+
after: `proteusOn${vModelBindings.map(capitalize).join(' / proteusOn')}Input(setData 回写)`,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
// 导航链接自动 handler(模板出现 <a href> / <router-link> 时注入,仅 MP 产物存在)
|
|
266
|
+
// 方法名避免 __ 前缀(微信保留前缀);当前为临时调试版:无条件输出日志(验证通过后回收门控)
|
|
267
|
+
if (extra.usesNavigate && !disabled.has('script/nav-handler')) {
|
|
268
|
+
trace?.add('script/nav-handler', { before: '<a href> / <router-link>', after: 'proteusNavigateTo(e)(data-url → wx.navigateTo)' });
|
|
269
|
+
// 注意:生成代码避免数组解构/对象展开(微信 ES5 转译依赖 babel helper 模块)
|
|
270
|
+
// 调试日志统一 [proteus][环节] 格式,仅 debug 构建注入
|
|
271
|
+
lines.push(' proteusNavigateTo(e) {', ' const ds = e.currentTarget.dataset', ' const url = String(ds.url || "")', ...(extra.debug ? [` console.log('[proteus][nav] tap', JSON.stringify(ds), Date.now())`] : []), ' if (!url) return', ...(extra.debug ? [` console.log('[proteus][nav] navigateTo', url, Date.now())`] : []), ' const nav = {', ' url: url,', ...(extra.debug ? [` success: function () { console.log('[proteus][nav] navigateTo success', url, Date.now()) },`] : []), ' fail: function (err) {', ...(extra.debug ? [` console.warn('[proteus][nav] navigateTo fail', JSON.stringify(err), Date.now())`] : []), ' if (ds.routeType) wx.navigateTo({ url: url })', ' }', ' }', ' if (ds.routeType) nav.routeType = ds.routeType', ' wx.navigateTo(nav)', ' },');
|
|
272
|
+
}
|
|
273
|
+
if (lifecycles.onReady) {
|
|
274
|
+
lines.push(` onReady() {\n${indentBody(rewriteRefAccess(lifecycles.onReady, refNames, trace, disabled))}\n },`);
|
|
275
|
+
}
|
|
276
|
+
else if (extra.debug) {
|
|
277
|
+
// 调试:注入页面就绪日志(无显式 onReady 时)
|
|
278
|
+
lines.push(` onReady() {\n console.log('[proteus][page] onReady ${extra.file ?? ''}', Date.now())\n },`);
|
|
279
|
+
}
|
|
280
|
+
if (lifecycles.onUnload)
|
|
281
|
+
lines.push(` onUnload() {\n${indentBody(rewriteRefAccess(lifecycles.onUnload, refNames, trace, disabled))}\n },`);
|
|
282
|
+
if (lifecycles.onLoad) {
|
|
283
|
+
lines.push(` onLoad(options) {\n${indentBody(rewriteRefAccess(lifecycles.onLoad, refNames, trace, disabled))}\n },`);
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
// 默认 onLoad:路由参数自动 decode 并注入 data(P5 契约,与 runtime/pageLifecycle 的 createPage 行为一致)
|
|
287
|
+
// 注意:不用数组解构/对象展开(微信 ES5 转译需要 babel helper 模块,真机报 arrayWithHoles 未定义)
|
|
288
|
+
if (!disabled.has('script/onload-params')) {
|
|
289
|
+
trace?.add('script/onload-params', { before: '(无显式 onLoad)', after: 'onLoad(options) → decodeURIComponent + JSON.parse + setData' });
|
|
290
|
+
lines.push([
|
|
291
|
+
' onLoad(options) {',
|
|
292
|
+
...(extra.debug ? [` console.log('[proteus][page] onLoad ${extra.file ?? ''}', JSON.stringify(options), Date.now())`] : []),
|
|
293
|
+
' const params = {}',
|
|
294
|
+
' const keys = Object.keys(options || {})',
|
|
295
|
+
' for (let i = 0; i < keys.length; i++) {',
|
|
296
|
+
' const k = keys[i]',
|
|
297
|
+
' const v = options[k]',
|
|
298
|
+
' const s = decodeURIComponent(v)',
|
|
299
|
+
' try { params[k] = (s.startsWith("{") || s.startsWith("[")) ? JSON.parse(s) : s } catch { params[k] = s }',
|
|
300
|
+
' }',
|
|
301
|
+
' this.setData(params)',
|
|
302
|
+
' },',
|
|
303
|
+
].join('\n'));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
for (const src of Object.values(methods))
|
|
307
|
+
lines.push(` ${rewriteRefAccess(src, refNames, trace, disabled)},`);
|
|
308
|
+
lines.push('})');
|
|
309
|
+
// 产物级约束(es5-safe 贯穿全部生成代码;component-mode 决定构造器)
|
|
310
|
+
trace?.add('script/component-mode', {
|
|
311
|
+
before: 'SFC',
|
|
312
|
+
after: extra.isComponent ? 'Component({ ... })' : 'Page({ ... })',
|
|
313
|
+
});
|
|
314
|
+
trace?.add('script/es5-safe', { before: '?? / ?. / 解构 / 展开', after: '显式 null 三元 / 索引循环 / 直接赋值' });
|
|
315
|
+
for (const w of warnings)
|
|
316
|
+
console.warn(`[mp-transform] ${w}`);
|
|
317
|
+
return { js: lines.join('\n') + '\n', warnings };
|
|
318
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type StylePlatform = 'web' | 'skyline' | 'ios' | 'android' | 'harmony';
|
|
2
|
+
export interface StyleBindingViolation {
|
|
3
|
+
code: string;
|
|
4
|
+
prop: string;
|
|
5
|
+
value: string;
|
|
6
|
+
line: number;
|
|
7
|
+
message: string;
|
|
8
|
+
}
|
|
9
|
+
export interface StyleBindingStats {
|
|
10
|
+
staticChecked: number;
|
|
11
|
+
dynamic: number;
|
|
12
|
+
/** 静态推导覆盖率(05 §4:staticChecked / (staticChecked + dynamic)) */
|
|
13
|
+
coverage: number;
|
|
14
|
+
}
|
|
15
|
+
export interface StyleBindingAnalysis {
|
|
16
|
+
violations: StyleBindingViolation[];
|
|
17
|
+
stats: StyleBindingStats;
|
|
18
|
+
}
|
|
19
|
+
/** 静态字面量求值:字符串/数字/布尔/null → 值;否则 undefined(动态源) */
|
|
20
|
+
export declare function tryStaticValue(expr: string): unknown | undefined;
|
|
21
|
+
/** 模板 :style 绑定分析(B3 主入口;scriptSource 提供常量表 → 常量折叠,05 §5) */
|
|
22
|
+
export declare function analyzeStyleBindings(templateSource: string, constants?: Record<string, unknown>): StyleBindingAnalysis;
|
|
23
|
+
export { deriveReachableValues } from './reachability';
|
|
24
|
+
export type { ReachableSet } from './reachability';
|
|
25
|
+
export { extractScriptConstants } from './constants';
|