如何将工厂函数形式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
相关产品推荐
相关产品推荐

