Skip to content

Aplicações executáveis únicas

[Histórico]

VersãoMudanças
v20.6.0Adicionado suporte para "useSnapshot".
v20.6.0Adicionado suporte para "useCodeCache".
v19.7.0, v18.16.0Adicionado em: v19.7.0, v18.16.0

[Estável: 1 - Experimental]

Estável: 1 Estabilidade: 1.1 - Desenvolvimento ativo

Código Fonte: src/node_sea.cc

Este recurso permite a distribuição de uma aplicação Node.js convenientemente para um sistema que não tem o Node.js instalado.

Node.js suporta a criação de aplicações executáveis únicas permitindo a injeção de um blob preparado pelo Node.js, que pode conter um script empacotado, no binário node. Durante a inicialização, o programa verifica se algo foi injetado. Se o blob for encontrado, ele executa o script no blob. Caso contrário, o Node.js opera como normalmente.

O recurso de aplicação executável única atualmente suporta apenas a execução de um único script embutido usando o sistema de módulos CommonJS.

Os usuários podem criar uma aplicação executável única a partir de seu script empacotado com o próprio binário node e qualquer ferramenta que possa injetar recursos no binário.

Aqui estão os passos para criar uma aplicação executável única usando uma dessas ferramentas, postject:

Gerando blobs de preparação para executáveis únicos

Blobs de preparação para executáveis únicos que são injetados na aplicação podem ser gerados usando a flag --experimental-sea-config do binário Node.js que será usado para construir o executável único. Ele recebe um caminho para um arquivo de configuração no formato JSON. Se o caminho passado não for absoluto, o Node.js usará o caminho relativo ao diretório de trabalho atual.

A configuração atualmente lê os seguintes campos de nível superior:

json
{
  "main": "/caminho/para/script/empacotado.js",
  "output": "/caminho/para/escrever/o/blob/gerado.blob",
  "disableExperimentalSEAWarning": true, // Padrão: false
  "useSnapshot": false,  // Padrão: false
  "useCodeCache": true, // Padrão: false
  "assets": {  // Opcional
    "a.dat": "/caminho/para/a.dat",
    "b.txt": "/caminho/para/b.txt"
  }
}

Se os caminhos não forem absolutos, o Node.js usará o caminho relativo ao diretório de trabalho atual. A versão do binário Node.js usado para produzir o blob deve ser a mesma daquele no qual o blob será injetado.

Nota: Ao gerar SEAs multiplataforma (por exemplo, gerar uma SEA para linux-x64 em darwin-arm64), useCodeCache e useSnapshot devem ser definidos como false para evitar gerar executáveis incompatíveis. Como o cache de código e os snapshots só podem ser carregados na mesma plataforma onde são compilados, o executável gerado pode falhar na inicialização ao tentar carregar o cache de código ou os snapshots construídos em uma plataforma diferente.

Recursos

Os usuários podem incluir recursos adicionando um dicionário de chave-caminho à configuração como o campo assets. No momento da construção, o Node.js leria os recursos dos caminhos especificados e os agruparia no blob de preparação. No executável gerado, os usuários podem recuperar os recursos usando as APIs sea.getAsset() e sea.getAssetAsBlob().

json
{
  "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"
  }
}

O aplicativo executável único pode acessar os recursos da seguinte forma:

js
const { getAsset, getAssetAsBlob, getRawAsset } = require('node:sea');
// Retorna uma cópia dos dados em um ArrayBuffer.
const image = getAsset('a.jpg');
// Retorna uma string decodificada do recurso como UTF8.
const text = getAsset('b.txt', 'utf8');
// Retorna um Blob contendo o recurso.
const blob = getAssetAsBlob('a.jpg');
// Retorna um ArrayBuffer contendo o recurso bruto sem copiar.
const raw = getRawAsset('a.jpg');

Consulte a documentação das APIs sea.getAsset(), sea.getAssetAsBlob() e sea.getRawAsset() para obter mais informações.

Suporte a snapshot de inicialização

O campo useSnapshot pode ser usado para habilitar o suporte a snapshot de inicialização. Nesse caso, o script main não seria executado quando o executável final fosse lançado. Em vez disso, ele seria executado quando o blob de preparação do aplicativo executável único fosse gerado na máquina de construção. O blob de preparação gerado incluiria então um snapshot capturando os estados inicializados pelo script main. O executável final com o blob de preparação injetado deserializaria o snapshot em tempo de execução.

Quando useSnapshot é verdadeiro, o script principal deve invocar a API v8.startupSnapshot.setDeserializeMainFunction() para configurar o código que precisa ser executado quando o executável final for lançado pelos usuários.

O padrão típico para um aplicativo usar snapshot em um aplicativo executável único é:

As restrições gerais dos scripts de snapshot de inicialização também se aplicam ao script principal quando ele é usado para construir o snapshot para o aplicativo executável único, e o script principal pode usar a API v8.startupSnapshot API para se adaptar a essas restrições. Consulte a documentação sobre suporte a snapshot de inicialização no Node.js.

Suporte ao cache de código V8

Quando useCodeCache é definido como true na configuração, durante a geração do blob de preparação executável único, o Node.js compilará o script main para gerar o cache de código V8. O cache de código gerado fará parte do blob de preparação e será injetado no executável final. Quando o aplicativo executável único for iniciado, em vez de compilar o script main do zero, o Node.js usará o cache de código para acelerar a compilação e, em seguida, executará o script, o que melhoraria o desempenho de inicialização.

Nota: import() não funciona quando useCodeCache é true.

No script principal injetado

API de aplicação executável única

O builtin node:sea permite a interação com a aplicação executável única a partir do script principal JavaScript incorporado no executável.

sea.isSea()

Adicionado em: v21.7.0, v20.12.0

  • Retorna: <boolean> Se este script está sendo executado dentro de uma aplicação executável única.

sea.getAsset(key[, encoding])

Adicionado em: v21.7.0, v20.12.0

Este método pode ser usado para recuperar os ativos configurados para serem agrupados na aplicação executável única no momento da construção. Um erro é lançado quando nenhum ativo correspondente pode ser encontrado.

  • key <string> a chave para o ativo no dicionário especificado pelo campo assets na configuração da aplicação executável única.
  • encoding <string> Se especificado, o ativo será decodificado como uma string. Qualquer codificação suportada pelo TextDecoder é aceita. Se não especificado, um ArrayBuffer contendo uma cópia do ativo seria retornado em vez disso.
  • Retorna: <string> | <ArrayBuffer>

sea.getAssetAsBlob(key[, options])

Adicionado em: v21.7.0, v20.12.0

Similar a sea.getAsset(), mas retorna o resultado em um Blob. Um erro é lançado quando nenhum recurso correspondente pode ser encontrado.

  • key <string> a chave para o recurso no dicionário especificado pelo campo assets na configuração do aplicativo executável único.

  • options <Object>

    • type <string> Um tipo mime opcional para o blob.
  • Retorna: <Blob>

sea.getRawAsset(key)

Adicionado em: v21.7.0, v20.12.0

Este método pode ser usado para recuperar os recursos configurados para serem agrupados no aplicativo executável único no momento da construção. Um erro é lançado quando nenhum recurso correspondente pode ser encontrado.

Ao contrário de sea.getAsset() ou sea.getAssetAsBlob(), este método não retorna uma cópia. Em vez disso, ele retorna o recurso bruto agrupado dentro do executável.

Por enquanto, os usuários devem evitar escrever no buffer de array retornado. Se a seção injetada não estiver marcada como gravável ou não estiver alinhada corretamente, as gravações no buffer de array retornado provavelmente resultarão em uma falha.

  • key <string> a chave para o recurso no dicionário especificado pelo campo assets na configuração do aplicativo executável único.
  • Retorna: <ArrayBuffer>

require(id) no script principal injetado não é baseado em arquivo

require() no script principal injetado não é o mesmo que o require() disponível para módulos que não são injetados. Ele também não possui nenhuma das propriedades que o require() não injetado possui, exceto require.main. Ele só pode ser usado para carregar módulos integrados. A tentativa de carregar um módulo que só pode ser encontrado no sistema de arquivos lançará um erro.

Em vez de confiar em um require() baseado em arquivo, os usuários podem agrupar seu aplicativo em um arquivo JavaScript independente para injetar no executável. Isso também garante um gráfico de dependência mais determinístico.

No entanto, se um require() baseado em arquivo ainda for necessário, isso também pode ser alcançado:

js
const { createRequire } = require('node:module');
require = createRequire(__filename);

__filename e module.filename no script principal injetado

Os valores de __filename e module.filename no script principal injetado são iguais a process.execPath.

__dirname no script principal injetado

O valor de __dirname no script principal injetado é igual ao nome do diretório de process.execPath.

Notas

Processo de criação de aplicativo executável único

Uma ferramenta com o objetivo de criar um aplicativo Node.js executável único deve injetar o conteúdo do blob preparado com --experimental-sea-config" em:

  • um recurso chamado NODE_SEA_BLOB se o binário node for um arquivo PE
  • uma seção chamada NODE_SEA_BLOB no segmento NODE_SEA se o binário node for um arquivo Mach-O
  • uma nota chamada NODE_SEA_BLOB se o binário node for um arquivo ELF

Procure no binário pela string fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2:0 e inverta o último caractere para 1 para indicar que um recurso foi injetado.

Suporte de plataforma

O suporte a executável único é testado regularmente no CI apenas nas seguintes plataformas:

Isso se deve à falta de melhores ferramentas para gerar executáveis ​​únicos que possam ser usados ​​para testar esse recurso em outras plataformas.

Sugestões para outras ferramentas/fluxos de trabalho de injeção de recursos são bem-vindas. Por favor, inicie uma discussão em https://github.com/nodejs/single-executable/discussions para nos ajudar a documentá-las.