skills/ 现包含 inkreach-teable + inkreach-teable-plugin, npx skills add 一并安装两个;install.sh 改为遍历安装 skills/*。
107 lines
4.6 KiB
Markdown
107 lines
4.6 KiB
Markdown
# Common pitfalls and how to avoid them
|
|
|
|
These are real issues discovered during plugin development. Read before writing
|
|
plugin logic — each one will save you debugging time.
|
|
|
|
## Pitfall 1: Importing `@teable/*` packages instead of using host globals
|
|
|
|
```typescript
|
|
// WRONG — webpack externals map these away at build time, so at runtime
|
|
// the import produces `undefined` or throws:
|
|
import { useFields } from '@teable/sdk'; // → undefined at runtime
|
|
import { Button } from '@teable/ui-lib'; // → undefined at runtime
|
|
import { definePlugin } from '@teable/plugin-sdk'; // fine (it re-exports from host)
|
|
|
|
// CORRECT — always read from the host global:
|
|
const host = window.__TEABLE_HOST__;
|
|
const { useFields } = host.SDK;
|
|
const { Button } = host.UILib;
|
|
const { definePlugin } = host.PluginSDK;
|
|
const React = host.React;
|
|
```
|
|
|
|
The webpack `externals` config in the scaffold template maps `@teable/sdk` →
|
|
`window.__TEABLE_HOST__.SDK` at build time. This means the bundled code replaces
|
|
the import with `window.__TEABLE_HOST__.SDK`, but destructuring from the import
|
|
statement doesn't survive the webpack transformation — use direct `host.*`
|
|
access instead. The only exception is `@teable/plugin-sdk` which re-exports
|
|
from `window.__TEABLE_HOST__.PluginSDK` at the level webpack expects.
|
|
|
|
**Symptoms**: `useFields is not a function`, `Cannot read properties of undefined`,
|
|
or components silently rendering nothing.
|
|
|
|
## Pitfall 2: `window.__TEABLE_HOST__` is undefined
|
|
|
|
The host object is injected by `HostInitializer` which mounts inside
|
|
`<AppProvider>`. This happens after the initial React tree render. If you try
|
|
to access `window.__TEABLE_HOST__` in a script that executes before the host
|
|
tree mounts, it will be `undefined`.
|
|
|
|
**Fix**: The `PluginLoader` component ensures the host is ready before loading
|
|
plugin scripts, so plugins loaded through the normal flow don't hit this. But
|
|
if you're testing a plugin directly (e.g. loading main.js in a browser console),
|
|
you'll see `__TEABLE_HOST__` is null until the app renders.
|
|
|
|
## Pitfall 3: Upload returns 403 — not a developer
|
|
|
|
Only users in the `plugin_developer` DB table can upload bundles. The
|
|
`DeveloperGuard` checks this. If you get 403 on `upload-bundle`, your user is
|
|
not whitelisted.
|
|
|
|
**Fix**: Add the user to `plugin_developer`:
|
|
|
|
```sql
|
|
INSERT INTO plugin_developer (id, user_id, email, created_by)
|
|
VALUES (gen_random_uuid(), '<userId>', '<email>', '<adminUserId>');
|
|
```
|
|
|
|
## Pitfall 4: Enable (plugin-config) must be called before install
|
|
|
|
Installing a plugin to a position (contextMenu / panel / view / dashboard)
|
|
requires the plugin to be enabled first. If you skip enable and call install
|
|
directly, you'll get 403.
|
|
|
|
**The correct sequence is always**:
|
|
1. Upload bundle → get pluginId
|
|
2. Enable via `PUT /base/:baseId/plugin-config/:pluginId`
|
|
3. Install to position
|
|
|
|
## Pitfall 5: `getSelectionRecords` returns empty array, not throw
|
|
|
|
The old iframe/ZIP `bridge.getSelectionRecords()` threw when nothing was
|
|
selected. The dynamic system's `PluginSDK.getSelectionRecords()` returns an
|
|
empty array instead. You still need to handle the empty case in your UI,
|
|
but you don't need a try/catch wrapper.
|
|
|
|
```typescript
|
|
const { getSelectionRecords } = host.PluginSDK;
|
|
const res = await getSelectionRecords(tableId, viewId, selection);
|
|
if (res.records.length === 0) {
|
|
// Show "no records selected" message — no try/catch needed
|
|
}
|
|
```
|
|
|
|
## Pitfall 6: Plugin component renders nothing / context menu empty
|
|
|
|
If your plugin uploads, enables, and installs successfully but the component
|
|
never appears:
|
|
|
|
1. **Check `plugin.json` `positions`** — it must include the position you're
|
|
installing to (e.g. `"positions": ["contextMenu"]`)
|
|
2. **Check `onLoad` registration** — `ctx.addContextMenuAction('main', Component)`
|
|
must be called inside `onLoad`
|
|
3. **Check Authority Matrix** — the user's role must have the plugin enabled
|
|
in AM. For the test account, the owner bypasses AM, but for other users,
|
|
an admin must enable the plugin per role in the RoleEditor UI.
|
|
4. **Check browser console** — errors in `onLoad` are silently caught; add a
|
|
`console.log` at the top of `onLoad` to verify it runs.
|
|
|
|
## Pitfall 7: Component uses JSX but webpack doesn't transpile it
|
|
|
|
The scaffold template uses `ts-loader` with `jsx: "react-jsx"` in tsconfig.
|
|
If you're setting up the webpack config manually, make sure:
|
|
- `tsconfig.json` has `"jsx": "react-jsx"` under `compilerOptions`
|
|
- The plugin does NOT need `import React from 'react'` at the top when using
|
|
`react-jsx` transform — JSX is auto-transpiled. But you still need
|
|
`const React = host.React` for any explicit `React.createElement` calls.
|