Crear un entorno de ejecución modular

Qué debe hacer un host que ejecuta módulos públicos de Beyond, mostrado con BEE Node y Deno: resolver identidades bare a través de un servicio de desarrollo o de la resolución de un release, un host Node que renderiza widgets en el servidor, la diferencia entre artefactos empaquetados y compuestos, y qué puede y qué no puede esperar un host de las actualizaciones.

  • Disponibilidad: Experimental
  • Evidencia: Ejecución registrada
  • Guía práctica

El contrato del host

Un entorno de ejecución asocia identidades de módulos públicos a artefactos, resuelve sus referencias bare, los ejecuta y se encarga de su ciclo de vida. No convierte archivos internos en módulos propios, y no elige versiones: se lo pregunta a la fuente de entrega, que responde con el contrato de módulos compilados de Beyond.

La ruta de integración, en orden: resolver un especificador bare a través de una sesión de una fuente de entrega; cargar el artefacto para la plataforma y el entorno del host; mantener las referencias externas bare para que un framework se ejecute una sola vez; opcionalmente conectarse al servicio de desarrollo para recibir actualizaciones; liberar lo que el host retiene cuando termina. La ejecución de producción no necesita servicio de desarrollo.

BEE Node: el cargador

El paquete @beyond-js/bee-node registra hooks de módulos de Node que resuelven y cargan módulos públicos desde una fuente de entrega de Beyond. El adaptador nombra el contrato de esa fuente: packages para el servicio de desarrollo de Packages, resolution para cualquier base que responda un documento de resolución (un servicio de desarrollo o un release del CDN), y engine para el servidor de arranque que la suite todavía usa para compilar el propio Packages.

Shellrun.sh
# A Node process that loads the widget modules of a workspace from its development service through the
# installed loader, registers them and renders each widget with its server controller.
# <installation> is the directory where the Beyond toolchain is installed; <endpoint> the running service.
BEE_URL=<endpoint> BEE_ADAPTER=packages \
node --import <installation>/node_modules/@beyond-js/bee-node/register.mjs render.mjs "Server"

El cargador pide al servicio el módulo con las opciones de Node de la sesión (target=node, format=esm, env=development) e importa el artefacto como un módulo ES nativo. El runtime que importan los artefactos, y los frameworks de los que dependen los adaptadores suministrados, se resuelven una sola vez desde la instalación, así que un widget del workspace y su adaptador comparten un solo React, un solo Vue y un solo Svelte.

Hosts que leen una resolución

Todo origen del contrato de módulos compilados responde /resolution.json y /importmap.json: qué dirección entrega cada especificador público. Un host que lee uno de ellos no necesita nada más del origen, así que el mismo host se ejecuta contra un servicio de desarrollo y contra una aplicación publicada.

  • Node.js, con el adaptador resolution de BEE Node: BEE_ADAPTER=resolution y BEE_URL=<base>, donde la base es un servicio de desarrollo o la base de un release del CDN (/_r/<release number>). Lee resolution.json?target=node&format=esm una vez, carga módulos solo de debajo de la base y, con BEE_CACHE=<directory>, guarda en disco las respuestas immutable, de modo que un reinicio contra un release no hace ninguna solicitud.
  • Deno, sin adaptador: deno run --allow-import=<host> --import-map=<base>/importmap.json?target=node&format=esm <program>. Las direcciones del mapa son relativas a su URL, así que Deno solicita cada módulo a la base.

Un host Node que renderiza widgets

JavaScriptrender.mjs
/**
 * The server execution host of the boundaries case: a Node process that loads the widget modules of the
 * workspace from the development service through the installed loader, registers them as the module
 * registration does on import, and renders each widget with its server controller. It prints the rendered
 * HTML of each widget as JSON. It runs twice to show that the output is deterministic.
 *
 * Usage: BEE_URL=<service> BEE_ADAPTER=packages node --import <loader> ssr.mjs <label>
 */
const [label] = process.argv.slice(2);
const { widgets } = await import('@beyond-js/widgets/render');

const rendered = {};
for (const [name, specifier] of [['hello-react', '@testbed/boundaries/react'], ['hello-vue', '@testbed/boundaries/vue'], ['hello-svelte', '@testbed/boundaries/svelte'], ['hello-html', '@testbed/boundaries/html']]) {
	const { Controller } = await import(specifier);
	const specs = widgets.get(name);
	if (!specs) throw new Error(`Importing ${specifier} did not register ${name}`);
	const controller = new Controller({ specs });
	const result = await controller.render({ attributes: new Map([['label', label]]) });
	rendered[name] = { html: result.html, errors: result.errors ?? [], styles: controller.styles };
}
console.log(JSON.stringify(rendered));

Importar un módulo de widget registra su elemento en el registro de Widgets, como en un navegador; el host lee la especificación, construye el controlador de servidor con ella y llama a render. El resultado lleva el HTML del widget y, en el controlador, las direcciones de las hojas de estilos de sus dependencias. Nada aquí inicia un servidor web: un host que sirve páginas compone estos resultados en un documento y le da al navegador los mismos módulos para hidratar.

Artefactos empaquetados y compuestos

Artefacto Producido por Ejecución Actualizaciones
Empaquetado El bundler esbuild, un módulo público por módulo ES nativo Importado directamente; las referencias públicas quedan bare La hoja de estilos del módulo se reemplaza en el lugar; el código se muestra al recargar
Compuesto El bundler ts, sobre el runtime de desarrollo El artefacto importa el runtime y registra un creador por archivo fuente, sus dependencias y sus exportaciones Los módulos internos que cambiaron se evalúan de nuevo en el lugar; los demás conservan su estado

Ambos conservan referencias públicas bare y ambos se entregan por las mismas direcciones. Un host que ejecuta artefactos compuestos necesita el runtime de desarrollo, que la fuente de entrega lista en el import map de una página y la instalación provee en Node.

Qué puede esperar un host de las actualizaciones

El runtime de desarrollo aplica a los módulos cargados las compilaciones que anuncia el servicio: reemplaza los creadores cuyo hash cambió, los evalúa de nuevo, conserva los demás y dispara el cambio del paquete para que un widget montado se refresque. Una fuente que ya no compila no publica nada y el último módulo bueno permanece; un creador que lanza una excepción hace fallar su actualización sin bloquear el módulo para la corregida; dos ediciones guardadas en rápida sucesión terminan en la última. Después de que el servicio se reinicia, al runtime se le informa que está desactualizado y no aplica nada más: es un límite de reinicio del host, informado en lugar de adivinado. Los módulos internos eliminados se conservan, y no se promete migración de estado.

La conexión de desarrollo necesita fetch con cuerpos en streaming, TextDecoder, AbortController, temporizadores e import() dinámico. Node 22, Deno y los navegadores los proveen.

El mismo mecanismo de actualización se ejecuta en todos los hosts; un host solo aporta cómo importa una actualización (import() nativo, o el import propio de SystemJS en una página SystemJS) y si tiene un documento para las hojas de estilos. Las notificaciones vienen por defecto del flujo de eventos del servicio, de cualquier emisor que hable el mismo protocolo (Workspace es uno) o de local.hmr.notify(event); las actualizaciones en sí siempre se solicitan al origen.

Siguiente

Extiende el compilador en lugar del host: Crear un bundler o un procesador.