You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将工厂函数形式CommonJS模块迁移为非Class实现的ESM

问题背景

我用NodeJS编写JavaScript已有数年时间,目前仍在持续学习进阶。
在编写CommonJS模块时,我习惯定义一个根函数,在根函数内部编写所有子函数,最终通过return {subfunction1, subfunction2}的形式返回需要对外暴露的方法。坦白说这种编写风格是我从Jonathan Mills的教程中学到的,一直以来使用体验都很好。
现在我遇到的问题是,不知道如何正确将这类CommonJS模块迁移为ESM规范,希望可以不使用Class语法完成迁移;当然如果Class确实是ESM场景下的标准实现方式,我也可以适配调整。

原CommonJS实现示例

service.js

function WebexService(webex) {
  async function processMessage(messageData) {
    try {
      const user = await webex.people.get(messageData.personId);
      //debug(user);
      sendMessage({ displayName: user.displayName, roomId: messageData.roomId });
    } catch (error) {
      debug(error);
      throw error;
    }
  }
  function sendMessage(messageInfo) {
    webex.messages.create({
      roomId: messageInfo.roomId,
      text: `Howdy! ${messageInfo.displayName}`,
    });
  }
  return { processMessage }
}

module.exports = WebexService()

CommonJS下的调用方式(app.js)

const { processMessage } = require('../services/webexService');

function superCool() {
  const messageResponse = await processMessage(messageData);
}

目前已实现的Class版ESM适配

ESM版service.js

import debugInit from 'debug';
import chalk from 'chalk';
const debug = debugInit('app:services:webex');

export default class WebexService {
  constructor(webex) {
    this.webex = webex;
  }
  async processMessage(messageData) {
    try {
      const user = await this.webex.people.get(messageData.personId);
      //debug(user);
      this.sendMessage({ displayName: user.displayName, roomId: messageData.roomId });
    } catch (error) {
      debug(error);
      throw error;
    }
  }
  sendMessage(messageInfo) {
    this.webex.messages.create({
      roomId: messageInfo.roomId,
      text: `Howdy! ${messageInfo.displayName}`,
    });
  }
}

ESM下的Class版调用(app.js)

import WebexService from '../services/webex.js';

const WebexServiceInstance = new WebexService(webex);
WebexServiceInstance.processMessage(event.data);
无Class语法的ESM迁移方案

ESM完全不强制要求使用Class实现模块,你原来的闭包工厂写法可以几乎零修改迁移,只需要替换CommonJS的导出语法为ESM导出语法即可:

  • 你原来的根函数本质是工厂函数,通过闭包持有内部依赖(比如webex实例)和私有方法(比如未对外暴露的sendMessage),这种模式在ESM规范下完全合法,没有任何兼容性问题。
  • 可以根据你的初始化场景选择导出单例或者导出工厂函数,适配不同的依赖传入需求。

无Class的ESM实现示例

service.js

import debugInit from 'debug';
const debug = debugInit('app:services:webex');

// 保留你原来的工厂函数写法,不需要做逻辑修改
function createWebexService(webex) {
  async function processMessage(messageData) {
    try {
      const user = await webex.people.get(messageData.personId);
      sendMessage({ displayName: user.displayName, roomId: messageData.roomId });
    } catch (error) {
      debug(error);
      throw error;
    }
  }
  function sendMessage(messageInfo) {
    webex.messages.create({
      roomId: messageInfo.roomId,
      text: `Howdy! ${messageInfo.displayName}`,
    });
  }
  // 对外暴露方法的逻辑和你之前的写法完全一致
  return { processMessage }
}

// 场景1:如果webex在当前模块内可以直接初始化,直接导出单例,调用习惯和CommonJS完全一致
// const webex = /* 你的webex初始化逻辑 */
// export const { processMessage } = createWebexService(webex);

// 场景2:如果需要在调用方传入webex做初始化,直接导出工厂函数
export default createWebexService;

对应调用方式(app.js)

// 对应场景1:单例导出,直接解构方法即可使用
// import { processMessage } from '../services/webex.js';
// 直接调用 processMessage(messageData) 即可,和你之前的CommonJS调用体验完全一致

// 对应场景2:导出工厂函数,先初始化实例再调用方法
import createWebexService from '../services/webex.js';
const { processMessage } = createWebexService(webex);

async function superCool() {
  const messageResponse = await processMessage(messageData);
}
补充说明
  • ESM没有规定必须使用Class语法,闭包工厂、纯函数导出、普通对象导出都是完全合规的写法,直接选择你用着顺手的模式即可。
  • 你原来的闭包写法通过作用域实现私有状态和私有方法,相比Class的this绑定,还能避免this指向丢失的问题,稳定性更好。
  • 如果你的服务不需要多实例,优先选择模块内初始化后直接导出方法的写法,调用成本最低。

内容的提问来源于stack exchange,提问作者Jeremy M

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 07:45:29