Однозадачные исполняемые приложения
Исходный код: src/node_sea.cc
Эта функция позволяет удобно распространять приложение Node.js на систему, на которой Node.js не установлен.
Node.js поддерживает создание однозадачных исполняемых приложений, позволяя встраивать подготовленный Node.js блок, который может содержать собранный скрипт, в node бинарник. Во время запуска программа проверяет, было ли что-либо вставлено. Если блок найден, он выполняет скрипт в блоке. В противном случае Node.js работает как обычно.
Функция однозадачного исполняемого приложения в настоящее время поддерживает только выполнение одного встроенного скрипта с использованием системы модулей CommonJS.
Пользователи могут создать однозадачное исполняемое приложение из своего собранного скрипта с использованием самого бинарника node и любого инструмента, который может встраивать ресурсы в бинарник.
Вот шаги для создания однозадачного исполняемого приложения с использованием такого инструмента, как postject:
-
Создайте файл JavaScript:
echo 'console.log(`Hello, ${process.argv[2]}!`);' > hello.js copy -
Создайте файл конфигурации, создающий блок, который можно вставить в однозадачное исполняемое приложение (см. Создание блоков подготовки однозадачных исполняемых приложений для получения подробностей):
echo '{ "main": "hello.js", "output": "sea-prep.blob" }' > sea-config.json copy -
Сгенерируйте блок для вставки:
node --experimental-sea-config sea-config.json copy
-
Создайте копию
nodeисполняемого файла и назовите его по своему желанию:- В системах, отличных от Windows:
cp $(command -v node) hello copy
- В Windows:
node -e "require('fs').copyFileSync(process.execPath, 'hello.exe')" copyРасширение
.exeнеобходимо. -
Удалите подпись бинарника (только macOS и Windows):
- В macOS:
codesign --remove-signature hello copy
- В Windows (необязательно):
signtool можно использовать из установленного пакета Windows SDK. Если этот шаг пропущен, игнорируйте любые предупреждения, связанные с подписью, от postject.
signtool remove /s hello.exe copy
-
Вставьте блок в скопированный бинарник, выполнив
postjectс указанными параметрами:-
hello/hello.exe- Имя копии исполняемого файлаnodeсозданного на шаге 4. -
NODE_SEA_BLOB- Имя ресурса/примечания/раздела в бинарнике, где будут храниться данные блока. -
sea-prep.blob- Имя блока, созданного на шаге 1. -
--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2- fuse, используемый проектом Node.js для определения, был ли файл вставлен. -
--macho-segment-name NODE_SEA(необходимо только в macOS) - Имя сегмента в бинарнике, где будут храниться данные блока.
Для подведения итогов, вот необходимая команда для каждой платформы:
-
В Linux:
npx postject hello NODE_SEA_BLOB sea-prep.blob \ --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 copy -
В Windows - PowerShell:
npx postject hello.exe NODE_SEA_BLOB sea-prep.blob ` --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 copy -
В Windows - Командная строка:
npx postject hello.exe NODE_SEA_BLOB sea-prep.blob ^ --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 copy -
В macOS:
npx postject hello NODE_SEA_BLOB sea-prep.blob \ --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 \ --macho-segment-name NODE_SEA copy
-
-
Подпишите бинарник (только macOS и Windows):
- В macOS:
codesign --sign - hello copy
- В Windows (необязательно):
Для этого должен быть доступен сертификат. Тем не менее, неподписанный бинарник все равно будет запускаться.
signtool sign /fd SHA256 hello.exe copy
-
Запустите бинарник:
- В системах, отличных от Windows
$ ./hello world Hello, world! copy
- В Windows
$ .\hello.exe world Hello, world! copy
Создание блоков подготовки однозадачных исполняемых приложений
Блоки подготовки однозадачных исполняемых приложений, которые встраиваются в приложение, могут быть сгенерированы с помощью флага --experimental-sea-config бинарника Node.js, который будет использоваться для построения однозадачного исполняемого файла. Он принимает путь к файлу конфигурации в формате JSON. Если переданный путь не является абсолютным, Node.js будет использовать путь относительно текущего каталога.
В настоящее время конфигурация считывает следующие поля верхнего уровня:
{
"main": "/path/to/bundled/script.js",
"output": "/path/to/write/the/generated/blob.blob",
"disableExperimentalSEAWarning": true, // Default: false
"useSnapshot": false, // Default: false
"useCodeCache": true, // Default: false
"assets": { // Optional
"a.dat": "/path/to/a.dat",
"b.txt": "/path/to/b.txt"
}
} copy Если пути не являются абсолютными, Node.js будет использовать путь относительно текущего рабочего каталога. Версия бинарника Node.js, используемого для создания блока, должна совпадать с версией, в которую будет вставлен блок.
Ресурсы
Пользователи могут включать ресурсы, добавив словарь ключ-путь в конфигурацию в качестве поля assets. Во время сборки Node.js будет считывать ресурсы из указанных путей и собирать их в блок подготовки. В сгенерированном исполняемом файле пользователи могут получить доступ к ресурсам, используя API sea.getAsset() и sea.getAssetAsBlob().
{
"main": "/path/to/bundled/script.js",
"output": "/path/to/write/the/generated/blob.blob",
"assets": {
"a.jpg": "/path/to/a.jpg",
"b.txt": "/path/to/b.txt"
}
} copy Однозадачное исполняемое приложение может получить доступ к ресурсам следующим образом:
const { getAsset } = require('node:sea');
// Returns a copy of the data in an ArrayBuffer.
const image = getAsset('a.jpg');
// Returns a string decoded from the asset as UTF8.
const text = getAsset('b.txt', 'utf8');
// Returns a Blob containing the asset.
const blob = getAssetAsBlob('a.jpg');
// Returns an ArrayBuffer containing the raw asset without copying.
const raw = getRawAsset('a.jpg'); copy См. документацию API sea.getAsset() и sea.getAssetAsBlob() для получения дополнительной информации.
Поддержка моментального снимка при запуске
Поле useSnapshot можно использовать для включения поддержки моментального снимка при запуске. В этом случае скрипт main не будет выполняться при запуске конечного исполняемого файла. Вместо этого он будет выполняться при генерации блока подготовки однозадачного исполняемого приложения на машине сборки. Сгенерированный блок подготовки будет включать в себя моментальный снимок, захватывающий состояния, инициализированные скриптом main. Конечный исполняемый файл с вставленным блоком подготовки будет десериализовывать снимок во время выполнения.
Когда useSnapshot имеет значение true, основной скрипт должен вызвать API v8.startupSnapshot.setDeserializeMainFunction() для настройки кода, который необходимо выполнить при запуске конечного исполняемого файла пользователями.
Типичный шаблон использования моментального снимка в однозадачном исполняемом приложении для приложения выглядит следующим образом:
- Во время сборки на машине сборки основной скрипт выполняется для инициализации кучи в состояние, готовое принять пользовательский ввод. Скрипт также должен настроить главную функцию с помощью
v8.startupSnapshot.setDeserializeMainFunction(). Эта функция будет скомпилирована и сериализована в моментальный снимок, но не вызвана во время сборки. - Во время выполнения главная функция будет выполнена поверх десериализованной кучи на пользовательской машине для обработки пользовательского ввода и генерации вывода.
Общие ограничения скриптов моментальных снимков при запуске также применяются к основному скрипту, когда он используется для построения моментального снимка для однозадачного исполняемого приложения, и основной скрипт может использовать API v8.startupSnapshot API для адаптации к этим ограничениям. См. документацию о поддержке моментальных снимков при запуске в Node.js.
Поддержка кэша кода V8
Когда useCodeCache установлено в значение true в конфигурации, во время генерации блока подготовки однозадачного исполняемого файла Node.js будет компилировать скрипт main для генерации кэша кода V8. Сгенерированный кэш кода будет частью блока подготовки и будет вставлен в конечный исполняемый файл. При запуске однозадачного исполняемого приложения вместо компиляции скрипта main с нуля Node.js будет использовать кэш кода для ускорения компиляции, затем выполнить скрипт, что улучшит производительность запуска.
Примечание: import() не работает, когда useCodeCache равно true.
В инжектированном основном скрипте
API приложения с одним исполняемым файлом
Функция node:sea позволяет взаимодействовать с приложением с одним исполняемым файлом из основного JavaScript-скрипта, вложенного в исполняемый файл.
sea.isSea()
- Возвращает: <boolean> Является ли этот скрипт исполняемым внутри приложения с одним исполняемым файлом.
sea.getAsset(key[, encoding])
Этот метод можно использовать для получения ресурсов, настроенных для объединения в приложение с одним исполняемым файлом во время сборки. При отсутствии соответствия выбрасывается ошибка.
-
key<string> ключ ресурса в словаре, указанном в полеassetsконфигурации приложения с одним исполняемым файлом. -
encoding<string> Если указано, ресурс будет декодирован как строка. Поддерживается любой кодирование, поддерживаемыйTextDecoder. Если не указано, вместо этого будет возвращенArrayBuffer, содержащий копию ресурса. - Возвращает: <string> | <ArrayBuffer>
sea.getAssetAsBlob(key[, options])
Аналогично sea.getAsset(), но возвращает результат в формате Blob. При отсутствии соответствия выбрасывается ошибка.
-
key<string> ключ ресурса в словаре, указанном в полеassetsконфигурации приложения с одним исполняемым файлом. -
options<Object>-
type<string> Необязательный тип MIME для BLOB.
-
- Возвращает: <Blob>
sea.getRawAsset(key)
Этот метод можно использовать для получения ресурсов, настроенных для объединения в приложение с одним исполняемым файлом во время сборки. При отсутствии соответствия выбрасывается ошибка.
В отличие от sea.getRawAsset() или sea.getAssetAsBlob(), этот метод не возвращает копию. Вместо этого он возвращает исходный ресурс, включённый в исполняемый файл.
В данный момент пользователям не следует записывать в возвращаемый буфер массива. Если инжектируемый раздел не помечен как доступный для записи или не выровнен должным образом, записи в возвращаемый буфер массива могут привести к сбою.
-
key<string> ключ ресурса в словаре, указанном в полеassetsконфигурации приложения с одним исполняемым файлом. - Возвращает: <string> | <ArrayBuffer>
require(id) в инжектированном основном скрипте не основан на файлах
require() в инжектированном основном скрипте отличается от require(), доступного модулям, которые не инжектированы. Он также не имеет свойств, которыми обладает require() (кроме require.main). Он может использоваться только для загрузки встроенных модулей. Попытка загрузить модуль, который можно найти только в файловой системе, приведёт к ошибке.
Вместо того, чтобы полагаться на файловый require(), пользователи могут объединить своё приложение в отдельный JavaScript-файл для инъекции в исполняемый файл. Это также гарантирует более детерминированный граф зависимостей.
Однако, если все же требуется файловый require(), это также возможно:
const { createRequire } = require('node:module');
require = createRequire(__filename); copy
__filename и module.filename в инжектированном основном скрипте
Значения __filename и module.filename в инжектированном основном скрипте равны process.execPath.
__dirname в инжектированном основном скрипте
Значение __dirname в инжектированном основном скрипте равно имени каталога process.execPath.
Примечания
Процесс создания приложения с одним исполняемым файлом
Инструмент, предназначенный для создания приложения Node.js с одним исполняемым файлом, должен инжектировать содержимое BLOB, подготовленного с помощью --experimental-sea-config", в:
- ресурс под именем
NODE_SEA_BLOB, если бинарный файлnodeявляется файлом PE; - раздел под именем
NODE_SEA_BLOBв сегментеNODE_SEA, если бинарный файлnodeявляется файлом Mach-O; - примечание под именем
NODE_SEA_BLOB, если бинарный файлnodeявляется файлом ELF.
Найдите в бинарном файле строку NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2:0 fuse и измените последний символ на 1, чтобы указать, что ресурс был инжектирован.
Поддержка платформ
Поддержка приложений с одним исполняемым файлом регулярно тестируется на CI только на следующих платформах:
- Windows
- macOS
- Linux (все дистрибутивы поддерживаемые Node.js, за исключением Alpine, и все архитектуры поддерживаемые Node.js, за исключением s390x)
Это связано с отсутствием лучших инструментов для создания приложений с одним исполняемым файлом, которые можно использовать для тестирования этой функции на других платформах.
Предложения по другим инструментам/потокам работы для инъекции ресурсов приветствуются. Пожалуйста, начните обсуждение на https://github.com/nodejs/single-executable/discussions, чтобы помочь нам их задокументировать.
© Joyent, Inc. and other Node contributors
Licensed under the MIT License.
Node.js is a trademark of Joyent, Inc. and is used with its permission.
We are not endorsed by or affiliated with Joyent.
https://nodejs.org/dist/latest-v20.x/docs/api/single-executable-applications.html