Source Maps: быстро и понятно

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.


Механизм Source Maps используется для отображения исходных текстов программы на сгенерированные на их основе скрипты. Несмотря на то, что тема не нова и по ней уже написан ряд статей (например эта, эта и эта) некоторые аспекты все же нуждаются в прояснении. Представляемая статья представляет собой попытку упорядочить и систематизировать все, что известно по данной теме в краткой и доступной форме.

В статье Source Maps рассматриваются применительно к клиентской разработке в среде популярных браузеров (на примере, DevTools Google Chrome), хотя область их применения не привязана к какому-либо конкретному языку или среде. Главным источникам по Source Maps является, конечно, стандарт, хотя он до сих пор не принят (статус — proposal), но, тем не менее, широко поддерживается браузерами.

Работа над Source Maps была начата в конце нулевых, первая версия была создана для плагина Firebug Closure Inspector. Вторая версия вышла в 2010 и содержала изменения в части сокращения размера map-файла. Третья версия разработана в рамках сотрудничества Google и Mozilla и предложена в 2011 (последняя редакция в 2013).

В настоящее время в среде клиентской разработки сложилась ситуация, когда исходный код почти никогда не интегрируется на веб-страницу непосредственно, но проходит перед этим различные стадии обработки: минификацию, оптимизацию, конкатенацию, более того, сам исходный код может быть написан на языках требующих транспиляции. В таком случае, для целей отладки необходим механизм позволяющий наблюдать в дебаггере именно исходный, человекочитаемый код.

Для работы Source Maps необходимы следующие файлы:

  • собственно сгенерированный JavaScript-файл
  • набор файлов с исходным кодом использовавшийся для его создания
  • map-файл отображающий их друг на друга

Map-файл


Вся работа Source Maps основана на map-файле, который может выглядеть, например, так:

{
    "version":3,
    "file":"index.js",
    "sourceRoot":"",
    "sources":["../src/index.ts"],
    "names":[],
    "mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,SAAS,SAAS;IACd,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;",
    "sourcesContent": []
}

Обычно, имя map-файла складывается из имени скрипта, к которому он относится, с добавлением расширения ".map", bundle.js — bundle.js.map. Это обычный json-файл со следующими полями:

  • «version» — версия Source Maps;
  • «file» — (опционально) имя сгенерированного файла, к которому относится текущий map-файл;
  • «sourceRoot» — (опционально) префикс для путей к файлам-исходникам;
  • «sources» — список путей к файлам-исходникам (разрешаются аналогично адресам src тега script, можно использовать file://.);
  • «names» — список имен переменных и функций, которые подверглись изменению в сгенерированном файле;
  • «mappings» — координаты отображения переменных и функций исходных файлов на сгенерированный файл в формате Base64 VLQ;
  • «sourcesContent» — (опционально) в случае self-contained map-файла список строк, каждая из которых содержит исходный текст файла из sources;

Загрузка Source Maps


Для того, чтобы браузер загрузил map-файл может быть использован один из следующих способов:

  • JavaScript-файл пришел с HTTP-заголовком: SourceMap: <url> (ранее использовался ныне устаревший X-SourceMap: <url>)
  • в сгенерированном JavaScript-файле есть особый комментарий вида:

//# sourceMappingURL=<url> (для CSS /*# sourceMappingURL=<url> */)

Таким образом, загрузив map-файл браузер подтянет и исходники из поля «sources» и с помощью данных в поле «mappings» отобразит их на сгенерированный скрипт. Во вкладке Sources DevTools можно будет найти оба варианта.

Для указания пути может использоваться пседопротокол file://. Также, в <url> может быть включено все содержимое map-файла в кодировке Base64. В терминологии Webpack подобные Source Maps названы inline source maps.

//# sourceMappingURL=data:application/json;charset=utf-8;base64,<source maps Base64 code>

Ошибки загрузки Source Maps
Следует заметить, что map-файлы не являются частью веб-страницы, поэтому вы не увидите информации об их загрузке во вкладке Network DevTools. Тем не менее, если в сгенерированном файле находится ссылка на несуществующий map-файл, в Console DevTools будет предупреждение вида: «DevTools failed to load SourceMap: ...». Также при наличии ссылки на несуществующий исходник, вместо него будет сообщение вида: «Could not load content for ...».

Self-contained map-файлы


Код файлов-исходников можно включить непосредственно в map-файл в поле «sourcesContent», при наличии этого поля необходимость в их отдельной загрузке отпадает. В этом случае названия файлов в «sources» не отражают их реального адреса и могут быть совершенно произвольными. Именно поэтому, вы можете видеть во вкладке Sources DevTools такие странные «протоколы»: webpack://, ng:// и т.д

Mappings


Сущность механизма отображения состоит в том, что координаты (строка/столбец) имен переменных и функций в сгенерированном файле отображаются на координаты в соотвествующем файле исходного кода. Для работы механизма отображения необходима следующая информация:

(#1) номер строки в сгенерированном файле;
(#2) номер столбца в сгенерированном файле;
(#3) индекс исходника в «sources»;
(#4) номер строки исходника;
(#5) номер столбца исходника;

Все эти данные находятся в поле «mappings», значение которого — длинная строка с особой структурой и значениями закодированными в Base64 VLQ.

Строка разделена точками с запятой (;) на разделы, соответствующие строкам в сгенерированном файле (#1).

Каждый раздел разделен запятыми (,) на сегменты, каждый из которых может содержать 1,4 или 5 значений:

  • номер столбца в сгенерированном файле (#2);
  • индекс исходника в «sources» (#3);
  • номер строки исходника (#4);
  • номер столбца исходника (#5);
  • индекс имени переменной/функции из списка «names»;

Значения номеров строк и столбцов — относительные, указывают смещение относительно предыдущих координат и только первое от начала файла или раздела.

Каждое значение представляет собой число в формате Base64 VLQ. VLQ (Variable-length quantity) представляет собой принцип кодирования сколь угодно большого числа с помощью произвольного числа двоичных блоков фиксированной длины.

В Source Maps используются шестибитные блоки, которые следуют в порядке от младшей части числа к старшей. Старший 6-й бит каждого блока (continuation bit) зарезервирован, если он установлен, то за текущим следует следующий блок относящийся к этому же числу, если сброшен — последовательность завершена.

Поскольку в Source Maps значение должно иметь знак, для него также зарезервирован младший 1-бит (sign bit), но только в первом блоке последовательности. Как и ожидается, установленный sign бит означает отрицательно число.

Таким образом, если число можно закодировать единственным блоком, оно не может быть по модулю больше 15 (11112), так как в первом шестибитном блоке последовательности два бита зарезервированы: continuation бит всегда будет сброшен, sign бит будет установлен в зависимости от знака числа.

Шестибитные блоки VLQ отображаются на кодировку Base64, где каждой шестибитной последовательности соответствует определенный символ ASCII.



Декодируем число mE. Инверсируем порядок, младшая часть последняя — Em. Декодируем числа из Base64: E — 000100, m — 100110. В первом отбрасываем старший continuation бит и два лидирующих нуля — 100. Во втором отбрасываем старший continuation и младший sign биты (sign бит сброшен — число положительное) — 0011. В итоге получаем 100 00112, что соответствует десятичному 67.

Можно и в обратную сторону, закодируем 41. Его двоичный код 1010012, разбиваем на два блока: старшая часть — 10, младшая часть (всегда 4-битная) — 1001. К старшей части добавляем старший continuation бит (сброшен) и три лидирующих нуля — 000010. К младшей части добавляем старший continuation бит (установлен) и младший sign бит (сброшен — число положительное) — 110010. Кодируем числа в Base64: 000010 — C, 110010 — y. Инверсируем порядок и, в итоге, получаем yC.

Для работы с VLQ весьма полезна одноименная библиотека.
Источник: https://habr.com/ru/post/509250/


Интересные статьи

Интересные статьи

Всем привет, меня зовут Роман Пятаков! Я техлид во фронтенд-команде Lamoda. И сегодня хочу поговорить с вами о разработке сложных компонентов. Lamoda — это технически сложный продукт,...
SWAP (своп) — это механизм виртуальной памяти, при котором часть данных из оперативной памяти (ОЗУ) перемещается на хранение на HDD (жёсткий диск), SSD (твёрдотельный накоп...
Под катом, пожалуй, самый быстрый в мире и удобный способ охладить вино до нужной температуры в бытовых условиях.
Автокэширование в 1с-Битрикс — хорошо развитая и довольно сложная система, позволяющая в разы уменьшить число обращений к базе данных и ускорить выполнение страниц.
Практически все коммерческие интернет-ресурсы создаются на уникальных платформах соответствующего типа. Среди них наибольшее распространение получил Битрикс24.