@actview/plugin-babel 1.0.7 → 2.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/README.md CHANGED
@@ -42,10 +42,32 @@ const result = babel.transformSync(code, {
42
42
  | 导出 | 说明 |
43
43
  |---|---|
44
44
  | `defineComponentPlugin`(默认导出同) | Babel 插件工厂:组件 → `defineComponent` 转换 |
45
- | `createBabelTransform(plugin)` | 宿主壳工厂:模块级创建一次 ConfigItem,返回 `(code, filename) => { code, map } \| null` |
45
+ | `createBabelTransform(plugin, options?)` | 宿主壳工厂:模块级创建一次 ConfigItem,返回 `(code, filename) => { code, map } \| null` |
46
46
  | `createBabelItem(plugin)` | 把插件工厂预编译为 ConfigItem(Babel 8 同步) |
47
- | `transformWithBabel(code, filename, pluginItem)` | 统一参数的 `transformSync` |
48
- | 类型:`BabelPlugin` / `BabelHostResult` | 宿主壳类型 |
47
+ | `transformWithBabel(code, filename, pluginItem, options?)` | 统一参数的 `transformSync` |
48
+ | `isExcludedTransform(filename, options?)` | 排除判定:node_modules 硬排除 / 未命中 include / 命中 exclude |
49
+ | 类型:`BabelPlugin` / `BabelHostResult` / `BabelTransformOptions` | 宿主壳类型 |
50
+
51
+ ## 排除规则(node_modules 硬排除)
52
+
53
+ 宿主壳**硬排除 node_modules 下的文件**(返回 `null` 不转换):依赖是第三方代码,属依赖管线(esbuild 预构建),不是源码管线——`babel-loader` / `@rollup/plugin-babel` 同款默认。`BabelTransformOptions`:
54
+
55
+ ```ts
56
+ {
57
+ include?: Array<string | RegExp>, // 白名单:命中任一才转换(默认不过滤;node_modules 硬排除优先)
58
+ exclude?: Array<string | RegExp>, // 黑名单:命中任一即跳过(优先于 include)
59
+ }
60
+ ```
61
+
62
+ **源码分发库包 / 主题包(node_modules 下)需要现场编译时**,不在插件层开洞,而是在宿主构建配置里做**路径转换**,让文件解析路径脱离 node_modules 段、进入源码管线:
63
+
64
+ ```ts
65
+ // vite.config.ts
66
+ resolve: {
67
+ alias: { 'my-lib': 'node_modules/my-lib/src' }, // 路径不再含 node_modules 段 → 正常转换
68
+ },
69
+ optimizeDeps: { exclude: ['my-lib'] }, // 不预构建,保持源码形态
70
+ ```
49
71
 
50
72
  ## 依赖关系
51
73
 
package/dist/index.d.ts CHANGED
@@ -21,15 +21,24 @@ interface BabelHostResult {
21
21
  code: string;
22
22
  map: unknown;
23
23
  }
24
+ /** 宿主壳排除规则(createBabelTransform / transformWithBabel 第二/四参) */
25
+ interface BabelTransformOptions {
26
+ /** 白名单:文件命中任一规则才转换(默认不过滤;node_modules 硬排除优先) */
27
+ include?: Array<string | RegExp>;
28
+ /** 黑名单:命中任一规则即跳过(优先级高于 include) */
29
+ exclude?: Array<string | RegExp>;
30
+ }
31
+ /** 是否应跳过转换:node_modules 硬排除 / 未命中 include / 命中 exclude */
32
+ declare function isExcludedTransform(filename: string, options?: BabelTransformOptions): boolean;
24
33
  /** 把插件工厂预编译为 ConfigItem(Babel 8 同步版本,可跨多次 transformSync 复用) */
25
34
  declare function createBabelItem(plugin: BabelPlugin): PluginItem;
26
- /** 统一参数的 transformSync:失败返回 null,成功返回 { code, map } */
27
- declare function transformWithBabel(code: string, filename: string, pluginItem: PluginItem): BabelHostResult | null;
35
+ /** 统一参数的 transformSync:排除命中或失败返回 null,成功返回 { code, map } */
36
+ declare function transformWithBabel(code: string, filename: string, pluginItem: PluginItem, options?: BabelTransformOptions): BabelHostResult | null;
28
37
  /**
29
38
  * 静态插件的便捷工厂:模块级调用一次,ConfigItem 只创建一次。
30
39
  * 适用于插件对象不随文件变化的场景(如 defineComponentPlugin)。
31
40
  */
32
- declare function createBabelTransform(plugin: BabelPlugin | BabelPlugin[]): (code: string, filename: string) => BabelHostResult | null;
41
+ declare function createBabelTransform(plugin: BabelPlugin | BabelPlugin[], options?: BabelTransformOptions): (code: string, filename: string) => BabelHostResult | null;
33
42
 
34
43
  declare function solidPlugin(): {
35
44
  visitor: {
@@ -40,4 +49,4 @@ declare function solidPlugin(): {
40
49
  };
41
50
  };
42
51
 
43
- export { type BabelHostResult, createBabelItem, createBabelTransform, defineComponentPlugin as default, defineComponentPlugin, solidPlugin, transformWithBabel };
52
+ export { type BabelHostResult, type BabelTransformOptions, createBabelItem, createBabelTransform, defineComponentPlugin as default, defineComponentPlugin, isExcludedTransform, solidPlugin, transformWithBabel };
package/dist/index.js CHANGED
@@ -130,9 +130,10 @@ function defineComponentPlugin() {
130
130
  if (!node.id) return;
131
131
  const name = node.id.name;
132
132
  if (!/^[A-Z]/.test(name)) return;
133
+ assertNoSetupStyleEarlyReturns(path, name);
133
134
  const fn = t.functionExpression(null, node.params, node.body, false, false);
134
135
  if (node.typeParameters) fn.typeParameters = node.typeParameters;
135
- const wrapped = wrapComponentFn(fn, name);
136
+ const wrapped = wrapComponentFn(fn, name, path);
136
137
  if (!wrapped) return;
137
138
  const call = wrapped;
138
139
  hasTransformed = true;
@@ -153,11 +154,12 @@ function defineComponentPlugin() {
153
154
  const init = node.init;
154
155
  const isFn = t.isFunctionExpression(init) || t.isArrowFunctionExpression(init);
155
156
  if (!isFn) return;
156
- const wrapped = wrapComponentFn(init, id.name);
157
+ const initPath = path.get("init");
158
+ assertNoSetupStyleEarlyReturns(initPath, id.name);
159
+ const wrapped = wrapComponentFn(init, id.name, initPath);
157
160
  if (!wrapped) return;
158
161
  const call = wrapped;
159
162
  hasTransformed = true;
160
- const initPath = path.get("init");
161
163
  wrapEarlyReturns(initPath);
162
164
  node.init = call;
163
165
  },
@@ -184,18 +186,19 @@ function defineComponentPlugin() {
184
186
  ;
185
187
  fn.typeParameters = decl.typeParameters;
186
188
  }
187
- const wrapped = wrapComponentFn(fn);
189
+ const declPath = path.get("declaration");
190
+ assertNoSetupStyleEarlyReturns(declPath);
191
+ const wrapped = wrapComponentFn(fn, void 0, path);
188
192
  if (!wrapped) return;
189
193
  const call = wrapped;
190
194
  hasTransformed = true;
191
- const declPath = path.get("declaration");
192
195
  wrapEarlyReturns(declPath);
193
196
  path.node.declaration = call;
194
197
  }
195
198
  }
196
199
  };
197
200
  }
198
- function wrapComponentFn(fn, name) {
201
+ function wrapComponentFn(fn, name, fnPath) {
199
202
  const body = fn.body;
200
203
  const isExprBody = !t.isBlockStatement(body);
201
204
  let last = null;
@@ -214,7 +217,13 @@ function wrapComponentFn(fn, name) {
214
217
  const isJsxCall = t.isCallExpression(ret) && t.isIdentifier(ret.callee) && /^_?jsx/.test(ret.callee.name);
215
218
  const isNullRet = t.isNullLiteral(ret);
216
219
  const isCondRet = isRenderExpr(ret);
217
- if (!isJsx && !isJsxCall && !isNullRet && !isCondRet) return null;
220
+ if (!isJsx && !isJsxCall && !isNullRet && !isCondRet) {
221
+ const returnsFn = t.isArrowFunctionExpression(ret) || t.isFunctionExpression(ret);
222
+ if (returnsFn && (name !== void 0 || containsRenderNode(ret))) {
223
+ throw buildSetupStyleError(fnPath, name);
224
+ }
225
+ return null;
226
+ }
218
227
  if (isJsx) walkJSX(ret);
219
228
  if (isExprBody) {
220
229
  fn.body = t.blockStatement([
@@ -229,6 +238,54 @@ function wrapComponentFn(fn, name) {
229
238
  if (name) args.push(t.stringLiteral(name));
230
239
  return t.callExpression(t.identifier("defineComponent"), args);
231
240
  }
241
+ function assertNoSetupStyleEarlyReturns(fnPath, name) {
242
+ const body = fnPath.node.body;
243
+ if (!t.isBlockStatement(body)) return;
244
+ const stmts = body.body;
245
+ const trailing = stmts.length ? stmts[stmts.length - 1] : null;
246
+ fnPath.traverse({
247
+ ReturnStatement(retPath) {
248
+ if (retPath.getFunctionParent() !== fnPath) return;
249
+ if (trailing && retPath.node === trailing) return;
250
+ const arg = retPath.node.argument;
251
+ if (t.isArrowFunctionExpression(arg) || t.isFunctionExpression(arg)) {
252
+ throw buildSetupStyleError(retPath, name);
253
+ }
254
+ }
255
+ });
256
+ }
257
+ function buildSetupStyleError(fnPath, name) {
258
+ const label = name ? `\u7EC4\u4EF6 ${name}` : "\u7EC4\u4EF6\u51FD\u6570";
259
+ const msg = `[actview] \u5DF2\u5E9F\u5F03\u7684 setup \u98CE\u683C\uFF1A${label}\u4F53\u5185 return \u4E86\u6E32\u67D3\u51FD\u6570\uFF08() => JSX / function () { JSX }\uFF09\u3002
260
+ ActView \u53EA\u652F\u6301\u7B80\u5199\u7EC4\u4EF6\u2014\u2014\u76F4\u63A5 return JSX\uFF0C\u63D2\u4EF6\u4F1A\u81EA\u52A8\u5305\u88C5 render \u51FD\u6570\uFF1A
261
+
262
+ \u2717 function App() { return () => <div /> }
263
+ \u2713 function App() { return <div /> }
264
+ `;
265
+ try {
266
+ return fnPath.buildCodeFrameError(msg);
267
+ } catch {
268
+ return new Error(msg);
269
+ }
270
+ }
271
+ function containsRenderNode(node, seen = /* @__PURE__ */ new Set()) {
272
+ if (!node || typeof node !== "object" || seen.has(node)) return false;
273
+ seen.add(node);
274
+ if (t.isJSXElement(node) || t.isJSXFragment(node)) return true;
275
+ if (t.isCallExpression(node) && t.isIdentifier(node.callee) && /^_?jsx/.test(node.callee.name)) {
276
+ return true;
277
+ }
278
+ for (const k in node) {
279
+ if (k === "leadingComments" || k === "trailingComments") continue;
280
+ const v = node[k];
281
+ if (Array.isArray(v)) {
282
+ if (v.some((c) => containsRenderNode(c, seen))) return true;
283
+ } else if (v !== null && typeof v === "object") {
284
+ if (containsRenderNode(v, seen)) return true;
285
+ }
286
+ }
287
+ return false;
288
+ }
232
289
  function wrapEarlyReturns(fnPath) {
233
290
  fnPath.traverse({
234
291
  ReturnStatement(innerPath) {
@@ -380,10 +437,24 @@ function buildSolidMark(el, state) {
380
437
 
381
438
  // src/babel-host.ts
382
439
  import * as babel from "@babel/core";
440
+ var NODE_MODULES_RE = /(^|[\\/])node_modules[\\/]/;
441
+ function matchesRule(rule, filename) {
442
+ return typeof rule === "string" ? filename.includes(rule) : rule.test(filename);
443
+ }
444
+ function isExcludedTransform(filename, options) {
445
+ const norm = filename.replace(/\\/g, "/");
446
+ if (NODE_MODULES_RE.test(norm)) return true;
447
+ if (options?.include?.length && !options.include.some((r) => matchesRule(r, norm))) {
448
+ return true;
449
+ }
450
+ if (options?.exclude?.some((r) => matchesRule(r, norm))) return true;
451
+ return false;
452
+ }
383
453
  function createBabelItem(plugin) {
384
454
  return babel.createConfigItemSync(plugin, { type: "plugin" });
385
455
  }
386
- function transformWithBabel(code, filename, pluginItem) {
456
+ function transformWithBabel(code, filename, pluginItem, options) {
457
+ if (isExcludedTransform(filename, options)) return null;
387
458
  const result = babel.transformSync(code, {
388
459
  filename,
389
460
  plugins: [pluginItem],
@@ -396,9 +467,10 @@ function transformWithBabel(code, filename, pluginItem) {
396
467
  if (!result) return null;
397
468
  return { code: result.code || code, map: result.map };
398
469
  }
399
- function createBabelTransform(plugin) {
470
+ function createBabelTransform(plugin, options) {
400
471
  const items = (Array.isArray(plugin) ? plugin : [plugin]).map((p) => createBabelItem(p));
401
472
  return (code, filename) => {
473
+ if (isExcludedTransform(filename, options)) return null;
402
474
  const result = babel.transformSync(code, {
403
475
  filename,
404
476
  plugins: items,
@@ -713,6 +785,7 @@ export {
713
785
  createBabelTransform,
714
786
  defineComponentPlugin as default,
715
787
  defineComponentPlugin,
788
+ isExcludedTransform,
716
789
  solidPlugin,
717
790
  transformWithBabel
718
791
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actview/plugin-babel",
3
- "version": "1.0.7",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "description": "Babel 插件:JSX 组件自动 defineComponent 转换(ActView 编译核心,独立于 Vite 宿主)",
6
6
  "exports": {