Skip to content

Aplicações executáveis únicas

[Histórico]

VersãoAlterações
v20.6.0Adicionou suporte para "useSnapshot".
v20.6.0Adicionou suporte para "useCodeCache".
v19.7.0, v18.16.0Adicionada 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 conveniente de um aplicativo Node.js para um sistema que não tenha o Node.js instalado.

O 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 agrupado, 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 normalmente.

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

Os usuários podem criar um aplicativo executável único a partir de seu script agrupado com o próprio binário node e qualquer ferramenta que possa injetar recursos no binário.

Aqui estão os passos para criar um aplicativo executável único usando uma dessas ferramentas, postject:

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

Blobs de preparação de executáveis únicos que são injetados no aplicativo 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 em 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": "/path/to/bundled/script.js",
  "output": "/path/to/write/the/generated/blob.blob",
  "disableExperimentalSEAWarning": true, // Padrão: false
  "useSnapshot": false, // Padrão: false
  "useCodeCache": true, // Padrão: false
  "assets": {
    // Opcional
    "a.dat": "/path/to/a.dat",
    "b.txt": "/path/to/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 usada para produzir o blob deve ser a mesma que aquela na qual o blob será injetado.

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

Ativos

Os usuários podem incluir ativos adicionando um dicionário de caminho-chave à configuração como o campo assets. No momento da construção, o Node.js leria os ativos dos caminhos especificados e os agregaria ao blob de preparação. No executável gerado, os usuários podem recuperar os ativos 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 single-executable pode acessar os ativos da seguinte maneira:

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 ativo como UTF8.
const text = getAsset('b.txt', 'utf8')
// Retorna um Blob contendo o ativo.
const blob = getAssetAsBlob('a.jpg')
// Retorna um ArrayBuffer contendo o ativo bruto sem copiar.
const raw = getRawAsset('a.jpg')

Consulte a documentação das APIs sea.getAsset(), sea.getAssetAsBlob() e sea.getRawAsset() para 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 for lançado. Em vez disso, ele seria executado quando o blob de preparação do aplicativo single-executable for 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 desserializaria 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 single-executable é:

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 single-executable, e o script principal pode usar a API v8.startupSnapshot 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 do executável único, o Node.js irá 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 lançado, 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 melhorará o desempenho de inicialização.

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

No script principal injetado

API de aplicativo executável único

O built-in node:sea permite a interação com o aplicativo executável único a partir do script principal JavaScript embutido no executável.

sea.isSea()

Adicionado em: v21.7.0, v20.12.0

  • Retorna: <boolean> Se este script está sendo executado dentro de um aplicativo executável único.

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 empacotados no aplicativo executável único no momento da compilação. Um erro é lançado quando nenhum ativo correspondente é encontrado.

  • key <string> a chave para o ativo no dicionário especificado pelo campo assets na configuração do aplicativo executável único.
  • 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 será retornado.
  • 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 ativo correspondente é encontrado.

  • key <string> a chave para o ativo 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 ativos configurados para serem empacotados no aplicativo executável único no momento da compilação. Um erro é lançado quando nenhum ativo correspondente é 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 ativo bruto empacotado 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, é provável que gravações no buffer de array retornado resultem em uma falha.

  • key <string> a chave para o ativo 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. Tentar carregar um módulo que só pode ser encontrado no sistema de arquivos lançará um erro.

Em vez de depender de um require() baseado em arquivo, os usuários podem empacotar seu aplicativo em um arquivo JavaScript independente para injetar no executável. Isso também garante um gráfico de dependências 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 que visa 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 a string NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2:0 fuse e inverta o último caractere para 1 para indicar que um recurso foi injetado.

Suporte à plataforma

O suporte a executáveis únicos é testado regularmente em CI apenas nas seguintes plataformas:

Isso se deve à falta de ferramentas melhores 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. Inicie uma discussão em https://github.com/nodejs/single-executable/discussions para nos ajudar a documentá-las.