PyScript Package Loading: Preload with py-config vs Runtime Micropip Install
Choose between static preload via py-config and dynamic micropip install in PyScript. Compare startup latency, bundle size, offline use and maintenance, with concrete HTML examples and a validation approach.
23 Dec 2025, 22:16 UTC

The decision
You need Python in the browser with PyScript, but you cannot optimize for startup latency, bundle size, offline use, and maintenance at the same time. The practical choice is how Python packages are made available to your script: static preload via py-config or dynamic installation with micropip at runtime.
The useful takeaway is to preload only the packages required for first paint and keep optional features lazy. That gives deterministic start-up for core functionality and avoids paying download cost for code the user may never trigger.
Constraints that drive the choice
Startup latency is time from page load to first successful import and execution. Bundle size is the amount of Pyodide wheels and assets fetched before the user can interact. Offline use requires packages to be cached and available without network. Maintenance means how easily you can update versions and handle missing packages in Pyodide.
PyScript runs on Pyodide. Package availability is limited to the Pyodide package index, not the full PyPI. Browser security policies and Content Security Policy can block dynamic script loading that micropip relies on. Behavior is version sensitive because Pyodide core and the package index change over time.
Options compared
| Strategy | When packages are fetched | Initial payload | First execution | Offline | Maintenance |
|---|---|---|---|---|---|
| Preload via py-config | Before first script runs, declared in HTML | Larger, deterministic | Fast after load, no install wait | Works if previously cached | Explicit list, easier to audit |
| Dynamic with micropip.install | On demand inside a py-script block | Smaller initial download | Slower first use, async install | Requires network on first use | Flexible, risk of version conflicts |
Trade-offs in practice
Preload improves perceived responsiveness after the initial load and avoids runtime install errors. It increases time-to-first-byte and storage use because all declared packages are downloaded even if unused. It is the safer choice for core dependencies and for offline-first scenarios after the first successful cache.
Lazy micropip install keeps the initial page light and allows conditional features. It introduces async install complexity, possible failures on slow or offline connections, and harder reproducibility because install order and versions are resolved at runtime. CSP restrictions can block the dynamic fetch micropip performs.
Concrete configuration examples
Preload with py-config
Declare packages in the HTML head. PyScript loads them before executing any py-script.
<html lang='en'> <head> <link rel='stylesheet' href='https://pyscript.net/latest/pyscript.css' /> <script defer src='https://pyscript.net/latest/pyscript.js'></script> <py-config> packages = ['numpy'] </py-config> </head> <body> <py-script> from js import performance t0 = performance.now() import numpy as np print('imported', performance.now() - t0) </py-script> </body> </html> Place this file on a static host and open it in a modern browser. The network panel should show requests for Pyodide core and the numpy wheel before the script runs. The console shows import timing measured with JavaScript performance.now.
Runtime install with micropip
Install inside the script when needed. This defers download until the code path executes.
<html lang='en'> <head> <link rel='stylesheet' href='https://pyscript.net/latest/pyscript.css' /> <script defer src='https://pyscript.net/latest/pyscript.js'></script> </head> <body> <py-script> from micropip import install from js import performance t0 = performance.now() await install('numpy') import numpy as np print('installed and imported', performance.now() - t0) </py-script> </body> </html> Run the same file under identical network conditions. Compare the time from page load to the print output and the network requests. With micropip you will see a fetch for the wheel triggered by the await install call, not during initial load.
How to validate the decision
- Open the test HTML locally in a modern browser and observe network requests for Pyodide packages to confirm preload versus lazy fetch.
- Run the script and check console for import success and execution timing using performance.now equivalents exposed via js.
- Test offline by disabling network after initial cache to verify preload works without connection while lazy install fails on first use.
Limitations to keep in mind: not all pip packages are supported in Pyodide, and package versions available in Pyodide may lag upstream. Dynamic loading can be blocked by strict CSP. Version assumptions should be documented because PyScript and Pyodide APIs can change.
A practical rule is to preload a minimal core set that enables first interaction, and use micropip for optional, heavy, or rarely used features with a visible loading state and fallback message.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.