После сборки проекта исходный код обычно подвергается различным преобразованиям:
В результате код, который выполняется в браузере, может существенно отличаться от исходных файлов проекта. Это усложняет диагностику ошибок, анализ стека вызовов и использование точек останова.
Source map — специальный файл сопоставления, позволяющий браузерным инструментам разработки связать сгенерированный код с оригинальными исходниками. Благодаря этому DevTools отображает настоящий исходный код вместо результата сборки.
Без source maps ошибка может выглядеть следующим образом:
bundle.js:1 Uncaught TypeError: Cannot read properties of undefined
При этом весь код находится в одной минифицированной строке.
С source maps браузер показывает:
src/components/UserProfile.tsx:27
и позволяет сразу перейти к проблемному месту.
Esbuild предоставляет встроенную поддержку генерации source maps без подключения дополнительных инструментов.
Основные режимы работы:
| Режим | Описание |
|---|---|
| linked | Создается отдельный файл карты исходников |
| external | Создается внешний файл карты без ссылки в JS |
| inline | Карта встраивается непосредственно в итоговый файл |
| both | Генерируются встроенная и внешняя карты одновременно |
Простейшая настройка:
await esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
sourcemap: true,
outfile: 'dist/app.js'
});
Эквивалентно:
await esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
sourcemap: 'linked',
outfile: 'dist/app.js'
});
После сборки появятся файлы:
dist/
├─ app.js
└─ app.js.map
В конец JavaScript-файла автоматически добавляется ссылка:
//# sourceMappingURL=app.js.map
Браузер обнаруживает эту директиву и загружает карту исходников.
Наиболее распространённый вариант.
Конфигурация:
await esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
sourcemap: 'linked',
outfile: 'dist/app.js'
});
Результат:
app.js
app.js.map
Внутри app.js:
//# sourceMappingURL=app.js.map
Преимущества:
Недостатки:
Source map кодируется в формате Base64 и помещается непосредственно в итоговый файл.
Конфигурация:
await esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
sourcemap: 'inline',
outfile: 'dist/app.js'
});
Фрагмент результата:
//# sourceMappingURL=dat a:application/json;base64,...
Преимущества:
Недостатки:
Карта создаётся отдельно, но ссылка в JavaScript не добавляется.
await esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
sourcemap: 'external',
outfile: 'dist/app.js'
});
Результат:
app.js
app.js.map
Однако в файле app.js не будет строки:
//# sourceMappingURL=app.js.map
Подобный вариант используется в ситуациях, когда карта должна публиковаться отдельно или загружаться специальными инструментами мониторинга ошибок.
Одновременно создаются две версии карты.
await esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
sourcemap: 'both',
outfile: 'dist/app.js'
});
Создаются:
app.js
app.js.map
При этом карта присутствует и внутри JS-файла.
Esbuild позволяет генерировать карты напрямую из командной строки.
Включение source maps:
esbuild src/index.ts \
--bundle \
--sourcemap \
--outfile=dist/app.js
Явное указание режима:
esbuild src/index.ts \
--bundle \
--sourcemap=inline \
--outfile=dist/app.js
Другие варианты:
--sourcemap=linked
--sourcemap=external
--sourcemap=both
Одним из наиболее важных сценариев является отладка TypeScript-приложений.
Исходный код:
interface User {
name: string;
}
function printUser(user: User) {
console.log(user.name.toUpperCase());
}
printUser(undefined as any);
Сборка:
await esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
sourcemap: true,
outfile: 'dist/app.js'
});
При возникновении ошибки DevTools покажет:
src/index.ts
а не:
dist/app.js
Стек вызовов также будет ссылаться на TypeScript-файлы.
Source maps особенно полезны при работе с JSX и TSX.
Компонент:
export function UserCard({ user }) {
return (
<div>
{user.name.toUpperCase()}
</div>
);
}
После сборки JSX превращается в вызовы React API:
jsx("div", {
children: user.name.toUpperCase()
});
Без карты исходников DevTools показывает именно этот код.
С включёнными source maps отображается исходный TSX-файл:
{user.name.toUpperCase()}
Точки останова ставятся непосредственно в компоненте.
После генерации source maps DevTools позволяет устанавливать breakpoints в оригинальных файлах.
Пример:
function calculatePrice(price: number) {
return price * 1.2;
}
Вкладка Sources отображает:
src/
└─ utils/
└─ price.ts
Точка останова ставится на строку:
return price * 1.2;
Хотя в браузере исполняется уже собранный код.
Рассмотрим пример:
function stepOne() {
stepTwo();
}
function stepTwo() {
stepThree();
}
function stepThree() {
throw new Error('Failure');
}
stepOne();
Без source maps стек может выглядеть так:
app.js:1
app.js:1
app.js:1
С source maps:
stepThree (src/main.ts:9)
stepTwo (src/main.ts:5)
stepOne (src/main.ts:1)
Диагностика становится значительно проще.
В production-сборках часто используется минификация.
Настройка:
await esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
minify: true,
sourcemap: true,
outfile: 'dist/app.js'
});
Исходный код:
function calculateDiscount(price, discount) {
return price - price * discount;
}
После минификации:
function n(t,e){return t-t*e}
Без source maps анализ подобного кода крайне неудобен.
При наличии карты DevTools восстанавливает исходное представление файла.
По умолчанию esbuild помещает содержимое исходников непосредственно в source map.
Это позволяет браузеру показывать оригинальный код даже без доступа к исходным файлам.
Настройка:
await esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
sourcemap: true,
sourcesContent: true,
outfile: 'dist/app.js'
});
Фрагмент карты:
{
"sources": [
"../src/index.ts"
],
"sourcesContent": [
"const message = 'Hello';"
]
}
Иногда необходимо уменьшить размер карты.
await esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
sourcemap: true,
sourcesContent: false,
outfile: 'dist/app.js'
});
Теперь карта содержит только ссылки на исходники:
{
"sources": [
"../src/index.ts"
]
}
Браузеру потребуется доступ к самим файлам.
Esbuild поддерживает создание отдельных чанков.
Конфигурация:
await esbuild.build({
entryPoints: [
'src/home.ts',
'src/admin.ts'
],
bundle: true,
splitting: true,
format: 'esm',
sourcemap: true,
outdir: 'dist'
});
Результат:
dist/
├─ home.js
├─ home.js.map
├─ admin.js
├─ admin.js.map
├─ chunk-A.js
└─ chunk-A.js.map
Каждый чанк получает собственную карту исходников.
При переходе между модулями DevTools автоматически связывает их с соответствующими исходными файлами.
В Chrome DevTools открыть вкладку:
Sources
В дереве файлов должны отображаться исходники проекта:
webpack://
src/
или
localhost
└─ src
В зависимости от структуры сборки.
Дополнительно можно проверить вкладку:
Network
Файл карты должен успешно загружаться:
app.js.map
Status: 200
Если карта отсутствует:
Status: 404
браузер не сможет восстановить исходный код.
Причина:
sourcemap: false
или отсутствие параметра.
Исправление:
sourcemap: true
Сборка создаёт:
app.js.map
но сервер отдаёт только:
app.js
В результате DevTools сообщает:
Failed to load source map
Необходимо публиковать файл карты вместе со сборкой.
Пример структуры:
dist/app.js.map
src/index.ts
Если пути внутри карты повреждены или изменены сторонними инструментами, DevTools не сможет открыть оригинальные файлы.
Проверка содержимого:
{
"sources": [
"../src/index.ts"
]
}
Иногда после esbuild запускаются:
Если они не поддерживают корректное обновление source maps, связь между итоговым и исходным кодом нарушается.
Симптомы:
Для локальной разработки наиболее удобной считается конфигурация:
await esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
sourcemap: true,
minify: false,
outfile: 'dist/app.js'
});
Преимущества:
Для production-сборок распространён следующий подход:
await esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
minify: true,
sourcemap: 'external',
outfile: 'dist/app.js'
});
При такой конфигурации минифицированный код остаётся компактным, а карты исходников могут использоваться системами мониторинга ошибок, средствами анализа сбоев и браузерными инструментами разработчика без вмешательства в основной бандл.