При преобразовании исходного кода SWC способен генерировать Source Maps — специальные файлы, связывающие итоговый скомпилированный код с исходными файлами. Благодаря этому отладчики, браузеры и инструменты разработки могут показывать реальные строки и файлы проекта вместо результатов трансформации.
В процессе создания карт исходников важную роль играют два параметра:
sourceFileName
sourceRoot
Оба поля влияют на информацию о расположении исходного кода внутри Source Map, однако решают разные задачи.
Перед рассмотрением параметров необходимо понимать структуру карты исходников.
Типичный файл Source Map содержит данные следующего вида:
{
"version": 3,
"sources": [
"src/index.js"
],
"names": [],
"mappings": "AAAA..."
}
Поле sources содержит список исходных файлов,
использованных при генерации результирующего кода.
Например:
// Исходный файл
src/index.js
После компиляции:
// dist/index.js
console.log("Hello");
Source Map сообщает инструментам разработки, что данный код был получен из файла:
src/index.js
Именно на содержимое поля sources оказывают влияние
параметры sourceFileName и sourceRoot.
sourceFileName
Параметр sourceFileName позволяет явно указать имя или путь
исходного файла, который должен попасть в Source Map.
Пример конфигурации:
const { transformSync } = require("@swc/core");
const result = transformSync(code, {
sourceMaps: true,
sourceFileName: "src/app.js"
});
В этом случае SWC использует указанное значение при формировании карты исходников.
Результат:
{
"sources": [
"src/app.js"
]
}
Наиболее часто параметр применяется при работе через программный API.
Например, код читается из памяти:
const source = `
const value = 10;
console.log(value);
`;
Физического файла может не существовать.
Если не указать имя файла:
transformSync(source, {
sourceMaps: true
});
SWC не сможет корректно записать информацию о происхождении кода.
Поэтому задаётся:
transformSync(source, {
sourceMaps: true,
sourceFileName: "virtual/input.js"
});
Многие сборщики работают с файлами, которые существуют только в памяти.
Например:
sourceFileName: "memory://component.jsx"
Итоговая карта:
{
"sources": [
"memory://component.jsx"
]
}
Такой подход позволяет сохранять корректную навигацию в инструментах разработки.
Допустимо указание абсолютного пути:
sourceFileName: "/Users/project/src/index.js"
Результат:
{
"sources": [
"/Users/project/src/index.js"
]
}
Однако подобная практика обычно считается нежелательной.
Причины:
Чаще используются относительные пути:
sourceFileName: "src/index.js"
Большинство интеграций автоматически передают текущий файл:
sourceFileName: filename
Например:
transformSync(code, {
filename: "src/main.ts",
sourceMaps: true,
sourceFileName: "src/main.ts"
});
После генерации:
{
"sources": [
"src/main.ts"
]
}
filename и sourceFileName
Разработчики часто путают эти параметры.
filename
Используется самим компилятором:
filename: "src/index.ts"
SWC может применять его для:
sourceFileName
Используется исключительно для Source Maps:
sourceFileName: "src/index.ts"
Он определяет запись внутри поля:
{
"sources": [
"src/index.ts"
]
}
Эти параметры могут совпадать, но выполняют разные функции.
sourceRoot
Параметр sourceRoot задаёт общий базовый путь для всех
элементов массива sources.
Пример:
transformSync(code, {
sourceMaps: true,
sourceFileName: "app.js",
sourceRoot: "/src"
});
Полученная карта:
{
"sourceRoot": "/src",
"sources": [
"app.js"
]
}
Полный путь интерпретируется как:
/src/app.js
Предположим, имеется следующая конфигурация:
{
sourceRoot: "/project/src",
sourceFileName: "components/Button.jsx"
}
Source Map будет содержать:
{
"sourceRoot": "/project/src",
"sources": [
"components/Button.jsx"
]
}
Инструменты отладки объединяют значения:
/project/src/components/Button.jsx
Таким образом можно избежать повторения длинного префикса в каждом файле.
Пусть проект имеет структуру:
project/
├── src/
│ ├── index.js
│ ├── app.js
│ └── utils.js
└── dist/
Без sourceRoot карта может выглядеть так:
{
"sources": [
"src/index.js",
"src/app.js",
"src/utils.js"
]
}
С использованием общего корня:
{
"sourceRoot": "src",
"sources": [
"index.js",
"app.js",
"utils.js"
]
}
Логический результат остаётся тем же:
src/index.js
src/app.js
src/utils.js
Но карта становится компактнее.
sourceRoot
Поле допускает не только файловые пути.
Например:
sourceRoot: "https://cdn.example.com/sources/"
Карта:
{
"sourceRoot": "https://cdn.example.com/sources/",
"sources": [
"app.js"
]
}
Инструмент отладки будет искать исходник по адресу:
https://cdn.example.com/sources/app.js
Это особенно полезно при публикации карт исходников на удалённом сервере.
В крупных репозиториях может существовать множество пакетов:
packages/
├── core/
├── ui/
├── cli/
└── shared/
Для пакета UI можно задать:
sourceRoot: "/packages/ui/src",
sourceFileName: "Button.tsx"
Результат:
{
"sourceRoot": "/packages/ui/src",
"sources": [
"Button.tsx"
]
}
Отладчик получит путь:
/packages/ui/src/Button.tsx
sourceFileName и sourceRoot
| Характеристика | sourceFileName | sourceRoot |
|---|---|---|
| Указывает конкретный файл | Да | Нет |
| Указывает общий каталог | Нет | Да |
Попадает в sources
|
Да | Нет |
Попадает в sourceRoot Source Map
|
Нет | Да |
| Может использоваться отдельно | Да | Да |
| Влияет на путь к исходнику | Да | Да |
sourceFileName
Конфигурация:
{
sourceFileName: "src/index.js"
}
Source Map:
{
"sources": [
"src/index.js"
]
}
Полный путь:
src/index.js
sourceRoot
Конфигурация:
{
sourceRoot: "/src"
}
При наличии файла:
{
"sourceRoot": "/src",
"sources": [
"index.js"
]
}
Полный путь:
/src/index.js
Наиболее распространённый вариант:
transformSync(code, {
sourceMaps: true,
sourceRoot: "/project/src",
sourceFileName: "pages/home.js"
});
Source Map:
{
"sourceRoot": "/project/src",
"sources": [
"pages/home.js"
]
}
Итоговый путь:
/project/src/pages/home.js
sourceFileName
Нежелательный вариант:
sourceFileName:
"C:\\Users\\Alex\\Projects\\app\\src\\index.js"
Такая карта плохо переносится между машинами.
Предпочтительно:
sourceFileName: "src/index.js"
Неверная конфигурация:
sourceRoot: "src",
sourceFileName: "src/index.js"
Результат:
src/src/index.js
Путь оказывается продублированным.
Правильнее:
sourceRoot: "src",
sourceFileName: "index.js"
Конфигурация:
sourceRoot: "/frontend",
sourceFileName: "backend/server.js"
Формально карта будет корректной:
/frontend/backend/server.js
Однако реального файла может не существовать.
Важно, чтобы оба параметра описывали одну и ту же структуру проекта.
Обычно достаточно:
sourceFileName: "src/index.js"
Без указания sourceRoot.
Практичным решением становится разделение:
sourceRoot: "src",
sourceFileName: "components/Button.tsx"
Рекомендуется использовать относительные пути:
sourceRoot: "",
sourceFileName: "src/index.ts"
Это обеспечивает лучшую переносимость опубликованных пакетов.
Подходит конфигурация:
sourceRoot: "https://cdn.example.com/source/",
sourceFileName: "index.js"
Позволяющая инструментам разработки автоматически находить оригинальные файлы через сеть.
Последовательность формирования пути к исходнику выглядит следующим образом:
sourceRoot
+
sourceFileName
=
Полный путь к исходному файлу
Пример:
sourceRoot = /project/src
sourceFileName = utils/math.js
Результат:
/project/src/utils/math.js
Именно этот путь будет использоваться браузером, отладчиком, IDE и другими инструментами, работающими с картами исходников, сгенерированными SWC.