При разработке современных JavaScript-приложений исходный код редко попадает в браузер в первоначальном виде. Перед публикацией выполняются транспиляция, объединение модулей, минификация, удаление неиспользуемого кода и другие этапы обработки. В результате итоговый файл может значительно отличаться от исходных модулей проекта.
Без специальных механизмов отладка такого кода становится крайне сложной. Ошибки в консоли браузера будут указывать на строки внутри сгенерированного бандла, который может содержать тысячи строк объединённого и оптимизированного кода.
Для решения этой проблемы используются source maps — специальные файлы сопоставления, позволяющие браузеру связывать итоговый код с оригинальными исходниками.
Source map хранит информацию о том:
Благодаря этому инструменты разработчика могут показывать оригинальные файлы вместо сгенерированного бандла.
Предположим, существует файл:
// src/math.js
export function sum(a, b) {
return a + b;
}
После сборки Rollup может создать следующий результат:
function sum(n,o){return n+o}
export{sum};
Если в коде возникнет ошибка, браузер увидит только минифицированную версию.
При наличии source map инструменты разработчика получают дополнительную информацию:
{
"version": 3,
"file": "bundle.js",
"sources": [
"src/math.js"
]
}
На основании этих данных браузер сможет показать:
export function sum(a, b) {
return a + b;
}
даже несмотря на то, что фактически выполняется минифицированный код.
Поддержка source maps встроена в Rollup и активируется через параметр
sourcemap.
Простейшая конфигурация:
export default {
input: 'src/main.js',
output: {
file: 'dist/bundle.js',
format: 'esm',
sourcemap: true
}
};
После сборки будут созданы файлы:
dist/
├── bundle.js
└── bundle.js.map
Файл .map содержит всю информацию о сопоставлении
исходников.
В конец итогового файла автоматически добавляется специальный комментарий:
//# sourceMappingURL=bundle.js.map
Этот комментарий сообщает браузеру местоположение карты исходников.
При открытии страницы DevTools обнаруживает директиву:
//# sourceMappingURL=bundle.js.map
после чего загружает файл карты и строит соответствия между скомпилированным и исходным кодом.
sourcemap: trueНаиболее распространённый вариант настройки:
output: {
file: 'dist/app.js',
format: 'iife',
sourcemap: true
}
Результат:
app.js
app.js.map
Преимущества:
Такой вариант чаще всего используется в режиме разработки.
Иногда требуется хранить карту прямо внутри итогового файла.
Для этого используется режим:
output: {
file: 'dist/app.js',
format: 'esm',
sourcemap: 'inline'
}
В конце файла появится конструкция вида:
//# sourceMappingURL=dat a:application/json;base64,...
Карта будет закодирована в Base64 и встроена непосредственно в бандл.
Преимущества:
.map файл.Недостатки:
Иногда карта нужна для анализа ошибок, но её не следует автоматически подключать в браузере.
Для этого используется режим:
output: {
file: 'dist/app.js',
format: 'esm',
sourcemap: 'hidden'
}
В этом случае:
app.js
app.js.map
будут созданы, но комментарий
//# sourceMappingURL=
в итоговый файл не попадёт.
Такой подход часто используется в production-среде.
При сборке TypeScript-проектов обычно участвуют два источника карт:
Конфигурация TypeScript:
{
"compilerOptions": {
"sourceMap": true
}
}
Конфигурация Rollup:
export default {
input: 'src/main.ts',
output: {
file: 'dist/app.js',
format: 'esm',
sourcemap: true
}
};
Rollup способен объединять карты, созданные TypeScript, с собственными картами сборки.
В результате браузер будет показывать исходные файлы:
const message: string = 'Hello';
а не промежуточный JavaScript-код.
Аналогичная ситуация возникает при использовании Babel.
Плагин:
babel({
babelHelpers: 'bundled'
})
может генерировать собственные карты преобразований.
Пример цепочки обработки:
TypeScript
↓
Babel
↓
Rollup
Каждый этап вносит изменения в код.
Если source maps включены на всех этапах, Rollup сможет объединить их в единую карту, сохраняющую связь с первоначальными файлами.
Рассмотрим проект:
src/
├── main.js
├── api.js
├── auth.js
└── utils.js
После сборки появляется:
dist/
└── bundle.js
Без source maps браузер покажет только:
bundle.js
С включёнными картами в DevTools можно увидеть:
src/
├── main.js
├── api.js
├── auth.js
└── utils.js
Это значительно упрощает поиск ошибок.
После открытия вкладки Sources отображаются:
Page
└─ localhost
└─ dist
└─ bundle.js
При наличии source maps появляется дополнительная структура:
webpack://
rollup://
src/
или аналогичное дерево исходных файлов.
Разработчик получает возможность:
Предположим, существует модуль:
export function divide(a, b) {
return a / b;
}
Точка останова устанавливается непосредственно на строку:
return a / b;
Несмотря на то что браузер выполняет уже объединённый бандл, выполнение остановится именно в исходном модуле.
Это одно из главных преимуществ source maps.
Без карт исходников стек может выглядеть так:
app.min.js:1:5421
app.min.js:1:5518
app.min.js:1:5572
Подобный стек практически бесполезен.
С включёнными source maps браузер покажет:
src/api.js:14
src/auth.js:27
src/main.js:53
Поиск проблемы становится значительно проще.
Часто production-сборка использует плагин минификации.
Например:
import terser from '@rollup/plugin-terser';
Конфигурация:
export default {
input: 'src/main.js',
output: {
file: 'dist/app.min.js',
format: 'esm',
sourcemap: true
},
plugins: [
terser()
]
};
После минификации код может превратиться в:
function n(n,t){return n+t}
Однако source map сохранит соответствие исходному варианту:
function add(first, second) {
return first + second;
}
После сборки необходимо убедиться, что карта действительно работает.
Признаки корректной настройки:
.map;Если браузер показывает только итоговый бандл, карта либо отсутствует, либо настроена неправильно.
Rollup позволяет задавать собственное имя файла карты.
Пример:
output: {
file: 'dist/app.js',
sourcemap: 'app.debug.map'
}
Результат:
app.js
app.debug.map
Такой подход применяется редко, но может быть полезен в сложных процессах публикации.
По умолчанию карта может содержать содержимое исходных файлов.
Иногда это нежелательно.
Настройка:
output: {
file: 'dist/app.js',
format: 'esm',
sourcemap: true,
sourcemapExcludeSources: true
}
В результате:
Типичная конфигурация разработки:
export default {
input: 'src/main.js',
output: {
file: 'dist/bundle.js',
format: 'esm',
sourcemap: true
}
};
Особенности:
Для production используются различные стратегии.
Полностью отключить:
sourcemap: false
Создавать отдельный файл:
sourcemap: true
Создавать скрытую карту:
sourcemap: 'hidden'
Наиболее распространённым компромиссом считается вариант:
sourcemap: 'hidden'
Он позволяет анализировать ошибки и одновременно не публиковать ссылки на карты в браузере.
.mapПричина:
sourcemap: false
или отсутствие параметра вообще.
Решение:
sourcemap: true
Возможные причины:
.map;Обычно связано с тем, что один из инструментов в цепочке сборки не генерирует собственные карты.
Например:
TypeScript
↓
Babel
↓
Rollup
Если Babel не создаёт source maps, итоговая карта может содержать некорректные позиции.
Некоторые плагины могут нарушать цепочку преобразований.
Признаки:
В подобных случаях необходимо проверить поддержку source maps каждым используемым плагином.
Для современных проектов на Rollup обычно применяется следующая конфигурация:
export default {
input: 'src/main.js',
output: {
file: 'dist/app.js',
format: 'esm',
sourcemap: true
}
};
Для production-сборки:
export default {
input: 'src/main.js',
output: {
file: 'dist/app.min.js',
format: 'esm',
sourcemap: 'hidden'
}
};
Такой подход обеспечивает удобную локальную отладку, корректное отображение исходных модулей в браузере, полноценный анализ стеков вызовов и возможность расследования ошибок даже после минификации и оптимизации итогового бандла.