Frontend Development 9 min read

Implementing Vue‑Style Directives in React with a Babel Plugin

This article explores three approaches to bring Vue‑like directives such as r‑if and r‑for into React, compares their pros and cons, and provides a complete Babel plugin implementation that transforms custom JSX attributes into standard React conditional and list rendering syntax.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Implementing Vue‑Style Directives in React with a Babel Plugin

Introduction

As a developer who works with both Vue and React, I was fascinated by Vue's directive system (v‑if, v‑for, v‑show) and wondered whether a similar system could be created for React. After several experiments I identified three possible solutions, with the Babel‑plugin approach offering the most complete result.

Why React Directives?

Traditional React conditional rendering relies on logical && and map for lists, which can be verbose. An ideal syntax would look like <div r-if={isShow}>Display content</div> and <div r-for={item in items} key={item.id}>{item.name}</div> , making templates concise and expressive.

Solution Comparison

Solution 1: Higher‑Order Components (Imperfect)

const If = ({ condition, children }) => condition ? children : null;
const For = ({ list, children }) => list.map((item, index) => children(item, index));
显示内容
{(item, index) =>
{item}
}

Pros: Follows React's design philosophy and requires no extra tooling.

Cons: syntax is not intuitive, nesting depth increases, and true directive behavior cannot be achieved.

Solution 2: Babel Plugin

Implementation Idea

React JSX is essentially JavaScript syntax sugar, so a custom Babel plugin can transform attributes like r‑if into regular React code during the compilation phase.

Core principle: Using Babel AST transformation, replace <div r-if={count > 4}>I am larger than 4</div> with {count > 4 && <div>I am larger than 4</div>} .

With Vite, the plugin is added via @vitejs/plugin-react and the plugin file is specified in the Vite config.

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: ["./babel-plugin-react-directives.js"],
      },
    }),
  ],
});

The plugin itself exports a function that receives the Babel object, extracts types as t , and returns an object with a visitor handling JSXAttribute nodes whose name starts with r- . For r‑if , it finds the parent JSX element, replaces it with a logical‑AND expression container, and removes the original attribute.

export default function (babel) {
  const { types: t } = babel;
  return {
    name: "react-directives",
    visitor: {
      JSXAttribute(path) {
        if (path.node.name.name?.startsWith("r-")) {
          const directive = path.node.name.name;
          const condition = path.node.value?.expression;
          if (directive === "r-if" && condition) {
            const jsxElement = path.findParent(p => p.isJSXElement());
            jsxElement.replaceWith(
              t.jSXExpressionContainer(
                t.logicalExpression("&&", condition, jsxElement.node)
              )
            );
            path.remove();
          }
        }
      },
    },
  };
}

Result

After configuring Vite and adding the plugin, the custom directives work as expected, allowing concise JSX such as <div r-if={isShow}>... and <div r-for={item in items} ...>... .

Solution 3: Overriding createElement

An alternative idea is to monkey‑patch React.createElement at runtime to process r‑if , but this approach was not successful.

import React from "react";
const originalCreateElement = React.createElement;
const customCreateElement = function (type, props, ...children) {
  // handle r-if directive
  if (props && props["r-if"] === false) {
    return null;
  }
  if (props && typeof props["r-if"] !== "undefined") {
    return props["r-if"]
      ? originalCreateElement(
          type,
          { ...props, "r-if": undefined },
          ...children
        )
      : null;
  }
  return originalCreateElement(type, props, ...children);
};
export const applyDirectives = () => {
  if (!React.__directivesApplied) {
    React.__directivesApplied = true;
    React.createElement = customCreateElement; // apply override
  }
};

Then import and invoke applyDirectives() in main.jsx .

Conclusion

By creating a Babel plugin we successfully introduced Vue‑like directives into React, simplifying component code and demonstrating the power of AST manipulation. This is just a starting point, but it opens new possibilities for React developers.

frontendReactpluginBabelVueJSXDirectives
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.