Skip to content

Aplicaciones ejecutables únicas

[Historial]

VersiónCambios
v20.6.0Se añadió soporte para "useSnapshot".
v20.6.0Se añadió soporte para "useCodeCache".
v19.7.0, v18.16.0Añadido en: v19.7.0, v18.16.0

[Estable: 1 - Experimental]

Estable: 1 Estabilidad: 1.1 - Desarrollo activo

Código fuente: src/node_sea.cc

Esta característica permite la distribución de una aplicación Node.js convenientemente a un sistema que no tiene Node.js instalado.

Node.js admite la creación de aplicaciones ejecutables únicas permitiendo la inyección de un blob preparado por Node.js, que puede contener un script empaquetado, en el binario node. Durante el inicio, el programa comprueba si se ha inyectado algo. Si se encuentra el blob, ejecuta el script en el blob. De lo contrario, Node.js funciona como lo hace normalmente.

La característica de aplicación ejecutable única actualmente solo admite la ejecución de un único script incrustado usando el sistema de módulos CommonJS.

Los usuarios pueden crear una aplicación ejecutable única a partir de su script empaquetado con el propio binario node y cualquier herramienta que pueda inyectar recursos en el binario.

Estos son los pasos para crear una aplicación ejecutable única utilizando una de estas herramientas, postject:

Generación de blobs de preparación de un solo ejecutable

Los blobs de preparación de un solo ejecutable que se inyectan en la aplicación se pueden generar utilizando el flag --experimental-sea-config del binario de Node.js que se utilizará para construir el único ejecutable. Toma una ruta a un archivo de configuración en formato JSON. Si la ruta que se le pasa no es absoluta, Node.js utilizará la ruta relativa al directorio de trabajo actual.

La configuración actualmente lee los siguientes campos de nivel superior:

json
{
  "main": "/ruta/al/script/empaquetado.js",
  "output": "/ruta/para/escribir/el/blob/generado.blob",
  "disableExperimentalSEAWarning": true, // Predeterminado: false
  "useSnapshot": false, // Predeterminado: false
  "useCodeCache": true, // Predeterminado: false
  "assets": {
    // Opcional
    "a.dat": "/ruta/a/a.dat",
    "b.txt": "/ruta/a/b.txt"
  }
}

Si las rutas no son absolutas, Node.js utilizará la ruta relativa al directorio de trabajo actual. La versión del binario de Node.js utilizada para producir el blob debe ser la misma a la que se inyectará el blob.

Nota: Al generar SEAs multiplataforma (por ejemplo, generar un SEA para linux-x64 en darwin-arm64), useCodeCache y useSnapshot deben establecerse en false para evitar generar ejecutables incompatibles. Dado que la caché de código y las instantáneas solo se pueden cargar en la misma plataforma donde se compilan, el ejecutable generado podría fallar al inicio al intentar cargar la caché de código o las instantáneas construidas en una plataforma diferente.

Activos

Los usuarios pueden incluir activos agregando un diccionario clave-ruta a la configuración como el campo assets. En tiempo de compilación, Node.js leería los activos desde las rutas especificadas y los agruparía en el blob de preparación. En el ejecutable generado, los usuarios pueden recuperar los activos utilizando las API sea.getAsset() y sea.getAssetAsBlob().

json
{
  "main": "/ruta/a/script/empaquetado.js",
  "output": "/ruta/para/escribir/el/blob/generado.blob",
  "assets": {
    "a.jpg": "/ruta/a/a.jpg",
    "b.txt": "/ruta/a/b.txt"
  }
}

La aplicación de un solo ejecutable puede acceder a los activos de la siguiente manera:

js
const { getAsset, getAssetAsBlob, getRawAsset } = require('node:sea')
// Devuelve una copia de los datos en un ArrayBuffer.
const image = getAsset('a.jpg')
// Devuelve una cadena decodificada del activo como UTF8.
const text = getAsset('b.txt', 'utf8')
// Devuelve un Blob que contiene el activo.
const blob = getAssetAsBlob('a.jpg')
// Devuelve un ArrayBuffer que contiene el activo sin procesar sin copiar.
const raw = getRawAsset('a.jpg')

Consulte la documentación de las API sea.getAsset(), sea.getAssetAsBlob() y sea.getRawAsset() para obtener más información.

Soporte para instantáneas de inicio

El campo useSnapshot se puede usar para habilitar el soporte de instantáneas de inicio. En este caso, el script main no se ejecutaría cuando se inicie el ejecutable final. En cambio, se ejecutaría cuando se genere el blob de preparación de la aplicación ejecutable única en la máquina de construcción. El blob de preparación generado incluiría entonces una instantánea que captura los estados inicializados por el script main. El ejecutable final con el blob de preparación inyectado deserializaría la instantánea en tiempo de ejecución.

Cuando useSnapshot es verdadero, el script principal debe invocar la API v8.startupSnapshot.setDeserializeMainFunction() para configurar el código que necesita ejecutarse cuando los usuarios inicien el ejecutable final.

El patrón típico para que una aplicación use una instantánea en una aplicación ejecutable única es:

Las restricciones generales de los scripts de instantánea de inicio también se aplican al script principal cuando se usa para construir una instantánea para la aplicación ejecutable única, y el script principal puede usar la API v8.startupSnapshot para adaptarse a estas restricciones. Consulte la documentación sobre el soporte de instantáneas de inicio en Node.js.

Soporte de caché de código V8

Cuando useCodeCache se establece en true en la configuración, durante la generación del blob de preparación del ejecutable único, Node.js compilará el script main para generar la caché de código V8. La caché de código generada formaría parte del blob de preparación y se inyectaría en el ejecutable final. Cuando se inicia la aplicación ejecutable única, en lugar de compilar el script main desde cero, Node.js usaría la caché de código para acelerar la compilación y luego ejecutar el script, lo que mejoraría el rendimiento de inicio.

Nota: import() no funciona cuando useCodeCache es true.

En el script principal inyectado

API de aplicación de ejecutable único

El módulo integrado node:sea permite la interacción con la aplicación de ejecutable único desde el script principal de JavaScript incrustado en el ejecutable.

sea.isSea()

Agregado en: v21.7.0, v20.12.0

  • Devuelve: <boolean> Indica si este script se está ejecutando dentro de una aplicación de ejecutable único.

sea.getAsset(key[, encoding])

Agregado en: v21.7.0, v20.12.0

Este método se puede utilizar para recuperar los activos configurados para ser agrupados en la aplicación de un solo ejecutable en tiempo de compilación. Se lanza un error cuando no se encuentra ningún activo coincidente.

  • key <string> la clave del activo en el diccionario especificado por el campo assets en la configuración de la aplicación de un solo ejecutable.
  • encoding <string> Si se especifica, el activo se decodificará como una cadena. Se acepta cualquier codificación admitida por TextDecoder. Si no se especifica, se devolverá un ArrayBuffer que contiene una copia del activo.
  • Devuelve: <string> | <ArrayBuffer>

sea.getAssetAsBlob(key[, options])

Agregado en: v21.7.0, v20.12.0

Similar a sea.getAsset(), pero devuelve el resultado en un Blob. Se produce un error cuando no se encuentra ningún recurso coincidente.

  • key <string> La clave del recurso en el diccionario especificado por el campo assets en la configuración de la aplicación de un solo ejecutable.

  • options <Objeto>

    • type <string> Un tipo MIME opcional para el blob.
  • Devuelve: <Blob>

sea.getRawAsset(key)

Agregado en: v21.7.0, v20.12.0

Este método se puede utilizar para recuperar los recursos configurados para ser incluidos en la aplicación de un solo ejecutable en tiempo de compilación. Se produce un error cuando no se encuentra ningún recurso coincidente.

A diferencia de sea.getAsset() o sea.getAssetAsBlob(), este método no devuelve una copia. En cambio, devuelve el recurso sin procesar incluido dentro del ejecutable.

Por ahora, los usuarios deben evitar escribir en el búfer de matriz devuelto. Si la sección inyectada no está marcada como grabable o no está alineada correctamente, es probable que las escrituras en el búfer de matriz devuelto provoquen un fallo.

  • key <string> La clave del recurso en el diccionario especificado por el campo assets en la configuración de la aplicación de un solo ejecutable.
  • Devuelve: <ArrayBuffer>

require(id) en el script principal inyectado no se basa en archivos

require() en el script principal inyectado no es lo mismo que el require() disponible para módulos que no están inyectados. Tampoco tiene ninguna de las propiedades que require() no inyectado tiene, excepto require.main. Solo se puede usar para cargar módulos integrados. Intentar cargar un módulo que solo se puede encontrar en el sistema de archivos lanzará un error.

En lugar de depender de un require() basado en archivos, los usuarios pueden agrupar su aplicación en un archivo JavaScript independiente para inyectar en el ejecutable. Esto también asegura un grafo de dependencia más determinista.

Sin embargo, si todavía se necesita un require() basado en archivos, también se puede lograr:

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

__filename y module.filename en el script principal inyectado

Los valores de __filename y module.filename en el script principal inyectado son iguales a process.execPath.

__dirname en el script principal inyectado

El valor de __dirname en el script principal inyectado es igual al nombre del directorio de process.execPath.

Notas

Proceso de creación de aplicaciones ejecutables únicas

Una herramienta que tiene como objetivo crear una aplicación Node.js ejecutable única debe inyectar el contenido del blob preparado con --experimental-sea-config" en:

  • un recurso llamado NODE_SEA_BLOB si el binario node es un archivo PE
  • una sección llamada NODE_SEA_BLOB en el segmento NODE_SEA si el binario node es un archivo Mach-O
  • una nota llamada NODE_SEA_BLOB si el binario node es un archivo ELF

Busque en el binario la cadena fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2:0 y cambie el último carácter a 1 para indicar que se ha inyectado un recurso.

Soporte de plataforma

El soporte de un solo ejecutable se prueba regularmente en CI solo en las siguientes plataformas:

Esto se debe a la falta de mejores herramientas para generar archivos ejecutables únicos que se puedan utilizar para probar esta función en otras plataformas.

Se agradecen las sugerencias de otras herramientas/flujos de trabajo de inyección de recursos. Por favor, inicia una discusión en https://github.com/nodejs/single-executable/discussions para ayudarnos a documentarlas.