feat: 收录 inkreach-teable-plugin skill;install.sh 安装全部技能
skills/ 现包含 inkreach-teable + inkreach-teable-plugin, npx skills add 一并安装两个;install.sh 改为遍历安装 skills/*。
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user