Поле sourceFileName и sourceRoot

При преобразовании исходного кода SWC способен генерировать Source Maps — специальные файлы, связывающие итоговый скомпилированный код с исходными файлами. Благодаря этому отладчики, браузеры и инструменты разработки могут показывать реальные строки и файлы проекта вместо результатов трансформации.

В процессе создания карт исходников важную роль играют два параметра:

  • sourceFileName
  • sourceRoot

Оба поля влияют на информацию о расположении исходного кода внутри Source Map, однако решают разные задачи.


Что такое 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

Но карта становится компактнее.


Использование URL в 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"

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


Для CDN и удалённых исходников

Подходит конфигурация:

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.