@zerodep-dev/vue-tsx-pro-tsx 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +44 -2
- package/dist/compile-worker.d.ts +1 -0
- package/dist/compile-worker.js +23 -0
- package/dist/compiler-BDi92tYb.js +1647 -0
- package/dist/compiler.d.ts +104 -0
- package/dist/compiler.js +1 -0
- package/dist/detect-s3UM7xuN.js +17 -0
- package/dist/index.d.ts +140 -0
- package/dist/index.js +283 -0
- package/dist/module--rQ2-rvm.js +194 -0
- package/dist/ssr-register-CZ_lIN2f.js +17 -0
- package/dist/state-YxL7IqUO.js +108 -0
- package/dist/styles.d.ts +15402 -0
- package/dist/styles.js +1282 -0
- package/dist/vite.d.ts +31 -0
- package/dist/vite.js +509 -0
- package/package.json +98 -3
- package/types/jsx-runtime.d.ts +174 -0
- package/types/runes.d.ts +100 -0
|
@@ -0,0 +1,1647 @@
|
|
|
1
|
+
import { n as languageOf, r as parserPlugins, t as STYLE_SOURCE } from "./detect-s3UM7xuN.js";
|
|
2
|
+
import { parseAsync, transformAsync, transformFromAstAsync, types } from "@babel/core";
|
|
3
|
+
import typescript from "@babel/plugin-transform-typescript";
|
|
4
|
+
import vueJsx from "@vue/babel-plugin-jsx";
|
|
5
|
+
//#region src/compiler/errors.ts
|
|
6
|
+
/** 生成指向某个节点的 RunesError(由调用方 throw) */
|
|
7
|
+
function fail(path, message) {
|
|
8
|
+
const error = path.buildCodeFrameError(message);
|
|
9
|
+
const loc = path.node.loc;
|
|
10
|
+
if (loc) error.loc = {
|
|
11
|
+
start: { ...loc.start },
|
|
12
|
+
end: { ...loc.end }
|
|
13
|
+
};
|
|
14
|
+
return error;
|
|
15
|
+
}
|
|
16
|
+
/** 生成指向某个节点的编译警告 */
|
|
17
|
+
function warning(path, message) {
|
|
18
|
+
const { loc } = fail(path, message);
|
|
19
|
+
return {
|
|
20
|
+
message,
|
|
21
|
+
loc,
|
|
22
|
+
frame: path.buildCodeFrameError(message).message
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/compiler/ast.ts
|
|
27
|
+
/** 与符文无关的 AST 判断与改写工具 */
|
|
28
|
+
/** 组件边界:第一个参数是 setup 函数的调用 */
|
|
29
|
+
const COMPONENT_CALLEES = /* @__PURE__ */ new Set(["_component", "defineComponent"]);
|
|
30
|
+
/** 是否是组件(_component 或 Vue 的 defineComponent)的 setup 函数 */
|
|
31
|
+
function isComponentSetup(path) {
|
|
32
|
+
const call = path.parentPath;
|
|
33
|
+
return !!call?.isCallExpression() && call.node.arguments[0] === path.node && types.isIdentifier(call.node.callee) && COMPONENT_CALLEES.has(call.node.callee.name);
|
|
34
|
+
}
|
|
35
|
+
/** 由我们编译 props 的组件:_component(setup)。Vue 自己的 defineComponent 保持原样 */
|
|
36
|
+
function isPropsComponent(path) {
|
|
37
|
+
const call = path.parentPath;
|
|
38
|
+
return !!call?.isCallExpression() && call.node.arguments[0] === path.node && types.isIdentifier(call.node.callee, { name: "_component" });
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 是否位于组件的渲染函数(setup 返回的函数)之内,包括其中嵌套的回调。
|
|
42
|
+
* 渲染函数每次更新都会重新执行,在里面创建的状态会被重置。
|
|
43
|
+
*/
|
|
44
|
+
function isInsideRender(path) {
|
|
45
|
+
return renderFunctionOf(path) !== null;
|
|
46
|
+
}
|
|
47
|
+
/** path 所在的渲染函数(组件函数体返回的那个函数);不在渲染函数里时 null */
|
|
48
|
+
function renderFunctionOf(path) {
|
|
49
|
+
for (let fn = path.getFunctionParent(); fn; fn = fn.parentPath?.getFunctionParent() ?? null) {
|
|
50
|
+
const parent = fn.parentPath;
|
|
51
|
+
if (parent?.isReturnStatement()) {
|
|
52
|
+
const owner = parent.getFunctionParent();
|
|
53
|
+
if (owner && isComponentSetup(owner)) return fn;
|
|
54
|
+
}
|
|
55
|
+
if (parent?.isArrowFunctionExpression() && parent.node.body === fn.node && isComponentSetup(parent)) return fn;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
/** 是否是组件元素(大写开头或 a.B 形式的标签)的直接子节点表达式 */
|
|
60
|
+
function isComponentChild(container) {
|
|
61
|
+
const element = container.parentPath;
|
|
62
|
+
if (!container.isJSXExpressionContainer() || !element?.isJSXElement()) return false;
|
|
63
|
+
const name = element.node.openingElement.name;
|
|
64
|
+
return types.isJSXMemberExpression(name) || types.isJSXIdentifier(name) && /^[A-Z]/.test(name.name);
|
|
65
|
+
}
|
|
66
|
+
/** 是否是从 vue 导入的 watch 的监听源:watch(x, …) 或 watch([x, y], …) */
|
|
67
|
+
function isVueWatchSource(path) {
|
|
68
|
+
const parent = path.parentPath;
|
|
69
|
+
const call = parent?.isArrayExpression() ? parent.parentPath : parent;
|
|
70
|
+
if (!call?.isCallExpression()) return false;
|
|
71
|
+
const source = call.node.arguments[0];
|
|
72
|
+
if (source !== path.node && source !== parent?.node) return false;
|
|
73
|
+
const callee = call.node.callee;
|
|
74
|
+
if (!types.isIdentifier(callee)) return false;
|
|
75
|
+
const specifier = call.scope.getBinding(callee.name)?.path;
|
|
76
|
+
return !!specifier?.isImportSpecifier() && types.isIdentifier(specifier.node.imported, { name: "watch" }) && specifier.parent.source.value === "vue";
|
|
77
|
+
}
|
|
78
|
+
/** 引用出现在类型里(typeof x、类型注解):不是运行时读取,不改写 */
|
|
79
|
+
function isInTypePosition(path) {
|
|
80
|
+
return !!path.findParent((p) => p.isTSType() || p.isTSTypeQuery() || p.isTSInterfaceDeclaration());
|
|
81
|
+
}
|
|
82
|
+
/** 路径对应的节点是否仍在树上(避免对已被替换的旧路径重复改写) */
|
|
83
|
+
function isAttached(path) {
|
|
84
|
+
const container = path.container;
|
|
85
|
+
if (!container) return false;
|
|
86
|
+
return (path.listKey ? container[path.key] : container[path.key]) === path.node;
|
|
87
|
+
}
|
|
88
|
+
/** 箭头函数简写体先补成块,再在开头插入声明 */
|
|
89
|
+
function prependToBody(fn, statements) {
|
|
90
|
+
if (!statements.length) return;
|
|
91
|
+
const body = fn.get("body");
|
|
92
|
+
if (body.isBlockStatement()) body.unshiftContainer("body", statements);
|
|
93
|
+
else body.replaceWith(types.blockStatement([...statements, types.returnStatement(body.node)]));
|
|
94
|
+
}
|
|
95
|
+
/** 把标识符 x 改成 x.value(JSX 标签名改成 <x.value />,对象简写改成 { x: x.value }) */
|
|
96
|
+
function replaceWithValue(path) {
|
|
97
|
+
const node = path.node;
|
|
98
|
+
if (types.isJSXIdentifier(node)) {
|
|
99
|
+
path.replaceWith(types.jsxMemberExpression(types.jsxIdentifier(node.name), types.jsxIdentifier("value")));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (!types.isIdentifier(node)) return;
|
|
103
|
+
const parent = path.parentPath;
|
|
104
|
+
const property = parent?.isAssignmentPattern() ? parent.parentPath : parent;
|
|
105
|
+
const value = parent?.isAssignmentPattern() ? parent.node : node;
|
|
106
|
+
if (property?.isObjectProperty() && property.node.shorthand && property.node.value === value) property.node.shorthand = false;
|
|
107
|
+
path.replaceWith(types.memberExpression(types.identifier(node.name), types.identifier("value")));
|
|
108
|
+
path.skip();
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* 一次写入里,属于该绑定的所有目标标识符。
|
|
112
|
+
* 支持 x = 1、x += 1、x++、[a, b] = [b, a]、({ x } = obj)、for (x of list)
|
|
113
|
+
*/
|
|
114
|
+
function writeTargets(violation, binding) {
|
|
115
|
+
let target;
|
|
116
|
+
switch (violation.node.type) {
|
|
117
|
+
case "AssignmentExpression":
|
|
118
|
+
case "ForOfStatement":
|
|
119
|
+
case "ForInStatement":
|
|
120
|
+
target = violation.get("left");
|
|
121
|
+
break;
|
|
122
|
+
case "UpdateExpression":
|
|
123
|
+
target = violation.get("argument");
|
|
124
|
+
break;
|
|
125
|
+
default: throw fail(violation, `不支持这种方式修改响应式变量 ${binding.identifier.name}`);
|
|
126
|
+
}
|
|
127
|
+
if (target.isIdentifier()) return [target];
|
|
128
|
+
const found = [];
|
|
129
|
+
target.traverse({ Identifier(path) {
|
|
130
|
+
const parent = path.parentPath;
|
|
131
|
+
if (parent.isObjectProperty() && parent.node.key === path.node && !parent.node.computed || path.node.name !== binding.identifier.name) return;
|
|
132
|
+
if (path.scope.getBinding(path.node.name) === binding) found.push(path);
|
|
133
|
+
} });
|
|
134
|
+
return found;
|
|
135
|
+
}
|
|
136
|
+
/** 在一条变量声明里找到声明 name 的那一项 */
|
|
137
|
+
function findDeclarator(declaration, name) {
|
|
138
|
+
return declaration.node.declarations.find((d) => Object.keys(types.getBindingIdentifiers(d.id)).includes(name));
|
|
139
|
+
}
|
|
140
|
+
//#endregion
|
|
141
|
+
//#region src/compiler/runes.ts
|
|
142
|
+
/** 符文的名单与识别 */
|
|
143
|
+
/** 所有符文的全局名;只有未被本地变量遮蔽时才当作符文 */
|
|
144
|
+
const RUNE_GLOBALS = /* @__PURE__ */ new Set([
|
|
145
|
+
"_state",
|
|
146
|
+
"_derived",
|
|
147
|
+
"_effect",
|
|
148
|
+
"_bindable",
|
|
149
|
+
"_bind",
|
|
150
|
+
"_fromRef",
|
|
151
|
+
"_toRef",
|
|
152
|
+
"_untrack"
|
|
153
|
+
]);
|
|
154
|
+
/** 带成员的符文 */
|
|
155
|
+
const MEMBER_RUNES = /* @__PURE__ */ new Set([
|
|
156
|
+
"_state.raw",
|
|
157
|
+
"_state.snapshot",
|
|
158
|
+
"_derived.by",
|
|
159
|
+
"_effect.pre",
|
|
160
|
+
"_effect.root"
|
|
161
|
+
]);
|
|
162
|
+
/** _bind 的修饰符,与 Vue v-model 的修饰符一致,只用于原生输入元素 */
|
|
163
|
+
const BIND_MODIFIERS = /* @__PURE__ */ new Set([
|
|
164
|
+
"trim",
|
|
165
|
+
"number",
|
|
166
|
+
"lazy"
|
|
167
|
+
]);
|
|
168
|
+
/** 判断调用是否为某个符文(且没有被同名本地变量遮蔽) */
|
|
169
|
+
function runeOf(path) {
|
|
170
|
+
const chain = memberChain(path.node.callee);
|
|
171
|
+
if (!chain) return null;
|
|
172
|
+
const [owner, ...members] = chain;
|
|
173
|
+
if (!RUNE_GLOBALS.has(owner) || path.scope.hasBinding(owner)) return null;
|
|
174
|
+
if (!members.length) return owner;
|
|
175
|
+
if (owner === "_bind") {
|
|
176
|
+
const unknown = members.find((member) => !BIND_MODIFIERS.has(member));
|
|
177
|
+
if (unknown) throw fail(path, `不支持 _bind.${unknown},可用的修饰符只有 trim、number、lazy`);
|
|
178
|
+
return "_bind";
|
|
179
|
+
}
|
|
180
|
+
const name = `${owner}.${members.join(".")}`;
|
|
181
|
+
if (MEMBER_RUNES.has(name)) return name;
|
|
182
|
+
throw fail(path, `不支持 ${name},可用的只有 ${[...MEMBER_RUNES].join("、")} 与 _bind 的修饰符`);
|
|
183
|
+
}
|
|
184
|
+
/** a.b.c → ['a', 'b', 'c'];不是纯标识符链时返回 null */
|
|
185
|
+
function memberChain(node) {
|
|
186
|
+
if (types.isIdentifier(node)) return [node.name];
|
|
187
|
+
if (types.isMemberExpression(node) && !node.computed && types.isIdentifier(node.property)) {
|
|
188
|
+
const chain = memberChain(node.object);
|
|
189
|
+
return chain && [...chain, node.property.name];
|
|
190
|
+
}
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
//#endregion
|
|
194
|
+
//#region src/compiler/bind.ts
|
|
195
|
+
/**
|
|
196
|
+
* JSX 的双向绑定:下划线属性(_value、_open…)的值是可写的位置时编译成 Vue 的 v-model 协议;
|
|
197
|
+
* 原生输入框的修饰符写成 _bind.trim(x);Vue 指令报错并给出 TSX 写法
|
|
198
|
+
*/
|
|
199
|
+
/** Vue 指令在 TSX 里都有更自然的写法 */
|
|
200
|
+
const DIRECTIVE_HINTS = {
|
|
201
|
+
model: "双向绑定直接写下划线属性:<Switch _checked={on} />、<input _value={name} />;去空白等修饰符写 _value={_bind.trim(name)}",
|
|
202
|
+
show: "显示与隐藏请用原生属性 hidden={!visible},或条件渲染 {visible && <X />}",
|
|
203
|
+
slots: "没有插槽:内容写成子节点(children),具名内容作为 JSX prop 传入",
|
|
204
|
+
html: "请改用 innerHTML={html}",
|
|
205
|
+
text: "请直接把文本写成子节点 {text}"
|
|
206
|
+
};
|
|
207
|
+
/** 每个 JSX 属性的检查:TSX 里有更自然写法的 Vue 指令属性(v-html、v-model…)直接报错,并给出替代写法 */
|
|
208
|
+
function checkAttribute(attribute) {
|
|
209
|
+
const { name } = attribute.node;
|
|
210
|
+
const full = types.isJSXNamespacedName(name) ? `${name.namespace.name}:${name.name.name}` : name.name;
|
|
211
|
+
if (!full.startsWith("v-")) return;
|
|
212
|
+
const directive = full.slice(2).split(/[:_]/)[0];
|
|
213
|
+
throw fail(attribute, DIRECTIVE_HINTS[directive] ?? `不支持 Vue 指令 ${full}:请改用 ref 回调或组合式函数`);
|
|
214
|
+
}
|
|
215
|
+
/** 第 1 步:_bind 只剩原生输入框的修饰符写法(_bind.trim / .number / .lazy,可以连写) */
|
|
216
|
+
function checkBindCall(call) {
|
|
217
|
+
if (memberChain(call.node.callee).length > 1) return;
|
|
218
|
+
if (call.node.arguments.length === 2) throw fail(call, "不再需要 _bind(get, set):写成 _value={取值} onUpdate:_value={(next) => …},写了 onUpdate:_value 就由它处理更新");
|
|
219
|
+
throw fail(call, "不用再写 _bind:下划线属性的值是可写的变量或对象属性时自动双向绑定,直接写 _value={x};_bind 只用于原生输入框的修饰符(_bind.trim / .number / .lazy)");
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* 下划线属性的值是可写的位置时包成 _bind(x),之后与修饰符写法一起按 rewriteBind 改写(第 1 步之后、建立绑定之前):
|
|
223
|
+
* <Switch _checked={on} /> → 双向(on 是 let 声明的 _state)
|
|
224
|
+
* <TextField _value={form.name} /> → 双向(对象属性)
|
|
225
|
+
* <Select _value="a" /> <Steps _current={step + 1} /> → 单向:字面量、表达式、函数调用、const 派生、普通 prop
|
|
226
|
+
* 组件上另写了 onUpdate:_x(或展开的对象里有这个键)时由它处理更新,不再自动写回。
|
|
227
|
+
* 原生元素的 _value、_checked、<details> 的 _open、文件输入的 _files 只用于绑定,值不可写时报错;
|
|
228
|
+
* <input type="number"> 与 type="range" 的 _value 自动按数字读回(同 Svelte)。返回是否包过:包过要重建作用域
|
|
229
|
+
*/
|
|
230
|
+
function wrapAutoBinds(program, writable) {
|
|
231
|
+
let wrapped = false;
|
|
232
|
+
program.traverse({ JSXAttribute(attribute) {
|
|
233
|
+
const { name } = attribute.node;
|
|
234
|
+
if (!types.isJSXIdentifier(name) || !name.name.startsWith("_")) return;
|
|
235
|
+
const opening = attribute.parent;
|
|
236
|
+
const native = isNativeTag(opening.name);
|
|
237
|
+
const value = attribute.get("value");
|
|
238
|
+
if (!value.isJSXExpressionContainer()) {
|
|
239
|
+
if (native) throw fail(attribute, nativeMessage(name.name));
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const expression = value.get("expression");
|
|
243
|
+
if (expression.isJSXEmptyExpression()) return;
|
|
244
|
+
if (expression.isCallExpression() && memberChain(expression.node.callee)?.[0] === "_bind") return;
|
|
245
|
+
if (!native && hasUpdateHandler(opening, name.name)) return;
|
|
246
|
+
const target = unwrapTypes$1(expression);
|
|
247
|
+
if (!isWritableTarget(target, writable)) {
|
|
248
|
+
if (native) throw fail(attribute, nativeMessage(name.name));
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const callee = native && name.name === "_value" && isNumericInput(opening) ? types.memberExpression(types.identifier("_bind"), types.identifier("number")) : types.identifier("_bind");
|
|
252
|
+
expression.replaceWith(types.callExpression(callee, [target.node]));
|
|
253
|
+
wrapped = true;
|
|
254
|
+
} });
|
|
255
|
+
return wrapped;
|
|
256
|
+
}
|
|
257
|
+
const isNativeTag = (tag) => types.isJSXIdentifier(tag) && /^[a-z]/.test(tag.name);
|
|
258
|
+
const nativeMessage = (name) => `原生元素的 ${name} 用于双向绑定,值要是可写的变量或对象属性;只给值时写 ${name.slice(1)}={…}`;
|
|
259
|
+
/** 去掉只给类型检查看的外壳:x!、x as T、x satisfies T、(x) */
|
|
260
|
+
function unwrapTypes$1(path) {
|
|
261
|
+
let current = path;
|
|
262
|
+
while (current.isTSNonNullExpression() || current.isTSAsExpression() || current.isTSSatisfiesExpression() || current.isTSTypeAssertion() || current.isParenthesizedExpression()) current = current.get("expression");
|
|
263
|
+
return current;
|
|
264
|
+
}
|
|
265
|
+
/** 可写的位置:能整体赋值的响应式变量,或对象属性(form.name、rows[i].qty;可选链 a?.b 不能赋值) */
|
|
266
|
+
function isWritableTarget(path, writable) {
|
|
267
|
+
if (path.isMemberExpression()) return true;
|
|
268
|
+
return path.isIdentifier() && writable(path);
|
|
269
|
+
}
|
|
270
|
+
/** 元素上写了 onUpdate:_x:属性 onUpdate:_x={…},或展开的对象字面量里有 'onUpdate:_x' 键 */
|
|
271
|
+
function hasUpdateHandler(opening, prop) {
|
|
272
|
+
const event = `onUpdate:${prop}`;
|
|
273
|
+
return opening.attributes.some((attribute) => {
|
|
274
|
+
if (types.isJSXAttribute(attribute)) {
|
|
275
|
+
const { name } = attribute;
|
|
276
|
+
return types.isJSXNamespacedName(name) && name.namespace.name === "onUpdate" && name.name.name === prop;
|
|
277
|
+
}
|
|
278
|
+
return types.isObjectExpression(attribute.argument) && attribute.argument.properties.some((property) => types.isObjectProperty(property) && types.isStringLiteral(property.key, { value: event }));
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
/** <input type="number"> 或 type="range":读回的值按数字 */
|
|
282
|
+
function isNumericInput(opening) {
|
|
283
|
+
if (!types.isJSXIdentifier(opening.name, { name: "input" })) return false;
|
|
284
|
+
return opening.attributes.some((attribute) => types.isJSXAttribute(attribute) && types.isJSXIdentifier(attribute.name, { name: "type" }) && types.isStringLiteral(attribute.value) && (attribute.value.value === "number" || attribute.value.value === "range"));
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* _属性={_bind(x)} → Vue 的 v-model 协议(此时 x 已改写为 x.value);_bind(x) 由 wrapAutoBinds 包上,
|
|
288
|
+
* 修饰符写法 _bind.trim(x) 由作者写:
|
|
289
|
+
* <Switch _checked={on} /> → <Switch _checked={on.value} onUpdate:_checked={($event) => (on.value = $event)} />
|
|
290
|
+
* <input _value={_bind.trim(name)} /> → <input v-model_trim={name.value} />
|
|
291
|
+
* 可双向绑定的属性一律以下划线开头(组件的 _bindable prop 本身就叫 _value、_open;原生元素的 _value、_checked、
|
|
292
|
+
* <details> 的 _open、文件输入的 _files 是绑定专用的写法),一眼看出哪些属性能双向绑定。
|
|
293
|
+
* 组件直接写出值与更新回调(同 vue-jsx 对 v-model:x 的输出),回调就能被渲染优化缓存;
|
|
294
|
+
* 原生输入框仍交给 vue-jsx 的 v-model 指令。属性名就是 prop 本身,补全、跳转、重命名都由 TypeScript 原生支持。
|
|
295
|
+
*/
|
|
296
|
+
function rewriteBind(call) {
|
|
297
|
+
const container = call.parentPath;
|
|
298
|
+
const attribute = container.parentPath;
|
|
299
|
+
if (!container.isJSXExpressionContainer() || !attribute?.isJSXAttribute() || !types.isJSXIdentifier(attribute.node.name)) throw fail(call, "_bind 的修饰符只能写在 JSX 属性的值里,例如 <input _value={_bind.trim(name)} />");
|
|
300
|
+
const name = attribute.node.name.name;
|
|
301
|
+
if (!name.startsWith("_")) throw fail(attribute, `可双向绑定的属性以下划线开头:写成 _${name}={…}`);
|
|
302
|
+
const [target, ...extra] = call.node.arguments;
|
|
303
|
+
if (!types.isMemberExpression(target) || extra.length) throw fail(call, "_bind 修饰符的参数必须是 let 声明的响应式变量(_state / _bindable / _fromRef)或对象属性,例如 _bind.trim(form.name)");
|
|
304
|
+
const prop = name;
|
|
305
|
+
const modifiers = memberChain(call.node.callee).slice(1);
|
|
306
|
+
const tag = attribute.parent.name;
|
|
307
|
+
if (isNativeTag(tag)) {
|
|
308
|
+
const property = prop.slice(1);
|
|
309
|
+
const native = NATIVE_BINDINGS[property];
|
|
310
|
+
if (native && native.tag !== tag.name) throw fail(attribute, `${prop} 的绑定只能写在 <${native.tag}> 上`);
|
|
311
|
+
if (native) {
|
|
312
|
+
if (modifiers.length) throw fail(call, `${prop} 的绑定没有修饰符`);
|
|
313
|
+
bindNativeProperty(call, attribute, target, property, native);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (property !== "value" && property !== "checked") throw fail(attribute, `原生元素能绑定 _value、_checked(多选组绑到数组)、<details> 的 _open、文件输入的 _files,不能绑定 ${prop};单选组请用 RadioGroup,或 checked={x === 值} 加 onChange`);
|
|
317
|
+
attribute.node.name = types.jsxIdentifier(["v-model", ...modifiers].join("_"));
|
|
318
|
+
} else {
|
|
319
|
+
if (modifiers.length) throw fail(call, "_bind 的修饰符只用于原生输入元素");
|
|
320
|
+
const event = types.identifier("$event");
|
|
321
|
+
const update = types.arrowFunctionExpression([event], types.assignmentExpression("=", types.cloneNode(target), types.identifier("$event")));
|
|
322
|
+
attribute.insertAfter(types.jsxAttribute(types.jsxNamespacedName(types.jsxIdentifier("onUpdate"), types.jsxIdentifier(prop)), types.jsxExpressionContainer(update)));
|
|
323
|
+
}
|
|
324
|
+
call.replaceWith(target);
|
|
325
|
+
}
|
|
326
|
+
/** v-model 以外、原生元素能绑定的属性:只能写在哪个标签上,元素上哪个事件之后读回,读回时能不能写到元素上 */
|
|
327
|
+
const NATIVE_BINDINGS = {
|
|
328
|
+
open: {
|
|
329
|
+
tag: "details",
|
|
330
|
+
event: "onToggle",
|
|
331
|
+
readonly: false
|
|
332
|
+
},
|
|
333
|
+
files: {
|
|
334
|
+
tag: "input",
|
|
335
|
+
event: "onChange",
|
|
336
|
+
readonly: true
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
/**
|
|
340
|
+
* 原生元素上 v-model 管不到的属性:值照常交给元素(只读的不交),元素的事件之后把属性读回变量:
|
|
341
|
+
* <details _open={expanded} /> → <details open={expanded.value} onToggle={(e) => (expanded.value = e.currentTarget.open)} />
|
|
342
|
+
* <input type="file" _files={picked} /> → <input type="file" onChange={(e) => (picked.value = e.currentTarget.files)} />
|
|
343
|
+
*/
|
|
344
|
+
function bindNativeProperty(call, attribute, target, prop, native) {
|
|
345
|
+
if (attribute.parent.attributes.some((other) => types.isJSXAttribute(other) && types.isJSXIdentifier(other.name, { name: native.event }))) throw fail(attribute, `_${prop} 的绑定要占用 ${native.event}:请去掉 ${native.event},或改写 ${prop}={…} 并在 ${native.event} 里自己读回 ${prop}`);
|
|
346
|
+
const event = types.identifier("event");
|
|
347
|
+
const update = types.arrowFunctionExpression([event], types.assignmentExpression("=", types.cloneNode(target), types.memberExpression(types.memberExpression(event, types.identifier("currentTarget")), types.identifier(prop))));
|
|
348
|
+
attribute.insertAfter(types.jsxAttribute(types.jsxIdentifier(native.event), types.jsxExpressionContainer(update)));
|
|
349
|
+
if (native.readonly) attribute.remove();
|
|
350
|
+
else {
|
|
351
|
+
attribute.node.name = types.jsxIdentifier(prop);
|
|
352
|
+
call.replaceWith(target);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
//#endregion
|
|
356
|
+
//#region src/compiler/imports.ts
|
|
357
|
+
/** 编译产物用到的运行时函数:按需生成不冲突的本地名,最后每个模块合成一条 import */
|
|
358
|
+
/** 编译产物里 prop / bindable 等运行时函数的来源 */
|
|
359
|
+
const RUNTIME_MODULE = "@zerodep-dev/vue-tsx-pro-tsx";
|
|
360
|
+
/** 编译时用到的 Vue 与本包运行时函数:按需生成带前缀的本地名,最后统一插入 import */
|
|
361
|
+
var RuntimeImports = class {
|
|
362
|
+
/** `模块:名称` → 导入项;uses 是生成的调用数,换成别的函数后为 0 的不导入 */
|
|
363
|
+
entries = /* @__PURE__ */ new Map();
|
|
364
|
+
program;
|
|
365
|
+
constructor(program) {
|
|
366
|
+
this.program = program;
|
|
367
|
+
}
|
|
368
|
+
/** 调用 Vue 的响应式 API:vue('ref', [init]) → _vue_ref(init) */
|
|
369
|
+
vue(name, args) {
|
|
370
|
+
return this.call("vue", name, args);
|
|
371
|
+
}
|
|
372
|
+
/** 调用本包的运行时函数:runtime('prop', [...]) → _vtp_prop(...) */
|
|
373
|
+
runtime(name, args) {
|
|
374
|
+
return this.call(RUNTIME_MODULE, name, args);
|
|
375
|
+
}
|
|
376
|
+
/** 把 runtime() 生成的调用换成另一个运行时函数(参数不变) */
|
|
377
|
+
retarget(call, from, to) {
|
|
378
|
+
this.entries.get(`${RUNTIME_MODULE}:${from}`).uses--;
|
|
379
|
+
call.callee = this.runtime(to, []).callee;
|
|
380
|
+
}
|
|
381
|
+
/** 把用到的导入按模块插到文件开头;所有改写完成后调用一次 */
|
|
382
|
+
insert() {
|
|
383
|
+
const byModule = /* @__PURE__ */ new Map();
|
|
384
|
+
for (const { module, name, local, uses } of this.entries.values()) {
|
|
385
|
+
if (!uses) continue;
|
|
386
|
+
const specifiers = byModule.get(module) ?? [];
|
|
387
|
+
specifiers.push(types.importSpecifier(types.identifier(local.name), types.identifier(name)));
|
|
388
|
+
byModule.set(module, specifiers);
|
|
389
|
+
}
|
|
390
|
+
for (const [module, specifiers] of byModule) this.program.unshiftContainer("body", types.importDeclaration(specifiers, types.stringLiteral(module)));
|
|
391
|
+
}
|
|
392
|
+
call(module, name, args) {
|
|
393
|
+
const id = `${module}:${name}`;
|
|
394
|
+
let entry = this.entries.get(id);
|
|
395
|
+
if (!entry) {
|
|
396
|
+
const prefix = module === "vue" ? "vue" : "vtp";
|
|
397
|
+
entry = {
|
|
398
|
+
module,
|
|
399
|
+
name,
|
|
400
|
+
local: this.program.scope.generateUidIdentifier(`${prefix}_${name}`),
|
|
401
|
+
uses: 0
|
|
402
|
+
};
|
|
403
|
+
this.entries.set(id, entry);
|
|
404
|
+
}
|
|
405
|
+
entry.uses++;
|
|
406
|
+
return types.callExpression(types.identifier(entry.local.name), args);
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
//#endregion
|
|
410
|
+
//#region src/compiler/optimize.ts
|
|
411
|
+
/**
|
|
412
|
+
* 第 6 步:渲染优化。JSX 交给 vue-jsx 后没有模板编译器的那几项优化,父组件一重新渲染,
|
|
413
|
+
* 子组件就跟着重新渲染(插槽每次都是新的、没有 $stable;内联函数每次都是新的,props 比较时总算“变了”)。
|
|
414
|
+
* 这里在不改变语义的前提下补上两项:
|
|
415
|
+
*
|
|
416
|
+
* 1. 稳定插槽:组件的子节点只用到“不会随这次渲染变”的变量时,插槽标 $stable,
|
|
417
|
+
* 父组件因为别的状态重新渲染时,子组件不再跟着重做(内容里读到的响应式数据由子组件自己追踪)。
|
|
418
|
+
* <Box><Btn label="保存" /></Box>
|
|
419
|
+
* → <Box>{{ default: () => [<Btn label="保存" />], $stable: true }}</Box>
|
|
420
|
+
* 2. 缓存:JSX 属性里的函数与常量数组、对象,只用到“不会随这次渲染变”的变量时,每个实例只创建一次。
|
|
421
|
+
* onPress={() => n++} → onPress={_cache[0] || (_cache[0] = () => n.value++)}
|
|
422
|
+
*
|
|
423
|
+
* “这次渲染才有的值”(渲染函数里的变量、map 回调的参数)一旦用到就不优化,保持 vue-jsx 原来的输出。
|
|
424
|
+
* 稳定插槽还要求内容不读非响应式的可变数据(普通 let、...rest、字面量初始化的 const):
|
|
425
|
+
* 这类数据变了不会通知任何组件,以前靠父组件碰巧重新渲染才刷新。
|
|
426
|
+
*/
|
|
427
|
+
/** 子节点不能当插槽对象传的内置组件 */
|
|
428
|
+
const VUE_BUILTINS = /* @__PURE__ */ new Set([
|
|
429
|
+
"Teleport",
|
|
430
|
+
"KeepAlive",
|
|
431
|
+
"Suspense"
|
|
432
|
+
]);
|
|
433
|
+
/** 第 6 步的入口:稳定插槽与属性值缓存,改动直接写在 AST 上(规则见文件开头) */
|
|
434
|
+
function optimizeRender(program, context) {
|
|
435
|
+
const caches = /* @__PURE__ */ new Map();
|
|
436
|
+
const summaries = /* @__PURE__ */ new WeakMap();
|
|
437
|
+
let sites = 0;
|
|
438
|
+
const siteKey = () => `__site${sites++}`;
|
|
439
|
+
/** setup 里的缓存数组:用到时才声明 */
|
|
440
|
+
const cacheSlot = (setup, value) => {
|
|
441
|
+
let cache = caches.get(setup);
|
|
442
|
+
if (!cache) {
|
|
443
|
+
cache = {
|
|
444
|
+
id: setup.scope.generateUidIdentifier("cache"),
|
|
445
|
+
size: 0
|
|
446
|
+
};
|
|
447
|
+
caches.set(setup, cache);
|
|
448
|
+
}
|
|
449
|
+
const slot = types.memberExpression(types.identifier(cache.id.name), types.numericLiteral(cache.size++), true);
|
|
450
|
+
return types.logicalExpression("||", slot, types.assignmentExpression("=", types.cloneNode(slot), value));
|
|
451
|
+
};
|
|
452
|
+
program.traverse({
|
|
453
|
+
JSXAttribute(path) {
|
|
454
|
+
cacheAttributeValue(path, context, cacheSlot);
|
|
455
|
+
},
|
|
456
|
+
JSXElement: { exit(path) {
|
|
457
|
+
const external = externalReferences(path, summaries).filter((binding) => binding === null || !declaredWithin(binding, path));
|
|
458
|
+
stabilizeChildren(path, context, summaries, siteKey);
|
|
459
|
+
summaries.set(path.node, external);
|
|
460
|
+
} }
|
|
461
|
+
});
|
|
462
|
+
for (const [setup, cache] of caches) prependToBody(setup, [types.variableDeclaration("const", [types.variableDeclarator(types.identifier(cache.id.name), types.arrayExpression([]))])]);
|
|
463
|
+
}
|
|
464
|
+
/** attr={() => …}、attr={[a, b]}、attr={{ … }}:只用到实例内不变的值时,每个实例只创建一次 */
|
|
465
|
+
function cacheAttributeValue(attribute, context, cacheSlot) {
|
|
466
|
+
const container = attribute.get("value");
|
|
467
|
+
if (!container.isJSXExpressionContainer()) return;
|
|
468
|
+
const value = container.get("expression");
|
|
469
|
+
if (!(value.isArrowFunctionExpression() || value.isFunctionExpression()) && !value.isArrayExpression() && !value.isObjectExpression()) return;
|
|
470
|
+
const setup = enclosingSetup(attribute);
|
|
471
|
+
if (!setup) return;
|
|
472
|
+
if (!isCacheable(value, setup, context)) return;
|
|
473
|
+
value.replaceWith(cacheSlot(setup, value.node));
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* 函数:闭包读的都是活的绑定,只要不用到这次渲染才有的变量就能缓存。
|
|
477
|
+
* 数组、对象:直接读到的值(不在函数里的)必须是不会变的常量;符文的值会变,不能缓存。
|
|
478
|
+
* 常量本身不变,它里面的属性却可能变(组合式函数返回的 store、导入的 _state 对象):
|
|
479
|
+
* 读它的成员、把它展开都不缓存,只有 css.create 的样式表与命名空间导入的一层成员例外
|
|
480
|
+
*/
|
|
481
|
+
function isCacheable(value, setup, context) {
|
|
482
|
+
let cacheable = true;
|
|
483
|
+
forEachReference(value, setup, context, (kind, insideFunction, reference) => {
|
|
484
|
+
if (kind === "local") cacheable = false;
|
|
485
|
+
else if (insideFunction) return;
|
|
486
|
+
else if (kind !== "constant") cacheable = false;
|
|
487
|
+
else if (readsInside(reference) && !isFrozenRead(reference)) cacheable = false;
|
|
488
|
+
});
|
|
489
|
+
if (cacheable && !value.isFunction()) value.traverse({
|
|
490
|
+
Function(inner) {
|
|
491
|
+
inner.skip();
|
|
492
|
+
},
|
|
493
|
+
"CallExpression|NewExpression|TaggedTemplateExpression|UpdateExpression|AssignmentExpression|AwaitExpression|YieldExpression"() {
|
|
494
|
+
cacheable = false;
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
return cacheable;
|
|
498
|
+
}
|
|
499
|
+
/** 读的是常量里面的东西:obj.x、obj?.x、...obj */
|
|
500
|
+
function readsInside(reference) {
|
|
501
|
+
const parent = reference.parentPath;
|
|
502
|
+
if (!parent) return false;
|
|
503
|
+
if (parent.isSpreadElement()) return true;
|
|
504
|
+
return (parent.isMemberExpression() || parent.isOptionalMemberExpression()) && parent.node.object === reference.node;
|
|
505
|
+
}
|
|
506
|
+
/** 样式入口里结果不再变的工厂 */
|
|
507
|
+
const STYLE_FACTORIES = /* @__PURE__ */ new Set([
|
|
508
|
+
"create",
|
|
509
|
+
"createTheme",
|
|
510
|
+
"defineVars",
|
|
511
|
+
"defineMarker",
|
|
512
|
+
"recipe"
|
|
513
|
+
]);
|
|
514
|
+
/**
|
|
515
|
+
* 读的成员不会变:同一文件里 css.create(等)的结果;命名空间导入(import * as icons)的一层成员。
|
|
516
|
+
* 再往下一层(icons.X.y)、或别的常量的成员都可能会变
|
|
517
|
+
*/
|
|
518
|
+
function isFrozenRead(reference) {
|
|
519
|
+
if (!reference.isIdentifier()) return false;
|
|
520
|
+
const binding = reference.scope.getBinding(reference.node.name);
|
|
521
|
+
if (!binding) return false;
|
|
522
|
+
const member = reference.parentPath;
|
|
523
|
+
if (binding.path.isImportNamespaceSpecifier()) {
|
|
524
|
+
const outer = member.parentPath;
|
|
525
|
+
const deeper = (outer?.isMemberExpression() || outer?.isOptionalMemberExpression()) && outer.node.object === member.node;
|
|
526
|
+
return member.isMemberExpression() && !deeper;
|
|
527
|
+
}
|
|
528
|
+
const init = binding.path.isVariableDeclarator() ? unwrapTypes(binding.path.node.init) : null;
|
|
529
|
+
if (types.isObjectExpression(init) || types.isArrayExpression(init)) return true;
|
|
530
|
+
if (!types.isCallExpression(init) || !types.isMemberExpression(init.callee)) return false;
|
|
531
|
+
const { object, property } = init.callee;
|
|
532
|
+
if (!types.isIdentifier(property) || !STYLE_FACTORIES.has(property.name)) return false;
|
|
533
|
+
if (!types.isIdentifier(object)) return false;
|
|
534
|
+
const source = binding.scope.getBinding(object.name)?.path.parent;
|
|
535
|
+
return types.isImportDeclaration(source) && source.source.value === "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
536
|
+
}
|
|
537
|
+
/** 组件元素的子节点改成显式的插槽对象,按用到的变量标 $stable */
|
|
538
|
+
function stabilizeChildren(element, context, summaries, siteKey) {
|
|
539
|
+
if (!isComponentElement(element)) return;
|
|
540
|
+
if (element.node.children.some((child) => types.isJSXSpreadChild(child))) return;
|
|
541
|
+
const children = types.react.buildChildren(element.node);
|
|
542
|
+
if (!children.length) return;
|
|
543
|
+
if (children.length === 1 && types.isObjectExpression(children[0])) return;
|
|
544
|
+
const setup = enclosingSetup(element);
|
|
545
|
+
const childPaths = element.get("children");
|
|
546
|
+
let dynamic = false;
|
|
547
|
+
let usesChildren = false;
|
|
548
|
+
for (const child of childPaths) {
|
|
549
|
+
if (child.isJSXText()) continue;
|
|
550
|
+
for (const binding of externalReferences(child, summaries)) {
|
|
551
|
+
if (binding && declaredWithin(binding, child)) continue;
|
|
552
|
+
const kind = binding ? kindOf(binding, setup, context) : "local";
|
|
553
|
+
if (kind === "local" || kind === "mutable") dynamic = true;
|
|
554
|
+
if (kind === "children") usesChildren = true;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (dynamic) return;
|
|
558
|
+
const only = children.length === 1 ? children[0] : void 0;
|
|
559
|
+
let slots;
|
|
560
|
+
if (only && isChildrenRead(only, element, context)) {
|
|
561
|
+
if (!setup) return;
|
|
562
|
+
slots = context.imports.runtime("forwardChildren", [propsOf(setup)]);
|
|
563
|
+
slots.loc = element.node.loc;
|
|
564
|
+
} else {
|
|
565
|
+
if (only && types.isIdentifier(only) && isImported(only, element)) return;
|
|
566
|
+
const content = only && (isFunctionNode(only) || bindsFunction(only, element)) ? only : types.arrowFunctionExpression([], types.arrayExpression(children));
|
|
567
|
+
const stable = usesChildren ? context.imports.runtime("childrenStable", []) : types.booleanLiteral(true);
|
|
568
|
+
slots = types.objectExpression([types.objectProperty(types.identifier("default"), content), types.objectProperty(types.identifier("$stable"), stable)]);
|
|
569
|
+
if (setup) keyBySite(element, setup, siteKey);
|
|
570
|
+
}
|
|
571
|
+
element.node.children = [types.jsxExpressionContainer(slots)];
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* 标了 $stable 的组件按 JSX 的位置给一个 key:两处同类型的组件出现在同一位置时(`cond ? <Grid>A</Grid> : <Grid>B</Grid>`、
|
|
575
|
+
* 分支里的第 n 个子元素),没有 key 会被当成同一个节点更新,稳定插槽又让子组件跳过重渲染,显示的还是另一处的内容。
|
|
576
|
+
* 模板编译器给 v-if 的各个分支加 key 是同样的道理。只给渲染函数里直接写的元素加:回调(map)、循环里的元素会重复出现,
|
|
577
|
+
* 同一位置的 key 相同会冲突;已经写了 key 的不动
|
|
578
|
+
*/
|
|
579
|
+
function keyBySite(element, setup, siteKey) {
|
|
580
|
+
const attributes = element.node.openingElement.attributes;
|
|
581
|
+
if (attributes.some((attribute) => types.isJSXAttribute(attribute) && attribute.name.name === "key")) return;
|
|
582
|
+
const render = element.getFunctionParent();
|
|
583
|
+
if (!render || !isRenderFunction(render, setup)) return;
|
|
584
|
+
if (element.findParent((parent) => parent.node === render.node || parent.isLoop())?.node !== render.node) return;
|
|
585
|
+
attributes.push(types.jsxAttribute(types.jsxIdentifier("key"), types.stringLiteral(siteKey())));
|
|
586
|
+
}
|
|
587
|
+
/** setup 返回的渲染函数:`return () => …`,或 setup 直接写成 `() => () => …` */
|
|
588
|
+
function isRenderFunction(fn, setup) {
|
|
589
|
+
if (fn.parentPath?.node === setup.node) return true;
|
|
590
|
+
return fn.parentPath?.isReturnStatement() === true && fn.getFunctionParent()?.node === setup.node;
|
|
591
|
+
}
|
|
592
|
+
const isFunctionNode = (node) => types.isArrowFunctionExpression(node) || types.isFunctionExpression(node);
|
|
593
|
+
/** 标识符绑定到函数:const renderRow = (row) => …、function renderRow() {} */
|
|
594
|
+
function bindsFunction(node, element) {
|
|
595
|
+
if (!types.isIdentifier(node)) return false;
|
|
596
|
+
const binding = element.scope.getBinding(node.name);
|
|
597
|
+
if (!binding?.constant) return false;
|
|
598
|
+
if (binding.path.isFunctionDeclaration()) return true;
|
|
599
|
+
const init = binding.path.isVariableDeclarator() ? binding.path.node.init : null;
|
|
600
|
+
return !!init && isFunctionNode(init);
|
|
601
|
+
}
|
|
602
|
+
/** 标识符来自 import */
|
|
603
|
+
function isImported(node, element) {
|
|
604
|
+
const path = element.scope.getBinding(node.name)?.path;
|
|
605
|
+
return !!path && (path.isImportSpecifier() || path.isImportDefaultSpecifier() || path.isImportNamespaceSpecifier());
|
|
606
|
+
}
|
|
607
|
+
/** 大写开头或 a.B 形式的标签;Vue 的 Teleport、KeepAlive、Suspense 子节点不是插槽 */
|
|
608
|
+
function isComponentElement(element) {
|
|
609
|
+
const name = element.node.openingElement.name;
|
|
610
|
+
if (types.isJSXMemberExpression(name)) return true;
|
|
611
|
+
if (!types.isJSXIdentifier(name) || !/^[A-Z]/.test(name.name)) return false;
|
|
612
|
+
const specifier = element.scope.getBinding(name.name)?.path;
|
|
613
|
+
if (specifier?.isImportSpecifier() && specifier.parent.source.value === "vue" && types.isIdentifier(specifier.node.imported) && VUE_BUILTINS.has(specifier.node.imported.name)) return false;
|
|
614
|
+
return !VUE_BUILTINS.has(name.name);
|
|
615
|
+
}
|
|
616
|
+
/** 子节点就是 children(第 3 步已改写成 unref(children)) */
|
|
617
|
+
function isChildrenRead(node, element, context) {
|
|
618
|
+
const read = types.isCallExpression(node) && node.arguments.length === 1 ? node.arguments[0] : node;
|
|
619
|
+
if (!types.isIdentifier(read)) return false;
|
|
620
|
+
const binding = element.scope.getBinding(read.name);
|
|
621
|
+
return !!binding && !!context.tracked.get(binding)?.childrenOf;
|
|
622
|
+
}
|
|
623
|
+
/** setup 的第一个参数(第 1 步生成的 __props) */
|
|
624
|
+
function propsOf(setup) {
|
|
625
|
+
const first = setup.node.params[0];
|
|
626
|
+
return types.isIdentifier(first) ? types.identifier(first.name) : types.objectExpression([]);
|
|
627
|
+
}
|
|
628
|
+
/** 所在的组件 setup(_component 的参数函数);不在组件里时为 null */
|
|
629
|
+
function enclosingSetup(path) {
|
|
630
|
+
for (let fn = path.getFunctionParent(); fn; fn = fn.parentPath?.getFunctionParent() ?? null) if (isComponentSetup(fn)) return fn;
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* 子树里引用的变量(含 this,记为 null;全局变量不算)。已经汇总过的元素用它的汇总、不再进去,
|
|
635
|
+
* 所以每个引用只在声明它的地方以内逐层往上传,不会被每一层祖先各遍历一次
|
|
636
|
+
*/
|
|
637
|
+
function externalReferences(path, summaries) {
|
|
638
|
+
const own = summaries.get(path.node);
|
|
639
|
+
if (own) return [...own];
|
|
640
|
+
const found = [];
|
|
641
|
+
const add = (reference, name) => {
|
|
642
|
+
const binding = reference.scope.getBinding(name);
|
|
643
|
+
if (binding) found.push(binding);
|
|
644
|
+
};
|
|
645
|
+
if (path.isIdentifier()) add(path, path.node.name);
|
|
646
|
+
path.traverse({
|
|
647
|
+
JSXElement(inner) {
|
|
648
|
+
const summary = summaries.get(inner.node);
|
|
649
|
+
if (!summary) return;
|
|
650
|
+
found.push(...summary);
|
|
651
|
+
inner.skip();
|
|
652
|
+
},
|
|
653
|
+
Identifier(reference) {
|
|
654
|
+
if (reference.isReferencedIdentifier()) add(reference, reference.node.name);
|
|
655
|
+
},
|
|
656
|
+
JSXIdentifier(reference) {
|
|
657
|
+
if (reference.isReferencedIdentifier()) add(reference, reference.node.name);
|
|
658
|
+
},
|
|
659
|
+
ThisExpression() {
|
|
660
|
+
found.push(null);
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
return found;
|
|
664
|
+
}
|
|
665
|
+
/** 变量是不是在 path 里面声明的:有源码位置时比较位置(不用逐层往上找),生成的节点退回 isDescendant */
|
|
666
|
+
function declaredWithin(binding, path) {
|
|
667
|
+
const scope = binding.scope.path.node;
|
|
668
|
+
const outer = path.node;
|
|
669
|
+
if (scope.start != null && scope.end != null && outer.start != null && outer.end != null) return scope.start >= outer.start && scope.end <= outer.end;
|
|
670
|
+
return isWithin(binding.scope.path, path);
|
|
671
|
+
}
|
|
672
|
+
/** 表达式用到的外部变量逐个回调:变量的性质、引用是否在表达式里的函数中,以及引用本身 */
|
|
673
|
+
function forEachReference(expression, setup, context, visit) {
|
|
674
|
+
const check = (reference, name) => {
|
|
675
|
+
const binding = reference.scope.getBinding(name);
|
|
676
|
+
if (!binding) return;
|
|
677
|
+
if (isWithin(binding.scope.path, expression)) return;
|
|
678
|
+
visit(kindOf(binding, setup, context), isInsideFunction(reference, expression), reference);
|
|
679
|
+
};
|
|
680
|
+
if (expression.isIdentifier()) check(expression, expression.node.name);
|
|
681
|
+
if (expression.isThisExpression()) visit("local", false, expression);
|
|
682
|
+
expression.traverse({
|
|
683
|
+
Identifier(reference) {
|
|
684
|
+
if (reference.isReferencedIdentifier()) check(reference, reference.node.name);
|
|
685
|
+
},
|
|
686
|
+
JSXIdentifier(reference) {
|
|
687
|
+
if (reference.isReferencedIdentifier()) check(reference, reference.node.name);
|
|
688
|
+
},
|
|
689
|
+
ThisExpression(reference) {
|
|
690
|
+
visit("local", isInsideFunction(reference, expression), reference);
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
function kindOf(binding, setup, context) {
|
|
695
|
+
const tracked = context.tracked.get(binding);
|
|
696
|
+
if (tracked) return tracked.childrenOf ? "children" : "reactive";
|
|
697
|
+
if (context.rest.has(binding)) return "mutable";
|
|
698
|
+
const owner = binding.scope.getFunctionParent()?.path ?? null;
|
|
699
|
+
if (owner === null || setup !== null && isWithin(setup, owner) && owner !== setup) return binding.constant ? "constant" : "mutable";
|
|
700
|
+
if (owner !== setup) return "local";
|
|
701
|
+
if (binding.kind === "param") return binding.path.node === setup?.node.params[0] ? "children" : "mutable";
|
|
702
|
+
if (!binding.constant) return "mutable";
|
|
703
|
+
if (binding.kind === "hoisted" || binding.kind === "module") return "constant";
|
|
704
|
+
const declarator = binding.path;
|
|
705
|
+
const init = declarator.isVariableDeclarator() ? unwrapTypes(declarator.node.init) : null;
|
|
706
|
+
if (types.isObjectExpression(init) || types.isArrayExpression(init) || types.isNewExpression(init)) return "mutable";
|
|
707
|
+
return "constant";
|
|
708
|
+
}
|
|
709
|
+
/** 去掉只影响类型的外壳:[] as T[]、{…} satisfies S、x!、(x) */
|
|
710
|
+
function unwrapTypes(node) {
|
|
711
|
+
let current = node;
|
|
712
|
+
while (types.isTSAsExpression(current) || types.isTSSatisfiesExpression(current) || types.isTSNonNullExpression(current) || types.isTSTypeAssertion(current) || types.isParenthesizedExpression(current)) current = current.expression;
|
|
713
|
+
return current;
|
|
714
|
+
}
|
|
715
|
+
/** inner 是否就是 outer 或在它里面 */
|
|
716
|
+
function isWithin(inner, outer) {
|
|
717
|
+
return inner === outer || inner.isDescendant(outer);
|
|
718
|
+
}
|
|
719
|
+
/** 引用是否在表达式里的某个函数中(调用时才读取) */
|
|
720
|
+
function isInsideFunction(reference, expression) {
|
|
721
|
+
for (let path = reference.parentPath; path && path !== expression; path = path.parentPath) if (path.isFunction()) return true;
|
|
722
|
+
return expression.isFunction() && reference !== expression;
|
|
723
|
+
}
|
|
724
|
+
//#endregion
|
|
725
|
+
//#region src/compiler/props.ts
|
|
726
|
+
/** _component 的 props:参数解构换成 prop 访问器,并补上组件名与 props 声明 */
|
|
727
|
+
/**
|
|
728
|
+
* 参数解构 → 实例内的 prop 访问器(参考 Svelte 5 的 $.prop):
|
|
729
|
+
*
|
|
730
|
+
* _component(({ size = 'md', icon = pick(size), _checked: checked = _bindable(false), ...rest }) => …)
|
|
731
|
+
* ↓
|
|
732
|
+
* _component((__props, __ctx) => {
|
|
733
|
+
* const size = prop(__props, 'size', () => 'md')
|
|
734
|
+
* const icon = prop(__props, 'icon', () => pick(size.value))
|
|
735
|
+
* const checked = bindable(__props, '_checked', () => false, __ctx.emit)
|
|
736
|
+
* const rest = restProps(__props, __ctx, true) // attrs 加上 children;已单独解构 children 时为 false
|
|
737
|
+
* …
|
|
738
|
+
* }, { name: 'Toggle', props: ['size', 'icon', '_checked', 'onUpdate:_checked'] })
|
|
739
|
+
*
|
|
740
|
+
* 默认值在实例内惰性求值:可以是任意表达式,也可以引用前面的 prop。
|
|
741
|
+
*/
|
|
742
|
+
function declareProps(setup, context) {
|
|
743
|
+
const params = setup.get("params");
|
|
744
|
+
const pattern = params[0];
|
|
745
|
+
const names = [];
|
|
746
|
+
if (pattern) {
|
|
747
|
+
if (params.length > 1) throw fail(params[1], "组件只接收一个参数:props 解构");
|
|
748
|
+
if (!pattern.isObjectPattern()) throw fail(pattern, "请解构 props,例如 _component(({ size = \"md\", ...rest }) => …);解构出的变量会保持响应式");
|
|
749
|
+
const propsId = setup.scope.generateUidIdentifier("props");
|
|
750
|
+
const ctxId = setup.scope.generateUidIdentifier("ctx");
|
|
751
|
+
const declarations = [];
|
|
752
|
+
for (const property of pattern.get("properties")) {
|
|
753
|
+
if (property.isRestElement()) {
|
|
754
|
+
const local = property.node.argument;
|
|
755
|
+
if (!types.isIdentifier(local)) throw fail(property, "...rest 需要是一个变量名");
|
|
756
|
+
const withChildren = types.booleanLiteral(!destructuresChildren(pattern.node));
|
|
757
|
+
const init = context.imports.runtime("restProps", [
|
|
758
|
+
propsId,
|
|
759
|
+
ctxId,
|
|
760
|
+
withChildren
|
|
761
|
+
]);
|
|
762
|
+
const declarator = types.variableDeclarator(local, init);
|
|
763
|
+
context.trackRest(declarator);
|
|
764
|
+
declarations.push(types.variableDeclaration("const", [declarator]));
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
const { key, local, fallback, isBindable } = readPropPattern(property);
|
|
768
|
+
if (key !== "children") names.push(key);
|
|
769
|
+
if (isBindable) names.push(`onUpdate:${key}`);
|
|
770
|
+
const thunk = fallback && types.arrowFunctionExpression([], fallback);
|
|
771
|
+
const init = key === "children" && !isBindable ? context.imports.runtime("childrenProp", [propsId, ...thunk ? [thunk] : []]) : isBindable ? context.imports.runtime("bindable", [
|
|
772
|
+
propsId,
|
|
773
|
+
types.stringLiteral(key),
|
|
774
|
+
thunk ?? types.identifier("undefined"),
|
|
775
|
+
types.memberExpression(ctxId, types.identifier("emit"))
|
|
776
|
+
]) : context.imports.runtime("prop", [
|
|
777
|
+
propsId,
|
|
778
|
+
types.stringLiteral(key),
|
|
779
|
+
...thunk ? [thunk] : []
|
|
780
|
+
]);
|
|
781
|
+
const declarator = types.variableDeclarator(types.identifier(local), init);
|
|
782
|
+
context.track(declarator, {
|
|
783
|
+
writable: key !== "children",
|
|
784
|
+
source: isBindable ? "_bindable" : "prop",
|
|
785
|
+
childrenOf: key === "children" ? setup.node : void 0
|
|
786
|
+
});
|
|
787
|
+
declarations.push(types.variableDeclaration("const", [declarator]));
|
|
788
|
+
}
|
|
789
|
+
propsId.typeAnnotation = pattern.node.typeAnnotation;
|
|
790
|
+
setup.node.params = [propsId, ctxId];
|
|
791
|
+
prependToBody(setup, declarations);
|
|
792
|
+
}
|
|
793
|
+
addComponentOptions(setup.parentPath, names, context.filename);
|
|
794
|
+
}
|
|
795
|
+
function destructuresChildren(pattern) {
|
|
796
|
+
return pattern.properties.some((property) => types.isObjectProperty(property) && !property.computed && (types.isIdentifier(property.key, { name: "children" }) || types.isStringLiteral(property.key, { value: "children" })));
|
|
797
|
+
}
|
|
798
|
+
/** 读取一项 props 解构:键名(驼峰)、本地变量名、默认值,以及是否 _bindable */
|
|
799
|
+
function readPropPattern(property) {
|
|
800
|
+
if (!property.isObjectProperty() || property.node.computed) throw fail(property, "props 解构不支持计算属性名");
|
|
801
|
+
const keyNode = property.node.key;
|
|
802
|
+
const key = types.isIdentifier(keyNode) ? keyNode.name : types.isStringLiteral(keyNode) ? keyNode.value : null;
|
|
803
|
+
if (key === null) throw fail(property, "props 的键名需要是标识符或字符串");
|
|
804
|
+
let value = property.get("value");
|
|
805
|
+
let fallback;
|
|
806
|
+
let isBindable = false;
|
|
807
|
+
if (value.isAssignmentPattern()) {
|
|
808
|
+
const right = value.get("right");
|
|
809
|
+
if (right.isCallExpression() && runeOf(right) === "_bindable") {
|
|
810
|
+
isBindable = true;
|
|
811
|
+
fallback = right.node.arguments[0];
|
|
812
|
+
} else fallback = right.node;
|
|
813
|
+
value = value.get("left");
|
|
814
|
+
}
|
|
815
|
+
if (!value.isIdentifier()) throw fail(value, "props 不支持嵌套解构:先解构出整个值,再在函数体里读取它的属性");
|
|
816
|
+
const local = value.node.name;
|
|
817
|
+
if (isBindable && !key.startsWith("_")) throw fail(property, `可双向绑定的 prop 以下划线开头:写成 { _${key}: ${local} = _bindable(…) }`);
|
|
818
|
+
if (!isBindable && key.startsWith("_")) throw fail(property, `下划线开头的 prop 留给可双向绑定的值:用 _bindable 声明({ ${key}: ${key.slice(1)} = _bindable(…) }),或去掉下划线`);
|
|
819
|
+
if (local.startsWith("_")) throw fail(property, `本地变量不用下划线开头(留给符文):写成 { ${key}: ${key.replace(/^_+/, "")} = _bindable(…) }`);
|
|
820
|
+
return {
|
|
821
|
+
key: key.replace(/-(\w)/g, (_, c) => c.toUpperCase()),
|
|
822
|
+
local,
|
|
823
|
+
fallback,
|
|
824
|
+
isBindable
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* 给 _component 补上第二个参数:组件名(取自 const X = _component(...))与 props 声明。
|
|
829
|
+
* 作者已经写了选项对象时,只补充其中缺少的项。
|
|
830
|
+
*/
|
|
831
|
+
function addComponentOptions(call, names, filename) {
|
|
832
|
+
const generated = [];
|
|
833
|
+
const name = componentName(call, filename);
|
|
834
|
+
if (name) generated.push(types.objectProperty(types.identifier("name"), types.stringLiteral(name)));
|
|
835
|
+
if (names.length) {
|
|
836
|
+
const list = types.arrayExpression(names.map((n) => types.stringLiteral(n)));
|
|
837
|
+
generated.push(types.objectProperty(types.identifier("props"), list));
|
|
838
|
+
}
|
|
839
|
+
if (!generated.length) return;
|
|
840
|
+
const existing = call.node.arguments[1];
|
|
841
|
+
if (!existing) call.node.arguments.push(types.objectExpression(generated));
|
|
842
|
+
else if (types.isObjectExpression(existing)) {
|
|
843
|
+
const has = (name) => existing.properties.some((p) => types.isObjectProperty(p) && types.isIdentifier(p.key, { name }));
|
|
844
|
+
existing.properties.unshift(...generated.filter((p) => !has(p.key.name)));
|
|
845
|
+
} else call.node.arguments[1] = types.objectExpression([...generated, types.spreadElement(existing)]);
|
|
846
|
+
}
|
|
847
|
+
/** const X = _component(...) 取变量名;export default _component(...) 取文件名,index.tsx 取所在目录名 */
|
|
848
|
+
function componentName(call, filename) {
|
|
849
|
+
const parent = call.parentPath;
|
|
850
|
+
if (parent?.isVariableDeclarator() && types.isIdentifier(parent.node.id)) return parent.node.id.name;
|
|
851
|
+
if (!parent?.isExportDefaultDeclaration() || !filename) return void 0;
|
|
852
|
+
const parts = filename.split(/[\\/]/);
|
|
853
|
+
const base = parts.at(-1).replace(/\.[^.]+$/, "");
|
|
854
|
+
const name = base === "index" ? parts.at(-2) : base;
|
|
855
|
+
return name && /^[A-Za-z]/.test(name) ? name : void 0;
|
|
856
|
+
}
|
|
857
|
+
//#endregion
|
|
858
|
+
//#region src/compiler/transform.ts
|
|
859
|
+
/** 符文编译:一个文件的流程按顺序执行 run() 里的各步,runesBabelPlugin 是它的 Babel 插件形式 */
|
|
860
|
+
/**
|
|
861
|
+
* 用到符文、定义了 _component(需要编译 props 解构)、或写了下划线属性(_value={x} 可能要双向绑定)的文件才需要编译;
|
|
862
|
+
* 宿主项目的其他文件原样放过
|
|
863
|
+
*/
|
|
864
|
+
const NEEDS_COMPILE = /\b_(?:state|derived|bindable|bind|effect|untrack|fromRef|toRef)\b|\b_component\s*\(|\s_[a-zA-Z]\w*=\{/;
|
|
865
|
+
/** 文件里有没有要编译的符文(解析之前的快速判断) */
|
|
866
|
+
const needsRunes = (code) => NEEDS_COMPILE.test(code);
|
|
867
|
+
/** 符文编译的 Babel 插件:误用直接抛出 RunesError,警告放在文件的 metadata.runesWarnings */
|
|
868
|
+
function runesBabelPlugin() {
|
|
869
|
+
return {
|
|
870
|
+
name: "vue-tsx-pro-runes",
|
|
871
|
+
visitor: { Program(program, state) {
|
|
872
|
+
const transform = new RunesTransform(program, state.filename, state.opts.optimize ?? true);
|
|
873
|
+
transform.run();
|
|
874
|
+
state.file.metadata.runesWarnings = transform.warnings;
|
|
875
|
+
} }
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
/** 一个文件的符文改写:五个步骤见 run(),由 runesBabelPlugin 对每个文件新建一个 */
|
|
879
|
+
var RunesTransform = class {
|
|
880
|
+
/** 不影响正确性、但多半不是作者本意的写法 */
|
|
881
|
+
warnings = [];
|
|
882
|
+
/** 第 1 步记录下的声明节点,第 2 步再换成作用域里的 Binding */
|
|
883
|
+
pending = /* @__PURE__ */ new Map();
|
|
884
|
+
tracked = /* @__PURE__ */ new Map();
|
|
885
|
+
pendingRest = /* @__PURE__ */ new Set();
|
|
886
|
+
/** _derived / _derived.by 的求值函数:里面不能修改状态 */
|
|
887
|
+
derivedGetters = /* @__PURE__ */ new WeakSet();
|
|
888
|
+
rest = /* @__PURE__ */ new Set();
|
|
889
|
+
program;
|
|
890
|
+
filename;
|
|
891
|
+
imports;
|
|
892
|
+
optimize;
|
|
893
|
+
constructor(program, filename, optimize = true) {
|
|
894
|
+
this.program = program;
|
|
895
|
+
this.filename = filename;
|
|
896
|
+
this.optimize = optimize;
|
|
897
|
+
this.imports = new RuntimeImports(program);
|
|
898
|
+
}
|
|
899
|
+
/** 按顺序执行全部步骤,结果直接改在 Babel 的 AST 上 */
|
|
900
|
+
run() {
|
|
901
|
+
this.declareRunes();
|
|
902
|
+
this.program.scope.crawl();
|
|
903
|
+
if (wrapAutoBinds(this.program, (identifier) => this.isWritableRune(identifier))) this.program.scope.crawl();
|
|
904
|
+
this.resolveBindings();
|
|
905
|
+
this.narrowReadonlyProps();
|
|
906
|
+
this.rewriteAccesses();
|
|
907
|
+
this.rewriteCalls();
|
|
908
|
+
this.reportMisuse();
|
|
909
|
+
if (this.optimize) optimizeRender(this.program, {
|
|
910
|
+
imports: this.imports,
|
|
911
|
+
tracked: this.tracked,
|
|
912
|
+
rest: this.rest
|
|
913
|
+
});
|
|
914
|
+
this.imports.insert();
|
|
915
|
+
}
|
|
916
|
+
declareRunes() {
|
|
917
|
+
this.program.traverse({
|
|
918
|
+
Function: (path) => {
|
|
919
|
+
if (!isPropsComponent(path)) return;
|
|
920
|
+
declareProps(path, {
|
|
921
|
+
imports: this.imports,
|
|
922
|
+
filename: this.filename,
|
|
923
|
+
track: (declarator, info) => this.pending.set(declarator, info),
|
|
924
|
+
trackRest: (declarator) => this.pendingRest.add(declarator)
|
|
925
|
+
});
|
|
926
|
+
},
|
|
927
|
+
JSXAttribute: (path) => checkAttribute(path),
|
|
928
|
+
CallExpression: (path) => {
|
|
929
|
+
const rune = runeOf(path);
|
|
930
|
+
switch (rune) {
|
|
931
|
+
case "_bind":
|
|
932
|
+
checkBindCall(path);
|
|
933
|
+
return;
|
|
934
|
+
case null:
|
|
935
|
+
case "_toRef":
|
|
936
|
+
case "_state.snapshot":
|
|
937
|
+
case "_untrack":
|
|
938
|
+
case "_effect":
|
|
939
|
+
case "_effect.pre":
|
|
940
|
+
case "_effect.root": return;
|
|
941
|
+
case "_bindable": throw fail(path, "_bindable() 只能写在 _component 参数解构的默认值位置,例如 ({ _checked: checked = _bindable(false) })");
|
|
942
|
+
default: this.declareState(path, rune);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
/** let x = _state(0) 之类:把调用换成 Vue API,并记下声明 */
|
|
948
|
+
declareState(path, rune) {
|
|
949
|
+
const declarator = path.parentPath;
|
|
950
|
+
if (!declarator.isVariableDeclarator() || declarator.node.init !== path.node) throw fail(path, `${rune}() 只能直接初始化一个变量,例如 let count = ${rune}(0)`);
|
|
951
|
+
const kind = declarator.parent.kind;
|
|
952
|
+
if (kind === "var") throw fail(declarator, "响应式变量请用 let 或 const 声明");
|
|
953
|
+
if (isInsideRender(path)) throw fail(path, `${rune} 写在渲染函数里,每次重新渲染都会重新创建;请移到组件函数体中 return 之前`);
|
|
954
|
+
if (path.node.arguments.length > 1) throw fail(path, `${rune}() 只接受一个参数`);
|
|
955
|
+
const id = declarator.node.id;
|
|
956
|
+
const arg = path.node.arguments[0];
|
|
957
|
+
const initial = arg ?? types.identifier("undefined");
|
|
958
|
+
switch (rune) {
|
|
959
|
+
case "_state":
|
|
960
|
+
if (kind === "const" && isPrimitiveLiteral(arg)) throw fail(path, "const 声明的原始值无法修改,响应式状态请用 let");
|
|
961
|
+
requireIdentifier(declarator, rune);
|
|
962
|
+
if (kind === "const" && declarator.parentPath.parentPath?.isExportNamedDeclaration()) {
|
|
963
|
+
path.replaceWith(this.imports.vue("reactive", [initial]));
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
path.replaceWith(this.imports.vue("ref", [initial]));
|
|
967
|
+
this.pending.set(declarator.node, {
|
|
968
|
+
writable: kind === "let",
|
|
969
|
+
source: rune
|
|
970
|
+
});
|
|
971
|
+
return;
|
|
972
|
+
case "_state.raw":
|
|
973
|
+
if (kind !== "let") throw fail(path, "_state.raw 只能用 let 声明(它只能整体替换)");
|
|
974
|
+
requireIdentifier(declarator, rune);
|
|
975
|
+
path.replaceWith(this.imports.vue("shallowRef", [initial]));
|
|
976
|
+
this.pending.set(declarator.node, {
|
|
977
|
+
writable: true,
|
|
978
|
+
source: rune
|
|
979
|
+
});
|
|
980
|
+
return;
|
|
981
|
+
case "_derived":
|
|
982
|
+
case "_derived.by": {
|
|
983
|
+
if (!arg) throw fail(path, `${rune} 需要一个参数`);
|
|
984
|
+
const getter = rune === "_derived" ? types.arrowFunctionExpression([], arg) : arg;
|
|
985
|
+
this.derivedGetters.add(getter);
|
|
986
|
+
const writable = kind === "let";
|
|
987
|
+
if (types.isObjectPattern(id) || types.isArrayPattern(id)) {
|
|
988
|
+
this.declareDestructuredDerived(declarator, id, getter, rune, writable);
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
requireIdentifier(declarator, rune);
|
|
992
|
+
path.replaceWith(this.derived(getter, writable));
|
|
993
|
+
this.pending.set(declarator.node, {
|
|
994
|
+
writable,
|
|
995
|
+
source: rune
|
|
996
|
+
});
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
case "_fromRef":
|
|
1000
|
+
if (!arg) throw fail(path, "_fromRef 需要一个 Ref 或由 Ref 组成的对象");
|
|
1001
|
+
if (!types.isIdentifier(id) && !types.isObjectPattern(id)) throw fail(path, "_fromRef 的结果请赋给一个变量或对象解构");
|
|
1002
|
+
path.replaceWith(arg);
|
|
1003
|
+
this.pending.set(declarator.node, {
|
|
1004
|
+
writable: kind === "let",
|
|
1005
|
+
source: rune
|
|
1006
|
+
});
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* 解构派生(同 Svelte 5):每个变量各自是一个派生值。
|
|
1012
|
+
*
|
|
1013
|
+
* const { name, tags: [first] } = _derived(user)
|
|
1014
|
+
* ↓
|
|
1015
|
+
* const _derivedParts = computed(() => { const { name, tags: [first] } = (() => user)(); return { name, first } })
|
|
1016
|
+
* const name = computed(() => _derivedParts.value.name)
|
|
1017
|
+
* const first = computed(() => _derivedParts.value.first)
|
|
1018
|
+
*
|
|
1019
|
+
* 按原样解构一次,默认值、嵌套与 ...rest 因此与普通解构完全一致,默认值也只执行一次。
|
|
1020
|
+
* let 声明时每个变量都可以临时覆盖。
|
|
1021
|
+
*/
|
|
1022
|
+
declareDestructuredDerived(declarator, pattern, getter, rune, writable) {
|
|
1023
|
+
const names = Object.keys(types.getBindingIdentifiers(pattern));
|
|
1024
|
+
const parts = declarator.scope.generateUidIdentifier("derivedParts");
|
|
1025
|
+
const destructure = types.arrowFunctionExpression([], types.blockStatement([types.variableDeclaration("const", [types.variableDeclarator(types.cloneNode(pattern), types.callExpression(getter, []))]), types.returnStatement(types.objectExpression(names.map((name) => types.objectProperty(types.identifier(name), types.identifier(name), false, true))))]));
|
|
1026
|
+
const declarators = [types.variableDeclarator(parts, this.imports.vue("computed", [destructure]))];
|
|
1027
|
+
for (const name of names) {
|
|
1028
|
+
const pick = types.arrowFunctionExpression([], types.memberExpression(types.memberExpression(types.identifier(parts.name), types.identifier("value")), types.identifier(name)));
|
|
1029
|
+
const derived = types.variableDeclarator(types.identifier(name), this.derived(pick, writable));
|
|
1030
|
+
this.pending.set(derived, {
|
|
1031
|
+
writable,
|
|
1032
|
+
source: rune
|
|
1033
|
+
});
|
|
1034
|
+
declarators.push(derived);
|
|
1035
|
+
}
|
|
1036
|
+
declarator.replaceWithMultiple(declarators);
|
|
1037
|
+
}
|
|
1038
|
+
/** 只读派生 → computed;可覆盖的派生 → overridable */
|
|
1039
|
+
derived(getter, writable) {
|
|
1040
|
+
return writable ? this.imports.runtime("overridable", [getter]) : this.imports.vue("computed", [getter]);
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* 能整体赋值的响应式变量:let 声明的 _state / _derived / _fromRef 与 _bindable 的 prop(还没建立绑定,按声明节点查)。
|
|
1044
|
+
* 普通 prop 不算:往下绑定要声明成 _bindable
|
|
1045
|
+
*/
|
|
1046
|
+
isWritableRune(identifier) {
|
|
1047
|
+
const declarator = identifier.scope.getBinding(identifier.node.name)?.path.node;
|
|
1048
|
+
const info = declarator && this.pending.get(declarator);
|
|
1049
|
+
return !!info && info.writable && info.source !== "prop";
|
|
1050
|
+
}
|
|
1051
|
+
resolveBindings() {
|
|
1052
|
+
this.program.traverse({
|
|
1053
|
+
VariableDeclarator: (path) => {
|
|
1054
|
+
if (this.pendingRest.has(path.node) && types.isIdentifier(path.node.id)) this.rest.add(path.scope.getBinding(path.node.id.name));
|
|
1055
|
+
const info = this.pending.get(path.node);
|
|
1056
|
+
if (!info) return;
|
|
1057
|
+
for (const name of Object.keys(path.getBindingIdentifiers())) this.tracked.set(path.scope.getBinding(name), info);
|
|
1058
|
+
},
|
|
1059
|
+
ExportNamedDeclaration: (path) => {
|
|
1060
|
+
const declaration = path.get("declaration");
|
|
1061
|
+
if (!declaration.isVariableDeclaration()) return;
|
|
1062
|
+
for (const name of Object.keys(declaration.getBindingIdentifiers())) if (this.pending.has(findDeclarator(declaration, name))) throw fail(path, `不能导出会被整体替换的响应式变量 ${name}:导入方只会拿到一个值。共享对象请写 export const ${name} = _state({...}),或导出读写函数`);
|
|
1063
|
+
}
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* 组件里没赋值过、也没交给 _toRef / _bind(拿到 Ref 的一方可能写入)的普通 prop,
|
|
1068
|
+
* 换成只读访问器 readonlyProp:不建可覆盖所需的 computed、customRef 与 watch(runtime/props.ts)
|
|
1069
|
+
*/
|
|
1070
|
+
narrowReadonlyProps() {
|
|
1071
|
+
for (const [binding, info] of this.tracked) {
|
|
1072
|
+
if (info.source !== "prop" || info.childrenOf || binding.constantViolations.length) continue;
|
|
1073
|
+
const escapes = binding.referencePaths.some((reference) => {
|
|
1074
|
+
const parent = reference.parentPath;
|
|
1075
|
+
const rune = parent?.isCallExpression() ? runeOf(parent) : null;
|
|
1076
|
+
return rune === "_toRef" || rune === "_bind";
|
|
1077
|
+
});
|
|
1078
|
+
const init = binding.path.node.init;
|
|
1079
|
+
if (escapes || !types.isCallExpression(init)) continue;
|
|
1080
|
+
this.imports.retarget(init, "prop", "readonlyProp");
|
|
1081
|
+
const fallback = init.arguments[2];
|
|
1082
|
+
if (types.isArrowFunctionExpression(fallback) && isConstantLiteral(fallback.body)) init.arguments[2] = fallback.body;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
rewriteAccesses() {
|
|
1086
|
+
for (const [binding, info] of this.tracked) {
|
|
1087
|
+
for (const violation of binding.constantViolations) {
|
|
1088
|
+
if (!info.writable) throw fail(violation, readonlyMessage(binding, info));
|
|
1089
|
+
if (violation.findParent((parent) => this.derivedGetters.has(parent.node))) throw fail(violation, `不能在 _derived 里修改 ${binding.identifier.name}:派生值只读取状态;需要跟着变化去改状态时用 _effect`);
|
|
1090
|
+
for (const target of writeTargets(violation, binding)) replaceWithValue(target);
|
|
1091
|
+
}
|
|
1092
|
+
for (const reference of binding.referencePaths) {
|
|
1093
|
+
if (!isAttached(reference) || isInTypePosition(reference)) continue;
|
|
1094
|
+
if (!types.isIdentifier(reference.node) && !types.isJSXIdentifier(reference.node)) continue;
|
|
1095
|
+
this.rewriteRead(reference, binding, info);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
rewriteRead(reference, binding, info) {
|
|
1100
|
+
const parent = reference.parentPath;
|
|
1101
|
+
const name = binding.identifier.name;
|
|
1102
|
+
if (parent.isCallExpression() && runeOf(parent) === "_toRef" && parent.node.arguments[0] === reference.node) {
|
|
1103
|
+
parent.replaceWith(types.identifier(name));
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
if (parent.isCallExpression() && runeOf(parent) === "_bind" && !info.writable) throw fail(reference, `${readonlyMessage(binding, info)},不能用 _bind 双向绑定`);
|
|
1107
|
+
if (info.childrenOf && reference.getFunctionParent()?.node === info.childrenOf) throw fail(reference, `${name} 是父级传入的内容,读取时才生成;请在渲染函数或回调里读取,不要直接写在组件函数体中`);
|
|
1108
|
+
if (parent.isExportSpecifier() || parent.isExportDefaultDeclaration()) throw fail(reference, `不能导出响应式变量 ${name}`);
|
|
1109
|
+
if (isVueWatchSource(reference)) throw fail(reference, `watch 的监听源请写成 getter:watch(() => ${name}, …);需要 Ref 时写 watch(_toRef(${name}), …)`);
|
|
1110
|
+
const owner = reference.getFunctionParent();
|
|
1111
|
+
const fixed = !info.writable && info.source === "_state";
|
|
1112
|
+
if (owner && isComponentSetup(owner) && !fixed && !isInsideUntrack(reference)) this.warnings.push(warning(reference, `${name} 在组件函数体里直接读取,只拿到当前值、之后不会更新;需要跟随变化请放进渲染函数、回调或 _derived`));
|
|
1113
|
+
if (isComponentChild(parent)) {
|
|
1114
|
+
reference.replaceWith(this.imports.vue("unref", [types.identifier(name)]));
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
replaceWithValue(reference);
|
|
1118
|
+
}
|
|
1119
|
+
rewriteCalls() {
|
|
1120
|
+
this.program.traverse({ CallExpression: (path) => {
|
|
1121
|
+
const rune = runeOf(path);
|
|
1122
|
+
if (rune === "_bind") rewriteBind(path);
|
|
1123
|
+
if (rune === "_state.snapshot") path.replaceWith(this.imports.runtime("snapshot", [singleArgument(path, rune)]));
|
|
1124
|
+
if (rune === "_untrack") {
|
|
1125
|
+
const argument = singleArgument(path, rune);
|
|
1126
|
+
if (types.isArrowFunctionExpression(argument) || types.isFunctionExpression(argument)) throw fail(path, "_untrack 的参数就是要读的值:写 _untrack(x),不要写成 _untrack(() => x)(那样拿到的是函数本身)");
|
|
1127
|
+
const read = types.arrowFunctionExpression([], argument);
|
|
1128
|
+
path.replaceWith(this.imports.runtime("untrack", [read]));
|
|
1129
|
+
}
|
|
1130
|
+
if (rune === "_effect" || rune === "_effect.pre" || rune === "_effect.root") this.rewriteEffect(path, rune);
|
|
1131
|
+
} });
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* _effect(fn) → effect(fn),_effect.pre(fn) → effect(fn, 'pre'),_effect.root(fn) → effectRoot(fn)。
|
|
1135
|
+
* 读写已在第 3 步改成 .value,effect 里读到的响应式变量自然成为依赖。
|
|
1136
|
+
*/
|
|
1137
|
+
rewriteEffect(path, rune) {
|
|
1138
|
+
const fn = singleArgument(path, rune);
|
|
1139
|
+
if (isInsideRender(path)) throw fail(path, `${rune} 写在渲染函数里,每次重新渲染都会重新创建;请移到组件函数体中 return 之前`);
|
|
1140
|
+
if (rune === "_effect.root") {
|
|
1141
|
+
path.replaceWith(this.imports.runtime("effectRoot", [fn]));
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
if (!path.parentPath.isExpressionStatement()) throw fail(path, `${rune}() 没有返回值,请单独写成一条语句`);
|
|
1145
|
+
const args = rune === "_effect.pre" ? [fn, types.stringLiteral("pre")] : [fn];
|
|
1146
|
+
path.replaceWith(this.imports.runtime("effect", args));
|
|
1147
|
+
}
|
|
1148
|
+
reportMisuse() {
|
|
1149
|
+
this.program.traverse({ Identifier(path) {
|
|
1150
|
+
const { name } = path.node;
|
|
1151
|
+
if (!RUNE_GLOBALS.has(name) || path.scope.hasBinding(name)) return;
|
|
1152
|
+
if (!path.isReferencedIdentifier() || isInTypePosition(path)) return;
|
|
1153
|
+
switch (name) {
|
|
1154
|
+
case "_toRef": throw fail(path, "_toRef(x) 的参数必须是 _state / _derived / _bindable / _fromRef 声明的变量");
|
|
1155
|
+
case "_bind": throw fail(path, "_bind 的修饰符只能在 JSX 属性的值里直接调用,例如 <input _value={_bind.trim(name)} />");
|
|
1156
|
+
default: throw fail(path, `${name} 只能按约定的位置直接调用,不能当作普通值使用`);
|
|
1157
|
+
}
|
|
1158
|
+
} });
|
|
1159
|
+
}
|
|
1160
|
+
};
|
|
1161
|
+
/** 引用位于 _untrack(…) 的参数里(第 3 步时调用还没改写) */
|
|
1162
|
+
function isInsideUntrack(path) {
|
|
1163
|
+
return !!path.findParent((parent) => parent.isCallExpression() && runeOf(parent) === "_untrack");
|
|
1164
|
+
}
|
|
1165
|
+
/** _state.snapshot(x)、_effect(fn) 这类只接受一个参数的符文 */
|
|
1166
|
+
function singleArgument(path, rune) {
|
|
1167
|
+
const args = path.node.arguments;
|
|
1168
|
+
if (args.length !== 1 || !types.isExpression(args[0])) throw fail(path, rune + "() 只接受一个参数");
|
|
1169
|
+
return args[0];
|
|
1170
|
+
}
|
|
1171
|
+
function requireIdentifier(declarator, rune) {
|
|
1172
|
+
if (!types.isIdentifier(declarator.node.id)) throw fail(declarator, `${rune} 不能解构声明:解构得到的是快照。请先声明变量,再读取属性`);
|
|
1173
|
+
}
|
|
1174
|
+
/** 只读绑定被写入时的提示:prop 建议改用 _bindable,符文直接说明来源 */
|
|
1175
|
+
function readonlyMessage(binding, info) {
|
|
1176
|
+
const name = binding.identifier.name;
|
|
1177
|
+
if (info.source === "_state" || info.source === "_fromRef") return `${name} 用 const 声明,不能整体替换(属性照常可改);需要替换请用 let`;
|
|
1178
|
+
return `${name} 来自 ${info.source},是只读的`;
|
|
1179
|
+
}
|
|
1180
|
+
/** 不引用任何变量的原始值字面量:'md'、0、-1、false、null、1n、不含插值的模板字符串 */
|
|
1181
|
+
function isConstantLiteral(node) {
|
|
1182
|
+
if (types.isUnaryExpression(node, { operator: "-" })) return types.isNumericLiteral(node.argument);
|
|
1183
|
+
if (types.isTemplateLiteral(node)) return node.expressions.length === 0;
|
|
1184
|
+
return types.isStringLiteral(node) || types.isNumericLiteral(node) || types.isBooleanLiteral(node) || types.isNullLiteral(node) || types.isBigIntLiteral(node);
|
|
1185
|
+
}
|
|
1186
|
+
/** 0、'x'、true、null、模板字符串、undefined 或不传参数:const 声明这类初始值就永远改不了 */
|
|
1187
|
+
function isPrimitiveLiteral(node) {
|
|
1188
|
+
if (!node || types.isIdentifier(node, { name: "undefined" })) return true;
|
|
1189
|
+
return types.isNumericLiteral(node) || types.isStringLiteral(node) || types.isBooleanLiteral(node) || types.isNullLiteral(node) || types.isBigIntLiteral(node) || types.isTemplateLiteral(node);
|
|
1190
|
+
}
|
|
1191
|
+
//#endregion
|
|
1192
|
+
//#region src/compiler/style-bindings.ts
|
|
1193
|
+
/**
|
|
1194
|
+
* 样式的响应式绑定:渲染函数的 JSX 里,随状态变的样式改成绑定,状态变化时由元素上的 effect 直接改元素,组件不重新渲染
|
|
1195
|
+
* (styles/dynamic.ts 的 bind、select,styles/bindings.ts 的 effect)。
|
|
1196
|
+
*
|
|
1197
|
+
* 动态样式的参数:<div {...css.attrs(styles.bar(item.width))} />
|
|
1198
|
+
* → <div {...css.attrs(css.bind(styles.bar, () => [item.width]))} /> 参数变化只改元素上的 CSS 变量
|
|
1199
|
+
* 按状态选样式:<div {...css.attrs(styles.row, item.hot && styles.hot)} />
|
|
1200
|
+
* → <div {...css.attrs(styles.row, css.select(() => item.hot && styles.hot))} /> 状态变化就地换组合类
|
|
1201
|
+
*
|
|
1202
|
+
* 只认本文件顶层 `const styles = css.create({ … })` 的样式(能静态确定是样式、哪些命名空间是动态样式)。
|
|
1203
|
+
* 动态样式的参数全是字面量时不改(不会变)。按状态选样式只改交给 attrs 或 sx 的(`cond && styles.x`、
|
|
1204
|
+
* `cond ? styles.a : styles.b`、`styles[key]`),而且读到的状态在渲染里没有别处用到:别处也读的话组件反正要重新渲染,
|
|
1205
|
+
* 绑定只会多一个 effect。
|
|
1206
|
+
*/
|
|
1207
|
+
/** 粗筛:文件里有样式入口的导入、也有 create 调用 */
|
|
1208
|
+
const needsStyleBindings = (code) => code.includes("@zerodep-dev/vue-tsx-pro-tsx/styles") && /\bcreate\s*\(/.test(code);
|
|
1209
|
+
/** 编译的第一遍、符文之前加的 Babel 插件(见文件开头) */
|
|
1210
|
+
function styleBindingsPlugin() {
|
|
1211
|
+
return {
|
|
1212
|
+
name: "vue-tsx-pro-style-bindings",
|
|
1213
|
+
visitor: { Program(program) {
|
|
1214
|
+
const imports = styleImports(program);
|
|
1215
|
+
if (!imports) return;
|
|
1216
|
+
const tables = styleTables(program, imports);
|
|
1217
|
+
if (!tables.size) return;
|
|
1218
|
+
const reference = (name) => helperReference(program, imports, name);
|
|
1219
|
+
const tableOf = (node) => {
|
|
1220
|
+
if (!types.isMemberExpression(node) || !types.isIdentifier(node.object)) return void 0;
|
|
1221
|
+
const binding = program.scope.getBinding(node.object.name);
|
|
1222
|
+
return binding && tables.get(binding.identifier);
|
|
1223
|
+
};
|
|
1224
|
+
const select = (path, condition) => {
|
|
1225
|
+
const render = renderFunctionOf(path);
|
|
1226
|
+
if (!render || !inStyleSlot(path, imports) || !worthBinding(condition, render, (other) => inAnyStyleSlot(other, imports))) return;
|
|
1227
|
+
const stable = readsOutsideRender(condition, render);
|
|
1228
|
+
path.replaceWith(types.callExpression(reference("select"), [types.arrowFunctionExpression([], path.node), ...stable ? [types.booleanLiteral(true)] : []]));
|
|
1229
|
+
path.skip();
|
|
1230
|
+
};
|
|
1231
|
+
/** 样式表里的一段静态样式:`styles.x`(不是动态样式),或表里没有动态样式时的 `styles[key]` */
|
|
1232
|
+
const isStyle = (node) => {
|
|
1233
|
+
const table = tableOf(node);
|
|
1234
|
+
if (!table || !types.isMemberExpression(node)) return false;
|
|
1235
|
+
if (node.computed) return !table.dynamic.size;
|
|
1236
|
+
return types.isIdentifier(node.property) && !table.dynamic.has(node.property.name);
|
|
1237
|
+
};
|
|
1238
|
+
const isStyleOrNothing = (node) => isStyle(node) || types.isNullLiteral(node) || types.isBooleanLiteral(node, { value: false }) || types.isIdentifier(node, { name: "undefined" });
|
|
1239
|
+
program.traverse({
|
|
1240
|
+
CallExpression(path) {
|
|
1241
|
+
const callee = path.node.callee;
|
|
1242
|
+
if (types.isMemberExpression(callee) && callee.computed) return;
|
|
1243
|
+
const table = tableOf(callee);
|
|
1244
|
+
if (!table || !types.isMemberExpression(callee) || !types.isIdentifier(callee.property)) return;
|
|
1245
|
+
if (!table.dynamic.has(callee.property.name)) return;
|
|
1246
|
+
const render = inJsxAttribute(path) ? renderFunctionOf(path) : null;
|
|
1247
|
+
if (!render) return;
|
|
1248
|
+
const args = path.node.arguments;
|
|
1249
|
+
if (args.every((arg) => types.isLiteral(arg) && !types.isTemplateLiteral(arg))) return;
|
|
1250
|
+
if (args.some((arg) => types.isSpreadElement(arg) || types.isArgumentPlaceholder(arg))) return;
|
|
1251
|
+
const stable = path.get("arguments").every((arg) => readsOutsideRender(arg, render));
|
|
1252
|
+
path.replaceWith(types.callExpression(reference("bind"), [
|
|
1253
|
+
callee,
|
|
1254
|
+
types.arrowFunctionExpression([], types.arrayExpression(args)),
|
|
1255
|
+
...stable ? [types.booleanLiteral(true)] : []
|
|
1256
|
+
]));
|
|
1257
|
+
path.skip();
|
|
1258
|
+
},
|
|
1259
|
+
LogicalExpression(path) {
|
|
1260
|
+
if (path.node.operator === "&&" && isStyle(path.node.right)) select(path, path.get("left"));
|
|
1261
|
+
},
|
|
1262
|
+
ConditionalExpression(path) {
|
|
1263
|
+
const { consequent, alternate } = path.node;
|
|
1264
|
+
if ((isStyle(consequent) || isStyle(alternate)) && isStyleOrNothing(consequent) && isStyleOrNothing(alternate)) select(path, path.get("test"));
|
|
1265
|
+
},
|
|
1266
|
+
MemberExpression(path) {
|
|
1267
|
+
if (!path.node.computed || !isStyle(path.node)) return;
|
|
1268
|
+
const key = path.get("property");
|
|
1269
|
+
if (key.isLiteral() && !key.isTemplateLiteral()) return;
|
|
1270
|
+
select(path, key);
|
|
1271
|
+
}
|
|
1272
|
+
});
|
|
1273
|
+
} }
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
function styleImports(program) {
|
|
1277
|
+
for (const statement of program.node.body) {
|
|
1278
|
+
if (!types.isImportDeclaration(statement) || statement.source.value !== "@zerodep-dev/vue-tsx-pro-tsx/styles") continue;
|
|
1279
|
+
const found = { source: statement.source.value };
|
|
1280
|
+
for (const specifier of statement.specifiers) if (types.isImportNamespaceSpecifier(specifier)) found.namespace = specifier.local.name;
|
|
1281
|
+
else if (types.isImportSpecifier(specifier) && types.isIdentifier(specifier.imported)) {
|
|
1282
|
+
if (specifier.imported.name === "create") found.create = specifier.local.name;
|
|
1283
|
+
if (specifier.imported.name === "attrs") found.attrs = specifier.local.name;
|
|
1284
|
+
}
|
|
1285
|
+
if (found.namespace || found.create) return found;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
/** 顶层 `const 名 = css.create({ … })`:名字的声明节点 → 样式表 */
|
|
1289
|
+
function styleTables(program, imports) {
|
|
1290
|
+
const result = /* @__PURE__ */ new Map();
|
|
1291
|
+
for (const statement of program.node.body) {
|
|
1292
|
+
const declaration = types.isExportNamedDeclaration(statement) ? statement.declaration : statement;
|
|
1293
|
+
if (!types.isVariableDeclaration(declaration) || declaration.kind !== "const") continue;
|
|
1294
|
+
for (const declarator of declaration.declarations) {
|
|
1295
|
+
if (!types.isIdentifier(declarator.id) || !isCreateCall(declarator.init, imports)) continue;
|
|
1296
|
+
const [styles] = declarator.init.arguments;
|
|
1297
|
+
if (!types.isObjectExpression(styles)) continue;
|
|
1298
|
+
if (styles.properties.some((p) => types.isSpreadElement(p) || p.computed)) continue;
|
|
1299
|
+
const dynamic = /* @__PURE__ */ new Set();
|
|
1300
|
+
for (const property of styles.properties) {
|
|
1301
|
+
if (types.isSpreadElement(property)) continue;
|
|
1302
|
+
const key = property.key;
|
|
1303
|
+
if (!types.isIdentifier(key) && !types.isStringLiteral(key)) continue;
|
|
1304
|
+
const name = types.isIdentifier(key) ? key.name : key.value;
|
|
1305
|
+
if (types.isObjectMethod(property) || types.isArrowFunctionExpression(property.value) || types.isFunctionExpression(property.value)) dynamic.add(name);
|
|
1306
|
+
}
|
|
1307
|
+
result.set(declarator.id, { dynamic });
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
return result;
|
|
1311
|
+
}
|
|
1312
|
+
function isCreateCall(node, imports) {
|
|
1313
|
+
if (!types.isCallExpression(node)) return false;
|
|
1314
|
+
const callee = node.callee;
|
|
1315
|
+
if (types.isIdentifier(callee)) return callee.name === imports.create;
|
|
1316
|
+
return types.isMemberExpression(callee) && !callee.computed && types.isIdentifier(callee.object, { name: imports.namespace }) && types.isIdentifier(callee.property, { name: "create" });
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* 交给 attrs 或 sx 的样式:attrs 的参数、sx 属性(JSX 的 sx={…}、对象里的 sx: …)的值,中间可以隔着数组。
|
|
1320
|
+
* 别处(传给组件的其他 prop、拿去比较)不改:那里要的是样式本身
|
|
1321
|
+
*/
|
|
1322
|
+
function inStyleSlot(path, imports) {
|
|
1323
|
+
let current = path;
|
|
1324
|
+
while (current.parentPath?.isArrayExpression()) current = current.parentPath;
|
|
1325
|
+
const parent = current.parentPath;
|
|
1326
|
+
if (!parent) return false;
|
|
1327
|
+
if (parent.isCallExpression() && current.listKey === "arguments") {
|
|
1328
|
+
const callee = parent.node.callee;
|
|
1329
|
+
return types.isIdentifier(callee, { name: imports.attrs }) || types.isMemberExpression(callee) && types.isIdentifier(callee.object, { name: imports.namespace }) && types.isIdentifier(callee.property, { name: "attrs" });
|
|
1330
|
+
}
|
|
1331
|
+
if (parent.isJSXExpressionContainer()) {
|
|
1332
|
+
const attribute = parent.parentPath;
|
|
1333
|
+
return !!attribute?.isJSXAttribute() && types.isJSXIdentifier(attribute.node.name, { name: "sx" });
|
|
1334
|
+
}
|
|
1335
|
+
return parent.isObjectProperty() && current.key === "value" && !parent.node.computed && types.isIdentifier(parent.node.key, { name: "sx" });
|
|
1336
|
+
}
|
|
1337
|
+
/** 在交给 attrs 或 sx 的样式里(往上找到 JSX 属性为止) */
|
|
1338
|
+
function inAnyStyleSlot(path, imports) {
|
|
1339
|
+
for (let current = path; current; current = current.parentPath) {
|
|
1340
|
+
if (current.isJSXAttribute() || current.isJSXSpreadAttribute()) return false;
|
|
1341
|
+
if (current.isExpression() && inStyleSlot(current, imports)) return true;
|
|
1342
|
+
}
|
|
1343
|
+
return false;
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* 改成绑定值不值得:条件读到的状态(`item.hot`、`active`)至少有一个在渲染里没有别处读到。别处也读的话组件反正要
|
|
1347
|
+
* 重新渲染,绑定只会多一个 effect。组件自己的 prop 整个值不算(值变了 Vue 照样重新渲染子组件)。交给样式的那些读(attrs、sx 里,同样会改成绑定)、事件回调里的、赋值的不算“别处”。
|
|
1348
|
+
* 只读模块常量的条件不会变;读了渲染里算出来的局部变量的,它的来源已经被渲染追踪
|
|
1349
|
+
*/
|
|
1350
|
+
function worthBinding(condition, render, inStyles) {
|
|
1351
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1352
|
+
let local = false;
|
|
1353
|
+
const visit = (path) => {
|
|
1354
|
+
if (!path.isReferencedIdentifier()) return;
|
|
1355
|
+
const binding = path.scope.getBinding(path.node.name);
|
|
1356
|
+
if (!binding || binding.scope.path.isProgram()) return;
|
|
1357
|
+
if ((binding.path === render || binding.path.isDescendant(render)) && binding.kind !== "param") local = true;
|
|
1358
|
+
const chain = chainOf(path);
|
|
1359
|
+
if (binding.kind === "param" && !chain.includes(".") && !isCalled(path)) return;
|
|
1360
|
+
if (binding.kind === "const" && !chain.includes(".") && !isCalled(path) && !isReactiveInit(binding.path)) return;
|
|
1361
|
+
keys.add(chain);
|
|
1362
|
+
};
|
|
1363
|
+
if (condition.isIdentifier()) visit(condition);
|
|
1364
|
+
else condition.traverse({ Identifier: visit });
|
|
1365
|
+
if (local || !keys.size) return false;
|
|
1366
|
+
const elsewhere = /* @__PURE__ */ new Set();
|
|
1367
|
+
render.traverse({ Identifier(path) {
|
|
1368
|
+
if (!path.isReferencedIdentifier() || inStyles(path)) return;
|
|
1369
|
+
if (inEventHandler(path, render) || isWritten(path)) return;
|
|
1370
|
+
const chain = chainOf(path);
|
|
1371
|
+
for (const key of keys) if (chain === key || chain.startsWith(`${key}.`)) elsewhere.add(key);
|
|
1372
|
+
} });
|
|
1373
|
+
return elsewhere.size < keys.size;
|
|
1374
|
+
}
|
|
1375
|
+
/** 被调用:`compact()` 这类读状态的 getter */
|
|
1376
|
+
const isCalled = (path) => !!path.parentPath?.isCallExpression() && path.parentPath.node.callee === path.node;
|
|
1377
|
+
/** 常量的初值是响应式的:_derived、_derived.by、_fromRef 的结果 */
|
|
1378
|
+
function isReactiveInit(declarator) {
|
|
1379
|
+
if (!declarator.isVariableDeclarator()) return false;
|
|
1380
|
+
const init = declarator.node.init;
|
|
1381
|
+
if (!types.isCallExpression(init)) return false;
|
|
1382
|
+
const callee = types.isMemberExpression(init.callee) ? init.callee.object : init.callee;
|
|
1383
|
+
return types.isIdentifier(callee) && (callee.name === "_derived" || callee.name === "_fromRef");
|
|
1384
|
+
}
|
|
1385
|
+
/** 从标识符起的成员链:`item.hot` → 'item.hot';计算属性、调用处截断 */
|
|
1386
|
+
function chainOf(path) {
|
|
1387
|
+
let chain = path.node.name;
|
|
1388
|
+
let current = path;
|
|
1389
|
+
while (current.parentPath?.isMemberExpression() && current.parentPath.node.object === current.node && !current.parentPath.node.computed && types.isIdentifier(current.parentPath.node.property)) {
|
|
1390
|
+
current = current.parentPath;
|
|
1391
|
+
chain += `.${current.node.property.name}`;
|
|
1392
|
+
}
|
|
1393
|
+
return chain;
|
|
1394
|
+
}
|
|
1395
|
+
/** 在事件回调(onXxx={() => …})里:渲染时不执行,不算渲染读到的 */
|
|
1396
|
+
function inEventHandler(path, render) {
|
|
1397
|
+
for (let fn = path.getFunctionParent(); fn && fn !== render; fn = fn.parentPath?.getFunctionParent() ?? null) {
|
|
1398
|
+
const attribute = fn.parentPath?.parentPath;
|
|
1399
|
+
if (attribute?.isJSXAttribute() && types.isJSXIdentifier(attribute.node.name) && /^on[A-Z]/.test(attribute.node.name.name)) return true;
|
|
1400
|
+
}
|
|
1401
|
+
return false;
|
|
1402
|
+
}
|
|
1403
|
+
/** 被赋值、自增的那一处(写,不是读) */
|
|
1404
|
+
function isWritten(path) {
|
|
1405
|
+
let current = path;
|
|
1406
|
+
while (current.parentPath?.isMemberExpression() && current.parentPath.node.object === current.node) current = current.parentPath;
|
|
1407
|
+
const parent = current.parentPath;
|
|
1408
|
+
return !!parent?.isAssignmentExpression() && current.key === "left" || !!parent?.isUpdateExpression();
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* 只读渲染函数外面声明的东西(组件函数体里的状态、props、模块里的常量):每次渲染生成的读取函数读的是同一批来源,
|
|
1412
|
+
* 运行时重新渲染时只换读取函数、不必重跑 effect 收集依赖(bind、select 的最后一个参数)。
|
|
1413
|
+
* 读了渲染函数里的局部变量、map 回调的参数时不算:它们每次渲染可能指向不同的响应式来源
|
|
1414
|
+
*/
|
|
1415
|
+
function readsOutsideRender(expression, render) {
|
|
1416
|
+
let stable = true;
|
|
1417
|
+
const check = (path) => {
|
|
1418
|
+
if (!path.isReferencedIdentifier()) return;
|
|
1419
|
+
const binding = path.scope.getBinding(path.node.name);
|
|
1420
|
+
if (binding && (binding.path === render || binding.path.isDescendant(render))) stable = false;
|
|
1421
|
+
};
|
|
1422
|
+
if (expression.isIdentifier()) check(expression);
|
|
1423
|
+
else expression.traverse({ Identifier: check });
|
|
1424
|
+
return stable;
|
|
1425
|
+
}
|
|
1426
|
+
/** 在 JSX 属性(sx={…}、{...css.attrs(…)})里:渲染时用到的样式 */
|
|
1427
|
+
const inJsxAttribute = (path) => !!path.findParent((parent) => parent.isJSXAttribute() || parent.isJSXSpreadAttribute());
|
|
1428
|
+
/** bind、select 的引用:有命名空间导入时用 css.bind,否则补一个具名导入(同一文件只补一次) */
|
|
1429
|
+
function helperReference(program, imports, name) {
|
|
1430
|
+
if (imports.namespace) return types.memberExpression(types.identifier(imports.namespace), types.identifier(name));
|
|
1431
|
+
imports.helpers ??= /* @__PURE__ */ new Map();
|
|
1432
|
+
let local = imports.helpers.get(name);
|
|
1433
|
+
if (!local) {
|
|
1434
|
+
local = program.scope.generateUidIdentifier(name);
|
|
1435
|
+
imports.helpers.set(name, local);
|
|
1436
|
+
program.unshiftContainer("body", types.importDeclaration([types.importSpecifier(local, types.identifier(name))], types.stringLiteral(imports.source)));
|
|
1437
|
+
}
|
|
1438
|
+
return types.cloneNode(local);
|
|
1439
|
+
}
|
|
1440
|
+
//#endregion
|
|
1441
|
+
//#region src/compiler/style-hot.ts
|
|
1442
|
+
/**
|
|
1443
|
+
* 样式的热更新原地生效(开发服务器、运行时样式模式):
|
|
1444
|
+
*
|
|
1445
|
+
* const styles = css.create({ … })
|
|
1446
|
+
* → const styles = __hotStyles("src/Button.tsx#styles", css.create({ … }))
|
|
1447
|
+
*
|
|
1448
|
+
* 模块重新执行时 hotStyles 按 id 找到旧对象,把新声明写进旧片段、改写已插入的规则(styles/hot.ts),
|
|
1449
|
+
* 类名不变,组件不用重新渲染。同时算出“骨架”:去掉这些调用的参数后的源码哈希,写进 file.metadata.styleSkeleton;
|
|
1450
|
+
* 热更新时骨架没变(只改了样式)就原地接住,变了才照常让导入方重新执行(vite/hmr.ts)。
|
|
1451
|
+
* 只认顶层声明里的 create、recipe:函数里的调用每次执行都是新对象,不能按 id 合并。
|
|
1452
|
+
*/
|
|
1453
|
+
/** 能原地更新的样式函数 */
|
|
1454
|
+
const HOT_FUNCTIONS = /* @__PURE__ */ new Set(["create", "recipe"]);
|
|
1455
|
+
/** 编译的第一遍、符文之前加的 Babel 插件;moduleId 是相对项目根目录的模块路径(见文件开头) */
|
|
1456
|
+
function styleHotPlugin(moduleId) {
|
|
1457
|
+
return {
|
|
1458
|
+
name: "vue-tsx-pro-style-hot",
|
|
1459
|
+
visitor: { Program(program, state) {
|
|
1460
|
+
const imports = hotImports(program);
|
|
1461
|
+
if (!imports.namespace && !imports.named.size) return;
|
|
1462
|
+
const code = state.file.code;
|
|
1463
|
+
const removed = [];
|
|
1464
|
+
let wrapper;
|
|
1465
|
+
for (const statement of program.get("body")) {
|
|
1466
|
+
const declaration = statement.isExportNamedDeclaration() ? statement.get("declaration") : statement;
|
|
1467
|
+
if (!declaration.isVariableDeclaration() || declaration.node.kind !== "const") continue;
|
|
1468
|
+
for (const declarator of declaration.get("declarations")) {
|
|
1469
|
+
const id = declarator.node.id;
|
|
1470
|
+
const init = declarator.get("init");
|
|
1471
|
+
if (!types.isIdentifier(id) || !init.isCallExpression() || !isHotCall(init.node, imports)) continue;
|
|
1472
|
+
const args = init.node.arguments;
|
|
1473
|
+
const first = args[0];
|
|
1474
|
+
const last = args.at(-1);
|
|
1475
|
+
if (first?.start == null || last?.end == null) continue;
|
|
1476
|
+
removed.push([first.start, last.end]);
|
|
1477
|
+
wrapper ??= program.scope.generateUidIdentifier("hotStyles");
|
|
1478
|
+
init.replaceWith(types.callExpression(types.cloneNode(wrapper), [types.stringLiteral(`${moduleId}#${id.name}`), init.node]));
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
if (!wrapper) return;
|
|
1482
|
+
program.unshiftContainer("body", types.importDeclaration([types.importSpecifier(wrapper, types.identifier("hotStyles"))], types.stringLiteral(imports.source)));
|
|
1483
|
+
state.file.metadata.styleSkeleton = skeleton(code, removed);
|
|
1484
|
+
} }
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
function hotImports(program) {
|
|
1488
|
+
const found = {
|
|
1489
|
+
source: STYLE_SOURCE,
|
|
1490
|
+
named: /* @__PURE__ */ new Set()
|
|
1491
|
+
};
|
|
1492
|
+
for (const statement of program.node.body) {
|
|
1493
|
+
if (!types.isImportDeclaration(statement) || statement.source.value !== "@zerodep-dev/vue-tsx-pro-tsx/styles") continue;
|
|
1494
|
+
found.source = statement.source.value;
|
|
1495
|
+
for (const specifier of statement.specifiers) if (types.isImportNamespaceSpecifier(specifier)) found.namespace = specifier.local.name;
|
|
1496
|
+
else if (types.isImportSpecifier(specifier) && types.isIdentifier(specifier.imported) && HOT_FUNCTIONS.has(specifier.imported.name)) found.named.add(specifier.local.name);
|
|
1497
|
+
}
|
|
1498
|
+
return found;
|
|
1499
|
+
}
|
|
1500
|
+
function isHotCall(node, imports) {
|
|
1501
|
+
const callee = node.callee;
|
|
1502
|
+
if (types.isIdentifier(callee)) return imports.named.has(callee.name);
|
|
1503
|
+
return types.isMemberExpression(callee) && !callee.computed && types.isIdentifier(callee.object, { name: imports.namespace }) && types.isIdentifier(callee.property) && HOT_FUNCTIONS.has(callee.property.name);
|
|
1504
|
+
}
|
|
1505
|
+
/** 去掉样式参数后的源码的哈希(FNV-1a;编译器也在浏览器里跑,不用 node:crypto) */
|
|
1506
|
+
function skeleton(code, removed) {
|
|
1507
|
+
let text = "";
|
|
1508
|
+
let from = 0;
|
|
1509
|
+
for (const [start, end] of removed) {
|
|
1510
|
+
text += code.slice(from, start);
|
|
1511
|
+
from = end;
|
|
1512
|
+
}
|
|
1513
|
+
text += code.slice(from);
|
|
1514
|
+
let hash = 2166136261;
|
|
1515
|
+
for (let index = 0; index < text.length; index++) {
|
|
1516
|
+
hash ^= text.charCodeAt(index);
|
|
1517
|
+
hash = Math.imul(hash, 16777619);
|
|
1518
|
+
}
|
|
1519
|
+
return (hash >>> 0).toString(36);
|
|
1520
|
+
}
|
|
1521
|
+
//#endregion
|
|
1522
|
+
//#region src/compiler/compile.ts
|
|
1523
|
+
/**
|
|
1524
|
+
* 一个文件的完整编译:解析一次、生成一次,中间两遍遍历,组合本包与官方的 Babel 插件。
|
|
1525
|
+
*
|
|
1526
|
+
* 第 1 遍:样式的响应式绑定与热更新(style-bindings.ts、style-hot.ts)→ 符文与 props(transform.ts)
|
|
1527
|
+
* 第 2 遍:Vue JSX(@vue/babel-plugin-jsx)→ 去掉 TS 类型 → 调用方追加的插件(热更新标记、演练场的模块表)
|
|
1528
|
+
*
|
|
1529
|
+
* 以前每一步各自解析、生成一次,成本大头是每一遍的遍历与生成(含 source map),合并后编译快三四成。
|
|
1530
|
+
* 分成两遍而不是一遍:第 1 遍的改写(符文换成 .value、样式调用换成绑定)要在 vue-jsx 处理元素之前完成,
|
|
1531
|
+
* 同一遍里各插件的访问器按节点交错执行,vue-jsx 会先看到没改写的属性。
|
|
1532
|
+
* 第 2 遍开始时重建作用域:第 1 遍插入的 import 与声明没有全部登记,vue-jsx 与去类型插件要靠作用域里的绑定判断。
|
|
1533
|
+
*
|
|
1534
|
+
* Vite 预设(vite/)与文档站的代码演练场都用它,两处的结果一致。
|
|
1535
|
+
*/
|
|
1536
|
+
/**
|
|
1537
|
+
* 编译一个文件;什么都不用做(没有符文、样式绑定与热更新,也不是 JSX 文件)时返回 null。
|
|
1538
|
+
* 写法错误时抛出带代码帧的错误。
|
|
1539
|
+
*/
|
|
1540
|
+
async function compile(code, options) {
|
|
1541
|
+
const lang = options.lang ?? languageOf(options.filename) ?? "js";
|
|
1542
|
+
const first = [];
|
|
1543
|
+
if (options.runes !== false && needsRunes(code)) first.push([runesBabelPlugin, { optimize: options.optimize ?? true }]);
|
|
1544
|
+
if (options.styleHot && needsStyleBindings(code)) {
|
|
1545
|
+
const moduleId = options.styleHot;
|
|
1546
|
+
first.unshift(() => styleHotPlugin(moduleId));
|
|
1547
|
+
}
|
|
1548
|
+
if (options.styleBindings && needsStyleBindings(code)) first.unshift(styleBindingsPlugin);
|
|
1549
|
+
const second = [];
|
|
1550
|
+
if (lang === "tsx" || lang === "jsx") {
|
|
1551
|
+
second.push([vueJsx, options.jsx ?? {}]);
|
|
1552
|
+
if (lang === "tsx") second.push([typescript, options.typescript ?? {}]);
|
|
1553
|
+
}
|
|
1554
|
+
if (!first.length && !second.length) return null;
|
|
1555
|
+
const passes = [first, [...second, ...options.plugins ?? []]].filter((pass) => pass.length);
|
|
1556
|
+
if (first.length && passes.length === 2) passes[1].unshift(rescope);
|
|
1557
|
+
const shared = {
|
|
1558
|
+
filename: options.filename,
|
|
1559
|
+
babelrc: false,
|
|
1560
|
+
configFile: false
|
|
1561
|
+
};
|
|
1562
|
+
let ast = await parseAsync(code, {
|
|
1563
|
+
...shared,
|
|
1564
|
+
parserOpts: { plugins: parserPlugins(lang) }
|
|
1565
|
+
});
|
|
1566
|
+
const metadata = {};
|
|
1567
|
+
for (const [index, plugins] of passes.entries()) {
|
|
1568
|
+
const last = index === passes.length - 1;
|
|
1569
|
+
const result = await transformFromAstAsync(ast, code, {
|
|
1570
|
+
...shared,
|
|
1571
|
+
plugins,
|
|
1572
|
+
ast: true,
|
|
1573
|
+
code: last,
|
|
1574
|
+
cloneInputAst: false,
|
|
1575
|
+
sourceMaps: last && (options.sourceMaps ?? true),
|
|
1576
|
+
generatorOpts: { jsescOption: { minimal: true } }
|
|
1577
|
+
});
|
|
1578
|
+
Object.assign(metadata, result.metadata);
|
|
1579
|
+
ast = result.ast;
|
|
1580
|
+
if (last) return {
|
|
1581
|
+
code: result.code ?? "",
|
|
1582
|
+
map: result.map,
|
|
1583
|
+
ast,
|
|
1584
|
+
warnings: metadata.runesWarnings ?? [],
|
|
1585
|
+
styleSkeleton: metadata.styleSkeleton
|
|
1586
|
+
};
|
|
1587
|
+
}
|
|
1588
|
+
return null;
|
|
1589
|
+
}
|
|
1590
|
+
/** 重建作用域(见文件开头) */
|
|
1591
|
+
function rescope() {
|
|
1592
|
+
return {
|
|
1593
|
+
name: "vue-tsx-pro-rescope",
|
|
1594
|
+
visitor: { Program(program) {
|
|
1595
|
+
program.scope.crawl();
|
|
1596
|
+
} }
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
//#endregion
|
|
1600
|
+
//#region src/compiler/index.ts
|
|
1601
|
+
/**
|
|
1602
|
+
* 编译器入口。
|
|
1603
|
+
*
|
|
1604
|
+
* compile(compile.ts)是一个文件的完整编译,Vite 预设与文档站的代码演练场都用它:解析一次、生成一次,
|
|
1605
|
+
* 中间依次是样式的响应式绑定与热更新、符文、Vue JSX 与去掉类型。
|
|
1606
|
+
*
|
|
1607
|
+
* 其中本包自己的转换:
|
|
1608
|
+
* - 符文(transform.ts):让本地响应式变量像普通变量一样读写,语义全部交给 Vue
|
|
1609
|
+
* 1. 把符文声明换成 Vue API:_state → ref,_derived → computed,_fromRef → 直接持有外部 Ref
|
|
1610
|
+
* 2. 把 _component 的参数解构换成实例内的 prop 访问器(参考 Svelte 5 的 $.prop),
|
|
1611
|
+
* 并生成 Vue 需要的 props 声明;_bindable 走标准 v-model 协议(props.ts)
|
|
1612
|
+
* 3. 把这些变量的读写改写成 .value
|
|
1613
|
+
* 4. _toRef(x) 取回背后的 Ref;属性={_bind(x)} 换成 Vue 的 v-model(bind.ts)
|
|
1614
|
+
* 5. 对会悄悄失去响应性的写法、以及 TSX 里有更自然写法的 Vue 指令直接报错
|
|
1615
|
+
* 6. 渲染优化:组件子节点的插槽标 $stable,JSX 属性里的函数与常量每个实例只建一次(optimize.ts,可关)
|
|
1616
|
+
* 生成的代码就是手写 Vue 会写的代码,JSX 交给官方的 @vue/babel-plugin-jsx。
|
|
1617
|
+
* - 样式(style-bindings.ts、style-hot.ts):JSX 里的动态样式调用改成响应式绑定,顶层 create 包上热更新
|
|
1618
|
+
*
|
|
1619
|
+
* compileRunes 只编译符文,测试与语言服务用。
|
|
1620
|
+
*
|
|
1621
|
+
* 目录内的相对导入带 .ts 扩展名:语言服务(scripts/language-services)与 Vitest 配置在 Node 里直接加载这些源码。
|
|
1622
|
+
*/
|
|
1623
|
+
/**
|
|
1624
|
+
* 只编译一个文件里的符文与 _component 参数解构;不需要编译时返回 null。
|
|
1625
|
+
* 结果带 source map 与警告(只拿到初始值的读取等),误用直接抛出 RunesError。
|
|
1626
|
+
*/
|
|
1627
|
+
async function compileRunes(code, filename = "input.tsx", options = {}) {
|
|
1628
|
+
if (!needsRunes(code)) return null;
|
|
1629
|
+
const result = await transformAsync(code, {
|
|
1630
|
+
filename,
|
|
1631
|
+
babelrc: false,
|
|
1632
|
+
configFile: false,
|
|
1633
|
+
sourceMaps: true,
|
|
1634
|
+
generatorOpts: { jsescOption: { minimal: true } },
|
|
1635
|
+
parserOpts: { plugins: parserPlugins(languageOf(filename)) },
|
|
1636
|
+
plugins: [[runesBabelPlugin, options]]
|
|
1637
|
+
});
|
|
1638
|
+
if (!result) return null;
|
|
1639
|
+
const warnings = result.metadata.runesWarnings ?? [];
|
|
1640
|
+
return {
|
|
1641
|
+
code: result.code ?? "",
|
|
1642
|
+
map: result.map ?? null,
|
|
1643
|
+
warnings
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
//#endregion
|
|
1647
|
+
export { compile as n, runesBabelPlugin as r, compileRunes as t };
|