Обработка аргументов командной строки в скрипте

Источник аргументов и базовая модель обработки

В среде Node.js входные аргументы командной строки доступны через массив process.argv. Его структура фиксирована:

  • process.argv[0] — путь к интерпретатору Node.js
  • process.argv[1] — путь к выполняемому файлу скрипта
  • начиная с process.argv[2] — пользовательские аргументы

При интеграции с esbuild этот механизм используется для построения собственных CLI-обёрток над сборкой, генерации конфигураций и динамического управления параметрами бандлинга.

console.log(process.argv);

При запуске:

node build.js src/index.js --minify --outdir=dist

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


Разделение позиционных и именованных аргументов

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

  • позиционные — пути входных файлов, режимы сборки
  • флаги — параметры конфигурации (--minify, --watch, --outdir)

Базовая схема разбиения:

const rawArgs = process.argv.slice(2);

const positional = [];
const flags = {};

for (const arg of rawArgs) {
  if (arg.startsWith('--')) {
    const [key, value] = arg.replace('--', '').split('=');
    flags[key] = value ?? true;
  } else {
    positional.push(arg);
  }
}

Такой подход позволяет подготовить данные для передачи в API esbuild.


Приведение CLI-аргументов к конфигурации esbuild

Библиотека esbuild принимает конфигурацию в виде объекта:

import esbuild from 'esbuild';

Преобразование аргументов CLI в конфигурацию:

const config = {
  entryPoints: positional.length ? positional : ['src/index.js'],
  bundle: true,
  platform: flags.platform || 'browser',
  minify: Boolean(flags.minify),
  sourcemap: flags.sourcemap === 'true' || flags.sourcemap === true,
  outdir: flags.outdir || 'dist'
};

Так формируется слой адаптации между CLI и API.


Обработка булевых флагов

CLI-аргументы часто не содержат значений. Например:

--minify
--watch

В таких случаях значение интерпретируется как true.

function toBoolean(value) {
  if (value === undefined) return true;
  if (value === 'true') return true;
  if (value === 'false') return false;
  return Boolean(value);
}

Использование:

minify: toBoolean(flags.minify)

Поддержка числовых параметров

Некоторые параметры esbuild требуют числовых значений, например logLevel, chunkNames (в сторонних обёртках), лимиты и кастомные настройки.

Преобразование:

function toNumber(value, fallback = 0) {
  const n = Number(value);
  return Number.isNaN(n) ? fallback : n;
}

Пример:

logLevel: toNumber(flags.logLevel, 2)

Поддержка списков значений

Параметры CLI могут содержать списки через разделители:

--external:react,react-dom

Разбор:

function toList(value) {
  if (!value) return [];
  return value.split(',').map(s => s.trim()).filter(Boolean);
}

Использование в esbuild:

external: toList(flags.external)

Обработка конфликтующих аргументов

CLI часто допускает несовместимые комбинации:

  • --watch и --minify
  • --outfile и --outdir
  • --bundle и одиночная компиляция без входной точки

Логика проверки:

function validate(config) {
  if (config.outfile && config.outdir) {
    throw new Error('Нельзя использовать outfile и outdir одновременно');
  }

  if (config.watch && config.minify) {
    throw new Error('watch несовместим с minify в данном режиме');
  }
}

Переопределение конфигурации через CLI

Структура приоритетов обычно следующая:

  1. значения по умолчанию
  2. конфигурационный файл (build.config.js)
  3. CLI-аргументы

Слияние:

const finalConfig = {
  ...defaultConfig,
  ...fileConfig,
  ...configFromCLI
};

Для вложенных объектов используется глубокое слияние:

function deepMerge(a, b) {
  const result = { ...a };
  for (const key in b) {
    if (typeof b[key] === 'object' && !Array.isArray(b[key])) {
      result[key] = deepMerge(a[key] || {}, b[key]);
    } else {
      result[key] = b[key];
    }
  }
  return result;
}

Поддержка переменных окружения

CLI-параметры часто комбинируются с process.env:

const config = {
  minify: toBoolean(flags.minify ?? process.env.MINIFY),
  sourcemap: toBoolean(flags.sourcemap ?? process.env.SOURCEMAP)
};

Приоритет обычно ниже CLI, но выше дефолтов.


Обработка режима watch

Режим watch в esbuild требует отдельной логики запуска:

if (config.watch) {
  const ctx = await esbuild.context(config);
  await ctx.watch();
} else {
  await esbuild.build(config);
}

CLI-аргумент:

--watch

Флаг переключает поведение с однократной сборки на постоянное отслеживание файлов.


Работа с входными файлами

Позиционные аргументы часто интерпретируются как entry points:

config.entryPoints = positional.length
  ? positional
  : ['src/index.js'];

Поддержка нескольких входов:

node build.js src/a.js src/b.js

Алиасы и сокращения флагов

Для удобства CLI часто поддерживает короткие формы:

  • -w--watch
  • -m--minify
  • -o--outdir

Парсинг:

const aliasMap = {
  w: 'watch',
  m: 'minify',
  o: 'outdir'
};

function expandFlags(args) {
  const result = [];

  for (const arg of args) {
    if (arg.startsWith('-') && !arg.startsWith('--')) {
      const chars = arg.slice(1).split('');
      for (const c of chars) {
        result.push(`--${aliasMap[c] || c}`);
      }
    } else {
      result.push(arg);
    }
  }

  return result;
}

Обработка ошибок аргументов

Неверные аргументы должны выявляться до запуска esbuild:

function assertValidFlags(flags) {
  const allowed = [
    'minify',
    'watch',
    'outdir',
    'outfile',
    'platform',
    'sourcemap'
  ];

  for (const key of Object.keys(flags)) {
    if (!allowed.includes(key)) {
      throw new Error(`Неизвестный флаг: ${key}`);
    }
  }
}

Интеграция с esbuild API

Финальный этап — передача сформированной конфигурации:

import esbuild from 'esbuild';

async function run() {
  const rawArgs = process.argv.slice(2);
  const expanded = expandFlags(rawArgs);
  const { positional, flags } = parse(expanded);

  const config = buildConfig(positional, flags);

  validate(config);
  await esbuild.build(config);
}

run();

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

Чистая архитектура обработки CLI обычно разделяется на слои:

  • слой чтения (process.argv)
  • слой нормализации (alias, типизация)
  • слой валидации
  • слой маппинга в esbuild config

Такое разделение позволяет масштабировать CLI без привязки к API esbuild.


Особенности обработки нестандартных значений

Некоторые значения CLI могут содержать специальные символы:

--define:process.env.NODE_ENV='"production"'

Обработка требует сохранения экранирования:

function stripQuotes(value) {
  return value.replace(/^['"]|['"]$/g, '');
}

Поток выполнения CLI-скрипта

Типичный цикл:

  1. чтение process.argv
  2. нормализация аргументов
  3. разбор на флаги и позиции
  4. преобразование типов
  5. валидация
  6. формирование конфигурации esbuild
  7. запуск esbuild.build или esbuild.context

Масштабирование CLI-логики

При усложнении сборочных сценариев добавляются:

  • профили конфигураций (--profile=dev|prod)
  • условные сборки
  • динамическая загрузка плагинов
  • генерация конфигурации на основе файловой системы

CLI-слой остаётся точкой входа, изолирующей esbuild от внешних источников данных.