Skip to content

babel 插件

vIfTransformPlugin

  • 让 react 的 v-if 语法支持在 jsx 中使用

实现思路

  • 通过visitor策略模式遍历 JSXElement 节点
  • 判断节点属性是否包含 v-if 属性
  • 获取 v-if 的条件表达式
  • 移除 v-if 属性
  • 替换为条件渲染:{condition &&
    }

插件实现

js
// plugins/vIfTransformPlugin.js
import { types } from "@babel/core";

function vIfTransformPlugin() {
  return {
    visitor: {
      JSXElement(path) {
        const { node } = path;
        const { openingElement } = node;
        const { attributes } = openingElement;

        const attrIndex = attributes.findIndex(
          (attr) => attr.name?.name === "v-if"
        );

        if (attrIndex === -1) return;

        // 获取条件表达式
        const condition = attributes[attrIndex].value?.expression;

        // 移除属性
        attributes.splice(attrIndex, 1);

        // 替换为条件渲染:{condition && <div />}
        path.replaceWith(
          types.jsxExpressionContainer(
            types.logicalExpression("&&", condition, node)
          )
        );
      },
    },
  };
}

export default vIfTransformPlugin;

引用插件

js
// vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import vIfElsePlugin from "./plugins/vIfTransformPlugin";

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: [vIfElsePlugin], // 应用自定义 Babel 插件
      },
    }),
  ],
});

使用

jsx
import { useState } from "react";

function App() {
  let [num, setNum] = useState(0);
  return (
    <>
      <button onClick={() => setNum((num) => ++num)}>click{num}</button>
      <div v-if={num == 0}>aa{num}</div>
      <div v-if={num == 1}>bb{num}</div>
      <div v-if={num >= 2}>cc{num}</div>
    </>
  );
}

export default App;

vShowTransformPlugin

  • 让 react 的 v-show 语法支持在 jsx 中使用

实现思路

  • 通过visitor策略模式遍历 JSXElement 节点
  • 判断节点属性是否包含 v-show 属性
  • 获取 v-show 的条件表达式
  • 移除 v-show 属性
  • 查找现有的 style 属性
  • 如果有,则合并样式,否则创建新样式
  • 替换为条件渲染

插件实现

js
// plugins/vShowTransformPlugin.js
import { types } from "@babel/core";

function vShowTransformPlugin() {
  return {
    visitor: {
      JSXElement(path) {
        const { node } = path;
        const { openingElement } = node;
        const { attributes } = openingElement;

        // 查找 v-show 属性
        const showAttrIndex = attributes.findIndex(
          (attr) => attr.name?.name === "v-show"
        );
        if (showAttrIndex === -1) return;

        // 获取条件表达式
        const condition = attributes[showAttrIndex].value?.expression;

        // 移除 v-show 属性
        attributes.splice(showAttrIndex, 1);

        // 查找现有的 style 属性
        let styleAttrIndex = attributes.findIndex(
          (attr) => attr.name?.name === "style"
        );

        let existingStyle = null;
        if (styleAttrIndex !== -1) {
          // 提取现有样式表达式
          existingStyle = attributes[styleAttrIndex].value;
          attributes.splice(styleAttrIndex, 1); // 移除旧样式
        }

        // 构建新样式表达式
        let newStyleExpression;
        if (existingStyle) {
          // 合并样式:condition ? existingStyle : { ...existingStyle, display: 'none' }
          newStyleExpression = types.conditionalExpression(
            condition,
            existingStyle.expression,
            types.objectExpression([
              types.spreadElement(existingStyle.expression),
              types.objectProperty(
                types.identifier("display"),
                types.stringLiteral("none")
              ),
            ])
          );
        } else {
          // 无现有样式:condition ? undefined : { display: 'none' }
          newStyleExpression = types.conditionalExpression(
            condition,
            types.identifier("undefined"),
            types.objectExpression([
              types.objectProperty(
                types.identifier("display"),
                types.stringLiteral("none")
              ),
            ])
          );
        }

        // 创建新 style 属性
        const newStyleAttr = types.jsxAttribute(
          types.jsxIdentifier("style"),
          types.jsxExpressionContainer(newStyleExpression)
        );

        // 添加新样式属性到元素
        attributes.push(newStyleAttr);
      },
    },
  };
}

export default vShowTransformPlugin;

引用插件

js
// vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import vShowTransformPlugin from "./plugins/vShowTransformPlugin";

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: [vShowTransformPlugin], // 应用自定义 Babel 插件
      },
    }),
  ],
});

当元素有 style 属性时:

jsx
// 转换前
<div v-show={isVisible} style={{ color: 'red' }} />

// 转换后
<div style={isVisible
  ? { color: 'red' }
  : { ...{ color: 'red' }, display: 'none' }} />

当元素无 style 属性时:

jsx
// 转换前
<button v-show={isActive}>Submit</button>

// 转换后
<button style={isActive
  ? undefined
  : { display: 'none' }}>Submit</button>