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?

App Shell the host application npx piral build emits two artifacts Emulator my-shell-1.0.tgz debug shell + HMR + PiletApi types install Your Pilet real app shell your pilet page npm start · hot reload
The emulator lets a pilet run inside the real shell locally — no platform access required.

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:

dist/
├── release/            ← deploy to production
└── emulator/
    └── my-app-shell-1.0.0.tgz   ← share with pilet developers

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 importmap declarations so the pilet knows what's centrally shared
  • Metadata about the shell version and supported pilet spec

Using the emulator

# The emulator is installed as a dev dependency when you scaffold the pilet.
# To add or update it manually:
npm install --save-dev my-app-shell

# Start local pilet development
npm start
# or explicitly (the target shell is read from package.json):
npx pilet debug

pilet debug:

  1. Reads the emulator from node_modules/my-app-shell
  2. Starts a local web server rendering the full app shell
  3. Injects the current pilet bundle via an in-memory API stub
  4. 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.

Load live pilets alongside your local one
npx pilet debug --feed https://feed.example.com/api/v1/pilets

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.

npx piral publish --type emulator --registry https://npm.my-company.com

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:

npx piral build --type emulator-website
# deploy dist/emulator-website/ to any static host, then in the pilet:
npx pilet debug --app https://emulator.my-portal.example.com

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.

Which to choose

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:

- run: npm ci                 # installs emulator from package-lock
- run: npx tsc --noEmit       # type-checks against real PiletApi types
- run: npx pilet build        # confirms the bundle builds cleanly
- run: npx pilet validate     # validates pilet format and metadata

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:

npx pilet upgrade          # upgrade to latest emulator
npx pilet upgrade 2.0.0    # pin to a specific version

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

AspectEmulatorProduction
MinificationNoYes
Source mapsYesOptional
Hot reloadYesNo
Pilet served fromlocalhost API stubCDN URL
Error displayDevTools overlayError boundary
Other piletsOptional via --feedAll from feed response

These differences exist to make development fast. None affect the pilet's runtime behavior when deployed.