# 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', () =>