Coze同插件不同工具之间代码复用

Posted on 2026-03-03 17:06  笔名钟意  阅读(0)  评论(0)    收藏  举报

问题描述

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

闲聊

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