Latest commit

History

93 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Rue

Vue's mutable reactivity model for React on the web and React Native.

Rue re-exports @vue/reactivity and adds React hooks that connect refs, reactive objects, computed values, watchers, and effect scopes to the React lifecycle. Rendering stays ordinary React and JSX.

import{useComputed,useReactive}from'@themakers/rue'exportfunctionCounter(){conststate=useReactive({count: 0})constdoubled=useComputed(()=>state.count*2)return(<buttononClick={()=>state.count++}>{state.count} / {doubled.value}</button>)}

Platforms

PlatformStatusValidation
React DOMSupportedVitest, jsdom, Vite example
React NativeSupportedjest-expo, React Native Testing Library, Expo example
HermesSupportedHermes bytecode compile and Expo Android Hermes bundle
React Strict DOMSupportedExpo + RSD example and Android Hermes bundle
SSRArchitecture-readygetServerSnapshot and renderToString smoke test
React Server ComponentsNot supportedOutside the current scope

The runtime imports only react and @vue/reactivity. It has no dependency on react-dom, react-native, browser globals, or renderer-specific batching.

Installation

pnpm add @themakers/rue react @vue/reactivity

Rue can also be installed directly from a pinned git commit before an npm release exists:

{
"dependencies": {
"@themakers/rue": "github:themakers/rue#<commit-sha>"
}
}

The repository does not commit dist. During an npm or pnpm git installation, prepare uses the installed development toolchain to generate the package. Bun does not install a git dependency's development dependencies, so Rue's trusted prepare automatically falls back to Bun's built-in bundler and its source declarations:

bun add --trust github:themakers/rue#<commit-sha>

If the application declares trustedDependencies manually, include @themakers/rue while retaining any other packages whose install scripts the application needs. prepack always performs the full build and validates the output before creating a package tarball. Pin a commit SHA so installs remain reproducible.

Requirements:

  • React 18 or 19;
  • @vue/reactivity 3.5.x;
  • Proxy support, available in current browsers and Hermes.

CI tests the minimum React 18.3.1 peer, the latest React 19 web release, and the React/React Native versions pinned by the current Expo SDK. Expo applications should follow Expo's renderer version matrix rather than upgrading React Native independently.

react and @vue/reactivity are peer dependencies. Ensure the application has only one resolved copy of @vue/reactivity, especially when Vue and Rue are installed together. Separate copies maintain separate dependency graphs, so a value created by one copy cannot notify effects owned by another.

API

Rue exports the complete public API of @vue/reactivity, plus:

APIPurpose
onMountedPassive effect setup after mount
onUpdatedPassive effect after every commit except the initial mount
onBeforeUnmountPassive effect cleanup
useRefComponent-owned deep Vue ref
useShallowRefComponent-owned shallow Vue ref
useTemplateRefVue-style ref that binds directly to a JSX ref
useCustomRefComponent-owned custom Vue ref
useReactiveComponent-owned deep reactive object
useShallowReactiveComponent-owned shallow reactive object
useReadonlyReadonly view subscribed to its reactive source
useShallowReadonlyShallow readonly view
useComputedComponent-owned computed ref
watchSynchronous Vue watch wrapper
baseWatchDirect @vue/reactivity watch export
useWatchComponent-owned watch with automatic cleanup
watchEffectSynchronous Vue watch effect wrapper
useWatchEffectComponent-owned watch effect with automatic cleanup
useEffectScopeComponent-owned effect scope facade
useReactivitySubscribe a component to module-level reactive data

Component state

import{useComputed,useReactive,useRef,useWatch}from'@themakers/rue'exportfunctionProfile(){constname=useRef('Ada')conststate=useReactive({visits: 0})constlabel=useComputed(()=>`${name.value}: ${state.visits}`)useWatch(()=>state.visits,(visits)=>console.log('visits',visits),)return<buttononClick={()=>state.visits++}>{label.value}</button>}

Initial arguments follow React state initializer semantics: changing an initializer on a later render does not replace the existing ref, proxy, or computed object.

Template refs

useTemplateRef provides one ref for both JSX binding and Vue-style access. React's renderer writes through the internal .current bridge while application code reads the same element through .value:

import{onMounted,useTemplateRef}from'@themakers/rue'exportfunctionAutofocusInput(){constinput=useTemplateRef<HTMLInputElement>()onMounted(()=>input.value?.focus())return<inputref={input}/>}

The ref starts at null, receives the host element after commit, and returns to null when the element unmounts.

Shared stores

Create a normal Vue reactive value at module scope and subscribe each consumer with useReactivity:

import{computed,reactive,useReactivity}from'@themakers/rue'constcounter=reactive({count: 0})constdoubled=computed(()=>counter.count*2)exportfunctionCounter(){conststate=useReactivity(()=>({ counter, doubled }))return(<buttononClick={()=>state.counter.count++}>{state.counter.count} / {state.doubled.value}</button>)}

Rue uses useSyncExternalStore with a stable numeric version snapshot. A reactive mutation invalidates subscribed components synchronously, allowing React to detect an external-store change during a transition and restart the render before commit.

Do not mutate external stores during render. For SSR, create request-local stores rather than sharing a mutable singleton between requests.

Watchers

Raw watch and watchEffect run synchronously and return the Vue watch handle. Their hook variants are installed after commit and stop automatically during cleanup:

useWatchEffect((onCleanup)=>{constrequest=loadItem(itemId.value)onCleanup(()=>request.abort())})

Vue's flush option is intentionally not supported. Rue has no Vue renderer scheduler and does not emulate one with platform-specific APIs.

In development StrictMode, React may rehearse setup and cleanup. User effects must follow the same idempotency rules as useEffect; Rue guarantees that each backing watcher is stopped and that no discarded render leaves a watcher.

Effect scopes

scope.run(setup) registers setup during render. Rue executes it after commit inside a fresh Vue effect scope and stops that backing scope during cleanup:

import{onScopeDispose,useEffectScope,watch}from'@themakers/rue'constscope=useEffectScope()scope.run(()=>{consthandle=watch(source,consume)onScopeDispose(()=>console.log('scope disposed'))returnhandle})

Because setup is deferred, scope.run() returns undefined. pause, resume, and stop operate on the active backing scope. StrictMode can execute the setup more than once with a complete disposal between runs.

Lifecycle

Lifecycle helpers use passive React effects. Their development StrictMode semantics intentionally match React:

  • onMounted setup may run twice;
  • onBeforeUnmount may observe the rehearsal cleanup and the real cleanup;
  • onUpdated never fires for either initial-mount setup, then runs after later commits.

Examples

The workspace contains three applications using the exact same examples/shared-store package:

pnpm --filter @rue/example-web dev
pnpm --filter @rue/example-expo start
pnpm --filter @rue/example-expo-rsd start

Each renderer also includes component-owned TODO state built with useReactive, alongside the shared module store consumed through useReactivity.

Validate all examples:

pnpm examples:check
pnpm examples:bundle

The Expo Strict DOM example also includes an EAS Android + Maestro workflow that asserts the app is executing on Hermes and that a Rue mutation updates the UI.

Development

pnpm install
pnpm run ci

Contributors need Node 22.13 or newer for the current Expo toolchain. The published ES2017 runtime itself supports Node 18+ and does not execute Node APIs inside applications.

Useful focused commands:

pnpm test:web
pnpm test:native
pnpm test:hermes
pnpm test:coverage
pnpm check:platform
pnpm check:size

See docs/audit.md, docs/architecture.md, and MIGRATION.md for design and compatibility details.

License

MIT. Rue is a fork of Veact and preserves the original copyright and attribution.

About

A mutable state enhancer library for React, based on @vue/reactivity.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

93 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Rue

Vue's mutable reactivity model for React on the web and React Native.

Rue re-exports @vue/reactivity and adds React hooks that connect refs, reactive objects, computed values, watchers, and effect scopes to the React lifecycle. Rendering stays ordinary React and JSX.

import{useComputed,useReactive}from'@themakers/rue'exportfunctionCounter(){conststate=useReactive({count: 0})constdoubled=useComputed(()=>state.count*2)return(<buttononClick={()=>state.count++}>{state.count} / {doubled.value}</button>)}

Platforms

PlatformStatusValidation
React DOMSupportedVitest, jsdom, Vite example
React NativeSupportedjest-expo, React Native Testing Library, Expo example
HermesSupportedHermes bytecode compile and Expo Android Hermes bundle
React Strict DOMSupportedExpo + RSD example and Android Hermes bundle
SSRArchitecture-readygetServerSnapshot and renderToString smoke test
React Server ComponentsNot supportedOutside the current scope

The runtime imports only react and @vue/reactivity. It has no dependency on react-dom, react-native, browser globals, or renderer-specific batching.

Installation

pnpm add @themakers/rue react @vue/reactivity

Rue can also be installed directly from a pinned git commit before an npm release exists:

{
"dependencies": {
"@themakers/rue": "github:themakers/rue#<commit-sha>"
}
}

The repository does not commit dist. During an npm or pnpm git installation, prepare uses the installed development toolchain to generate the package. Bun does not install a git dependency's development dependencies, so Rue's trusted prepare automatically falls back to Bun's built-in bundler and its source declarations:

bun add --trust github:themakers/rue#<commit-sha>

If the application declares trustedDependencies manually, include @themakers/rue while retaining any other packages whose install scripts the application needs. prepack always performs the full build and validates the output before creating a package tarball. Pin a commit SHA so installs remain reproducible.

Requirements:

  • React 18 or 19;
  • @vue/reactivity 3.5.x;
  • Proxy support, available in current browsers and Hermes.

CI tests the minimum React 18.3.1 peer, the latest React 19 web release, and the React/React Native versions pinned by the current Expo SDK. Expo applications should follow Expo's renderer version matrix rather than upgrading React Native independently.

react and @vue/reactivity are peer dependencies. Ensure the application has only one resolved copy of @vue/reactivity, especially when Vue and Rue are installed together. Separate copies maintain separate dependency graphs, so a value created by one copy cannot notify effects owned by another.

API

Rue exports the complete public API of @vue/reactivity, plus:

APIPurpose
onMountedPassive effect setup after mount
onUpdatedPassive effect after every commit except the initial mount
onBeforeUnmountPassive effect cleanup
useRefComponent-owned deep Vue ref
useShallowRefComponent-owned shallow Vue ref
useTemplateRefVue-style ref that binds directly to a JSX ref
useCustomRefComponent-owned custom Vue ref
useReactiveComponent-owned deep reactive object
useShallowReactiveComponent-owned shallow reactive object
useReadonlyReadonly view subscribed to its reactive source
useShallowReadonlyShallow readonly view
useComputedComponent-owned computed ref
watchSynchronous Vue watch wrapper
baseWatchDirect @vue/reactivity watch export
useWatchComponent-owned watch with automatic cleanup
watchEffectSynchronous Vue watch effect wrapper
useWatchEffectComponent-owned watch effect with automatic cleanup
useEffectScopeComponent-owned effect scope facade
useReactivitySubscribe a component to module-level reactive data

Component state

import{useComputed,useReactive,useRef,useWatch}from'@themakers/rue'exportfunctionProfile(){constname=useRef('Ada')conststate=useReactive({visits: 0})constlabel=useComputed(()=>`${name.value}: ${state.visits}`)useWatch(()=>state.visits,(visits)=>console.log('visits',visits),)return<buttononClick={()=>state.visits++}>{label.value}</button>}

Initial arguments follow React state initializer semantics: changing an initializer on a later render does not replace the existing ref, proxy, or computed object.

Template refs

useTemplateRef provides one ref for both JSX binding and Vue-style access. React's renderer writes through the internal .current bridge while application code reads the same element through .value:

import{onMounted,useTemplateRef}from'@themakers/rue'exportfunctionAutofocusInput(){constinput=useTemplateRef<HTMLInputElement>()onMounted(()=>input.value?.focus())return<inputref={input}/>}

The ref starts at null, receives the host element after commit, and returns to null when the element unmounts.

Shared stores

Create a normal Vue reactive value at module scope and subscribe each consumer with useReactivity:

import{computed,reactive,useReactivity}from'@themakers/rue'constcounter=reactive({count: 0})constdoubled=computed(()=>counter.count*2)exportfunctionCounter(){conststate=useReactivity(()=>({ counter, doubled }))return(<buttononClick={()=>state.counter.count++}>{state.counter.count} / {state.doubled.value}</button>)}

Rue uses useSyncExternalStore with a stable numeric version snapshot. A reactive mutation invalidates subscribed components synchronously, allowing React to detect an external-store change during a transition and restart the render before commit.

Do not mutate external stores during render. For SSR, create request-local stores rather than sharing a mutable singleton between requests.

Watchers

Raw watch and watchEffect run synchronously and return the Vue watch handle. Their hook variants are installed after commit and stop automatically during cleanup:

useWatchEffect((onCleanup)=>{constrequest=loadItem(itemId.value)onCleanup(()=>request.abort())})

Vue's flush option is intentionally not supported. Rue has no Vue renderer scheduler and does not emulate one with platform-specific APIs.

In development StrictMode, React may rehearse setup and cleanup. User effects must follow the same idempotency rules as useEffect; Rue guarantees that each backing watcher is stopped and that no discarded render leaves a watcher.

Effect scopes

scope.run(setup) registers setup during render. Rue executes it after commit inside a fresh Vue effect scope and stops that backing scope during cleanup:

import{onScopeDispose,useEffectScope,watch}from'@themakers/rue'constscope=useEffectScope()scope.run(()=>{consthandle=watch(source,consume)onScopeDispose(()=>console.log('scope disposed'))returnhandle})

Because setup is deferred, scope.run() returns undefined. pause, resume, and stop operate on the active backing scope. StrictMode can execute the setup more than once with a complete disposal between runs.

Lifecycle

Lifecycle helpers use passive React effects. Their development StrictMode semantics intentionally match React:

  • onMounted setup may run twice;
  • onBeforeUnmount may observe the rehearsal cleanup and the real cleanup;
  • onUpdated never fires for either initial-mount setup, then runs after later commits.

Examples

The workspace contains three applications using the exact same examples/shared-store package:

pnpm --filter @rue/example-web dev
pnpm --filter @rue/example-expo start
pnpm --filter @rue/example-expo-rsd start

Each renderer also includes component-owned TODO state built with useReactive, alongside the shared module store consumed through useReactivity.

Validate all examples:

pnpm examples:check
pnpm examples:bundle

The Expo Strict DOM example also includes an EAS Android + Maestro workflow that asserts the app is executing on Hermes and that a Rue mutation updates the UI.

Development

pnpm install
pnpm run ci

Contributors need Node 22.13 or newer for the current Expo toolchain. The published ES2017 runtime itself supports Node 18+ and does not execute Node APIs inside applications.

Useful focused commands:

pnpm test:web
pnpm test:native
pnpm test:hermes
pnpm test:coverage
pnpm check:platform
pnpm check:size

See docs/audit.md, docs/architecture.md, and MIGRATION.md for design and compatibility details.

License

MIT. Rue is a fork of Veact and preserves the original copyright and attribution.

About

A mutable state enhancer library for React, based on @vue/reactivity.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

93 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Rue

Vue's mutable reactivity model for React on the web and React Native.

Rue re-exports @vue/reactivity and adds React hooks that connect refs, reactive objects, computed values, watchers, and effect scopes to the React lifecycle. Rendering stays ordinary React and JSX.

import{useComputed,useReactive}from'@themakers/rue'exportfunctionCounter(){conststate=useReactive({count: 0})constdoubled=useComputed(()=>state.count*2)return(<buttononClick={()=>state.count++}>{state.count} / {doubled.value}</button>)}

Platforms

PlatformStatusValidation
React DOMSupportedVitest, jsdom, Vite example
React NativeSupportedjest-expo, React Native Testing Library, Expo example
HermesSupportedHermes bytecode compile and Expo Android Hermes bundle
React Strict DOMSupportedExpo + RSD example and Android Hermes bundle
SSRArchitecture-readygetServerSnapshot and renderToString smoke test
React Server ComponentsNot supportedOutside the current scope

The runtime imports only react and @vue/reactivity. It has no dependency on react-dom, react-native, browser globals, or renderer-specific batching.

Installation

pnpm add @themakers/rue react @vue/reactivity

Rue can also be installed directly from a pinned git commit before an npm release exists:

{
"dependencies": {
"@themakers/rue": "github:themakers/rue#<commit-sha>"
}
}

The repository does not commit dist. During an npm or pnpm git installation, prepare uses the installed development toolchain to generate the package. Bun does not install a git dependency's development dependencies, so Rue's trusted prepare automatically falls back to Bun's built-in bundler and its source declarations:

bun add --trust github:themakers/rue#<commit-sha>

If the application declares trustedDependencies manually, include @themakers/rue while retaining any other packages whose install scripts the application needs. prepack always performs the full build and validates the output before creating a package tarball. Pin a commit SHA so installs remain reproducible.

Requirements:

  • React 18 or 19;
  • @vue/reactivity 3.5.x;
  • Proxy support, available in current browsers and Hermes.

CI tests the minimum React 18.3.1 peer, the latest React 19 web release, and the React/React Native versions pinned by the current Expo SDK. Expo applications should follow Expo's renderer version matrix rather than upgrading React Native independently.

react and @vue/reactivity are peer dependencies. Ensure the application has only one resolved copy of @vue/reactivity, especially when Vue and Rue are installed together. Separate copies maintain separate dependency graphs, so a value created by one copy cannot notify effects owned by another.

API

Rue exports the complete public API of @vue/reactivity, plus:

APIPurpose
onMountedPassive effect setup after mount
onUpdatedPassive effect after every commit except the initial mount
onBeforeUnmountPassive effect cleanup
useRefComponent-owned deep Vue ref
useShallowRefComponent-owned shallow Vue ref
useTemplateRefVue-style ref that binds directly to a JSX ref
useCustomRefComponent-owned custom Vue ref
useReactiveComponent-owned deep reactive object
useShallowReactiveComponent-owned shallow reactive object
useReadonlyReadonly view subscribed to its reactive source
useShallowReadonlyShallow readonly view
useComputedComponent-owned computed ref
watchSynchronous Vue watch wrapper
baseWatchDirect @vue/reactivity watch export
useWatchComponent-owned watch with automatic cleanup
watchEffectSynchronous Vue watch effect wrapper
useWatchEffectComponent-owned watch effect with automatic cleanup
useEffectScopeComponent-owned effect scope facade
useReactivitySubscribe a component to module-level reactive data

Component state

import{useComputed,useReactive,useRef,useWatch}from'@themakers/rue'exportfunctionProfile(){constname=useRef('Ada')conststate=useReactive({visits: 0})constlabel=useComputed(()=>`${name.value}: ${state.visits}`)useWatch(()=>state.visits,(visits)=>console.log('visits',visits),)return<buttononClick={()=>state.visits++}>{label.value}</button>}

Initial arguments follow React state initializer semantics: changing an initializer on a later render does not replace the existing ref, proxy, or computed object.

Template refs

useTemplateRef provides one ref for both JSX binding and Vue-style access. React's renderer writes through the internal .current bridge while application code reads the same element through .value:

import{onMounted,useTemplateRef}from'@themakers/rue'exportfunctionAutofocusInput(){constinput=useTemplateRef<HTMLInputElement>()onMounted(()=>input.value?.focus())return<inputref={input}/>}

The ref starts at null, receives the host element after commit, and returns to null when the element unmounts.

Shared stores

Create a normal Vue reactive value at module scope and subscribe each consumer with useReactivity:

import{computed,reactive,useReactivity}from'@themakers/rue'constcounter=reactive({count: 0})constdoubled=computed(()=>counter.count*2)exportfunctionCounter(){conststate=useReactivity(()=>({ counter, doubled }))return(<buttononClick={()=>state.counter.count++}>{state.counter.count} / {state.doubled.value}</button>)}

Rue uses useSyncExternalStore with a stable numeric version snapshot. A reactive mutation invalidates subscribed components synchronously, allowing React to detect an external-store change during a transition and restart the render before commit.

Do not mutate external stores during render. For SSR, create request-local stores rather than sharing a mutable singleton between requests.

Watchers

Raw watch and watchEffect run synchronously and return the Vue watch handle. Their hook variants are installed after commit and stop automatically during cleanup:

useWatchEffect((onCleanup)=>{constrequest=loadItem(itemId.value)onCleanup(()=>request.abort())})

Vue's flush option is intentionally not supported. Rue has no Vue renderer scheduler and does not emulate one with platform-specific APIs.

In development StrictMode, React may rehearse setup and cleanup. User effects must follow the same idempotency rules as useEffect; Rue guarantees that each backing watcher is stopped and that no discarded render leaves a watcher.

Effect scopes

scope.run(setup) registers setup during render. Rue executes it after commit inside a fresh Vue effect scope and stops that backing scope during cleanup:

import{onScopeDispose,useEffectScope,watch}from'@themakers/rue'constscope=useEffectScope()scope.run(()=>{consthandle=watch(source,consume)onScopeDispose(()=>console.log('scope disposed'))returnhandle})

Because setup is deferred, scope.run() returns undefined. pause, resume, and stop operate on the active backing scope. StrictMode can execute the setup more than once with a complete disposal between runs.

Lifecycle

Lifecycle helpers use passive React effects. Their development StrictMode semantics intentionally match React:

  • onMounted setup may run twice;
  • onBeforeUnmount may observe the rehearsal cleanup and the real cleanup;
  • onUpdated never fires for either initial-mount setup, then runs after later commits.

Examples

The workspace contains three applications using the exact same examples/shared-store package:

pnpm --filter @rue/example-web dev
pnpm --filter @rue/example-expo start
pnpm --filter @rue/example-expo-rsd start

Each renderer also includes component-owned TODO state built with useReactive, alongside the shared module store consumed through useReactivity.

Validate all examples:

pnpm examples:check
pnpm examples:bundle

The Expo Strict DOM example also includes an EAS Android + Maestro workflow that asserts the app is executing on Hermes and that a Rue mutation updates the UI.

Development

pnpm install
pnpm run ci

Contributors need Node 22.13 or newer for the current Expo toolchain. The published ES2017 runtime itself supports Node 18+ and does not execute Node APIs inside applications.

Useful focused commands:

pnpm test:web
pnpm test:native
pnpm test:hermes
pnpm test:coverage
pnpm check:platform
pnpm check:size

See docs/audit.md, docs/architecture.md, and MIGRATION.md for design and compatibility details.

License

MIT. Rue is a fork of Veact and preserves the original copyright and attribution.

About

A mutable state enhancer library for React, based on @vue/reactivity.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

93 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Rue

Vue's mutable reactivity model for React on the web and React Native.

Rue re-exports @vue/reactivity and adds React hooks that connect refs, reactive objects, computed values, watchers, and effect scopes to the React lifecycle. Rendering stays ordinary React and JSX.

import{useComputed,useReactive}from'@themakers/rue'exportfunctionCounter(){conststate=useReactive({count: 0})constdoubled=useComputed(()=>state.count*2)return(<buttononClick={()=>state.count++}>{state.count} / {doubled.value}</button>)}

Platforms

PlatformStatusValidation
React DOMSupportedVitest, jsdom, Vite example
React NativeSupportedjest-expo, React Native Testing Library, Expo example
HermesSupportedHermes bytecode compile and Expo Android Hermes bundle
React Strict DOMSupportedExpo + RSD example and Android Hermes bundle
SSRArchitecture-readygetServerSnapshot and renderToString smoke test
React Server ComponentsNot supportedOutside the current scope

The runtime imports only react and @vue/reactivity. It has no dependency on react-dom, react-native, browser globals, or renderer-specific batching.

Installation

pnpm add @themakers/rue react @vue/reactivity

Rue can also be installed directly from a pinned git commit before an npm release exists:

{
"dependencies": {
"@themakers/rue": "github:themakers/rue#<commit-sha>"
}
}

The repository does not commit dist. During an npm or pnpm git installation, prepare uses the installed development toolchain to generate the package. Bun does not install a git dependency's development dependencies, so Rue's trusted prepare automatically falls back to Bun's built-in bundler and its source declarations:

bun add --trust github:themakers/rue#<commit-sha>

If the application declares trustedDependencies manually, include @themakers/rue while retaining any other packages whose install scripts the application needs. prepack always performs the full build and validates the output before creating a package tarball. Pin a commit SHA so installs remain reproducible.

Requirements:

  • React 18 or 19;
  • @vue/reactivity 3.5.x;
  • Proxy support, available in current browsers and Hermes.

CI tests the minimum React 18.3.1 peer, the latest React 19 web release, and the React/React Native versions pinned by the current Expo SDK. Expo applications should follow Expo's renderer version matrix rather than upgrading React Native independently.

react and @vue/reactivity are peer dependencies. Ensure the application has only one resolved copy of @vue/reactivity, especially when Vue and Rue are installed together. Separate copies maintain separate dependency graphs, so a value created by one copy cannot notify effects owned by another.

API

Rue exports the complete public API of @vue/reactivity, plus:

APIPurpose
onMountedPassive effect setup after mount
onUpdatedPassive effect after every commit except the initial mount
onBeforeUnmountPassive effect cleanup
useRefComponent-owned deep Vue ref
useShallowRefComponent-owned shallow Vue ref
useTemplateRefVue-style ref that binds directly to a JSX ref
useCustomRefComponent-owned custom Vue ref
useReactiveComponent-owned deep reactive object
useShallowReactiveComponent-owned shallow reactive object
useReadonlyReadonly view subscribed to its reactive source
useShallowReadonlyShallow readonly view
useComputedComponent-owned computed ref
watchSynchronous Vue watch wrapper
baseWatchDirect @vue/reactivity watch export
useWatchComponent-owned watch with automatic cleanup
watchEffectSynchronous Vue watch effect wrapper
useWatchEffectComponent-owned watch effect with automatic cleanup
useEffectScopeComponent-owned effect scope facade
useReactivitySubscribe a component to module-level reactive data

Component state

import{useComputed,useReactive,useRef,useWatch}from'@themakers/rue'exportfunctionProfile(){constname=useRef('Ada')conststate=useReactive({visits: 0})constlabel=useComputed(()=>`${name.value}: ${state.visits}`)useWatch(()=>state.visits,(visits)=>console.log('visits',visits),)return<buttononClick={()=>state.visits++}>{label.value}</button>}

Initial arguments follow React state initializer semantics: changing an initializer on a later render does not replace the existing ref, proxy, or computed object.

Template refs

useTemplateRef provides one ref for both JSX binding and Vue-style access. React's renderer writes through the internal .current bridge while application code reads the same element through .value:

import{onMounted,useTemplateRef}from'@themakers/rue'exportfunctionAutofocusInput(){constinput=useTemplateRef<HTMLInputElement>()onMounted(()=>input.value?.focus())return<inputref={input}/>}

The ref starts at null, receives the host element after commit, and returns to null when the element unmounts.

Shared stores

Create a normal Vue reactive value at module scope and subscribe each consumer with useReactivity:

import{computed,reactive,useReactivity}from'@themakers/rue'constcounter=reactive({count: 0})constdoubled=computed(()=>counter.count*2)exportfunctionCounter(){conststate=useReactivity(()=>({ counter, doubled }))return(<buttononClick={()=>state.counter.count++}>{state.counter.count} / {state.doubled.value}</button>)}

Rue uses useSyncExternalStore with a stable numeric version snapshot. A reactive mutation invalidates subscribed components synchronously, allowing React to detect an external-store change during a transition and restart the render before commit.

Do not mutate external stores during render. For SSR, create request-local stores rather than sharing a mutable singleton between requests.

Watchers

Raw watch and watchEffect run synchronously and return the Vue watch handle. Their hook variants are installed after commit and stop automatically during cleanup:

useWatchEffect((onCleanup)=>{constrequest=loadItem(itemId.value)onCleanup(()=>request.abort())})

Vue's flush option is intentionally not supported. Rue has no Vue renderer scheduler and does not emulate one with platform-specific APIs.

In development StrictMode, React may rehearse setup and cleanup. User effects must follow the same idempotency rules as useEffect; Rue guarantees that each backing watcher is stopped and that no discarded render leaves a watcher.

Effect scopes

scope.run(setup) registers setup during render. Rue executes it after commit inside a fresh Vue effect scope and stops that backing scope during cleanup:

import{onScopeDispose,useEffectScope,watch}from'@themakers/rue'constscope=useEffectScope()scope.run(()=>{consthandle=watch(source,consume)onScopeDispose(()=>console.log('scope disposed'))returnhandle})

Because setup is deferred, scope.run() returns undefined. pause, resume, and stop operate on the active backing scope. StrictMode can execute the setup more than once with a complete disposal between runs.

Lifecycle

Lifecycle helpers use passive React effects. Their development StrictMode semantics intentionally match React:

  • onMounted setup may run twice;
  • onBeforeUnmount may observe the rehearsal cleanup and the real cleanup;
  • onUpdated never fires for either initial-mount setup, then runs after later commits.

Examples

The workspace contains three applications using the exact same examples/shared-store package:

pnpm --filter @rue/example-web dev
pnpm --filter @rue/example-expo start
pnpm --filter @rue/example-expo-rsd start

Each renderer also includes component-owned TODO state built with useReactive, alongside the shared module store consumed through useReactivity.

Validate all examples:

pnpm examples:check
pnpm examples:bundle

The Expo Strict DOM example also includes an EAS Android + Maestro workflow that asserts the app is executing on Hermes and that a Rue mutation updates the UI.

Development

pnpm install
pnpm run ci

Contributors need Node 22.13 or newer for the current Expo toolchain. The published ES2017 runtime itself supports Node 18+ and does not execute Node APIs inside applications.

Useful focused commands:

pnpm test:web
pnpm test:native
pnpm test:hermes
pnpm test:coverage
pnpm check:platform
pnpm check:size

See docs/audit.md, docs/architecture.md, and MIGRATION.md for design and compatibility details.

License

MIT. Rue is a fork of Veact and preserves the original copyright and attribution.

About

A mutable state enhancer library for React, based on @vue/reactivity.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

93 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Rue

Vue's mutable reactivity model for React on the web and React Native.

Rue re-exports @vue/reactivity and adds React hooks that connect refs, reactive objects, computed values, watchers, and effect scopes to the React lifecycle. Rendering stays ordinary React and JSX.

import{useComputed,useReactive}from'@themakers/rue'exportfunctionCounter(){conststate=useReactive({count: 0})constdoubled=useComputed(()=>state.count*2)return(<buttononClick={()=>state.count++}>{state.count} / {doubled.value}</button>)}

Platforms

PlatformStatusValidation
React DOMSupportedVitest, jsdom, Vite example
React NativeSupportedjest-expo, React Native Testing Library, Expo example
HermesSupportedHermes bytecode compile and Expo Android Hermes bundle
React Strict DOMSupportedExpo + RSD example and Android Hermes bundle
SSRArchitecture-readygetServerSnapshot and renderToString smoke test
React Server ComponentsNot supportedOutside the current scope

The runtime imports only react and @vue/reactivity. It has no dependency on react-dom, react-native, browser globals, or renderer-specific batching.

Installation

pnpm add @themakers/rue react @vue/reactivity

Rue can also be installed directly from a pinned git commit before an npm release exists:

{
"dependencies": {
"@themakers/rue": "github:themakers/rue#<commit-sha>"
}
}

The repository does not commit dist. During an npm or pnpm git installation, prepare uses the installed development toolchain to generate the package. Bun does not install a git dependency's development dependencies, so Rue's trusted prepare automatically falls back to Bun's built-in bundler and its source declarations:

bun add --trust github:themakers/rue#<commit-sha>

If the application declares trustedDependencies manually, include @themakers/rue while retaining any other packages whose install scripts the application needs. prepack always performs the full build and validates the output before creating a package tarball. Pin a commit SHA so installs remain reproducible.

Requirements:

  • React 18 or 19;
  • @vue/reactivity 3.5.x;
  • Proxy support, available in current browsers and Hermes.

CI tests the minimum React 18.3.1 peer, the latest React 19 web release, and the React/React Native versions pinned by the current Expo SDK. Expo applications should follow Expo's renderer version matrix rather than upgrading React Native independently.

react and @vue/reactivity are peer dependencies. Ensure the application has only one resolved copy of @vue/reactivity, especially when Vue and Rue are installed together. Separate copies maintain separate dependency graphs, so a value created by one copy cannot notify effects owned by another.

API

Rue exports the complete public API of @vue/reactivity, plus:

APIPurpose
onMountedPassive effect setup after mount
onUpdatedPassive effect after every commit except the initial mount
onBeforeUnmountPassive effect cleanup
useRefComponent-owned deep Vue ref
useShallowRefComponent-owned shallow Vue ref
useTemplateRefVue-style ref that binds directly to a JSX ref
useCustomRefComponent-owned custom Vue ref
useReactiveComponent-owned deep reactive object
useShallowReactiveComponent-owned shallow reactive object
useReadonlyReadonly view subscribed to its reactive source
useShallowReadonlyShallow readonly view
useComputedComponent-owned computed ref
watchSynchronous Vue watch wrapper
baseWatchDirect @vue/reactivity watch export
useWatchComponent-owned watch with automatic cleanup
watchEffectSynchronous Vue watch effect wrapper
useWatchEffectComponent-owned watch effect with automatic cleanup
useEffectScopeComponent-owned effect scope facade
useReactivitySubscribe a component to module-level reactive data

Component state

import{useComputed,useReactive,useRef,useWatch}from'@themakers/rue'exportfunctionProfile(){constname=useRef('Ada')conststate=useReactive({visits: 0})constlabel=useComputed(()=>`${name.value}: ${state.visits}`)useWatch(()=>state.visits,(visits)=>console.log('visits',visits),)return<buttononClick={()=>state.visits++}>{label.value}</button>}

Initial arguments follow React state initializer semantics: changing an initializer on a later render does not replace the existing ref, proxy, or computed object.

Template refs

useTemplateRef provides one ref for both JSX binding and Vue-style access. React's renderer writes through the internal .current bridge while application code reads the same element through .value:

import{onMounted,useTemplateRef}from'@themakers/rue'exportfunctionAutofocusInput(){constinput=useTemplateRef<HTMLInputElement>()onMounted(()=>input.value?.focus())return<inputref={input}/>}

The ref starts at null, receives the host element after commit, and returns to null when the element unmounts.

Shared stores

Create a normal Vue reactive value at module scope and subscribe each consumer with useReactivity:

import{computed,reactive,useReactivity}from'@themakers/rue'constcounter=reactive({count: 0})constdoubled=computed(()=>counter.count*2)exportfunctionCounter(){conststate=useReactivity(()=>({ counter, doubled }))return(<buttononClick={()=>state.counter.count++}>{state.counter.count} / {state.doubled.value}</button>)}

Rue uses useSyncExternalStore with a stable numeric version snapshot. A reactive mutation invalidates subscribed components synchronously, allowing React to detect an external-store change during a transition and restart the render before commit.

Do not mutate external stores during render. For SSR, create request-local stores rather than sharing a mutable singleton between requests.

Watchers

Raw watch and watchEffect run synchronously and return the Vue watch handle. Their hook variants are installed after commit and stop automatically during cleanup:

useWatchEffect((onCleanup)=>{constrequest=loadItem(itemId.value)onCleanup(()=>request.abort())})

Vue's flush option is intentionally not supported. Rue has no Vue renderer scheduler and does not emulate one with platform-specific APIs.

In development StrictMode, React may rehearse setup and cleanup. User effects must follow the same idempotency rules as useEffect; Rue guarantees that each backing watcher is stopped and that no discarded render leaves a watcher.

Effect scopes

scope.run(setup) registers setup during render. Rue executes it after commit inside a fresh Vue effect scope and stops that backing scope during cleanup:

import{onScopeDispose,useEffectScope,watch}from'@themakers/rue'constscope=useEffectScope()scope.run(()=>{consthandle=watch(source,consume)onScopeDispose(()=>console.log('scope disposed'))returnhandle})

Because setup is deferred, scope.run() returns undefined. pause, resume, and stop operate on the active backing scope. StrictMode can execute the setup more than once with a complete disposal between runs.

Lifecycle

Lifecycle helpers use passive React effects. Their development StrictMode semantics intentionally match React:

  • onMounted setup may run twice;
  • onBeforeUnmount may observe the rehearsal cleanup and the real cleanup;
  • onUpdated never fires for either initial-mount setup, then runs after later commits.

Examples

The workspace contains three applications using the exact same examples/shared-store package:

pnpm --filter @rue/example-web dev
pnpm --filter @rue/example-expo start
pnpm --filter @rue/example-expo-rsd start

Each renderer also includes component-owned TODO state built with useReactive, alongside the shared module store consumed through useReactivity.

Validate all examples:

pnpm examples:check
pnpm examples:bundle

The Expo Strict DOM example also includes an EAS Android + Maestro workflow that asserts the app is executing on Hermes and that a Rue mutation updates the UI.

Development

pnpm install
pnpm run ci

Contributors need Node 22.13 or newer for the current Expo toolchain. The published ES2017 runtime itself supports Node 18+ and does not execute Node APIs inside applications.

Useful focused commands:

pnpm test:web
pnpm test:native
pnpm test:hermes
pnpm test:coverage
pnpm check:platform
pnpm check:size

See docs/audit.md, docs/architecture.md, and MIGRATION.md for design and compatibility details.

License

MIT. Rue is a fork of Veact and preserves the original copyright and attribution.

About

A mutable state enhancer library for React, based on @vue/reactivity.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

93 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Rue

Vue's mutable reactivity model for React on the web and React Native.

Rue re-exports @vue/reactivity and adds React hooks that connect refs, reactive objects, computed values, watchers, and effect scopes to the React lifecycle. Rendering stays ordinary React and JSX.

import{useComputed,useReactive}from'@themakers/rue'exportfunctionCounter(){conststate=useReactive({count: 0})constdoubled=useComputed(()=>state.count*2)return(<buttononClick={()=>state.count++}>{state.count} / {doubled.value}</button>)}

Platforms

PlatformStatusValidation
React DOMSupportedVitest, jsdom, Vite example
React NativeSupportedjest-expo, React Native Testing Library, Expo example
HermesSupportedHermes bytecode compile and Expo Android Hermes bundle
React Strict DOMSupportedExpo + RSD example and Android Hermes bundle
SSRArchitecture-readygetServerSnapshot and renderToString smoke test
React Server ComponentsNot supportedOutside the current scope

The runtime imports only react and @vue/reactivity. It has no dependency on react-dom, react-native, browser globals, or renderer-specific batching.

Installation

pnpm add @themakers/rue react @vue/reactivity

Rue can also be installed directly from a pinned git commit before an npm release exists:

{
"dependencies": {
"@themakers/rue": "github:themakers/rue#<commit-sha>"
}
}

The repository does not commit dist. During an npm or pnpm git installation, prepare uses the installed development toolchain to generate the package. Bun does not install a git dependency's development dependencies, so Rue's trusted prepare automatically falls back to Bun's built-in bundler and its source declarations:

bun add --trust github:themakers/rue#<commit-sha>

If the application declares trustedDependencies manually, include @themakers/rue while retaining any other packages whose install scripts the application needs. prepack always performs the full build and validates the output before creating a package tarball. Pin a commit SHA so installs remain reproducible.

Requirements:

  • React 18 or 19;
  • @vue/reactivity 3.5.x;
  • Proxy support, available in current browsers and Hermes.

CI tests the minimum React 18.3.1 peer, the latest React 19 web release, and the React/React Native versions pinned by the current Expo SDK. Expo applications should follow Expo's renderer version matrix rather than upgrading React Native independently.

react and @vue/reactivity are peer dependencies. Ensure the application has only one resolved copy of @vue/reactivity, especially when Vue and Rue are installed together. Separate copies maintain separate dependency graphs, so a value created by one copy cannot notify effects owned by another.

API

Rue exports the complete public API of @vue/reactivity, plus:

APIPurpose
onMountedPassive effect setup after mount
onUpdatedPassive effect after every commit except the initial mount
onBeforeUnmountPassive effect cleanup
useRefComponent-owned deep Vue ref
useShallowRefComponent-owned shallow Vue ref
useTemplateRefVue-style ref that binds directly to a JSX ref
useCustomRefComponent-owned custom Vue ref
useReactiveComponent-owned deep reactive object
useShallowReactiveComponent-owned shallow reactive object
useReadonlyReadonly view subscribed to its reactive source
useShallowReadonlyShallow readonly view
useComputedComponent-owned computed ref
watchSynchronous Vue watch wrapper
baseWatchDirect @vue/reactivity watch export
useWatchComponent-owned watch with automatic cleanup
watchEffectSynchronous Vue watch effect wrapper
useWatchEffectComponent-owned watch effect with automatic cleanup
useEffectScopeComponent-owned effect scope facade
useReactivitySubscribe a component to module-level reactive data

Component state

import{useComputed,useReactive,useRef,useWatch}from'@themakers/rue'exportfunctionProfile(){constname=useRef('Ada')conststate=useReactive({visits: 0})constlabel=useComputed(()=>`${name.value}: ${state.visits}`)useWatch(()=>state.visits,(visits)=>console.log('visits',visits),)return<buttononClick={()=>state.visits++}>{label.value}</button>}

Initial arguments follow React state initializer semantics: changing an initializer on a later render does not replace the existing ref, proxy, or computed object.

Template refs

useTemplateRef provides one ref for both JSX binding and Vue-style access. React's renderer writes through the internal .current bridge while application code reads the same element through .value:

import{onMounted,useTemplateRef}from'@themakers/rue'exportfunctionAutofocusInput(){constinput=useTemplateRef<HTMLInputElement>()onMounted(()=>input.value?.focus())return<inputref={input}/>}

The ref starts at null, receives the host element after commit, and returns to null when the element unmounts.

Shared stores

Create a normal Vue reactive value at module scope and subscribe each consumer with useReactivity:

import{computed,reactive,useReactivity}from'@themakers/rue'constcounter=reactive({count: 0})constdoubled=computed(()=>counter.count*2)exportfunctionCounter(){conststate=useReactivity(()=>({ counter, doubled }))return(<buttononClick={()=>state.counter.count++}>{state.counter.count} / {state.doubled.value}</button>)}

Rue uses useSyncExternalStore with a stable numeric version snapshot. A reactive mutation invalidates subscribed components synchronously, allowing React to detect an external-store change during a transition and restart the render before commit.

Do not mutate external stores during render. For SSR, create request-local stores rather than sharing a mutable singleton between requests.

Watchers

Raw watch and watchEffect run synchronously and return the Vue watch handle. Their hook variants are installed after commit and stop automatically during cleanup:

useWatchEffect((onCleanup)=>{constrequest=loadItem(itemId.value)onCleanup(()=>request.abort())})

Vue's flush option is intentionally not supported. Rue has no Vue renderer scheduler and does not emulate one with platform-specific APIs.

In development StrictMode, React may rehearse setup and cleanup. User effects must follow the same idempotency rules as useEffect; Rue guarantees that each backing watcher is stopped and that no discarded render leaves a watcher.

Effect scopes

scope.run(setup) registers setup during render. Rue executes it after commit inside a fresh Vue effect scope and stops that backing scope during cleanup:

import{onScopeDispose,useEffectScope,watch}from'@themakers/rue'constscope=useEffectScope()scope.run(()=>{consthandle=watch(source,consume)onScopeDispose(()=>console.log('scope disposed'))returnhandle})

Because setup is deferred, scope.run() returns undefined. pause, resume, and stop operate on the active backing scope. StrictMode can execute the setup more than once with a complete disposal between runs.

Lifecycle

Lifecycle helpers use passive React effects. Their development StrictMode semantics intentionally match React:

  • onMounted setup may run twice;
  • onBeforeUnmount may observe the rehearsal cleanup and the real cleanup;
  • onUpdated never fires for either initial-mount setup, then runs after later commits.

Examples

The workspace contains three applications using the exact same examples/shared-store package:

pnpm --filter @rue/example-web dev
pnpm --filter @rue/example-expo start
pnpm --filter @rue/example-expo-rsd start

Each renderer also includes component-owned TODO state built with useReactive, alongside the shared module store consumed through useReactivity.

Validate all examples:

pnpm examples:check
pnpm examples:bundle

The Expo Strict DOM example also includes an EAS Android + Maestro workflow that asserts the app is executing on Hermes and that a Rue mutation updates the UI.

Development

pnpm install
pnpm run ci

Contributors need Node 22.13 or newer for the current Expo toolchain. The published ES2017 runtime itself supports Node 18+ and does not execute Node APIs inside applications.

Useful focused commands:

pnpm test:web
pnpm test:native
pnpm test:hermes
pnpm test:coverage
pnpm check:platform
pnpm check:size

See docs/audit.md, docs/architecture.md, and MIGRATION.md for design and compatibility details.

License

MIT. Rue is a fork of Veact and preserves the original copyright and attribution.

About

A mutable state enhancer library for React, based on @vue/reactivity.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

93 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Rue

Vue's mutable reactivity model for React on the web and React Native.

Rue re-exports @vue/reactivity and adds React hooks that connect refs, reactive objects, computed values, watchers, and effect scopes to the React lifecycle. Rendering stays ordinary React and JSX.

import{useComputed,useReactive}from'@themakers/rue'exportfunctionCounter(){conststate=useReactive({count: 0})constdoubled=useComputed(()=>state.count*2)return(<buttononClick={()=>state.count++}>{state.count} / {doubled.value}</button>)}

Platforms

PlatformStatusValidation
React DOMSupportedVitest, jsdom, Vite example
React NativeSupportedjest-expo, React Native Testing Library, Expo example
HermesSupportedHermes bytecode compile and Expo Android Hermes bundle
React Strict DOMSupportedExpo + RSD example and Android Hermes bundle
SSRArchitecture-readygetServerSnapshot and renderToString smoke test
React Server ComponentsNot supportedOutside the current scope

The runtime imports only react and @vue/reactivity. It has no dependency on react-dom, react-native, browser globals, or renderer-specific batching.

Installation

pnpm add @themakers/rue react @vue/reactivity

Rue can also be installed directly from a pinned git commit before an npm release exists:

{
"dependencies": {
"@themakers/rue": "github:themakers/rue#<commit-sha>"
}
}

The repository does not commit dist. During an npm or pnpm git installation, prepare uses the installed development toolchain to generate the package. Bun does not install a git dependency's development dependencies, so Rue's trusted prepare automatically falls back to Bun's built-in bundler and its source declarations:

bun add --trust github:themakers/rue#<commit-sha>

If the application declares trustedDependencies manually, include @themakers/rue while retaining any other packages whose install scripts the application needs. prepack always performs the full build and validates the output before creating a package tarball. Pin a commit SHA so installs remain reproducible.

Requirements:

  • React 18 or 19;
  • @vue/reactivity 3.5.x;
  • Proxy support, available in current browsers and Hermes.

CI tests the minimum React 18.3.1 peer, the latest React 19 web release, and the React/React Native versions pinned by the current Expo SDK. Expo applications should follow Expo's renderer version matrix rather than upgrading React Native independently.

react and @vue/reactivity are peer dependencies. Ensure the application has only one resolved copy of @vue/reactivity, especially when Vue and Rue are installed together. Separate copies maintain separate dependency graphs, so a value created by one copy cannot notify effects owned by another.

API

Rue exports the complete public API of @vue/reactivity, plus:

APIPurpose
onMountedPassive effect setup after mount
onUpdatedPassive effect after every commit except the initial mount
onBeforeUnmountPassive effect cleanup
useRefComponent-owned deep Vue ref
useShallowRefComponent-owned shallow Vue ref
useTemplateRefVue-style ref that binds directly to a JSX ref
useCustomRefComponent-owned custom Vue ref
useReactiveComponent-owned deep reactive object
useShallowReactiveComponent-owned shallow reactive object
useReadonlyReadonly view subscribed to its reactive source
useShallowReadonlyShallow readonly view
useComputedComponent-owned computed ref
watchSynchronous Vue watch wrapper
baseWatchDirect @vue/reactivity watch export
useWatchComponent-owned watch with automatic cleanup
watchEffectSynchronous Vue watch effect wrapper
useWatchEffectComponent-owned watch effect with automatic cleanup
useEffectScopeComponent-owned effect scope facade
useReactivitySubscribe a component to module-level reactive data

Component state

import{useComputed,useReactive,useRef,useWatch}from'@themakers/rue'exportfunctionProfile(){constname=useRef('Ada')conststate=useReactive({visits: 0})constlabel=useComputed(()=>`${name.value}: ${state.visits}`)useWatch(()=>state.visits,(visits)=>console.log('visits',visits),)return<buttononClick={()=>state.visits++}>{label.value}</button>}

Initial arguments follow React state initializer semantics: changing an initializer on a later render does not replace the existing ref, proxy, or computed object.

Template refs

useTemplateRef provides one ref for both JSX binding and Vue-style access. React's renderer writes through the internal .current bridge while application code reads the same element through .value:

import{onMounted,useTemplateRef}from'@themakers/rue'exportfunctionAutofocusInput(){constinput=useTemplateRef<HTMLInputElement>()onMounted(()=>input.value?.focus())return<inputref={input}/>}

The ref starts at null, receives the host element after commit, and returns to null when the element unmounts.

Shared stores

Create a normal Vue reactive value at module scope and subscribe each consumer with useReactivity:

import{computed,reactive,useReactivity}from'@themakers/rue'constcounter=reactive({count: 0})constdoubled=computed(()=>counter.count*2)exportfunctionCounter(){conststate=useReactivity(()=>({ counter, doubled }))return(<buttononClick={()=>state.counter.count++}>{state.counter.count} / {state.doubled.value}</button>)}

Rue uses useSyncExternalStore with a stable numeric version snapshot. A reactive mutation invalidates subscribed components synchronously, allowing React to detect an external-store change during a transition and restart the render before commit.

Do not mutate external stores during render. For SSR, create request-local stores rather than sharing a mutable singleton between requests.

Watchers

Raw watch and watchEffect run synchronously and return the Vue watch handle. Their hook variants are installed after commit and stop automatically during cleanup:

useWatchEffect((onCleanup)=>{constrequest=loadItem(itemId.value)onCleanup(()=>request.abort())})

Vue's flush option is intentionally not supported. Rue has no Vue renderer scheduler and does not emulate one with platform-specific APIs.

In development StrictMode, React may rehearse setup and cleanup. User effects must follow the same idempotency rules as useEffect; Rue guarantees that each backing watcher is stopped and that no discarded render leaves a watcher.

Effect scopes

scope.run(setup) registers setup during render. Rue executes it after commit inside a fresh Vue effect scope and stops that backing scope during cleanup:

import{onScopeDispose,useEffectScope,watch}from'@themakers/rue'constscope=useEffectScope()scope.run(()=>{consthandle=watch(source,consume)onScopeDispose(()=>console.log('scope disposed'))returnhandle})

Because setup is deferred, scope.run() returns undefined. pause, resume, and stop operate on the active backing scope. StrictMode can execute the setup more than once with a complete disposal between runs.

Lifecycle

Lifecycle helpers use passive React effects. Their development StrictMode semantics intentionally match React:

  • onMounted setup may run twice;
  • onBeforeUnmount may observe the rehearsal cleanup and the real cleanup;
  • onUpdated never fires for either initial-mount setup, then runs after later commits.

Examples

The workspace contains three applications using the exact same examples/shared-store package:

pnpm --filter @rue/example-web dev
pnpm --filter @rue/example-expo start
pnpm --filter @rue/example-expo-rsd start

Each renderer also includes component-owned TODO state built with useReactive, alongside the shared module store consumed through useReactivity.

Validate all examples:

pnpm examples:check
pnpm examples:bundle

The Expo Strict DOM example also includes an EAS Android + Maestro workflow that asserts the app is executing on Hermes and that a Rue mutation updates the UI.

Development

pnpm install
pnpm run ci

Contributors need Node 22.13 or newer for the current Expo toolchain. The published ES2017 runtime itself supports Node 18+ and does not execute Node APIs inside applications.

Useful focused commands:

pnpm test:web
pnpm test:native
pnpm test:hermes
pnpm test:coverage
pnpm check:platform
pnpm check:size

See docs/audit.md, docs/architecture.md, and MIGRATION.md for design and compatibility details.

License

MIT. Rue is a fork of Veact and preserves the original copyright and attribution.

About

A mutable state enhancer library for React, based on @vue/reactivity.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

93 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Rue

Vue's mutable reactivity model for React on the web and React Native.

Rue re-exports @vue/reactivity and adds React hooks that connect refs, reactive objects, computed values, watchers, and effect scopes to the React lifecycle. Rendering stays ordinary React and JSX.

import{useComputed,useReactive}from'@themakers/rue'exportfunctionCounter(){conststate=useReactive({count: 0})constdoubled=useComputed(()=>state.count*2)return(<buttononClick={()=>state.count++}>{state.count} / {doubled.value}</button>)}

Platforms

PlatformStatusValidation
React DOMSupportedVitest, jsdom, Vite example
React NativeSupportedjest-expo, React Native Testing Library, Expo example
HermesSupportedHermes bytecode compile and Expo Android Hermes bundle
React Strict DOMSupportedExpo + RSD example and Android Hermes bundle
SSRArchitecture-readygetServerSnapshot and renderToString smoke test
React Server ComponentsNot supportedOutside the current scope

The runtime imports only react and @vue/reactivity. It has no dependency on react-dom, react-native, browser globals, or renderer-specific batching.

Installation

pnpm add @themakers/rue react @vue/reactivity

Rue can also be installed directly from a pinned git commit before an npm release exists:

{
"dependencies": {
"@themakers/rue": "github:themakers/rue#<commit-sha>"
}
}

The repository does not commit dist. During an npm or pnpm git installation, prepare uses the installed development toolchain to generate the package. Bun does not install a git dependency's development dependencies, so Rue's trusted prepare automatically falls back to Bun's built-in bundler and its source declarations:

bun add --trust github:themakers/rue#<commit-sha>

If the application declares trustedDependencies manually, include @themakers/rue while retaining any other packages whose install scripts the application needs. prepack always performs the full build and validates the output before creating a package tarball. Pin a commit SHA so installs remain reproducible.

Requirements:

  • React 18 or 19;
  • @vue/reactivity 3.5.x;
  • Proxy support, available in current browsers and Hermes.

CI tests the minimum React 18.3.1 peer, the latest React 19 web release, and the React/React Native versions pinned by the current Expo SDK. Expo applications should follow Expo's renderer version matrix rather than upgrading React Native independently.

react and @vue/reactivity are peer dependencies. Ensure the application has only one resolved copy of @vue/reactivity, especially when Vue and Rue are installed together. Separate copies maintain separate dependency graphs, so a value created by one copy cannot notify effects owned by another.

API

Rue exports the complete public API of @vue/reactivity, plus:

APIPurpose
onMountedPassive effect setup after mount
onUpdatedPassive effect after every commit except the initial mount
onBeforeUnmountPassive effect cleanup
useRefComponent-owned deep Vue ref
useShallowRefComponent-owned shallow Vue ref
useTemplateRefVue-style ref that binds directly to a JSX ref
useCustomRefComponent-owned custom Vue ref
useReactiveComponent-owned deep reactive object
useShallowReactiveComponent-owned shallow reactive object
useReadonlyReadonly view subscribed to its reactive source
useShallowReadonlyShallow readonly view
useComputedComponent-owned computed ref
watchSynchronous Vue watch wrapper
baseWatchDirect @vue/reactivity watch export
useWatchComponent-owned watch with automatic cleanup
watchEffectSynchronous Vue watch effect wrapper
useWatchEffectComponent-owned watch effect with automatic cleanup
useEffectScopeComponent-owned effect scope facade
useReactivitySubscribe a component to module-level reactive data

Component state

import{useComputed,useReactive,useRef,useWatch}from'@themakers/rue'exportfunctionProfile(){constname=useRef('Ada')conststate=useReactive({visits: 0})constlabel=useComputed(()=>`${name.value}: ${state.visits}`)useWatch(()=>state.visits,(visits)=>console.log('visits',visits),)return<buttononClick={()=>state.visits++}>{label.value}</button>}

Initial arguments follow React state initializer semantics: changing an initializer on a later render does not replace the existing ref, proxy, or computed object.

Template refs

useTemplateRef provides one ref for both JSX binding and Vue-style access. React's renderer writes through the internal .current bridge while application code reads the same element through .value:

import{onMounted,useTemplateRef}from'@themakers/rue'exportfunctionAutofocusInput(){constinput=useTemplateRef<HTMLInputElement>()onMounted(()=>input.value?.focus())return<inputref={input}/>}

The ref starts at null, receives the host element after commit, and returns to null when the element unmounts.

Shared stores

Create a normal Vue reactive value at module scope and subscribe each consumer with useReactivity:

import{computed,reactive,useReactivity}from'@themakers/rue'constcounter=reactive({count: 0})constdoubled=computed(()=>counter.count*2)exportfunctionCounter(){conststate=useReactivity(()=>({ counter, doubled }))return(<buttononClick={()=>state.counter.count++}>{state.counter.count} / {state.doubled.value}</button>)}

Rue uses useSyncExternalStore with a stable numeric version snapshot. A reactive mutation invalidates subscribed components synchronously, allowing React to detect an external-store change during a transition and restart the render before commit.

Do not mutate external stores during render. For SSR, create request-local stores rather than sharing a mutable singleton between requests.

Watchers

Raw watch and watchEffect run synchronously and return the Vue watch handle. Their hook variants are installed after commit and stop automatically during cleanup:

useWatchEffect((onCleanup)=>{constrequest=loadItem(itemId.value)onCleanup(()=>request.abort())})

Vue's flush option is intentionally not supported. Rue has no Vue renderer scheduler and does not emulate one with platform-specific APIs.

In development StrictMode, React may rehearse setup and cleanup. User effects must follow the same idempotency rules as useEffect; Rue guarantees that each backing watcher is stopped and that no discarded render leaves a watcher.

Effect scopes

scope.run(setup) registers setup during render. Rue executes it after commit inside a fresh Vue effect scope and stops that backing scope during cleanup:

import{onScopeDispose,useEffectScope,watch}from'@themakers/rue'constscope=useEffectScope()scope.run(()=>{consthandle=watch(source,consume)onScopeDispose(()=>console.log('scope disposed'))returnhandle})

Because setup is deferred, scope.run() returns undefined. pause, resume, and stop operate on the active backing scope. StrictMode can execute the setup more than once with a complete disposal between runs.

Lifecycle

Lifecycle helpers use passive React effects. Their development StrictMode semantics intentionally match React:

  • onMounted setup may run twice;
  • onBeforeUnmount may observe the rehearsal cleanup and the real cleanup;
  • onUpdated never fires for either initial-mount setup, then runs after later commits.

Examples

The workspace contains three applications using the exact same examples/shared-store package:

pnpm --filter @rue/example-web dev
pnpm --filter @rue/example-expo start
pnpm --filter @rue/example-expo-rsd start

Each renderer also includes component-owned TODO state built with useReactive, alongside the shared module store consumed through useReactivity.

Validate all examples:

pnpm examples:check
pnpm examples:bundle

The Expo Strict DOM example also includes an EAS Android + Maestro workflow that asserts the app is executing on Hermes and that a Rue mutation updates the UI.

Development

pnpm install
pnpm run ci

Contributors need Node 22.13 or newer for the current Expo toolchain. The published ES2017 runtime itself supports Node 18+ and does not execute Node APIs inside applications.

Useful focused commands:

pnpm test:web
pnpm test:native
pnpm test:hermes
pnpm test:coverage
pnpm check:platform
pnpm check:size

See docs/audit.md, docs/architecture.md, and MIGRATION.md for design and compatibility details.

License

MIT. Rue is a fork of Veact and preserves the original copyright and attribution.

About

A mutable state enhancer library for React, based on @vue/reactivity.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages