Релиз Bun Shell (новый shell для JavaScript)

Kate

Administrator
Команда форума
avaScript — самый популярный скриптовый язык в мире.

Так почему же так сложно запускать shell-скрипты на JavaScript?

import { spawnSync } from "child_process";

// this is a lot more work than it could be
const { status, stdout, stderr } = spawnSync("ls", ["-l", "*.js"], {
encoding: "utf8",
});
Также можно использовать Node.js API, чтобы сделать что-то подобное:

import { readdir } from "fs/promises";

(await readdir(".", { withFileTypes: true })).filter((a) =>
a.name.endsWith(".js")
);
Но это все еще не так просто, как shell:

ls *.js

Почему существующие shell-оболочки не работают в JavaScript​

bashили shсуществуют уже несколько десятилетий.

Shell - это решенная проблема!!
Комментатор Hacker New, возможно.
Но они плохо работают в JavaScript. Почему?

macOS (zsh), Linux (bash) и Windows (cmd) имеют немного разные оболочки с разным синтаксисом и разными командами. Команды, доступные на каждой платформе, разные, и даже одна и та же команда может иметь разные флаги и поведение.

На сегодняшний день решение npm заключается в том, чтобы положиться на сообщество, которое дополнит недостающие команды реализациями JavaScript.

rm -rf не работает в Windows​

rimraf - кроссплатформенная реализация JavaScript rm -rf - загружается более 60 миллионов раз в неделю:

1582f39533ce05d2f354f0331221e265.png

Переменные среды, например, FOO=bar <script> не работают в Windows​

Установка переменных среды различна на каждой платформе. Вместо простой надписи FOO=barвы, вероятно, устанавливаете и используете cross-env:

f37cbaf4fd2f33f8e5ac2a9c80b0e242.png

which - это where на Windows​

Таким образом родился еще один пакет с 60 миллионами загрузок в неделю:

f321b1f2db0507605a171f31d25420fa.png

Оболочки командной строки слишком долго запускаются​

Сколько времени занимает запуск shell?

На x64 Hetzner Arch Linux это занимает около 7 мс:

$ hyperfine --warmup 3 'bash -c "echo hello"' 'sh -c "echo hello"' -N
Benchmark 1: bash -c 'echo hello'
Time (mean ± σ): 7.3 ms ± 1.5 ms [User: 5.1 ms, System: 1.9 ms]
Range (min … max): 1.7 ms … 9.4 ms 529 runs
Benchmark 2: sh -c 'echo hello'
Time (mean ± σ): 7.2 ms ± 1.6 ms [User: 4.8 ms, System: 2.1 ms]
Range (min … max): 1.5 ms … 9.6 ms 327 runs

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

Вы можете попробовать встроить shell, но это действительно сложно, и их лицензия может быть несовместима с вашим проектом.

Действительно ли необходимы все эти полифилы?​

В 2009–2016 годах, когда JavaScript был еще относительно новым и экспериментальным, полагаться на сообщество для заполнения недостающих функций имело большой смысл. Но сейчас 2024 год. JavaScript на сервере является зрелым и широко распространенным. Экосистема JavaScript понимает сегодняшние требования так, как никто не понимал в 2009 году.

Мы можем лучше!

Представляем Bun Shell​

Примечание переводчика: Если хочется немного больше прочесть непосредственно про сам Bun, то предлагаю к прочтению анонс - Релиз Bun 1.0 (новый runtime для JavaScript).

Bun Shell — это новый экспериментальный встроенный язык и интерпретатор внутри Bun, который позволяет запускать кроссплатформенные сценарии оболочки на JavaScript и TypeScript.

import { $ } from "bun";

// to stdout:
await $`ls *.js`;

// to string:
const text = await $`ls *.js`.text();
Вы можете использовать переменные JavaScript в своих shell-скриптах:

import { $ } from "bun";

const resp = await fetch("https://example.com");

const stdout = await $`gzip -c < ${resp}`.arrayBuffer();
В целях безопасности все переменные шаблона экранированы:

const filename = "foo.js; rm -rf /";

// This will run `ls 'foo.js; rm -rf /'`
const results = await $`ls ${filename}`;

console.log(results.exitCode); // 1
console.log(results.stderr.toString()); // ls: cannot access 'foo.js; rm -rf /': No such file or directory
Использование Bun Shell похоже на обычный JavaScript. Вы можете перенаправить (с помощью >) стандартный вывод в буферы:

import { $ } from "bun";

const buffer = Buffer.alloc(1024);

await $`ls *.js > ${buffer}`;

console.log(buffer.toString("utf8"));
Можно перенаправить стандартный вывод в файл:

import { $, file } from "bun";

// as a file()
await $`ls *.js > ${file("output.txt")}`;

// or as a file path string, if you prefer:
await $`ls *.js > output.txt`;
await $`ls *.js > ${"output.txt"}`;
С помощью pipe (знак |) можно передать стандартный вывод другой команде:

import { $ } from "bun";

await $`ls *.js | grep foo`;
Можно использовать Response в качестве стандартного ввода:

import { $ } from "bun";

const buffer = new Response("bar\n foo\n bar\n foo\n");

await $`grep foo < ${buffer}`;
Доступны встроенные команды, такие как cd, echoи rm:

import { $ } from "bun";

await $`cd .. && rm -rf node_modules/rimraf`;
Это работает в Windows, macOS и Linux. Мы реализовали множество общих команд и возможностей, такие как подстановка (globbing), переменные окружения, перенаправление, пайпы и многое другое.

Bun Shell разработан как замена простым shell-скриптам. В Windows он будет интерпретировать scripts из package.json при запуске через bun run.

По приколу вы также можете использовать его как отдельный интерпретатор сценариев оболочки:

echo "cat package.json" > script.bun.sh
bun script.bun.sh

Как установить?​

Bun Shell встроен в Bun . Если у вас уже установлен Bun v1.0.24 или новее, то вы уже можете его использовать:

bun --version
1.0.24
Если у вас не установлен Bun, вы можете установить его с помощью curl:

curl -fsSL <https://bun.sh/install> | bash
Или с помощью npm:

npm install -g bun

Bun Shell находится в статусе Alpha, соответственно завышать ожидания не стоит.

Вот тут - https://bun.sh/docs/runtime/shell - находится официальная документация. При проектировании этой истории вдохновлялись проектами zx, dax, and bnx.

Для себя главную ценность вижу именно в выразительности shell cинтаксиса, в сравнении с await readdir(...) и подобным, и одновременно возможности использовать привычный язык для описания самой логики.

Выглядит это все, конечно, занятно/интересно/многообещающе. Но лично я перестал сильно обращать внимание на проблему взаимодействия Node.js и Shell, и вот видимо почему:

  • Давно не пересекаюсь с Windows и коллеги-разработчики используют либо MacOS, либо Linux. Давно не ставил cross-env из-за жесткой необходимости.
  • Современные сборщики для Frontend уже давно умеют самостоятельно очищать dist-директорию без rimraf (Webpack и Vite точно умеют).
    Даже загрузку статики на S3 (для дальнейшей раздачи через CDN) можно делать через плагины, а не shell + rclone или еще какой-то "дедовский" способ.
  • При сборке Docker тоже не помню, чтобы приходилось писать какие-то сложные скрипты. Без нюансов всяких multi stage билдов обычно все ограничивается установкой зависимостей, а затем сборкой TypeScript.
  • А вот в рамках CI действительно иной раз хочется что-то такое на стыке шелла и js написать, но пары вызовов node -e или node -p (для вывода результата кода сразу в stdout) опять таки хватает

 
Сверху