# Host-less Mode _Path: en/frontend/micro-frontends/host-less-mode_ ## Table of Contents - Host-less Mode ## Content # Host-less Mode Authoritative guide for the standalone-aware design contract that lets every Wippy micro frontend app and web component build, run, and test **without** the Wippy Web Host wrapping it. > **Default injection state:** The dev overlay starts with `themeConfig`, `primevue`, `markdown`, and `iframe` **disabled**, but `customCss` and `customVariables` **enabled**. So an app that relies only on custom overrides may appear to work, while one that expects the platform theme variables or PrimeVue styles will render unstyled until you enable those injections. Open the overlay FAB → enable the injections you need → check "Auto-accept on reload" to persist across reloads. --- ## Table of contents - [Mental model — apps and WCs are intentionally standalone-aware](#mental-model--apps-and-wcs-are-intentionally-standalone-aware) - [The `@wippy/scripts` switchpoint — one tag, two boot paths](#the-wippyscripts-switchpoint--one-tag-two-boot-paths) - [What `dev-proxy.js` actually does](#what-dev-proxyjs-actually-does) - [The dev overlay (config modal)](#the-dev-overlay-config-modal) - [Host stubs — the standalone `host` API](#host-stubs--the-standalone-host-api) - [Web components — host-less playground and tests](#web-components--host-less-playground-and-tests) - [Common deviations and how to spot them](#common-deviations-and-how-to-spot-them) - [Troubleshooting](#troubleshooting) - [Related docs](#related-docs) --- ## Mental model — apps and WCs are intentionally standalone-aware Every Wippy micro frontend app and web component is built around a small, deliberate constraint: > **The runtime contract is the proxy API surface. Nothing else.** What that means in practice: - The only thing an app or WC touches at runtime is the proxy API surface: the sync getters imported from `@wippy-fe/proxy` (`host`, `api`, `on`, `config`, `state`, `ws`, `logger`). Both apps and WCs use the same imports; under the hood they resolve to the same `ProxyApiInstance` that the runtime installs as internal globals (`window.$W`, `window.__WIPPY_APP_API__` — never read these directly). - Apps and WCs do **not** import code from neighboring apps, the parent module's Lua side, the Wippy Web Host, or any other module in the project. They live in their own folder, declare their externals (`vue`, `pinia`, `vue-router`, `@iconify/vue`, `axios`, `@wippy-fe/proxy`, etc.) in their own `package.json`, and read their own `wippy.yaml` / `package.json` metadata. - The same `app.ts` (or WC `index.ts`) boots correctly in two environments: 1. **Hosted** — inside a Wippy Web Host that injects `proxy.js`, AppConfig, importmap, and CSS. 2. **Host-less** — running its `app.html` directly via Vite dev server, file://, a unit-test page, a Storybook-style playground, etc. You can think of every app/WC as a "small program with a tiny standardized I/O surface." The host is one possible runtime; standalone is another. The app code does not know which one it's in. This isn't an accident or an afterthought. It is what makes: - Local FE iteration possible without spinning up a full Wippy backend. - WCs unit-testable in isolation under vitest + jsdom. - Apps shareable between Wippy modules — every micro-frontend-app and web component builds with the same toolchain regardless of which module ships it. - Customer-specific overlays viable — operators patch metadata (theming, importmap, env) without rebuilding the FE bundle. --- ## The `@wippy/scripts` switchpoint — one tag, two boot paths Every canonical app's `app.html` ships with **one** script tag that decides the boot path at load time: ```html ``` Full `app.html` scaffold in [Micro Frontend App](./micro-frontend-app.md). Two attributes on that one tag carry the entire dual-mode contract: | Attribute | Role | Used by | |---|---|---| | `data-role="@wippy/scripts"` | Marker for the host. When present, the host removes this ` ``` Conventions: - **Use `https://esm.sh/` URLs.** They're the de-facto Wippy default (see canonical app-template apps); no build step or local server needed. - **Pin majors only** (`vue@3`, `vue-router@4`) unless you have a reason to lock a minor. esm.sh resolves to the latest patch automatically and the host's importmap (which overrides yours when wrapping) decides the canonical version anyway. - **Don't include `@wippy-fe/proxy`.** dev-proxy.js / the host injects it for you. The same goes for `@wippy-fe/markdown-iframe` (only include it explicitly if your app code imports the markdown iframe directly — the canonical app-template main app does). - **Don't include packages that are not external.** Anything bundled into your output (your shared utils, internal components) doesn't need an entry. The host's `processWebPage` merges the host's importmap with whatever you declare in `app.html` — keys you declare are kept, host adds the wippy-side entries. So the same `app.html` works in both modes without conditionals. ### Exposing `package.json` to dev-proxy (canonical scaffold) Every Wippy app's `package.json` carries metadata that determines runtime defaults — proxy injections (`wippy.proxy.injections.css.*`), per-page theming overrides (`wippy.configOverrides.customization`), iconify icon collections, etc. In hosted mode the host reads these from the registry. In host-less mode dev-proxy needs the same data to apply the same defaults. The canonical pattern is `wippyPagePlugin()` from `@wippy-fe/vite-plugin` ≥ `0.0.32`, added once to your `vite.config.ts`. The plugin reads your `package.json` at build time and does **two** things: 1. **Resolves `file://` references** in the `wippy` block (any string value of the form `"file://"` is replaced with the referenced file's UTF-8 contents — see `*.do-not-link.` naming convention in [build-system.md](./build-system.md)). 2. **Emits two outputs** with the resolved JSON: - ``-injected ` ``` dev-proxy.js reads this synchronously at boot via `document.querySelector('script[data-role="@wippy/package"]')` and uses `wippy.proxy.injections` to seed the proxy-config defaults and `wippy.configOverrides.customization` to seed `appConfig.theming.global`. The data-role string `@wippy/package` is exported as `WIPPY_PACKAGE_DATA_ROLE` from `@wippy-fe/shared` so both sides of the boundary share the constant. Why this shape: - **No duplication.** `package.json` is the single source of truth — the plugin reads it at build time, nothing in your `src/` references it. - **No fetch.** Inline in the served HTML — readable synchronously by `dev-proxy.js` before any app code runs. - **Right ordering.** Injected at the top of `` before any script tag, so it's in the DOM by the time the dev-proxy executes (dev-proxy is a sync UMD script; module scripts are deferred and run later). - **No `app.html` editing.** The template stays clean; the plugin owns the injection. - **Constant from shared package.** The string `'@wippy/package'` lives in exactly one place (`@wippy-fe/shared` → `WIPPY_PACKAGE_DATA_ROLE`); apps don't reference it directly, dev-proxy and the plugin both import it from there. - **Cleanly ignored under a real host.** The host's `processWebPage` reads `package.json` from the registry server-side; the inline JSON tag is harmless metadata. dev-proxy reads the JSON during `resolveDevConfig()` and uses it to populate the dev-overlay defaults. If the script tag is absent (older app, plugin not yet added), dev-proxy falls back to `getDefaultProxyConfig()`. So adding the plugin is purely additive — apps without it keep working with the generic defaults. > **Why a plugin and not a runtime `window` global?** Dev-proxy.js is a non-module synchronous script that runs early during `` parsing — before any module script (including your `app.ts`) has loaded. So `app.ts` cannot set a global *before* dev-proxy reads it. A build-time HTML transform places the data in the DOM up-front, available the instant dev-proxy executes. > **Why one tag and not two?** A second ` ``` Same switchpoint, same dev overlay. Your WC's `index.ts` calls `define(import.meta.url, ...)` and the element registers itself; dev-proxy provides the host stubs. If `dev-proxy.js` fails to load (or you forget to include it), `entry.web-component.ts` throws an explicit error: > `@wippy-fe/proxy: Proxy globals not found. For dev/testing without the Wippy host, add to your HTML.` That error is the canonical signal that you're missing the host-less boot script. ### Vitest / jsdom tests For unit tests the dev overlay is unnecessary — tests don't have a UI to interact with. The pattern is to **fake the host context directly** by attaching the wrapper object the host would attach: ```ts import { describe, expect, it } from 'vitest' import { WippyElement } from './base-element' class TestEl extends WippyElement { static get wippyConfig() { return { propsSchema: { properties: {} }, hostCssKeys: [] } } protected onMount(): void {} protected onUnmount(): void {} } const TAG = 'wippy-test-el' customElements.define(TAG, TestEl) it('reads host wrapper attached by resolver as __wippyHost', () => { const el = document.createElement(TAG) as TestEl const fakeHost = { layout: { broadcast: () => {} } } ;(el as any).__wippyHost = fakeHost expect(el.host).toBe(fakeHost) }) ``` The `__wippyHost` property is the contract the managed-layout host uses. Tests that need API or proxy globals can either mount dev-proxy via a vitest setup file, or stub `window.__WIPPY_APP_API__` themselves: ```ts // vitest.setup.ts ;(window as any).__WIPPY_APP_API__ = { api: mockApi, host: mockHost, on: mockOn, // ...other ProxyApiInstance fields } ``` Either approach is "host-less" in the same sense as the browser dev-proxy: the proxy contract is satisfied by code the test owns rather than a real Wippy server. --- ## Common deviations and how to spot them When an app or WC has drifted from the standalone-aware contract, the symptoms are predictable: | Symptom | Probable cause | Fix | |---|---|---| | `app.html` has `` with no `src=` | Page can't boot host-less. Loading the file directly produces a blank page — the proxy runtime never installs, so `@wippy-fe/proxy` imports fail to resolve. | Add `src="https://web-host.wippy.ai//dev-proxy.js"` to the tag — the URL always requires a release-tag segment. | | `app.html` has the dev-proxy `