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:
-
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:rtdesaparece. -
Un cambio de nombre conserva el nombre antiguo como alias de la misma implementación, marcado con
@deprecatedy el nombre nuevo, para siempre. Mover una exportación a otro módulo también la conserva en el antiguo. -
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.osgana 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 eldefaultde unswitch:switch (platform.os) { case "ios": case "android": return <MobileLayout />; default: // The desktops, the web, and platforms NSH adds later. return <WideLayout />; } -
El comportamiento se conserva. El comportamiento nuevo es opcional (opt-in), mediante una nueva prop, opción o valor.
-
nsh:rtsolo crece, y cada función mantiene su firma y su significado. -
Los formatos se leen para siempre. Los paquetes, certificados, listas de revocación y
app.jsonde versiones anteriores los leen las versiones posteriores; un paquete indica el runtime mínimo que necesita (nsh publish --min-runtime). -
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
- Desarrollar. Una API llega a
maincon un caso entests/compat/cuyo script la comprueba. Hasta que una versión la registra, puede renombrarse o remodelarse; el caso cambia con ella. - Registrar. En una versión,
cargo xtask compat record <version>escribereleases/v<version>/en un checkout densh-compat: la superficie que ofrece la versión y cada app detests/compat/compilada por su compilador, con sus scripts. Solo registra una API cuyas partes comprueba todas un script. - 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:
- Escribe un módulo
tests/compat/cases/<domain>/<case>.tsxcuya exportación por defecto sea un componente que use la API y muestre su resultado. Los dominios sonbase,core,events,layout,paint,props,modulesyweb, además deandroid,macosybrowserpara lo que decide una plataforma o un ajuste densh.json. - Añádelo al
App.tsxdel dominio, que muestra un caso cada vez. - Añade los pasos al
script.txtdel dominio:tap id:case-<case>, luego los pasos que comprueban lo que hace la API, y un pasocovers <item>por cada parte que añade. - 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.