You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: document CJS/ESM interop issue and solution in AGENTS.md
All four import strategies fail in OpenCode's Bun runtime for CJS
packages that use Object.defineProperty getters. The only reliable
fix is to bundle CJS dependencies with tsup by moving them to
devDependencies.
Signed-off-by: xuezhaojun <xuezhaokeepgoing@gmail.com>
Copy file name to clipboardExpand all lines: AGENTS.md
+69Lines changed: 69 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -303,6 +303,74 @@ OIDC Trusted Publishing requires the package to already exist on npm:
303
303
304
304
All plugins are published under the `@kubeopencode` npm scope (e.g. `@kubeopencode/opencode-slack-plugin`). The `package.json``name` field must use this scoped format.
305
305
306
+
## CJS/ESM Interop (Critical)
307
+
308
+
OpenCode plugins run inside **Bun's JavaScript runtime**, not Node.js. Bun's CJS/ESM interop differs from Node.js in ways that cause silent failures for CJS packages that use `Object.defineProperty` (getter/setter) for their exports.
309
+
310
+
### The Problem
311
+
312
+
Packages like `@slack/web-api` and `@slack/socket-mode` are **CJS modules** that export classes via `Object.defineProperty`:
In **Node.js**, `import { WebClient } from "@slack/web-api"` works because Node creates a live binding to the getter. But **Bun** (which OpenCode uses internally to load plugins via `await import()`) creates a namespace object that **drops getter-defined properties**. This means:
320
+
321
+
| Import style | Node.js | Bun (OpenCode runtime) |
undefined is not a constructor (evaluating 'new WebClient(botToken)')
332
+
```
333
+
334
+
### The Solution: Bundle CJS Dependencies
335
+
336
+
**Always bundle CJS dependencies into the plugin output.** This eliminates runtime CJS/ESM interop entirely — tsup's bundler resolves all exports at build time using `__commonJS` wrappers that correctly handle `Object.defineProperty`.
337
+
338
+
**Configuration:**
339
+
340
+
1. Move CJS dependencies from `dependencies` to `devDependencies` in `package.json`:
341
+
342
+
```json
343
+
{
344
+
"dependencies": {},
345
+
"devDependencies": {
346
+
"@slack/web-api": "^7.13.0",
347
+
"@slack/socket-mode": "^2.0.5"
348
+
}
349
+
}
350
+
```
351
+
352
+
2. tsup automatically bundles `devDependencies` — no extra config needed. Dependencies listed in `dependencies` are treated as external (left as `import` statements in the output).
**Trade-off:** The bundle grows from ~17KB to ~830KB when including `@slack/web-api`. This is acceptable for a plugin that runs once per Agent pod.
362
+
363
+
### Detection Checklist
364
+
365
+
If a plugin dependency uses CJS (`"type"` is not `"module"` or absent in `package.json`, `main` points to a `.js` file without ESM exports), and uses `Object.defineProperty` for exports, it **must** be bundled. Check with:
366
+
367
+
```bash
368
+
# Does the package use Object.defineProperty for exports?
0 commit comments