docs(skill): plugin bundle pipeline v2 (IIFE/多资产/热更新) 文档入仓
This commit is contained in:
@@ -49,11 +49,17 @@ gated by a developer whitelist and bundles are validated for security.
|
||||
→ check minAppVersion compatibility
|
||||
→ <link> inject CSS (if manifest.style exists)
|
||||
→ <script src="/api/plugin/:id/runtime/main.js"> (IIFE executes)
|
||||
→ if the bundle code-splits, lazy chunks load from the same runtime
|
||||
endpoint (output.publicPath = /api/plugin/:id/runtime/), served as
|
||||
plain .js assets
|
||||
→ script calls definePlugin({ id, name, onLoad, ... })
|
||||
→ window.__TEABLE_PLUGINS__[pluginId] = config
|
||||
→ PluginLoader reads registration, creates PluginContext, calls onLoad(ctx)
|
||||
→ ctx.addContextMenuAction(...) etc. register React components to componentRegistry
|
||||
→ PluginSlot renders registered components at the right mount point
|
||||
→ if manifest.devMode is true (dev loop only), the loader polls the
|
||||
manifest every 4s and remounts the plugin when buildId changes —
|
||||
no page refresh (production manifests never set devMode)
|
||||
|
||||
3. Plugin unloads
|
||||
→ onUnload(ctx) called
|
||||
@@ -117,32 +123,41 @@ Location: `apps/nextjs-app/src/features/app/components/plugin/host/event-bus.ts`
|
||||
|
||||
```
|
||||
dist/
|
||||
├── main.js # IIFE bundle (webpack output)
|
||||
└── styles.css # optional stylesheet
|
||||
plugin.json # manifest
|
||||
├── main.js # IIFE entry bundle (webpack/vite output)
|
||||
├── <name>-<hash>.chunk.js # optional lazy chunks (code splitting) — FLAT names
|
||||
├── *.js.map # optional source maps (served as application/json)
|
||||
└── styles.css # optional stylesheet (Tailwind/SCSS output OK)
|
||||
plugin.json # manifest
|
||||
```
|
||||
|
||||
## Shared dependency bridging
|
||||
|
||||
Plugin webpack config marks these as externals, mapping to host globals:
|
||||
Plugin webpack config marks ONLY the React singleton as external, mapping to
|
||||
a host global:
|
||||
|
||||
```javascript
|
||||
externals: {
|
||||
'react': 'window.__TEABLE_HOST__.React',
|
||||
'react-dom': 'window.__TEABLE_HOST__.ReactDOM',
|
||||
'@teable/sdk': 'window.__TEABLE_HOST__.SDK',
|
||||
'@teable/ui-lib': 'window.__TEABLE_HOST__.UILib',
|
||||
'@teable/openapi': 'window.__TEABLE_HOST__.OpenAPI',
|
||||
'@teable/plugin-sdk': 'window.__TEABLE_HOST__.PluginSDK',
|
||||
'react': 'window.__TEABLE_HOST__.React',
|
||||
'react-dom': 'window.__TEABLE_HOST__.ReactDOM',
|
||||
}
|
||||
```
|
||||
|
||||
This means the plugin does NOT bundle React or host packages — it uses the
|
||||
host's singleton instances. The bundle only contains the plugin's own code.
|
||||
Everything else on the host (`SDK`, `UILib`, `OpenAPI`, `PluginSDK`) is read
|
||||
from `window.__TEABLE_HOST__` at runtime — there is NO working externals
|
||||
mapping for `@teable/*` packages (a value import via the old
|
||||
window-global-externals style compiled to `undefined`; via monorepo source
|
||||
aliases it would inline the whole package source — see troubleshooting.md
|
||||
Pitfall 1). `react/jsx-runtime` is intentionally NOT external: the jsx
|
||||
automatic transform bundles it, which is safe — it only creates element
|
||||
objects, while hooks/context/state still resolve through the externalized
|
||||
`react` → host singleton.
|
||||
|
||||
Other npm dependencies (react-router, charts, lodash…) are bundled into the
|
||||
plugin as normal.
|
||||
|
||||
## Two ways to access host APIs
|
||||
|
||||
### 1. Through `window.__TEABLE_HOST__` (recommended for IIFE)
|
||||
### 1. Through `window.__TEABLE_HOST__` (the only runtime path)
|
||||
|
||||
```javascript
|
||||
const host = window.__TEABLE_HOST__;
|
||||
@@ -153,17 +168,21 @@ const { getRecords } = host.OpenAPI;
|
||||
const { definePlugin, useSelection } = host.PluginSDK;
|
||||
```
|
||||
|
||||
This is what `plugins/batch-edit-pro/` uses — the plugin bundle has no
|
||||
`@teable/*` imports, everything resolves through the host global.
|
||||
This is what `plugins/plugin-showcase/` and `plugins/batch-edit-pro/` use —
|
||||
the plugin bundle has no `@teable/*` runtime imports, everything resolves
|
||||
through the host global. `import { useState } from 'react'` also works (the
|
||||
externals mapping above redirects it to the host singleton).
|
||||
|
||||
### 2. Through `@teable/plugin-sdk` (also works because webpack externals)
|
||||
### 2. `import type` from `@teable/*` (type-checking only)
|
||||
|
||||
```typescript
|
||||
import { definePlugin } from '@teable/plugin-sdk';
|
||||
import type { ITeableHost } from '@teable/plugin-sdk';
|
||||
export const host = window.__TEABLE_HOST__ as unknown as ITeableHost;
|
||||
```
|
||||
|
||||
At build time, webpack resolves this to `window.__TEABLE_HOST__.PluginSDK.definePlugin`.
|
||||
Both styles work — the externals config handles the mapping.
|
||||
`import type` is erased at compile time — it never reaches the bundle. This
|
||||
is how scaffolded projects get full type safety inside the monorepo
|
||||
(standalone scaffolds ship a loose-typed local host module instead).
|
||||
|
||||
## Positions and registration
|
||||
|
||||
@@ -173,4 +192,4 @@ Both styles work — the externals config handles the mapping.
|
||||
| Panel | Right-side drawer of a table view | `ctx.addPanel(id, component)` |
|
||||
| View | View-type dropdown (alongside grid/form/gallery) | `ctx.addView(id, component)` |
|
||||
| Dashboard | Grid card on space dashboard | `ctx.addDashboardWidget(id, component)` |
|
||||
| Route | Full-page route (e.g., `/plugin/:pluginId/my-page`) | `ctx.addRoute(path, component)` |
|
||||
| Route | Full-page app at `/base/:baseId/p/:pluginId` (whole viewport inside BaseLayout; own router OK, HashRouter recommended) | `ctx.addRoute(id, component)` |
|
||||
|
||||
Reference in New Issue
Block a user