Source Mapの仕様をちゃんと把握したくspecのドキュメントを読んだので、その抄訳メモ(と一部はそのセクションについて解説しているブログ記事へのリンク).
Source Map Revision 3 Proposal - Google Docs
ドキュメントのRevision
Document Revisionsの最後がNovember 18, 2013になっているRevision.
ライセンス
オリジナルと同様にCreative Commons Attribution-ShareAlike 3.0 Unported License.
Revision 3 Format
Proposed Format
1. {
2. "version" : 3,
3. "file": "out.js",
4. "sourceRoot": "",
5. "sources": ["foo.js", "bar.js"],
6. "sourcesContent": [null, null],
7. "names": ["src", "maps", "are", "fun"],
9. "mappings": "A,AAAB;;ABCDE;"
10. }
- Line 1: ファイル全体で単一のJSONオブジェクト.
- Line 2: バージョン(常にオブジェクト内で最初のエントリー)、正の整数.
- Line 3: オプショナル、このsource mapに紐づいている生成コードの名前(ファイル名じゃない場合も許容されるのかな?).
- Line 4: オプショナル、ソースファイルをサーバーに置く場合で生成コードと別ディレクトリにオリジナルソース群を置く場合にそのディレクトリパスを設定するのに利用でき、
sourcesの各要素先頭に連結される(のでsourcesを短く書ける). - Line 5:
mappingsエントリーで利用するオリジナルソースのリスト. - Line 6: オプショナル、オリジナルソースのコンテント(ソースコード自体)のリスト、オリジナルソースをサーバーに置かない場合に利用できる. リスト内の順序は
sourcesと合わせる必要があり、nullの場合はsourcesの値を使ってサーバーから取得される. - Line 7:
mappingsエントリーで利用するシンボル名のリスト. - Line 8: エンコードされたマッピングデータ.
マッピングデータについて
以下の記事で大変詳しく解説されているのでリンク.
Safx: JavaScriptのSource Mapの内部表現について
http://safx-dev.blogspot.jp/2013/08/javascriptsource-map.html
Resolving Sources
sourceRootを連結したsourcesが絶対パスではない場合、 このSource Mapファイルを起点とした相対パスで解決される .
Encoding
常にUTF-8.
Compression
Source MapファイルはGZIP圧縮もアリ.
Extensions
トップレベルにフィールド追加する場合は名前規約でx_からはじめるフィールド名とする. x_からはじまるもの以外は将来のために予約されている(という体なので使うな、と). 既知のものでx_google_linecountがある.
Index map: supporting post processing
生成コードのconcatenating(連結)や、また一般的な後処理(minifyなどを指しているのかな)をサポートするために、Index mapという形式もサポートする.
1. {
2. version : 3,
3. file: “app.js”,
4. sections: [
5. { offset: {line:0, column:0}, url: “url_for_part1.map” }
6. { offset: {line:100, column:10}, map:
7. {
8. version : 3,
9. file: “section.js”,
10. sources: ["foo.js", "bar.js"],
11. names: ["src", "maps", "are", "fun"],
12. mappings: "AAAA,E;;ABCDE;"
13. }
14. }
15. ],
16. }
(なんか元記事のサンプルJSONがmalformedな気がするが修正せずに記載する...)
version, fileは通常のmapと同様.
sectionsはJSONオブジェクトの配列でoffsetとSource mapへの参照を保持する. offsetはlineとcolumnを持つオブジェクトで、参照されるSource mapが生成コードのどの位置から該当するかを指し示す. Source mapへの参照はurlもしくはmapで、urlは該当するSource mapのURLでsourcesと同様にパス解決される(つまり相対パスの場合はこのIndex mapを起点としたパス). mapはSource mapオブジェクトそのもの(つまり埋め込み形式).
sectionsはoffsetでの開始位置順になっている必要があり、オーバーラップしてはいけない.
Conventions
Source Map Naming
Source mapファイルのファイル名について. 任意で、生成コードと同じ名前+.mapとする. (e.g. page.js => page.js.map)
Linking generated code to source maps
生成コードとSource mapファイルの紐付けについて. 2通りの方法あり.
1つ目は生成コードを配信するサーバーがHTTPレスポンスヘッダーにSource mapファイルURLを含める.
SourceMap: <url>
Note: 前のRevisionでX-SourceMapだったがこれはdeprecatedになった.
2つ目は生成コードの最後の行に以下形式の行を含める.
//# sourceMappingURL=<url>
Note: 最初は//@という形式だったがIEのConditional Compilationとコンフリクトするので変更された.
CSSの場合は以下形式になる.
/*# sourceMappingURL=<url> */
Note: <url>はRFC3986で定義されたURLとなる. URLで使用できない文字はURLエンコードする必要がある.
Note: <url>はdata URIとすることもできる. その場合にsourcesContentを含めることも出来る.
source mapping URLが絶対パスではない場合、 生成コードのsource originをベースにパス解決される . source originは、
- 生成コードが
src属性を持つscriptタグに紐づいていなく、生成コード中に//# sourceURLコメントが含まれる場合、そのコメントがsource originを決定するのに使用される.
Note: 以前は//@ sourceURLという形式だったがsourceMappingURLと同様な理由で変更 - 生成コードが
src属性を持つscriptタグに紐づいている場合、そのsrc属性がsource originになる. - 生成コードが
src属性を持たないscriptタグに紐づいている場合、page's originがsource originになる. -
eval()もしくはnew Function()による生成コードの場合、page's originがsource originになる.
Linking eval’d code to named generate code
evalコードでSource mapを利用するためにサポートすべき規約があり、
//@ sourceURL=foo.js
詳細は以下から.
http://blog.getfirebug.com/2009/08/11/give-your-eval-a-name-with-sourceurl/
Multi-level Mapping Notes
多段Source mapについて.
例えば compile CoffeeScript -> JavaScript -> minified JavaScript などの複数回の変換が一般的になっている. これについて問題があり、2通りの方法で対処される.
簡単だけど完全ではない方法としては中間の変換を端折る(?)、上流のSource mapを無視する(?)、もしくはなんとかして中間の変換を隠してやりすごす(?). (ちょっと訳が分からなかった...)
より完全な方法としては多段階のマッピングをサポートする. もしオリジナルソースがSource mapの参照を持っているならば、ユーザーにどれを使うかの選択を与えるなど.
ただしJavaScript以外でどのようにSource mapの参照を持たせるのかが明確ではない. 具体的に言うと、JavaScriptのような単一行コメントをサポートしない言語の場合にどうあるべきかということ. HTTPヘッダーでこれが言及されており、現在の仕様はまだ確定したものではない.
JSON over HTTP Transport
XSSI attacksが実行される危険性がある(そもそもXSSIが理解できていないので適当訳). これはシンタックスエラーとなるコードをコンテンツの先頭に付与することで効果的に防ぐことが出来る.
例えばSource mapがHTTPで配信される場合、サーバーが")]}"から始まる行をSource mapに付与し、クライアントはその付与された行を無視したレスポンスを処理すればよい.