Aplicações executáveis únicas
[Histórico]
Versão | Alterações |
---|---|
v20.6.0 | Adicionou suporte para "useSnapshot". |
v20.6.0 | Adicionou suporte para "useCodeCache". |
v19.7.0, v18.16.0 | Adicionada 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:
{
"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()
.
{
"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:
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 campoassets
na configuração do aplicativo executável único.encoding
<string> Se especificado, o ativo será decodificado como uma string. Qualquer codificação suportada peloTextDecoder
é aceita. Se não especificado, umArrayBuffer
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 campoassets
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 campoassets
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:
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árionode
for um arquivo PE - uma seção chamada
NODE_SEA_BLOB
no segmentoNODE_SEA
se o binárionode
for um arquivo Mach-O - uma nota chamada
NODE_SEA_BLOB
se o binárionode
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:
- Windows
- macOS
- Linux (todas as distribuições suportadas pelo Node.js exceto Alpine e todas as arquiteturas suportadas pelo Node.js exceto s390x)
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.