CommonJS에서 네이티브 ES 모듈로 마이그레이션
require/module.exports를 import/export로 변환하고 이중 패키지와 상호 운용성의 문제를 처리합니다.
CommonJS에서 네이티브 ES 모듈로 마이그레이션은(는) CoddyKit의 무료 Node.js Backend Development Bootcamp 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Node.js Backend Development Bootcamp 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Node.js Backend Development Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.
이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.
Two Module Systems, One Runtime
Node.js historically used CommonJS (CJS): you load code with require() and expose it with module.exports. Modern JavaScript has a built-in standard, ES Modules (ESM), using import and export.
- CommonJS loads modules synchronously at runtime.
- ESM is statically analyzed, loaded asynchronously, and is the official ECMAScript standard.
Node now supports both, but mixing them has rules. This lesson walks you through migrating a backend project from CJS to native ESM cleanly.
// CommonJS (old)
const fs = require('fs');
module.exports = { readConfig };
// ES Modules (new)
import fs from 'fs';
export { readConfig };Telling Node You Mean ESM
Node decides how to treat a file based on its extension and the nearest package.json:
"type": "module"inpackage.json→.jsfiles are treated as ESM.- No
typefield (or"commonjs") →.jsfiles are CommonJS. .mjsis always ESM;.cjsis always CommonJS, regardless oftype.
The cleanest migration step is adding "type": "module" once, then fixing the files it breaks.
{
"name": "my-api",
"version": "1.0.0",
"type": "module",
"main": "src/server.js",
"scripts": {
"start": "node src/server.js"
}
}Converting Exports
Replace module.exports and exports.foo with export statements.
module.exports = X(single value) →export default X.exports.foo = ...(multiple named) →export const foo = ...or a groupedexport { foo, bar }.
Prefer named exports for utilities so consumers get autocompletion and clearer imports.
// Before (CJS)
// module.exports.add = (a, b) => a + b;
// module.exports.PI = 3.14159;
// After (ESM)
export const add = (a, b) => a + b;
export const PI = 3.14159;
console.log(add(2, 3)); // 5
console.log(PI); // 3.14159Converting Imports
Replace require() with import. Match the export style:
- Named:
const { add } = require('./math')→import { add } from './math.js'. - Default:
const express = require('express')→import express from 'express'.
Critical rule: in ESM, relative imports of your own files must include the file extension (.js). Node will not guess it for you like CommonJS did.
// Before (CJS)
// const { add } = require('./math');
// After (ESM) — note the explicit .js extension
import { add } from './math.js';
console.log(add(10, 5)); // 15No More __dirname or __filename
ESM does not provide the CommonJS globals __dirname and __filename. If your backend builds file paths (config, uploads, templates), you must recreate them from import.meta.url.
Modern Node (20.11+) also exposes import.meta.dirname and import.meta.filename directly, which is the simplest option going forward.
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
// Classic portable approach
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const configPath = join(__dirname, 'config.json');
console.log(configPath);
// Node 20.11+ shortcut:
// const dir = import.meta.dirname;Importing JSON and Built-ins
Two more changes that bite backend projects:
- JSON:
require('./data.json')no longer works directly. Use an import attribute:import data from './data.json' with { type: 'json' }. - Core modules: prefer the
node:prefix (e.g.import { readFile } from 'node:fs/promises'). It is unambiguous and future-proof.
If you target older Node, reading JSON via fs avoids attribute-syntax compatibility concerns entirely.
import { readFile } from 'node:fs/promises';
// Robust, version-agnostic way to load JSON in ESM
const raw = await readFile(new URL('./pkg.json', import.meta.url));
const pkg = JSON.parse(raw);
console.log(pkg.name);Top-Level await Is a Superpower
One genuine upgrade ESM gives backend code: top-level await. In CommonJS you had to wrap async startup in an IIFE. In an ESM module you can await directly at the top level.
This makes database connections, config fetching, and warm-up logic much cleaner in server entry files.
// ESM entry file
async function connectDb() {
await new Promise((r) => setTimeout(r, 50));
return { status: 'connected' };
}
const db = await connectDb(); // top-level await — no IIFE needed
console.log('DB:', db.status);Interop: ESM Importing CommonJS
You will still depend on CJS-only npm packages. Good news: ESM can import CommonJS. Node wraps the package's module.exports as the default export.
- Use
import pkg from 'cjs-lib'to getmodule.exports. - Node tries to detect named exports too, but for complex CJS this can fail — destructure from the default instead.
// 'lodash' is CommonJS; the whole export object is the default
import _ from 'lodash';
const { chunk } = _; // safe: destructure from default
console.log(chunk([1, 2, 3, 4], 2)); // [[1,2],[3,4]]Interop: CommonJS Loading ESM
The reverse is harder. A CommonJS file cannot require() an ESM module synchronously (older Node throws ERR_REQUIRE_ESM). Options:
- Use a dynamic
import(), which returns a Promise — works from inside async functions in CJS. - Node 22+ added experimental synchronous
require()of ESM, but don't rely on it for portable code.
This asymmetry is the main reason teams migrate the whole codebase to ESM rather than mixing.
// Inside a CommonJS file:
async function run() {
// dynamic import works even from CJS
const { add } = await import('./math.mjs');
console.log(add(4, 6)); // 10
}
run();Dual-Package Publishing
If you publish a library, some users are ESM and some are CJS. The modern solution is the exports field with conditional exports, shipping both builds.
importcondition → the ESM entry.requirecondition → the CJS entry.
Beware the dual-package hazard: if both builds get loaded, you get two copies of your module state (e.g. two separate singletons). Keep stateful logic in a single internal module both builds import.
{
"name": "my-lib",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
}
}
}A Pragmatic Migration Checklist
A reliable order of operations for an existing backend:
- Add
"type": "module"topackage.json. - Rename any file that must stay CJS to
.cjs. - Convert
require/module.exportstoimport/export. - Add
.jsextensions to all relative imports. - Replace
__dirname/__filenamewithimport.metahelpers. - Fix JSON imports and CJS-default interop.
- Run the test suite; let failures point to remaining
requirecalls.
Tools like cjstoesm or codemods can automate the bulk edits, but always review the diff.
Quick Check
You convert a backend file to ESM and add "type": "module". Suddenly an import of your own helper throws ERR_MODULE_NOT_FOUND. What is the most likely fix?
Recap
You migrated a Node.js backend from CommonJS to native ES Modules. Key takeaways:
- Opt in with
"type": "module"; use.cjs/.mjsto override per file. - Swap
require/module.exportsforimport/export, and always include the.jsextension on relative imports. - Recreate
__dirnameviaimport.meta.url(or useimport.meta.dirname). - ESM can import CJS (as default export); CJS must use dynamic
import()for ESM. - Top-level
awaitsimplifies async startup. - For libraries, use conditional
exportsand beware the dual-package hazard.
With these rules, you can confidently modernize any Node service to standard ES Modules.
자주 묻는 질문
“CommonJS에서 네이티브 ES 모듈로 마이그레이션” 강의는 무료인가요?
네 — “CommonJS에서 네이티브 ES 모듈로 마이그레이션” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Node.js Backend Development Bootcamp 강의 전체를 잠금 해제할 수 있습니다. Node.js Backend Development Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.
“CommonJS에서 네이티브 ES 모듈로 마이그레이션”에서 뭘 배우나요?
require/module.exports를 import/export로 변환하고 이중 패키지와 상호 운용성의 문제를 처리합니다. 브라우저에서 직접 실행하는 실습 코드로 Node.js Backend Development Bootcamp을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Node.js Backend Development Bootcamp을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Node.js Backend Development Bootcamp은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.
“CommonJS에서 네이티브 ES 모듈로 마이그레이션” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Node.js Backend Development Bootcamp 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Node.js Backend Development Bootcamp 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- CommonJS에서 네이티브 ES 모듈로 마이그레이션
- Node 백엔드 프로젝트를 위한 tsconfig 구성
- 타입 안전 환경 구성 및 런타임 검증
- tsx, 핫 리로드 및 소스 맵으로 빠르게 반복하기