초안 ECMA-426 / 2025년 11월 13일

소스맵 형식 사양

이 사양서에 대하여

https://tc39.es/ecma426/에 있는 문서가 가장 정확하고 최신의 소스맵 사양서입니다. 이 문서는 가장 최근에 발행된 스냅샷의 내용과 다음 스냅샷에 포함될 예정인 모든 변경 내역을 포함합니다.

사양서에 기여하기

이 사양서는 GitHub에서 개발됩니다. 여러 방법으로 이 사양서 개발에 기여할 수 있습니다:

이 문서가 어떻게 작성되는지에 대한 더 많은 정보는 간행 정보를 참고하세요.

소개

이 Ecma 표준은 트랜스파일된 소스코드를 원본 소스코드로 맵핑하기 위해 사용되는 소스맵 형식을 정의합니다.

소스맵 형식의 주요 목표는 다음과 같습니다:

원본 소스 맵 형식(v1)은 최적화된 JavaScript 코드의 소스 수준 디버깅을 위해 Closure Inspector에서 Joseph Schorr가 만들었으며, 형식 자체는 언어에 구애받지 않습니다. 하지만 소스맵을 사용하는 프로젝트의 규모가 커짐에 따라 형식의 장황함이 문제가 되기 시작했습니다. v2 형식(Source Map Revision 2 Proposal)은 전체 소스맵 크기를 줄이기 위해 일부 단순성과 유연성을 희생하여 만들어졌습니다. v2에서도 파일 크기가 여전히 한계에 봉착하여 v3 형식은 Pavel Podivilov(Google)의 제안을 기반으로 만들어졌습니다.

소스맵 형식은 더 이상 버전 번호를 사용하지 않으며, 항상 "3"으로 고정되어 있습니다.

2023~2024년 동안 소스맵 형식은 Ecma 표준으로 발전되었고, 많은 사람들의 의미 있는 기여가 있었습니다. 소스맵 형식의 추가적인 개선은 TC39-TG4에서 이어질 예정입니다.

Asumu Takikawa, Nicolò Ribaudo, Jon Kuperman
ECMA-426, 제1판, 프로젝트 편집자

1 범위

이 표준은 다양한 종류의 개발자 도구들이 JavaScript, WebAssembly, 그리고 CSS로 컴파일된 코드의 디버깅 경험을 향상시키기 위해 사용하는 소스맵 형식을 정의합니다.

2 적합성

적합한 소스맵 문서는 이 사양에서 자세히 설명하는 구조를 따른 JSON 문서입니다.

적합한 소스맵 생성기는 적합한 소스맵 문서를 생성해야 하며, 이 사양서의 알고리즘으로 어떤 오류도 보고받지 않고(명시적으로 선택적 오류로 지정된 경우조차) 디코드가 가능해야 합니다.

적합한 소스맵 소비자는 소스맵 문서의 검색(해당하는 경우) 및 디코딩을 위해 이 사양서에 지정된 알고리즘을 구현해야 합니다. 적합한 소비자는 오류를 무시하거나, 사양서가 알고리즘에서 선택적으로 오류를 보고해도 됨을 명시한 경우 종료하지 않고 오류를 보고할 수 있습니다.

3 참고 문헌

다음 문서들은 이 문서에서 일부 또는 전체 내용이 요구사항을 구성하는 방식으로 참조됩니다. 날짜가 명시된 경우 해당 판만 적용됩니다. 날짜가 없으면, 참조된 문서의 최신 판(개정 포함)을 적용합니다.

3.1 기준 문헌

ECMA-262, ECMAScript® 언어 사양.
https://tc39.es/ecma262/

ECMA-404, JSON 데이터 교환 형식.
https://www.ecma-international.org/publications-and-standards/standards/ecma-404/

3.2 참고 문헌

IETF RFC 4648, Base16, Base32, Base64 데이터 인코딩.
https://datatracker.ietf.org/doc/html/rfc4648

WebAssembly Core Specification.
https://www.w3.org/TR/wasm-core-2/

WHATWG Encoding.
https://encoding.spec.whatwg.org/

WHATWG Fetch.
https://fetch.spec.whatwg.org/

WHATWG Infra.
https://infra.spec.whatwg.org/

WHATWG URL.
https://url.spec.whatwg.org/

4 표기 규칙

이 사양서는 ECMA-262 (표기 규칙)에서 정의된 동일한 표기 규칙을 따르며, 이 절에서 정의된 확장사항을 적용합니다.

4.1 알고리즘 규칙

4.1.1 암시적 완료

이 사양에서 선언된 모든 추상 연산은 알고리즘에서 선언된 반환 타입을 포함한 정상적인 완료 또는 throw completion을 반환하는 것으로 간주합니다. 예를 들면, 다음과 같이 선언된 추상 연산:

4.1.1.1 GetTheAnswer ( input )

추상 연산 GetTheAnswer는 input ( integer 타입 )을 인수로 받고 integer를 반환합니다.

는 다음과 동일합니다:

4.1.1.2 GetTheAnswer2 ( input )

추상 연산 GetTheAnswer2는 input ( integer 타입 )을 인수로 받고, 정상적인 완료integer 또는 throw completion을 반환합니다.

모든 추상 연산 호출 중 completion 레코드를 반환하는 것은 암시적으로 ReturnIfAbrupt 매크로로 감싸는 것으로 간주하며, 명시적으로 Completion 호출로 감싸는 경우는 예외입니다. 예시:

  1. Let result be GetTheAnswer(value).
  2. Let second be Completion(GetTheAnswer(value)).

다음과 동일합니다:

  1. Let result be ReturnIfAbrupt(GetTheAnswer(value)).
  2. Let second be Completion(GetTheAnswer(value)).

4.1.2 선택적 오류

알고리즘이 선택적으로 오류를 보고할 때 구현체는 다음 중 하나의 동작을 선택할 수 있습니다:

  • 알고리즘의 나머지 부분을 계속 실행한다.
  • 사용자(예: 브라우저 콘솔)에게 오류를 보고한 후 알고리즘을 계속 실행한다.
  • ThrowCompletion을 반환한다.

구현체는 선택적 오류마다 서로 다른 동작을 선택해도 됩니다.

4.2 문법 표기법

이 사양서는 ECMA-262 (문법 표기법)에서 정의된 문법 표기 규칙을 따르며, 다음의 주의사항이 있습니다:

5 용어 및 정의

본 문서에서 다음 용어와 정의를 적용합니다.

생성된 코드

컴파일러 또는 트랜스파일러에 의해 생성된 코드.

원본 소스

컴파일러 또는 트랜스파일러에 의해 처리되지 않은 소스 코드.

소스맵 URL

URL로, 생성된 코드에서 소스맵 위치를 참조함.

컬럼

생성된 코드의 한 줄 내에서 0부터 시작하는 인덱스 오프셋. JavaScript 및 CSS 소스맵에서는 UTF-16 코드 유닛 수로, WebAssembly 소스맵에서는 바이너리 콘텐츠의 바이트 인덱스(단일 줄로 표현됨)로 계산됨.

참고
즉, "A"(LATIN CAPITAL LETTER A)는 코드 유닛 1로 계산되고, "🔥"(FIRE)는 코드 유닛 2로 계산됩니다. 다른 콘텐츠 타입의 소스맵에서는 이 규칙이 다를 수 있습니다.

6 base64 VLQ

base64 VLQbase64로 인코딩된 가변 길이 정수로, 가장 큰 비트(6번째 비트)가 연속 비트로 사용되며, "숫자"들은 문자열로 최하위부터 순서대로 인코딩되고, 첫 숫자의 최하위 비트가 부호 비트로 사용됩니다.

노트 1
base64 VLQ 인코딩으로 표현할 수 있는 값은 32비트까지만 제한되며, 더 큰 값이 필요한 경우가 있을 때 확장될 수 있습니다. 즉, 32비트를 초과하는 값은 유효하지 않으며 구현체는 이를 거부할 수 있습니다. 부호 비트는 제한에 포함되지만 연속 비트는 포함되지 않습니다.
노트 2
문자열 "iB"는 두 자리의 base64 VLQ를 의미합니다. 첫 자리 "i"0b100010 비트 패턴을 인코딩하며, 연속 비트 1(VLQ가 계속됨), 부호 비트 0(음수가 아님), 값 비트 0b0001을 가집니다. 두 번째 자리 B0b000001을 인코딩하며, 연속 비트 0, 부호 비트 없음, 값 비트 0b00001을 가집니다. 이 VLQ 문자열을 디코딩하면 숫자 17이 됩니다.
노트 3
문자열 "V"는 한 자리의 base64 VLQ를 의미합니다. "V"0b010101 비트 패턴을 인코딩하며, 연속 비트 0(계속 없음), 부호 비트 1(음수), 값 비트 0b1010을 가집니다. 이 VLQ 문자열을 디코딩하면 숫자 -10이 됩니다.

base64 VLQ는 다음의 렉시컬 문법을 따릅니다:

Vlq :: VlqDigitList VlqDigitList :: TerminalDigit ContinuationDigit VlqDigitList TerminalDigit :: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f ContinuationDigit :: g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9 + /

6.1 VLQSignedValue

구문 지향 연산 VLQSignedValue는 인수를 받지 않고 정수를 반환합니다. 다음 생성 규칙에 따라 단계적으로 정의됩니다:

Vlq :: VlqDigitList
  1. unsignedVLQUnsignedValue VlqDigitList의 값으로 한다.
  2. unsigned modulo 2 = 1이라면, sign을 -1로 한다.
  3. 그렇지 않으면, sign을 1로 한다.
  4. valuefloor(unsigned / 2)으로 한다.
  5. value가 0이고 sign이 -1이면, -231을 반환한다.
  6. value가 231 이상이면, 오류를 발생시킨다.
  7. sign × value를 반환한다.
참고
6번 단계의 확인은 unsignedVLQUnsignedValue VlqDigitList의 값이지, Vlq의 값이 아니기 때문에 필요합니다.

6.2 VLQUnsignedValue

구문 지향 연산 VLQUnsignedValue는 인수를 받지 않으며, 음수가 아닌 정수를 반환합니다. 다음 생성 규칙에 따라 단계적으로 정의됩니다:

Vlq :: VlqDigitList
  1. valueVLQUnsignedValue VlqDigitList의 값으로 한다.
  2. value가 232 이상이면 오류를 발생시킨다.
  3. value를 반환한다.
VlqDigitList :: ContinuationDigit VlqDigitList
  1. leftVLQUnsignedValue ContinuationDigit의 값으로 한다.
  2. rightVLQUnsignedValue VlqDigitList의 값으로 한다.
  3. left + right × 25를 반환한다.
TerminalDigit :: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f
  1. 이 생성 규칙에 의해 매치된 문자를 digit에 할당한다.
  2. digit에 대응하는 정수를 IETF RFC 4648에 정의된 base64 인코딩에 따라 value에 할당한다.
  3. Assert: value < 32.
  4. value를 반환한다.
ContinuationDigit :: g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9 + /
  1. 이 생성 규칙에 의해 매치된 문자를 digit에 할당한다.
  2. digit에 대응하는 정수를 IETF RFC 4648에 정의된 base64 인코딩에 따라 value에 할당한다.
  3. Assert: 32 ≤ value < 64.
  4. value - 32를 반환한다.

7 JSON 값 유틸리티

이 명세의 알고리즘은 ECMA-262 내부 동작을 기반으로 정의되어 있지만, JavaScript가 아닌 플랫폼에서도 쉽게 구현할 수 있도록 설계되었습니다. 이 절에서는 JSON 값을 다뤄 ECMA-262의 세부 사항을 문서의 다른 부분과 분리하여 추상화합니다.

JSON 값JSON 객체, JSON 배열, 문자열(String), 숫자(Number), 불리언(Boolean) 또는 null 중 하나입니다.

JSON 객체객체(Object)로, 각 프로퍼티가 다음을 만족해야 합니다:

JSON 배열JSON 객체로서 다음을 만족해야 합니다:

7.1 ParseJSON ( string )

추상 연산 ParseJSON는 string(문자열)을 받아 JSON 값을 반환합니다. 호출 시 다음 단계를 수행합니다:

  1. resultCall(%JSON.parse%, null, « string »)로 한다.
  2. Assert: resultJSON 값이다.
  3. result를 반환한다.
편집자 주
이 추상 연산은 ECMA-262에서 직접 공개되는 작업 중에 있습니다. 관련 논의는 tc39/ecma262#3540에서 볼 수 있습니다.

7.2 JSONObjectGet ( object, key )

추상 연산 JSONObjectGet은 object(JSON 객체)와 key(문자열)를 받아 JSON 값 또는 missing을 반환합니다. object에서 지정된 key에 대응하는 값을 반환합니다. 호출 시 다음 단계를 수행합니다:

  1. objectkey로 된 자체 프로퍼티가 없다면, missing을 반환한다.
  2. propobjectkey에 해당하는 자체 프로퍼티를 할당한다.
  3. prop[[Value]] 속성을 반환한다.

7.3 JSONArrayIterate ( array )

추상 연산 JSONArrayIterate는 array(JSON 배열)를 받아 리스트(List) 형태의 JSON 값들을 반환합니다. 이는 "For each" 알고리즘 등에서 반복할 수 있도록 array 내의 모든 요소를 포함한 리스트를 만듭니다. 호출 시 다음 단계를 수행합니다:

  1. lengthJSONObjectGet(array, "length")로 한다.
  2. Assert: length는 음수가 아닌 정수(Number)이다.
  3. list를 새로운 빈 리스트로 한다.
  4. i를 0으로 한다.
  5. i < (length)인 동안 반복하여,
    1. valueJSONObjectGet(array, ToString(𝔽(i))))로 한다.
    2. Assert: valuemissing이 아니다.
    3. valuelist에 추가한다.
    4. ii + 1로 한다.
  6. list를 반환한다.

7.4 StringSplit ( string, separators )

추상 연산 StringSplit은 string(문자열)과 separators(문자열 리스트)를 받아, 리스트 형식의 문자열 목록을 반환합니다. 문자열을 separators 요소 중 하나로 분리된 하위 문자열로 나누며, 여러 구분자가 동시에 일치하는 경우 separators에서 먼저 등장하는 구분자가 우선 적용됩니다. 다음 단계로 동작합니다:

  1. parts를 새로운 빈 리스트로 한다.
  2. strLenstring의 길이로 한다.
  3. lastStart를 0으로 한다.
  4. i를 0으로 한다.
  5. i < strLen인 동안 반복하여,
    1. matchedfalse로 한다.
    2. separators의 각 문자열 sep에 대해,
      1. sepLensep의 길이로 한다.
      2. candidatestringi에서 min(i+sepLen, strLen)까지의 부분 문자열로 한다.
      3. candidate = sep이고 matchedfalse이면,
        1. chunkstringlastStart에서 i까지의 부분 문자열로 한다.
        2. chunkparts에 추가한다.
        3. lastStarti + sepLen로 한다.
        4. ii + sepLen로 한다.
        5. matchedtrue로 한다.
    3. matchedfalse이면, ii + 1로 한다.
  6. chunkstringlastStart에서 strLen까지의 부분 문자열로 한다.
  7. chunkparts에 추가한다.
  8. parts를 반환한다.

8 위치 타입

8.1 위치 레코드

위치 레코드는 음수가 아닌 행 번호와 음수가 아닌 컬럼 번호의 쌍입니다:

표 1: 위치 레코드 필드
필드 이름 값 타입
[[Line]] 음수가 아닌 정수(Number)
[[Column]] 음수가 아닌 정수(Number)

8.2 원본 위치 레코드

원본 위치 레코드디코드된 소스 레코드, 음수가 아닌 행 번호, 음수가 아닌 컬럼 번호의 쌍입니다. 이는 위치 레코드와 유사하지만, 구체적인 원본 소스 파일 안의 소스 위치를 나타냅니다.

표 2: 원본 위치 레코드 필드
필드 이름 값 타입
[[Source]] 디코드된 소스 레코드
[[Line]] 음수가 아닌 정수(Number)
[[Column]] 음수가 아닌 정수(Number)

8.3 ComparePositions ( first, second )

추상 연산 ComparePositions는 인수 first(위치 레코드 또는 원본 위치 레코드)와 second(위치 레코드 또는 원본 위치 레코드)를 받아 lesser, equal, greater를 반환합니다. firstsecond보다 앞에 있으면 lesser, 같으면 equal, 뒤에 있으면 greater를 반환합니다. 원본 위치 레코드[[Source]] 필드는 비교 시 무시됩니다. 호출 시 다음 단계를 수행합니다:

  1. first.[[Line]] < second.[[Line]]이면, lesser를 반환합니다.
  2. first.[[Line]] > second.[[Line]]이면, greater를 반환합니다.
  3. Assert: first.[[Line]]second.[[Line]]은 같습니다.
  4. first.[[Column]] < second.[[Column]]이면, lesser를 반환합니다.
  5. first.[[Column]] > second.[[Column]]이면, greater를 반환합니다.
  6. equal를 반환합니다.

9 소스맵 형식

소스맵은 아래와 같이 최상위 JSON 객체 구조를 갖는 JSON 문서입니다:

{
  "version" : 3,
  "file": "out.js",
  "sourceRoot": "",
  "sources": ["foo.js", "bar.js"],
  "sourcesContent": [null, null],
  "names": ["src", "maps", "are", "fun"],
  "mappings": "A,AAAB;;ABCDE",
  "ignoreList": [0]
}

9.1 소스맵 디코딩

디코딩된 소스맵 레코드는 다음 필드들을 가집니다:

표 3: 디코딩된 소스맵 레코드의 필드
필드 이름 값 타입
[[File]] 문자열 또는 null
[[Sources]] List디코딩된 소스 레코드
[[Mappings]] List디코딩된 매핑 레코드

디코딩된 소스 레코드는 다음 필드들을 가집니다:

표 4: 디코딩된 소스 레코드의 필드
필드 이름 값 타입
[[URL]] URL 또는 null
[[Content]] 문자열 또는 null
[[Ignored]] Boolean

9.1.1 ParseSourceMap ( string, baseURL )

추상 연산 ParseSourceMap은 인수 string(문자열)과 baseURL(URL)을 받아 디코딩된 소스맵 레코드를 반환합니다. 호출 시 다음 단계들을 수행합니다:

  1. jsonParseJSON(string)으로 한다.
  2. jsonJSON 객체가 아니면 오류를 발생시킨다.
  3. JSONObjectGet(json, "sections")이 missing이 아니면,
    1. DecodeIndexSourceMap(json, baseURL)을 반환한다.
  4. DecodeSourceMap(json, baseURL)을 반환한다.

9.1.2 DecodeSourceMap ( json, baseURL )

추상 연산 DecodeSourceMap은 json (JSON 객체)와 baseURL (URL)을 인자로 받고 디코딩된 소스 맵 레코드를 반환한다. 호출 시 다음 단계를 수행한다:

  1. JSONObjectGet(json, "version")이 3𝔽이 아니면, 선택적으로 오류를 보고한다.
  2. mappingsFieldJSONObjectGet(json, "mappings")로 둔다.
  3. mappingsField문자열이 아니면, 오류를 발생시킨다.
  4. JSONObjectGet(json, "sources")가 JSON 배열이 아니면, 오류를 발생시킨다.
  5. fileFieldGetOptionalString(json, "file")로 둔다.
  6. sourceRootFieldGetOptionalString(json, "sourceRoot")로 둔다.
  7. sourcesFieldGetOptionalListOfOptionalStrings(json, "sources")로 둔다.
  8. sourcesContentFieldGetOptionalListOfOptionalStrings(json, "sourcesContent")로 둔다.
  9. ignoreListFieldGetOptionalListOfArrayIndexes(json, "ignoreList")로 둔다.
  10. sourcesDecodeSourceMapSources(baseURL, sourceRootField, sourcesField, sourcesContentField, ignoreListField)로 둔다.
  11. namesFieldGetOptionalListOfStrings(json, "names")로 둔다.
  12. mappingsDecodeMappings(mappingsField, namesField, sources)로 둔다.
  13. mappings를 오름차순으로 정렬한다. 디코딩된 매핑 레코드 a디코딩된 매핑 레코드 b보다 작은 경우는 ComparePositions(a.[[GeneratedPosition]], b.[[GeneratedPosition]])이 lesser일 때이다.
  14. 디코딩된 소스 맵 레코드를 반환한다. { [[File]]: fileField, [[Sources]]: sources, [[Mappings]]: mappings  }.

9.1.2.1 GetOptionalString ( object, key )

추상 연산 GetOptionalString은 object (JSON 객체)와 key (문자열)을 인자로 받고 문자열 또는 null을 반환한다. 호출 시 다음 단계를 수행한다:

  1. valueJSONObjectGet(object, key)로 둔다.
  2. value문자열이면, value를 반환한다.
  3. valuemissing이 아니면, 선택적으로 오류를 보고한다.
  4. null을 반환한다.

9.1.2.2 GetOptionalListOfStrings ( object, key )

추상 연산 GetOptionalListOfStrings은 object (JSON 객체)와 key (문자열)을 인자로 받아 문자열의 리스트를 반환한다. 호출 시 다음 단계를 수행한다:

  1. list를 새로운 빈 리스트로 둔다.
  2. valuesJSONObjectGet(object, key)로 둔다.
  3. valuesmissing이면, list를 반환한다.
  4. valuesJSON 배열이 아니면,
    1. 선택적으로 오류를 보고한다.
    2. list를 반환한다.
  5. JSONArrayIterate(values)의 각 요소 item에 대해,
    1. item문자열이면,
      1. itemlist에 추가한다.
    2. 그렇지 않으면,
      1. 선택적으로 오류를 보고한다.
      2. ""list에 추가한다.
  6. list를 반환한다.

9.1.2.3 GetOptionalListOfOptionalStrings ( object, key )

추상 연산 GetOptionalListOfOptionalStrings은 object (JSON 객체)와 key (문자열)을 인자로 받아 문자열 또는 null의 리스트를 반환한다. 호출 시 다음 단계를 수행한다:

  1. list를 새로운 빈 리스트로 둔다.
  2. valuesJSONObjectGet(object, key)로 둔다.
  3. valuesmissing이면, list를 반환한다.
  4. valuesJSON 배열이 아니면,
    1. 선택적으로 오류를 보고한다.
    2. list를 반환한다.
  5. JSONArrayIterate(values)의 각 요소 item에 대해,
    1. item문자열이면,
      1. itemlist에 추가한다.
    2. 그렇지 않으면,
      1. itemnull이 아니면, 선택적으로 오류를 보고한다.
      2. nulllist에 추가한다.
  6. list를 반환한다.

9.1.2.4 GetOptionalListOfArrayIndexes ( object, key )

추상 연산 GetOptionalListOfArrayIndexes는 object (객체)와 key (문자열)을 인자로 받아 0 이상의 정수 리스트를 반환한다. 호출 시 다음 단계를 수행한다:

  1. list를 새로운 빈 리스트로 둔다.
  2. valuesJSONObjectGet(object, key)로 둔다.
  3. valuesmissing이면, list를 반환한다.
  4. valuesJSON 배열이 아니면,
    1. 선택적으로 오류를 보고한다.
    2. list를 반환한다.
  5. JSONArrayIterate(values)의 각 요소 item에 대해,
    1. item정수형 숫자이고, item+0𝔽이며 item+0𝔽이면,
      1. (item)을 list에 추가한다.
    2. 그렇지 않으면,
      1. itemnull이 아니면, 선택적으로 오류를 보고한다.
      2. nulllist에 추가한다.
  6. list를 반환한다.

9.2 매핑 구조

mappings 필드 데이터는 다음과 같이 분할됩니다:

  • 생성된 파일에서 각각의 그룹(한 줄을 나타냄)은 세미콜론 (;)으로 구분됩니다
  • 각 세그먼트는 콤마(,)로 구분됩니다
  • 각 세그먼트는 1개, 4개, 또는 5개의 가변 길이 필드로 구성됩니다.

각 세그먼트의 필드는 다음과 같습니다:

  1. column의 0부터 시작하는 생성 코드의 줄 번호. 해당 세그먼트가 첫 번째 필드이거나, 새로운 생성 코드 줄(;) 뒤의 첫번째 세그먼트인 경우, 이 필드는 전체 base64 VLQ 값을 갖습니다. 그렇지 않은 경우, 이 필드는 이전 필드의 발생과 상대적인 base64 VLQ 값을 갖습니다. 이 필드는 아래의 후속 필드들과 달리 이전 값이 각 생성된 라인마다 초기화됨에 유의하세요.
  2. 존재하는 경우, 소스 리스트의 0부터 시작하는 인덱스입니다. 이 필드는 base64 VLQ로 이전의 필드 발생과 상대값을 표시하는데, 이 필드가 처음 발생하면 전체값을 가집니다.
  3. 존재하는 경우, 원본 소스에서의 0부터 시작하는 줄 번호입니다. 이 필드는 또한 base64 VLQ로 이전 발생과 상대값을 갖으며, 처음이면 전체값이 됩니다. source 필드가 있으면 반드시 존재해야 합니다.
  4. 존재하는 경우, column의 0부터 시작하는 원본 소스 줄 번호. 이 필드는 base64 VLQ로 이전 발생과 상대값을 표시하며, 처음이면 전체값을 갖습니다. source 필드가 있으면 반드시 존재해야 합니다.
  5. 존재하는 경우, 해당 세그먼트와 연결된 names 리스트의 0부터 시작하는 인덱스입니다. 이 필드는 base64 VLQ로 이전 발생과 상대값을 표시하며, 처음이면 전체값입니다.
참고 1
이 인코딩의 목적은 소스 맵의 크기를 줄이는 것입니다. VLQ 인코딩은 Google Calendar를 이용하여 테스트한 결과, Source Map Revision 2 Proposal 에 비해 50%가량 소스맵을 줄였습니다.
참고 2
필드가 1개인 세그먼트는 생성 코드가 매핑되지 않은 경우(즉, 원본 소스 코드가 없어서 컴파일러에 의해 생성된 코드 등) 나타내기 위해 사용됩니다. 필드가 4개인 세그먼트는 이름이 없는 매핑된 코드를 나타내고, 필드가 5개인 세그먼트는 매핑된 이름이 있는 매핑된 코드를 나타냅니다.
참고 3
file 오프셋 사용도 고려되었으나, 원본 파일과의 불일치(플랫폼별 줄 끝 처리 차이 등)를 방지하기 위해 line/column 데이터를 사용하는 방식을 채택했습니다.

디코딩된 매핑 레코드는 다음과 같은 필드를 가집니다:

표 5: 디코딩된 매핑 레코드의 필드
필드 이름 값 타입
[[GeneratedPosition]] Position Record
[[OriginalPosition]] Original Position Record 또는 null
[[Name]] 문자열 또는 null

9.2.1 매핑 문법

Mappings 문자열은 다음 문법을 따라야 합니다:

MappingsField : LineList LineList : Line Line ; LineList Line : MappingListopt MappingList : Mapping Mapping , MappingList Mapping : GeneratedColumn GeneratedColumn OriginalSource OriginalLine OriginalColumn Nameopt GeneratedColumn : Vlq OriginalSource : Vlq OriginalLine : Vlq OriginalColumn : Vlq Name : Vlq

매핑 상태 레코드는 다음과 같은 필드를 가집니다:

표 6: 매핑 상태 레코드의 필드
필드 이름 값 타입
[[GeneratedLine]] 0 이상의 정수
[[GeneratedColumn]] 0 이상의 정수
[[SourceIndex]] 0 이상의 정수
[[OriginalLine]] 0 이상의 정수
[[OriginalColumn]] 0 이상의 정수
[[NameIndex]] 0 이상의 정수

9.2.1.1 DecodeMappingsField

구문 지향 연산 DecodeMappingsField는 state (매핑 상태 레코드), mappings (문자열 리스트 디코딩된 매핑 레코드), names(문자열 리스트) 그리고 sources(리스트 디코딩된 소스 레코드)를 인자값으로 받습니다. 아래 생성 규칙에 대해 부분적으로 정의됩니다:

LineList : Line ; LineList
  1. DecodeMappingsFieldLine에 대해 state, mappings, names, sources 인자로 실행합니다.
  2. state.[[GeneratedLine]] 값을 state.[[GeneratedLine]] + 1로 설정합니다.
  3. state.[[GeneratedColumn]] 값을 0으로 설정합니다.
  4. DecodeMappingsFieldLineList에 대해 state, mappings, names, sources 인자로 실행합니다.
Line : [empty]
  1. 반환합니다.
MappingList : Mapping , MappingList
  1. DecodeMappingsFieldMapping에 대해 state, mappings, names, sources 인자로 실행합니다.
  2. DecodeMappingsFieldMappingList에 대해 state, mappings, names, sources 인자로 실행합니다.
Mapping : GeneratedColumn
  1. DecodeMappingsFieldGeneratedColumn에 대해 state, mappings, names, sources 인자로 실행합니다.
  2. state.[[GeneratedColumn]] < 0이면
    1. 오류를 선택적으로 보고합니다.
    2. 반환합니다.
  3. position을 새로운 Position Record { [[Line]]: state.[[GeneratedLine]], [[Column]]: state.[[GeneratedColumn]]}로 설정합니다.
  4. decodedMapping을 새로운 DecodedMappingRecord { [[GeneratedPosition]]: position, [[OriginalPosition]]: null, [[Name]]: null}로 설정합니다.
  5. decodedMappingmappings에 추가합니다.
Mapping : GeneratedColumn OriginalSource OriginalLine OriginalColumn Nameopt
  1. DecodeMappingsFieldGeneratedColumn에 대해 state, mappings, names, sources 인자로 실행합니다.
  2. state.[[GeneratedColumn]] < 0이면
    1. 오류를 선택적으로 보고합니다.
    2. 반환합니다.
  3. generatedPosition을 새로운 Position Record { [[Line]]: state.[[GeneratedLine]], [[Column]]: state.[[GeneratedColumn]]}로 설정합니다.
  4. DecodeMappingsFieldOriginalSource에 대해 state, mappings, names, sources 인자로 실행합니다.
  5. DecodeMappingsFieldOriginalLine에 대해 state, mappings, names, sources 인자로 실행합니다.
  6. DecodeMappingsFieldOriginalColumn에 대해 state, mappings, names, sources 인자로 실행합니다.
  7. state.[[SourceIndex]] < 0 또는 state.[[SourceIndex]]sources 요소 개수 이상이거나 state.[[OriginalLine]] < 0 또는 state.[[OriginalColumn]] < 0이면
    1. 오류를 선택적으로 보고합니다.
    2. originalPositionnull로 설정합니다.
  8. 그 외의 경우,
    1. originalPosition을 새로운 Original Position Record { [[Source]]: sources[state.[[SourceIndex]]], [[Line]]: state.[[OriginalLine]], [[Column]]: state.[[OriginalColumn]]}로 설정합니다.
  9. namenull로 설정합니다.
  10. Name이 존재하면,
    1. DecodeMappingsFieldName에 대해 state, mappings, names, sources 인자로 실행합니다.
    2. state.[[NameIndex]] < 0 또는 state.[[NameIndex]]names 요소 개수 이상이면, 오류를 선택적으로 보고합니다.
    3. 그 외의 경우, namenames[state.[[NameIndex]]]로 설정합니다.
  11. decodedMapping을 새로운 DecodedMappingRecord { [[GeneratedPosition]]: generatedPosition, [[OriginalPosition]]: originalPosition, [[Name]]: name}로 설정합니다.
  12. decodedMappingmappings에 추가합니다.
GeneratedColumn : Vlq
  1. relativeColumnVLQSignedValueVlq로 설정합니다.
  2. state.[[GeneratedColumn]]state.[[GeneratedColumn]] + relativeColumn 값으로 설정합니다.
OriginalSource : Vlq
  1. relativeSourceIndexVLQSignedValueVlq로 설정합니다.
  2. state.[[SourceIndex]]state.[[SourceIndex]] + relativeSourceIndex 값으로 설정합니다.
OriginalLine : Vlq
  1. relativeLineVLQSignedValueVlq로 설정합니다.
  2. state.[[OriginalLine]]state.[[OriginalLine]] + relativeLine 값으로 설정합니다.
OriginalColumn : Vlq
  1. relativeColumnVLQSignedValueVlq로 설정합니다.
  2. state.[[OriginalColumn]]state.[[OriginalColumn]] + relativeColumn 값으로 설정합니다.
Name : Vlq
  1. relativeNameVLQSignedValueVlq로 설정합니다.
  2. state.[[NameIndex]]state.[[NameIndex]] + relativeName 값으로 설정합니다.

9.2.2 DecodeMappings ( rawMappings, names, sources )

추상 연산 DecodeMappings는 rawMappings(문자열), names(문자열의 리스트), sources(리스트디코딩된 소스 레코드)를 인자로 받고, 리스트디코딩된 매핑 레코드를 반환합니다. 호출 시 다음 단계를 수행합니다:

  1. mappings를 새로운 빈 리스트로 설정합니다.
  2. mappingsNoderawMappingsMappingsField목표 심볼로 사용하여 파싱할 때의 루트 파스 노드로 설정합니다.
  3. 파싱이 실패했으면,
    1. 선택적으로 오류를 보고합니다.
    2. mappings를 반환합니다.
  4. state를 모든 필드가 0으로 설정된 새로운 매핑 상태 레코드로 설정합니다.
  5. mappingsNode에 대해 state, mappings, names, sources 인자로 DecodeMappingsField를 수행합니다.
  6. mappings를 반환합니다.

9.2.3 생성된 자바스크립트 코드의 매핑

생성 코드의 위치에서 매핑 항목이 있을 수 있는 경우를 입력 요소로 정의하며, ECMAScript 어휘 문법을 따릅니다. 매핑 항목은 다음 중 하나를 가리켜야 합니다:

9.2.4 생성된 자바스크립트 코드의 이름

소스맵 생성기는 자바스크립트 토큰에 대해 매핑 항목과 [[Name]] 필드를 생성해야 하는데, 다음 조건을 만족할 때입니다:

  • 원본 소스 언어 구조가 생성된 자바스크립트 코드에 의미적으로 매핑되는 경우
  • 원본 소스 언어 구조가 이름을 갖는 경우

이때 매핑 항목의 [[Name]] 값은 원본 소스 언어 구조의 이름이 되어야 합니다. 매핑 중에서 [[Name]] 필드가 null이 아닌 값을 가진 것을 이름이 있는 매핑이라고 부릅니다.

참고 1
즉시 실행 함수식에서 함수와 변수명을 바꾸거나 함수명을 제거하는 미니파이어

다음 열거는 ECMAScript 구문 문법의 생성식과, 소스맵 생성기가 이름이 있는 매핑을 생성해야 하는 해당 토큰 혹은 비터미널(생성식의 오른쪽 항)을 나열합니다. 이때 생성되는 매핑 항목은 9.2.3 절을 따라야 합니다.

이 열거는 반드시 포함해야 하는 "최소한"의 항목으로 이해되어야 하며, 일반적으로 소스맵 생성기는 추가적인 이름 매핑을 자유롭게 생성할 수 있습니다.

참고 2
이 목록에는 생성기가 "추가적으로" 이름 매핑을 생성할 수 있는 토큰도 포함되어 있습니다. 이는 기존 도구가 이름 매핑을 생성하거나 필요로 하는 현실을 반영합니다. 같은 이름에 대해 중복된 이름 매핑을 생성하는 것은 비교적 저렴합니다: 이름 인덱스는 서로 상대적으로 인코딩되므로 이후 같은 이름으로 매핑할 경우 0(A)로 인코딩됩니다.

9.3 소스 해결

sourceRoot를 앞에 붙인 후에도 소스가 절대 URL이 아니면, 소스는 소스맵 기준으로 상대적으로 해결됩니다(HTML 문서에서 script src 속성을 해석하는 것과 유사).

9.3.1 DecodeSourceMapSources ( baseURL, sourceRoot, sources, sourcesContent, ignoreList )

추상 연산 DecodeSourceMapSources는 baseURL(URL), sourceRoot(문자열 또는 null), sources(문자열 또는 null을 포함하는 리스트), sourcesContent(문자열 또는 null을 포함하는 리스트), ignoreList(0 이상의 정수로 구성된 리스트)를 인자로 받아서 디코딩된 소스 레코드를 반환합니다. 호출 시 다음 절차를 수행합니다:

  1. decodedSources를 새로운 빈 리스트로 설정합니다.
  2. sourcesContentCountsourcesContent의 요소 개수로 설정합니다.
  3. sourceUrlPrefix""로 설정합니다.
  4. sourceRootnull이면,
    1. sourceRoot가 코드 포인트 U+002F (슬래시)로 끝나면,
      1. sourceUrlPrefixsourceRoot로 설정합니다.
    2. 그렇지 않으면,
      1. sourceUrlPrefixsourceRoot"/"문자열 연결로 설정합니다.
  5. index를 0으로 설정합니다.
  6. index < sources의 길이인 동안 반복,
    1. sourcesources[index]로 설정합니다.
    2. decodedSource디코딩된 소스 레코드 { [[URL]]: null, [[Content]]: null, [[Ignored]]: false}로 설정합니다.
    3. sourcenull이면,
      1. sourcesourceUrlPrefixsource문자열 연결로 설정합니다.
      2. sourceURLURL 파싱 결과로 sourcebaseURL로 설정합니다.
      3. sourceURLfailure이면 선택적으로 오류를 보고합니다.
      4. 그 외에는 decodedSource.[[URL]] 값을 sourceURL로 설정합니다.
    4. ignoreListindex가 포함되어 있으면 decodedSource.[[Ignored]]true로 설정합니다.
    5. sourcesContentCount > index이면 decodedSource.[[Content]]sourcesContent[index]로 설정합니다.
    6. decodedSourcedecodedSources에 추가합니다.
  7. decodedSources를 반환합니다.
참고
소스 내용을 표시할 수 있고 동일한 URL에 대한 서로 다른 내용을 동시에 표시할 수 없는 구현체는, 주어진 URL에 해당하는 내용 중 임의의 하나를 선택해 보여줄 수 있습니다.

9.4 확장

소스맵 소비자는 이 포맷에 추가 기능이 추가될 수 있도록, 인식되지 않는 추가 프로퍼티를 무시해야 하며, 그렇지 않고 소스맵을 거부하면 안 됩니다. 기존 사용자들이 깨지지 않도록 하기 위함입니다.

10 인덱스 소스맵

생성 코드의 연결 및 기타 일반적인 후처리를 지원하기 위해 대체 소스맵 표현이 지원됩니다:

{
  "version" : 3,
  "file": "app.js",
  "sections": [
    {
      "offset": {"line": 0, "column": 0},
      "map": {
        "version" : 3,
        "file": "section.js",
        "sources": ["foo.js", "bar.js"],
        "names": ["src", "maps", "are", "fun"],
        "mappings": "AAAA,E;;ABCDE"
      }
    },
    {
      "offset": {"line": 100, "column": 10},
      "map": {
        "version" : 3,
        "file": "another_section.js",
        "sources": ["more.js"],
        "names": ["more", "is", "better"],
        "mappings": "AAAA,E;AACA,C;ABCDE"
      }
    }
  ]
}

인덱스 맵은 표준 맵의 형태를 따릅니다. 일반 소스맵과 같이 파일 포맷은 최상위 객체를 가진 JSON입니다. 버전 및 file 필드는 일반 소스맵과 동일하며, 새로운 sections 필드가 추가됩니다.

sections 필드는 다음 필드를 가진 객체 배열입니다:

sections는 시작 위치로 정렬되어 있어야 하며, 나타내는 영역이 겹치면 안 됩니다.

10.1 DecodeIndexSourceMap ( json, baseURL )

추상 연산 DecodeIndexSourceMap은 json(객체)와 baseURL(URL)을 인자로 받고 디코딩된 소스맵 레코드를 반환합니다. 호출 시 다음을 수행합니다:

  1. sectionsFieldJSONObjectGet(json, "sections")로 설정합니다.
  2. Assert: sectionsFieldmissing이 아님을 보장합니다.
  3. sectionsFieldJSON 배열이 아니면 오류를 발생시킵니다.
  4. JSONObjectGet(json, "version")이 3𝔽가 아니면 선택적으로 오류를 보고합니다.
  5. fileFieldGetOptionalString(json, "file")로 설정합니다.
  6. sourceMap디코딩된 소스맵 레코드 { [[File]]: fileField, [[Sources]]: « », [[Mappings]]: « » }로 설정합니다.
  7. previousOffsetPositionnull로 설정합니다.
  8. previousLastMappingnull로 설정합니다.
  9. JSON 값 section마다 JSONArrayIterate(sectionsField)에서, 다음을 수행합니다:
    1. sectionJSON 객체가 아니면,
      1. 선택적으로 오류를 보고합니다.
    2. 그 외,
      1. offsetJSONObjectGet(section, "offset")로 설정합니다.
      2. offsetJSON 객체가 아니면 오류를 발생시킵니다.
      3. offsetLineJSONObjectGet(offset, "line")로 설정합니다.
      4. offsetColumnJSONObjectGet(offset, "column")로 설정합니다.
      5. offsetLine정수형 Number가 아니면,
        1. 선택적으로 오류를 보고합니다.
        2. offsetLine+0𝔽로 설정합니다.
      6. offsetColumn정수형 Number가 아니면,
        1. 선택적으로 오류를 보고합니다.
        2. offsetColumn+0𝔽로 설정합니다.
      7. offsetPosition을 새로운 포지션 레코드 { [[Line]]: offsetLine, [[Column]]: offsetColumn }로 설정합니다.
      8. previousOffsetPositionnull이면,
        1. ComparePositions(offsetPosition, previousOffsetPosition)이 lesser선택적으로 오류를 보고합니다.
      9. previousLastMappingnull이면,
        1. ComparePositions(offsetPosition, previousLastMapping.[[GeneratedPosition]])이 lesser선택적으로 오류를 보고합니다.
        2. 참고: 이 디코딩 알고리즘 부분은 인덱스 소스맵의 sections 필드가 정렬되고 겹치지 않음을 확인하는 것입니다. 생성기는 겹치는 구역을 가진 인덱스 소스맵을 생성하지 않아야 하지만, 소스맵 소비자는 일반적으로 오프셋의 순서만 검사할 수도 있습니다.
      10. mapFieldJSONObjectGet(section, "map")로 설정합니다.
      11. mapFieldJSON 객체가 아니면 오류를 발생시킵니다.
      12. decodedSectionCompletionCompletion(DecodeSourceMap(json, baseURL))로 설정합니다.
      13. decodedSectionCompletionthrow completion이면,
        1. 선택적으로 오류를 보고합니다.
      14. 그 외,
        1. decodedSectiondecodedSectionCompletion.[[Value]]로 설정합니다.
        2. 디코딩된 소스 레코드 additionalSource마다 decodedSection.[[Sources]]에서,
          1. sourceMap.[[Sources]]additionalSource가 포함되어 있지 않으면,
            1. additionalSourcesourceMap.[[Sources]]에 추가합니다.
        3. offsetMappings를 새로운 빈 리스트로 설정합니다.
        4. 디코딩된 매핑 레코드 mapping마다 decodedSection.[[Mappings]]에서,
          1. mapping.[[GeneratedPosition]].[[Line]] = 0이면,
            1. mapping.[[GeneratedPosition]].[[Column]]mapping.[[GeneratedPosition]].[[Column]] + offsetColumn으로 설정합니다.
          2. mapping.[[GeneratedPosition]].[[Line]]mapping.[[GeneratedPosition]].[[Line]] + offsetLine으로 설정합니다.
          3. mappingoffsetMappings에 추가합니다.
        5. sourceMap.[[Mappings]] 값을 리스트 연결sourceMap.[[Mappings]]offsetMappings를 결합한 값으로 설정합니다.
        6. previousOffsetPositionoffsetPosition로 설정합니다.
        7. offsetMappings가 비어 있지 않으면 previousLastMappingoffsetMappings의 마지막 요소로 설정합니다.
    3. sourceMap을 반환합니다.
참고
구현체는 인덱스 소스맵의 각 섹션을 합치지 않고 따로 저장하거나, 예를 들어 이진 탐색을 사용할 수도 있습니다.

11 소스맵 가져오기

11.1 생성 코드와 소스맵 연결

소스맵 포맷은 언어와 플랫폼에 독립적인 것을 목표로 하지만, 웹 서버에서 호스팅되는 자바스크립트의 예상 사용 사례에 대해 어떻게 참조하는지 정의하는 것이 유용합니다.

출력에 소스맵을 연결하는 방법은 두 가지가 있습니다. 첫 번째 방법은 서버가 HTTP 헤더를 추가하는 지원이 필요하고, 두 번째 방법은 소스 내에 주석을 추가하는 방식입니다.

소스맵은 WHATWG URL에 정의된 것처럼 URL을 통해 연결됩니다. 특히, URI에 허용되지 않은 문자는 퍼센트 인코딩되어야 하며, 데이터 URI일 수도 있습니다. 데이터 URI와 sourcesContent를 함께 사용하면 완전히 자체 포함된 소스맵을 만들 수 있습니다.

HTTP sourcemap 헤더는 소스 주석보다 우선하며, 둘 다 존재할 경우 헤더의 URL을 이용해 소스맵 파일을 해석해야 합니다.

소스맵 URL을 가져오는 방법에 관계없이 같은 과정을 통해 해석됩니다. 그 과정은 아래와 같습니다.

소스맵 URL이 절대 경로가 아니라면, 생성 코드source origin에 상대적으로 해석됩니다. source origin은 아래 경우 중 하나로 결정됩니다:

  • 생성 소스가 src 속성이 있는 script 요소에 연결되어 있지 않고 생성 코드//# sourceURL 주석이 있으면, 그 주석을 사용해 source origin을 정해야 합니다.

    참고
    이전에는 //@ sourceURL이었으며, //@ sourceMappingURL과 마찬가지로 두 형태 모두 허용하는 것이 타당하지만 //#를 우선적으로 사용해야 합니다.
  • 생성 코드가 script 요소에 연결되고, script 요소가 src 속성을 가지면, script 요소의 src 속성이 source origin입니다.
  • 생성 코드가 script 요소에 연결되어 있지만 src 속성이 없다면, source origin은 페이지의 origin입니다.
  • 생성 코드eval()이나 new Function()으로 문자열 평가 시에는 source origin은 페이지의 origin입니다.

11.1.1 HTTP 헤더로 연결

파일이 HTTP(S)로 제공되며 sourcemap 헤더를 포함한 경우, 헤더 값이 연결된 소스맵의 URL입니다.

sourcemap: <url>
참고
이전 버전에서는 헤더 이름으로 x-sourcemap을 추천했지만 이는 이제 폐지되었고 현재는 sourcemap을 사용합니다.

11.1.2 인라인 주석으로 연결

생성 코드sourceMappingURL라는 주석 또는 그 언어/포맷에 맞는 동등한 구조를 포함해야 하며, 해당 주석에는 소스맵의 URL이 들어가야 합니다. 이 명세는 JavaScript, CSS, WebAssembly에서 해당 주석이 어떻게 보여야 하는지 정의하며, 다른 언어 또한 이와 유사한 관례를 따라야 합니다.

특정 언어에서는 sourceMappingURL 주석을 감지하는 여러 방법이 존재할 수 있어, 구현체마다 가장 간단한 방식을 선택할 수 있습니다. 생성 코드가 모든 추출 방식의 결과가 동일할 때 명확하게 소스맵에 연결됨이라 정의합니다.

툴이 하나 이상의 소스파일을 소비하는데 그 소스가 명확하게 소스맵에 연결되어 있고, 출력 파일 또한 소스맵에 연결한다면, 반드시 명확하게 연결해야 합니다.

참고 1

다음 JavaScript 코드는 소스맵에 연결되지만 명확하게 연결되지는 않습니다:

let a = `
//# sourceMappingURL=foo.js.map
//`

이로부터 소스맵 URL파싱 방식으로 추출하면 null을 반환하지만, 파싱 없이 추출하면 foo.js.map이 반환됩니다.

참고 2

소스맵 URL을 추출하는 여러 방법이 서로 다른 결과를 낼 수 있으며, 이는 보안이나 프라이버시 문제를 일으킬 수 있습니다. 소스맵을 감지해야 하는 구현체는 두 알고리즘을 모두 적용해 결과가 같음을 확인하는 것이 권장됩니다.

이 문제를 해결하는 방안이 향후 표준 버전에 반영될 예정이며, 아래 알고리즘에서 U+0060 (`), U+0022 ("), U+0027 ('), 또는 U+002A U+002F (*/) 문자가 포함된 주석이 있으면 조기 종료하는 방식이 도입될 예정입니다.

11.1.2.1 JavaScriptExtractSourceMapURL ( source )

추상 연산 JavaScriptExtractSourceMapURL은 source(문자열)를 인자로 받아 문자열 또는 null을 반환합니다. JavaScript 소스에서 소스맵 URL을 추출합니다. 구현 방식은 파싱 방식 혹은 파싱 없이 중 하나입니다.

소스맵 URL파싱 방식으로 추출하는 방법:

  1. tokens토큰 리스트로, sourceECMA-262 어휘 문법대로 파싱해 얻습니다.
  2. tokens의 각 비터미널 token을 역순으로 검사합니다.
    1. tokenSingleLineComment 또는 MultiLineComment가 아니라면 null 반환
    2. commenttoken의 내용으로 설정
    3. sourceMapURLMatchSourceMapURL(comment)로 설정
    4. sourceMapURL이 문자열이면 그 값을 반환
  3. null 반환

소스맵 URL파싱 없이 추출하는 방법:

  1. linesStringSplit(source, « "\u000D\u000A", "\u000A", "\u000D", "\u2028", "\u2029" »)로 구분
  2. NOTE: 위의 정규식은 LineTerminatorSequence에 해당
  3. lastURLnull로 설정
  4. lines의 각 문자열 lineStr에 대해:
    1. lineStringToCodePoints(lineStr)로 설정
    2. position을 0으로 설정
    3. lineLengthline의 길이로 설정
    4. position < lineLength인 동안 반복
      1. firstline[position]로 설정
      2. position을 1 증가
      3. first가 U+002F이고 position < lineLength이면:
        1. secondline[position]로 설정
        2. position을 1 증가
        3. second가 U+002F면:
          1. commentsubstring(lineStr, position, lineLength)로 설정
          2. sourceMapURLMatchSourceMapURL(comment)로 설정
          3. sourceMapURL이 문자열이면 lastURL에 할당
          4. positionlineLength로 설정
        4. 그 외 second가 U+002A면:
          1. commentCp를 빈 리스트로 설정
          2. position + 1 < lineLength 동안 반복
            1. c1line[position]로 설정
            2. position을 1 증가
            3. c2line[position]로 설정
            4. c1이 U+002A, c2가 U+002F면:
              1. position을 1 증가
              2. sourceMapURLMatchSourceMapURL(CodePointsToString(commentCp))로 설정
              3. sourceMapURL이 문자열이면 lastURL에 할당
            5. c1commentCp에 추가
        5. 그 외:
          1. lastURLnull로 설정
      4. 그 외 first가 ECMAScript WhiteSpace가 아니라면:
        1. lastURLnull로 설정
      5. NOTE: 주석이 아닌 코드 문자를 찾을 때 lastURLnull로 초기화
  5. lastURL 반환
참고 1
위 알고리즘은 소스 행을 역순으로 순회하며 sourceMappingURL 주석이 포함된 행 발견 시 빨리 반환하도록 설계되었습니다.
참고 2

위 알고리즘은 다음 JavaScript 코드와 동일합니다:

const JS_NEWLINE = /^/m;

// 아래 정규 표현식은 다음을 구분합니다:
// - single-line comments
// - "single-line" multi-line comments
// - unclosed multi-line comments
// - just trailing whitespaces
// - a code character
// 아래 반복문은 각 경우를 구분합니다.
const JS_COMMENT =
  /\s*(?:\/\/(?<single>.*)|\/\*(?<multi>.*?)\*\/|\/\*.*|$|(?<code>[^\/]+))/uym;

const PATTERN = /^[@#]\s*sourceMappingURL=(\S*?)\s*$/;

let lastURL = null;
for (const line of source.split(JS_NEWLINE)) {
  JS_COMMENT.lastIndex = 0;
  while (JS_COMMENT.lastIndex < line.length) {
    let commentMatch = JS_COMMENT.exec(line).groups;
    let comment = commentMatch.single ?? commentMatch.multi;
    if (comment != null) {
      let match = PATTERN.exec(comment);
      if (match !== null) lastURL = match[1];
    } else if (commentMatch.code != null) {
      lastURL = null;
    } else {
      // 공백이나 미닫힌 주석만 나온 경우
      // Assert: JS_COMMENT.lastIndex === line.length
    }
  }
}
return lastURL;

11.1.2.1.1 MatchSourceMapURL ( comment )

추상 연산 MatchSourceMapURL은 comment(문자열) 인자를 받아 none 또는 문자열을 반환합니다. 호출 시 다음을 실행합니다:

  1. patternRegExpCreate("^[@#]\s*sourceMappingURL=(\S*?)\s*$", "")로 설정
  2. matchRegExpExec(pattern, comment)로 설정
  3. matchnull이 아니면 Get(match, "1")을 반환
  4. none 반환
참고
이 주석 접두사는 원래 //@였으나, Internet Explorer의 조건부 컴파일과 충돌하여 //#로 변경되었습니다.

소스맵 생성기는 반드시 //#만 내보내야 하며, 소스맵 소비자는 //@//# 모두 허용해야 합니다.

11.1.2.2 CSSExtractSourceMapURL ( source )

추상 연산 CSSExtractSourceMapURL은 source(문자열) 인자를 받아 문자열 또는 null을 반환합니다. CSS 소스에서 소스맵 URL을 추출합니다.

CSS에서 소스맵 URL을 추출하는 방식은 JavaScript와 유사하지만, CSS는 /* ... */ 스타일의 주석만 지원합니다.

11.1.2.3 WebAssemblyExtractSourceMapURL ( bytes )

추상 연산 WebAssemblyExtractSourceMapURL은 bytes(Data Block) 인자를 받아 문자열 또는 null을 반환합니다. WebAssembly 바이너리 소스에서 소스맵 URL을 추출합니다.

  1. modulemodule_decode(bytes)로 설정
  2. moduleWebAssembly 오류이면 null 반환
  3. module의 각 custom section customSection마다 반복
    1. namecustomSectionname 값으로 설정
    2. CodePointsToString(name)이 "sourceMappingURL"이면:
      1. valuecustomSectionbytes로 설정
      2. CodePointsToString(value)를 반환
  4. null 반환

WebAssembly는 텍스트 포맷이 아니고 주석을 지원하지 않으므로 단일 명확한 추출 방식만 지원합니다. URLWebAssembly 이름으로 인코딩되어 custom section의 내용으로 저장됩니다. WebAssembly 코드를 생성하는 도구가 sourceMappingURL 이름의 custom section을 둘 이상 생성하면 잘못된 것입니다.

11.2 소스맵 가져오기

11.2.1 FetchSourceMap ( url )

추상 연산 FetchSourceMap은 url(URL)을 인자로 받아 Promise를 반환합니다. 호출 시 다음을 수행합니다:

  1. promiseCapabilityNewPromiseCapability(%Promise%)로 설정
  2. requestrequest로, request URLurl로 설정하여 생성
  3. processResponseConsumeBody를 매개변수 (response, bodyBytes)를 받으며 promiseCapabilityurl을 캡처하는 Abstract Closure로, 다음을 실행:
    1. bodyBytesnull 또는 failure
      1. Call(promiseCapability.[[Reject]], undefined, « 새로운 TypeError ») 실행
      2. 반환
    2. urlschemeHTTP(S) scheme이고, byte sequence `)]}'`가 bodyBytesprefix면:
      1. bodyBytesbyte-sequence-length ≠ 0이고, bodyBytes[0]이 HTTP newline byte가 아닐 때 계속 반복:
        1. bodyBytes의 0번째 요소 제거
    3. bodyStringCompletion(UTF-8 decode(bodyBytes))로 설정
    4. IfAbruptRejectPromise(bodyString, promiseCapability) 실행
    5. jsonValueCompletion(ParseJSON(bodyString))로 설정
    6. IfAbruptRejectPromise(jsonValue, promiseCapability) 실행
    7. Call(promiseCapability.[[Resolve]], undefined, « jsonValue ») 실행
  4. fetch requestprocessResponseConsumeBody로 설정하여 실행
  5. promiseCapability.[[Promise]] 반환
참고

과거에는 HTTP(S)로 소스맵을 제공할 때 서버가 )]}'로 시작하는 행을 소스맵 앞에 붙일 수 있습니다.

)]}'garbage here
{"version": 3, ...}

이는 다음과 같이 해석됩니다.

{"version": 3, ...}

12 소스맵 레코드에 대한 연산

소스맵을 디코딩한 후, 소스맵 소비자는 결과 디코딩된 소스맵 레코드를 사용해 디버깅이나 기타 용도에 필요한 위치 정보를 조회할 수 있습니다. 이 절에서는 소스맵 소비자가 지원할 수 있는 일반적인 연산의 동작을 설명합니다.

GetOriginalPositions 연산은 원본 소스에서 생성 코드의 위치에 대응하는 위치를 질의하는 데 사용할 수 있습니다. 예를 들어, 디버거에서 사용자가 생성 코드를 클릭해 원본 소스로 이동하는 경우에 활용됩니다.

12.1 GetOriginalPositions ( sourceMapRecord, generatedPosition )

추상 연산 GetOriginalPositions는 sourceMapRecord (디코딩된 소스맵 레코드)와 generatedPosition (포지션 레코드)를 인자로 받아 리스트원본 포지션 레코드를 반환합니다. 호출 시 아래 절차로 수행됩니다:

  1. mappingssourceMapRecord.[[Mappings]]로 설정합니다.
  2. lastnull로 설정합니다.
  3. originalPositions를 새로운 빈 리스트로 설정합니다.
  4. mappings의 각 mapping을 역순으로 살펴보며,
    1. lastnull일 때,
      1. ComparePositions(mapping.[[GeneratedPosition]], generatedPosition) 결과가 lesser 또는 equal이면,
        1. lastmapping으로 설정합니다.
  5. lastnull이 아니면,
    1. mappings의 각 mapping에 대해,
      1. ComparePositions(last.[[GeneratedPosition]], mapping.[[GeneratedPosition]]) 결과가 equal이면,
        1. mapping.[[OriginalPosition]]originalPositions에 추가합니다.
  6. originalPositions를 반환합니다.

Annex A (informative) 관례

소스맵을 다루거나 생성할 때 다음과 같은 관례를 따라야 합니다.

A.1 소스맵 네이밍

일반적으로 소스맵은 생성된 파일과 동일한 이름에 .map 확장자를 붙여 사용합니다. 예를 들어, page.js의 소스맵은 page.js.map로 생성됩니다.

A.2 eval된 코드와 명명된 생성 코드 연결

eval된 코드에 소스맵을 사용하는 경우 지원해야 하는 기존 관례가 있으며, 다음과 같은 형식을 갖습니다:

//# sourceURL=foo.js

Give your eval a name with //@ sourceURL에서 서술되어 있습니다.

Annex B (informative) 참고사항

B.1 언어 중립적 스택 매핑

소스 언어에 대한 지식 없이 수행하는 스택 트레이싱 매핑은 covered되지 않습니다.

B.2 다단계 매핑

최근에는 도구가 일부 DSL(템플릿)이나 TypeScript → JavaScript → minified JavaScript 등 여러 단계를 거쳐 소스를 생성한 뒤 최종 소스맵을 만드는 경우가 많아지고 있습니다. 이 문제는 두 가지 방법 중 하나로 처리할 수 있습니다. 쉬운 방법이지만 정보가 손실되는 방식은 과정 중간의 단계를 디버깅 목적상 무시하는 것이고, 변환의 소스 위치 정보가 무시되거나(중간 변환이 “원본 소스”로 간주됨) 혹은 위치 정보가 이어지도록(중간 변환은 숨김) 구현하는 것입니다. 보다 완전한 방식은 다단계 매핑을 지원하는 것입니다. 즉, 원본 소스에도 소스맵 참조가 있다면, 사용자가 그 참조도 사용할 수 있게 하는 것입니다.

단, 자바스크립트 외의 언어에서는 "소스맵 참조"가 어떻게 생겼는지 명확하지 않습니다. 특히 자바스크립트 스타일의 단일행 주석을 지원하지 않는 언어에서는 소스맵 참조가 어떻게 제공되는지 불분명합니다.

Annex C (informative) 외부 명세에서 정의된 용어

본 문서에서 사용한 ECMA-262 이외의 외부 명세에서 정의된 모든 용어와 알고리즘을 나열합니다.

WebAssembly Core Specification <https://www.w3.org/TR/wasm-core-2/>
custom section, module_decode, WebAssembly error, WebAssembly names
WHATWG Encoding <https://encoding.spec.whatwg.org/>
UTF-8 decode
WHATWG Fetch <https://fetch.spec.whatwg.org/>
fetch, HTTP newline byte, processResponseConsumeBody, request, request URL
WHATWG Infra <https://infra.spec.whatwg.org/>
byte sequence, byte-sequence-prefix, byte-sequence-length,
WHATWG URL <https://url.spec.whatwg.org/>
HTTP(S) scheme, scheme, URL, URL parsing

Annex D (informative) 참고문헌

  1. IETF RFC 4648, The Base16, Base32, and Base64 Data Encodings, <https://datatracker.ietf.org/doc/html/rfc4648>
  2. ECMA-262, ECMAScript® 언어 스펙, <https://tc39.es/ecma262/>
  3. ECMA-404, The JSON Data Interchange Format, <https://www.ecma-international.org/publications-and-standards/standards/ecma-404/>
  4. WebAssembly Core Specification, <https://www.w3.org/TR/wasm-core-2/>
  5. WHATWG Encoding, <https://encoding.spec.whatwg.org/>
  6. WHATWG Fetch, <https://fetch.spec.whatwg.org/>
  7. WHATWG Infra, <https://infra.spec.whatwg.org/>
  8. WHATWG URL, <https://url.spec.whatwg.org/>
  9. Give your eval a name with //@ sourceURL, Firebug (2009), <http://blog.getfirebug.com/2009/08/11/give-your-eval-a-name-with-sourceurl/>
  10. Source Map Revision 2 Proposal, John Lenz (2010), <https://docs.google.com/document/d/1xi12LrcqjqIHTtZzrzZKmQ3lbTv9mKrN076UB-j3UZQ/>
  11. 가변 길이 quantity, 위키백과, <https://en.wikipedia.org/wiki/Variable-length_quantity>

Annex E (informative) 간기

이 명세서는 GitHub에서 평문 소스 포맷인 Ecmarkup으로 작성되었습니다. Ecmarkup은 HTML과 Markdown의 방언으로, ECMAScript 명세를 평문으로 작성하고, 본 문서의 편집 규약을 따르면서 완전한 기능의 HTML 렌더링으로 가공할 수 있는 프레임워크 및 도구 세트를 제공합니다. Ecmarkup은 Grammarkdown을 통한 문법 정의와 Ecmarkdown을 통한 알고리즘 기술 등 다양한 형식과 기술을 통합합니다. 이 명세서의 PDF 버전은 HTML 렌더링을 PDF로 인쇄하여 생성됩니다.

이 명세서의 첫 에디션은 Bikeshed라는 또 다른 평문 소스 포맷(HTML과 Markdown 기반)으로 작성되었습니다.

표준화 이전 버전의 문서는 Google Docs로 작성되었습니다.

Copyright & Software License

Ecma International

Rue du Rhone 114

CH-1204 Geneva

Tel: +41 22 849 6000

Fax: +41 22 849 6001

Web: https://ecma-international.org/

Software License

All Software contained in this document ("Software") is protected by copyright and is being made available under the "BSD License", included below. This Software may be subject to third party rights (rights from parties other than Ecma International), including patent rights, and no licenses under such third party rights are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT https://ecma-international.org/memento/codeofconduct.htm FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  3. Neither the name of the authors nor Ecma International may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ECMA INTERNATIONAL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.