Compiled plugins

A quick .js plugin (see Plugin system) is great for small things. For anything substantial — real components with state and hooks — build the plugin like a normal project and compile it down to one droppable file: a compiled, externalized-React plugin model.

The trick: externalize React

The output bundle must not carry its own copy of React. Two React instances on one page break hooks ("invalid hook call"). Instead, mark react, react-dom and react/jsx-runtime external and map them to Orbit's globals, so your plugin shares the host's single React instance:

Module Maps to
react Orbit.React
react-dom Orbit.ReactDOM
react/jsx-runtime Orbit.jsxRuntime

Because your component is created with Orbit's React and rendered through a slot into Orbit's tree, hooks, state, context and effects all work.

Vite config

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

export default defineConfig({
  plugins: [react()],
  build: {
    lib: { entry: 'src/index.tsx', formats: ['iife'], name: 'MyOrbitPlugin',
           fileName: () => 'my-plugin.js' },
    rollupOptions: {
      external: ['react', 'react-dom', 'react/jsx-runtime'],
      output: { globals: {
        react: 'Orbit.React',
        'react-dom': 'Orbit.ReactDOM',
        'react/jsx-runtime': 'Orbit.jsxRuntime',
      } },
    },
  },
});

Write normal TSX

import { useState } from 'react';

Orbit.plugin('my-plugin', (orbit) => {
  orbit.addUi('composer_button', () => <ShrugButton orbit={orbit} />);
  orbit.addSettingsSection({
    label: 'My plugin', icon: '🧩',
    render: () => <Panel orbit={orbit} />,
  });
});

function Panel({ orbit }) {
  const [count, setCount] = useState(() => orbit.storage.get('count', 0));
  const bump = () => { const n = count + 1; setCount(n); orbit.storage.set('count', n); };
  return (
    <div className="scard"><div className="scard__body">
      <div className="sfield"><div className="sfield__intro">
        Compiled plugin — real React with hooks. You are <b>{orbit.state.nick()}</b>.
      </div></div>
      <div className="modal__actions">
        <button className="upbtn upbtn--primary" onClick={bump}>Clicked {count}×</button>
      </div>
    </div></div>
  );
}

The compiled bundle is tiny — it contains only your code, calling Orbit's React.

Starter template

A ready-to-copy starter (Vite config with the externals set up, tsconfig, ambient types and a working example) lives in the repo at plugin-template/.

git clone https://codeberg.org/reversefr/orbit.git
cd orbit/plugin-template
npm install
npm run build        # → dist/orbit-plugin-template.js

Deploy

  1. Copy the built .js to where Orbit can fetch it (e.g. /app/plugins/).
  2. List it in the deployed config.json — no rebuild of Orbit needed:

json { "plugins": ["/app/plugins/my-plugin.js"] }

  1. Reload. Your composer button and Settings section appear.