惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Stack Overflow Blog
Stack Overflow Blog
量子位
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
美团技术团队
小众软件
小众软件
aimingoo的专栏
aimingoo的专栏
Recent Announcements
Recent Announcements
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Microsoft Security Blog
Microsoft Security Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
酷 壳 – CoolShell
酷 壳 – CoolShell
J
Java Code Geeks
V
V2EX
大猫的无限游戏
大猫的无限游戏
D
DataBreaches.Net
博客园 - Franky
爱范儿
爱范儿
T
Tailwind CSS Blog
A
About on SuperTechFans
Google DeepMind News
Google DeepMind News
博客园_首页
B
Blog RSS Feed
博客园 - 司徒正美
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知

钟意博客

国家统计局数据查询 MCP 照见·硅·肆 涌现之前 照见·硅·叁 换行重启 照见·硅·贰 钢筋水泥 照见·硅·壹 你好世界 照见·硅·零 序言 大语言模型的不确定性 科艺知识库 ARM64 胶东半岛观察 海光 K100 DCU VLLM 推理环境构建 博客多平台负载均衡方案 浅谈RAG Web技术构建桌面应用方案 Spring WebSocket 错误 SpringBoot WebSocket 代理模式、客户端模式 Spring AOP 调用自身失效 window 端口占用但是查不到 Pachelbel's Greatest Hit: The Ultimate Canon - 纪念帕海贝尔:终极卡农 圣诞快乐,劳伦斯先生 Merry Christmas Mr. Lawrence
Coze同插件不同工具之间代码复用
钟意 · 2024-12-21 · via 钟意博客

问题描述

用官方在线IDE的Node环境开发Coze插件的工具时,如果import复用其它模块定义好的函数、类、类型等,会出现类似如下报错:

1
2
3
Error: Cannot find module 'xxx'

ESLint couldn't find an eslint.config.(js|mjs|cjs) file.

问题已解决,急的话直接点击跳转到 最终方案 部分。

最终效果

最终效果

分析原因

毫无疑问,我们编写的IDE文件是一个ts文件,而Coze插件运行时是Node环境,Node环境运行时模块加载机制不能直接加载ts文件,因此需要先编译成js文件才能运行。

而编译过程中,如果遇到import语句,就会去查找对应的模块文件,但由于Node环境无法直接运行ts文件,因此会报错。

尝试解决

我大致思考尝试了如下方案

  • 方案一:修改配置,但是我们无法修改IDE的配置。
  • 方案二:用额外的包去支持ts文件,比如ts-nodets-node-dev等。但是我们不能控制命令行。
  • 方案三:用 const {xxx} = require('../common/common') 这种方式导入模块。但是这样会导致IDE没有注释提示且无法提示具体属性(导入的类型是一个any),无法自动补全。

经过一番挣扎,方案三是最佳可行方案,起码能解决基本的模块导入问题。最后我们要解决的是IDE的注释提示和自动补全问题,也就是编译时类型推断问题。

最终方案

虽然丑陋,但是好用。

  • 举例定义一个通用请求工具
通用工具
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import { Args, Logger } from '@/runtime';
import axios, { AxiosInstance } from 'axios';

// 省略handle函数

// 基础响应类型
export type HttpRes<T> = {
code: number
message: string
result: boolean
data: T[]
}

// 定义通用请求工具
export abstract class BaseApi<T> {
private logger: Logger;
info(...args: any[]) {
this.logger.info(...args);
}
host: string = 'https://example.com';
constructor(baseUrl: string, logger: Logger){
this.api = axios.create({
baseURL: this.host + baseUrl,
headers: {
"Content-Type": "application/json"
}
});
this.logger = logger;
}
async get(url: string, params: Object): Promise<HttpRes<T> | null> {
const res = (await this.api.get(url, {params}))?.data
this.info(url, res, params, {count: res?.data?.length || null})
return res
}
// async post()
// ...
}

export type TOrder = {}
export class OrderApi extends BaseApi<TOrder> {
constructor(logger: Logger) {
super("/order", logger);
}
async getOrders(userId: string): Promise<HttpRes<TOrder> | null> {
return await this.get('/list', {userId})
}
}
// ... 省略其它API

  • 在其它工具代码中导入,并使用 typeof import() 去获取类型信息,这样IDE能自动补全提示。
需求工具导入
1
2
3
4
5
6
7
8
9
10
11
import { Args, Logger } from '@/runtime';
const { OrderApi }: { OrderApi: typeof import("../common/common").OrderApi } = module.require("../common/common");

export async function handler({ input, logger }: Args<Input>): Promise<Output> {
const userId = input.userId
const api = new OrderApi(logger)
const orders = await api.get('/order', {userId})
return {
data: orders
};
};

总结

typeof import 是 TypeScript 提供的静态类型推断工具,它在 编译阶段 就能捕捉模块的导出结构,而无需等到运行时去加载实际模块。
这一特性让我们能够应付 Coze 插件运行时环境中无法使用 import 的限制,在编译时获取类型信息,而不必依赖模块是否能被实际解析。

至于为何 require() 能支持动态导入,是因为做了一些拦截并转译工作,使得 require() 运行能支持动态导入。

总之,在Coze的IDE的Node环境中,使用运行时依赖得靠 require(),而在编译时得到依赖类型得靠 typeof import() 去做静态类型检查。

闲聊

好久没更新博客,都忘了怎么发布文章,有闲暇时候还是多写写保持思考与输出。