소스 맵: 실행 중 보고된 위치를 원본 코드로 어떻게 되돌리는가
실행 중 보고된 생성 코드의 줄과 열을 원본 TypeScript·JSX 위치로 연결하는 소스 맵의 좌표 구조를 이해하고, 정확한 코드와 맵을 짝지어 오류 위치를 복원한다.
목차
표준·구현·측정·해석 표시는 무엇인가요?
- 표준웹 표준이나 언어 명세가 정한 동작
- 구현특정 기술이나 브라우저가 실제로 구현한 동작
- 측정명시한 환경에서 직접 실행해 관찰한 결과
- 해석앞선 근거에서 도출한 설계 판단
- 미확인아직 공식 근거나 재현 결과를 확인하지 못한 내용
30초 요약
이 글은 네 문장으로 시작한다.
- 실행환경은 대개 변환·축소된 파일의 위치를 보고한다.
- 소스 맵은 그 위치를 작성한 파일의 줄과 열에 연결한다.
- 위치를 정확히 되돌리려면 실제로 실행된 코드와 함께 만든 맵이 필요하다.
- 소스 맵은 위치를 복원하지만 오류 당시 상태나 근본 원인까지 설명하지는 않는다.
소스 맵은 두 좌표계를 잇는다
TypeScript와 JSX를 변환하고 여러 파일을 묶거나 축소하면 작성한 코드와 실행할 코드의
모양이 달라진다. 예를 들어 작성한 App.tsx의 12번째 줄이 한 줄로 축소된 app.js의
1번째 줄 18,420번째 열에 놓일 수 있다.
실행 중 보고된 위치 작성한 위치
app.js:1:18420 ───── 소스 맵 ─────> src/App.tsx:12:7여기서 **열(column)**은 한 줄 안의 위치다. ECMA-426 내부 좌표는 0부터 시작하고, JavaScript용 열은 JavaScript 문자열 길이를 세는 기본 단위인 UTF-16 코드 단위로 센다. 반면 오류 화면이나 도구의 입력 표기는 다른 기준을 쓸 수 있으므로 사람이 임의로 1을 더하거나 빼지 말고 소비 도구의 계약에 맡긴다.
JSON에는 원본 목록과 좌표 연결이 들어 있다
다음은 구조만 보여 주는 작은 예다. mappings의 AAAAA는 생성 위치 0:0을 첫 번째
원본의 0:0과 첫 번째 이름에 연결하는 유효한 한 구간이다.
{
"version": 3,
"file": "app.js",
"sources": ["../src/App.tsx"],
"sourcesContent": ["export function App() { /* ... */ }"],
"names": ["App"],
"mappings": "AAAAA"
}mappings는 눈으로 읽기 위한 본문이 아니다.
- 세미콜론(
;)은 생성된 파일의 다음 줄로 넘어간다. - 쉼표(
,)는 같은 줄 안의 다음 연결 구간을 나눈다. - 각 구간은 생성 열, 원본 인덱스, 원본 줄·열과 선택적인 이름 인덱스를 담는다.
- 숫자는 이전 값과의 차이를 짧은 문자 형식으로 압축한다.
- 생성 열만 있는 구간은 대응하는 원본이 없는 도구 생성 코드임을 나타낼 수 있다.
따라서 개발자가 mappings를 손으로 해독하는 것이 목표는 아니다. 어떤 입력이 있어야
좌표를 되돌릴 수 있고, 어느 단계에서 연결 정보가 끊겼는지를 판단하면 된다.
모든 문자에 표가 한 칸씩 있는 것은 아니다
소스 맵은 생성된 모든 문자마다 원본 좌표를 반복 저장하지 않는다. 연결이 달라지는 시작 지점들을 정렬된 구간으로 기록한다.
생성 위치 0:0 0:18 0:31
연결 구간 A-------------B-------------C
질의 위치 ^ 0:24
선택되는 시작점 B이 구조 때문에 열 정보가 없는 줄 단위 맵은 파일과 줄을 보여 줄 수 있어도 한 줄 안의
정확한 표현식이나 중간 지점 중단점은 구분하기 어렵다. names도 선택 사항이므로 함수
이름이 항상 원래 이름으로 복원된다고 기대하지 않는다.
여러 변환 단계는 좌표 연결을 이어 가야 한다
실제 React 프로젝트는 한 번만 변환되지 않을 수 있다.
App.tsx ── JSX·TypeScript 변환 ──> 중간 JavaScript ── 묶기·축소 ──> app.js
▲ 맵 1 ▲ 맵 2
└──────────────── 최종 원본까지 이어진 맵 ────────────────┘따라서 마지막 번들러만 소스 맵을 켰다고 항상 충분한 것은 아니다. 도구 경계가 나뉜 경우 앞선 변환 단계가 위치 정보를 제공하고 다음 도구가 이어받아야 작성한 TSX까지 돌아갈 수 있다. 한 도구가 여러 변환을 내부에서 처리한다면 최종 맵이 처음 원본까지 연결되는지를 결과로 확인한다.
Web과 React에서는 실행 코드와 표시 코드를 구분한다
React의 component stack(컴포넌트 스택)은 JavaScript 함수 호출 스택과 같은 목록은 아니지만, 운영에서 축소된 컴포넌트 이름과 소스 위치를 읽는 데도 소스 맵이 쓰일 수 있다.
하지만 원본 줄이 보인다고 컴포넌트 상태나 props가 당시 값으로 되살아나는 것은 아니다. 그 정보는 오류 이벤트, 로그나 별도의 실행 기록으로 남겨야 한다.
React Native는 릴리스 맵으로 스택을 복원한다
React Native 릴리스 오류에는 p@1:132161처럼 축소된 함수 이름과 위치가 보일 수
있다. Hermes는 React Native에서 JavaScript를 실행할 수 있는 엔진이고, 바이트코드는
이 엔진이 실행하기 좋게 바꾼 명령 형식이다. 오류의 숫자는 이 생성 결과 안의 위치를
나타내는 오프셋일 수 있다. 이를 App.js:54:initializeMap처럼 읽을 수 있는 위치와
이름으로 바꾸는 작업이 심볼리케이션이다.
여기서 Android와 iOS의 기본값은 현재 React Native 빌드 설정에 묶인 정보다. 더 오래 남는 원리는 릴리스 스택, 실제 실행된 코드, 그 코드와 함께 생성된 맵이 한 쌍이어야 한다는 점이다.
정확한 코드와 맵을 한 쌍으로 보관한다
보관 단위는 다음처럼 생각하면 된다.
릴리스·빌드 식별자
├─ 실제 배포한 JavaScript 또는 Hermes 결과
├─ 그 결과와 함께 생성한 소스 맵
└─ 플랫폼·실행환경·맵 생성 설정커밋 해시는 좋은 단서지만 그것만으로 항상 충분하지 않다. 같은 커밋도 환경 변수, 의존성이나 빌드 설정이 다르면 결과가 달라질 수 있다. 가능하면 코드 내용으로 계산한 식별값인 해시나 변경되지 않는 릴리스 식별자에 맵을 연결하고, 배포 전에 알려진 한 위치가 원본으로 정확히 돌아가는지 시험한다.
생성과 공개는 서로 다른 결정이다
소스 맵을 만드는 것, 브라우저가 자동으로 찾게 하는 것, 외부 사용자가 파일에 접근하게 하는 것은 서로 다른 결정이다.
현재 Webpack 설정도 이 세 결정을 한꺼번에 해결하지 않는다.
| 설정 | 하는 일 | 하지 않는 일 |
|---|---|---|
hidden-source-map | 생성 파일에서 맵을 자동으로 찾는 연결 주석을 뺀다 | 공개 서버에 저장된 맵의 접근을 차단하지 않는다 |
nosources-source-map | 맵의 sourcesContent, 즉 원본 코드 본문을 뺀다 | 파일 이름·폴더 구조 같은 나머지 정보까지 모두 숨기지는 않는다 |
설정 이름보다 무엇을 하지 않는지를 함께 본다. 목적에 따라 다음처럼 선택한다.
- 사용자의 브라우저에서도 원본 디버깅을 제공하려면 맵을 접근 가능한 위치에 둔다.
- 운영 오류 처리 시스템만 사용한다면 연결 주석 없이 맵을 별도 보관하고 정확한 릴리스에 업로드할 수 있다.
- 원본 공개 위험이 허용되지 않으면
sourcesContent, 경로와 이름까지 확인하고 접근 정책을 별도로 적용한다. - 맵을 보관하지 않기로 했다면 운영 위치 복원 능력을 포기한다는 비용도 함께 기록한다.
hidden이라는 이름은 접근 제어가 아니다. 공개 서버에 파일이 있다면 URL을 아는
사용자는 접근할 수 있으므로 서버·저장소 권한이 실제 공개 범위를 결정한다.
원본 위치는 조사 시작점이지 정답이 아니다
운영에서 원본 위치가 나오지 않으면 다음 순서로 좁힌다.
- 오류 이벤트에 생성 파일 이름과 줄·열이 실제로 있는지 확인한다.
- 그 파일과 정확히 같은 빌드의 맵인지 식별자나 해시로 확인한다.
- 마지막 변환뿐 아니라 앞선 변환 단계의 맵이 이어졌는지 확인한다.
- 맵에 열 정보와 해당 구간의 원본 연결이 있는지 확인한다.
- 브라우저, 오류 처리 도구나
metro-symbolicate가 기대하는 좌표·입력 형식을 확인한다. - 위치를 복원한 뒤에는 로그·오류 문맥과 재현으로 실제 원인을 검증한다.
이 글의 경계
이번 글은 생성 위치를 원본 위치로 되돌리는 좌표 계약에 집중했다. 다음 내용은 분리한다.
- Webpack·Vite·Metro에서 어떤 설정으로 맵을 만들고 이어 받는가: 다음 6편
- 오류 이벤트와 릴리스에 맵을 업로드하는 전체 관측 체계: 12편 관측성 신호
- 같은 입력으로 동일한 코드와 맵을 다시 만드는 방법: 7편 재현 가능한 빌드
- React Native 네이티브 기호 파일과 운영체제 충돌 복원: React Native 전용 글
마지막으로 두 문장만 기억한다. 소스 맵은 생성 위치를 원본 위치에 연결한다. 정확한 코드와 정확한 맵이 한 쌍이어야 한다.
Active recall
기억에서 꺼내 보기
답을 쓰지 않아도 됩니다. 먼저 머릿속으로 답한 뒤 펼쳐서 정답·이유·흔한 오해를 비교하세요.
01소스 맵은 운영 오류의 무엇을 복원하고 무엇은 복원하지 못할까?
정답
생성된 코드의 줄·열을 작성한 원본 파일의 줄·열로 연결하지만, 오류 당시 값과 사용자 행동이나 근본 원인까지 복원하지는 못한다.
관련 설명 다시 읽기왜 그런가
소스 맵은 두 좌표계의 관계를 기록한 데이터이고 실행 기록이나 애플리케이션 상태 저장소가 아니다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
02같은 커밋에서 만든 것처럼 보이는 다른 번들의 소스 맵을 사용해도 될까?
정답
안 된다. 실제로 오류를 낸 생성 코드와 그 코드에서 함께 만든 소스 맵을 정확히 짝지어야 한다.
관련 설명 다시 읽기왜 그런가
작은 코드나 설정 변화도 생성 위치를 크게 옮길 수 있어 다른 맵은 그럴듯하지만 틀린 원본 위치를 반환할 수 있다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
03번들에서 sourceMappingURL 주석을 없애면 소스 맵이 자동으로 비공개가 될까?
정답
아니다. 주석 제거는 자동 연결을 막을 뿐이며 맵 파일이 공개 서버에 남아 있다면 별도의 접근 통제가 필요하다.
관련 설명 다시 읽기왜 그런가
원본을 포함하는 sourcesContent와 파일 경로·이름의 공개 범위를 목적에 맞게 따로 결정해야 한다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
04React Native 릴리스 스택을 읽을 수 있게 만들려면 무엇이 필요할까?
정답
릴리스 스택과 그 앱에서 실제로 사용한 정확한 소스 맵을, 소스 맵을 읽는 metro-symbolicate 같은 도구에 함께 제공해야 한다.
관련 설명 다시 읽기왜 그런가
현재 React Native 문서의 축소된 함수 이름과 Hermes 바이트코드 위치는 맵 없이 원본 파일·줄·함수 이름으로 돌아가지 않는다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
05최종 오류 위치가 TSX가 아니라 중간 JavaScript를 가리키면 무엇을 확인할까?
정답
앞선 변환 단계의 위치 정보를 다음 단계가 이어받아 최종 맵이 처음 원본까지 연결하는지 확인한다.
관련 설명 다시 읽기왜 그런가
여러 변환 사이에서 위치 연결이 끊기면 마지막 맵은 중간 생성 코드까지만 가리킬 수 있다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
출처와 검증 범위
아래 날짜는 링크를 마지막으로 열어 본 날입니다. 문서 상단의 검증일은 글의 설명과 적용 범위를 다시 확인한 날입니다.
- ECMA-426 — Source map format specificationEcma International · 표준 · 확인 2026-08-04
- Debug your original code instead of deployed with source mapsChrome for Developers · 공식 문서 · 확인 2026-08-04
- Component — componentDidCatch(error, info)React · 공식 문서 · 확인 2026-08-04
- Debugging Release BuildsReact Native · 공식 문서 · 확인 2026-08-04
- Devtoolwebpack · 공식 문서 · 확인 2026-08-04