server.js CJS 到 ESM 转换指南

server.js CJS 到 ESM 转换指南

概述

本指南帮助你将 server.js 从 CommonJS (CJS) 转换为 ES Module (ESM) 格式。

转换步骤

1. 顶部静态 import(已完成部分)

文件顶部已经部分转换,确保包含以下 import 语句:

import dotenv from 'dotenv';
import { dirname } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

dotenv.config({ path: __dirname + '/.env', override: true });
process.setMaxListeners(0);

import * as http from 'http';
import * as https from 'https';
import * as fs from 'fs';
import * as path from 'path';
import * as url from 'url';
import * as crypto from 'crypto';

// 加载统一日志工具
import logger from './server/utils/logger.js';

2. 转换所有 require() 调用

#### 2.1 静态 require()(顶层,不在 try-catch 或条件语句中)

转换前:

const moduleName = require('./path/to/module');
const { namedExport } = require('./path/to/module');

转换后:

import moduleName from './path/to/module.js';
import { namedExport } from './path/to/module.js';

注意:

  • 所有本地模块路径必须添加 .js 扩展名
  • 内置模块使用 import as 语法:import as http from 'http';

#### 2.2 动态 require()(在 try-catch 或条件语句中)

转换前:

let moduleName;
try {
  moduleName = require('./path/to/module');
} catch (e) {
  console.warn('模块加载失败:', e.message);
}

转换后:

let moduleName;
(async () => {
  try {
    const imported = await import('./path/to/module.js');
    moduleName = imported.default || imported;
  } catch (e) {
    console.warn('模块加载失败:', e.message);
  }
})();

注意: import() 返回的是 { default: ... } 结构,需要根据实际情况访问。

3. 转换 module.exports

转换前:

module.exports = { config };
module.exports = someVariable;

转换后:

export default { config };
export default someVariable;

命名导出:

// 转换前
module.exports = { foo, bar };

// 转换后
export { foo, bar };

4. 处理 __dirname 和 __filename

ESM 中没有 __dirname__filename,需要手动定义:

import { dirname } from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

5. 处理动态 import() 的特殊情况

#### 5.1 safeRequire 和 safeRequireAsync 函数

这些函数需要完全重构,因为它们内部使用 require()

方案 1:转换为异步函数

async function safeImport(modulePath, label, options = {}) {
  try {
    const mod = await import(modulePath);
    const module = mod.default || mod;
    if (options.initFn) {
      const result = await options.initFn(module);
      if (result && typeof result.then === 'function') return result.then(() => { console.log(`✅ ${label}`); return module; });
    }
    console.log(`✅ ${label}`);
    return module;
  } catch (e) {
    console.log(`❌ ${label}加载失败:`, e.message);
    return null;
  }
}

方案 2:使用顶层并行 import

如果模块不是真正可选的,最好在文件顶部使用静态 import。

6. 处理循环依赖

ESM 的静态分析可能导致循环依赖问题。如果遇到问题:

  • 将循环依赖的模块改为动态 import()
  • 或者重构代码消除循环依赖

7. 验证转换结果

  1. 语法检查:
   node --check server.js
   
  1. 运行测试:
   npm test
   
  1. 手动测试主要功能

自动化转换脚本

使用以下脚本可以自动转换大部分内容(但可能需要手动修复):

# 安装转换工具
npm install --save-dev @babel/core @babel/cli jscodeshift

# 使用 jscodeshift 转换(需要 codemod 脚本)
npx jscodeshift --transform convert-cjs-to-esm.js server.js

# 或使用 Babel(需要配置)
npx babel server.js --out-file server.esm.js --presets @babel/preset-env

常见问题

Q1: import 路径错误

A: 确保所有本地模块路径都有 .js 扩展名,例如:

  • ./config-loader./config-loader.js
  • ./server/utils/logger./server/utils/logger.js

Q2: 动态 import() 返回的对象结构

A: import() 返回 { default: ... },访问时需要注意:

const mod = await import('./module.js');
const actualModule = mod.default || mod;

Q3: __dirname 在 ESM 中不可用

A: 使用 import.meta.urlfileURLToPath 来获取等效值(参见第4节)。

手动转换检查清单

  • [ ] 所有 require() 已转换为 import
  • [ ] 所有本地模块路径已添加 .js 扩展名
  • [ ] module.exports 已转换为 export
  • [ ] __dirname__filename 已正确定义
  • [ ] 动态 import() 已在 async 函数中使用
  • [ ] safeRequiresafeRequireAsync 已重构
  • [ ] 没有循环依赖问题
  • [ ] 文件通过语法检查
  • [ ] 所有测试通过

参考资料

  • Node.js ESM 文档: https://nodejs.org/docs/latest-v22.x/api/esm.html
  • MDN import 语句: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
  • CJS 到 ESM 迁移指南: https://flaviocopes.com/es-modules/
← 返回案例列表
分享:
🤖 Try Now →
🤖
🎁