问题描述
用官方在线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-node
、ts-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';
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 } }
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}) } }
|
- 在其它工具代码中导入,并使用 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() 去做静态类型检查。
闲聊
好久没更新博客,都忘了怎么发布文章,有闲暇时候还是多写写保持思考与输出。