Emulator
The emulator is Piral's answer to the hardest practical problem in micro frontend development: how do you work on your piece of the UI without running the entire platform?
The problem
In any micro frontend system, a pilet needs to run inside the real app shell to behave correctly. The shell provides the layout your component lives inside, the navigation it links into, the shared dependencies it imports from, and the extension slots it might fill.
Developing against a stub or blank page means integration issues surface late, in staging or production. Running the full platform locally requires infrastructure access that's often impractical — especially for contractors, new joiners, or developers on constrained hardware.
What the emulator contains
When you build the app shell, the CLI produces two outputs:
The emulator .tgz contains:
- A debug build of the app shell with source maps, no minification, HMR support
- TypeScript type definitions for
PiletApi— scoped to the installed plugins - The shell's
importmapdeclarations so the pilet knows what's centrally shared - Metadata about the shell version and supported pilet spec
Using the emulator
pilet debug:
- Reads the emulator from
node_modules/my-app-shell - Starts a local web server rendering the full app shell
- Injects the current pilet bundle via an in-memory API stub
- Watches for file changes and hot-reloads in-place
The developer sees their pilet running inside the actual shell — real layout, real navigation, real shared dependencies, real extension slots — without any network access to a live environment.
This loads pilets from the feed alongside your local pilet — useful for testing extension slot interactions.
Distributing the emulator
There are two ways to get the emulator to pilet developers, and neither one ties you to the public npm registry.
As a package. The emulator .tgz is an ordinary npm package, so you publish it wherever your team already hosts packages — a private/company registry (Azure Artifacts, GitHub Packages, Verdaccio, Nexus, Artifactory) or even a purely local registry. The public npmjs.org registry is just one option, not a requirement. Point each pilet at the right registry the same way you would for any scoped dependency (an .npmrc entry), and npm init pilet's --source can read from it.
As an emulator website. If you'd rather not deal with a registry at all, build the emulator as a hosted website and have pilets debug against its URL:
The pilet downloads the debug shell from that URL instead of node_modules. This is handy for teams without a private registry, for onboarding (nothing to install but the pilet itself), and for keeping every pilet on a single, centrally updated shell.
The package approach gives each pilet a pinned, offline-capable shell version and the best TypeScript story. The website approach removes registry setup entirely and guarantees everyone debugs against the same deployed shell. Many teams use both — a website for quick onboarding, packages for pinned CI builds.
Emulator in CI
Pilet build pipelines use the emulator to validate against real shell types:
A type error here means the pilet is calling an API that doesn't exist in the target shell version — caught before it ever reaches a user.
Version coordination
When the app shell adds a new plugin or changes the API, it bumps its version and publishes a new emulator. Pilet teams upgrade their dev dependency:
Pilets that haven't upgraded continue working — old API methods remain present. New methods only appear in TypeScript once the pilet upgrades. This is how backward compatibility is maintained across dozens of independently developed pilets.
Differences from production
These differences exist to make development fast. None affect the pilet's runtime behavior when deployed.