Rete.js: Visual Programming Framework
Rete.js is a robust JavaScript framework for building visual programming interfaces and workflows, allowing developers to create node-based editors for dataflow, control flow, or mixed processing. It provides out-of-the-box solutions for visualization with various UI libraries (React, Vue, Angular, Svelte, Lit) and offers different types of engines for graph processing. The current stable version is 2.0.6, with recent minor updates addressing bug fixes and performance improvements. Rete.js v2 represents a significant architectural shift from v1, adopting a TypeScript-first approach, a flexible plugin system, and enhanced customization capabilities. Its modular ecosystem allows developers to pick and choose necessary packages, avoiding unnecessary dependencies and supporting custom builds.
Common errors
-
Type instantiation is excessively deep and possibly infinite. ts(2589)
cause Using an older TypeScript version (below 4.7) with Rete.js v2.fixUpgrade TypeScript to version 4.7 or higher. If not possible, use `@ts-ignore` for the `use` method as a temporary workaround. -
Error: Cannot find module 'rete' or Cannot resolve module 'rete'
cause Incorrect package installation or module resolution issues in a CJS environment when expecting ESM.fixEnsure `rete` and its necessary plugins are installed via `npm install rete rete-area-plugin rete-connection-plugin rete-react-plugin ...`. For CJS, ensure your bundler (Webpack, Rollup) or Node.js environment is configured to handle ESM imports correctly, or explicitly use `require()` if the package provides a CJS entry point. Modern Rete.js is ESM-first. -
Editor does not render or appears blank until window resize.
cause The rendering container's dimensions might not be fully calculated or applied when the editor/area is initialized, especially in component lifecycle hooks (e.g., `ngAfterViewInit` in Angular).fixWrap the editor/area initialization in a `setTimeout` with a small delay (e.g., 0ms or 10ms) to ensure the DOM has settled. Alternatively, manually call `area.resize()` after initialization if the container's dimensions are known.
Warnings
- breaking Rete.js v2 introduces significant architectural changes and breaking changes compared to v1, including a TypeScript-first design, a new plugin system, and a different approach to node and component creation. Migration requires substantial code refactoring.
- gotcha Direct mutation of arrays returned by `editor.getNodes()` or `editor.getConnections()` might lead to unexpected behavior. These methods now return copies to prevent external interference with the editor's internal state.
- gotcha Rete.js v2 relies heavily on TypeScript for improved developer experience and type safety. While usable with plain JavaScript, the developer experience (DX) will be significantly poorer due to the lack of type inference and stricter typing in the framework's design.
- gotcha Performance issues can arise with a large number of nodes, especially in rendering-intensive scenarios. Synchronous operations or complex node rendering can block the main thread or cause low FPS.
Install
-
npm install rete -
yarn add rete -
pnpm add rete
Imports
- NodeEditor
const { NodeEditor } = require('rete')import { NodeEditor } from 'rete' - GetSchemes
import { GetSchemes, ClassicPreset } from 'rete' - Connection
import { Connection } from 'rete'; const connection = new Connection(...)import { ClassicPreset } from 'rete'; const connection = new ClassicPreset.Connection(sourceNode, 'portKey', targetNode, 'portKey')
Quickstart
import { NodeEditor, GetSchemes, ClassicPreset } from 'rete';
import { AreaPlugin, AreaExtensions } from 'rete-area-plugin';
import { ConnectionPlugin } from 'rete-connection-plugin';
import { ReactPlugin, Presets, ReactArea2D } from 'rete-react-plugin';
import { createRoot } from 'react-dom/client';
type Schemes = GetSchemes<
ClassicPreset.Node,
ClassicPreset.Connection<ClassicPreset.Node, ClassicPreset.Node>
>;
type AreaExtra = ReactArea2D<Schemes>;
const socket = new ClassicPreset.Socket('Number');
class MyNode extends ClassicPreset.Node {
width = 180;
height = 120;
constructor(name: string, numValue: number = 0) {
super(name);
this.addControl('value', new ClassicPreset.InputControl('number', { initialValue: numValue }));
this.addOutput('num', new ClassicPreset.Output(socket, 'Number'));
}
}
async function createEditor(container: HTMLElement) {
const editor = new NodeEditor<Schemes>();
const area = new AreaPlugin<Schemes, AreaExtra>(container);
const connection = new ConnectionPlugin<Schemes, AreaExtra>();
const render = new ReactPlugin<Schemes, AreaExtra>();
AreaExtensions.zoomAt(area, editor.getNodes());
AreaExtensions.simpleNodesOrder(area);
render.addPreset(Presets.contextMenu.setup());
render.addPreset(Presets.classic.setup());
editor.use(area);
area.use(connection);
area.use(render);
const nodeA = new MyNode('A', 10);
const nodeB = new MyNode('B', 20);
await editor.addNode(nodeA);
await editor.addNode(nodeB);
await area.translate(nodeA.id, { x: 0, y: 0 });
await area.translate(nodeB.id, { x: 250, y: 0 });
const connectionAtoB = new ClassicPreset.Connection(nodeA, 'num', nodeB, 'num');
await editor.addConnection(connectionAtoB);
// Render the editor
const root = createRoot(container);
root.render(<ReactArea2D area={area}
// Pass extra props if needed
/>);
console.log('Rete.js editor initialized with two nodes and a connection.');
return editor;
}
// To run this in a browser:
// const container = document.getElementById('rete-container');
// if (container) { createEditor(container); }