Типы source maps: inline, external, both

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

Source maps решают эту проблему, сохраняя соответствие между исходным и результирующим кодом. Инструменты разработчика используют карты исходников для восстановления оригинальной структуры проекта.

SWC поддерживает несколько вариантов генерации source maps:

  • inline — карта встраивается непосредственно в выходной файл;
  • external — карта сохраняется в отдельном файле;
  • both — одновременно создаются встроенная и внешняя карты.

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


Параметр sourceMaps

В конфигурации SWC настройка выполняется через свойство sourceMaps.

Пример:

{
  "sourceMaps": true
}

В зависимости от значения SWC генерирует карты исходников различным способом.

Поддерживаются следующие варианты:

{
  "sourceMaps": true
}

или

{
  "sourceMaps": "inline"
}

или

{
  "sourceMaps": "both"
}

В некоторых версиях инструментов и обёрток над SWC значение true соответствует генерации внешней карты.


External Source Maps

Общая характеристика

Режим external создаёт отдельный файл карты исходников рядом со скомпилированным файлом.

Исходный файл:

const message: string = "Hello";
console.log(message);

После компиляции:

const message = "Hello"; console.log(message);

//

Дополнительно создаётся файл:

app.js.map

Структура проекта:

dist/
├── app.js
└── app.js.map

Содержимое карты

Файл карты представляет собой JSON-документ.

Пример упрощённой структуры:

{
  "version": 3,
  "sources": ["src/app.ts"],
  "names": [],
  "mappings": "...",
  "file": "app.js"
}

Основные поля:

Поле Назначение
version Версия формата source map
sources Список исходных файлов
names Используемые идентификаторы
mappings Таблица соответствий
file Имя результирующего файла

Конфигурация

Файл .swcrc:

{
  "sourceMaps": true
}

или

{
  "sourceMaps": "external"
}

Если используется программный API:

const { transform } = require("@swc/core");

const result = await transform(code, {
  sourceMaps: true
});

SWC вернёт объект:

{
  code: "...",
  map: "..."
}

Полученную карту можно сохранить отдельно.


Преимущества external

Небольшой размер JavaScript-файла

В карту может входить значительный объём информации.

Если она хранится отдельно, основной файл остаётся компактнее:

bundle.js
bundle.js.map

Вместо:

bundle.js (включает карту внутри)

Удобство публикации

Можно публиковать только Jav * aScript:

dist/
└── bundle.js

или вместе с картами:

dist/
├── bundle.js
└── bundle.js.map

Карты легко исключаются из production-сборок.


Лучшее кэширование

Браузер может отдельно кэшировать:

bundle.js
bundle.js.map

Изменение карты не обязательно приводит к повторной загрузке основного файла.


Недостатки external

Дополнительные файлы

Большие проекты могут содержать множество карт:

dist/
├── app.js
├── app.js.map
├── admin.js
├── admin.js.map
├── vendor.js
└── vendor.js.map

Количество файлов увеличивается почти вдвое.


Необходимость правильного размещения

Если файл карты отсутствует:

app.js

но отсутствует:

app.js.map

инструменты разработчика не смогут восстановить исходный код.


Дополнительные HTTP-запросы

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

GET /app.js.map

Для крупных приложений это может слегка увеличивать сетевую активность.


Inline Source Maps

Общая характеристика

В режиме inline карта исходников встраивается непосредственно в JavaScript-файл.

Отдельный файл .map не создаётся.

Пример:

console.log("Hello");

//# sourceMappingURL=dat a:application/json;base64,...

После комментария располагается длинная строка Base64, содержащая JSON-карту.


Принцип работы

SWC создаёт обычную карту:

{
  "version": 3,
  "sources": ["src/index.ts"]
}

Затем:

  1. сериализует JSON;
  2. кодирует его в Base64;
  3. вставляет в конец файла.

Получается единый файл:

bundle.js

внутри которого уже содержится вся необходимая информация.


Конфигурация

Файл .swcrc:

{
  "sourceMaps": "inline"
}

Программный API:

const result = await transform(code, {
  sourceMaps: "inline"
});

Пример результата

Исходный код:

export const sum = (a: number, b: number) => a + b;

Скомпилированный файл:

export const sum = (a, b) => a + b;

//# sourceMappingURL=dat a:application/json;base64,eyJ2ZXJzaW9uIjoz...

Весь отладочный контекст находится внутри одного файла.


Преимущества inline

Один файл

Не требуется хранить:

app.js.map

или следить за корректностью путей.

Достаточно:

app.js

Простота локальной разработки

При запуске локального сервера отсутствует риск потерять файл карты.

Например:

src/
dist/

После сборки всё необходимое уже находится внутри JavaScript-файла.


Удобство временных сборок

Полезно для:

  • тестовых стендов;
  • локальных запусков;
  • прототипов;
  • демонстрационных проектов.

Недостатки inline

Существенное увеличение размера файла

Карта может занимать объём, сопоставимый с кодом.

Например:

bundle.js      800 KB
source map     600 KB

После встраивания:

bundle.js      1.4 MB

Сложности с кэшированием

Код и карта становятся единым объектом.

Даже небольшое изменение карты приводит к изменению всего файла:

bundle.js

Неудобство для production

Пользователи загружают вместе с кодом всю карту исходников.

Это приводит к:

  • увеличению трафика;
  • росту времени загрузки;
  • раскрытию структуры исходного проекта.

Both Source Maps

Общая характеристика

Режим both объединяет два предыдущих подхода.

SWC:

  1. создаёт внешний файл карты;
  2. встраивает карту в JavaScript.

Получаются одновременно:

bundle.js
bundle.js.map

и встроенная Base64-карта внутри bundle.js.


Конфигурация

{
  "sourceMaps": "both"
}

Программный API:

const result = await transform(code, {
  sourceMaps: "both"
});

Структура результата

dist/
├── bundle.js
└── bundle.js.map

Конец файла:

//# sourceMappingURL=dat a:application/json;base64,...

При этом карта также существует отдельно:

bundle.js.map

Когда применяется режим both

Чаще всего он используется в сложных сборочных конвейерах.

Например:

TypeScript
    ↓
SWC
    ↓
Webpack
    ↓
Production bundle

Некоторые инструменты могут читать встроенную карту, тогда как другие работают с отдельными файлами.

Наличие обоих вариантов обеспечивает максимальную совместимость.


Преимущества both

Максимальная совместимость

Поддерживаются сценарии, где различные инструменты ожидают разные типы карт.

Например:

  • браузер использует встроенную карту;
  • система мониторинга ошибок использует внешний файл.

Гибкость обработки

Карту можно:

  • передавать в следующую стадию сборки;
  • публиковать отдельно;
  • использовать локально.

Удобство сложных пайплайнов

При нескольких этапах трансформации карты часто объединяются:

TypeScript
   ↓
SWC
   ↓
Babel
   ↓
Webpack

Наличие обеих версий снижает вероятность потери информации о соответствии исходников.


Недостатки both

Максимальный расход места

Фактически карта хранится дважды:

bundle.js
bundle.js.map

Часть данных дублируется.


Более сложное сопровождение

При публикации необходимо учитывать:

bundle.js
bundle.js.map

а также наличие встроенной карты внутри файла.


Сравнение режимов

Характеристика Inline External Both
Отдельный .map файл Нет Да Да
Карта внутри JS Да Нет Да
Размер JS-файла Большой Минимальный Большой
Простота локальной разработки Высокая Средняя Высокая
Подходит для production Редко Да Иногда
Совместимость инструментов Средняя Высокая Максимальная
Дополнительные запросы к карте Нет Да Возможны

Использование вместе с minify

Source maps особенно важны при минификации.

Конфигурация:

{
  "minify": true,
  "sourceMaps": true
}

Исходный код:

function calculatePrice(price, tax) {
    return price + tax;
}

После минификации:

function n(n,t){return n+t}

Без карты исходников отладка становится затруднительной.

Source map позволяет инструментам разработчика восстановить первоначальный вид:

function calculatePrice(price, tax) {
    return price + tax;
}

несмотря на выполнение минифицированного варианта.


Использование вместе с TypeScript

SWC активно применяется как быстрый компилятор TypeScript.

Конфигурация:

{
  "jsc": {
    "parser": {
      "syntax": "typescript"
    }
  },
  "sourceMaps": true
}

Исходный файл:

interface User {
    name: string;
}

const user: User = {
    name: "Alex"
};

После компиляции типы удаляются:

const user = {
    name: "Alex"
};

Карта сохраняет связь между TypeScript-файлом и итоговым JavaScript, что позволяет видеть исходный код при отладке.


Практические рекомендации

Для локальной разработки

Наиболее распространённый вариант:

{
  "sourceMaps": "inline"
}

или

{
  "sourceMaps": true
}

если сборщик автоматически создаёт внешние карты.

Главная цель — максимальное удобство отладки.


Для production-среды

Наиболее распространённая схема:

{
  "sourceMaps": true
}

с публикацией:

bundle.js
bundle.js.map

или с загрузкой карт только в систему мониторинга ошибок.


Для библиотек

Часто используется внешний вариант:

{
  "sourceMaps": true
}

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


Для многоступенчатых сборок

Подходящим решением может быть:

{
  "sourceMaps": "both"
}

когда несколько инструментов последовательно обрабатывают один и тот же код и требуется максимальная сохранность информации об исходниках.