2026년 9월 23일
source map은 왜 필요할까?
//main.js
import { pay } from './payment.js';
pay(0);//payment.js
export function pay(amount) {
if (amount <= 0) {
throw new Error('결제 금액은 0보다 커야 합니다.');
}
console.log(`${amount}원 결제 완료`);
}이 코드들을
"build": "rolldown src/main.js --file dist/bundle.js --format esm --sourcemap --minify",rolldown으로 빌드를 한 뒤에 실행시키면 어디서 에러가 발생할까?
node dist/bundle.js로 실행시켜 봤다. 에러는 당연하게도 bundle.js에서 발생한다.
file:///Users/seunghunlee/Desktop/workspace/study/bundler-basics/examples/source-map/dist/bundle.js:1
function e(e){if(e<=0)throw Error(`결제 금액은 0보다 커야 합니다.`);console.log(`${e}원 결제 완료`)}e(0);
^
Error: 결제 금액은 0보다 커야 합니다.
at e (file:///Users/seunghunlee/Desktop/workspace/study/bundler-basics/examples/source-map/dist/bundle.js:1:29)
at file:///Users/seunghunlee/Desktop/workspace/study/bundler-basics/examples/source-map/dist/bundle.js:1:84
at ModuleJob.run (node:internal/modules/esm/module_job:413:25)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:660:26)
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)
Node.js v24.12.0function e(e){if(e<=0)throw Error(`결제 금액은 0보다 커야 합니다.`);console.log(`${e}원 결제 완료`)}e(0);
//# sourceMappingURL=bundle.js.map이게 번들링된 결과물인데 현재는 간단한 코드라서 볼 수 있지만, 나중에 코드 양이 많아지면 현실적으로 파악이 매우 어려워진다.
이번에는 node --enable-source-maps dist/bundle.js 명령어로 실행시켜 보자.
% node --enable-source-maps dist/bundle.js
/Users/seunghunlee/Desktop/workspace/study/bundler-basics/examples/source-map/src/payment.js:3
throw new Error('결제 금액은 0보다 커야 합니다.');
^
Error: 결제 금액은 0보다 커야 합니다.
at e (/Users/seunghunlee/Desktop/workspace/study/bundler-basics/examples/source-map/src/payment.js:3:15)
at <anonymous> (/Users/seunghunlee/Desktop/workspace/study/bundler-basics/examples/source-map/src/main.js:3:1)
at ModuleJob.run (node:internal/modules/esm/module_job:413:25)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:660:26)
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)
Node.js v24.12.0아까와는 다르게 payment.js:3로 원본 코드의 위치가 잘 나온다.
어떻게 번들링된 결과물을 실행했는데, 원본 코드의 위치가 나올까?
정답은 source map이다.
번들링된 결과물을 보면 밑에 주석으로
//# sourceMappingURL=bundle.js.map가 적혀있는 모습인데, 이 주석이 소스맵을 찾을 수 있게 도와준다.
참고로 저 소스맵의 주소가 잘못되어 있거나 소스맵이 존재하지 않는다면, 똑같이 번들링 결과물에서의 에러 위치를 알려준다.
source map은 어떻게 생겼을까?
{
"version": 3,
"file": "bundle.js",
"names": [],
"sources": [
"../src/payment.js",
"../src/main.js"
],
"sourcesContent": [
"export function pay(amount) {\n if (amount <= 0) {\n throw new Error('결제 금액은 0보다 커야 합니다.');\n }\n\n console.log(`${amount}원 결제 완료`);\n}\n",
"import { pay } from \"./payment.js\";\n\npay(0);\n"
],
"mappings": "AAAA,SAAgB,EAAI,EAAQ,CAC1B,GAAI,GAAU,EACZ,MAAU,MAAM,oBAAoB,EAGtC,QAAQ,IAAI,GAAG,EAAO,QAAQ,CAChC,CCJA,EAAI,CAAC"
}위 예시의 코드를 번들링하고 난 뒤의 source map이다.
sources에 원본 소스 코드의 위치가 있고, sourcesContent에 그 코드의 내용이 있다.
source map에서 좌표를 통해 원본 코드 찾는 방법
import { readFileSync } from 'node:fs';
import { SourceMap } from 'node:module';
const rawMap = readFileSync('dist/bundle.js.map', 'utf8');
const sourceMap = new SourceMap(JSON.parse(rawMap));
// 에러 스택에 표시된 bundle.js:1:29를 조회한다. 행과 열은 1부터 센다.
console.log(sourceMap.findOrigin(1, 29));이 코드를 실행시키면
{
name: undefined,
fileName: '../src/payment.js',
lineNumber: 3,
columnNumber: 15
}이런 결과가 나온다. 하나씩 해석을 해보면
번들링된 결과물에서 1행 29열을 찾고, 그에 대응하는 원본 코드의 위치를 찾는 코드이다.
결과를 보면 payment.js의 3행 15열이 정확하게 나온다.
예전 source map을 사용하면 어떻게 될까?
그렇다면 코드를 수정하고 다시 빌드한 뒤에 예전 source map을 사용하면 어떻게 될까?
우선 기존 source map을 따로 보관해뒀다.
cp dist/bundle.js.map dist/bundle.v1.js.map그리고 payment.js에 로그 한 줄을 추가했다.
export function pay(amount) {
console.log("결제 요청:", amount);
if (amount <= 0) {
throw new Error("결제 금액은 0보다 커야 합니다.");
}
console.log(`${amount}원 결제 완료`);
}main.js의 pay(0)은 그대로 두고 다시 빌드한 뒤 실행시켜 봤다.
pnpm run build
node dist/bundle.js이번에는 에러 위치가 bundle.js:1:53으로 나온다. 로그를 추가하면서 번들링된 결과물에서의 위치도 바뀐 것이다.
이제 findOrigin(1, 53)으로 좌표는 고정하고, 읽어오는 source map만 바꿔가며 조회해보자.
예전 파일인 bundle.v1.js.map으로 조회하면
{
name: undefined,
fileName: '../src/payment.js',
lineNumber: 3,
columnNumber: 39
}새로 만들어진 bundle.js.map으로 조회하면
{
name: undefined,
fileName: '../src/payment.js',
lineNumber: 5,
columnNumber: 15
}이렇게 결과가 달라진다. 현재 payment.js에서 에러가 발생하는 코드는 5행에 있으니, 새 source map으로 조회한 위치가 맞다.
여기서 중요한 건 예전 source map도 결과를 반환한다는 것이다. 못 찾았다고 알려주는 게 아니라, 현재 코드와 맞지 않는 위치를 알려준다.
findOrigin()은 전달받은 좌표를 읽어온 source map 기준으로 조회한다. 그 좌표가 어떤 버전의 번들에서 나온 것인지까지 확인해주지는 않는다.
그래서 source map 파일이 존재한다고 해서 정확한 위치를 찾을 수 있는 것은 아니다. 실행한 번들에 대응하는 source map을 함께 사용해야 한다.