2026년 9월 24일

어떠한 이유로 번들링된 결과물에 특정 코드를 추가해야 한다고 해보자. 이런 상황에서 소스맵은 어떻게 갱신되어야 할까?
번들 앞에 코드 추가하기
간단하게 예시를 들어보자.
예제 코드
src/main.js
import { pay } from "./payment.js";
pay(0);src/payment.js
export function pay(amount) {
console.log("결제 요청:", amount);
if (amount <= 0) {
throw new Error("결제 금액은 0보다 커야 합니다.");
}
console.log(`${amount}원 결제 완료`);
}플러그인으로 로그 추가하기
renderChunk에서 받은 번들 코드 앞에 로그를 추가하고, 우선 변경된 코드만 반환해보자.
prepend.config.mjs
import MagicString from "magic-string";
export default {
input: "src/main.js",
output: {
file: "dist/prepend/bundle.js",
format: "esm",
sourcemap: true,
minify: false,
},
plugins: [
{
name: "prepend-log",
renderChunk(code) {
const edited = new MagicString(code);
edited.prepend("console.log('추가한 코드');\n");
return {
code: edited.toString(),
};
},
},
],
};이렇게 한 뒤에 pnpm exec rolldown --config prepend.config.mjs를 실행시키면 이런 결과물이 나온다.
dist/prepend/bundle.js
console.log('추가한 코드');
//#region src/payment.js
function pay(amount) {
console.log("결제 요청:", amount);
if (amount <= 0) throw new Error("결제 금액은 0보다 커야 합니다.");
console.log(`${amount}원 결제 완료`);
}
//#endregion
//#region src/main.js
pay(0);
//#endregion
//# sourceMappingURL=bundle.js.mapdist/prepend/bundle.js.map
{
"version": 3,
"file": "bundle.js",
"names": [],
"sources": [
"../../src/payment.js",
"../../src/main.js"
],
"sourcesContent": [
"export function pay(amount) {\n console.log(\"결제 요청:\", amount);\n\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,IAAI,QAAQ;CAC1B,QAAQ,IAAI,UAAU,MAAM;CAE5B,IAAI,UAAU,GACZ,MAAM,IAAI,MAAM,oBAAoB;CAGtC,QAAQ,IAAI,GAAG,OAAO,QAAQ;AAChC;;;;ACNA,IAAI,CAAC"
}소스맵이 잘못된 위치를 가리킨다
이 상태로 node --enable-source-maps dist/prepend/bundle.js를 실행시키면 다음과 같은 결과가 나온다.
% node --enable-source-maps dist/prepend/bundle.js
추가한 코드
결제 요청: 0
/Users/seunghunlee/Desktop/workspace/study/bundler-basics/examples/source-map/src/payment.js:8
console.log(`${amount}원 결제 완료`);
^
Error: 결제 금액은 0보다 커야 합니다.
at pay (/Users/seunghunlee/Desktop/workspace/study/bundler-basics/examples/source-map/src/payment.js:8:25)
at <anonymous> (/Users/seunghunlee/Desktop/workspace/study/bundler-basics/examples/source-map/src/main.js:3:6)
at ModuleJob.run (node:internal/modules/esm/module_job:371:25)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:683:26)
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)
Node.js v24.7.0에러 위치가 src/payment.js:8로 표시되는데, 실제 코드를 보면 throw는 5행에 있다.
이유는 간단하다. 플러그인에서 변경된 코드만 반환하고, prepend()로 코드의 위치가 바뀌었다는 정보를 반환하지 않았기 때문이다. 그래서 기존 매핑에 이 변화가 반영되지 않았다.
이 경우 Rolldown은 빌드할 때 SOURCEMAP_BROKEN 경고를 내지만, 번들과 소스맵 파일은 생성한다. 그리고 전에 썼던 글 마지막 내용처럼, 이번 Node 실행에서도 소스맵 불일치에 대한 별도 에러 없이 잘못된 위치가 표시된다.
코드 변경에 맞춰 소스맵 갱신하기
이걸 어떻게 해결할 수 있을까?
변경된 코드와 소스맵을 함께 반환하기
prepend.config.mjs
import MagicString from "magic-string";
export default {
input: "src/main.js",
output: {
file: "dist/prepend/bundle.js",
format: "esm",
sourcemap: true,
minify: false,
},
plugins: [
{
name: "prepend-log",
renderChunk(code) {
const edited = new MagicString(code);
edited.prepend("console.log('추가한 코드');\n");
return {
code: edited.toString(),
map: edited.generateMap({ hires: true }),
};
},
},
],
};간단하게 map: edited.generateMap({ hires: true })를 추가하면 된다.
생성한 소스맵은 어떻게 연결될까?
MagicString은 전달받은 번들 코드와 편집 내역을 기록한다. generateMap()은 추가 후 번들의 위치를 추가 전 번들의 위치에 연결하는 소스맵을 만든다. hires: true는 열 위치까지 정밀하게 매핑하도록 하는 옵션이다.
Rolldown은 플러그인이 반환한 이 맵을 기존 소스맵과 합쳐, 추가 후 번들에서 원본 코드의 위치까지 찾을 수 있게 한다. 공식 문서에서도 이 과정을 설명하고 있다.
추가 후 번들 → 추가 전 번들 → 원본 코드다시 실행해 원본 위치 확인하기
다시 빌드한 뒤 실행시켜 보자.
pnpm exec rolldown --config prepend.config.mjs
node --enable-source-maps dist/prepend/bundle.js이번에는 에러 위치가 src/payment.js:5:11로 나온다. 코드만 반환했을 때는 8행을 가리켰지만, 변경에 대한 소스맵을 함께 반환하니 실제 throw가 있는 5행을 가리킨다.