Source maps что это
Перейти к содержимому

Source maps что это

  • автор:

#3 — Autoprefixer и Source Maps

#3 - Autoprefixer и Source Maps

Некоторые CSS3 свойства некорректно отображаются в различных браузерах. Чтобы их исправить придумали специальные префиксы для свойств. Gulp обладает плагином Autoprefixer, что позволяет не прописывать префиксы к свойствам каждый раз вручную, а добавлять их в автоматическом режиме. В уроке вы научитесь работать с плагином Autoprefixer, а также познакомитесь с Source Mapping.

Видеоурок

Некоторые CSS3 свойства, например transition , могут некорректно отображаться в не особо популярных браузерах. Для исправления ошибки были придуманы специальные префиксы к свойствам.

Пример:

Прописывать префиксы под каждое CSS3 свойство очень неудобно и это занимает множество времени.

Для решения проблемы придумали плагин Autoprefixer. Подключив плагин и указав какие CSS или SCSS файлы вы отслеживаете, вы можете в автоматическом режиме добавлять все необходимые префиксы к свойствам и ваш сайт всегда будет отображаться корректно.

Source Maps

Source Maps — плагин, позволяющий указать исходный файл со стилями. Указав такой файл, вам намного проще будет отслеживать ошибки и проверять код через консоль разработчика в Google Chrome, Mozilla Firefox и в любых других веб браузерах.

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-файле, который может выглядеть, например, так:

Обычно, имя 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: (ранее использовался ныне устаревший X-SourceMap: )
  • в сгенерированном JavaScript-файле есть особый комментарий вида:
//# sourceMappingURL= (для CSS /*# sourceMappingURL= */) 

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

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

//# sourceMappingURL=data:application/json;charset=utf-8;base64,

Ошибки загрузки 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 весьма полезна одноименная библиотека.

Введение в 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

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

image

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

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

Зачем вообще нужны Source Maps?
  • CoffeeScript
  • ECMAScript 6 и выше
  • SASS/LESS и т.п.
  • Практически любой язык, который компилируется в JavaScript

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

image

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

Пример — можно написать код на 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:

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

image

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

Как говорилось раньше, каждый сегмент может быть 1, 4 или пятью VLQ. На диаграмме показаны 4 VLQ с одним битом продолжения. Разберём её подробнее и покажем, как маппер вычисляет положение в исходном файле. Сегмент состоит из пяти вещей:

  • Номер символа в сгенерированном файле
  • Исходный файл
  • Номер строки в исходном файле
  • Номер символа в исходном файле
  • Исходное название (если есть)

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

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

В спецификации говорится о возможных проблемах с внедрением XSS при использовании Source Map. Избавиться от неё можно, написав в начале своего map-файла » )]> «, чтобы сделать это js-файл невалидным и вызвать ошибку. WebKit dev tools уже умеет её забарывать:

if (response.slice(0, 3) === ")]>")

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

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

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

image

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

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

image

Пример — названия для анонимных функций через displayName (только WebKit NIghtly)

При профилировании будут показываться красивые названия вместо (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 могут появиться ещё более интересные его применения.

Инструменты и ресурсы
  • Nick Fitzgerald сделал форк UglifyJS с поддержкой Source Map
  • Paul Irish сделал простое демо Source Map
  • Conrad Irwin написал удобный Source Map gem для Ruby-разработчиков
  • Что ещё почитать про именование eval и свойство displayName
  • Можно посмотреть исходный код Closure Compiler создания Source Map
  • Несколько скриншотов и разговор о поддержке GWT source maps

Source Map — мощный инструмент для разработчика. Он позволяет держать production-код максимально сжатым, но при этом позволяет его отлаживать. Так же полезен для начинающих разработчиков, чтобы посмотреть код, написанный опытными разработчиками, чтобы поучиться правильному структурированию и написанию своего кода без необходимости продираться сквозь минифицированный код. Так чего же вы ждёте? Сгенерируйте Source Map для своего проекта!

Создание и использование Source Maps для css и js в GULP 4

Хотелось бы в двух словах рассказать, как настроить Source Maps для GULP 4, но не получится вырвать из контекста просто пару строк кода, поэтому разобью статью на 2 части. Первая будет с базовым кодом для Source Maps, а во второй части будет куча кода из рабочего проекта.

Source Maps нужны для проектов, где происходит сборка нескольких файлов в один, например, компиляция файлов scss в один файл main.min.css или конкатенация js-файлов в один scripts.min.js . Карты источников нужны для того, чтобы быстро вносить правки, и не искать в каком файле задано определенное правило или функция, особенно когда для сборки используются десятки файлов или различных библиотек.

Так будет выглядеть в инспекторе код без Source Maps:

Так будет выглядеть код с использованием Source Maps:

И по итогу, если нам нужно увеличить отступ снизу у элемента .icon , то нам не нужно искать по всем файлам scss где задано это правило, а с помощью карты источников видим, что данное правило указано в файле _general.scss на 44 строке.

Если вам это не нужно, то дальше можно не читать.

1. Базовый пример вывода Source Maps для js-файла

var gulp = require('gulp'); var plugin1 = require('gulp-plugin1'); var plugin2 = require('gulp-plugin2'); var sourcemaps = require('gulp-sourcemaps'); gulp.task('javascript', function() < gulp.src('src/**/*.js') .pipe(sourcemaps.init()) .pipe(plugin1()) .pipe(plugin2()) .pipe(sourcemaps.write()) .pipe(gulp.dest('dist')); >);

Где — plugin1() и plugin2() — это какие-то образные плагины, которые занимаются минификацией и конкатенацией js-файлов, например.

Дальше будет много кода из рабочего примера, будет использоваться scss, сборка, минификация, конкатенация и т.д.

2. Кастомизированный пример подключения Source Maps в js-файлах и в css-файлах

var syntax = 'scss', gulp = require('gulp'), sass = require('gulp-sass'), cleancss = require('gulp-clean-css'), autoprefixer = require('gulp-autoprefixer'), concat = require('gulp-concat'), uglify = require('gulp-uglify'), rename = require('gulp-rename'), sourcemaps = require('gulp-sourcemaps'), notify = require('gulp-notify'),

Задача для компиляции файла main.min.css с Source Maps

// Compile .scss to *.min.css gulp.task('styles', function() < return gulp.src('app/'+syntax+'/**/*.'+syntax+'') // берем все файлы scss .pipe(sourcemaps.init()) // инициализируем создание Source Maps .pipe(sass(< outputStyle: 'compressed' >).on("error", notify.onError())) // компилируем сжатый файл .css .pipe(rename(< suffix: '.min', prefix : '' >)) // переименовываем файл в .min.css .pipe(autoprefixer(['last 15 versions'])) // добавляем вендорные префиксы .pipe(cleancss( > >)) // удаляем все комментарии из кода .pipe(sourcemaps.write()) // пути для записи SourceMaps - в данном случае карта SourceMaps будет добавлена прям в данный файл main.min.css в самом конце .pipe(gulp.dest('app/css')) // перемещение скомпилированного файла main.min.css в папку app/css >);

Задача для конкатенации файла scripts.min.js с Source Maps

// Concatenate all .js from /libs/ to libs.min.js gulp.task('scripts', function() < return gulp.src([ 'app/libs/jquery/dist/jquery.min.js', // указываем пути всех подключаемых библиотек и файлов JS 'app/js/common.js', // основной файл функций подключаем в самом конце ]) .pipe(sourcemaps.init()) // инициализируем создание Source Maps .pipe(concat('scripts.min.js')) // объединяем все вышеперечисленные файлы в один scripts.min.js .pipe(uglify()) // минифицируем и удаляем комментарии из файла scripts.min.js .pipe(sourcemaps.write()) // пути для записи SourceMaps - в данном случае карта SourceMaps будет добавлена прям в данный файл scripts.min.js в самом конце в формате комментария .pipe(gulp.dest('app/js')) // перемещаем полученный файл scripts.min.js в директорию app/js >);

Все классно, но есть одна проблема.

sourcemaps.write() — не указаны пути для файла *.map, значит карта источников будет записана внутри файла в самый конец, тем самым увеличивая вес файлов более чем в два раза. Это не круто, но зато карты источников будут работать и в Chrome, и в Firefox.
sourcemaps.write(‘.’) — в таком формате source map запишется отдельным файлом в той же папке, что и основной файл, в формате main.min.css.map и scripts.min.js.map.

Проблема с лишним содержимым в виде карты путей решаема.

Source Maps нужны нам только во время разработки, поэтому при билде продашена просто удалим все комментарии из файлов .css и .js — а карта источников записана именно в виде комментария.

Это часть кода из моей таски ‘build’:

var buildCss = gulp.src('app/css/**/*.css') .pipe(cleancss( > >)) // удаляем все комментарии из css .pipe(gulp.dest('dist/css')); // отправляем полученный файл на продакшен var buildJs = gulp.src('app/js/**/*.js') .pipe(uglify()) // удаляем все комментарии из js .pipe(gulp.dest('dist/js')); // отправляем полученный файл на продакшен

По итогу, на продакшене будут чистые минифицированные файлы без лишнего кода и комментариев.

Для оптимизации запросов на сервер можно объединить иконки и небольшие картинки, которые используются в css,…

В общем, перепробовал десятки разных плагинов и настроек, но .jpg совсем не оптимизировались, а иногда…

Бесконечный анимированный фон с эффектом параллакса создан на основе варианта 2 из этой статьи HTML…

Изменение внешнего вида для чекбокса с помощью CSS HTML для чекбокса или радио-кнопок CSS для…

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *