Fast Refresh¶
Fast Refresh turns the edit-save-rebuild loop into edit-save-see. The
dev server watches app/, pushes each changed file
to every connected client (the browser preview, simulators, emulators,
phones), and each client reloads the affected modules and refreshes its
mounted screens in place, keeping component state.
There's nothing to turn on. pn start (or pn preview) is the server;
any debug build launched with pn run while it's running is a client.
What happens on save¶
- The server's watcher notices
app/screens/home.pychanged, updates its manifest, and broadcasts anupdatewith the new contents. - Each client writes the file into its overlay, a writable
directory that sits ahead of the bundled sources on
sys.path(the browser preview has no overlay; the project directory is already on its path). - The client resolves the path to a module (
app.screens.home) and callsapply_reloadon the application thread. apply_reloadre-executes the changed module, then the other imported modules underappthat may hold bindings to it (the entry module'sfrom app.screens.home import HomeScreen, for instance), leaves first.- The application host runs Fast Refresh: it walks its VNode
tree, finds each component function whose module was reloaded,
looks up the replacement by
__module__+__qualname__, and rewrites theElement.typereferences in place. The next reconcile sees the new function with the sameHookState, so state survives when the captured hook signature is compatible. Hook-order or custom-hook changes remount the affected component. Helper-class and service changes trigger an application remount. - The host re-renders. Layout and native views update incrementally through the normal reconciler path.
- The client reports back (
fast_refresh: app.screens.home 42ms), which prints in thepn startterminal and toasts in the preview.
If Fast Refresh can't find a clean swap (a component's __qualname__
changed, a render raised with the new function, or the swap itself
failed), the host falls back to a full remount of its root, so you
never get stuck with a stale tree. Hook state is reset in that case
and the report says remount.
If the saved file fails to import (a syntax error mid-edit), the
previous module stays in sys.modules, the traceback shows in the
RedBox and the terminal, and the app keeps running. Fix the file and
save again.
Refresh walks the application's shared logical tree, including covered screens and mounted list rows. Native containers don't create separate Python hosts.
What gets reloaded¶
Any .py file under app/. Assets under app/ (images, JSON, fonts)
are synced too, so an Image that points at a bundle-relative file
picks up the new bytes the next time it renders.
What doesn't reload¶
- Native template files (anything under
android_template/orios_template/) and native plugins. Changes there require a rebuild;pn rundetects them through the native fingerprint and runs the toolchain automatically. pythonnative.toml. Permissions, requirements, and app metadata are native inputs;pn runrebuilds when it changes.- Files outside
app/. If you have a shared library next to your project, copy or symlink it underapp/to pick up changes. - C extension modules. Recompiled
.so/.dyliblibraries are not reloaded mid-session. - The
pythonnativepackage itself. Reinstall and rebuild.
Common pitfalls¶
Top-level side effects
Code that runs at import time (a global registry that registers itself when the module is imported) runs again on every reload. Idempotent registration is fine; non-idempotent setup (counters, network calls, opening files) needs guarding.
References across modules
If module a does from b import Foo and only b.py changes,
a is re-executed too so its binding updates, but long-lived
references stashed elsewhere (a module-level cache, an object held
in use_ref) can drift. When in doubt, use Reload app in the
preview or relaunch the device build.
Hook signature changes
Adding or removing a hook in a component changes the slot layout. Fast Refresh compares captured hook signatures before preserving state. Hook-order and custom-hook changes remount affected component instances. Changes to helper classes or services remount the application.
Renaming a component
Fast Refresh keys on each function's __qualname__. Renaming a
component changes the key, so the live VNode keeps its old
function until the parent re-renders with the new name. Trigger a
navigation or state change, or reload the app.
Without a dev server¶
Fast Refresh needs pn start running. If you pn run without one, the
CLI says so and builds an app that runs its bundled sources; start the
server and relaunch to connect it. For rebuild-on-every-change (more
predictable, much slower), pass --rebuild.
Reading logs¶
Every dev client mirrors its print output, warnings, and tracebacks
to the pn start terminal, so you rarely need a device log viewer. For
native-level output, pn logs ios / pn logs android attach to
os_log and logcat; pn run does the same after launching unless
you pass --no-logs.
Next steps¶
- The whole loop: Development workflow.
- Reference: Hot reload API and Dev server API.