Перечень логических операторов drop

Параметр drop в Esbuild предназначен для удаления определённых конструкций из исходного кода ещё на этапе сборки. Это позволяет уменьшить размер итогового бандла, устранить лишние побочные действия и подготовить приложение к производственной эксплуатации.

Опция работает на уровне синтаксического анализа и удаляет заранее определённые конструкции независимо от того, используются ли они в дальнейшем коде. В отличие от механизмов удаления неиспользуемого кода (Tree Shaking), drop выполняет принудительное исключение указанных элементов.

Общий вид настройки:

await esbuild.build({
  entryPoints: ['src/index.js'],
  bundle: true,
  drop: ['console']
})

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


Перечень поддерживаемых значений

На текущий момент Esbuild поддерживает два значения для параметра drop:

drop: ['console']

и

drop: ['debugger']

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

drop: ['console', 'debugger']

Оператор console

Назначение

Значение console удаляет вызовы методов объекта console.

Пример исходного кода:

function calculate(a, b) {
  console.log('Начало вычислений')

  const result = a + b

  console.info('Результат:', result)

  return result
}

После сборки:

function calculate(a, b) {
  const result = a + b
  return result
}

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


Какие методы удаляются

Удаляются все обращения к стандартному объекту консоли:

console.log()
console.info()
console.warn()
console.error()
console.debug()
console.trace()
console.dir()
console.table()

Пример:

console.log('message')
console.error('error')
console.warn('warning')

После обработки:

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


Влияние на размер бандла

Во многих проектах количество диагностических сообщений может исчисляться сотнями или тысячами строк.

Например:

console.log(user)
console.log(settings)
console.log(response)
console.log(cache)
console.log(state)

После удаления:

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


Удаление вызовов внутри функций

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

function processData(data) {
  console.log('processing')

  return data.map(item => {
    console.log(item)
    return item * 2
  })
}

Результат:

function processData(data) {
  return data.map(item => {
    return item * 2
  })
}

Удаление выполняется независимо от уровня вложенности.


Особенности вычисления аргументов

Необходимо учитывать важную особенность. При использовании drop: ['console'] удаляется весь вызов консоли вместе с вычислением его аргументов.

Пример:

console.log(expensiveOperation())

После сборки:

Функция expensiveOperation() также не будет вызвана.

Это отличается от ситуаций, когда логирование отключается вручную:

if (DEBUG) {
  console.log(expensiveOperation())
}

В таком случае результат зависит от последующей оптимизации и удаления мёртвого кода.


Побочные эффекты

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

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

console.log(updateDatabase())

После применения:

Функция updateDatabase() больше не будет выполняться.

Другой пример:

console.log(counter++)

После удаления:

Инкремент также исчезнет.

По этой причине вызовы с важной бизнес-логикой не рекомендуется помещать внутрь аргументов методов консоли.


Типичный сценарий использования

Конфигурация для production-сборки:

await esbuild.build({
  entryPoints: ['src/index.js'],
  bundle: true,
  minify: true,
  drop: ['console']
})

Для режима разработки параметр обычно отключается:

await esbuild.build({
  entryPoints: ['src/index.js'],
  bundle: true
})

Оператор debugger

Назначение

Значение debugger удаляет все инструкции debugger.

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

function login(user) {
  debugger

  validate(user)
}

После сборки:

function login(user) {
  validate(user)
}

Для чего используется debugger

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

Пример:

function calculatePrice(product) {
  debugger

  return product.price * product.quantity
}

Во время отладки выполнение будет остановлено на этой строке.

После завершения разработки подобные инструкции обычно удаляются.


Автоматическое удаление

Вместо ручного поиска можно использовать:

await esbuild.build({
  entryPoints: ['src/index.js'],
  bundle: true,
  drop: ['debugger']
})

Любые инструкции:

debugger

будут исключены из выходного файла.


Удаление внутри условных конструкций

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

if (user.isAdmin) {
  debugger
  openAdminPanel()
}

После сборки:

if (user.isAdmin) {
  openAdminPanel()
}

Удаление внутри циклов

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

for (const item of items) {
  debugger
  process(item)
}

После обработки:

for (const item of items) {
  process(item)
}

Удаление внутри вложенных функций

Пример:

function outer() {
  function inner() {
    debugger
    return true
  }

  return inner()
}

После сборки:

function outer() {
  function inner() {
    return true
  }

  return inner()
}

Одновременное использование нескольких операторов

Наиболее распространённый вариант production-конфигурации:

await esbuild.build({
  entryPoints: ['src/index.js'],
  bundle: true,
  minify: true,
  drop: ['console', 'debugger']
})

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

console.log('application start')

debugger

startApplication()

Результат:

startApplication()

Использование через CLI

Удаление консоли:

esbuild app.js --bundle --drop:console

Удаление отладчиков:

esbuild app.js --bundle --drop:debugger

Одновременное удаление:

esbuild app.js --bundle --drop:console --drop:debugger

Использование через JavaScript API

Удаление только консоли:

import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['src/index.js'],
  outfile: 'dist/app.js',
  drop: ['console']
})

Удаление только отладчиков:

import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['src/index.js'],
  outfile: 'dist/app.js',
  drop: ['debugger']
})

Удаление обеих конструкций:

import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['src/index.js'],
  outfile: 'dist/app.js',
  drop: ['console', 'debugger']
})

Совместное применение с минификацией

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

Пример:

await esbuild.build({
  entryPoints: ['src/index.js'],
  bundle: true,
  minify: true,
  drop: ['console', 'debugger']
})

Последовательность обработки обычно выглядит следующим образом:

  1. Разбор исходного кода.
  2. Удаление инструкций из списка drop.
  3. Выполнение оптимизаций.
  4. Минификация результата.
  5. Генерация итогового бандла.

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


Отличия drop от define

Иногда параметры drop и define решают схожие задачи, но работают по-разному.

Пример с define:

if (DEBUG) {
  console.log('message')
}

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

define: {
  DEBUG: 'false'
}

После оптимизации код может быть удалён как недостижимый.

Параметр drop действует иначе:

console.log('message')

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

drop: ['console']

Вызов удаляется без каких-либо дополнительных условий.


Отличия drop от Tree Shaking

Tree Shaking анализирует импортируемые сущности и устраняет неиспользуемый код.

Пример:

import { used, unused } from './utils.js'

used()

Функция unused() может быть исключена из бандла.

Параметр drop не занимается анализом зависимостей и предназначен исключительно для удаления заранее определённых конструкций:

console.log('debug')
debugger

Они будут удалены независимо от того, используются ли остальные части программы.


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

Для production-сборок

drop: ['console', 'debugger']

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

Для development-сборок

drop: []

Сохраняет сообщения логирования и точки останова для диагностики.

Избегание побочных эффектов

Нежелательно писать:

console.log(saveUser())

Безопаснее разделять действия:

saveUser()
console.log('User saved')

Тогда удаление консоли не изменит поведение программы.

Использование в CI/CD

Часто параметр включается только для производственных сборок:

const production = process.env.NODE_ENV === 'production'

await esbuild.build({
  entryPoints: ['src/index.js'],
  bundle: true,
  drop: production
    ? ['console', 'debugger']
    : []
})

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