Saltar al contenido principal

Paquetes de npm

El runtime de paneles se publica en npm, de modo que una aplicación React fuera de este repositorio puede instalarlo y renderizar paneles de DeviceChain en vivo.

Estado

Estos paquetes son previos a 1.0. Los tipos, las props y las exportaciones pueden cambiar entre versiones; lee las notas de la versión antes de actualizar. Son exclusivamente ESM y están construidos para un bundler — ver Qué asumen estos paquetes.

Qué se publica

PaqueteQué es
@devicechain/clientEl SDK de TypeScript — la juntura de tokens de autenticación, GraphQL sobre fetch, decodificación de JWT y suscripciones en vivo. Agnóstico del framework; sin React.
@devicechain/dashboardsEl DashboardHub — es dueño de la conexión, resuelve selectores, multiplexa las suscripciones de telemetría — más los tipos de definición, selector, slot y manifiesto de vinculación.
@devicechain/widgetsLos widgets de React y el renderizador que dispone el panel.
@devicechain/brandLos tokens de marca y las hojas de estilo generadas. Opcional — los widgets se tematizan enteramente con propiedades personalizadas de CSS y no requieren nada de esto.
npm install @devicechain/widgets @devicechain/dashboards @devicechain/client \
graphql react react-dom

@devicechain/dashboards y @devicechain/client son dependencias peer de los widgets, fijadas a exactamente la misma versión. npm 7 en adelante instala las peers por ti, así que esa línea de instalación es todo lo necesario — pero la fijación es deliberada y vale la pena conocerla, porque es lo que impide que npm anide en silencio una segunda copia del SDK bajo los widgets. Dos copias serían peor que un error: el runtime distingue «no tienes permiso para ver esto» de «se cayó la conexión» por la identidad de la clase de error que captura, y un error lanzado por una copia no es una instancia de la clase de la otra. Un rechazo por permisos se leería entonces como una caída del servicio.

Versiones y dist-tags

Los cuatro paquetes se publican juntos, en una sola versión, desde la misma release que corta la plataforma. La versión 0.14.0 de los widgets va con la versión 0.14.0 del SDK; no hay versionado independiente por paquete que haya que razonar.

TagApunta a
latestLa release estable más reciente. Es lo que resuelve un npm install sin más.
nextLa prerelease más reciente (-rc.1 y compañía). Se elige explícitamente con npm install @devicechain/widgets@next.

Toda versión publicada por el workflow de release lleva procedencia (provenance) de npm — una declaración firmada y verificable públicamente de qué repositorio, workflow y commit la construyeron, que npmjs.com muestra en la página del paquete. Las versiones 0.14.0-0 de arranque se publicaron a mano para crear los paquetes y son anteriores a eso; de la próxima release en adelante, todas la llevan.

Renderizar un panel

import { DashboardRenderer } from '@devicechain/widgets';
import { DashboardHub, parseDashboardDefinition } from '@devicechain/dashboards';

const hub = new DashboardHub({ resolver, authorities: user.scopes });
const definition = parseDashboardDefinition(json);

<DashboardRenderer definition={definition} hub={hub} actions={hub} />;

Omite actions para un montaje estrictamente de solo lectura: los widgets que actúan — reconocer alarma, enviar comando — se renderizan entonces sin sus controles. Esa es toda la adhesión explícita, y es el cinturón sobre los tirantes del servidor. Un visor que nunca recibe actions no puede escribir desde el panel, contenga lo que contenga, y el servidor hace valer alarm:write y command:write en cualquier caso.

Ver Paneles para qué es una definición, cómo los slots y los manifiestos de vinculación permiten que una definición sirva a muchos dispositivos, y cómo los usa el visor independiente /dash.

Renderizar un mapa: una pieza obligatoria de cableado del anfitrión

El widget de mapa necesita una cosa de ti, y omitirla falla en silencio.

MapLibre analiza las teselas vectoriales en un web worker, y deriva la URL de ese worker en tiempo de ejecución, a partir de la URL de su propio módulo — una cadena calculada que ningún bundler puede rastrear. Así que ningún bundler emite el archivo, el navegador responde 404, new Worker() no lanza excepción, y el mapa se renderiza como una caja vacía sin nada en la consola. Solo tu bundler puede emitir ese archivo, y por eso la URL tiene que venir de ti.

En Vite eso es un solo import:

import { MapRuntimeProvider } from '@devicechain/widgets';
import { viteMapRuntime } from '@devicechain/widgets/vite';

<MapRuntimeProvider runtime={viteMapRuntime}>
<DashboardRenderer definition={definition} hub={hub} />
</MapRuntimeProvider>;

En cualquier otro bundler suministras la URL tú mismo. Hay un requisito y es toda la dificultad: MapLibre carga esa URL como un module worker, así que lo que sirvas ahí tiene que ser un módulo sin imports sin resolver. maplibre-gl/dist/maplibre-gl-worker.mjs no lo es por sí solo — su primera línea importa un hermano, maplibre-gl-shared.mjs.

No apuntes a una copia suelta del archivo del worker

En particular, no recurras a new URL('maplibre-gl/dist/maplibre-gl-worker.mjs', import.meta.url). Parece correcto y no lo es: webpack copia ese único archivo como un activo, el hermano nunca se emite, y el worker muere en su propia primera línea — con un 200 en la URL del worker, un canvas en pantalla, marcadores colocados, la compilación terminada en 0, y nada en la consola. Al mapa simplemente no le queda mapa dentro.

Dos recetas que funcionan. En webpack, dale al worker su propio entry, para que webpack empaquete dentro de él lo que importa:

entry: {
main: './src/index.tsx',
'maplibre-worker': 'maplibre-gl/dist/maplibre-gl-worker.mjs',
},
output: {
filename: (data) =>
data.chunk.name === 'maplibre-worker' ? 'maplibre-worker.js' : '[name].[contenthash].js',
},

…y luego apunta al nombre de archivo que elegiste:

const runtime = { workerUrl: '/maplibre-worker.js' };

O, en cualquier bundler, copia los dos archivos tú mismo a un mismo directorio servido:

// maplibre-gl/dist/maplibre-gl-worker.mjs -> public/vendor/
// maplibre-gl/dist/maplibre-gl-shared.mjs -> public/vendor/
const runtime = {
workerUrl: '/vendor/maplibre-gl-worker.mjs',
loadStyles: () => import('maplibre-gl/dist/maplibre-gl.css'),
};

loadStyles es opcional — importa la hoja de estilos de MapLibre de forma anticipada tú mismo si lo prefieres. Suministrarla aquí mantiene los 83 KB (10,7 KB comprimidos) en el chunk perezoso del mapa, de modo que un visor que nunca abre un mapa no descarga nada de eso.

Un widget de mapa sin proveedor por encima renderiza un aviso visible, no un canvas en blanco. Es deliberado: un anfitrión sin cablear debería poder ver qué falta en lugar de tener que adivinarlo.

maplibre-gl es una dependencia peer

Decláralo en tu propia aplicación, junto a los widgets:

npm install maplibre-gl

Dos razones, y la segunda no es obvia. La primera es que la URL del worker es tuya para emitirla, así que la librería tiene que ser tuya para poseerla. La segunda es el desfase de versiones: si tu aplicación suministrara el worker mientras los widgets resolvieran MapLibre desde una copia propia, un worker construido con una versión estaría gobernando un hilo principal de otra — el mismo mapa en blanco, por otro camino. Una peer hace que la copia compartida sea estructural.

Si tu aplicación no lo declara, el hoisting de npm dejará que el import se resuelva de todos modos, y seguirá funcionando hasta que alguien instale con el modo estricto de pnpm o una disposición aislada similar, donde una dependencia que nadie declaró es una dependencia que nadie obtiene.

Teselas de mapa base

TenantBasemapProvider suministra la fuente de teselas, y es genuinamente opcional — sin proveedor el mapa recae en una vista simple. La cascada de resolución vive en @devicechain/client, de modo que toda superficie de DeviceChain resuelve idénticamente una anulación por usuario sobre el valor por defecto del inquilino. Ver Mapas base.

Tematización

Todo color, radio y tipografía que usan los widgets viene de una propiedad personalizada de CSS, así que un anfitrión los reestiliza fijando variables en cualquier elemento ancestro. No hay Tailwind, ni hoja de estilos global, ni opinión alguna sobre el armazón de tu aplicación. Los valores de DeviceChain se publican como @devicechain/brand; nada los exige.

Qué asumen estos paquetes

Formato de móduloSolo ESM. No hay build de CommonJS.
React19.
BundlerVite, webpack, Rollup o esbuild.
Resolución de TypeScriptLas declaraciones emitidas usan especificadores sin extensión, que resuelven bajo moduleResolution: "bundler" y no bajo node16/nodenext.

Esas son las combinaciones soportadas, y la línea entre «soportado» y «probablemente funcione» está trazada a propósito. Se ejercitan contra un navegador real en tres aplicaciones construidas fuera de este repositorio a partir de los tarballs empaquetados — una sobre Vite, una sobre webpack, y una sobre la receta de copiar el worker de más arriba — cada una de las cuales renderiza el mapa y se comprueba por las teselas que realmente pidió y los marcadores que realmente colocó, porque «se renderizó» es exactamente la afirmación que el modo de fallo de este widget supera.

Nada de eso ejercita Next.js ni React Server Components, ni consumidores de CommonJS, ni la resolución node16/nodenext. Están sin probar más que sabidos-rotos, y esta página lo dirá cuando eso cambie.

Compilar contra el workspace en su lugar

Todo lo anterior describe instalar desde el registro. Los paquetes son además la fuente de verdad dentro de este repositorio: la consola y el visor /dash compilan ambos contra el mismo dist que descarga un consumidor, en lugar de contra las fuentes TypeScript, de modo que una rotura en el artefacto publicado rompe también las aplicaciones. Si estás trabajando en DeviceChain mismo, ver la guía de desarrollo local.