---
url: https://nsh.dev/ru/docs/dev-server-and-debugging
description: "Запускайте приложения на Rust и TSX с помощью nsh run и nsh dev, подключайте симуляторы, эмуляторы и устройства, а также используйте пересборки, горячую замену модулей (HMR), логи, оверлей ошибок и стеки вызовов с привязкой к исходному коду через source map."
---

# Dev-сервер и отладка

Во время разработки NSH собирает ваше приложение при каждом сохранении. Rust-приложение (`nsh.json` задаёт его `"root"`) собирается в собственный раннер, который запускает `nsh run`; изменение его крейта приводит к новой сборке и перезапуску приложения. TSX-приложение отправляется на каждый подключённый хост, который обновляется на месте. Это делают две команды:

- `nsh run` собирает приложение **и** запускает его: окно Mac Catalyst или AppKit, окно GTK в Linux, окно Win32 в Windows, симулятор iOS, устройство или эмулятор Android либо страницу в браузере. Для TSX она также запускает dev-сервер, к которому подключается хост разработки.
- `nsh dev --server-only` запускает dev-сервер для TSX, для хостов, которые вы запускаете сами: ваше собственное приложение с development SDK, устройство в сети или несколько хостов одновременно.

```sh
# 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` в macOS (AppKit), `linux` в Linux, `windows` в Windows. Rust-приложение запускается на всех из них, кроме `gpui`, который встраивает свой корень в приложение GPUI ([GPUI](https://nsh.dev/translations/ru/docs/gpui.md)). |
| `--device <name or UDID>` | Используемый симулятор iOS. По умолчанию: уже запущенный, иначе новейший iPhone (запускается за вас). |
| `--port <port>` | Порт dev-сервера. По умолчанию 8790; если он занят, используется следующий свободный порт. |
| `--release` | Отдавать релизные сборки TSX (минифицированные, без отладочной информации, без горячих обновлений). |
| `--trace` | Выводить в лог время каждого коммита на хосте (TSX-приложения). |

Что делает каждая цель:

- **`catalyst`** (macOS) запускает хост разработки как приложение для Mac. Остановка `nsh run` (Ctrl-C или `kill`) закрывает его, а закрытие окна завершает `nsh run`.
- **`macos`** (macOS) так же запускает хост разработки AppKit, десктопное приложение в окне 1200×800; Cmd+R перезагружает последнюю сборку. Требуется macOS 14 или новее ([macOS](https://nsh.dev/translations/ru/docs/macos.md)).
- **`linux`** (Linux) так же запускает хост разработки GTK в окне 390×844: остановка `nsh run` закрывает его, закрытие окна завершает `nsh run`. Требуется GTK 4.14 или новее ([Linux](https://nsh.dev/translations/ru/docs/linux.md)).
- **`windows`** (Windows) так же запускает хост разработки Win32 в окне 1200×800; хост никогда не переживёт `nsh run`, даже если консоль закрыта. Требуется Windows 10 1809 или новее ([Windows](https://nsh.dev/translations/ru/docs/windows.md)).
- **`ios`** устанавливает хост разработки в симулятор, запускает его с указанием на dev-сервер и продолжает следить за изменениями. Симулятор использует сеть 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, завершает хост разработки в симуляторе или принудительно останавливает его на устройстве.

Для Rust-приложения каждая цель запускает собственный раннер приложения, собранный вместе с его крейтом: на десктопах это отладочная сборка на месте, с выводом в терминал; на iOS и Android это пакет раннера, который устанавливается. Для TSX хосты универсальные: они запускают любое приложение NSH, которое присылает сервер, поэтому для каждого приложения ничего собирать не нужно (приложению с собственным крейтом достаются хосты, собранные вместе с ним).

## `nsh dev`

```sh
nsh dev App.tsx
```

```text
✓ 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>` | Порт dev-сервера. По умолчанию 8790 (следующий свободный, если он занят). |
| `--release` | Отдавать релизные сборки. |

Подключайте хосты к выведенному URL:

- **Ваше собственное iOS-приложение**, скомпонованное с `NativeShellDev.xcframework`:
  `try app.connectDevServer(URL(string: "ws://192.168.1.20:8790/ws")!)`. См. [Embedding in iOS apps](https://nsh.dev/translations/ru/docs/ios.md#development).
- **Ваше собственное 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`. См. [Embedding in Android apps](https://nsh.dev/translations/ru/docs/android.md#development).

Одновременно можно подключить любое число хостов; каждая сборка отправляется всем. Хост, подключившийся позже, сразу получает текущую сборку. Хост, который не может запустить приложение, потому что в нём нет набора UI, который импортирует приложение (хост разработки, собранный для другого приложения), не получает сборку: он показывает причину, а терминал выводит строку `warn` с командой, которая устанавливает хост приложения.

> [!NOTE]
> Устройство в локальной сети должно иметь доступ к вашему компьютеру по порту dev-сервера: та же сеть, между ними нет файрвола.
> Ассеты (изображения, шрифты) отдаются по обычному HTTP с того же порта.

<a id="rust-edits"></a>

## Изменения в Rust

Rust компилируется: изменение крейта приложения или крейта, от которого оно зависит по пути, приводит к новой сборке приложения, а `nsh run` перезапускает его с новым кодом. Состояние приложения начинается заново; то, что должно пережить перезапуск, храните в [storage](https://nsh.dev/translations/ru/modules/nsh.md#storage). Ошибка сборки выводится в терминал, а следующее сохранение, которое компилируется, запускается снова. В приложении с Rust и TSX правки TSX остаются горячими обновлениями (см. ниже), а правки Rust перезапускают приложение.

<a id="hot-module-replacement"></a>

## Горячая замена модулей

Development-сборки TSX хранят по одному модулю на каждый исходный файл. При сохранении NSH пересобирает проект, сравнивает модули с последней успешной сборкой и отправляет **патч** только с изменившимися модулями. Хост применяет его на месте существенно быстрее миллисекунды; от сохранения файла до появления изменения проходит примерно 120 мс.

Что происходит с вашим приложением:

- **Заново отрисовываются только отредактированные компоненты.** Каждый компонент верхнего уровня (функция с заглавной буквы, возвращающая JSX) обёрнут в стабильный прокси. Когда его код меняется (или меняется остальной код его модуля, который он может использовать), его экземпляры отрисовываются заново с новым кодом; компоненты, код которых не менялся, сохраняют свои экземпляры, а вместе с ними сигналы и состояние. Измените стиль кнопки, и экран останется на месте, с тем же введённым текстом и той же позицией прокрутки.
- **Изменившиеся модули выполняются заново**, как и модули, которые импортируют изменившийся модуль и не могут принять обновление сами, вплоть до входной точки. Поэтому код уровня модуля выполняется заново, кроме того, что сохраняет своё состояние:
- **Сигналы и стеки навигации верхнего уровня сохраняют состояние**: `const`, созданный через `signal(…)` или `createNavStack(…)` в начале модуля, сохраняет своё значение (стек сохраняет свои страницы) между правками, пока его собственный инициализатор тот же; чтобы начать заново, измените инициализатор (`signal(0)` на `signal(10)`).
- **Объекты `context()` верхнего уровня сохраняют идентичность**, получая новое значение по умолчанию, поэтому провайдеры и потребители в разных модулях продолжают соответствовать друг другу.
- **Редактирование входной точки не размонтирует приложение.** Повторный запуск `mount(App)` (или `mount(() => <App />)`) с тем же компонентом сохраняет то, что было смонтировано: заново отрисовываются только отредактированные компоненты. Если отредактировать сам `App`, он отрисуется заново вместе с тем, что он отрисовывает (их собственное состояние начинается заново), но навигатор, созданный в начале модуля, сохраняет свои маршруты, поэтому открытый экран остаётся открытым. Изменившийся вызов `mount` (другой компонент, другие props) монтирует приложение заново.
- **Компонент, который выбрасывает исключение**, сообщает об ошибке и ничего не отрисовывает, пока вы её не исправите; остальное приложение продолжает работать. Следующее удачное сохранение восстанавливает его.

Хост **перезагружает всё приложение** вместо применения патча, когда:

- изменение импортирует модуль NSH, который запущенное приложение ещё не импортировало (например, первый `import … from "nsh"`);
- изменился `capabilities` приложения в `nsh.json`;
- хост не запускает сборку, на которой основан патч (он только что подключился или предыдущая загрузка не удалась);
- патч невозможно применить.

> [!TIP]
> Состояние, которое компонент хранит в собственных сигналах, сбрасывается, когда редактируется этот компонент или компонент, который его отрисовывает. Состояние, которое должно пережить правки, храните в сигналах верхнего уровня (или в модуле хранилища), а навигаторы создавайте в начале модуля.

`--release` отдаёт релизные сборки без горячих обновлений: каждое сохранение перезагружает приложение. Используйте его, чтобы проверить минифицированную сборку. Всё, что загружается с dev-сервера, `--release` или нет, обходит усиленную защиту рантайма (`eval` остаётся доступным); см. [Sandbox and capabilities](https://nsh.dev/translations/ru/docs/sandbox-and-capabilities.md#hardening).

<a id="logs"></a>

## Логи

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` как есть (с префиксом в виде имени устройства, если подключено несколько устройств), предупреждения и ошибки после имени устройства:

```text
• 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. Когда хост не подключён, логи попадают в лог платформы (консоль Xcode / unified log в iOS, `adb logcat` в Android).

Promise, который отклонён и всё ещё не имеет обработчика после завершения работы, вызвавшей отклонение (его микрозадачи выполнены), тоже записывается в лог как ошибка, так же как это делает браузер: функция `async`, которая выбрасывает исключение, пока её никто не ожидает через await, `Promise.reject(…)`, который никто не перехватывает, вызов в Rust, который завершается ошибкой, когда его никто не ждёт. Строка попадает туда же, куда и `console.error` (в терминал, лог платформы, консоль браузера в вебе), а её стек указывает на ваши исходники:

```text
error iPhone 16 Pro: unhandled rejection: Error: HTTP 404
          at loadProfile (App.tsx:12:11)
```

Обработчик, добавленный позже в том же такте (`const p = load(); …; p.catch(…)`), считается обработкой. Ожидайте promise через await или добавляйте `.catch` там, где важен сбой.

Остальной вывод терминала — одна строка на шаг (`✓` готово, `→` куда подключаться, `•` что сделало устройство); вывод инструментов, которые запускает NSH (cargo, сами хосты), печатается только при их сбое. `nsh run -v` (или `NSH_VERBOSE=1`) печатает всё: каждую строку лога с уровнем и источником, вывод cargo и собственный вывод хостов.

`--trace` добавляет стоимость каждого коммита:

```text
perf commit: 10 ops, 151µs (list js 2µs), 8 mount items, 27 nodes
```

О том, что означают эти числа, см. [Testing](https://nsh.dev/translations/ru/docs/testing-and-trace.md).

<a id="the-error-overlay"></a>

## Оверлей ошибок

Эти ошибки показываются красным оверлеем поверх приложения на хосте, с сообщением и стеком:

- сбой сборки (синтаксическая ошибка, неизвестный элемент, отсутствующий импорт) с фрагментом кода, указывающим на строку;
- ошибка при выполнении бандла;
- необработанное исключение в обработчике события, таймере или эффекте.

Нажмите на оверлей, чтобы скрыть его. Он также исчезает при следующей успешной перезагрузке. Сбои сборки выводятся и в терминал:

```text
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`](https://nsh.dev/translations/ru/docs/cli.md#nsh-check) (нативный компилятор TypeScript с записями `tsconfig.json` `nsh init`; Node не нужен) или полагайтесь на редактор для поиска ошибок типов.

В релизных сборках (встраивающее приложение с релизным SDK, упакованное приложение) ошибки записываются в лог и передаются обработчику ошибок хост-приложения (`NshAppView.onError`, `NshView.setErrorListener`), но поверх приложения не показываются.

<a id="source-maps-and-stack-traces"></a>

## Source maps и стек-трейсы

При бандлинге создаётся source map, объединяющая все шаги (TypeScript → JSX → бандл), и она отправляется вместе с каждой сборкой. Перед тем как стек-трейс будет выведен или показан, рантайм переписывает позиции в бандле на позиции в ваших исходниках, поэтому стеки указывают на `App.tsx:2:28`, а не на бандл:

```text
error Mac Catalyst: uncaught exception: Error: tap boom
          at <anonymous> (App.tsx:2:58)
```

`nsh build` записывает карту как `bundle.js.map`; встраивающее приложение, которое загружает бандл из папки, читает её рядом с бандлом, а [подписанные пакеты](https://nsh.dev/translations/ru/docs/signed-packages.md) тоже содержат её, если только она не хранится отдельно от пакета и не применяется через `nsh symbolicate` ([Errors from published packages](https://nsh.dev/translations/ru/docs/signed-packages.md#errors-from-published-packages)).

<a id="debugging-tips"></a>

## Советы по отладке

- **После правки ничего не обновляется**: проверьте терминал на ошибку сборки; оверлей показывает её и на хосте.
- **Приложение хранит старое состояние, которое вы ожидали сбросить**: горячая замена намеренно сохраняет состояние неизменившихся компонентов, сигналов верхнего уровня и навигаторов. Перезапустите через Ctrl-C и `nsh run` или измените инициализатор состояния.
- **Запрос завершается ошибкой только в упакованной или встроенной сборке**: проверьте [capabilities](https://nsh.dev/translations/ru/docs/sandbox-and-capabilities.md) приложения; отказы записываются в лог с ключом, который нужно добавить.
- **Что-то работает медленно**: воспроизведите это в headless-режиме с помощью [`nsh test --trace`](https://nsh.dev/translations/ru/docs/testing-and-trace.md), который печатает каждую нативную операцию над представлением, или запустите TSX-приложение с `--trace`.
- **Ошибки типов в редакторе**: `nsh run` и `nsh dev` поддерживают `.nsh/types` в актуальном состоянии; см. [Project structure](https://nsh.dev/translations/ru/docs/project-structure.md).

<a id="see-also"></a>

## См. также

- [CLI](https://nsh.dev/translations/ru/docs/cli.md)
- [Testing](https://nsh.dev/translations/ru/docs/testing-and-trace.md)
- [Embedding in iOS apps](https://nsh.dev/translations/ru/docs/ios.md), [Embedding in Android apps](https://nsh.dev/translations/ru/docs/android.md)
