Esta página se tradujo automáticamente y puede contener errores. Leer el original en inglés

Documentación

Compatibilidad hacia atrás

Las apps host descargan las apps NSH como paquetes firmados y las ejecutan como plugins, y una app ya publicada puede no volver a compilarse nunca: su equipo pasó a otra cosa, su código fuente está dentro de un proceso de negocio y la app host actualizó NSH según su propio calendario. Por eso Native Shell mantiene una promesa a cada app: una app compilada para una versión compatible se ejecuta sin cambios en todas las versiones posteriores. Una app que deja de funcionar tras una actualización de NSH es un error de NSH.

Esta página describe la promesa, cómo se hace cumplir y cómo cambiar NSH sin romperla.

#En qué puede confiar una app

La promesa cubre todo lo que toca una app compilada:

Parte Por qué importa
Las exportaciones de cada módulo (nsh, nsh/*, nsh/base, los módulos estándar) y sus miembros Un bundle las importa por nombre: si falta un nombre, la app no arranca.
nsh:rt, las funciones a las que llama el JSX compilado Cada bundle lleva la salida del compilador que lo construyó.
Los globales (temporizadores, fetch, console, …) y sus miembros Las apps los llaman por nombre.
Props de elementos, eventos y sus payloads, claves de estilo y sus valores Las apps los pasan por nombre; una prop renombrada se ignora, así que la app funciona mal.
Utilidades y variantes de Tailwind Las clases se resuelven en tiempo de ejecución a partir de las cadenas del bundle.
Comportamiento: valores por defecto, unidades, payloads, el orden de los callbacks Una app depende de lo que vio cuando se escribió.
Las declaraciones de TypeScript (.nsh/types) El código fuente de una app antigua sigue pasando la comprobación de tipos cuando su equipo corrige un error.
El formato de paquete y sus firmas Un paquete firmado para una versión anterior sigue verificándose.

Las reglas que lo garantizan:

  1. Lo que una versión publicó se queda. Ningún módulo, exportación, miembro, prop, evento, clave de payload, clave de estilo o valor, utilidad de Tailwind, global o función de nsh:rt desaparece.

  2. Un cambio de nombre conserva el nombre antiguo como alias de la misma implementación, marcado con @deprecated y el nombre nuevo, para siempre. Mover una exportación a otro módulo también la conserva en el antiguo.

  3. Los tipos solo se amplían. Una unión gana alternativas, una función gana sobrecargas o parámetros opcionales al final, un parámetro pasa a ser opcional; lo que las apps pasan (props, estilos, opciones) solo gana miembros opcionales. Los valores que lee una app crecen del mismo modo: platform.os gana una plataforma, un estado o un motivo gana un caso. Un caso que la app ya vio conserva su valor, y la app gestiona un valor que no conoce, con el default de un switch:

    switch (platform.os) {
      case "ios":
      case "android":
        return <MobileLayout />;
      default:
        // The desktops, the web, and platforms NSH adds later.
        return <WideLayout />;
    }
  4. El comportamiento se conserva. El comportamiento nuevo es opcional (opt-in), mediante una nueva prop, opción o valor.

  5. nsh:rt solo crece, y cada función mantiene su firma y su significado.

  6. Los formatos se leen para siempre. Los paquetes, certificados, listas de revocación y app.json de versiones anteriores los leen las versiones posteriores; un paquete indica el runtime mínimo que necesita (nsh publish --min-runtime).

  7. La seguridad es la única excepción. Una capacidad que no se puede mantener segura se cierra, se lista con su motivo y se nombra en las notas de la versión.

La promesa empieza en la primera versión registrada como compatible y se mantiene para siempre, sea cual sea el número de versión. Entre versiones, una API que solo tiene main aún puede cambiar.

#Cómo se hace cumplir

Lo que las versiones congelaron vive en su propio repositorio, longbridge/nsh-compat, separado del código de NSH. Un cambio en NSH se ejecuta contra él y no puede editarlo: un pull request que renombra una API no puede reescribir también el registro de esa API.

nsh                                       nsh-compat
├─ crates/          the code              ├─ releases/v<version>/   one per compatible release, never changed
└─ tests/compat/    apps that test the    │  ├─ runtime.txt         every export, global and nsh:rt function
   ├─ cases/        API being written     │  ├─ types/              the declarations apps build against
   └─ examples/                           │  ├─ tailwind.txt        every Tailwind class and what it sets
                                          │  └─ apps/               the test apps as that release built them
                                          ├─ closed.txt             security closures, each with its reason
                                          └─ src/                   the checks

Cada pull request a NSH ejecuta las comprobaciones de nsh-compat contra su checkout (el workflow Compat, o cargo xtask compat en local):

Comprobación Qué la supera
checkout_apps_run Las apps de tests/compat/, compiladas por este checkout, hacen lo que dicen sus scripts, y cada parte de la API que un script afirma comprobar se comprueba realmente.
nothing_released_is_removed Todo lo que ofrecía cada versión registrada (exportaciones, miembros y sus tipos, declaraciones, clases de Tailwind y lo que establecen) se sigue ofreciendo, igual o ampliado.
released_apps_run Las apps que compiló cada versión registrada, tal como las compiló su compilador, arrancan en este runtime y hacen lo que dicen sus scripts.

La primera comprobación prueba la API que se está escribiendo; las otras dos sostienen cada versión que se registró. El cambio de nombre de una API publicada falla así:

v0.2.0: runtime [nsh] Platform: removed
Keep it: a rename keeps the old name as a @deprecated alias of the new one, a type only widens.

nsh-compat lo revisan los maintainers de NSH, y su propio CI lo ejecuta contra main de NSH.

#El flujo de lanzamiento

  1. Desarrollar. Una API llega a main con un caso en tests/compat/ cuyo script la comprueba. Hasta que una versión la registra, puede renombrarse o remodelarse; el caso cambia con ella.
  2. Registrar. En una versión, cargo xtask compat record <version> escribe releases/v<version>/ en un checkout de nsh-compat: la superficie que ofrece la versión y cada app de tests/compat/ compilada por su compilador, con sus scripts. Solo registra una API cuyas partes comprueba todas un script.
  3. Congelar. El registro se fusiona mediante un pull request de nsh-compat. Desde entonces, cada cambio de NSH se contrasta con él, junto con todos los registros anteriores.

#Desarrollar dentro de la promesa

#Añadir una API

Cada nueva exportación, global, prop, evento o clave de estilo viene con un caso:

  1. Escribe un módulo tests/compat/cases/<domain>/<case>.tsx cuya exportación por defecto sea un componente que use la API y muestre su resultado. Los dominios son base, core, events, layout, paint, props, modules y web, además de android, macos y browser para lo que decide una plataforma o un ajuste de nsh.json.
  2. Añádelo al App.tsx del dominio, que muestra un caso cada vez.
  3. Añade los pasos al script.txt del dominio: tap id:case-<case>, luego los pasos que comprueban lo que hace la API, y un paso covers <item> por cada parte que añade.
  4. Ejecuta cargo xtask compat (NSH_COMPAT_APP=<domain> ejecuta un dominio).
# props-media-video
tap id:case-props-media-video
prop id:clip src https://example.com/clip.mp4
prop id:clip paused true
tap id:play
prop id:clip paused false
covers [props] src
covers [props] paused

Un paso covers se verifica: la app debe usar el elemento y, para una prop, un evento o una clave de estilo, quitarlo de la app compilada debe hacer fallar un paso del script. Los elementos se nombran [<module>] <export>, [globals] <name>, [props] <name>, [events] <name>, [styles] <name>, los propios de un componente como [props] <Component>.<name>, y las props de una plataforma como ios.<name>, android.<name>, desktop.<name>, web.<name>.

#Renombrar o mover una API

Añade el nombre nuevo y conserva el antiguo como alias de la misma implementación, declarado @deprecated con el nombre nuevo. Actualiza el caso y su script al nombre nuevo. Una API que ninguna versión ha registrado todavía simplemente se renombra.

#Cambiar un tipo

Amplíalo: más alternativas en una unión, más sobrecargas, parámetros opcionales al final, nuevos miembros opcionales de lo que las apps pasan. Una unión que las apps leen gana miembros del mismo modo, cada nuevo miembro para un caso nuevo. Los nombres de los parámetros pueden cambiar libremente; su número, su opcionalidad y sus tipos se mantienen.

#Cambiar el comportamiento

Conserva el comportamiento antiguo y añade el nuevo detrás de una nueva prop, opción o valor. Una corrección de error que cambia lo que ve una app que funciona también es un cambio de comportamiento: ponla detrás de un opt-in, o asegúrate de que ninguna app pueda depender del resultado antiguo. Da al comportamiento un paso en un caso, para que se mantenga tal como está.

#Cambiar el compilador

Nada adicional: las apps de cada versión se quedan tal como las compiló el compilador de esa versión, y los casos los compila de nuevo el tuyo. Las funciones que emite en los bundles (nsh:rt) solo crecen.

#Cerrar una API por seguridad

Un pull request de nsh-compat añade la entrada a su closed.txt con el motivo (<entry> -- <reason>, la entrada tal como la informa la comprobación), y se integra primero; después, el cambio de NSH que elimina la API lo supera. Las notas de la versión explican por qué.

#Scripts

Un script.txt es un paso por línea. Pasos que actúan: tap <selector>, type <selector> <text>, key <selector> <key>, scroll <selector> <x> <y>, touch start|move|end|cancel, event <selector> <onEvent> [<key>=<value>...], wait <ms>, keyboard <height> <duration>, pixelratio <ratio>. Pasos que comprueban: expect <text>, absent <text>, frame <selector> <x> <y> <width> <height>, origin, prop / noprop, style / nostyle, listens, state, statusbar, log <text>. Las primeras líneas pueden configurar la pantalla: viewport <w> <h>, platform <os> [desktop], fontscale <scale>. Un selector es id:<testID>, label:<label>, text:<text> o span:<text>; las props y los estilos se escriben como los escriben las apps (style id:card backgroundColor #3366ffff). NSH_COMPAT_DUMP=1 imprime lo que cada paso añade a la pantalla y lo que quita de ella, para ayudar a escribir un script.

#Comandos

cargo xtask compat                      # the checks against this checkout
NSH_COMPAT_APP=props cargo xtask compat -v
cargo xtask compat record 0.2.0         # at a release: record it in nsh-compat, then open a pull request there

cargo xtask compat mantiene un checkout de nsh-compat en target/nsh-compat/ y lo actualiza en cada ejecución; uno con cambios propios se usa tal cual, de modo que un cambio en las comprobaciones se puede probar contra NSH antes de su pull request.