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
- Copy the built
.jsto where Orbit can fetch it (e.g./app/plugins/). - List it in the deployed
config.json— no rebuild of Orbit needed:
json
{ "plugins": ["/app/plugins/my-plugin.js"] }
- Reload. Your composer button and Settings section appear.