CLI-приложения (Command Line Interface) отличаются от обычных браузерных приложений тем, что запускаются непосредственно из терминала и часто распространяются через пакетный менеджер npm. При сборке таких программ возникает дополнительная задача: необходимо сохранить возможность запуска файла как исполняемого скрипта операционной системы.
Типичный исполняемый файл CLI-инструмента содержит специальную строку в самом начале:
#!/usr/bin/env node
Эта строка называется shebang. Она сообщает Unix-подобным системам, какой интерпретатор должен использоваться для выполнения файла.
При использовании Esbuild необходимо учитывать особенности обработки
shebang и понимать механизм добавления служебного содержимого через
параметр banner.
Shebang представляет собой первую строку файла, начинающуюся с
символов #!.
Пример:
#!/usr/bin/env node
console.log('CLI запущен');
При запуске файла:
./cli.js
операционная система считывает первую строку и выполняет:
node cli.js
Использование конструкции env считается наиболее
переносимым вариантом:
#!/usr/bin/env node
Вместо жёсткой привязки к пути:
#!/usr/bin/node
система самостоятельно найдёт установленный интерпретатор Node.js.
Типичная структура проекта выглядит следующим образом:
project/
├── src/
│ └── cli.js
├── package.json
└── esbuild.config.js
Файл CLI:
#!/usr/bin/env node
console.log('Hello CLI');
В package.json обычно присутствует секция:
{
"name": "my-cli",
"bin": {
"my-cli": "./dist/cli.js"
}
}
После установки пакета команда становится доступной глобально:
my-cli
Одним из преимуществ Esbuild является автоматическая обработка shebang.
Исходный файл:
#!/usr/bin/env node
import fs from 'fs';
console.log(fs.existsSync('.'));
Конфигурация:
import { build } from 'esbuild';
await build({
entryPoints: ['src/cli.js'],
bundle: true,
outfile: 'dist/cli.js',
platform: 'node'
});
После сборки строка shebang сохраняется:
#!/usr/bin/env node
"use strict";
// код бандла
Это позволяет продолжать использовать результат как полноценный исполняемый файл.
После сборки можно проверить начало файла:
head -n 5 dist/cli.js
Ожидаемый результат:
#!/usr/bin/env node
"use strict";
Если shebang отсутствует, запуск через терминал напрямую может перестать работать.
На Unix-подобных системах одного shebang недостаточно.
Файлу необходимо выдать права на выполнение:
chmod +x dist/cli.js
После этого запуск становится возможен:
./dist/cli.js
Без флага исполнения появится ошибка:
Permission denied
Параметр banner позволяет добавлять произвольный текст в
начало генерируемого файла.
Простейший пример:
await build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/index.js',
banner: {
js: '// Generated by Esbuild'
}
});
Результат:
// Generated by Esbuild
"use strict";
// код приложения
Banner вставляется перед основным содержимым бандла.
Параметр представляет собой объект с настройками для различных типов файлов.
Для Jav * aScript:
banner: {
js: '// Build information'
}
Для CSS:
banner: {
css: '/* Generated CSS */'
}
Можно одновременно использовать оба варианта:
banner: {
js: '// JavaScript bundle',
css: '/* CSS bundle */'
}
Иногда исходный файл не содержит shebang, но итоговая сборка должна быть исполняемой.
В такой ситуации shebang можно вставить вручную:
await build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/cli.js',
banner: {
js: '#!/usr/bin/env node'
}
});
Результат:
#!/usr/bin/env node
"use strict";
// код приложения
Подобный подход часто используется при генерации CLI-файлов из обычных модулей.
Banner может содержать несколько строк.
Пример:
banner: {
js: `#!/usr/bin/env node
/**
* My CLI Tool
* Version 1.0.0
*/`
}
Результат:
#!/usr/bin/env node
/**
* My CLI Tool
* Version 1.0.0
*/
"use strict";
Это удобно для добавления служебной информации.
Нередко CLI-программы содержат сведения о версии непосредственно в итоговом файле.
Пример:
import pkg from './package.json' assert { type: 'json' };
await build({
entryPoints: ['src/cli.js'],
bundle: true,
outfile: 'dist/cli.js',
banner: {
js: `#!/usr/bin/env node
// Version: ${pkg.version}`
}
});
Результат:
#!/usr/bin/env node
// Version: 2.4.1
Такой подход упрощает диагностику и аудит сборок.
Многие проекты обязаны распространять лицензионные уведомления.
Banner позволяет встроить их непосредственно в файл:
banner: {
js: `/*!
* My CLI Tool
* Copyright (c) 2026
* Licensed under MIT
*/`
}
Полученный файл начинается с лицензионного блока:
/*!
* My CLI Tool
* Copyright (c) 2026
* Licensed under MIT
*/
Особенно полезно это при публикации библиотек и открытого программного обеспечения.
Esbuild поддерживает не только начало файла, но и его конец.
Пример:
await build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/index.js',
banner: {
js: '// BEGIN'
},
footer: {
js: '// END'
}
});
Результат:
// BEGIN
"use strict";
// код
// END
Для shebang подходит исключительно banner, поскольку
shebang обязан находиться в первой строке файла.
CLI-приложения могут собираться в формате ECMAScript Modules.
Конфигурация:
await build({
entryPoints: ['src/cli.js'],
bundle: true,
outfile: 'dist/cli.js',
format: 'esm',
platform: 'node'
});
При наличии shebang в исходном файле Esbuild сохранит его:
#!/usr/bin/env node
import fs from "fs";
Node.js корректно обрабатывает shebang и в ESM-файлах.
Для CommonJS ситуация аналогична.
Конфигурация:
await build({
entryPoints: ['src/cli.js'],
bundle: true,
outfile: 'dist/cli.cjs',
format: 'cjs',
platform: 'node'
});
Результат:
#!/usr/bin/env node
"use strict";
Формат модуля не влияет на работу shebang.
Проект может содержать несколько исполняемых команд.
Структура:
src/
├── build.js
├── deploy.js
└── lint.js
Конфигурация:
await build({
entryPoints: [
'src/build.js',
'src/deploy.js',
'src/lint.js'
],
outdir: 'dist',
bundle: true,
platform: 'node'
});
Если каждый файл содержит собственный shebang:
#!/usr/bin/env node
Esbuild сохранит его в каждом результирующем бандле.
Иногда исходный код представляет собой обычный модуль:
export function run() {
console.log('CLI');
}
Для превращения его в исполняемый инструмент можно использовать banner:
await build({
entryPoints: ['src/index.js'],
outfile: 'dist/cli.js',
bundle: true,
platform: 'node',
banner: {
js: '#!/usr/bin/env node'
}
});
После этого файл становится пригодным для регистрации в поле
bin.
Некорректно:
banner: {
js: `// comment
#!/usr/bin/env node`
}
Результат:
// comment
#!/usr/bin/env node
Операционная система не распознает такой файл как исполняемый скрипт, поскольку shebang обязан находиться в первой строке.
Правильно:
banner: {
js: `#!/usr/bin/env node
// comment`
}
Некоторые инструменты постобработки могут удалять первую строку файла.
Типичные признаки:
env: node: No such file or directory
или
command not found
После интеграции дополнительных инструментов рекомендуется проверять начало результирующего файла.
Некорректно:
#!C:\Program Files\nodejs\node.exe
Такой вариант не является переносимым.
Предпочтительный вариант:
#!/usr/bin/env node
Ошибка:
footer: {
js: '#!/usr/bin/env node'
}
Результат:
// код приложения
#!/usr/bin/env node
Shebang в конце файла не имеет никакого смысла и будет проигнорирован.
import { build } from 'esbuild';
await build({
entryPoints: ['src/cli.js'],
outfile: 'dist/cli.js',
bundle: true,
platform: 'node',
format: 'esm',
minify: true,
banner: {
js: `#!/usr/bin/env node
/**
* My CLI Tool
* Production Build
*/`
}
});
Особенности такой конфигурации:
Комбинация shebang + banner является стандартным механизмом подготовки CLI-приложений в Esbuild. Shebang обеспечивает корректный запуск через Node.js, а banner позволяет внедрять лицензии, сведения о версии, комментарии сборки и другие служебные данные непосредственно в начало итогового файла без изменения исходного кода.