दस्तावेज़
Dev Server और डीबगिंग
डेवलपमेंट के दौरान NSH हर सेव पर आपका ऐप बिल्ड करता है। Rust ऐप (nsh.json अपने "root" का नाम बताता है) अपने ही रनर में बिल्ड होता है, जिसे nsh run शुरू करता है; उसके क्रेट में बदलाव होने पर वह दोबारा बिल्ड होता है और ऐप रीस्टार्ट हो जाता है। TSX ऐप हर कनेक्टेड होस्ट को भेजा जाता है, जो अपडेट को उसी जगह लागू कर लेता है। यह काम दो कमांड करते हैं:
nsh runऐप को बिल्ड और लॉन्च करता है: Mac Catalyst या AppKit विंडो, Linux पर GTK विंडो, Windows पर Win32 विंडो, iOS सिमुलेटर, Android डिवाइस या एमुलेटर, या ब्राउज़र पेज। TSX के लिए यह डेव सर्वर भी शुरू करता है, जिससे डेवलपमेंट होस्ट कनेक्ट होता है।nsh dev --server-onlyTSX के लिए डेव सर्वर शुरू करता है, उन होस्ट के लिए जिन्हें आप खुद शुरू करते हैं: डेवलपमेंट SDK वाला आपका अपना ऐप, नेटवर्क पर मौजूद कोई डिवाइस, या एक साथ कई होस्ट।
# 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 डिफ़ॉल्ट रूप से नेटिव होस्ट भी खोलता है; मोबाइल होस्ट --target ios या --target android का उपयोग करते हैं।
#nsh run
| फ़्लैग | विवरण |
|---|---|
--target <catalyst | ios | android | macos | linux | windows | gpui | web> | कहाँ चलाना है। macOS (AppKit) पर डिफ़ॉल्ट macos, Linux पर linux, Windows पर windows। Rust ऐप इन सभी पर चलता है, सिवाय gpui के, जो अपना रूट GPUI ऐप में एम्बेड करता है (GPUI)। |
--device <name or UDID> | उपयोग किया जाने वाला iOS सिमुलेटर। डिफ़ॉल्ट: पहले से बूट किया हुआ सिमुलेटर, नहीं तो सबसे नया iPhone (जिसे आपके लिए बूट किया जाता है)। |
--port <port> | डेव सर्वर पोर्ट। डिफ़ॉल्ट 8790; यह व्यस्त हो तो अगला खाली पोर्ट इस्तेमाल होता है। |
--release | TSX के रिलीज़ बिल्ड सर्व करें (minified, बिना डीबग जानकारी, बिना हॉट अपडेट)। |
--trace | होस्ट पर हर commit का समय लॉग करें (TSX ऐप)। |
हर टारगेट क्या करता है:
catalyst(macOS) डेवलपमेंट होस्ट को Mac ऐप के रूप में चलाता है।nsh runको रोकने (Ctrl-C, याkill) पर यह बंद हो जाता है, और विंडो बंद करने परnsh runसमाप्त हो जाता है।macos(macOS) AppKit डेवलपमेंट होस्ट को उसी तरह चलाता है, यानी 1200×800 विंडो में एक डेस्कटॉप ऐप; Cmd+R नवीनतम बिल्ड रीलोड करता है। इसके लिए macOS 14 या बाद का संस्करण चाहिए (macOS)।linux(Linux) GTK डेवलपमेंट होस्ट को 390×844 विंडो में उसी तरह चलाता है:nsh runको रोकने पर यह बंद हो जाता है, और विंडो बंद करने परnsh runसमाप्त हो जाता है। इसके लिए GTK 4.14 या बाद का संस्करण चाहिए (Linux)।windows(Windows) Win32 डेवलपमेंट होस्ट को 1200×800 विंडो में उसी तरह चलाता है; होस्ट कभीnsh runसे अधिक देर तक नहीं चलता, कंसोल बंद होने पर भी नहीं। इसके लिए Windows 10 1809 या बाद का संस्करण चाहिए (Windows)।iosडेवलपमेंट होस्ट को सिमुलेटर में इंस्टॉल करता है, उसे डेव सर्वर की ओर इंगित करके लॉन्च करता है और निगरानी जारी रखता है। सिमुलेटर Mac का नेटवर्क साझा करता है, इसलिए होस्ट सर्वर तक127.0.0.1पर पहुँचता है।nsh runको रोकने (Ctrl-C) पर होस्ट बंद हो जाता है, वरना वह अगले सेशन से जुड़ जाता। इसके लिए Xcode चाहिए।androidडेवलपमेंट होस्ट APK इंस्टॉल करता है (केवल तब दोबारा जब रनटाइम बदला हो),adb reverse tcp:<port> tcp:<port>चलाता है ताकि डिवाइस का अपना127.0.0.1सर्वर तक पहुँच सके, और उसे शुरू करता है। यह एमुलेटर या USB डिवाइस के साथ काम करता है। इसके लिए Android SDK (ANDROID_HOME) चाहिए। जबnsh runसमाप्त होता है (Ctrl-C पर भी), तो यह होस्ट को रोक देता है और वह पोर्ट फ़ॉरवर्डिंग हटा देता है, ताकि होस्ट अगले सेशन से न जुड़े।
जब nsh run समाप्त होता है, चाहे जैसे भी, तो वह अपने शुरू किए हुए होस्ट को रोक देता है: वह Catalyst ऐप को बंद करता है या macOS, Linux या Windows होस्ट को बंद करता है, सिमुलेटर में डेवलपमेंट होस्ट को समाप्त करता है, या डिवाइस पर उसे force-stop करता है।
Rust ऐप के लिए हर टारगेट ऐप का अपना रनर चलाता है, जो उसके क्रेट के साथ बिल्ड होता है: डेस्कटॉप पर डीबग बिल्ड उसी जगह चलता है और उसका आउटपुट टर्मिनल में आता है; iOS और Android पर उसका एक पैकेज इंस्टॉल किया जाता है। TSX के लिए होस्ट सामान्य (generic) होते हैं: वे सर्वर द्वारा भेजा गया कोई भी NSH ऐप चलाते हैं, इसलिए हर ऐप के लिए कुछ बिल्ड नहीं करना पड़ता (जिस ऐप का अपना क्रेट हो, उसे उसी के साथ बने होस्ट मिलते हैं)।
#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
| फ़्लैग | विवरण |
|---|---|
--port <port> | डेव सर्वर पोर्ट। डिफ़ॉल्ट 8790 (व्यस्त होने पर अगला खाली पोर्ट)। |
--release | रिलीज़ बिल्ड सर्व करें। |
होस्ट को प्रिंट किए गए URL से कनेक्ट करें:
- आपका अपना iOS ऐप, जो
NativeShellDev.xcframeworkके साथ लिंक किया गया हो:try app.connectDevServer(URL(string: "ws://192.168.1.20:8790/ws")!)। देखें iOS ऐप्स में एम्बेड करना। - आपका अपना Android ऐप,
nsh-dev.aarके साथ:nsh.connectDevServer("ws://192.168.1.20:8790/ws"), याadb reverse tcp:8790 tcp:8790औरws://127.0.0.1:8790/ws। देखें Android ऐप्स में एम्बेड करना।
एक साथ कितने भी होस्ट कनेक्ट हो सकते हैं; हर बिल्ड सभी को भेजा जाता है। बाद में कनेक्ट होने वाले होस्ट को वर्तमान बिल्ड तुरंत मिल जाता है। जो होस्ट ऐप नहीं चला सकता, क्योंकि ऐप जो UI सेट इम्पोर्ट करता है वह उसमें नहीं है (किसी दूसरे ऐप के लिए बना डेवलपमेंट होस्ट), उसे कोई बिल्ड नहीं मिलता: वह कारण दिखाता है, और टर्मिनल एक warn लाइन प्रिंट करता है जिसमें ऐप का होस्ट इंस्टॉल करने वाला कमांड होता है।
#Rust में बदलाव
Rust कंपाइल की जाने वाली भाषा है: ऐप के क्रेट में, या उस क्रेट में जिस पर वह path से निर्भर है, कोई बदलाव होने पर ऐप दोबारा बिल्ड होता है और nsh run उसे नए कोड के साथ रीस्टार्ट कर देता है। ऐप की स्टेट नए सिरे से शुरू होती है; जो कुछ रीस्टार्ट के बाद भी बचना चाहिए उसे स्टोरेज में रखें। बिल्ड एरर टर्मिनल में प्रिंट होता है, और जो अगला सेव कंपाइल हो जाए, वह ऐप को फिर चलाता है। Rust और TSX दोनों वाले ऐप में TSX के बदलाव हॉट अपडेट रहते हैं (नीचे देखें) और Rust के बदलाव ऐप को रीस्टार्ट करते हैं।
#Hot module replacement
TSX डेवलपमेंट बिल्ड हर सोर्स फ़ाइल के लिए एक मॉड्यूल रखते हैं। जब आप सेव करते हैं, NSH दोबारा बिल्ड करता है, मॉड्यूल की तुलना पिछले अच्छे बिल्ड से करता है और केवल बदले हुए मॉड्यूल वाला एक पैच भेजता है। होस्ट उसे एक मिलीसेकंड से भी काफ़ी कम समय में उसी जगह लागू कर देता है; फ़ाइल सेव करने से बदलाव दिखने तक लगभग 120 ms लगते हैं।
आपके ऐप के साथ क्या होता है:
- केवल वही कॉम्पोनेंट दोबारा रेंडर होते हैं जिन्हें आपने बदला। हर टॉप-लेवल कॉम्पोनेंट (बड़े अक्षर से शुरू होने वाला फ़ंक्शन जो JSX लौटाता है) एक स्थिर प्रॉक्सी में लिपटा होता है। जब उसका कोड बदलता है (या उसके मॉड्यूल का बाकी कोड, जिसका वह उपयोग कर सकता है), तो उसके इंस्टेंस नए कोड के साथ दोबारा रेंडर होते हैं; जिन कॉम्पोनेंट का कोड नहीं बदला, वे अपने इंस्टेंस रखते हैं, और उनके साथ अपने सिग्नल और स्टेट भी। किसी बटन की स्टाइल बदलें और स्क्रीन वहीं रहती है, उसी इनपुट टेक्स्ट और स्क्रॉल पोज़िशन के साथ।
- जो मॉड्यूल बदले वे दोबारा चलते हैं, और वे मॉड्यूल भी जो बदले हुए मॉड्यूल को इम्पोर्ट करते हैं और अपडेट खुद नहीं ले सकते, एंट्री तक। इसलिए मॉड्यूल-लेवल कोड दोबारा चलता है, सिवाय उसके जो अपनी स्टेट बनाए रखता है:
- टॉप-लेवल सिग्नल और नेविगेशन स्टैक अपनी स्टेट बनाए रखते हैं: किसी मॉड्यूल के शीर्ष पर
signal(…)याcreateNavStack(…)से बनाconstबदलावों के बीच अपना मान बनाए रखता है (स्टैक अपने पेज), जब तक उसका अपना इनिशियलाइज़र वही रहता है; नए सिरे से शुरू करने के लिए इनिशियलाइज़र बदलें (signal(0)सेsignal(10))। - टॉप-लेवल
context()ऑब्जेक्ट अपनी पहचान बनाए रखते हैं, और नया डिफ़ॉल्ट मान ले लेते हैं, ताकि अलग-अलग मॉड्यूल में प्रोवाइडर और कंज़्यूमर आपस में मेल खाते रहें। - एंट्री बदलने पर ऐप माउंटेड रहता है। उसी कॉम्पोनेंट के साथ दोबारा चला
mount(App)(याmount(() => <App />)) जो कुछ माउंट कर चुका है उसे बनाए रखता है: केवल वही कॉम्पोनेंट दोबारा रेंडर होते हैं जिन्हें आपने बदला।Appको खुद बदलें तो वह दोबारा रेंडर होता है, उन सबके साथ जिन्हें वह रेंडर करता है (उनकी अपनी स्टेट नए सिरे से शुरू होती है), लेकिन मॉड्यूल के शीर्ष पर बना नेविगेटर अपने रूट बनाए रखता है, इसलिए पुश की गई स्क्रीन पुश ही रहती है। जिसmountकॉल में बदलाव हुआ (दूसरा कॉम्पोनेंट, प्रॉप्स), वह ऐप को दोबारा माउंट करता है। - जो कॉम्पोनेंट एरर फेंकता है वह एरर रिपोर्ट करता है और जब तक आप उसे ठीक नहीं करते कुछ रेंडर नहीं करता; बाकी ऐप चलता रहता है। अगला सही सेव उसे ठीक कर देता है।
होस्ट पैच करने के बजाय पूरा ऐप रीलोड करता है जब:
- बदलाव ऐसा NSH मॉड्यूल इम्पोर्ट करता है जो चल रहे ऐप ने अभी तक इम्पोर्ट नहीं किया था (उदाहरण के लिए पहला
import … from "nsh"); nsh.jsonमें ऐप काcapabilitiesबदल गया;- होस्ट वह बिल्ड नहीं चला रहा जिस पर पैच आधारित है (वह अभी कनेक्ट हुआ है, या पिछला लोड विफल रहा);
- कोई पैच लागू नहीं हो पाता।
--release रिलीज़ बिल्ड सर्व करता है, बिना हॉट अपडेट के: हर सेव ऐप को रीलोड करता है। minified बिल्ड जाँचने के लिए इसका उपयोग करें। डेव सर्वर से लोड की गई कोई भी चीज़, --release हो या नहीं, रनटाइम की हार्डनिंग छोड़ देती है (eval उपलब्ध रहता है); देखें सैंडबॉक्स और कैपेबिलिटीज़।
#लॉग
Rust ऐप log क्रेट (log::info!, log::warn!, log::error!) से लॉग करता है: nsh run डेस्कटॉप पर और iOS सिमुलेटर में ये लाइनें ऐप के आउटपुट के साथ अपने टर्मिनल में दिखाता है।
TSX में console.log, console.info, console.debug, console.warn और console.error टारगेट js के साथ नेटिव लॉग में जाते हैं। जब तक कोई होस्ट कनेक्टेड है, उसकी लॉग लाइनें nsh run / nsh dev चलाने वाले टर्मिनल को फ़ॉरवर्ड की जाती हैं: आपकी console.log लाइनें जैसी हैं वैसी (कई डिवाइस कनेक्ट होने पर डिवाइस के नाम के उपसर्ग के साथ), और चेतावनियाँ तथा एरर डिवाइस के नाम के बाद:
• 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 मानों को ब्राउज़र कंसोल की तरह फ़ॉर्मैट करता है: स्ट्रिंग जैसी हैं वैसी, एरर अपने स्टैक के साथ, फ़ंक्शन नाम से, ऑब्जेक्ट और ऐरे JSON के रूप में। जब होस्ट कनेक्टेड नहीं होता, तो लॉग प्लेटफ़ॉर्म के लॉग में जाते हैं (iOS पर Xcode का कंसोल / unified log, Android पर adb logcat)।
जो promise रिजेक्ट हुआ और उसे रिजेक्ट करने वाला काम पूरा होने तक (उसके microtasks चलने के बाद) भी जिसका कोई हैंडलर नहीं है, उसे भी एरर के रूप में लॉग किया जाता है, जैसे ब्राउज़र रिपोर्ट करता है: ऐसा async फ़ंक्शन जो एरर फेंकता है जबकि कोई उसे await नहीं कर रहा, ऐसा Promise.reject(…) जिसे कोई catch नहीं करता, या Rust में ऐसी कॉल जो विफल होती है और जिसका कोई इंतज़ार नहीं कर रहा। यह लाइन वहीं जाती है जहाँ console.error जाता है (टर्मिनल, प्लेटफ़ॉर्म का लॉग, वेब पर ब्राउज़र कंसोल), और उसका स्टैक आपके सोर्स की ओर इशारा करता है:
error iPhone 16 Pro: unhandled rejection: Error: HTTP 404
at loadProfile (App.tsx:12:11)
उसी turn में बाद में जोड़ा गया हैंडलर (const p = load(); …; p.catch(…)) उसे हैंडल किया हुआ मानता है। जहाँ विफलता मायने रखती है, वहाँ promise को await करें या उसे .catch दें।
टर्मिनल का बाकी हिस्सा हर चरण के लिए एक लाइन है (✓ पूरा हुआ, → कहाँ कनेक्ट करना है, • डिवाइस ने क्या किया); NSH जो टूल चलाता है (cargo, खुद होस्ट) उनका आउटपुट केवल तब प्रिंट होता है जब कोई विफल हो। nsh run -v (या NSH_VERBOSE=1) सब कुछ प्रिंट करता है: हर लॉग लाइन अपने लेवल और सोर्स के साथ, cargo का आउटपुट और होस्ट का अपना आउटपुट।
--trace हर commit की लागत जोड़ देता है:
perf commit: 10 ops, 151µs (list js 2µs), 8 mount items, 27 nodes
इन संख्याओं का मतलब जानने के लिए टेस्टिंग देखें।
#एरर ओवरले
ये एरर होस्ट पर ऐप के ऊपर संदेश और स्टैक के साथ लाल ओवरले दिखाते हैं:
- बिल्ड विफलता (सिंटैक्स एरर, अज्ञात एलिमेंट, गायब इम्पोर्ट), उस लाइन की ओर इशारा करने वाले code frame के साथ;
- बंडल के evaluate होने के दौरान हुआ एरर;
- इवेंट हैंडलर, टाइमर या इफ़ेक्ट में अनकॉट एक्सेप्शन।
ओवरले को हटाने के लिए उस पर टैप करें। यह अगले सफल रीलोड पर भी गायब हो जाता है। बिल्ड विफलताएँ टर्मिनल में भी प्रिंट होती हैं:
error building App.tsx failed
App.tsx
x <Buton> names nothing in scope
,-[3:11]
2 | function App() {
3 | return <Buton />;
: ^^^^^
4 | }
बंडलर TypeScript टाइप बिना जाँचे हटा देता है; टाइप एरर के लिए nsh check चलाएँ (TypeScript का नेटिव कंपाइलर, जिसमें tsconfig.json nsh init लिखते हैं; Node की ज़रूरत नहीं) या अपने एडिटर पर भरोसा करें।
रिलीज़ बिल्ड में (रिलीज़ SDK वाला एम्बेडिंग ऐप, पैकेज्ड ऐप) एरर लॉग किए जाते हैं और होस्ट ऐप के एरर हैंडलर (NshAppView.onError, NshView.setErrorListener) को दिए जाते हैं, लेकिन ऐप के ऊपर दिखाए नहीं जाते।
#सोर्स मैप और स्टैक ट्रेस
बंडलिंग एक सोर्स मैप बनाती है जो हर चरण को जोड़ती है (TypeScript → JSX → बंडल), और वह हर बिल्ड के साथ भेजा जाता है। स्टैक ट्रेस प्रिंट या प्रदर्शित होने से पहले रनटाइम बंडल की पोज़िशन को आपके सोर्स में बदल देता है, इसलिए स्टैक App.tsx:2:28 की ओर इशारा करते हैं, बंडल की ओर नहीं:
error Mac Catalyst: uncaught exception: Error: tap boom
at <anonymous> (App.tsx:2:58)
nsh build मैप को bundle.js.map के रूप में लिखता है; जो एम्बेडिंग ऐप किसी फ़ोल्डर से बंडल लोड करता है वह उसे बंडल के बगल से पढ़ता है, और साइन किए गए पैकेज में भी वह होता है, जब तक कि उसे पैकेज के बगल में अलग न रखा गया हो और nsh symbolicate से लागू न किया गया हो (प्रकाशित पैकेजों के एरर)।
#डीबगिंग सुझाव
- बदलाव के बाद कुछ अपडेट नहीं होता: टर्मिनल में बिल्ड एरर देखें; ओवरले उसे होस्ट पर भी दिखाता है।
- ऐप वह पुरानी स्टेट रखता है जिसे आप रीसेट होने की उम्मीद कर रहे थे: हॉट रिप्लेसमेंट जानबूझकर बिना बदले कॉम्पोनेंट की स्टेट, टॉप-लेवल सिग्नल और नेविगेटर को बनाए रखता है। Ctrl-C और
nsh runसे रीस्टार्ट करें, या स्टेट का इनिशियलाइज़र बदलें। - अनुरोध केवल पैकेज्ड या एम्बेडेड बिल्ड में विफल होता है: ऐप की कैपेबिलिटीज़ जाँचें; अस्वीकृतियाँ जोड़ी जाने वाली key के साथ लॉग होती हैं।
- कुछ धीमा है: उसे
nsh test --traceसे headless दोबारा चलाएँ, जो हर नेटिव व्यू ऑपरेशन प्रिंट करता है, या TSX ऐप को--traceके साथ चलाएँ। - एडिटर में टाइप एरर:
nsh runऔरnsh dev.nsh/typesको अप-टू-डेट रखते हैं; देखें प्रोजेक्ट संरचना।