MCPcopy
hub / github.com/Ripple-TS/ripple

github.com/Ripple-TS/ripple @1.0.1 sqlite

repository ↗ · DeepWiki ↗ · release 1.0.1 ↗
3,113 symbols 8,550 edges 352 files 970 documented · 31%
README

<img src="https://github.com/Ripple-TS/ripple/raw/1.0.1/assets/ripple-mobile.png" alt="Ripple - the elegant TypeScript UI framework" />

CI Discord Open in StackBlitz

Ripple TS

Ripple is a TypeScript UI framework that combines the best parts of React, Solid, and Svelte. Created by @trueadm, who has contributed to Inferno, React, Lexical, and Svelte 5.

Key Philosophy: Ripple is TS-first with .tsrx as its default component file extension. This allows seamless TypeScript integration and a unique syntax that enhances both human and LLM developer experience.

.tsrx is also a standalone language: the same source can now compile to React, Solid, or Ripple via TSRX — a TypeScript language extension that treats Ripple as one of several target runtimes. If you want the authoring ergonomics without committing to Ripple's runtime, start there.

📚 Ripple Docs | 🎮 Ripple Playground | 🧩 TSRX Website

Features

  • Fine-grained Reactivity: track with lazy destructuring for a unique reactivity system
  • 🔥 Performance: Industry-leading rendering speed, bundle size, and memory usage
  • 📦 Reactive Collections: RippleArray, RippleObject, RippleMap, RippleSet imported from 'ripple' with full reactivity
  • 🎯 TypeScript First: Complete type safety with the default .tsrx component extension
  • 🛠️ Developer Tools: VSCode extension, Prettier, and ESLint support
  • 🎨 Scoped Styling: Component-level CSS with automatic scoping

🚀 Quick Start

Using CLI (Recommended)

npx create-ripple
cd my-app
npm install && npm run dev

Using Template

npx degit Ripple-TS/ripple/templates/basic my-app
cd my-app
npm install && npm run dev

Add to Existing Project

npm install ripple @ripple-ts/vite-plugin

Note: You can use npm, pnpm, yarn, or bun package managers.

→ Full Installation Guide

Mounting Your App

// index.ts
import { mount } from 'ripple';
import { App } from './App.tsrx';

mount(App, {
  props: { title: 'Hello world!' },
  target: document.getElementById('root'),
});

🔧 VSCode Extension

Install the Ripple VSCode extension for:

  • Syntax highlighting
  • TypeScript integration
  • Real-time diagnostics
  • IntelliSense autocomplete

→ Editor Setup Guide

Core Concepts

Components

Define components with the component keyword. Unlike React, you don't return JSX—you write it directly:

component Button(props: { text: string, onClick: () => void }) {
  <button onClick={props.onClick}>
    {props.text}
  </button>
}

export component App() {
  <Button text="Click me" onClick={() => console.log("Clicked!")} />
}

→ Component Guide

Reactivity

Create reactive state with track and use lazy destructuring (&[]) to access the value directly:

import { track } from 'ripple';

export component App() {
  let &[count] = track(0);






{"Count: "}{count}


    <button onClick={() => count++}>{"Increment"}</button>



}

You can also pass around the tracked value object from the second argument:

import { track } from 'ripple';

export component App() {
  let &[count, trackedCount] = track(0);



{count}


  <IncrementButton {trackedCount} />
}

Alternatively, you can read and write tracked values directly using the .value property on the Tracked<V> object:

import { track } from 'ripple';

export component App() {
  const count = track(0);



{count.value}


  <button onClick={() => count.value++}>{"Increment"}</button>
}

Using &[...] is preferred in most cases for cleaner code, but .value is useful when you need to keep the Tracked<V> object around — for example, when storing tracked values in data structures or passing them as Tracked<T> props.

Derived values automatically update:

import { track } from 'ripple';

export component App() {
  let &[count] = track(0);
  let &[double] = track(() => count * 2);
  let &[quadruple] = track(() => double * 2);






{"Count: "}{count}




{"Double: "}{double}




{"Quadruple: "}{quadruple}


    <button onClick={() => count++}>{"Increment"}</button>



}

Reactive collections with full reactivity:

import { RippleArray, RippleObject, RippleMap, RippleSet } from 'ripple';

export component App() {
  const items = new RippleArray(1, 2, 3);          // RippleArray
  const obj = new RippleObject({ a: 1, b: 2 });    // RippleObject
  const map = new RippleMap([['k', 'v']]);          // RippleMap
  const set = new RippleSet([1, 2, 3]);             // RippleSet






{"Items: "}{items.join(', ')}




{"Object: a="}{obj.a}{", b="}{obj.b}{", c="}{obj.c}


    <button onClick={() => items.push(items.length + 1)}>{"Add Item"}</button>
    <button onClick={() => obj.c = (obj.c ?? 0) + 1}>{"Increment c"}</button>



}

→ Reactivity Guide

Transporting Reactivity

Pass the tracked ref (second element) across function boundaries:

import { track } from 'ripple';

function createDouble(&[count]) {
  return track(() => count * 2);
}

export component App() {
  let &[count, countTracked] = track(0);
  const &[double] = createDouble(countTracked);






{"Double: "}{double}


    <button onClick={() => count++}>{"Increment"}</button>



}

→ Transporting Reactivity Guide

Effects & Side Effects

import { track, effect } from 'ripple';

export component App() {
  let &[count] = track(0);

  effect(() => {
    console.log('Count changed:', count);
  });

  <button onClick={() => count++}>{'Increment'}</button>
}

→ Effects & Reactivity Guide

Control Flow

Conditionals:

import { track } from 'ripple';

export component App() {
  let &[condition] = track(true);




    if (condition) {


{'True'}


    } else {


{'False'}


    }
    <button onClick={() => condition = !condition}>{"Toggle"}</button>



}

Loops:

import { RippleArray } from 'ripple';

export component App() {
  const items = new RippleArray(
    {id: 1, name: 'Item 1'},
    {id: 2, name: 'Item 2'},
    {id: 3, name: 'Item 3'}
  );




    for (const item of items; index i; key item.id) {


{item.name}{" (index: "}{i}{")"}


    }
    <button onClick={() => items.push({id: items.length + 1, name: `Item ${items.length + 1}`})}>{"Add Item"}</button>



}

Error Boundaries:

component ComponentThatMayFail(props: { shouldFail: boolean }) {
  if (props.shouldFail) {
    throw new Error('Component failed!');
    {'This will never render'}
  }



{"Component working fine"}


}

import { track } from 'ripple';

export component App() {
  let &[shouldFail] = track(false);




    try {
      <ComponentThatMayFail {shouldFail} />
    } catch (e) {


{'Error: ' + e.message}


    }
    <button onClick={() => shouldFail = !shouldFail}>{"Toggle Error"}</button>



}

→ Control Flow Guide

DOM Refs

Capture DOM elements with the {ref fn} syntax:

export component App() {


 console.log(node)}>{"Hello"}


}

→ DOM Refs Guide

Events

Use React-style event handlers:

import { track } from 'ripple';

export component App() {
  let &[value] = track('');




    <button onClick={() => console.log('Clicked')}>{'Click'}</button>
    <input onInput={(e) => value = e.target.value} />


{"You typed: "}{value}





}

→ Events Guide

Styling

Scoped CSS:

export component App() {


{"Content"}



  <style>
    .container {
      padding: 1rem;
      background: lightblue;
      border-radius: 8px;
    }
  </style>
}

Dynamic styles:

import { track } from 'ripple';

export component App() {
  let &[color] = track('red');






{"Styled text"}


    <button onClick={() => color = color === 'red' ? 'blue' : 'red'}>{"Toggle Color"}</button>



}

→ Styling Guide

Advanced Features

Context API

Share state across the component tree:

import { Context, track } from 'ripple';

const ThemeContext = new Context();

component Child() {
  const &[theme] = ThemeContext.get();


{"Theme: " + theme}


}

export component App() {
  let &[theme, themeTracked] = track('light');

  ThemeContext.set(themeTracked);




    <Child />
    <button onClick={() => theme = theme === 'light' ? 'dark' : 'light'}>{"Toggle Theme"}</button>



}

→ State Management Guide

Portals

Render content outside the component hierarchy:

import { Portal, track } from 'ripple';

export component App() {
  let &[showModal] = track(false);




    <button onClick={() => showModal = !showModal}>{"Toggle Modal"}</button>

    if (showModal) {
      <Portal target={document.body}>





{'Modal content'}


          <button onClick={() => showModal = false}>{"Close"}</button>



      </Portal>
    }



}

→ Portal & Component Guide

Resources

Contributing

Contributions are welcome! Please see our contributing guidelines.

License

MIT License - see LICENSE for details.

Extension points exported contracts — how you extend this code

Context (Interface)
(no doc) [4 implementers]
packages/tsrx/types/index.d.ts
Context (Interface)
(no doc) [4 implementers]
packages/ripple/types/index.d.ts
Context (Interface)
(no doc) [4 implementers]
packages/vite-plugin/types/index.d.ts
TsrxSolidOptions (Interface)
(no doc)
packages/vite-plugin-solid/types/index.d.ts
ServerlessConfig (Interface)
(no doc)
packages/adapter-vercel/types/index.d.ts
TsrxReactRspackPluginOptions (Interface)
(no doc)
packages/rspack-plugin-react/types/index.d.ts
HTMLElement (Interface)
(no doc)
packages/compat-react/tests/client.d.ts
ParseResult (Interface)
(no doc)
packages/eslint-parser/src/index.ts

Core symbols most depended-on inside this repo

push
called by 1199
packages/ripple/types/server.d.ts
append
called by 566
packages/ripple/src/runtime/url-search-params.js
querySelector
called by 552
packages/ripple/tests/common.d.ts
get
called by 469
packages/ripple/types/index.d.ts
toBeWithNewline
called by 274
packages/prettier-plugin/vitest-extensions.d.ts
child
called by 268
packages/tsrx/types/index.d.ts
sibling
called by 211
packages/tsrx/types/parse.d.ts
visit
called by 198
packages/tsrx/src/transform/lazy.js

Shape

Function 2,317
Method 436
Interface 292
Class 67
Enum 1

Languages

TypeScript100%
Python1%

Modules by API surface

packages/tsrx/types/parse.d.ts222 symbols
packages/tsrx/types/index.d.ts190 symbols
packages/ripple/tests/hydration/compiled/client/return.js128 symbols
packages/prettier-plugin/src/index.js117 symbols
packages/ripple/src/runtime/internal/server/index.js113 symbols
packages/tsrx-ripple/src/transform/client/index.js105 symbols
packages/tsrx-react/src/transform.js84 symbols
packages/tsrx/src/utils/builders.js83 symbols
packages/ripple/src/utils/builders.js83 symbols
packages/ripple/src/runtime/internal/client/runtime.js80 symbols
packages/ripple/tests/hydration/compiled/client/html.js67 symbols
packages/tsrx-solid/src/transform.js55 symbols

Dependencies from manifests, versioned

@changesets/changelog-githubcatalog:default · 1×
@changesets/clicatalog:default · 1×
@codemirror/commands6.9.1 · 1×
@codemirror/lang-javascript6.2.4 · 1×
@codemirror/state6.5.2 · 1×
@codemirror/theme-one-dark6.1.3 · 1×
@codemirror/view6.38.6 · 1×
@jridgewell/sourcemap-codeccatalog:default · 1×
@ripple-ts/adapterworkspace:* · 1×
@ripple-ts/adapter-nodeworkspace:* · 1×
@ripple-ts/cliworkspace:* · 1×

For agents

$ claude mcp add ripple \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact