Spec-Zone.ru › Node.js

Однозадачные исполняемые приложения

История
Версия Изменения
v20.6.0

Добавлена поддержка «useSnapshot».

v20.6.0

Добавлена поддержка «useCodeCache».

v19.7.0, v18.16.0

Добавлена в: v19.7.0, v18.16.0

Устойчивость: 1.1 - Активное развитие

Исходный код: src/node_sea.cc

Эта функция позволяет удобно распространять приложение Node.js на систему, на которой Node.js не установлен.

Node.js поддерживает создание однозадачных исполняемых приложений, позволяя встраивать подготовленный Node.js фрагмент, который может содержать связанный скрипт, в node двоичный файл. Во время запуска программа проверяет, вставлен ли какой-либо фрагмент. Если фрагмент найден, он выполняет скрипт в этом фрагменте. В противном случае Node.js работает как обычно.

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

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

Вот шаги для создания однозадачного исполняемого приложения с помощью одного из таких инструментов, postject:

  1. Создайте файл JavaScript:

    echo 'console.log(`Hello, ${process.argv[2]}!`);' > hello.js copy
  2. Создайте файл конфигурации, создающий фрагмент, который можно вставить в однозадачное исполняемое приложение (подробнее см. Создание фрагментов подготовки однозадачных исполняемых приложений):

    echo '{ "main": "hello.js", "output": "sea-prep.blob" }' > sea-config.json copy
  3. Сгенерируйте фрагмент для вставки:

    node --experimental-sea-config sea-config.json copy
  4. Создайте копию двоичного файла node и назовите её по своему усмотрению:

    • На системах, отличных от Windows:
    cp $(command -v node) hello copy
    • В Windows:
    node -e "require('fs').copyFileSync(process.execPath, 'hello.exe')" copy

    Расширение .exe необходимо.

  5. Удалите цифровую подпись двоичного файла (только macOS и Windows):

    • На macOS:
    codesign --remove-signature hello copy
    • В Windows (необязательно):

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

    signtool remove /s hello.exe copy
  6. Вставьте фрагмент в скопированный двоичный файл, выполнив 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
  7. Подпишите двоичный файл (только macOS и Windows):

    • На macOS:
    codesign --sign - hello copy
    • В Windows (необязательно):

    Для этого должен быть сертификат. Однако, неподписанный двоичный файл всё равно будет выполняться.

    signtool sign /fd SHA256 hello.exe copy
  8. Запустите двоичный файл:

    • На системах, отличных от 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() для настройки кода, который необходимо выполнить при запуске конечного исполняемого файла пользователем.

Типичный шаблон для использования моментального снимка в однозадачном исполняемом приложении:

  1. Во время сборки на машине сборки основной скрипт выполняется для инициализации кучи до состояния, готового к приёму пользовательского ввода. Скрипт также должен настроить основную функцию с помощью v8.startupSnapshot.setDeserializeMainFunction(). Эта функция будет скомпилирована и сериализована в моментальный снимок, но не вызвана во время сборки.
  2. Во время выполнения основная функция будет выполнена поверх десериализованной кучи на машине пользователя для обработки пользовательского ввода и генерации вывода.

Общие ограничения скриптов моментального снимка при запуске также применяются к основному скрипту при построении моментального снимка для однозадачного исполняемого приложения, и основной скрипт может использовать v8.startupSnapshot API для адаптации к этим ограничениям. См. документацию о поддержке моментальных снимков при запуске в Node.js.

Поддержка кэша кода V8

Когда useCodeCache установлено в true в конфигурации, при создании фрагмента подготовки однозадачного исполняемого приложения Node.js будет компилировать скрипт main для генерации кэша кода V8. Сгенерированный кэш кода будет частью фрагмента подготовки и будет вставлен в конечный исполняемый файл. При запуске однозадачного исполняемого приложения вместо компиляции скрипта main с нуля Node.js будет использовать кэш кода для ускорения компиляции, затем выполнит скрипт, что улучшит производительность запуска.

Примечание: import() не работает, когда useCodeCache имеет значение true.

В инжектированном основном скрипте

API однозадачного приложения

Встроенный элемент node:sea позволяет взаимодействовать с однозадачным приложением из основного JavaScript-скрипта, встроенного в исполняемый файл.

sea.isSea()
Добавлен в: v21.7.0, v20.12.0
  • Возвращает: <логическое значение> Является ли этот скрипт выполняемым внутри однозадачного приложения.

sea.getAsset(key[, encoding])

Добавлен в: v21.7.0, v20.12.0

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

  • key <строка> ключ ресурса в словаре, указанном полем assets в конфигурации однозадачного приложения.
  • encoding <строка> Если указано, ресурс будет декодирован как строка. Принимаются любые кодировки, поддерживаемые TextDecoder. Если не указано, вместо этого будет возвращено ArrayBuffer, содержащее копию ресурса.
  • Возвращает: <строка> | <ArrayBuffer>

sea.getAssetAsBlob(key[, options])

Добавлен в: v21.7.0, v20.12.0

Аналогично sea.getAsset(), но возвращает результат в виде Blob. Если соответствующего ресурса не найдено, выбрасывается ошибка.

  • key <строка> ключ ресурса в словаре, указанном полем assets в конфигурации однозадачного приложения.
  • options <Объект>
    • type <строка> Необязательный тип MIME для объекта Blob.
  • Возвращает: <Blob>

sea.getRawAsset(key)

Добавлен в: v21.7.0, v20.12.0

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

В отличие от sea.getRawAsset() или sea.getAssetAsBlob(), этот метод не возвращает копию. Вместо этого он возвращает исходный ресурс, объединённый внутри исполняемого файла.

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

  • key <строка> ключ ресурса в словаре, указанном полем assets в конфигурации однозадачного приложения.
  • Возвращает: <строка> | <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/api/single-executable-applications.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API