问题描述
用官方在线IDE的Node环境开发Coze插件的工具时,如果import复用其它模块定义好的函数、类、类型等,会出现类似如下报错:
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-node、ts-node-dev等。但是我们不能控制命令行。 - 方案三:用
const {xxx} = require('../common/common')这种方式导入模块。但是这样会导致IDE没有注释提示且无法提示具体属性(导入的类型是一个any),无法自动补全。
经过一番挣扎,方案三是最佳可行方案,起码能解决基本的模块导入问题。最后我们要解决的是IDE的注释提示和自动补全问题,也就是编译时类型推断问题。
最终方案
虽然丑陋,但是好用。
- 举例定义一个通用请求工具
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能自动补全提示。
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() 去做静态类型检查。
闲聊
好久没更新博客,都忘了怎么发布文章,有闲暇时候还是多写写保持思考与输出。
浙公网安备 33010602011771号