@actview/plugin-babel 1.0.3

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 ADDED
@@ -0,0 +1,64 @@
1
+ # @actview/plugin-babel
2
+
3
+ > **迁移记录**:本包原为 `@actview/babel-plugin-actview`(目录 `plugins/babel-plugin-actview`),现更名为 `@actview/plugin-babel`(目录 `plugins/babel`),与 `@actview/plugin-vite` / `@actview/plugin-scoped` 统一为 `@actview/plugin-*` 命名。旧包名已弃用,请使用新包名安装与引用。
4
+
5
+ **ActView 编译核心(Babel 插件)** —— 把 JSX 组件函数自动转换为 `defineComponent`,独立于 Vite 宿主。
6
+
7
+ Babel 插件 `defineComponentPlugin` 是 ActView 编译链的**核心**:将大写开头的函数 / 箭头 / 默认导出组件包装为 `defineComponent` 产物(`{ __setup }`),并完成具名插槽提取、props 白名单、早退 return 包装等转换。`@actview/plugin-vite` 与 `@actview/plugin-scoped` 都基于它。
8
+
9
+ 同时本包导出**共享 Babel 宿主壳**(`createBabelTransform` / `createBabelItem` / `transformWithBabel`),供各 Vite 插件复用统一的 `transformSync` 调用参数。
10
+
11
+ ## 核心功能
12
+
13
+ - **组件自动转换**:函数 / 箭头 / 默认导出组件 → `defineComponent`(自动注入 `import { defineComponent } from '@actview/core'`)
14
+ - **具名插槽**:`<template slot>` → `slots` prop(编译期提取)
15
+ - **props 白名单**:从 `defineComponent({ props })` 声明提取 `__props`
16
+ - **早退 return**:组件渲染函数中的 `if / 三元 / &&` 早退分支包装(`isRenderExpr`)
17
+ - **共享宿主壳**:`createBabelTransform` 等,统一 `parserOpts` / `retainLines` / `sourceMaps` / `babelrc:false` / `configFile:false`
18
+
19
+ ## 安装
20
+
21
+ ```bash
22
+ pnpm add -D @actview/plugin-babel
23
+ ```
24
+
25
+ ## 快速开始
26
+
27
+ ```ts
28
+ import { defineComponentPlugin } from '@actview/plugin-babel'
29
+ import * as babel from '@babel/core'
30
+
31
+ const result = babel.transformSync(code, {
32
+ filename: 'App.tsx',
33
+ plugins: [[defineComponentPlugin, {}]],
34
+ parserOpts: { plugins: ['jsx', 'typescript'] },
35
+ })
36
+ ```
37
+
38
+ > 大多数场景不需要直接使用 —— 接入 `@actview/plugin-vite`(Vite 项目)即可自动完成转换。
39
+
40
+ ## API
41
+
42
+ | 导出 | 说明 |
43
+ |---|---|
44
+ | `defineComponentPlugin`(默认导出同) | Babel 插件工厂:组件 → `defineComponent` 转换 |
45
+ | `createBabelTransform(plugin)` | 宿主壳工厂:模块级创建一次 ConfigItem,返回 `(code, filename) => { code, map } \| null` |
46
+ | `createBabelItem(plugin)` | 把插件工厂预编译为 ConfigItem(Babel 8 同步) |
47
+ | `transformWithBabel(code, filename, pluginItem)` | 统一参数的 `transformSync` |
48
+ | 类型:`BabelPlugin` / `BabelHostResult` | 宿主壳类型 |
49
+
50
+ ## 依赖关系
51
+
52
+ - `@babel/core`(^8)
53
+ - 被依赖方:`@actview/plugin-vite`、`@actview/plugin-scoped`(复用宿主壳)
54
+
55
+ ## 开发
56
+
57
+ ```bash
58
+ pnpm build # tsup 打包 dist
59
+ pnpm test # vitest(test/plugin.test.ts:组件转换/插槽/props/早退等 30 用例)
60
+ ```
61
+
62
+ ## License
63
+
64
+ MIT
@@ -0,0 +1,43 @@
1
+ import { PluginObject, PluginItem } from '@babel/core';
2
+
3
+ declare function defineComponentPlugin(): {
4
+ visitor: {
5
+ Program: {
6
+ enter(): void;
7
+ exit(path: any): void;
8
+ };
9
+ FunctionDeclaration(path: any): void;
10
+ VariableDeclarator(path: any): void;
11
+ JSXElement(path: any): void;
12
+ JSXFragment(path: any): void;
13
+ ExportDefaultDeclaration(path: any): void;
14
+ };
15
+ };
16
+
17
+ /** Babel 插件工厂形态(() => { visitor }),createConfigItemSync 的标准输入 */
18
+ type BabelPlugin = () => PluginObject;
19
+ /** transformSync 统一产物(result.map 类型为 BabelSourceMap | null | undefined) */
20
+ interface BabelHostResult {
21
+ code: string;
22
+ map: unknown;
23
+ }
24
+ /** 把插件工厂预编译为 ConfigItem(Babel 8 同步版本,可跨多次 transformSync 复用) */
25
+ declare function createBabelItem(plugin: BabelPlugin): PluginItem;
26
+ /** 统一参数的 transformSync:失败返回 null,成功返回 { code, map } */
27
+ declare function transformWithBabel(code: string, filename: string, pluginItem: PluginItem): BabelHostResult | null;
28
+ /**
29
+ * 静态插件的便捷工厂:模块级调用一次,ConfigItem 只创建一次。
30
+ * 适用于插件对象不随文件变化的场景(如 defineComponentPlugin)。
31
+ */
32
+ declare function createBabelTransform(plugin: BabelPlugin | BabelPlugin[]): (code: string, filename: string) => BabelHostResult | null;
33
+
34
+ declare function solidPlugin(): {
35
+ visitor: {
36
+ Program: {
37
+ enter(): void;
38
+ exit(path: any): void;
39
+ };
40
+ };
41
+ };
42
+
43
+ export { type BabelHostResult, createBabelItem, createBabelTransform, defineComponentPlugin as default, defineComponentPlugin, solidPlugin, transformWithBabel };
package/dist/index.js ADDED
@@ -0,0 +1,709 @@
1
+ // src/babel-plugin.ts
2
+ import { types as t } from "@babel/core";
3
+ import generate from "@babel/generator";
4
+ function extractNamedSlots(el) {
5
+ const nameNode = el.openingElement.name;
6
+ if (!t.isJSXIdentifier(nameNode) || !/^[A-Z]/.test(nameNode.name)) return;
7
+ const children = el.children;
8
+ const slotProps = [];
9
+ const remaining = [];
10
+ for (const child of children) {
11
+ if (t.isJSXElement(child) && t.isJSXIdentifier(child.openingElement.name, { name: "template" })) {
12
+ const attrs = child.openingElement.attributes;
13
+ const slotAttr = attrs.find(
14
+ (a) => t.isJSXAttribute(a) && t.isJSXIdentifier(a.name, { name: "slot" })
15
+ );
16
+ if (slotAttr && t.isStringLiteral(slotAttr.value)) {
17
+ const scopeParams = attrs.filter(
18
+ (a) => t.isJSXAttribute(a) && !t.isJSXIdentifier(a.name, { name: "slot" }) && !a.value
19
+ ).map((a) => t.identifier(a.name.name));
20
+ const slotBody = t.jsxFragment(
21
+ t.jsxOpeningFragment(),
22
+ t.jsxClosingFragment(),
23
+ child.children
24
+ );
25
+ slotProps.push(
26
+ t.objectProperty(
27
+ t.stringLiteral(slotAttr.value.value),
28
+ t.arrowFunctionExpression(scopeParams, slotBody)
29
+ )
30
+ );
31
+ continue;
32
+ }
33
+ }
34
+ remaining.push(child);
35
+ }
36
+ if (!slotProps.length) return;
37
+ el.children = remaining;
38
+ el.openingElement.attributes.push(
39
+ t.jsxAttribute(
40
+ t.jsxIdentifier("slots"),
41
+ t.jsxExpressionContainer(t.objectExpression(slotProps))
42
+ )
43
+ );
44
+ }
45
+ function walkJSX(node) {
46
+ if (t.isJSXElement(node)) {
47
+ extractNamedSlots(node);
48
+ node.children.forEach(walkJSX);
49
+ } else if (t.isJSXFragment(node)) {
50
+ node.children.forEach(walkJSX);
51
+ } else if (t.isJSXExpressionContainer(node)) {
52
+ walkExpression(node.expression);
53
+ }
54
+ }
55
+ function walkExpression(expr) {
56
+ if (!expr) return;
57
+ if (t.isJSXElement(expr) || t.isJSXFragment(expr)) {
58
+ walkJSX(expr);
59
+ } else if (t.isJSXExpressionContainer(expr)) {
60
+ walkExpression(expr.expression);
61
+ } else if (t.isLogicalExpression(expr) || t.isBinaryExpression(expr)) {
62
+ walkExpression(expr.left);
63
+ walkExpression(expr.right);
64
+ } else if (t.isConditionalExpression(expr)) {
65
+ walkExpression(expr.consequent);
66
+ walkExpression(expr.alternate);
67
+ } else if (t.isArrowFunctionExpression(expr) || t.isFunctionExpression(expr)) {
68
+ walkExpression(expr.body);
69
+ } else if (t.isCallExpression(expr)) {
70
+ expr.arguments.forEach(walkExpression);
71
+ } else if (t.isArrayExpression(expr)) {
72
+ expr.elements.forEach(walkExpression);
73
+ } else if (t.isObjectExpression(expr)) {
74
+ expr.properties.forEach((p) => walkExpression(p.value));
75
+ }
76
+ }
77
+ function defineComponentPlugin() {
78
+ let hasTransformed = false;
79
+ const jstate = { usedJsx: false, usedFragment: false };
80
+ return {
81
+ visitor: {
82
+ Program: {
83
+ enter() {
84
+ hasTransformed = false;
85
+ jstate.usedJsx = false;
86
+ jstate.usedFragment = false;
87
+ },
88
+ exit(path) {
89
+ if (jstate.usedJsx) {
90
+ const jsxSpecs = [];
91
+ if (jstate.usedJsx) {
92
+ jsxSpecs.push(t.importSpecifier(t.identifier("_jsx"), t.identifier("jsx")));
93
+ }
94
+ if (jstate.usedFragment) {
95
+ jsxSpecs.push(
96
+ t.importSpecifier(t.identifier("_Fragment"), t.identifier("Fragment"))
97
+ );
98
+ }
99
+ const hasJsxImport = path.node.body.some(
100
+ (n) => t.isImportDeclaration(n) && n.source.value === "@actview/jsx/jsx-runtime"
101
+ );
102
+ if (!hasJsxImport) {
103
+ path.node.body.unshift(
104
+ t.importDeclaration(jsxSpecs, t.stringLiteral("@actview/jsx/jsx-runtime"))
105
+ );
106
+ }
107
+ }
108
+ if (!hasTransformed) return;
109
+ const alreadyImported = path.node.body.some(
110
+ (n) => t.isImportDeclaration(n) && n.specifiers.some(
111
+ (s) => s.imported?.name === "defineComponent"
112
+ )
113
+ );
114
+ if (!alreadyImported) {
115
+ path.node.body.unshift(
116
+ t.importDeclaration(
117
+ [t.importSpecifier(t.identifier("defineComponent"), t.identifier("defineComponent"))],
118
+ t.stringLiteral("@actview/core")
119
+ )
120
+ );
121
+ }
122
+ }
123
+ },
124
+ FunctionDeclaration(path) {
125
+ const node = path.node;
126
+ if (!node.id) return;
127
+ const name = node.id.name;
128
+ if (!/^[A-Z]/.test(name)) return;
129
+ const fn = t.functionExpression(null, node.params, node.body, false, false);
130
+ const wrapped = wrapComponentFn(fn, name);
131
+ if (!wrapped) return;
132
+ const call = wrapped;
133
+ hasTransformed = true;
134
+ wrapEarlyReturns(path);
135
+ path.replaceWith(
136
+ t.variableDeclaration("const", [
137
+ t.variableDeclarator(node.id, call)
138
+ ])
139
+ );
140
+ },
141
+ // 缺陷 2 修复:函数表达式 / 箭头函数组件
142
+ // const X = function (props) {...}
143
+ // const X = (props) => <JSX> / (props) => { ...; return function(){...} }
144
+ VariableDeclarator(path) {
145
+ const node = path.node;
146
+ const id = node.id;
147
+ if (!t.isIdentifier(id) || !/^[A-Z]/.test(id.name)) return;
148
+ const init = node.init;
149
+ const isFn = t.isFunctionExpression(init) || t.isArrowFunctionExpression(init);
150
+ if (!isFn) return;
151
+ const wrapped = wrapComponentFn(init, id.name);
152
+ if (!wrapped) return;
153
+ const call = wrapped;
154
+ hasTransformed = true;
155
+ const initPath = path.get("init");
156
+ wrapEarlyReturns(initPath);
157
+ node.init = call;
158
+ },
159
+ // 顺带支持:export default (props) => <JSX>(默认导出箭头/函数/匿名函数组件)
160
+ // JSX 编译:组件转换(含 <solid> 提取)之后剩余 JSX → _jsx 调用
161
+ JSXElement(path) {
162
+ const name = path.node.openingElement.name;
163
+ if (t.isJSXIdentifier(name) && name.name === "solid") {
164
+ path.replaceWith(buildSolidMark(path.node, jstate));
165
+ return;
166
+ }
167
+ path.replaceWith(compileJsxElement(path.node, jstate));
168
+ },
169
+ JSXFragment(path) {
170
+ path.replaceWith(compileJsxFragment(path.node, jstate));
171
+ },
172
+ ExportDefaultDeclaration(path) {
173
+ const decl = path.node.declaration;
174
+ const isFn = t.isFunctionExpression(decl) || t.isArrowFunctionExpression(decl) || // export default function() {...}(匿名函数声明)
175
+ t.isFunctionDeclaration(decl);
176
+ if (!isFn) return;
177
+ const fn = t.isFunctionDeclaration(decl) ? t.functionExpression(null, decl.params, decl.body, false, false) : decl;
178
+ const wrapped = wrapComponentFn(fn);
179
+ if (!wrapped) return;
180
+ const call = wrapped;
181
+ hasTransformed = true;
182
+ const declPath = path.get("declaration");
183
+ wrapEarlyReturns(declPath);
184
+ path.node.declaration = call;
185
+ }
186
+ }
187
+ };
188
+ }
189
+ function wrapComponentFn(fn, name) {
190
+ const body = fn.body;
191
+ const isExprBody = !t.isBlockStatement(body);
192
+ let last = null;
193
+ let ret;
194
+ if (isExprBody) {
195
+ ret = body;
196
+ } else {
197
+ const stmts = body.body;
198
+ if (stmts.length === 0) return null;
199
+ last = stmts[stmts.length - 1];
200
+ if (!t.isReturnStatement(last)) return null;
201
+ ret = last.argument;
202
+ if (ret == null) return null;
203
+ }
204
+ const isJsx = t.isJSXElement(ret) || t.isJSXFragment(ret);
205
+ const isJsxCall = t.isCallExpression(ret) && t.isIdentifier(ret.callee) && /^_?jsx/.test(ret.callee.name);
206
+ const isNullRet = t.isNullLiteral(ret);
207
+ const isCondRet = isRenderExpr(ret);
208
+ if (!isJsx && !isJsxCall && !isNullRet && !isCondRet) return null;
209
+ if (isJsx) walkJSX(ret);
210
+ if (isExprBody) {
211
+ fn.body = t.blockStatement([
212
+ t.returnStatement(t.arrowFunctionExpression([], ret))
213
+ ]);
214
+ } else if (isJsx || isJsxCall || isCondRet) {
215
+ last.argument = t.arrowFunctionExpression([], ret);
216
+ } else if (isNullRet) {
217
+ last.argument = t.arrowFunctionExpression([], t.nullLiteral());
218
+ }
219
+ const args = [fn];
220
+ if (name) args.push(t.stringLiteral(name));
221
+ return t.callExpression(t.identifier("defineComponent"), args);
222
+ }
223
+ function wrapEarlyReturns(fnPath) {
224
+ fnPath.traverse({
225
+ ReturnStatement(innerPath) {
226
+ if (innerPath.getFunctionParent() !== fnPath) return;
227
+ const arg = innerPath.node.argument;
228
+ if (arg == null) return;
229
+ const isStmtJsx = t.isJSXElement(arg) || t.isJSXFragment(arg);
230
+ const isStmtJsxCall = t.isCallExpression(arg) && t.isIdentifier(arg.callee) && /^_?jsx/.test(arg.callee.name);
231
+ const isStmtNull = t.isNullLiteral(arg);
232
+ const isStmtCond = isRenderExpr(arg);
233
+ if (isStmtJsx || isStmtJsxCall || isStmtNull || isStmtCond) {
234
+ innerPath.node.argument = t.arrowFunctionExpression([], arg);
235
+ }
236
+ }
237
+ });
238
+ }
239
+ function isRenderExpr(expr) {
240
+ if (!expr) return false;
241
+ if (t.isJSXElement(expr) || t.isJSXFragment(expr)) return true;
242
+ if (t.isCallExpression(expr) && t.isIdentifier(expr.callee) && /^_?jsx/.test(expr.callee.name)) {
243
+ return true;
244
+ }
245
+ if (t.isConditionalExpression(expr)) {
246
+ return isRenderExpr(expr.consequent) || isRenderExpr(expr.alternate);
247
+ }
248
+ if (t.isLogicalExpression(expr)) {
249
+ return isRenderExpr(expr.right) || isRenderExpr(expr.left);
250
+ }
251
+ return false;
252
+ }
253
+ function compileJsxElement(el, state) {
254
+ const name = el.openingElement.name;
255
+ let typeExpr;
256
+ if (t.isJSXIdentifier(name)) {
257
+ typeExpr = /^[a-z]/.test(name.name) ? t.stringLiteral(name.name) : t.identifier(name.name);
258
+ } else if (t.isJSXMemberExpression(name)) {
259
+ typeExpr = jsxMemberToMember(name);
260
+ } else {
261
+ typeExpr = t.stringLiteral(name.name);
262
+ }
263
+ let keyExpr = null;
264
+ const propEntries = [];
265
+ for (const attr of el.openingElement.attributes) {
266
+ if (t.isJSXSpreadAttribute(attr)) {
267
+ propEntries.push(t.spreadElement(attr.argument));
268
+ continue;
269
+ }
270
+ const aname = attr.name.name;
271
+ let value;
272
+ if (attr.value == null) {
273
+ value = t.booleanLiteral(true);
274
+ } else if (t.isStringLiteral(attr.value)) {
275
+ value = attr.value;
276
+ } else {
277
+ value = attr.value.expression;
278
+ }
279
+ if (aname === "key") {
280
+ keyExpr = t.isStringLiteral(value) ? value : value;
281
+ continue;
282
+ }
283
+ propEntries.push(t.objectProperty(t.stringLiteral(aname), value));
284
+ }
285
+ const children = compileJsxChildren(el.children, state);
286
+ if (children.length === 1) {
287
+ propEntries.push(t.objectProperty(t.stringLiteral("children"), children[0]));
288
+ } else if (children.length > 1) {
289
+ propEntries.push(
290
+ t.objectProperty(t.stringLiteral("children"), t.arrayExpression(children))
291
+ );
292
+ }
293
+ state.usedJsx = true;
294
+ const args = [typeExpr, t.objectExpression(propEntries)];
295
+ args.push(keyExpr ?? t.identifier("undefined"));
296
+ return t.callExpression(t.identifier("_jsx"), args);
297
+ }
298
+ function compileJsxFragment(frag, state) {
299
+ const children = compileJsxChildren(frag.children, state);
300
+ state.usedJsx = true;
301
+ state.usedFragment = true;
302
+ const propEntries = [];
303
+ if (children.length === 1) {
304
+ propEntries.push(t.objectProperty(t.stringLiteral("children"), children[0]));
305
+ } else if (children.length > 1) {
306
+ propEntries.push(
307
+ t.objectProperty(t.stringLiteral("children"), t.arrayExpression(children))
308
+ );
309
+ }
310
+ return t.callExpression(t.identifier("_jsx"), [
311
+ t.identifier("_Fragment"),
312
+ t.objectExpression(propEntries),
313
+ t.identifier("undefined")
314
+ ]);
315
+ }
316
+ function compileJsxChildren(children, state) {
317
+ const out = [];
318
+ for (const c of children) {
319
+ if (t.isJSXText(c)) {
320
+ if (c.value.trim() === "") continue;
321
+ out.push(t.stringLiteral(processJsxText(c.value)));
322
+ } else if (t.isJSXExpressionContainer(c)) {
323
+ if (t.isJSXEmptyExpression(c.expression)) continue;
324
+ out.push(c.expression);
325
+ } else if (t.isJSXElement(c)) {
326
+ const cname = c.openingElement.name;
327
+ if (t.isJSXIdentifier(cname) && cname.name === "solid") {
328
+ out.push(buildSolidMark(c, state));
329
+ } else {
330
+ out.push(compileJsxElement(c, state));
331
+ }
332
+ } else if (t.isJSXFragment(c)) {
333
+ out.push(compileJsxFragment(c, state));
334
+ }
335
+ }
336
+ return out;
337
+ }
338
+ function jsxMemberToMember(name) {
339
+ const obj = t.isJSXMemberExpression(name.object) ? jsxMemberToMember(name.object) : t.identifier(name.object.name);
340
+ return t.memberExpression(obj, t.identifier(name.property.name));
341
+ }
342
+ function processJsxText(value) {
343
+ if (!value.includes("\n")) return value;
344
+ return value.replace(/^[ \t]*\n[ \t]*/, "").replace(/[ \t]*\n[ \t]*$/, "").replace(/[ \t]*\n[ \t]*/g, " ");
345
+ }
346
+ function buildSolidMark(el, state) {
347
+ state.usedJsx = true;
348
+ const parts = [];
349
+ for (const c of el.children) {
350
+ if (t.isJSXText(c)) {
351
+ if (c.value.trim() === "") continue;
352
+ parts.push(c.value);
353
+ } else if (t.isJSXExpressionContainer(c)) {
354
+ if (t.isJSXEmptyExpression(c.expression)) continue;
355
+ parts.push(generate(c.expression).code);
356
+ } else {
357
+ parts.push(generate(c).code);
358
+ }
359
+ }
360
+ return t.callExpression(t.identifier("_jsx"), [
361
+ t.stringLiteral("solid"),
362
+ t.objectExpression([
363
+ t.objectProperty(
364
+ t.stringLiteral("children"),
365
+ t.arrayExpression(parts.map((p) => t.stringLiteral(p)))
366
+ )
367
+ ]),
368
+ t.identifier("undefined")
369
+ ]);
370
+ }
371
+
372
+ // src/babel-host.ts
373
+ import * as babel from "@babel/core";
374
+ function createBabelItem(plugin) {
375
+ return babel.createConfigItemSync(plugin, { type: "plugin" });
376
+ }
377
+ function transformWithBabel(code, filename, pluginItem) {
378
+ const result = babel.transformSync(code, {
379
+ filename,
380
+ plugins: [pluginItem],
381
+ parserOpts: { plugins: ["jsx", "typescript"] },
382
+ retainLines: true,
383
+ sourceMaps: true,
384
+ babelrc: false,
385
+ configFile: false
386
+ });
387
+ if (!result) return null;
388
+ return { code: result.code || code, map: result.map };
389
+ }
390
+ function createBabelTransform(plugin) {
391
+ const items = (Array.isArray(plugin) ? plugin : [plugin]).map((p) => createBabelItem(p));
392
+ return (code, filename) => {
393
+ const result = babel.transformSync(code, {
394
+ filename,
395
+ plugins: items,
396
+ parserOpts: { plugins: ["jsx", "typescript"] },
397
+ retainLines: true,
398
+ sourceMaps: true,
399
+ babelrc: false,
400
+ configFile: false
401
+ });
402
+ if (!result) return null;
403
+ return { code: result.code || code, map: result.map };
404
+ };
405
+ }
406
+
407
+ // src/solid-plugin.ts
408
+ import { types as t2, parseSync } from "@babel/core";
409
+ var solidVarCounter = 0;
410
+ function solidPlugin() {
411
+ let usedSolid = false;
412
+ return {
413
+ visitor: {
414
+ Program: {
415
+ enter() {
416
+ usedSolid = false;
417
+ solidVarCounter = 0;
418
+ },
419
+ exit(path) {
420
+ path.traverse({
421
+ CallExpression(p) {
422
+ handleSolidCall(p, () => {
423
+ usedSolid = true;
424
+ });
425
+ }
426
+ });
427
+ if (!usedSolid) return;
428
+ const hasImport = path.node.body.some(
429
+ (n) => t2.isImportDeclaration(n) && n.source.value === "@actview/core" && n.specifiers.some((s) => s.imported?.name === "solidGet")
430
+ );
431
+ if (!hasImport) {
432
+ path.node.body.unshift(
433
+ t2.importDeclaration(
434
+ ["solidGet", "createEffect", "mapArray"].map(
435
+ (n) => t2.importSpecifier(t2.identifier(n), t2.identifier(n))
436
+ ),
437
+ t2.stringLiteral("@actview/core")
438
+ )
439
+ );
440
+ }
441
+ }
442
+ }
443
+ }
444
+ };
445
+ }
446
+ function handleSolidCall(path, markUsed) {
447
+ const node = path.node;
448
+ if (!(t2.isIdentifier(node.callee) && /^_?jsx/.test(node.callee.name))) return;
449
+ if (!(node.arguments[0] && t2.isStringLiteral(node.arguments[0], { value: "solid" }))) return;
450
+ const propsObj = node.arguments[1];
451
+ if (!t2.isObjectExpression(propsObj)) return;
452
+ const childrenProp = propsObj.properties.find(
453
+ (p) => t2.isObjectProperty(p) && t2.isStringLiteral(p.key, { value: "children" })
454
+ );
455
+ if (!childrenProp || !t2.isArrayExpression(childrenProp.value)) return;
456
+ const srcs = [];
457
+ for (const e of childrenProp.value.elements) {
458
+ if (e && t2.isStringLiteral(e)) srcs.push(e.value);
459
+ }
460
+ if (srcs.length === 0 || srcs.every((s) => !s.trim())) return;
461
+ const counter = { n: 0 };
462
+ const stmts = [];
463
+ try {
464
+ for (const src of srcs) {
465
+ const ast = parseSync(src, { parserOpts: { plugins: ["jsx", "typescript"] } });
466
+ if (!ast) return;
467
+ for (const st of ast.program.body) {
468
+ if (t2.isExpressionStatement(st)) {
469
+ compileSolidTop(st.expression, t2.identifier("container"), stmts, counter);
470
+ } else {
471
+ stmts.push(st);
472
+ }
473
+ }
474
+ }
475
+ } catch (e) {
476
+ console.error("[solid-plugin] compile error:", e?.message);
477
+ return;
478
+ }
479
+ if (stmts.length === 0) return;
480
+ markUsed();
481
+ const holderId = t2.identifier("_solid$$" + ++solidVarCounter);
482
+ const renderFn = path.getFunctionParent();
483
+ const setupFn = renderFn && renderFn.getFunctionParent();
484
+ const holderDecl = t2.variableDeclaration("const", [
485
+ t2.variableDeclarator(holderId, t2.objectExpression([]))
486
+ ]);
487
+ if (setupFn && t2.isFunction(setupFn.node)) {
488
+ setupFn.get("body").unshiftContainer("body", holderDecl);
489
+ } else {
490
+ const prog = path.findParent((p) => p.isProgram());
491
+ prog?.unshiftContainer("body", holderDecl);
492
+ }
493
+ path.replaceWith(
494
+ t2.callExpression(t2.identifier("solidGet"), [
495
+ holderId,
496
+ t2.arrowFunctionExpression(
497
+ [t2.identifier("container")],
498
+ t2.blockStatement(stmts)
499
+ )
500
+ ])
501
+ );
502
+ }
503
+ function compileSolidTop(expr, containerVar, stmts, counter) {
504
+ if (t2.isJSXElement(expr)) {
505
+ return compileSolidElement(expr, stmts, counter);
506
+ }
507
+ if (t2.isJSXFragment(expr)) {
508
+ compileSolidChildren(expr.children, containerVar, stmts, counter);
509
+ return null;
510
+ }
511
+ const mapCall = matchMapCall(expr);
512
+ if (mapCall) {
513
+ stmts.push(
514
+ t2.expressionStatement(
515
+ t2.callExpression(t2.identifier("mapArray"), [
516
+ t2.arrowFunctionExpression([], mapCall.listExpr),
517
+ containerVar,
518
+ compileMapArrow(mapCall.arrow, counter)
519
+ ])
520
+ )
521
+ );
522
+ return null;
523
+ }
524
+ const textVar = t2.identifier("_t" + ++counter.n);
525
+ stmts.push(
526
+ t2.variableDeclaration("const", [
527
+ t2.variableDeclarator(
528
+ textVar,
529
+ t2.callExpression(t2.memberExpression(t2.identifier("document"), t2.identifier("createTextNode")), [
530
+ t2.stringLiteral("")
531
+ ])
532
+ )
533
+ ])
534
+ );
535
+ stmts.push(
536
+ t2.expressionStatement(
537
+ t2.callExpression(t2.identifier("createEffect"), [
538
+ t2.arrowFunctionExpression(
539
+ [],
540
+ t2.assignmentExpression(
541
+ "=",
542
+ t2.memberExpression(textVar, t2.identifier("textContent")),
543
+ expr
544
+ )
545
+ )
546
+ ])
547
+ )
548
+ );
549
+ stmts.push(appendStmt(containerVar, textVar));
550
+ return null;
551
+ }
552
+ function compileSolidChildren(children, containerVar, stmts, counter) {
553
+ for (const c of children) {
554
+ if (t2.isJSXText(c)) {
555
+ if (c.value.trim() === "") continue;
556
+ const textVar = t2.identifier("_t" + ++counter.n);
557
+ stmts.push(
558
+ t2.variableDeclaration("const", [
559
+ t2.variableDeclarator(
560
+ textVar,
561
+ t2.callExpression(t2.memberExpression(t2.identifier("document"), t2.identifier("createTextNode")), [
562
+ t2.stringLiteral(c.value)
563
+ ])
564
+ )
565
+ ])
566
+ );
567
+ stmts.push(appendStmt(containerVar, textVar));
568
+ } else if (t2.isJSXExpressionContainer(c)) {
569
+ const expr = c.expression;
570
+ if (t2.isJSXEmptyExpression(expr)) continue;
571
+ const mapCall = matchMapCall(expr);
572
+ if (mapCall) {
573
+ stmts.push(
574
+ t2.expressionStatement(
575
+ t2.callExpression(t2.identifier("mapArray"), [
576
+ t2.arrowFunctionExpression([], mapCall.listExpr),
577
+ containerVar,
578
+ compileMapArrow(mapCall.arrow, counter)
579
+ ])
580
+ )
581
+ );
582
+ } else {
583
+ const textVar = t2.identifier("_t" + ++counter.n);
584
+ stmts.push(
585
+ t2.variableDeclaration("const", [
586
+ t2.variableDeclarator(
587
+ textVar,
588
+ t2.callExpression(t2.memberExpression(t2.identifier("document"), t2.identifier("createTextNode")), [
589
+ t2.stringLiteral("")
590
+ ])
591
+ )
592
+ ])
593
+ );
594
+ stmts.push(
595
+ t2.expressionStatement(
596
+ t2.callExpression(t2.identifier("createEffect"), [
597
+ t2.arrowFunctionExpression(
598
+ [],
599
+ t2.assignmentExpression(
600
+ "=",
601
+ t2.memberExpression(textVar, t2.identifier("textContent")),
602
+ expr
603
+ )
604
+ )
605
+ ])
606
+ )
607
+ );
608
+ stmts.push(appendStmt(containerVar, textVar));
609
+ }
610
+ } else if (t2.isJSXElement(c)) {
611
+ const elVar = compileSolidElement(c, stmts, counter);
612
+ stmts.push(appendStmt(containerVar, elVar));
613
+ } else if (t2.isJSXFragment(c)) {
614
+ compileSolidChildren(c.children, containerVar, stmts, counter);
615
+ }
616
+ }
617
+ }
618
+ function compileSolidElement(el, stmts, counter) {
619
+ const name = el.openingElement.name;
620
+ const tag = t2.isJSXIdentifier(name) ? name.name : name.name;
621
+ const elVar = t2.identifier("_el" + ++counter.n);
622
+ stmts.push(
623
+ t2.variableDeclaration("const", [
624
+ t2.variableDeclarator(
625
+ elVar,
626
+ t2.callExpression(t2.memberExpression(t2.identifier("document"), t2.identifier("createElement")), [
627
+ t2.stringLiteral(tag)
628
+ ])
629
+ )
630
+ ])
631
+ );
632
+ for (const attr of el.openingElement.attributes) {
633
+ if (!t2.isJSXAttribute(attr)) continue;
634
+ const attrName = attr.name.name;
635
+ if (attrName === "key" || attrName === "ref" || attrName === "v-memo") continue;
636
+ if (/^on[A-Z]/.test(attrName)) {
637
+ const evt = attrName[2].toLowerCase() + attrName.slice(3);
638
+ const handler = attr.value?.expression ?? attr.value ?? t2.identifier("undefined");
639
+ stmts.push(
640
+ t2.expressionStatement(
641
+ t2.callExpression(t2.memberExpression(elVar, t2.identifier("addEventListener")), [
642
+ t2.stringLiteral(evt),
643
+ handler
644
+ ])
645
+ )
646
+ );
647
+ continue;
648
+ }
649
+ if (attr.value && t2.isJSXExpressionContainer(attr.value)) {
650
+ stmts.push(
651
+ t2.expressionStatement(
652
+ t2.callExpression(t2.identifier("createEffect"), [
653
+ t2.arrowFunctionExpression(
654
+ [],
655
+ t2.callExpression(t2.memberExpression(elVar, t2.identifier("setAttribute")), [
656
+ t2.stringLiteral(attrName),
657
+ attr.value.expression
658
+ ])
659
+ )
660
+ ])
661
+ )
662
+ );
663
+ } else {
664
+ const value = attr.value ? t2.isStringLiteral(attr.value) ? attr.value.value : true : true;
665
+ stmts.push(
666
+ t2.expressionStatement(
667
+ t2.callExpression(t2.memberExpression(elVar, t2.identifier("setAttribute")), [
668
+ t2.stringLiteral(attrName),
669
+ value === true ? t2.stringLiteral("true") : t2.stringLiteral(value)
670
+ ])
671
+ )
672
+ );
673
+ }
674
+ }
675
+ compileSolidChildren(el.children, elVar, stmts, counter);
676
+ return elVar;
677
+ }
678
+ function appendStmt(containerVar, childVar) {
679
+ return t2.expressionStatement(
680
+ t2.callExpression(t2.memberExpression(containerVar, t2.identifier("appendChild")), [childVar])
681
+ );
682
+ }
683
+ function matchMapCall(expr) {
684
+ if (t2.isCallExpression(expr) && t2.isMemberExpression(expr.callee) && t2.isIdentifier(expr.callee.property, { name: "map" }) && expr.arguments.length === 1 && t2.isArrowFunctionExpression(expr.arguments[0])) {
685
+ return { listExpr: expr.callee.object, arrow: expr.arguments[0] };
686
+ }
687
+ return null;
688
+ }
689
+ function compileMapArrow(arrow, counter) {
690
+ const stmts = [];
691
+ const bodyExpr = arrow.body;
692
+ if (t2.isJSXElement(bodyExpr)) {
693
+ const elVar = compileSolidElement(bodyExpr, stmts, counter);
694
+ stmts.push(t2.returnStatement(elVar));
695
+ return t2.arrowFunctionExpression(arrow.params, t2.blockStatement(stmts));
696
+ }
697
+ if (t2.isJSXFragment(bodyExpr)) {
698
+ return arrow;
699
+ }
700
+ return arrow;
701
+ }
702
+ export {
703
+ createBabelItem,
704
+ createBabelTransform,
705
+ defineComponentPlugin as default,
706
+ defineComponentPlugin,
707
+ solidPlugin,
708
+ transformWithBabel
709
+ };
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@actview/plugin-babel",
3
+ "version": "1.0.3",
4
+ "type": "module",
5
+ "description": "Babel 插件:JSX 组件自动 defineComponent 转换(ActView 编译核心,独立于 Vite 宿主)",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ }
11
+ },
12
+ "dependencies": {
13
+ "@babel/core": "^8.0.1",
14
+ "@babel/generator": "^8.0.0"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsup"
24
+ },
25
+ "main": "./dist/index.js",
26
+ "module": "./dist/index.js",
27
+ "types": "./dist/index.d.ts"
28
+ }