Пользователь
0,0
рейтинг
20 июля 2012 в 12:15

Разработка → Введение в Javascript Source Maps перевод

Вы когда-нибудь думали, как было бы здорово, если бы слитый в один файл и минифицированный яваскрипт код в production-окружении можено было удобно читать и даже отлаживать без ущерба производительности? Теперь это возможно, если использовать штуку под названием source maps.

Если коротко, то это способ связать минифицированный/объединённый файл с файлами, из которых он получился. Во время сборки для боевого окружения помимо минификации и объединения файлов также генерируется файл-маппер, который содержит информацию об исходных файлах. Когда производится обращение к конкретному месту в минифицированном файле, то производится поиск в маппере, по которому вычисляется строка и символ в исходном файле. Developer Tools (WebKit nightly builds или Google Chrome Canary) умеет парсить этот файл автоматически и прозрачно подменять файлы, как будто ведётся работа с исходными файлами. На момент написания (оригинальной статьи — прим. перев.) Firefox заблокировал развитие поддержки Source Map. Подробнее — на MozillaWiki Source Map.

Пример — правильное определение места в исходном коде

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

image

Реальное использование


Прежде чем смотреть следующий пример, нужно активировать просмотр source maps в Chrome Canary или WebKit nightly, для этого в свойствах активировать пункт «Enable source maps» (см. скриншот)
image

Продолжим. Предыдущий пример был интересным, но как это можно использовать? Зайдите на dev.fontdragr.com настроенным браузером Google Chrome и вы увидите, что яваскрипты на странице не скомпилированы и можно смотреть отдельные js-файлы. Это всё благодаря использованию маппера, а на самом деле код на странице скомпилирован. Все ошибки, выводы в лог и точки останова будут маппиться на исходный код, и отлаживать код будет очень удобно. В итоге можно работать с production-сайтом как с тестовым.

Пример — посмотрите в консоль на fontdragr.com

Зачем вообще нужны Source Maps?


Сейчас маппинг работает только между исходными файлами и сжатой/объединённой версией, но ведутся разговоры о том, чтобы сделать маппинг для языков, компилируемых в JavaScript (например, CoffeeScript), и даже о поддержке CSS-препроцессоров, таких как SASS и LESS.
В будущем мы могли бы легко использовать почти любой язык, как если бы он поддерживался браузером нативно:
  • CoffeeScript
  • ECMAScript 6 и выше
  • SASS/LESS и т.п.
  • Практически любой язык, который компилируется в JavaScript

Посмотрите скринкаст, в котором CoffeeScript отлаживается в экспериментальной сборке консоли Firefox:


Google Web Toolkit (GWT) недавно добавил поддержку Source Maps и Ray Cromwell из GWT сделал отличный скринкаст, показывающий работу Source Map в действии.


Другой пример использует библиотеку Google Traceur, которая позволяет писать на ES6 (ECMAScript 6) и компилировать в ES3-совместимый код. Компилятор Traceur также генерирует source map. Посмотрите на пример использования особенностей ES6 (классов и traits), как если бы они поддерживались браузером нативно. Textarea в примере также позволяет писать ES6-код, который будет компилироваться на лету в ES3 и также будет создаваться файл-маппер.
image
Пример — можно написать код на ES6 и сразу посмотреть в отладчике

Как это работает?


Единственный пока компилятор/минификатор с поддержкой Source Map — Closure compiler (как при компиляции сгенерировать маппер — написано ниже). При минификации JavaScript будет создан и файл-маппер. Пока Closure compiler не добавляет в конец файла специальный комментарий для Google Chrome Canary dev tools о том, что доступен файл-маппер:
//@ sourceMappingURL=/path/to/file.js.map

Такой комментарий позволяет браузеру искать нужное место в исходном файле, используя файл-маппер. Если идея использовать странные комментарии вам не нравится, то можно добавить к скомпилированному файлу специальный заголовок:
X-SourceMap: /path/to/file.js.map

Как и комментарий, это скажет клиенту, где искать маппер для этого файла. Использование заголовка также позволяет работать с языками, которые не поддерживают однострочные комментарии.
image
Файл-маппер будет скачан только если включено свойство и открыта консоль. Ну и конечно нужно будет залить исходные файлы, чтобы они были доступны по указанным в маппере путям.

Как сгенерировать файл-маппер?


Как уже говорилось выше, нужен будет Closure compiler для минификаци, склейки и генерации файла-маппера для нужных JavaScript-файлов. Для этого нужно выполнить команду:
java -jar compiler.jar \ 
     --js script.js \
     --create_source_map ./script-min.js.map \
     --source_map_format=V3 \
     --js_output_file script-min.js

Нужные флаги — это --create_source_map и --source_map_format. Последний нужен, т.к. по умолчанию маппер создаётся в формате V2, а нам нужен V3.

Внутреннее устройство Source Map


Чтобы лучше понять Source Map, возьмём для примера небольшой файл-маппер и подробно разберём, как устроена «адресация». Ниже приведён немного модифицированный пример из V3 spec:
{
    version : 3,
    file: "out.js",
    sourceRoot : "",
    sources: ["foo.js", "bar.js"],
    names: ["src", "maps", "are", "fun"],
    mappings: "AAgBC,SAAQ,CAAEA"
}


Можно заметить, что это обычный литерал объекта, содержащий всю нужную информацию:
  • Версию маппера
  • Название минифицированного/объединённого файла для production
  • sourceRoot позволяет дописывать префикс в путь к исходным файлам
  • sources содержит названия исходных файлов
  • names содержит все настоящие названия переменных/функций из полученного файла
  • а mappings — это соответствующие минифицированные названия


BASE64 VLQ или как сделать Source Map маленьким


Изначально в спецификации был описан очень подробный вывод всех зависимостей, что делало файл-маппер в 10 раз больше размером, чем сгенерированный файл. Вторая версия уменьшила размер файла вполовину, а третья версия — уменьшила ещё раз вполовину. Теперь для 133kB файла генерируется ~300kB файл-маппер. Как же удалось добиться такого уменьшения и при этом уметь отслеживать сложные зависимости?
Используется VLQ (Variable Length Quantity) и Base64-кодирование. Свойство mappings — это одна очень большая строка. Внутри этой строки точки с запятой (;) отделяют номера строк в сгенерированном файле. Внутри получившейся строки используются запятые для отделения сегментов кода. Каждый из сегментов представляет собой 1, 4 или 5 VLQ-полей. Некоторые могут быть длиннее за счёт бита продолжения. Каждый сегмент строится на основе предыдущего, что помогает уменьшить размер файла.
image
Как говорилось раньше, каждый сегмент может быть 1, 4 или пятью VLQ. На диаграмме показаны 4 VLQ с одним битом продолжения. Разберём её подробнее и покажем, как маппер вычисляет положение в исходном файле. Сегмент состоит из пяти вещей:
  • Номер символа в сгенерированном файле
  • Исходный файл
  • Номер строки в исходном файле
  • Номер символа в исходном файле
  • Исходное название (если есть)

(прим. перев.: не осилил до конца перевести эту часть статьи, полностью можно прочесть в оригинале; если есть желающие помочь — пишите, буду благодарен)

Потенциальные проблемы с XSSI


В спецификации говорится о возможных проблемах с внедрением XSS при использовании Source Map. Избавиться от неё можно, написав в начале своего map-файла ")]}", чтобы сделать это js-файл невалидным и вызвать ошибку. WebKit dev tools уже умеет её забарывать:
if (response.slice(0, 3) === ")]}") {
    response = response.substring(response.indexOf('\n'));
}

Как видно, первые три символа обрезаются и производится проверка их на соответствие указанному в спецификации невалидному коду и в этом случае вырезается всё до следующего символа перевода строки.

@sourceURL и displayName в действии: eval и анонимные функции


Эти два соглашения хотя пока и не входят в спецификацию Source Map, но позволяют серьёзно упростить работу с eval и анонимными функциями.
Первый хелпер очень похож на свойство //@ sourceMappingURL и вообще-то в спецификации (V3) упоминается. Включив этот специальный комментарий в код, который потом будет выполнен через eval, можно назвать eval-ы, что даст им более логичные имена при работе в консоли. Ниже приведён простой пример с использованием компилятора CoffeeScript:

Пример — пропущенный через eval код со сгенерированным именем
image

Другой хелпер позволяет давать имена анонимным функциям при помощи свойства displayName, указанного в контексте этой функции. Попрофилируйте этот пример, чтобы увидеть displayName в действии.

Пример — названия для анонимных функций через displayName (только WebKit NIghtly)
image
При профилировании будут показываться красивые названия вместо (anonymous function). Но скорее всего displayName не будет включён в финальную сборку Google Chrome. Хотя надежды ещё остаются, предлагают также переименовать свойство в debugName.
К моменту написания статьи присваивание названий коду, выполненному через eval, поддерживают только Firefox и Google Chrome. Свойство displayName доступно только в ночных сборках Google Chrome.

Вливайтесь


Есть очень длинное обсуждение по поддержке Source Map в CoffeeScript.
У UglifyJS также есть тикет про поддержку Source Map.
Вы можете помочь, если примете участие в обсуждении и выскажете мнение по поводу нужности поддержки Source Map. Чем больше будет инструментов, поддерживающих эту технологию, тем будет проще работать, так что требуйте её поддержки в вашем любимом OpenSource-проекте.

Source Map не идеален


Есть одна неприятность с использованием Source Map для нормальной отладки. Проблема заключается в том, что при попытке проверить значение аргумента или переменной, определённой в контексте исходного файла, контекст ничего не вернёт, т.к. он на самом деле не существует. Нужен какой-то обратный маппинг, чтобы проверить значение соответствующей переменной/аргумента в минифицированном коде и сопоставить его исходному коду.
Проблема решаемая, а при должном внимании к Source Map могут появиться ещё более интересные его применения.

Инструменты и ресурсы




Source Map — мощный инструмент для разработчика. Он позволяет держать production-код максимально сжатым, но при этом позволяет его отлаживать. Так же полезен для начинающих разработчиков, чтобы посмотреть код, написанный опытными разработчиками, чтобы поучиться правильному структурированию и написанию своего кода без необходимости продираться сквозь минифицированный код. Так чего же вы ждёте? Сгенерируйте Source Map для своего проекта!
Перевод: Ryan Seddon
bullgare @bullgare
карма
25,7
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (12)

  • +1
    Да, было бы действительно классно видеть эту поддержку во всех браузерах.

    У себя в данный момент решаем эту задачу так — в куках/локалсторейдже держим флаг, DeveloperModeOn. На определенные страницы делаем файлики лоадеров, в которых yepnope-ом, в зависимости от флага, грузим или продакшен- (скомбайненые/отминифаенные), или девелопер- файлы. Работает без сбоев.
    • 0
      немного не понял, т.е. в продакшн окружении для кук разработчиков грузятся отдельные файлы?
      да, удобнее, чем минифицированный файл:)
      но в этом случае тоже есть один неприятный баг — может получиться так, что возникает ошибка именно при сливании файлов в один (особенно в legacy code, где, к примеру, забыли закрывающую скобку или точку запятой в конце файла). и её ну очень трудно отследить в этом случае.
      • 0
        При минифайинге файлы линтуются, и js и css — так что тут все надежно. У нас порядка 200 файлов, пока проблем не было.
        • 0
          ну если не было — отлично.
          но в случае, когда не все файлы грузятся на каждой страничке, а минифицированный файл — один, то тут тоже могут быть проблемы.
          в общем, всё зависит от реализации, в любом случае source map сильно бы помог)
    • 0
      > в куках/локалсторейдже держим флаг
      Ой небезопасно. Лучше привязывать по юзеру, или по ид сессии. Или другой трудноугадываемый ид.
      • 0
        на предыдущем месте работы мы привязывались к ip
      • 0
        Зачем? Ну получит юзер вместо быстрых продакшенских файлов, поток медленных девелоперских — в чем небезопасность?
        • 0
          Тут зависит, чем они отличаются. Может, DeveloperModeOn включает логи или еще что-то. В общем случае — нежелательно.
          • 0
            Жесть. Я-то знаю ЧТО они включают, и описал выше…
  • 0
    а для grunt есть что-нибудь подобное?
    • 0
      Что вы имеете ввиду под поддержкой grunt? Все определяется плагинами, например uglify и browserify генерировать source maps умеют без проблем.
      • 0
        да, я имел ввиду конкретный плагин, видимо меня переклинило на мысле что uglify только минифицирует), благодарю за пояснения

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.