Loader empty

В системе загрузчиков esbuild empty представляет собой специализированный режим обработки модулей, при котором содержимое файла полностью исключается из результирующего бандла. Такой модуль сохраняет только факт импорта, но не вносит ни кода, ни побочных эффектов, ни экспортов.

Поведение loader empty сводится к следующему: любой файл, к которому он применён, интерпретируется как пустой модуль. В процессе сборки его содержимое игнорируется, а на выходе формируется модуль с нулевой семантической нагрузкой.


Семантика пустого загрузчика

При применении loader: "empty" esbuild выполняет следующие трансформации:

  • исходный файл полностью исключается из анализа AST;
  • импорты из такого файла считаются валидными на этапе разрешения модулей;
  • экспортируемые значения заменяются на пустую структуру;
  • побочные эффекты внутри файла не исполняются и не учитываются;
  • размер итогового бандла уменьшается за счёт исключения содержимого.

Фактически модуль превращается в эквивалент:

export {}

либо полностью исчезает при дальнейшей оптимизации и tree-shaking, если на него нет ссылок.


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

Назначение загрузчика выполняется через поле loader в конфигурации сборки.

import * as esbuild from 'esbuild';

esbuild.build({
  entryPoints: ['src/index.js'],
  bundle: true,
  outfile: 'dist/bundle.js',
  loader: {
    '.txt': 'empty',
    '.md': 'empty'
  }
});

В данном примере все файлы с расширениями .txt и .md будут полностью исключены из бандла.


Принцип работы при разрешении модулей

При встрече импорта:

import doc from './manual.txt';

и наличии конфигурации:

loader: {
  '.txt': 'empty'
}

esbuild:

  1. находит файл manual.txt;
  2. применяет загрузчик empty;
  3. не парсит содержимое файла;
  4. заменяет модуль на пустую сущность;
  5. продолжает сборку без включения содержимого.

Если импорт предполагает использование значения:

console.log(doc);

значение doc будет undefined либо оптимизировано до пустого объекта в зависимости от контекста сборки и настроек minify/tree-shaking.


Отличие от других загрузчиков

text

Loader text преобразует файл в строку:

loader: { '.txt': 'text' }
import doc from './file.txt';
// doc = "содержимое файла"

file

Loader file копирует файл в выходной каталог и возвращает URL:

loader: { '.png': 'file' }

empty

Loader empty полностью устраняет содержимое:

loader: { '.txt': 'empty' }

Сравнение поведения:

Loader Результат
text строка с содержимым
file путь к файлу
empty отсутствие содержимого

Влияние на tree-shaking

Loader empty усиливает эффект tree-shaking, так как модуль становится нейтральным узлом графа зависимостей.

Если модуль содержит только побочные эффекты:

// debug.txt (условно)
console.log("init");

и применяется:

loader: { '.txt': 'empty' }

то:

  • код console.log не попадёт в бандл;
  • модуль может быть полностью удалён;
  • цепочки импортов, зависящие только от него, схлопываются.

Поведение с экспортами

При наличии кода вида:

// config.json (условно)
export const value = 42;

и применении:

loader: { '.json': 'empty' }

все экспортируемые значения становятся недоступными. Импорт:

import { value } from './config.json';

приведёт к тому, что value будет неопределённым или удалённым на этапе оптимизации.

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


Использование в условиях окружений

Loader empty часто применяется для условного исключения модулей:

esbuild.build({
  entryPoints: ['src/app.js'],
  bundle: true,
  outfile: 'dist/app.js',
  loader: {
    '.mock.js': 'empty'
  }
});

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

src/
  api.js
  api.mock.js

Импорт:

import api from './api.mock.js';

Результат: модуль api.mock.js полностью исключается.


Совместимость с плагинами

Плагины esbuild, работающие на уровне onResolve и onLoad, могут изменять поведение empty.

Типичный сценарий:

plugins: [
  {
    name: 'conditional-empty',
    setup(build) {
      build.onLoad({ filter: /\.debug\.js$/ }, () => {
        return {
          contents: '',
          loader: 'js'
        };
      });
    }
  }
]

В этом случае loader empty может быть переопределён логикой плагина, если он срабатывает раньше в цепочке обработки.


Влияние на анализ зависимостей

Graph traversal в esbuild включает модули с empty loader, но без анализа содержимого.

Это означает:

  • зависимости внутри файла не извлекаются;
  • импортируемые символы не резолвятся;
  • циклические зависимости через такие модули обрываются.

Пример:

// a.js
import './b.txt';
export const a = 1;

// b.txt (empty loader)
import { a } from './a.js';

Файл b.txt не будет участвовать в графе импортов, несмотря на синтаксически валидный импорт внутри.


Минификация и удаление следов

При включённой минификации (minify: true) пустые модули:

  • удаляются из результирующего графа;
  • не оставляют IIFE или обёрток;
  • не создают runtime-зависимостей.

В результате:

import './noop.txt';
console.log('ok');

может трансформироваться в:

console.log("ok");

Ограничения и особенности

Loader empty имеет ряд устойчивых ограничений:

  • не может сохранять частичное содержимое файла;
  • не поддерживает трансформации формата;
  • не предназначен для runtime-логики;
  • не предоставляет доступ к исходному содержимому;
  • полностью исключает анализ AST.

Также важно учитывать, что:

  • TypeScript-типизация не может опираться на значения из таких модулей;
  • декларации типов становятся бессмысленными при отсутствии содержимого;
  • инструменты линтинга могут фиксировать «пустые импорты».

Роль в архитектуре сборки

Loader empty используется как механизм управления видимостью модулей в сборке. Он позволяет:

  • отключать неиспользуемые форматы файлов без удаления из проекта;
  • создавать конфигурационные «заглушки»;
  • управлять окружениями без условного кода в исходниках;
  • снижать размер бандла за счёт исключения вспомогательных ресурсов.

В крупных проектах он часто выступает как часть стратегии разделения окружений:

  • production → реальные модули;
  • test → часть модулей пустые;
  • legacy → устаревшие зависимости нейтрализуются.

Поведение при кешировании сборки

esbuild активно кеширует результаты обработки модулей. Для empty loader это означает:

  • файл после первой обработки сохраняется как пустой результат;
  • повторные сборки ускоряются за счёт отсутствия парсинга;
  • изменения содержимого файла не влияют на результат, пока сохраняется правило loader.

Это создаёт стабильное поведение в инкрементальных сборках.


Взаимодействие с TypeScript и JSX

При использовании TypeScript:

import config from './config.txt';

и loader empty, тип config фактически теряет значение. TypeScript может интерпретировать его как any или undefined, в зависимости от деклараций.

В JSX-сборках loader empty может использоваться для исключения вспомогательных файлов:

loader: {
  '.stories.js': 'empty'
}

что позволяет удалять storybook-данные из production-бандла.


Поведение в ESM и CJS

В ESM:

import x from './file.txt';

результат — пустой модуль без экспорта.

В CommonJS:

const x = require('./file.txt');

возвращаемое значение обычно undefined или пустой объект, так как esbuild генерирует совместимый shim без содержимого модуля.


Практическое значение в сборочных пайплайнах

Loader empty используется как инструмент фильтрации на уровне сборки, а не на уровне кода. Его основная ценность проявляется в:

  • исключении документационных файлов;
  • удалении тестовых модулей;
  • отключении debug-логики;
  • замене тяжёлых ресурсов заглушками;
  • управлении окружениями без runtime-условий.

Он формирует предсказуемый граф зависимостей, где часть узлов существует только формально, не влияя на результат выполнения.