# 用 Python、JavaScript 或 Go 转换 Markdown 复制 [external-lib 项目](../assets/examples/external-lib/) 后,就能检查源码并运行 fake 测试。无需安装 Python/Node/Go 或下载依赖。 项目用现成 Markdown 库转换内容,App 只调用 Port。生成的 HTML 没有做安全清洗;展示不可信输入前,需要按产品要求清洗。外部语言只承接需要复用的能力,不转移普通业务逻辑。 先读 `module/document/markup/app.dever`、`port.dever`、`adapter.dever` 和两个测试。Port 的成功输出必须带名字 html,失败合同来自公开 App 的 RenderResult;同一份 Dever 声明供三种实现复用。 默认选择 pip。切换生态时只替换 `adapter.dever` 中的 external 声明、同一文件的 lib 值及 `config/setting.json` 的根 lib 数组;不要同时复制三个 Adapter 到同一角色,也不要改 Port/App/Test: | 生态 | external 声明 | lib 声明 | setting.json 根 lib 中的值 | 已附实现 | | --- | --- | --- | --- | --- | | Python | `external pip "python/render.py"` | `lib "markdown@3.8.2"` | `pip:markdown@3.8.2` | `module/document/markup/python/render.py` | | JavaScript | `external npm "javascript/render.mjs"` | `lib "marked@15.0.12"` | `npm:marked@15.0.12` | `module/document/markup/javascript/render.mjs` | | Go | `external go "go/render.go"` | `lib "github.com/yuin/goldmark@v1.7.8"` | `go:github.com/yuin/goldmark@v1.7.8` | `module/document/markup/go/render.go` | 项目默认 Python 的完整声明如下,JS/Go 按表替换: 源码:[外部 Adapter](../assets/examples/external-lib/module/document/markup/adapter.dever) ```dever external pip "python/render.py" { lib "markdown@3.8.2" } ``` 版本是示例固定输入,不表示“最新版本”;显式依赖准备仍须验证来源、许可证和产品需求。库的 Markdown 方言与尾部换行不同,切换生态不保证输出逐字节等价,真实输出验收需针对所选库进行。普通 fake 用例只验证 App/Port 合同。 ## 外部函数怎么写 Python 导出 `async def render(payload, setting)`;JS 导出 `async function render(payload, setting, signal)`;Go 导出 `func Render(context.Context, any, any) (any, error)`,使用 `package main`,但不手写 main。操作名 `render` 在 Go 映射为 `Render`,蛇形名按段转 PascalCase。 编译器从 Port 生成 `contract.json`,写入参数、返回值、业务错误和接口标识。运行器自动找到同名处理函数,不需要项目复制 SDK、维护注册表、计算 hash 或编写 stdin 协议。 Python 使用 `Worker.from_manifest`,JS 使用 `Worker.fromManifest`,Go 由生成的入口调用 `ServeManifest`。SDK 来自匹配版本的运行资源。 三种实现共用的是 Port 的输入、输出、错误和操作合同。schema 身份还包含所选生态与 Lib 声明,因此切换生态或依赖后会变化,不能复用旧 manifest 或要求三种 hash 相等。 本例输入是 `{"source":"# Report"}`,成功返回 `{"html":"..."}`。即使只有一个输出,也不能返回裸字符串。 没有 setting 声明时,setting 为 null/nil。需要配置再增加 Adapter setting 类型和对应配置绑定,不能读取环境变量。 业务拒绝使用 SDK 的 `BusinessError`:本例的 identity 是 `document.markup.RenderResult.Rejected`,payload 是 `{"message":"..."}`。未知异常属于运行失败,不自动变成业务拒绝,也不返回假成功。 stdout 专用于协议,日志写 stderr。Python 处理函数要响应取消,JS 使用 signal,Go 使用 context。需要网络、文件、子进程或 GPU 时,通过 `allow` 声明效果;实际权限继承容器用户,由部署侧管理,不再使用 Dever 内层沙箱或 Adapter 挂载授权。真实文件路径通过业务 setting 传入。只运行可信 Worker,本例纯文档转换不需要这些能力。 ## 准备与测试 ```sh dever check dever test --filter document.markup --timeout-ms 5000 ``` `rendered` 与 `rejected` 各自替换 `port.render`,分别检查成功和业务拒绝。fake 只对当前用例有效,不会影响其他测试。编译器检查参数、返回值、允许的错误和调用能力。普通测试不加载真实库、不启动 Worker,也不要求依赖锁或生产配置。 需要真实运行时,按用户授权准备依赖: ```sh dever lib add pip:markdown@3.8.2 dever run -- document.markup.render '{"source":"# Report"}' ``` JS/Go 改用表中的依赖声明。已有锁迁移机器时使用 `dever lib install `。run/build 离线;缺资源就报错,不借用宿主解释器。切换依赖使用 Lib 命令并核对锁,不手工伪造 dever.lock。 静态示例与 fake 检查不证明第三方依赖已下载、真实库输出已验收或跨架构 Worker 已可运行。