Dokumentation
Dev-Server und Debugging
Während der Entwicklung baut NSH Ihre App bei jedem Speichern neu. Eine Rust-App (nsh.json nennt ihr "root") wird in ein
eigenes Runner-Programm gebaut, das nsh run startet; eine Änderung an ihrem Crate baut sie erneut und startet die App neu. Eine TSX-App wird an
jeden verbundenen Host gesendet, der sich an Ort und Stelle aktualisiert. Zwei Befehle erledigen das:
nsh runbaut die App und startet sie: ein Mac-Catalyst- oder AppKit-Fenster, ein GTK-Fenster unter Linux, ein Win32-Fenster unter Windows, einen iOS-Simulator, ein Android-Gerät oder einen Emulator oder eine Browserseite. Für TSX startet er außerdem den Dev-Server, mit dem sich der Development-Host verbindet.nsh dev --server-onlystartet den Dev-Server für TSX, für Hosts, die Sie selbst starten: Ihre eigene App mit dem Development-SDK, ein Gerät im Netzwerk oder mehrere Hosts gleichzeitig.
# A Rust app, from its directory
nsh run # AppKit window (macOS); GTK window (Linux); Win32 window (Windows)
nsh run --target ios # iOS simulator
nsh run --target android # Android device or emulator
# A TSX app
nsh run App.tsx # AppKit window (macOS); GTK window (Linux); Win32 window (Windows)
nsh run App.tsx --target macos # AppKit window (macOS)
nsh run App.tsx --target ios # iOS simulator
nsh run App.tsx --target android # Android device or emulator
nsh dev App.tsx --server-only # server only
nsh dev öffnet standardmäßig auch den nativen Host; mobile Hosts verwenden --target ios oder --target android.
#nsh run
| Flag | Beschreibung |
|---|---|
--target <catalyst | ios | android | macos | linux | windows | gpui | web> |
Wo die App läuft. Standard ist macos unter macOS (AppKit), linux unter Linux, windows unter Windows. Eine Rust-App läuft auf allen außer gpui, das sein Root in eine GPUI-App einbettet (GPUI). |
--device <name or UDID> |
Der zu verwendende iOS-Simulator. Standard: ein bereits gestarteter, sonst das neueste iPhone (wird für Sie gestartet). |
--port <port> |
Port des Dev-Servers. Standard 8790; ist er belegt, wird der nächste freie Port verwendet. |
--release |
Release-Builds von TSX ausliefern (minifiziert, ohne Debug-Informationen, keine Hot-Updates). |
--trace |
Die Zeit jedes Commits auf dem Host protokollieren (TSX-Apps). |
Was die einzelnen Targets tun:
catalyst(macOS) führt den Development-Host als Mac-App aus. Das Beenden vonnsh run(Ctrl-C oderkill) schließt ihn, und das Schließen des Fensters beendetnsh run.macos(macOS) führt den AppKit-Development-Host, eine Desktop-App in einem 1200×800-Fenster, auf die gleiche Weise aus; Cmd+R lädt den neuesten Build neu. Erfordert macOS 14 oder neuer (macOS).linux(Linux) führt den GTK-Development-Host in einem 390×844-Fenster auf die gleiche Weise aus: Das Beenden vonnsh runschließt ihn, das Schließen des Fensters beendetnsh run. Erfordert GTK 4.14 oder neuer (Linux).windows(Windows) führt den Win32-Development-Host in einem 1200×800-Fenster auf die gleiche Weise aus; der Host überlebtnsh runnie, auch nicht, wenn die Konsole geschlossen wird. Erfordert Windows 10 1809 oder neuer (Windows).iosinstalliert den Development-Host im Simulator, startet ihn mit Verweis auf den Dev-Server und beobachtet weiter. Der Simulator teilt sich das Netzwerk des Macs, sodass der Host den Server unter127.0.0.1erreicht. Das Beenden vonnsh run(Ctrl-C) schließt den Host, der sonst an der nächsten Sitzung teilnehmen würde. Erfordert Xcode.androidinstalliert die APK des Development-Hosts (erneut nur, wenn sich die Runtime geändert hat), führtadb reverse tcp:<port> tcp:<port>aus, damit das127.0.0.1des Geräts den Server erreicht, und startet ihn. Funktioniert mit einem Emulator oder einem USB-Gerät. Erfordert das Android SDK (ANDROID_HOME). Wennnsh runendet (auch per Ctrl-C), beendet es den Host und entfernt diese Portweiterleitung, damit der Host nicht an der nächsten Sitzung teilnimmt.
Wenn nsh run endet, egal wie, beendet es den Host, den es gestartet hat: Es beendet die Catalyst-App oder schließt den macOS-, Linux- oder Windows-Host, beendet den
Development-Host im Simulator oder erzwingt sein Beenden auf dem Gerät.
Für eine Rust-App führt jedes Target den eigenen Runner der App aus, der mit ihrem Crate gebaut wird: auf den Desktops ein Debug-Build an Ort und Stelle, mit seiner Ausgabe im Terminal; unter iOS und Android ein Paket davon, installiert. Für TSX sind die Hosts generisch: Sie führen jede NSH-App aus, die der Server sendet, sodass pro App nichts gebaut werden muss (eine App mit eigenem Crate erhält damit gebaute Hosts).
#nsh dev
nsh dev App.tsx
✓ built App.tsx (3.2 KB js, 9.8 KB bytecode, 21ms)
→ dev server ws://127.0.0.1:8790/ws
devices on this network: ws://192.168.1.20:8790/ws
| Flag | Beschreibung |
|---|---|
--port <port> |
Port des Dev-Servers. Standard 8790 (der nächste freie, falls belegt). |
--release |
Release-Builds ausliefern. |
Verbinden Sie Hosts mit der ausgegebenen URL:
- Ihre eigene iOS-App, gelinkt mit
NativeShellDev.xcframework:try app.connectDevServer(URL(string: "ws://192.168.1.20:8790/ws")!). Siehe Embedding in iOS apps. - Ihre eigene Android-App, mit
nsh-dev.aar:nsh.connectDevServer("ws://192.168.1.20:8790/ws")oderadb reverse tcp:8790 tcp:8790undws://127.0.0.1:8790/ws. Siehe Embedding in Android apps.
Beliebig viele Hosts können gleichzeitig verbunden sein; jeder Build geht an alle. Ein Host, der sich später verbindet, erhält den
aktuellen Build sofort. Ein Host, der die App nicht ausführen kann, weil ihm ein UI-Set fehlt, das die App importiert (ein Development-Host,
der für eine andere App gebaut wurde), erhält keinen Build: Er zeigt den Grund an, und das Terminal gibt eine warn-Zeile mit dem Befehl aus, der
den Host der App installiert.
#Rust-Änderungen
Rust wird kompiliert: Eine Änderung am Crate einer App oder an einem Crate, von dem sie per Pfad abhängt, baut die App erneut, und nsh run
startet sie mit dem neuen Code neu. Der Zustand der App beginnt von vorn; bewahren Sie, was einen Neustart überleben muss, im
Storage auf. Ein Build-Fehler wird im Terminal ausgegeben, und der nächste Speichervorgang, der kompiliert, führt die App
erneut aus. In einer App mit Rust und TSX bleiben TSX-Änderungen Hot-Updates (siehe unten), und Rust-Änderungen starten die App neu.
#Hot Module Replacement
TSX-Development-Builds behalten pro Quelldatei ein Modul. Beim Speichern baut NSH neu, vergleicht die Module mit dem letzten guten Build und sendet einen Patch nur mit den geänderten Modulen. Der Host wendet ihn an Ort und Stelle in deutlich unter einer Millisekunde an; vom Speichern einer Datei bis zur sichtbaren Änderung vergehen etwa 120 ms.
Was mit Ihrer App geschieht:
- Nur die Komponenten, die Sie bearbeitet haben, werden erneut gerendert. Jede Top-Level-Komponente (eine mit Großbuchstaben beginnende Funktion, die JSX zurückgibt) wird in einen stabilen Proxy gehüllt. Ändert sich ihr Code (oder der übrige Code ihres Moduls, den sie verwenden kann), werden ihre Instanzen mit dem neuen Code erneut gerendert; Komponenten, deren Code sich nicht geändert hat, behalten ihre Instanzen und damit ihre Signals und ihren Zustand. Bearbeiten Sie den Stil eines Buttons, und der Bildschirm bleibt, wo er war, mit demselben Eingabetext und derselben Scroll-Position.
- Geänderte Module werden erneut ausgeführt, ebenso die Module, die ein geändertes Modul importieren und das Update nicht selbst übernehmen können, bis hinauf zum Entry. Code auf Modulebene wird daher erneut ausgeführt, außer dem, was seinen Zustand behält:
- Top-Level-Signals und Navigation-Stacks behalten ihren Zustand: Ein
const, das mitsignal(…)odercreateNavStack(…)am Anfang eines Moduls erzeugt wurde, behält seinen Wert (ein Stack seine Seiten) über Änderungen hinweg, solange sein eigener Initialisierer derselbe ist; ändern Sie den Initialisierer (signal(0)zusignal(10)), um neu zu beginnen. - Top-Level-
context()-Objekte behalten ihre Identität und übernehmen den neuen Standardwert, sodass Provider und Consumer in verschiedenen Modulen weiterhin zusammenpassen. - Das Bearbeiten des Entry hält die App gemountet. Ein erneut ausgeführtes
mount(App)(odermount(() => <App />)) mit derselben Komponente behält, was es gemountet hat: Nur die Komponenten, die Sie bearbeitet haben, werden erneut gerendert. Bearbeiten SieAppselbst, wird es erneut gerendert, samt dessen, was es rendert (deren eigener Zustand beginnt von vorn), aber ein Navigator, der am Anfang des Moduls erzeugt wurde, behält seine Routen, sodass ein gepushter Screen gepusht bleibt. Ein geändertermount-Aufruf (andere Komponente, andere Props) mountet die App erneut. - Eine Komponente, die einen Fehler wirft, meldet den Fehler und rendert nichts, bis Sie ihn beheben; der Rest der App läuft weiter. Der nächste gute Speichervorgang stellt sie wieder her.
Der Host lädt die gesamte App neu, anstatt zu patchen, wenn:
- die Änderung ein NSH-Modul importiert, das die laufende App noch nicht importiert hatte (zum Beispiel das erste
import … from "nsh"); - sich das
capabilitiesder App innsh.jsongeändert hat; - der Host nicht den Build ausführt, auf dem der Patch basiert (er hat sich gerade verbunden, oder ein früheres Laden ist fehlgeschlagen);
- ein Patch nicht angewendet werden kann.
--release liefert Release-Builds ohne Hot-Updates aus: Jedes Speichern lädt die App neu. Verwenden Sie es, um den minifizierten Build zu prüfen.
Alles, was von einem Dev-Server geladen wird, ob mit --release oder nicht, umgeht die Härtung der Runtime (eval bleibt verfügbar); siehe
Sandbox and capabilities.
#Logs
Eine Rust-App protokolliert mit dem Crate log (log::info!, log::warn!, log::error!): nsh run zeigt die Zeilen in seinem
Terminal zusammen mit der Ausgabe der App an, auf den Desktops und im iOS-Simulator.
In TSX gehen console.log, console.info, console.debug, console.warn und console.error an das native Log mit
dem Target js. Solange ein Host verbunden ist, werden seine Logzeilen an das Terminal weitergeleitet, in dem nsh run / nsh dev läuft: Ihre
console.log-Zeilen unverändert (mit dem Namen des Geräts als Präfix, wenn mehrere Geräte verbunden sind), Warnungen und
Fehler hinter dem Namen des Geräts:
• iPhone 16 Pro connected, build 1
loading more after 1000
warn iPhone 16 Pro: deprecated option
error iPhone 16 Pro: uncaught exception: TypeError: cannot read property 'id' of undefined
at Row (demos/Photos.tsx:18:22)
console.log formatiert Werte wie eine Browser-Konsole: Strings unverändert, Fehler mit ihrem Stack, Funktionen mit ihrem Namen,
Objekte und Arrays als JSON. Ist der Host nicht verbunden, gehen die Logs an das Log der Plattform (die Xcode-Konsole / das
Unified Log unter iOS, adb logcat unter Android).
Ein Promise, das abgelehnt wurde und noch keinen Handler hat, sobald die Arbeit, die es abgelehnt hat, erledigt ist (seine Microtasks sind gelaufen), wird
ebenfalls als Fehler protokolliert, so wie es ein Browser meldet: eine async-Funktion, die einen Fehler wirft, während niemand sie awaitet, ein
Promise.reject(…), das niemand abfängt, ein Aufruf in Rust, der fehlschlägt, ohne dass etwas darauf wartet. Die Zeile geht dorthin, wohin
console.error geht (das Terminal, das Log der Plattform, die Browser-Konsole im Web), mit einem Stack, der auf
Ihre Quellen zeigt:
error iPhone 16 Pro: unhandled rejection: Error: HTTP 404
at loadProfile (App.tsx:12:11)
Ein Handler, der später im selben Turn angehängt wird (const p = load(); …; p.catch(…)), gilt als Behandlung. Awaiten Sie das
Promise oder geben Sie ihm ein .catch, wo der Fehlschlag von Bedeutung ist.
Der Rest des Terminals ist eine Zeile pro Schritt (✓ fertig, → wo man sich verbindet, • was ein Gerät getan hat); die Ausgabe der
Tools, die NSH ausführt (cargo, die Hosts selbst), wird nur ausgegeben, wenn eines fehlschlägt. nsh run -v (oder NSH_VERBOSE=1)
gibt alles aus: jede Logzeile mit Level und Quelle, die Ausgabe von cargo und die der Hosts selbst.
--trace fügt die Kosten jedes Commits hinzu:
perf commit: 10 ops, 151µs (list js 2µs), 8 mount items, 27 nodes
Siehe Testing für die Bedeutung dieser Zahlen.
#Das Error-Overlay
Diese Fehler zeigen auf dem Host ein rotes Overlay über der App mit Meldung und Stack:
- ein Build-Fehler (ein Syntaxfehler, ein unbekanntes Element, ein fehlender Import), mit einem Code-Frame, der auf die Zeile zeigt;
- ein Fehler beim Auswerten des Bundles;
- eine nicht abgefangene Exception in einem Event-Handler, Timer oder Effect.
Tippen Sie auf das Overlay, um es zu schließen. Es verschwindet auch beim nächsten erfolgreichen Neuladen. Build-Fehler werden auch im Terminal ausgegeben:
error building App.tsx failed
App.tsx
x <Buton> names nothing in scope
,-[3:11]
2 | function App() {
3 | return <Buton />;
: ^^^^^
4 | }
Der Bundler entfernt TypeScript-Typen, ohne sie zu prüfen; führen Sie nsh check aus (den
nativen Compiler von TypeScript mit den tsconfig.json nsh init schreibt; kein Node erforderlich) oder verlassen Sie sich für Typfehler auf Ihren Editor.
In Release-Builds (eine einbettende App mit dem Release-SDK, eine paketierte App) werden Fehler protokolliert und an den Error-Handler der Host-App
(NshAppView.onError, NshView.setErrorListener) übergeben, aber nicht über der App angezeigt.
#Source Maps und Stack Traces
Das Bundling erzeugt eine Source Map, die jeden Schritt zusammensetzt (TypeScript → JSX → das Bundle), und sie wird mit jedem
Build mitgesendet. Bevor ein Stack Trace ausgegeben oder angezeigt wird, schreibt die Runtime die Positionen des Bundles auf Ihre Quellen um, sodass Stacks
auf App.tsx:2:28 zeigen, nicht auf das Bundle:
error Mac Catalyst: uncaught exception: Error: tap boom
at <anonymous> (App.tsx:2:58)
nsh build schreibt die Map als bundle.js.map; eine einbettende App, die ein Bundle aus einem Ordner lädt, liest sie neben
dem Bundle, und signierte Pakete enthalten sie ebenfalls, es sei denn, sie wird neben dem Paket aufbewahrt und mit
nsh symbolicate angewendet (Errors from published packages).
#Tipps zum Debuggen
- Nach einer Änderung aktualisiert sich nichts: Prüfen Sie das Terminal auf einen Build-Fehler; das Overlay zeigt ihn auch auf dem Host an.
- Die App behält alten Zustand, den Sie zurücksetzen wollten: Hot Replacement behält absichtlich den Zustand unveränderter Komponenten, Top-Level-
Signals und Navigatoren. Starten Sie mit Ctrl-C und
nsh runneu oder ändern Sie den Initialisierer des Zustands. - Eine Anfrage schlägt nur in einem paketierten oder eingebetteten Build fehl: Prüfen Sie die Capabilities der App; Ablehnungen werden mit dem hinzuzufügenden Schlüssel protokolliert.
- Etwas ist langsam: Reproduzieren Sie es headless mit
nsh test --trace, das jede native View-Operation ausgibt, oder führen Sie eine TSX-App mit--traceaus. - Typfehler im Editor:
nsh runundnsh devhalten.nsh/typesaktuell; siehe Project structure.