问题描述

用官方在线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() 去做静态类型检查。

闲聊

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


本站由 钟意 使用 Stellar 1.28.1 主题创建。
又拍云 提供CDN加速/云存储服务
vercel 提供托管服务
湘ICP备2023019799号-1
总访问 次 | 本页访问