100 lines
3.1 KiB
Markdown
100 lines
3.1 KiB
Markdown
# Compiled plugins
|
||
|
||
A quick `.js` plugin (see [Plugin system](/docs/plugins/)) 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
|
||
|
||
```ts
|
||
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
|
||
|
||
```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/`](https://codeberg.org/reversefr/orbit/src/branch/main/plugin-template).
|
||
|
||
```bash
|
||
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"] }
|
||
```
|
||
|
||
3. Reload. Your composer button and Settings section appear.
|