Rue.JS
The Compiler
Framework For Native DOM

Signal 细粒度响应式 . 编译优化 . JSX 函数式组件

Quick Start
$ npm create rue@latest
简洁易用

轻量、直观的 API,适合渐进式接入;保留熟悉的 JSX / TSX 开发方式,无需额外模板语法。

🦀
编译驱动的原生 DOM 渲染

静态 JSX 直接生成 DOM;Signal 交互加载 compiled core,复杂组件与结构按需回退 Vapor。

🤝
React JSX + Vue 式响应式

适合希望保留 React 风格 JSX,同时获得 ref、reactive、computed 等 Vue 式响应式 API 的项目。

import { type FC, ref } from '@rue-js/rue'

const App: FC = () => {
  const count = ref(0)

  return (
    <div className="card h-[310px] border border-base-300/70 bg-base-100/80 shadow-sm backdrop-blur-sm">
      <div className="card-body flex h-full p-6">
        <div className="flex items-start justify-between gap-4">
          <div>
            <div className="text-sm uppercase tracking-[0.22em] text-base-content/45">Live Demo</div>
            <div className="mt-2 text-sm text-base-content/70">
              一个 ref signal 同时驱动计数和 v-if 条件分支。
            </div>
          </div>
          <div className="badge badge-outline badge-lg">{count.value}</div>
        </div>

        <div className="mt-6 grid gap-4">
          <button
            className="rounded-xl border border-base-300 bg-base-100 px-4 py-3 text-base font-medium text-base-content shadow-sm transition hover:border-primary hover:text-primary"
            onClick={() => count.value++}
          >
            计数:{count.value}
          </button>

          <div className="min-h-[96px] rounded-2xl border border-base-300/70 bg-base-200/40 p-4">
            <div v-if={count.value % 2 === 0} className="flex h-full items-center justify-between gap-3">
              <div>
                <div className="font-semibold text-base-content">偶数态 UI</div>
                <div className="mt-1 text-sm text-base-content/70">当前展示欢迎提示卡片。</div>
              </div>
              <span className="badge badge-info badge-lg">v-if</span>
            </div>
            <div v-else className="flex h-full items-center justify-between gap-3">
              <div>
                <div className="font-semibold text-base-content">奇数态 UI</div>
                <div className="mt-1 text-sm text-base-content/70">signal 更新后切到另一套界面。</div>
              </div>
              <span className="badge badge-success badge-lg">v-else</span>
            </div>
          </div>
        </div>

        <div className="mt-auto text-xs tracking-[0.16em] text-base-content/45">
          点击按钮时,下面的两个 UI 会跟着 signal 一起切换。
        </div>
      </div>
    </div>
  )
}

export default App
Live Demo
一个 ref signal 同时驱动计数和 v-if 条件分支。
0
偶数态 UI
当前展示欢迎提示卡片。
v-if
奇数态 UI
signal 更新后切到另一套界面。
v-else
点击按钮时,下面的两个 UI 会跟着 signal 一起切换。

生态与插件

官方路由、设计组件库与构建插件协同工作,也支持渐进式接入现有应用。

用组件组织界面

Rue 以 JSX / TSX 组件表达界面中的可复用片段。组件同时包含结构与逻辑,按需组合即可形成页面或模块。

import { type FC } from '@rue-js/rue'

const Hello: FC = () => (
  <div className="card bg-primary text-primary-content shadow-sm">
    <div className="card-body items-center text-center">
      <div className="text-3xl font-extrabold">Hello</div>
      <div className="mt-2 text-sm opacity-90">Hello component</div>
    </div>
  </div>
)

const World: FC = () => (
  <div className="card bg-base-100 text-base-content border border-base-200 shadow-sm">
    <div className="card-body items-center text-center">
      <div className="text-3xl font-extrabold">World</div>
      <div className="mt-2 text-sm text-base-content/70">World component</div>
    </div>
  </div>
)

const HelloRue: FC = () => (
  <div className="card bg-accent text-accent-content shadow-sm">
    <div className="card-body items-center text-center">
      <div className="text-3xl font-extrabold">Hi</div>
      <div className="mt-2 text-sm opacity-90">Rue</div>
    </div>
  </div>
)

const IAmRue: FC = () => (
  <div className="card bg-base-100 text-base-content border border-base-200 shadow-sm">
    <div className="card-body items-center text-center">
      <div className="text-3xl font-extrabold">Yes</div>
      <div className="mt-2 text-sm text-base-content/70">My name is Rue</div>
    </div>
  </div>
)

const HelloWorld: FC = () => (
  <div className="grid gap-6">
    <Hello />
    <World />
    <HelloRue />
    <IAmRue />
  </div>
)

export default HelloWorld
Hello
Hello component
World
World component
Hi
Rue
Yes
My name is Rue

Rue 的组件强调可复用与可组合,保持清晰的数据与事件流,也方便把交互片段渐进接入现有页面。

细粒度响应式:更贴近真实 DOM 更新模型

Rue 的组件、状态和渲染路径都建立在细粒度响应式系统之上,依赖变化后只接管受影响的更新边界。

编译器会把 JSX 路由为静态 DOM、Signal compiled core 或 Vapor fallback,只加载保持当前语义所需的最小能力。

const Hello: FC = () => (
  <div className="card bg-primary text-primary-content shadow-sm">
    <div className="card-body items-center text-center">
      <div className="text-3xl font-extrabold">Hello</div>
      <div className="mt-2 text-sm opacity-90">Hello component</div>
    </div>
  </div>
)

const World: FC = () => (
  <div className="card bg-base-100 text-base-content border border-base-200 shadow-sm">
    <div className="card-body items-center text-center">
      <div className="text-3xl font-extrabold">World</div>
      <div className="mt-2 text-sm text-base-content/70">World component</div>
    </div>
  </div>
)

const HelloRue: FC = () => (
  <div className="card bg-accent text-accent-content shadow-sm">
    <div className="card-body items-center text-center">
      <div className="text-3xl font-extrabold">Hi</div>
      <div className="mt-2 text-sm opacity-90">Rue</div>
    </div>
  </div>
)

const IAmRue: FC = () => (
  <div className="card bg-base-100 text-base-content border border-base-200 shadow-sm">
    <div className="card-body items-center text-center">
      <div className="text-3xl font-extrabold">Yes</div>
      <div className="mt-2 text-sm text-base-content/70">My name is Rue</div>
    </div>
  </div>
)

const HelloWorld: FC = () => (
  <div className="grid gap-6">
    <Hello />
    <World />
    <HelloRue />
    <IAmRue />
  </div>
)

export default HelloWorld
/* RUE_TRANSFORMED */
import { _$template, _$compiledCreateDocumentFragment, _$compiledAppendChild, _$compiledBatch, _$compiledCreateElement, _$compiledCreateTextNode, _$compiledRoot, _$mountCompiledComponent, _$withCompiledPropsUpdater } from "@rue-js/rue/internal/compiler";
const _$getTemplate5 = _$template('<div class="grid gap-6"><!--rue:opaque-hole:0--><!--rue:opaque-hole:1--><!--rue:opaque-hole:2--><!--rue:opaque-hole:3--></div>');
const Hello = ()=>{
    return _$withCompiledPropsUpdater(_$compiledRoot(Object.assign((__rue_parent_context)=>{
        const _root = _$compiledCreateElement("div", __rue_parent_context);
        _root.className = "card bg-primary text-primary-content shadow-sm";
        const _el1 = _$compiledCreateElement("div", _root);
        _$compiledAppendChild(_root, _el1);
        _el1.className = "card-body items-center text-center";
        const _el2 = _$compiledCreateElement("div", _el1);
        _$compiledAppendChild(_el1, _el2);
        _el2.className = "text-3xl font-extrabold";
        _$compiledAppendChild(_el2, _$compiledCreateTextNode("Hello"));
        const _el3 = _$compiledCreateElement("div", _el1);
        _$compiledAppendChild(_el1, _el3);
        _el3.className = "mt-2 text-sm opacity-90";
        _$compiledAppendChild(_el3, _$compiledCreateTextNode("Hello component"));
        return {
            __rue_compiled_host: _root,
            __rue_compiled_roots: [
                _root
            ]
        };
    }, {
        __rue_compiled_explicit_roots: true
    })), (_$rueNextProps)=>_$compiledBatch(()=>{}));
};
const World = ()=>{
    return _$withCompiledPropsUpdater(_$compiledRoot(Object.assign((__rue_parent_context)=>{
        const _root = _$compiledCreateElement("div", __rue_parent_context);
        _root.className = "card bg-base-100 text-base-content border border-base-200 shadow-sm";
        const _el4 = _$compiledCreateElement("div", _root);
        _$compiledAppendChild(_root, _el4);
        _el4.className = "card-body items-center text-center";
        const _el5 = _$compiledCreateElement("div", _el4);
        _$compiledAppendChild(_el4, _el5);
        _el5.className = "text-3xl font-extrabold";
        _$compiledAppendChild(_el5, _$compiledCreateTextNode("World"));
        const _el6 = _$compiledCreateElement("div", _el4);
        _$compiledAppendChild(_el4, _el6);
        _el6.className = "mt-2 text-sm text-base-content/70";
        _$compiledAppendChild(_el6, _$compiledCreateTextNode("World component"));
        return {
            __rue_compiled_host: _root,
            __rue_compiled_roots: [
                _root
            ]
        };
    }, {
        __rue_compiled_explicit_roots: true
    })), (_$rueNextProps)=>_$compiledBatch(()=>{}));
};
const HelloRue = ()=>{
    return _$withCompiledPropsUpdater(_$compiledRoot(Object.assign((__rue_parent_context)=>{
        const _root = _$compiledCreateElement("div", __rue_parent_context);
        _root.className = "card bg-accent text-accent-content shadow-sm";
        const _el7 = _$compiledCreateElement("div", _root);
        _$compiledAppendChild(_root, _el7);
        _el7.className = "card-body items-center text-center";
        const _el8 = _$compiledCreateElement("div", _el7);
        _$compiledAppendChild(_el7, _el8);
        _el8.className = "text-3xl font-extrabold";
        _$compiledAppendChild(_el8, _$compiledCreateTextNode("Hi"));
        const _el9 = _$compiledCreateElement("div", _el7);
        _$compiledAppendChild(_el7, _el9);
        _el9.className = "mt-2 text-sm opacity-90";
        _$compiledAppendChild(_el9, _$compiledCreateTextNode("Rue"));
        return {
            __rue_compiled_host: _root,
            __rue_compiled_roots: [
                _root
            ]
        };
    }, {
        __rue_compiled_explicit_roots: true
    })), (_$rueNextProps)=>_$compiledBatch(()=>{}));
};
const IAmRue = ()=>{
    return _$withCompiledPropsUpdater(_$compiledRoot(Object.assign((__rue_parent_context)=>{
        const _root = _$compiledCreateElement("div", __rue_parent_context);
        _root.className = "card bg-base-100 text-base-content border border-base-200 shadow-sm";
        const _el10 = _$compiledCreateElement("div", _root);
        _$compiledAppendChild(_root, _el10);
        _el10.className = "card-body items-center text-center";
        const _el11 = _$compiledCreateElement("div", _el10);
        _$compiledAppendChild(_el10, _el11);
        _el11.className = "text-3xl font-extrabold";
        _$compiledAppendChild(_el11, _$compiledCreateTextNode("Yes"));
        const _el12 = _$compiledCreateElement("div", _el10);
        _$compiledAppendChild(_el10, _el12);
        _el12.className = "mt-2 text-sm text-base-content/70";
        _$compiledAppendChild(_el12, _$compiledCreateTextNode("My name is Rue"));
        return {
            __rue_compiled_host: _root,
            __rue_compiled_roots: [
                _root
            ]
        };
    }, {
        __rue_compiled_explicit_roots: true
    })), (_$rueNextProps)=>_$compiledBatch(()=>{}));
};
const HelloWorld = ()=>{
    return _$withCompiledPropsUpdater(_$compiledRoot(Object.assign((__rue_parent_context)=>{
        const _fragment = _$getTemplate5().content.cloneNode(true);
        const _root = _fragment.firstChild;
        const _el13 = _root.childNodes[0];
        const _el14 = _el13.parentNode;
        const _el15 = _root.childNodes[1];
        const _el16 = _el15.parentNode;
        const _el17 = _root.childNodes[2];
        const _el18 = _el17.parentNode;
        const _el19 = _root.childNodes[3];
        const _el20 = _el19.parentNode;
        const _el21 = _$compiledCreateDocumentFragment(_el14);
        _$mountCompiledComponent(_el21, Hello, ()=>({}));
        _el14.insertBefore(_el21, _el13);
        const _el22 = _$compiledCreateDocumentFragment(_el16);
        _$mountCompiledComponent(_el22, World, ()=>({}));
        _el16.insertBefore(_el22, _el15);
        const _el23 = _$compiledCreateDocumentFragment(_el18);
        _$mountCompiledComponent(_el23, HelloRue, ()=>({}));
        _el18.insertBefore(_el23, _el17);
        const _el24 = _$compiledCreateDocumentFragment(_el20);
        _$mountCompiledComponent(_el24, IAmRue, ()=>({}));
        _el20.insertBefore(_el24, _el19);
        return {
            __rue_compiled_host: _root,
            __rue_compiled_roots: [
                _root
            ]
        };
    }, {
        __rue_compiled_explicit_roots: true
    })), (_$rueNextProps)=>_$compiledBatch(()=>{}));
};
export default HelloWorld;

纯静态 JSX 可以没有 Rue 值运行时;Signal 页面使用最小 compiled core。组件、Hydration、Teleport、Transition 等复杂能力仍会按需进入 Vapor,因此这不是“所有应用绝对零运行时”的承诺。

React 风格 JSX,Vue 式响应式 API

Rue 适合希望保留 React 风格 JSX,同时获得 Vue 式响应式 API 的项目。你既可以用 useState,也可以用 ref / reactive / computed 来组织交互逻辑。

import { type FC, useState, ref, reactive, computed } from '@rue-js/rue';

type Video = { title: string; desc: string };
const videos: Video[] = [
  { title: '原始 DOM 编程', desc: '直接操作节点与事件' },
  { title: 'jQuery 的崛起', desc: 'Write Less, Do More' },
  { title: 'Backbone.js 与 MVC', desc: '早期前端架构探索' },
  { title: 'Web Components', desc: '原生组件标准' },
  { title: '现代构建工具与生态', desc: '模块化与开发体验' },
];

const SearchInput: FC<{ value: string; onChange: (t: string) => void }> = p => (
  <input
    className="w-full rounded-md border border-base-300 bg-base-100 px-3 py-2 text-base-content shadow-sm focus:border-violet-500 focus:ring focus:ring-violet-200"
    value={p.value}
    onInput={(e: any) => p.onChange((e.target as HTMLInputElement).value)}
    placeholder="搜索视频"
  />
)

// VideoList 组件
const VideoList: FC<{ videos: Video[]; emptyHeading?: string }> = p => (
  <div className="mt-3 space-y-2">
    <div className="text-sm text-base-content/70">{p.videos.length} 个视频</div>
    {p.videos.length === 0 ? (
      <div className="rounded-md border border-base-300/70 bg-base-100/70 backdrop-blur-sm p-3 text-sm text-base-content/70">
        {p.emptyHeading || '暂无匹配'}
      </div>
    ) : (
      <ul className="space-y-2">
        {p.videos.map((v, i) => (
          <li
            key={i}
            className="rounded-md border border-base-300/70 bg-base-100/70 backdrop-blur-sm p-3"
          >
            <div className="font-medium text-base-content">{v.title}</div>
            <div className="text-sm text-base-content/70">{v.desc}</div>
          </li>
        ))}
      </ul>
    )}
  </div>
)

const SearchableVideoList: FC<{ videos: Video[] }> = (p) => {
  const [searchText, setSearchText] = useState('');
  const foundVideos = computed(() =>
    p.videos.filter(v =>
      v.title.toLowerCase().includes(searchText.toLowerCase()),
    )
  )

  return (
    <>
      <SearchInput value={searchText} onChange={setSearchText} />
      <VideoList videos={foundVideos} emptyHeading={`没有匹配 “${searchText}”`} />
    </>
  );
};

const ReactiveDemo: FC = () => {
  const count = ref(0);
  const state = reactive({ enabled: false });
  return (
    <div className="rounded-xl border border-gray-200/70 bg-white/60 backdrop-blur-sm p-4">
      <div className="flex items-center gap-3">
        <button className="btn btn-primary" onClick={() => (count.value++)}>+1</button>
        <button className="btn btn-outline" onClick={() => (count.value = 0)}>重置</button>
        <label className="flex items-center gap-2 ml-auto">
          <input
            type="checkbox"
            className="checkbox"
            checked={state.enabled}
            onChange={(e: any) => (state.enabled = (e.target as HTMLInputElement).checked)}
          />
          <span className="text-sm">启用</span>
        </label>
      </div>
      <div className="mt-2 text-sm text-gray-700">
        计数:{count.value},启用:{state.enabled ? '是' : '否'}
      </div>
    </div>
  );
};

const Reactive: FC = () => (
  <div className="grid gap-6">
    <div>
      <h3 className="text-lg font-semibold mb-2 text-base-content">前端的发展,从原始DOM,到 JQUERY等</h3>
      <SearchableVideoList videos={videos} />
    </div>
    <div>
      <h3 className="text-lg font-semibold mb-2 text-base-content">ref / reactive 示例</h3>
      <ReactiveDemo />
    </div>
  </div>
)

export default Reactive;

前端的发展,从原始DOM,到 JQUERY等

5 个视频
  • 原始 DOM 编程
    直接操作节点与事件
  • jQuery 的崛起
    Write Less, Do More
  • Backbone.js 与 MVC
    早期前端架构探索
  • Web Components
    原生组件标准
  • 现代构建工具与生态
    模块化与开发体验

ref / reactive 示例

计数:0,启用:否

Rue 支持渐进集成:你可以在现有页面中按需挂载组件、路由或交互片段,而不必一次性重写整站。

Platinum Sponsor

白金赞助商

成为赞助商
Gold Sponsor

黄金赞助商

成为赞助商
Links

友情链接