{"owner":"umijs","repo":"qiankun","hasSkills":true,"totalSkillsCount":19,"totalTokensCount":19135,"categories":["anthropic-skill","root-instruction","claude-rule","plugin-manifest"],"hasMcp":false,"mcpConfig":null,"found":[".claude/skills/release-changelog/SKILL.md","AGENTS.md","CLAUDE.md","packages/bundler-plugin/README.md","packages/loader/AGENTS.md","packages/loader/README.md","packages/loader/src/writable-dom/README.md","packages/qiankun/README.md","packages/sandbox/AGENTS.md","packages/sandbox/README.md","packages/shared/AGENTS.md","packages/shared/README.md","packages/single-spa/README.md","packages/ui-bindings/react/README.md","packages/ui-bindings/shared/README.md","packages/ui-bindings/vue/README.md","skills/qiankun/SKILL.md","skills/qiankun/references/create-main-app.md","skills/qiankun/references/create-micro-app.md"],"skills":{".claude/skills/release-changelog/SKILL.md":"---\nname: release-changelog\ndescription: Polish a published qiankun GitHub Release's aggregated notes into a bilingual (English then Chinese) write-up with an agent. Use after a release goes out — turning the mechanically-aggregated, per-package release notes into a clean, human-readable, English + Chinese write-up (highlights, de-duped cross-package lines, clearer wording) via `gh release edit`. Triggers include \"polish the release notes\", \"clean up the GitHub release\", \"improve the changelog for the latest release\".\n---\n\n# Release changelog\n\nQiankun releases are commit-driven: CI auto-derives changesets from Conventional Commits, changesets writes each sub-package's own `CHANGELOG.md` (so npm shows a per-package changelog), and on publish `scripts/generate-release-notes.mjs` aggregates those into **one GitHub Release** per version.\n\nThat aggregated Release is **mechanical** (raw per-package changelog lines). This skill polishes it into a human-readable, **bilingual** write-up — an English version on top, a Chinese version below — after the fact, directly on the GitHub Release, without touching any committed changelog. Read `.changeset/README.md` for the surrounding flow.\n\n## When to run\n\nAfter a release has been published (the \"Version Packages\" PR was merged and CI created the GitHub Release). Typically: \"polish the latest release notes\".\n\n## Steps\n\n1. **Find the release to polish** (default: the most recent one):\n\n   ```bash\n   gh release list --limit 5\n   gh release view <tag> --json tagName,name,body\n   ```\n\n   Confirm the target tag with the user if it's not obviously the latest.\n\n2. **Read the mechanical notes** from the release `body`. This is the raw material — the aggregated per-package changelog. Do not invent anything not present in it (cross-check against `git log <prev-tag>..<tag>` only to understand, not to add unlisted changes).\n\n3. **Polish** the notes, keeping the per-package structure so consumers still see each sub-package and its version. Polishing means:\n   - Lead with a short **Highlights** list (2–5 bullets) when the release has notable user-facing changes; skip it for small patch releases.\n   - Rewrite terse lines into clear, user-facing wording; keep the `(#PR)` reference at the end of each line.\n   - De-duplicate: a change that touched many packages is often better stated once under the facade `qiankun` section than repeated under every package.\n   - Drop noise (pure \"Updated dependencies\" internal bumps).\n\n4. **Produce a bilingual document** — the full **English** version first, then the full **简体中文** version, separated by a `---` rule. Keep the identical per-package structure and `(#PR)` references in both halves; translate the prose (highlights, descriptions), never the package names, versions, or PR numbers. Template:\n\n   ```markdown\n   <!-- English -->\n\n   ## Highlights\n\n   - ...\n\n   ## qiankun `<version>`\n\n   - ...\n\n   ---\n\n   <!-- 简体中文 -->\n\n   ## 更新亮点\n\n   - ...\n\n   ## qiankun `<version>`\n\n   - ...\n   ```\n\n5. **Show the user the proposed notes** and get approval.\n\n6. **Update the release** (does not re-tag or re-publish npm):\n\n   ```bash\n   gh release edit <tag> --notes-file <polished.md>\n   ```\n\n## Do not\n\n- Do not run `gh release create`, re-tag, or re-publish to npm — only edit the notes of an existing release.\n- Do not edit any `CHANGELOG.md` or `.changeset/*` — the per-package changelogs are the committed record; this skill only refines the GitHub Release prose.\n- Do not add changes that aren't in the mechanical notes / commit history.\n","AGENTS.md":"# QIANKUN PROJECT KNOWLEDGE BASE\n\n**Updated:** 2026-07-06 · **Commit:** dcc42ae4 · **Branch:** next (qiankun 3.0, active dev)\n\nQiankun is a micro-frontend framework built on [single-spa](https://github.com/single-spa/single-spa). v3 rewrites the runtime around **streaming HTML-entry loading**, a **Proxy-membrane JS sandbox**, and native **ESM-sandbox** execution. It is a pnpm monorepo built with Vite 8; `tsc` emits package declarations.\n\n> Requires Node `^22.15 || >=24`, `pnpm@11.13.1` (see `packageManager`). Never use npm/yarn at the root.\n\n## STRUCTURE\n\n```\nqiankun/\n├── packages/\n│   ├── qiankun/         # Facade: public APIs (register/loadMicroApp, start, prefetch) + loadApp orchestration\n│   ├── sandbox/         # JS isolation: Proxy membrane + Compartment  → packages/sandbox/AGENTS.md\n│   ├── loader/          # Streaming HTML-entry loader (writable-dom)  → packages/loader/AGENTS.md\n│   ├── shared/          # Transpilers, fetch-utils, module-resolver, ESM-sandbox engine → packages/shared/AGENTS.md\n│   ├── single-spa/      # Vendored single-spa fork (@qiankunjs/single-spa, upstream 7.0 @ ce0f925a) → packages/single-spa/README.md\n│   ├── ui-bindings/     # <MicroApp> components: react/ vue/ shared/ (dogfooded by examples/{main,vue-host})\n│   └── bundler-plugin/  # Webpack(4/5) + Vite plugins: mark entry script, fix output library\n├── skills/              # Agent skills (installed by users via `npx skills add umijs/qiankun`) — content, not code: no build/publish\n├── e2e/                 # Playwright, runs against BUILT dist → e2e/README.md\n├── examples/            # Runnable demos: two hosts (main = @qiankunjs/react, vue-host = @qiankunjs/vue) + react/vue/purehtml/webpack\n└── docs/                # VitePress site (docs/rfcs holds design RFCs)\n```\n\n## PROGRESSIVE DISCLOSURE — read the scoped doc before editing a package\n\n| Working in… | Read first |\n| --- | --- |\n| `packages/sandbox/**` | [`packages/sandbox/AGENTS.md`](packages/sandbox/AGENTS.md) — membrane, compartment, patchers, ESM globals |\n| `packages/loader/**` | [`packages/loader/AGENTS.md`](packages/loader/AGENTS.md) — streaming pipeline, head virtualization |\n| `packages/shared/**` | [`packages/shared/AGENTS.md`](packages/shared/AGENTS.md) — transpilers, fetch decorators, **esm-sandbox** |\n| `e2e/**` | [`e2e/README.md`](e2e/README.md) — Playwright layout, fixtures, anti-flake rules |\n\n## ARCHITECTURE (big picture)\n\n`loadApp` (`packages/qiankun/src/core/loadApp.ts`) is the orchestrator. Per micro-app it wires:\n\n1. **fetch** — decorated `window.fetch`: `makeFetchCacheable(makeFetchRetryable(makeFetchThrowable(fetch)))`.\n2. **sandbox** — `createSandbox()` builds a Proxy-membrane `window`/`document` view; patchers (dynamicAppend, timers, listeners, history) each return a `free()` cleanup called on unmount. Without a container it uses the JS-only preset; a container enables DOM containment.\n3. **loader** — `loadEntry(entry, container, opts)` streams the HTML entry through `writable-dom`, virtualizing `<head>` → `<qiankun-head>` and running each node through a `nodeTransformer`.\n4. **transpilers** (`shared/assets-transpilers`) rewrite each script/link/style node before it hits live DOM.\n\nMicro apps sharing one container element take FIFO turns for their DOM writes through the **container occupancy gate** (`core/containerOccupancy.ts`, two critical sections: load-phase streaming and mount→unmount; see `docs/rfcs/container-occupancy-gate.md`).\n\nTwo execution paths, chosen per script type:\n\n- **Classic** (`<script entry>`, UMD/global): source is wrapped and run via a **blob URL** scoped to the sandbox membrane. The app's export = `sandbox.latestSetProp` (the last global the entry script set).\n- **ESM** (`<script type=\"module\">`): handled by `EsmSandboxEngine` (`shared/esm-sandbox`). Modules are fetched, lexer-rewritten to route globals through the membrane, given synthetic specifiers via a dynamically injected **import map**, and evaluated in order. The engine also handles dynamic `import()`.\n\n**Style isolation** (`shared/assets-transpilers/style.ts` + `link.ts`) uses CSS `@scope` at runtime; external stylesheets become blob-`<link>`s so `@scope` can wrap them. Opt-in via `styleIsolation`.\n\nInternal dependency graph (never invert it):\n\n```\nqiankun → loader → sandbox → shared\n                   sandbox → shared\nqiankun → single-spa   (vendored fork; zero deps, bottom of the graph — must not import shared/sandbox/loader)\nui-bindings/{react,vue} → ui-bindings/shared → qiankun\n```\n\n## COMMANDS\n\n```bash\npnpm install                 # install all workspace deps\n\n# build (Vite 8 → package JavaScript; tsc → declarations)\npnpm run build               # build everything (packages + examples)\npnpm run build:packages      # build only packages/** (prereq for e2e & examples)\n\n# unit tests — vitest + happy-dom, aliased to src (NO build needed, see vitest.config.ts)\npnpm run test                            # all packages\npnpm --filter @qiankunjs/shared run test # one package\npnpm --filter @qiankunjs/sandbox exec vitest run path/to.test.ts   # single file\npnpm --filter @qiankunjs/shared  exec vitest run -t \"test name\"    # single test by name\n\n# e2e — Playwright against BUILT dist (see e2e/README.md)\npnpm run test:e2e            # build all + run chromium suite\n\n# lint / format / full CI gate\npnpm run eslint              # eslint packages/\npnpm run prettier:check      # prettier -c .\npnpm run ci                  # build + eslint + prettier:check (what CI runs)\n\n# dev\npnpm run start:example       # build packages + run all example apps in parallel\npnpm run docs:dev            # VitePress docs\n```\n\n## CONVENTIONS (enforced by ESLint flat config — `pnpm run eslint` will reject violations)\n\nTypeScript is strict + type-checked (`typescript-eslint`'s type-checked flat config):\n\n- **No `any`** — `no-explicit-any` auto-fixes to `unknown`. No `as any`, `@ts-ignore`, `@ts-expect-error`.\n- **Inline type imports** — `import { type Foo, bar }`, not `import type { Foo }` on its own line (`consistent-type-imports`/`consistent-type-exports` with `fixStyle: inline-type-imports`).\n- `no-unnecessary-condition` is an error — don't guard values the types prove are always truthy.\n- Unused vars/args must be prefixed `_` (`argsIgnorePattern: ^_`).\n- `array-simple`: `T[]` for simple, `Array<T>` for complex element types.\n- JS-wide: `no-else-return` (no `else` after `return`), `object-shorthand`.\n- Path alias `@qiankunjs/*` → `packages/*/src` (tsconfig + vitest); imports resolve to **source**, not dist.\n\nBuild/release:\n\n- Vite 8 builds browser package JavaScript as ESM+CJS, while `tsc` emits declarations. Package entry fields and subpath exports must point at the corresponding `dist` outputs.\n- TypeScript 7 is installed as `@typescript/native` and owns the `tsc` binary. The `typescript` dependency intentionally aliases `@typescript/typescript6` because TypeScript 7.0 has no programmatic API yet and tools such as `typescript-eslint` and `vue-tsc` still require that compatibility API.\n- Versioning via **changesets**, but changesets are **auto-derived from Conventional Commits** in CI (`scripts/generate-changesets.mjs`) — do **not** hand-write `.changeset/*.md`. Just land a well-formed conventional commit (`feat`/`fix`/`feat!`…); the release job maps changed files → packages and infers the bump. Each sub-package keeps its own `CHANGELOG.md` (changeset default, visible on npm); on publish `scripts/generate-release-notes.mjs` aggregates them into **one GitHub Release**, which can be polished after the fact via the `/release-changelog` skill (`gh release edit`). Full flow: `.changeset/README.md`.\n- Conventional commits enforced by commitlint (`feat:`, `fix:`, `feat(esm-sandbox):`, …).\n\n## DOCS & README CONVENTIONS\n\n- **Chinese-first.** The primary audience is Chinese. `docs/zh-CN/` is the source of truth: write it to a native-author standard — no translationese, no AI-flavored filler (「让我们」「值得注意的是」「强大的/无缝的」), no calqued English syntax. The English pages align to the Chinese semantics, not the other way around; pure zh wording fixes don't require touching en. Same contract for `README.zh-CN.md` ↔ `README.md`: keep both editions in sync whenever either changes.\n- **Mainland reachability is a hard constraint.** Google Fonts only via the `.cn` mirrors (`fonts.googleapis.cn`/`fonts.gstatic.cn`); never reference resources on domains unreachable from mainland China.\n- **Domains**: canonical docs domain is `https://www.qiankunjs.com` (zh under `/zh-CN/`); `qiankun.umijs.org` stays alive and 307s to it; v2 docs live at `v2.qiankun.umijs.org`; live examples at `examples.qiankunjs.com`. Old-site URLs are kept working via `docs/public/_redirects` — extend it when moving pages, never break inbound links.\n- **Consistency rules**: terminology never drifts (主应用/微应用/沙箱/隔离膜; qiankun always lowercase); link text in reference lists (\"相关内容\"/\"继续阅读\"/\"延伸阅读\") equals the target page's H1; API pages use 「函数签名」/「默认值为 X」; `guide/browser-support` is the single source of truth for browser requirements — link it, don't restate version numbers elsewhere.\n- Until 3.0 reaches npm `latest`, install commands must say `qiankun@rc`, and the site shows a version banner (layout-top slot in `docs/.vitepress/theme/index.js`; remove it together with `--vp-layout-top-height` once stable ships).\n- **README GitHub alerts**: put the `[!WARNING]`/`[!NOTE]` marker on its own line followed by a blank `>` line. Prettier's `proseWrap: never` re-joins plain soft breaks, which silently breaks alert rendering.\n- VitePress compiles every `.md` as a Vue SFC: bare `<Tag>` in prose/link text or `{{` in inline code breaks the build (fenced code blocks are safe).\n\n## ANTI-PATTERNS (this project)\n\n- **NEVER** put more than one `entry` script in an HTML entry — the loader throws `QiankunError`.\n- **ALWAYS** unmount micro-apps; `loadMicroApp`/patchers return handles/`free()` — leaks break remount & multi-instance.\n- In sandbox code, **never touch the real `window`/`document.head`** — go through the proxied view.\n- The vendored `loader/src/writable-dom/` fork accepts **generic designs only** — never qiankun-coupled semantics; caller bookkeeping goes through its `assetTransformer` callback (see `packages/loader/AGENTS.md`).\n- Don't invert the package dependency graph above (e.g. `shared` must not import `sandbox`).\n- e2e: never `waitForTimeout`; use web-first assertions; all ports come from `e2e/ports.ts`.\n- Performance work is judged in this order: **proportional impact first** (does the win scale with asset size / network / app scale — e.g. eliminating a duplicate download), **readability second** (never trade it away for small wins), **constant absolute savings last** (fixed single-digit-ms pipeline costs are noise in real apps and rarely worth landing). Beware: the ~50ms benchmark fixture makes fixed milliseconds masquerade as percentages — convert to absolute ms against realistic load times before deciding. And fix root causes at the source, not with per-call-site markers/exemptions downstream.\n\n## NOTES\n\n- Firefox doesn't support dynamically injected import maps → ESM-sandbox e2e tests are annotated `test.fail(firefox, …)` (expected failure, not skip). See `e2e/README.md`.\n- Design decisions live in `docs/rfcs/` (e.g. the ESM-sandbox RFC).\n- v3 roadmap: github.com/umijs/qiankun/discussions/1378.\n","CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\nAll project guidance lives in `AGENTS.md` (imported below). It links out to package-scoped `packages/*/AGENTS.md` files for deep-dives — read those on demand when editing a given package.\n\n@AGENTS.md\n","packages/bundler-plugin/README.md":"# @qiankunjs/bundler-plugin\n\nBundler plugins for the [qiankun](https://github.com/umijs/qiankun) micro-frontend framework. Currently supports Webpack (4 & 5) and Vite (5+).\n\n## Installation\n\nUsing npm:\n\n```bash\nnpm install @qiankunjs/bundler-plugin --save-dev\n```\n\nOr using yarn:\n\n```bash\nyarn add @qiankunjs/bundler-plugin --dev\n```\n\n## Webpack Plugin\n\n### Features\n\n- Automatically sets the name and format of the output library\n- Ensures the uniqueness of the `jsonpFunction`/`chunkLoadingGlobal` name\n- Sets the global object to `window`, ensuring the library can run in the browser\n- Automatically adds an entry marker to the entry script tag in HTML\n- Supports both Webpack 4 and Webpack 5\n\n### Usage\n\nIn your `webpack.config.js` or other configuration files:\n\n```javascript\nconst { QiankunWebpackPlugin } = require('@qiankunjs/bundler-plugin');\n\nmodule.exports = {\n  plugins: [\n    new QiankunWebpackPlugin({\n      packageName: 'optionalPackageName',\n    }),\n  ],\n};\n```\n\n### Options\n\n- `packageName`: Specifies the name of the output library. If not provided, the name from `package.json` will be used.\n\n### Entry Script Detection\n\nThe plugin automatically detects and marks the entry script in your HTML:\n\n1. Uses webpack's `compilation.entrypoints` API to identify entry chunks\n2. For single entry builds: marks the main entry chunk's script\n3. For multi-entry builds: matches HTML filename to entrypoint name\n4. Falls back to the last script tag if automatic detection fails\n\n## Vite Plugin\n\nqiankun loads Vite apps natively through its ESM sandbox — no legacy/SystemJS transformation is required. The Vite plugin only takes care of the loading plumbing:\n\n### Features\n\n- Responds with permissive CORS headers from the dev and preview servers, so the main app can fetch the entry HTML and module graph cross-origin\n- Marks the entry module script with the `entry` attribute in the built HTML (same contract as the webpack plugin), so the qiankun loader picks the entry deterministically\n\n### Usage\n\nIn your `vite.config.ts`:\n\n```typescript\nimport { defineConfig } from 'vite';\nimport { qiankun } from '@qiankunjs/bundler-plugin/vite';\n\nexport default defineConfig({\n  plugins: [qiankun()],\n});\n```\n\nYour entry module just exports the qiankun lifecycles directly:\n\n```typescript\nexport async function bootstrap() {}\nexport async function mount(props) {\n  // render your app into props.container\n}\nexport async function unmount(props) {\n  // tear your app down\n}\n```\n\n## Contributing\n\nAny form of contribution is welcome! Please submit PRs or open issues for discussion.\n\n## License\n\nMIT\n","packages/loader/AGENTS.md":"# @qiankunjs/loader\n\nStreaming HTML-entry loader built on a forked `writable-dom`. Depends on `@qiankunjs/sandbox` + `@qiankunjs/shared`.\n\n## STRUCTURE\n\n```\nloader/\n├── index.ts              # loadEntry() — main API\n├── TagTransformStream.ts # <head> → <qiankun-head> string-level replacement\n├── writable-dom/         # forked streaming DOM engine (incremental parse + blocking scripts)\n│   └── index.ts\n└── parser.ts             # static HTML parsing for prefetch\n```\n\n## WHERE TO LOOK\n\n| Task | File | Notes |\n| --- | --- | --- |\n| Load micro-app | `index.ts` | `loadEntry(entry, container, opts)`; `opts.compartment` is the single sandbox/module facade |\n| Head virtualization | `TagTransformStream.ts` | rewrites `<head>` tags at the string level before DOM insertion |\n| Streaming DOM | `writable-dom/index.ts` | incremental parsing, blocks on sync scripts/styles, preloads the rest |\n\n## LOADING PIPELINE\n\n```\n1. fetch(entry)                              (decorated fetch: cacheable/retryable/throwable)\n2. TextDecoderStream                         bytes → string\n3. TagTransformStream                        <head> → <qiankun-head>\n4. WritableDOMStream                         stream → live DOM\n   ├─ nodeTransformer(node) per node         → shared/assets-transpilers rewrites script/link/style\n   ├─ classic <script entry> → synchronous Compartment host-adapter rewrite, then blob-URL execution\n   ├─ <script type=\"module\"> → registered through the compartment module facade\n   └─ blocks on sync scripts; preloads other assets while blocked\n5. resolve app export                        classic: compartment.latestSetProp · esm: compartment.importDocumentModules()\n```\n\n## KEY PATTERNS\n\n### Script classification (`index.ts`)\n\n```typescript\nisExternalScript; // has src or data-src\nisEntryScript; // external + [entry]  → resolves the load promise (app's main export point)\nisDeferScript; // external + [defer]  → ordered via shared prepareDeferredQueue\n```\n\n- Exactly **one** `entry` script is allowed per HTML entry; a second one throws `QiankunError`.\n- Loader and asset-transpiler interfaces must not mention `EsmSandboxEngine` or the concrete `Sandbox` class; consume structural `CompartmentLoaderFacade`.\n- `transformClassicScript()` is a stable sandbox-owned host adapter because detached node transformation is synchronous. `evaluateScript()` schedules a separate script asynchronously and cannot replace it in the streaming pipeline.\n\n### Detached parsing\n\nNodes are parsed/transformed in a detached document first, then moved to live DOM — this prevents premature script execution before the transpiler has rewritten the node.\n\n### writable-dom fork discipline\n\n`writable-dom/` is vendored from marko-js/writable-dom and periodically re-synced; every deviation carries a `[qiankun]` comment so syncs can re-apply them mechanically. Changes there demand deliberate thought and must stay **generic**: general-purpose hooks or upstream bug fixes only — never qiankun-specific semantics coupled to other packages (no `@qiankunjs/*` imports, no sandbox marks, no downstream contract knowledge). The fork's one integration seam is the `assetTransformer` callback (every element passes through it right before insertion); caller bookkeeping belongs in the callback `loadEntry` provides (`index.ts`), on the caller's side of that seam. The loader itself carries no sandbox semantics either: a sandbox-provided `nodeTransformer` stamps its own output (e.g. the native-passthrough mark its patcher consumes) — the loader just routes every element through whatever transformer it was given.\n\n## ANTI-PATTERNS\n\n- **NEVER** include more than one `entry` script per HTML entry (throws `QiankunError`).\n- **NEVER** add qiankun-coupled logic inside `writable-dom/` — generic designs only; hang caller bookkeeping on the `assetTransformer` callback in `loadEntry` (see the fork discipline above).\n- **FIXME** (in code): non-standard HTML chunks that lack a `<head>` tag.\n\n## EXPORTS (`src/index.ts`)\n\n```typescript\nexport { loadEntry, type LoaderOpts } from './index';\n// LoaderOpts = { fetch, compartment?, nodeTransformer?, streamTransformer?, onDOMStreamSettled? } & BaseTranspilerOpts\n// onDOMStreamSettled: notified exactly once when the DOM-write phase is over (stream piped, errored,\n// or never started) — distinct from the returned promise, which can settle at the entry script's\n// onload while tail nodes are still streaming; qiankun's container gate keys its release on it\n```\n","packages/loader/README.md":"# @qiankunjs/loader\n\nqiankun 的流式 HTML 入口加载器，负责将微应用的 HTML 逐步写入容器，并通过沙箱提供的接口完成资源转换和脚本执行。它是 qiankun 加载流程的底层组件；业务应用建议使用 `qiankun` 的公开 API，无需单独安装本包。\n\n文档：[HTML 入口](https://www.qiankunjs.com/zh-CN/concepts/html-entry-loading)。\n\n## English\n\nThe streaming HTML-entry loader for qiankun. It incrementally writes a micro-app's HTML into its container and uses sandbox-provided interfaces for asset transformation and script execution. This package is a low-level part of qiankun's loading pipeline; application code should use the public `qiankun` APIs without installing it separately.\n\nDocumentation: [HTML entry](https://www.qiankunjs.com/concepts/html-entry-loading).\n","packages/loader/src/writable-dom/README.md":"# Fork from https://github.com/marko-js/writable-dom\n","packages/qiankun/README.md":"# qiankun（乾坤）\n\n<p align=\"center\">\n  <a href=\"https://qiankun.umijs.org/\" target=\"_blank\" rel=\"noopener noreferrer\">\n    <img width=\"180\" src=\"https://gw.alipayobjects.com/zos/bmw-prod/8a74c1d3-16f3-4719-be63-15e467a68a24/km0cv8vn_w500_h500.png\" alt=\"qiankun logo\">\n  </a>\n</p>\nurl('')\n\n[![npm version](https://img.shields.io/npm/v/qiankun.svg?style=flat-square)](https://www.npmjs.com/package/qiankun) [![coverage](https://img.shields.io/codecov/c/github/umijs/qiankun.svg?style=flat-square)](https://codecov.io/gh/umijs/qiankun) [![npm downloads](https://img.shields.io/npm/dt/qiankun.svg?style=flat-square)](https://www.npmjs.com/package/qiankun) [![Build Status](https://img.shields.io/travis/umijs/qiankun.svg?style=flat-square)](https://travis-ci.com/umijs/qiankun)\n\n> In Chinese traditional culture `qian` means heaven and `kun` stands for earth, so `qiankun` is the universe.\n\nAn implementation of [Micro Frontends](https://micro-frontends.org/), based on [single-spa](https://github.com/CanopyTax/single-spa), but made it production-ready.\n\n## 🤔 Motivation\n\nAs we know what micro-frontends aims for:\n\n> Techniques, strategies and recipes for building a **modern web app** with **multiple teams** using **different JavaScript frameworks**. — [Micro Frontends](https://micro-frontends.org/)\n\nModularity is very important for large application. By breaking down a large system into individual sub-applications, we can achieve good divide-and-conquer between products and when necessary combination, especially for enterprise applications that usually involve multi-team collaboration. But if you're trying to implement such a micro frontends architecture system by yourself, you're likely to run into some tricky problems:\n\n- In what form do sub applications publish static resources?\n- How does the main application integrate individual sub-applications?\n- How do you ensure that sub-applications are independent of each other (development independent, deployment independent) and runtime isolated?\n- Performance issues? What about public dependencies?\n- And so on...\n\nAfter solving these common problems of micro frontends, we extracted the kernel of our solution after a lot of internal online application testing and polishing, and then named it `qiankun`.\n\n**Probably the most complete micro-frontends solution you ever met🧐.**\n\n## 📦 Installation\n\n```shell\n$ pnpm run add qiankun\n```\n\n## 📖 Documentation\n\nhttps://qiankun.umijs.org/\n\n## 💿 Getting started\n\nThis repo contains an `examples` folder with a sample Shell app and multiple mounted Micro FE apps. To run this app, first clone `qiankun`:\n\n```shell\n$ git clone https://github.com/umijs/qiankun.git\n$ cd qiankun\n```\n\nNow run the pnpm run scripts to install and run the examples project:\n\n```shell\n$ pnpm run install\n$ pnpm run examples:install\n$ pnpm run examples:start\n```\n\nVisit `http://localhost:7099`.\n\n![](./examples/example.gif)\n\n## :sparkles: Features\n\n- 📦 **Based On [single-spa](https://github.com/CanopyTax/single-spa)**\n- 📱 **Technology Agnostic**\n- 💪 **HTML Entry Access Mode**\n- 🛡 **Style Isolation**\n- 🧳 **JS Sandbox**\n- ⚡ **Prefetch Assets**\n- 🔌 **[Umi Plugin](https://github.com/umijs/plugins/tree/master/packages/plugin-qiankun) Integration**\n\n## 🎯 Roadmap\n\n- [x] Parcel apps integration (multiple sub apps displayed at the same time, but only one uses router at most)\n- [x] Communication development kits between master and sub apps\n- [ ] Custom side effects hijacker\n- [ ] Nested Microfrontends\n\n## ❓ FAQ\n\nhttps://qiankun.umijs.org/faq/\n\n## 👬 Community\n\nhttps://qiankun.umijs.org/#community\n\n## 🎁 Acknowledgements\n\n- [single-spa](https://github.com/CanopyTax/single-spa) What an awesome meta-framework for micro-frontends!\n- [import-html-entry](https://github.com/kuitos/import-html-entry/) An assets loader which supports html entry.\n","packages/sandbox/AGENTS.md":"# @qiankunjs/sandbox\n\nJS isolation engine: a Compartment-shaped facade owns the Proxy **Membrane**, classic-script evaluator, and module hooks. DOM behavior lives in independently registered **IsolationPlugin** instances. Depends only on `@qiankunjs/shared`.\n\n## STRUCTURE\n\n```\nsandbox/\n├── core/\n│   ├── sandbox/          # createSandbox() + StandardSandbox preset + container protocol\n│   ├── membrane/         # Proxy wrapper for global (window/document) isolation\n│   ├── compartment/      # globals + module hooks + blob classic evaluation facade\n│   ├── globals.ts        # global property definitions\n│   └── esm-globals.ts    # esmDestructurableGlobals — globals the ESM engine may destructure/rebind\n├── patchers/             # public IsolationPlugin protocol + built-in plugin presets\n│   ├── dynamicAppend/    # appendChild/insertBefore interception → redirect to app container\n│   ├── windowListener.ts # event listener tracking\n│   ├── interval.ts       # timer tracking\n│   └── historyListener.ts\n└── consts.ts             # qiankunHeadTagName / qiankunBodyTagName, nativeGlobal, nativeDocument\n```\n\n## WHERE TO LOOK\n\n| Task | File | Notes |\n| --- | --- | --- |\n| Create sandbox | `core/sandbox/index.ts` | Builds a `StandardSandbox`, installs the JS-only or DOM preset, and returns mount/unmount/dispose |\n| Prepare container | `core/sandbox/container.ts` | Owns `<qiankun-head>`, `data-name`, style scope, and cleanup contracts |\n| Proxy logic | `core/membrane/index.ts` | Write → local target; Read → local → configured globals → host window |\n| Compartment facade | `core/compartment/index.ts` | Owns the membrane, module facade, and CSP-safe blob classic evaluation |\n| Plugin protocol | `patchers/types.ts` | Public `IsolationPlugin`, context, `Free`, and `Rebuild` contracts |\n| Built-in plugins | `patchers/index.ts` | Default plugins; `dynamicAppend` joins only with a container; user plugins append after these |\n| DOM interception | `patchers/dynamicAppend/forStandardSandbox.ts` | Redirects dynamic script/style/link to app container |\n| Side-effect cleanup | `patchers/*.ts` | Each patcher returns a `free()` called on unmount |\n| ESM globals contract | `core/esm-globals.ts` | Consumed by `shared/esm-sandbox` engine, passed as `globalsBaseSet` |\n\n## KEY PATTERNS\n\n### Membrane (Proxy)\n\n- **Writes** are trapped and stored on a local `target` object (the sandbox's own globals).\n- **Reads** check local target → configured globals → fall back to the real host window.\n- **Native rebinding**: `fetch`, `console`, etc. are rebound to the real receiver to avoid \"Illegal invocation\".\n- `latestSetProp` records the last global the entry script assigned — that's how the loader recovers a classic app's exported lifecycles when no explicit export exists.\n- **Non-transitive by design**: unlike full membranes (es-membrane, Salesforce near-membrane), only the global (and document view) entry point is proxied — objects crossing the boundary keep their raw identity (`===`/`instanceof` hold across it). This is qiankun's deliberate isolation stance (isolate writes, share identity, for DOM compatibility and performance), not a gap to fill. Do NOT wrap crossing objects with proxies. The Membrane is a private mechanism of `Compartment` — the spec has no membrane concept, which is exactly why it must stay behind the facade.\n- `incubatorContext` (the host window the view reads through to) is a qiankun host extension: the Compartment spec and ses isolate by absence and have no read-through concept. The name borrows the ShadowRealm proposal's official \"incubator realm\" term for the creating context.\n\n### Compartment surface and deliberate differences\n\n`new Compartment({ globals, modules, resolveHook, importHook, loadHook, transforms })` owns its `globalThis`. StandardSandbox adds qiankun self-references (`window`, `self`, `globalThis`, `top`, `parent`) in a second phase with `defineUnshadowableGlobals()`.\n\n- Deliberately absent: synchronous `evaluate`, `harden`, and `lockdown`.\n- qiankun host extensions: DOM globals, `evaluateScript`, the synchronous `transformClassicScript` streaming adapter, lifecycle and entry-discovery facades, module-document registration, `defineUnshadowableGlobals`, `dispose`, `incubatorContext` / `moduleHost`, and the IsolationPlugin protocol. The executable list lives in `COMPARTMENT_HOST_EXTENSIONS`.\n- `evaluateScript()` uses a native blob `<script>` path. Do not introduce `eval` or `new Function` as an implementation shortcut.\n- `transformClassicScript()` exists because loader node transformation must finish synchronously before the original node enters live DOM. Loader consumes it through structural `CompartmentLoaderFacade`; never restore a concrete-class or private WeakMap bridge.\n- The pre-alignment names `Endowments` and `addIntrinsics()` have been removed entirely (the package has no external users, so no compatibility aliases). Use `CompartmentGlobals` / `globals` and `defineUnshadowableGlobals()`.\n\nThe module engine in `@qiankunjs/shared` is an implementation detail behind `Compartment.import()`, `load()`, and `importDocumentModules()`. Cross-package consumers must depend on the structural Compartment facade, never `EsmSandboxEngine`.\n\n`createSandbox().dispose()` is the terminal owner cleanup: it frees active plugin effects, restores container protocol state, deactivates the sandbox, and idempotently disposes the Compartment. Initial load failures must call it without replacing the original loading error.\n\nFetch precedence in `createSandbox()` is \"more specific wins\": module sources use `compartmentOptions.moduleHost.fetch ?? fetch ?? native`, dynamic assets use `fetch ?? native`. This is the one exception to the rule that promoted top-level options beat `compartmentOptions`; qiankun relies on it to give module sources a separate response cache.\n\n`createSandbox(appName)` is the JS-only preset: interval, window-listener, and history cleanup are available without a container. Providing `container` enables dynamic DOM interception; `mount()` prepares standalone containers lazily (`provisionContainerHead`, default `true` — qiankun's loader passes `false` because the entry HTML materializes the head in-stream). The controller keeps **two views of one configured transformer**, split by call-site identity: the public `nodeTransformer` is the pipeline variant — its output is stamped for native passthrough (`core/nativePassthrough.ts`), so patched mount points insert it untouched and it never enters the dynamic ledger — while the dynamic-append patcher receives the bare variant through the plugin config, whose output must NOT carry the mark (re-inserted nodes depend on re-entering the pipeline for ledger bookkeeping).\n\n### IsolationPlugin / Free pattern\n\n```typescript\nconst plugin: IsolationPlugin = {\n  name: 'example',\n  bootstrap: (ctx) => installBeforeAppCode(ctx),\n  mount: async (ctx) => installForThisMount(ctx),\n};\n\nconst free = await plugin.mount?.(context);\nif (free) {\n  const rebuild = free(); // unmount: remove this plugin's side effects\n  await rebuild(container); // remount: restore recorded side effects\n}\n```\n\n- `bootstrap` runs synchronously before any application script is evaluated.\n- `mount` runs on every mount and may asynchronously produce its `Free`.\n- Built-in plugins run in preset order; user plugins run afterward in registration order.\n- Prototype-level coordination belongs in browser-realm shared state with refcounts; it is not per-app context state. Use a stable `Symbol.for(...)` slot on `nativeGlobal` when independently bundled qiankun copies must coordinate the same prototype patch.\n- Code under `patchers/**` may use only the public Compartment surface. ESLint forbids imports from membrane internals.\n\n### Realm-shared metadata\n\n- `dynamicAppend.sharedState` is stored on `nativeGlobal` under `Symbol.for('qiankun.dynamicAppend.sharedState')`, so multiple qiankun copies in the same browser realm share prototype/CSSOM refcounts and ownership metadata.\n- `sandboxConfigs` — per-Compartment dynamic-append config.\n- `elementConfigs` — which Compartment owns each dynamically appended DOM node.\n- `containerOwners` — which Compartment currently patches a micro-app container.\n\n## ANTI-PATTERNS\n\n- **NEVER** access the real `window` / `document.head` directly — always the proxied view.\n- **NEVER** import membrane internals from a plugin or expose `EsmSandboxEngine` in a cross-package contract.\n- **NEVER** move dynamicAppend/style/timer/listener behavior into Compartment itself; those are DOM host plugins.\n- **FIXME** (in code): indirect `eval` in the membrane can let System.js escape sandbox scope.\n- **FIXME**: the runtime-container global may miss monkey-patched append logic.\n\n## REPLACEMENT BOUNDARY\n\nIf a native Compartment becomes available, it may replace only globalThis virtualization, the evaluator, and the module graph. Document proxying, dynamic append interception, style isolation, timers/listeners cleanup, and history integration remain qiankun-owned DOM plugins because ECMAScript Compartment proposals do not define DOM behavior. Keep the plugin layer dependent only on the public Compartment surface so this boundary remains real.\n\n## EXPORTS (`src/index.ts`)\n\n```typescript\nexport * from './core/sandbox'; // createSandbox, prepareSandboxContainer, StandardSandbox, controller/options types\nexport * from './core/compartment'; // Compartment, options, compatibility lists\nexport * from './consts'; // qiankunHeadTagName, qiankunBodyTagName, nativeGlobal, nativeDocument\nexport { esmDestructurableGlobals } from './core/esm-globals';\nexport type { IsolationPlugin, IsolationPluginContext, Free, Rebuild } from './patchers';\n```\n","packages/sandbox/README.md":"# @qiankunjs/sandbox\n\n`@qiankunjs/sandbox` provides qiankun's browser sandbox as a standalone package. It can isolate classic-script and ESM global writes, and can optionally contain dynamic DOM and styles inside an application container. It does not require `qiankun` or `@qiankunjs/loader`.\n\n> This package is browser-only. It uses DOM APIs, blob URLs, and dynamically injected import maps; it does not support Node.js or SSR execution.\n\n## Choose an API layer\n\n| Need | API | What it provides |\n| --- | --- | --- |\n| JavaScript isolation only | `StandardSandbox` | A safe preset for classic scripts and ESM, including `window`/`self` self-references |\n| Low-level Compartment mechanics | `Compartment` | A Compartment-shaped global and module-hook facade for advanced hosts |\n| Browser containment and lifecycle | `createSandbox()` | `StandardSandbox` plus optional DOM/style containment, timer and listener cleanup, and isolation plugins |\n\nUse `StandardSandbox` for direct classic-script evaluation. A bare `Compartment` deliberately does not install a sandboxed `window` self-reference. Calling `Compartment.evaluateScript()` without defining one emits a development warning, but it does not change execution semantics: code such as `window.answer = 42` may write to the host window.\n\n## JavaScript isolation\n\n### Classic scripts\n\n```ts\nimport { StandardSandbox } from '@qiankunjs/sandbox';\n\nconst sandbox = new StandardSandbox('report-widget', {\n  tenantId: 'acme',\n});\n\nawait sandbox.evaluateScript(\n  `\n    window.reportState = { tenantId };\n  `,\n  { sourceURL: 'https://widgets.example/report.js' },\n);\n\nconsole.log(Reflect.get(sandbox.globalThis, 'reportState')); // { tenantId: 'acme' }\nconsole.log(Reflect.get(window, 'reportState')); // undefined\n\nsandbox.dispose();\n```\n\n`evaluateScript()` executes through a native blob script. It does not use `eval` or `new Function`.\n\n### ESM\n\n```ts\nconst sandbox = new StandardSandbox('report-module');\nconst namespace = await sandbox.import(new URL('./report-entry.js', document.baseURI).href);\n\nconsole.log(namespace);\nsandbox.dispose();\n```\n\nRelative module resolution defaults to `document.baseURI`. `StandardSandbox` accepts custom module tables and hooks through its fourth, Compartment-options argument; `createSandbox()` promotes `modules`, `resolveHook`, `importHook`, and `loadHook` to top-level options. See the [sandbox plugins guide](../../docs/cookbook/sandbox-plugins.md) for an advanced example.\n\n`createSandbox()` fetches dynamic assets and module sources with its top-level `fetch` option, which defaults to the native fetch. To give module sources their own transport, such as a separate response cache, set `compartmentOptions.moduleHost.fetch`. It is the only `compartmentOptions` field that takes precedence over a top-level option: the more specific module-source fetch wins when loading modules, while dynamic assets keep using the top-level `fetch`.\n\n## Full browser sandbox\n\nPass a container to `createSandbox()` to enable document virtualization and dynamic DOM interception. Set `styleIsolation: true` to scope application styles to that container.\n\n```ts\nimport { createSandbox } from '@qiankunjs/sandbox';\n\nconst container = document.querySelector<HTMLElement>('#widget')!;\nconst controller = createSandbox('report-widget', {\n  container,\n  styleIsolation: true,\n});\n\n// Mount before application code so timers and listeners are tracked.\nawait controller.mount();\n\nconst response = await fetch('/vendor/report-widget.js');\nconst source = await response.text();\nawait controller.instance.evaluateScript(source, {\n  sourceURL: response.url,\n});\n\n// Releases active plugin effects and keeps the controller available for remount.\nawait controller.unmount();\nawait controller.mount();\n\n// Terminal cleanup. A disposed controller cannot be mounted or evaluated again.\nawait controller.dispose();\n```\n\nWhen `container` is a function, it is resolved again for each mount. This supports hosts that replace the mount element between activations:\n\n```ts\nlet currentContainer = document.querySelector<HTMLElement>('#first')!;\n\nconst controller = createSandbox('report-widget', {\n  container: () => currentContainer,\n});\n\nawait controller.mount();\nawait controller.unmount();\n\ncurrentContainer = document.querySelector<HTMLElement>('#second')!;\nawait controller.mount();\n```\n\nCalling `createSandbox('name')` without a container creates the JS-only preset. Its timer, window-listener, and history plugins can be activated with `mount()` without providing an arbitrary DOM element, and an element passed to `mount()` is left untouched — container preparation belongs to the container-backed preset. `styleIsolation: true` requires a configured container.\n\n### Container preparation\n\n`createSandbox()` establishes its container contract automatically. Advanced hosts that compose the lower-level APIs can prepare a container directly:\n\n```ts\nimport { prepareSandboxContainer } from '@qiankunjs/sandbox';\n\nconst { styleIsolation, cleanup } = prepareSandboxContainer(container, 'report-widget');\n\nconsole.log(styleIsolation);\n// { appName: 'report-widget', scopeRoot: '[data-name=\"report-widget\"]' }\n\n// Later, release only the attributes and virtual head created by this call.\ncleanup();\n```\n\nThe helper sets `data-name`, ensures a `<qiankun-head>` mount point for the sandboxed `document.head`, and returns the matching style-isolation options. Cleanup is idempotent: it preserves an existing virtual head, restores a previous `data-name`, and does not overwrite a later host update to that attribute.\n\n## Default asset transformation\n\nThe default `nodeTransformer` is isolation-preserving. A classic `<script>` dynamically appended by sandboxed code is fetched, wrapped with the controller's classic-script transformer, and executed through a blob URL. Dynamic styles and stylesheet links go through the same pipeline, including style scoping when enabled.\n\nYou may provide a custom transformer. Returning each node unchanged is not a neutral optimization:\n\n```ts\nconst controller = createSandbox('trusted-widget', {\n  container,\n  nodeTransformer: (node) => node,\n});\n```\n\nAn identity transformer disables the default classic-script wrapping. A dynamically appended classic script can then execute against the host global. Use this override only when the host supplies equivalent isolation or fully trusts every script. The internal asset transpiler is intentionally not a public API; the supported extension point is `nodeTransformer`.\n\nFor a custom HTML loading pipeline, use `controller.nodeTransformer`. It is the fully configured transformer shared with dynamic DOM interception, including the current Compartment, fetch implementation, classic-script wrapper, and style-isolation options.\n\n## Lifecycle ownership\n\n- `mount(container?)` activates or rebuilds timer, listener, history, DOM, and user-plugin effects. The argument overrides the configured container for that mount.\n- `unmount()` releases active effects and records any state required for a later mount. It does not run an application's own unmount function or clear caller-owned DOM.\n- `dispose()` permanently releases plugin effects, generated module resources, global accessors, and container preparation. It is safe to call more than once.\n\nMount the controller before evaluating application code when its timers and listeners must participate in cleanup. Run the application's own teardown before `unmount()` or `dispose()`, and clear application DOM according to the host's ownership rules.\n\n## Isolation boundary\n\nThis package is a compatibility sandbox, not a security boundary for hostile code.\n\n- Writes to the sandbox global stay local to that sandbox.\n- Reads fall through to the host window unless a value is configured or shadowed.\n- The membrane is non-transitive: objects crossing the boundary retain their identity. `===`, `instanceof`, DOM nodes, events, and shared library instances continue to work.\n- Mutating a host object obtained through read-through also mutates that shared object. Only the global property write is isolated.\n- DOM containment redirects the sandboxed `document.head` and `document.body`; it does not create a separate browser realm or origin.\n\nUse an iframe, Worker, or another origin/callable boundary for untrusted code. See the [Compartment Alignment RFC](../../docs/rfcs/compartment-alignment.md) for the rationale behind the write-isolation and identity-sharing model.\n\n## Content Security Policy\n\nClassic and ESM execution do not require `'unsafe-eval'`. They do require the page's `script-src` policy to allow `blob:` URLs. The ESM engine also injects an inline `script[type=\"importmap\"]`, which must be permitted by the page's script policy. ESM and external classic assets are fetched first, so their origins must satisfy CORS and `connect-src` as well.\n\nStyle isolation can create blob stylesheet URLs; allow `blob:` in `style-src` when external styles are used. Dynamically created inline `<style>` elements remain subject to the page's normal nonce, hash, or inline-style policy.\n\n```http\nContent-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' blob:; style-src 'self' 'unsafe-inline' blob:; connect-src 'self' https://widgets.example\n```\n\nThis illustrative policy allows the runtime import map and a widget-created inline style. Tighten it to the assets and execution path the host actually uses. Do not add `'unsafe-eval'` for this package.\n\n## Further reading\n\n- [Standalone sandbox cookbook](../../docs/cookbook/standalone-sandbox.md)\n- [Extending sandbox isolation](../../docs/cookbook/sandbox-plugins.md)\n- [Standalone Sandbox RFC](../../docs/rfcs/standalone-sandbox.md)\n","packages/shared/AGENTS.md":"# @qiankunjs/shared\n\nBase utilities shared by `loader`/`sandbox`/`qiankun`: asset transpilers, fetch decorators, module resolution, error reporting, and the **ESM-sandbox engine**. No internal `@qiankunjs/*` dependencies — keep it at the bottom of the graph (it must never import `sandbox` or `loader`).\n\n## STRUCTURE\n\n```\nshared/\n├── assets-transpilers/   # transform script/link/style DOM nodes for the micro-frontend runtime\n│   ├── index.ts          # transpileAssets(node, baseURI, opts) — dispatch by tagName\n│   ├── script.ts         # classic scripts → blob-URL wrapping, sandbox scope\n│   ├── link.ts           # external stylesheets → blob-<link> (enables @scope); preload/URL rewriting; cache\n│   └── style.ts          # inline <style> → CSS @scope rewriting (runtime style isolation)\n├── esm-sandbox/          # per-instance native-ESM execution engine (see below)\n├── fetch-utils/          # higher-order fetch decorators\n│   ├── makeFetchCacheable.ts   # two runtime-wide LRUs (assets 50 / modules 512), clones responses, prunes failures\n│   ├── makeFetchRetryable.ts   # automatic retries\n│   └── makeFetchThrowable.ts   # non-2xx → throw\n├── module-resolver/      # shared-dependency canonicalization (semver via <script type=\"dependencymap\">)\n├── reporter/             # QiankunError + logger (warn/…)\n├── deferred-queue/       # async task sequencing (defer-script ordering)\n├── common.ts / utils.ts  # Deferred, defineProperty, hasOwnProperty, keys, resolveUrl, …\n└── typings.d.ts\n```\n\n## ESM-SANDBOX (`esm-sandbox/`, the largest subsystem here)\n\n`EsmSandboxEngine` is the replaceable mechanism behind the sandbox package's Compartment module facade; it must not leak into loader or qiankun interfaces. It runs a micro-app's `<script type=\"module\">` graph inside the sandbox membrane without a bundler. Pipeline: resolve through `resolveHook` → obtain and memoize a descriptor from `modules` or `importHook` / `loadHook` → fetch/lexer-rewrite source when needed → give modules synthetic specifiers → inject an **import map** → evaluate in dependency order → keep dunder-globals (`__qk_track`) live on later global writes.\n\n| File | Responsibility |\n| --- | --- |\n| `engine.ts` | `EsmSandboxEngine` — module-graph orchestration, redeclaration probing, in-order eval, dynamic `import()` |\n| `types.ts` | Public structural module contracts: hooks, descriptors, `ModuleSource`, Compartment facade |\n| `lexer.ts` | CSP-safe `es-module-lexer/js` wrapper; preparation Promise is already resolved |\n| `rewrite.ts` | `rewriteModule`, `buildSyntheticSpecifier`, `esmInternalPrefix`, `runtimeModuleSubpath` |\n| `import-bindings.ts` | import statement binding analysis |\n| `identifier-scan.ts` | `scanReferencedGlobals`, `isLiveBindableDunderName` — which globals a module touches |\n| `import-map-registry.ts` | `injectImportMapEntries`, `resetImportMapRegistry` — the dynamic import map |\n| `instance-registry.ts` | Per-engine instance/view lookup used by generated runtime modules |\n| `vite-client-stub.ts` | `isViteClientUrl`, `viteClientStubSource` — stubs Vite dev-client HMR |\n| `source-map.ts` | rewritten-module source-map fixups |\n\n> ESM sandbox depends on **dynamically injected import maps** → unsupported on Firefox (see `e2e/README.md`).\n\n## WHERE TO LOOK\n\n| Task | File | Notes |\n| --- | --- | --- |\n| Transpile any asset node | `assets-transpilers/index.ts` | `transpileAssets()` — the single dispatch entry |\n| Classic script sandboxing | `assets-transpilers/script.ts` | blob-URL wrap + sandbox scope |\n| Style isolation | `assets-transpilers/style.ts` + `link.ts` | CSS `@scope` rewrite; external CSS → blob-`<link>` |\n| Fetch enhancements | `fetch-utils/*` | decorator chain (compose with `make*` HOFs) |\n| Shared dependency reuse | `module-resolver/index.ts` | `moduleResolver(url, container, head)` semver match |\n| ESM module execution | `esm-sandbox/engine.ts` | `EsmSandboxEngine` (see section above) |\n| Module facade contracts | `esm-sandbox/types.ts` | The only ESM mechanism types cross-package callers should consume |\n| Promise/util helpers | `utils.ts` | `Deferred`, `keys`, `defineProperty`, `hasOwnProperty` |\n\n## KEY PATTERNS\n\n### Module hooks and descriptors\n\n- `resolveHook(specifier, referrer)` synchronously returns a canonical full specifier.\n- A `modules` descriptor wins before `importHook`; descriptors may provide precompiled `ModuleSource`, a namespace, or a redirect specifier.\n- `importHook(fullSpecifier)` is Promise-memoized, including failures. Probe-driven rebuilds reuse its result and do not call the hook again, so hooks must be idempotent.\n- A precompiled `ModuleSource` is already linked/re-written and skips the default fetch and rewrite pipeline.\n- Redirect cycles must fail deterministically; namespace descriptors keep their object identity.\n\nThe engine may be imported by the sandbox implementation itself, but do not add it to `LoaderOpts`, asset-transpiler options, or other cross-package interfaces. Those use `CompartmentModuleFacade`.\n\n### Higher-order fetch (decorator composition)\n\n```typescript\n// makeFetchCacheable(fetch, cacheScope: 'assets' | 'modules' = 'assets')\nconst resourceFetch = makeFetchRetryable(makeFetchThrowable(fetch));\nconst enhancedFetch = makeFetchCacheable(resourceFetch); // entries, classic scripts, styles\nconst moduleSourceFetch = makeFetchCacheable(resourceFetch, 'modules'); // passed as moduleHost.fetch\n```\n\nEach `cacheScope` maps to one LRU shared by the whole runtime copy: `assets` holds 50 responses and `modules` holds 512, so a large module graph cannot evict entries and styles. Both key requests by canonical URL and effective `credentials`, cache in-flight Promises, clone responses per reader, and drop failed responses. `loadApp` passes the module-scoped fetch as `compartmentOptions.moduleHost.fetch`, which the sandbox prefers over the top-level `fetch` for module sources.\n\n### Deferred promise\n\n```typescript\nconst d = new Deferred<void>();\nd.resolve();\nawait d.promise;\n```\n\n### DOM as metadata store\n\n- `data-src` — original source URL of a transpiled node.\n- `data-consumed` — marks an already-processed element.\n\n## EXPORTS\n\n`src/index.ts` re-exports everything: `./assets-transpilers`, `./utils`, `./common`, `./module-resolver`, `./reporter`, `./esm-sandbox`, the three `fetch-utils/make*`, and `./deferred-queue`. Notable named exports: `transpileAssets`, the Compartment-shaped module contracts, the internal `EsmSandboxEngine` mechanism, `moduleResolver`, `makeFetchCacheable`, `makeFetchRetryable`, `makeFetchThrowable`, `Deferred`, `QiankunError`, `warn`.\n","packages/shared/README.md":"# @qiankunjs/shared\n\nqiankun 内部共用的基础模块，为加载器、沙箱和运行时提供资源转换、请求处理、模块解析及 ESM 沙箱引擎等能力。它位于 qiankun 内部依赖关系的底层，不建议业务应用直接依赖；接入微应用请使用 `qiankun` 的公开 API。\n\n文档：[加载一个微应用实例](https://www.qiankunjs.com/zh-CN/concepts/architecture)。\n\n## English\n\nInternal foundation modules shared by qiankun's loader, sandbox, and runtime, including asset transformation, fetch utilities, module resolution, and the ESM sandbox engine. This package sits at the bottom of qiankun's internal dependency graph and is not recommended as a direct application dependency; use the public `qiankun` APIs to integrate micro-apps.\n\nDocumentation: [Loading a micro-app instance](https://www.qiankunjs.com/concepts/architecture).\n","packages/single-spa/README.md":"# @qiankunjs/single-spa\n\nqiankun's vendored fork of [single-spa](https://github.com/single-spa/single-spa), consumed by the `qiankun` package as its application/parcel lifecycle router. See the vendoring RFC in [umijs/qiankun#3168](https://github.com/umijs/qiankun/issues/3168) for the full rationale and plan.\n\n## Fork baseline\n\n- Upstream: `single-spa/single-spa`, `7.0` branch @ [`ce0f925a`](https://github.com/single-spa/single-spa/commit/ce0f925a40a81809c598de21b36ad79706222a73) (tag `v7.0.0-beta.13`, 2025-09-22).\n- Imported verbatim (`src/`, `spec/`, `node-spec/`, `LICENSE`), then naturalized to this monorepo's toolchain (Vite 8 build, `tsc` declarations, vitest). Upstream rollup/babel/jest configs were not carried over; the compile-time constants they injected (`__DEV__`, `__PROFILE__`, `process.env.BABEL_ENV`) are provided by `vite.library.config.ts` (build) and the root `vitest.config.ts` (tests).\n- The profile variant entry (`src/single-spa.profile.ts` + `src/devtools/profiler-api.ts`) is kept in the tree but not built or published yet; `__PROFILE__` is compiled to `false`.\n- Versioning is independent of upstream beta numbering and follows this monorepo's changesets flow.\n\n## Behavioral divergences from upstream\n\nMaintained deliberately; this list is the contract (keep it in sync when adding divergences):\n\n1. **The upstream `bootstrap` → `init` rename ([single-spa#1307](https://github.com/single-spa/single-spa/pull/1307), completed by [#1333](https://github.com/single-spa/single-spa/pull/1333)) is reverted wholesale — this fork speaks pure v6 vocabulary, with no `init` aliases.** qiankun's ecosystem was built on the bootstrap vocabulary and there was no reason to break it. Concretely:\n   - the `bootstrap` lifecycle is the only recognized name for applications and parcels (a v7-style `init` export is ignored, exactly as v6 ignored unknown fields);\n   - `parcel.bootstrapPromise`, `setBootstrapMaxTime` and the `timeouts.bootstrap` key are back; `initPromise`, `setInitMaxTime` and `timeouts.init` do not exist;\n   - the `NOT_BOOTSTRAPPED` / `BOOTSTRAPPING` statuses are restored, both as `AppOrParcelStatus` member names and as the raw `getStatus()` string values (v7's `NOT_INITIALIZED` / `INITIALIZING` strings do not exist in this fork);\n   - error message texts use the bootstrap vocabulary again;\n   - the vendored upstream specs were migrated back to the bootstrap vocabulary along with the sources.\n\n   If upstream ever ships a stable (non-beta) v7 that still carries the rename, re-evaluate aligning with it then — until that day this fork does not track it.\n\n## Invariant contracts (never break)\n\n- All `single-spa:*` window events (`single-spa:before-routing-event`, `single-spa:routing-event`, `single-spa:no-app-change`, `single-spa:first-mount`, …) — qiankun and the wider ecosystem listen to them.\n- The `window.__SINGLE_SPA_DEVTOOLS__` hook (single-spa-inspector support).\n- Public API and type signatures stay 1:1 with the upstream baseline during phase one, except for the bootstrap-naming revert above — qiankun-observable behavior stays 1:1 with what its ecosystem shipped against, which is the higher-priority contract.\n- Zero runtime dependencies; this package sits at the bottom of the workspace dependency graph and must not import `@qiankunjs/shared`, `@qiankunjs/sandbox` or `@qiankunjs/loader`.\n\n## Known semantics now owned by this fork\n\n- `urlRerouteOnly` (documented at length in qiankun's `docs/api/start.md`) — qiankun passes it through untouched; the implementation and its semantics live here now.\n- **Single-instance assumption**: single-spa is a stateful routing singleton. Loading both the ESM and CJS builds in one page (or mixing this fork with a separately installed npm `single-spa`) silently creates two independent routers — two history patches, two event streams, two `started` flags. Don't.\n\n## Upstream sync\n\nExpected cadence ≈ zero (upstream is dormant). If a fix is worth absorbing:\n\n```bash\ngit remote add single-spa-upstream https://github.com/single-spa/single-spa.git\ngit fetch single-spa-upstream 7.0\ngit log ce0f925a..single-spa-upstream/7.0 --oneline   # review the delta\n# apply selected commits manually onto packages/single-spa/src/ (path prefixes differ)\n```\n\nUpdate the baseline note above after absorbing anything; on conflict with the divergence list, the divergence list wins.\n\n## License\n\nMIT — upstream's [LICENSE](./LICENSE) is preserved.\n","packages/ui-bindings/react/README.md":"# qiankun ui binding for react\n\n## Usage\n\n```bash\nnpm i @qiankunjs/react\n```\n\n## MicroApp component\n\nLoad (or unload) child apps directly through the `<MicroApp/>` component, which provides loading and error catching-related capabilities:\n\n```tsx\nimport { MicroApp } from '@qiankunjs/react';\n\nexport default function Page() {\n  return <MicroApp name=\"app1\" entry=\"http://localhost:8000\" />;\n}\n```\n\nWhen the sub-app loading animation or error capture capability is enabled, the sub-app accepts an additional style class `wrapperClassName`, and the rendered result is as follows:\n\n```tsx\n<div style={{ position: 'relative' }} className={`${wrapperClassName} qiankun-micro-app-wrapper`}>\n  <MicroApp className={`${className} qiankun-micro-app-container`} />\n  <MicroAppLoader loading={loading} />\n  <ErrorBoundary error={e} />\n</div>\n```\n\nThe container is rendered first so its position never shifts as the loader and error slots come and go — qiankun keys its per-container caches on that position. Because the slots come after it, they paint above the micro app without needing a `z-index`.\n\n### Load animation\n\nWhen this capability is enabled, loading animations are automatically displayed when child apps are loading. When the sub-application is mounted and changes to the `MOUNTED` state, the loading status ends and the sub-application content is displayed.\n\nJust pass `autoSetLoading` as a parameter:\n\n```tsx\nimport { MicroApp } from '@qiankunjs/react';\n\nexport default function Page() {\n  return <MicroApp name=\"app1\" entry=\"http://localhost:8000\" autoSetLoading />;\n}\n```\n\n#### Custom loading animation\n\nIf you want to override the default loading animation style, you can set a custom loading component `loader` as the loading animation for the child app.\n\n```tsx\nimport CustomLoader from '@/components/CustomLoader';\nimport { MicroApp } from '@qiankunjs/react';\n\nexport default function Page() {\n  return (\n    <MicroApp name=\"app1\" entry=\"http://localhost:8000\" loader={(loading) => <CustomLoader loading={loading} />} />\n  );\n}\n```\n\nwhere `loading` is the `boolean` type parameter, `true` indicates that the loading state is still being loaded, and `false` indicates that the loading state has ended.\n\n### Error catching\n\nWhen this capability is enabled, an error message is automatically displayed when a child app loads unexpectedly. You can pass the `autoCaptureError` property to the sub-app to enable sub-app error capture capabilities:\n\n```tsx\nimport { MicroApp } from '@qiankunjs/react';\n\nexport default function Page() {\n  return <MicroApp name=\"app1\" entry=\"http://localhost:8000\" autoCaptureError />;\n}\n```\n\n#### Custom error capture\n\nIf you want to override the default error capture component style, you can set a custom component `errorBoundary` as the error capture component for the child app:\n\n```tsx\nimport CustomErrorBoundary from '@/components/CustomErrorBoundary';\nimport { MicroApp } from '@qiankunjs/react';\n\nexport default function Page() {\n  return (\n    <MicroApp\n      name=\"app1\"\n      entry=\"http://localhost:8000\"\n      errorBoundary={(error) => <CustomErrorBoundary error={error} />}\n    />\n  );\n}\n```\n\n### Component Props\n\n| Name | Required | Description | Type | Default |\n| --- | --- | --- | --- | --- |\n| `name` | yes | The name of the microapp | `string` |\n| `entry` | yes | The HTML address of the microapp | `string` |\n| `settings` | no | qiankun configuration for this microapp — sandbox, style isolation, fetch, … | `AppConfiguration` | `undefined` |\n| `lifeCycles` | no | qiankun lifecycle hooks for this microapp. They are captured on the first load into a given container, so avoid closing over component state in them | `LifeCycles` | `undefined` |\n| `autoSetLoading` | no | Automatically set the loading state of your microapp | `boolean` | `false` |\n| `loader` | no | Custom microapps load state components | `(loading) => React.ReactNode` | `undefined` |\n| `autoCaptureError` | no | Automatically set up error capture for microapps | `boolean` | `false` |\n| `errorBoundary` | no | Custom microapp error capture component | `(error: Error) => React.ReactNode` | `undefined` |\n| `className` | no | The style class for the microapp | `string` | `undefined` |\n| `wrapperClassName` | no | Wrap the microapp loading component, error capture component, and microapp's style classes, and are only valid when the load component or error capture component is enabled | `string` | `undefined` |\n\n## Get the child app load status\n\nThe loading status includes: \"NOT_LOADED\" | \"LOADING_SOURCE_CODE\" | \"NOT_BOOTSTRAPPED\" | \"BOOTSTRAPPING\" | \"NOT_MOUNTED\" | \"MOUNTING\" | \"MOUNTED\" | \"UPDATING\" | \"UNMOUNTING\" | \"UNLOADING\" |\n\n```tsx\nimport { useEffect, useRef } from 'react';\nimport { MicroApp, type MicroAppType } from '@qiankunjs/react';\n\nexport default function Page() {\n  const microAppRef = useRef<MicroAppType>(undefined);\n\n  useEffect(() => {\n    // Get the child app load status\n    console.log(microAppRef.current?.getStatus());\n  }, []);\n\n  return <MicroApp name=\"app1\" entry=\"http://localhost:8000\" ref={microAppRef} />;\n}\n```\n","packages/ui-bindings/shared/README.md":"# @qiankunjs/ui-shared\n\nqiankun 的 React 和 Vue 组件共用的内部模块，封装微应用的挂载、更新、加载状态和错误处理，并提供共用类型。它连接组件层与 qiankun 运行时，不建议单独使用；React 和 Vue 主应用请分别使用 `@qiankunjs/react` 和 `@qiankunjs/vue`。\n\n文档：[生态概览](https://www.qiankunjs.com/zh-CN/ecosystem/)。\n\n## English\n\nInternal modules shared by qiankun's React and Vue components, covering micro-app mounting, updates, loading state, error handling, and shared types. This package connects the component layer to the qiankun runtime and is not recommended for standalone use; React and Vue host applications should use `@qiankunjs/react` and `@qiankunjs/vue`, respectively.\n\nDocumentation: [Ecosystem overview](https://www.qiankunjs.com/ecosystem/).\n","packages/ui-bindings/vue/README.md":"# qiankun vue binding\n\n## Usage\n\n```bash\nnpm i @qiankunjs/vue\n```\n\n## MicroApp Component\n\nLoad (or unload) a sub-application directly through the `<MicroApp />` component, which provides capabilities related to loading and error capturing:\n\n```vue\n<script setup>\nimport { MicroApp } from '@qiankunjs/vue';\n</script>\n<template>\n  <micro-app name=\"app1\" entry=\"http://localhost:8000\" />\n</template>\n```\n\nWhen enabling the sub-application loading animation or error capturing capabilities, an additional style class `wrapperClassName` is accepted by the sub-application. The rendered result is as follows:\n\n```vue\n<div :class=\"wrapperClassName\">\n  <MicroAppLoader :loading=\"loading\" />\n  <ErrorBoundary :error=\"e\" />\n  <MicroApp :class=\"className\" />\n</div>\n```\n\n### Loading Animation\n\nAfter enabling this feature, a loading animation will automatically be displayed while the sub-application is loading. When the sub-application finishes mounting and becomes in the MOUNTED state, the loading state ends, and the sub-application content is displayed.\n\nSimply pass `autoSetLoading` as a parameter:\n\n```vue\n<script setup>\nimport { MicroApp } from '@qiankunjs/vue';\n</script>\n<template>\n  <micro-app name=\"app1\" entry=\"http://localhost:8000\" autoSetLoading />\n</template>\n```\n\n#### Custom Loading Animation\n\nIf you wish to override the default loading animation style, you can customize the loading component by using the loader slot as the sub-application's loading animation.\n\n```vue\n<script setup>\nimport CustomLoader from '@/components/CustomLoader.vue';\nimport { MicroApp } from '@qiankunjs/vue';\n</script>\n<template>\n  <micro-app name=\"app1\" entry=\"http://localhost:8000\">\n    <template #loader=\"{ loading }\">\n      <custom-loader :loading=\"loading\" />\n    </template>\n  </micro-app>\n</template>\n```\n\nHere, `loading` is a boolean type parameter; when true, it indicates that it is still in the loading state, and when false, it indicates that the loading state has ended.\n\n### Error Capturing\n\nAfter enabling this feature, when the sub-application encounters an exception while loading, an error message will automatically be displayed. You can pass the `autoCaptureError` property to the sub-application to enable error capturing capabilities:\n\n```vue\n<script setup>\nimport { MicroApp } from '@qiankunjs/vue';\n</script>\n<template>\n  <micro-app name=\"app1\" entry=\"http://localhost:8000\" autoCaptureError />\n</template>\n```\n\n#### Custom Error Capturing\n\nIf you wish to override the default error capturing component style, you can customize the error capturing component for the sub-application using the errorBoundary slot:\n\n```vue\n<script setup>\nimport CustomErrorBoundary from '@/components/CustomErrorBoundary.vue';\nimport { MicroApp } from '@qiankunjs/vue';\n</script>\n<template>\n  <micro-app name=\"app1\" entry=\"http://localhost:8000\">\n    <template #error-boundary=\"{ error }\">\n      <custom-error-boundary :error=\"error\" />\n    </template>\n  </micro-app>\n</template>\n```\n\n### Component Properties\n\n| Property | Required | Description | Type | Default Value |\n| --- | --- | --- | --- | --- |\n| `name` | Yes | The name of the micro-application | `string` |  |\n| `entry` | Yes | The HTML address of the micro-application | `string` |  |\n| `settings` | No | qiankun configuration for this micro-application — sandbox, style isolation, fetch, … | `AppConfiguration` | `{ sandbox: true }` |\n| `lifeCycles` | No | qiankun lifecycle hooks for this micro-application. They are captured on the first load into a given container, so avoid closing over component state in them | `LifeCycles` | `undefined` |\n| `autoSetLoading` | No | Automatically set the loading status of the micro-application | `boolean` | `false` |\n| `autoCaptureError` | No | Automatically set error capturing for the micro-application | `boolean` | `false` |\n| `className` | No | The style class for the micro-application | `string` | `undefined` |\n| `wrapperClassName` | No | The style class wrapping the micro-application's loading and error components | `string` | `undefined` |\n| `appProps` | No | Properties passed to the sub-application | `Record<string, any>` | `undefined` |\n\n### Component Slots\n\n| Slot                               | Slot props             | Description          |\n| ---------------------------------- | ---------------------- | -------------------- |\n| `loader`                           | `{ loading: boolean }` | Loading state slot   |\n| `errorBoundary` / `error-boundary` | `{ error: Error }`     | Error capturing slot |\n\nVue does not normalize slot names the way it normalizes props, so both spellings of the error slot are accepted. Unlike the React binding, this component's wrapper is not positioned — give it a positioned class through `wrapperClassName` if your slot renders an overlay.\n","skills/qiankun/SKILL.md":"---\nname: qiankun\ndescription: >-\n  Usage manual for building micro-frontend projects with qiankun 3.x. Use when the user wants to create a qiankun main (host) app or sub (micro) app, convert an existing Vite app into a qiankun micro app, or wire up micro-frontend loading with React/Vue.\n---\n\n# qiankun\n\nqiankun is a micro-frontend framework: a main (host) app loads sub (micro) apps from their HTML entries at runtime, each inside a JS sandbox, with opt-in CSS isolation. This skill is the agent-facing manual for **qiankun 3.x**.\n\n## Task routing\n\nRead only the reference file(s) the task at hand needs:\n\n| Task | Read |\n| --- | --- |\n| Create a micro (sub) app, or convert an existing Vite app | [references/create-micro-app.md](references/create-micro-app.md) |\n| Create a main (host) app | [references/create-main-app.md](references/create-main-app.md) |\n| Anything else — migration, style isolation, sandbox, debugging | https://qiankun.umijs.org and `examples/` in https://github.com/umijs/qiankun |\n\n## Before you start\n\nDetermine these, asking the user only when not inferable from context:\n\n1. **App type** — main (host/shell) or sub (micro app).\n2. **App name** — lowercase letters, numbers, hyphens. The name the host registers must exactly match the name the sub app uses in its classic-mode fallback.\n3. **Framework** — React or Vue; TypeScript or JavaScript. Templates in the references are TS; strip types for JS.\n4. **Dev port** — must be fixed and unique per app, because the host hard-references it in `entry`. Convention: main app `7099`, sub apps `7101`, `7102`, …\n\nPackage versions — until qiankun 3.0 stable ships, `rc` is the dist-tag for the core packages:\n\n| Package                               | Dist-tag | Goes in                                       |\n| ------------------------------------- | -------- | --------------------------------------------- |\n| `qiankun`                             | `rc`     | main app `dependencies`                       |\n| `@qiankunjs/react` / `@qiankunjs/vue` | `rc`     | main app `dependencies` (optional UI binding) |\n| `@qiankunjs/bundler-plugin`           | `rc`     | sub app `devDependencies`                     |\n\n## Key facts\n\nThese invariants shape every task below:\n\n- **Vite sub apps need no dedicated build mode.** qiankun 3 loads `<script type=\"module\">` natively through its ESM sandbox — regular `vite dev` / `vite build` output is qiankun-ready as-is.\n- The JS sandbox is **on by default**. CSS isolation is opt-in per app via `styleIsolation: true` (runtime CSS `@scope`).\n- A sub app must stay **runnable standalone**: when not loaded by qiankun (`window.__POWERED_BY_QIANKUN__` is undefined) it renders itself directly.\n- **Always unmount** what you mount. `loadMicroApp` returns a handle whose `.unmount()` must be called when the app leaves; the `MicroApp` component bindings do this automatically on component unmount.\n- Webpack sub apps use `@qiankunjs/bundler-plugin/webpack` instead of the Vite plugin; see the docs.\n\n## After any creation task\n\nVerify the result following the checklist at the end of the reference you used. If a browser tool is available, perform the checks yourself instead of asking the user to.\n","skills/qiankun/references/create-main-app.md":"# Create a main (host) app\n\nPrerequisites: the shared facts in [SKILL.md](../SKILL.md) — app name, framework, port, package versions.\n\n1. Scaffold the shell (any framework; below assumes React + TS on port `7099`) and install qiankun:\n\n   ```bash\n   pnpm create vite <main-app-name> --template react-ts\n   pnpm add qiankun@rc @qiankunjs/react@rc   # vue shell: @qiankunjs/vue@rc\n   ```\n\n   Pin the port in `vite.config.ts` (`server: { port: 7099, strictPort: true }`).\n\n2. Pick ONE loading style:\n\n   **`MicroApp` component (recommended for React/Vue shells)** — mounts on render, unmounts on component unmount, with built-in loading/error slots. Extra props are forwarded to the sub app (delivered via its `update` lifecycle on change):\n\n   ```tsx\n   import { MicroApp } from '@qiankunjs/react'; // same component name in @qiankunjs/vue\n\n   export default function SubAppPage() {\n     return <MicroApp name=\"<app-name>\" entry=\"//localhost:7101\" autoSetLoading />;\n   }\n   ```\n\n   **Router-driven registration (framework-agnostic)** — qiankun mounts/unmounts apps as the URL matches `activeRule`. Put this in the shell entry, once:\n\n   ```ts\n   import { registerMicroApps, start } from 'qiankun';\n\n   registerMicroApps([\n     {\n       name: '<app-name>',\n       entry: '//localhost:7101',\n       container: '#micro-app-container', // an element the shell always renders\n       activeRule: '/<app-name>',\n     },\n   ]);\n\n   start();\n   ```\n\n   For full manual control there is also `loadMicroApp({ name, entry, container }, configuration)` — it returns a handle; you own calling `.unmount()` when the app leaves.\n\n3. Per-app configuration (third argument of `loadMicroApp`, `configuration` prop of the `MicroApp` component, or per-app fields in `registerMicroApps`): `sandbox` defaults to on; add `styleIsolation: true` to scope the sub app's CSS with `@scope`.\n\n## Verify\n\n1. Run both dev servers, open the main app at `http://localhost:7099`, navigate to the sub app route — it must mount inside the shell with no console errors.\n2. Leave the sub app route — its DOM must be removed (unmount actually ran).\n3. The sub app must still render standalone at `http://localhost:7101` — see [create-micro-app.md](create-micro-app.md).\n","skills/qiankun/references/create-micro-app.md":"# Create a micro (sub) app, or convert an existing Vite app\n\nPrerequisites: the shared facts in [SKILL.md](../SKILL.md) — app name, framework, port, package versions.\n\nFor a **new** app, start at step 1. To **convert an existing Vite app**, skip to step 2 — the conversion is exactly steps 2–4 and touches nothing else (no changes to `index.html`, `App` components, or build scripts).\n\n1. Scaffold with the official Vite scaffolder (templates: `react-ts`, `react`, `vue-ts`, `vue`):\n\n   ```bash\n   pnpm create vite <app-name> --template react-ts\n   ```\n\n2. Add the bundler plugin:\n\n   ```bash\n   pnpm add -D @qiankunjs/bundler-plugin@rc\n   ```\n\n3. Edit `vite.config.ts` — register the plugin and pin the port:\n\n   ```ts\n   import { defineConfig } from 'vite';\n   import react from '@vitejs/plugin-react'; // vue: @vitejs/plugin-vue\n   import { qiankun } from '@qiankunjs/bundler-plugin/vite';\n\n   export default defineConfig({\n     plugins: [react(), qiankun()],\n     server: {\n       port: 7101,\n       strictPort: true,\n     },\n   });\n   ```\n\n4. Rewrite the entry to export qiankun lifecycles, using the template below for the app's framework. Replace `<app-name>` with the actual registered name in both templates.\n\n## React entry template (`src/main.tsx`)\n\n```tsx\nimport React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport App from './App';\nimport './index.css';\n\ndeclare global {\n  interface Window {\n    __POWERED_BY_QIANKUN__?: boolean;\n    [key: string]: unknown;\n  }\n}\n\nlet root: ReactDOM.Root | undefined;\n\nfunction render(props: { container?: Element } = {}) {\n  // when loaded by qiankun, resolve #root inside the host-provided container, not the top document\n  const container = props.container?.querySelector('#root') ?? document.getElementById('root');\n  if (!container) return;\n\n  root = ReactDOM.createRoot(container);\n  root.render(\n    <React.StrictMode>\n      <App />\n    </React.StrictMode>,\n  );\n}\n\nexport async function bootstrap() {}\n\nexport async function mount(props: { container?: Element }) {\n  render(props);\n}\n\n// the host re-delivers its props here whenever they change — react to them without remounting\nexport async function update(_props: Record<string, unknown>) {}\n\nexport async function unmount(_props: { container?: Element }) {\n  root?.unmount();\n  root = undefined;\n}\n\nif (window.__POWERED_BY_QIANKUN__) {\n  // classic-mode fallback: expose the lifecycles on window under the REGISTERED app name\n  window['<app-name>'] = { bootstrap, mount, update, unmount };\n} else {\n  render();\n}\n```\n\n## Vue entry template (`src/main.ts`)\n\n```ts\nimport { createApp, reactive } from 'vue';\nimport App from './App.vue';\nimport './style.css';\n\ndeclare global {\n  interface Window {\n    __POWERED_BY_QIANKUN__?: boolean;\n    [key: string]: unknown;\n  }\n}\n\nlet app: ReturnType<typeof createApp> | undefined;\n\n// props the host hands over — seeded on mount, kept current by `update`\nconst hostProps = reactive<Record<string, unknown>>({});\n\nfunction render(props: { container?: Element } = {}) {\n  const container = props.container?.querySelector('#app') ?? document.getElementById('app');\n  if (!container) return;\n\n  app = createApp(App, { hostProps });\n  app.mount(container);\n}\n\nexport async function bootstrap() {}\n\nexport async function mount(props: { container?: Element }) {\n  Object.assign(hostProps, props);\n  render(props);\n}\n\nexport async function update(props: Record<string, unknown>) {\n  Object.assign(hostProps, props);\n}\n\nexport async function unmount(_props: { container?: Element }) {\n  app?.unmount();\n  app = undefined;\n}\n\nif (window.__POWERED_BY_QIANKUN__) {\n  // classic-mode fallback: expose the lifecycles on window under the REGISTERED app name\n  window['<app-name>'] = { bootstrap, mount, update, unmount };\n} else {\n  render();\n}\n```\n\n## Verify\n\n1. `pnpm dev` in the sub app, open `http://localhost:7101` — it must render on its own (the non-qiankun branch of the entry).\n2. If a main app exists, run it too and confirm the sub app mounts inside it with no console errors — see the verification checklist in [create-main-app.md](create-main-app.md).\n"},"files":{".claude/skills/release-changelog/SKILL.md":"---\nname: release-changelog\ndescription: Polish a published qiankun GitHub Release's aggregated notes into a bilingual (English then Chinese) write-up with an agent. Use after a release goes out — turning the mechanically-aggregated, per-package release notes into a clean, human-readable, English + Chinese write-up (highlights, de-duped cross-package lines, clearer wording) via `gh release edit`. Triggers include \"polish the release notes\", \"clean up the GitHub release\", \"improve the changelog for the latest release\".\n---\n\n# Release changelog\n\nQiankun releases are commit-driven: CI auto-derives changesets from Conventional Commits, changesets writes each sub-package's own `CHANGELOG.md` (so npm shows a per-package changelog), and on publish `scripts/generate-release-notes.mjs` aggregates those into **one GitHub Release** per version.\n\nThat aggregated Release is **mechanical** (raw per-package changelog lines). This skill polishes it into a human-readable, **bilingual** write-up — an English version on top, a Chinese version below — after the fact, directly on the GitHub Release, without touching any committed changelog. Read `.changeset/README.md` for the surrounding flow.\n\n## When to run\n\nAfter a release has been published (the \"Version Packages\" PR was merged and CI created the GitHub Release). Typically: \"polish the latest release notes\".\n\n## Steps\n\n1. **Find the release to polish** (default: the most recent one):\n\n   ```bash\n   gh release list --limit 5\n   gh release view <tag> --json tagName,name,body\n   ```\n\n   Confirm the target tag with the user if it's not obviously the latest.\n\n2. **Read the mechanical notes** from the release `body`. This is the raw material — the aggregated per-package changelog. Do not invent anything not present in it (cross-check against `git log <prev-tag>..<tag>` only to understand, not to add unlisted changes).\n\n3. **Polish** the notes, keeping the per-package structure so consumers still see each sub-package and its version. Polishing means:\n   - Lead with a short **Highlights** list (2–5 bullets) when the release has notable user-facing changes; skip it for small patch releases.\n   - Rewrite terse lines into clear, user-facing wording; keep the `(#PR)` reference at the end of each line.\n   - De-duplicate: a change that touched many packages is often better stated once under the facade `qiankun` section than repeated under every package.\n   - Drop noise (pure \"Updated dependencies\" internal bumps).\n\n4. **Produce a bilingual document** — the full **English** version first, then the full **简体中文** version, separated by a `---` rule. Keep the identical per-package structure and `(#PR)` references in both halves; translate the prose (highlights, descriptions), never the package names, versions, or PR numbers. Template:\n\n   ```markdown\n   <!-- English -->\n\n   ## Highlights\n\n   - ...\n\n   ## qiankun `<version>`\n\n   - ...\n\n   ---\n\n   <!-- 简体中文 -->\n\n   ## 更新亮点\n\n   - ...\n\n   ## qiankun `<version>`\n\n   - ...\n   ```\n\n5. **Show the user the proposed notes** and get approval.\n\n6. **Update the release** (does not re-tag or re-publish npm):\n\n   ```bash\n   gh release edit <tag> --notes-file <polished.md>\n   ```\n\n## Do not\n\n- Do not run `gh release create`, re-tag, or re-publish to npm — only edit the notes of an existing release.\n- Do not edit any `CHANGELOG.md` or `.changeset/*` — the per-package changelogs are the committed record; this skill only refines the GitHub Release prose.\n- Do not add changes that aren't in the mechanical notes / commit history.\n","AGENTS.md":"# QIANKUN PROJECT KNOWLEDGE BASE\n\n**Updated:** 2026-07-06 · **Commit:** dcc42ae4 · **Branch:** next (qiankun 3.0, active dev)\n\nQiankun is a micro-frontend framework built on [single-spa](https://github.com/single-spa/single-spa). v3 rewrites the runtime around **streaming HTML-entry loading**, a **Proxy-membrane JS sandbox**, and native **ESM-sandbox** execution. It is a pnpm monorepo built with Vite 8; `tsc` emits package declarations.\n\n> Requires Node `^22.15 || >=24`, `pnpm@11.13.1` (see `packageManager`). Never use npm/yarn at the root.\n\n## STRUCTURE\n\n```\nqiankun/\n├── packages/\n│   ├── qiankun/         # Facade: public APIs (register/loadMicroApp, start, prefetch) + loadApp orchestration\n│   ├── sandbox/         # JS isolation: Proxy membrane + Compartment  → packages/sandbox/AGENTS.md\n│   ├── loader/          # Streaming HTML-entry loader (writable-dom)  → packages/loader/AGENTS.md\n│   ├── shared/          # Transpilers, fetch-utils, module-resolver, ESM-sandbox engine → packages/shared/AGENTS.md\n│   ├── single-spa/      # Vendored single-spa fork (@qiankunjs/single-spa, upstream 7.0 @ ce0f925a) → packages/single-spa/README.md\n│   ├── ui-bindings/     # <MicroApp> components: react/ vue/ shared/ (dogfooded by examples/{main,vue-host})\n│   └── bundler-plugin/  # Webpack(4/5) + Vite plugins: mark entry script, fix output library\n├── skills/              # Agent skills (installed by users via `npx skills add umijs/qiankun`) — content, not code: no build/publish\n├── e2e/                 # Playwright, runs against BUILT dist → e2e/README.md\n├── examples/            # Runnable demos: two hosts (main = @qiankunjs/react, vue-host = @qiankunjs/vue) + react/vue/purehtml/webpack\n└── docs/                # VitePress site (docs/rfcs holds design RFCs)\n```\n\n## PROGRESSIVE DISCLOSURE — read the scoped doc before editing a package\n\n| Working in… | Read first |\n| --- | --- |\n| `packages/sandbox/**` | [`packages/sandbox/AGENTS.md`](packages/sandbox/AGENTS.md) — membrane, compartment, patchers, ESM globals |\n| `packages/loader/**` | [`packages/loader/AGENTS.md`](packages/loader/AGENTS.md) — streaming pipeline, head virtualization |\n| `packages/shared/**` | [`packages/shared/AGENTS.md`](packages/shared/AGENTS.md) — transpilers, fetch decorators, **esm-sandbox** |\n| `e2e/**` | [`e2e/README.md`](e2e/README.md) — Playwright layout, fixtures, anti-flake rules |\n\n## ARCHITECTURE (big picture)\n\n`loadApp` (`packages/qiankun/src/core/loadApp.ts`) is the orchestrator. Per micro-app it wires:\n\n1. **fetch** — decorated `window.fetch`: `makeFetchCacheable(makeFetchRetryable(makeFetchThrowable(fetch)))`.\n2. **sandbox** — `createSandbox()` builds a Proxy-membrane `window`/`document` view; patchers (dynamicAppend, timers, listeners, history) each return a `free()` cleanup called on unmount. Without a container it uses the JS-only preset; a container enables DOM containment.\n3. **loader** — `loadEntry(entry, container, opts)` streams the HTML entry through `writable-dom`, virtualizing `<head>` → `<qiankun-head>` and running each node through a `nodeTransformer`.\n4. **transpilers** (`shared/assets-transpilers`) rewrite each script/link/style node before it hits live DOM.\n\nMicro apps sharing one container element take FIFO turns for their DOM writes through the **container occupancy gate** (`core/containerOccupancy.ts`, two critical sections: load-phase streaming and mount→unmount; see `docs/rfcs/container-occupancy-gate.md`).\n\nTwo execution paths, chosen per script type:\n\n- **Classic** (`<script entry>`, UMD/global): source is wrapped and run via a **blob URL** scoped to the sandbox membrane. The app's export = `sandbox.latestSetProp` (the last global the entry script set).\n- **ESM** (`<script type=\"module\">`): handled by `EsmSandboxEngine` (`shared/esm-sandbox`). Modules are fetched, lexer-rewritten to route globals through the membrane, given synthetic specifiers via a dynamically injected **import map**, and evaluated in order. The engine also handles dynamic `import()`.\n\n**Style isolation** (`shared/assets-transpilers/style.ts` + `link.ts`) uses CSS `@scope` at runtime; external stylesheets become blob-`<link>`s so `@scope` can wrap them. Opt-in via `styleIsolation`.\n\nInternal dependency graph (never invert it):\n\n```\nqiankun → loader → sandbox → shared\n                   sandbox → shared\nqiankun → single-spa   (vendored fork; zero deps, bottom of the graph — must not import shared/sandbox/loader)\nui-bindings/{react,vue} → ui-bindings/shared → qiankun\n```\n\n## COMMANDS\n\n```bash\npnpm install                 # install all workspace deps\n\n# build (Vite 8 → package JavaScript; tsc → declarations)\npnpm run build               # build everything (packages + examples)\npnpm run build:packages      # build only packages/** (prereq for e2e & examples)\n\n# unit tests — vitest + happy-dom, aliased to src (NO build needed, see vitest.config.ts)\npnpm run test                            # all packages\npnpm --filter @qiankunjs/shared run test # one package\npnpm --filter @qiankunjs/sandbox exec vitest run path/to.test.ts   # single file\npnpm --filter @qiankunjs/shared  exec vitest run -t \"test name\"    # single test by name\n\n# e2e — Playwright against BUILT dist (see e2e/README.md)\npnpm run test:e2e            # build all + run chromium suite\n\n# lint / format / full CI gate\npnpm run eslint              # eslint packages/\npnpm run prettier:check      # prettier -c .\npnpm run ci                  # build + eslint + prettier:check (what CI runs)\n\n# dev\npnpm run start:example       # build packages + run all example apps in parallel\npnpm run docs:dev            # VitePress docs\n```\n\n## CONVENTIONS (enforced by ESLint flat config — `pnpm run eslint` will reject violations)\n\nTypeScript is strict + type-checked (`typescript-eslint`'s type-checked flat config):\n\n- **No `any`** — `no-explicit-any` auto-fixes to `unknown`. No `as any`, `@ts-ignore`, `@ts-expect-error`.\n- **Inline type imports** — `import { type Foo, bar }`, not `import type { Foo }` on its own line (`consistent-type-imports`/`consistent-type-exports` with `fixStyle: inline-type-imports`).\n- `no-unnecessary-condition` is an error — don't guard values the types prove are always truthy.\n- Unused vars/args must be prefixed `_` (`argsIgnorePattern: ^_`).\n- `array-simple`: `T[]` for simple, `Array<T>` for complex element types.\n- JS-wide: `no-else-return` (no `else` after `return`), `object-shorthand`.\n- Path alias `@qiankunjs/*` → `packages/*/src` (tsconfig + vitest); imports resolve to **source**, not dist.\n\nBuild/release:\n\n- Vite 8 builds browser package JavaScript as ESM+CJS, while `tsc` emits declarations. Package entry fields and subpath exports must point at the corresponding `dist` outputs.\n- TypeScript 7 is installed as `@typescript/native` and owns the `tsc` binary. The `typescript` dependency intentionally aliases `@typescript/typescript6` because TypeScript 7.0 has no programmatic API yet and tools such as `typescript-eslint` and `vue-tsc` still require that compatibility API.\n- Versioning via **changesets**, but changesets are **auto-derived from Conventional Commits** in CI (`scripts/generate-changesets.mjs`) — do **not** hand-write `.changeset/*.md`. Just land a well-formed conventional commit (`feat`/`fix`/`feat!`…); the release job maps changed files → packages and infers the bump. Each sub-package keeps its own `CHANGELOG.md` (changeset default, visible on npm); on publish `scripts/generate-release-notes.mjs` aggregates them into **one GitHub Release**, which can be polished after the fact via the `/release-changelog` skill (`gh release edit`). Full flow: `.changeset/README.md`.\n- Conventional commits enforced by commitlint (`feat:`, `fix:`, `feat(esm-sandbox):`, …).\n\n## DOCS & README CONVENTIONS\n\n- **Chinese-first.** The primary audience is Chinese. `docs/zh-CN/` is the source of truth: write it to a native-author standard — no translationese, no AI-flavored filler (「让我们」「值得注意的是」「强大的/无缝的」), no calqued English syntax. The English pages align to the Chinese semantics, not the other way around; pure zh wording fixes don't require touching en. Same contract for `README.zh-CN.md` ↔ `README.md`: keep both editions in sync whenever either changes.\n- **Mainland reachability is a hard constraint.** Google Fonts only via the `.cn` mirrors (`fonts.googleapis.cn`/`fonts.gstatic.cn`); never reference resources on domains unreachable from mainland China.\n- **Domains**: canonical docs domain is `https://www.qiankunjs.com` (zh under `/zh-CN/`); `qiankun.umijs.org` stays alive and 307s to it; v2 docs live at `v2.qiankun.umijs.org`; live examples at `examples.qiankunjs.com`. Old-site URLs are kept working via `docs/public/_redirects` — extend it when moving pages, never break inbound links.\n- **Consistency rules**: terminology never drifts (主应用/微应用/沙箱/隔离膜; qiankun always lowercase); link text in reference lists (\"相关内容\"/\"继续阅读\"/\"延伸阅读\") equals the target page's H1; API pages use 「函数签名」/「默认值为 X」; `guide/browser-support` is the single source of truth for browser requirements — link it, don't restate version numbers elsewhere.\n- Until 3.0 reaches npm `latest`, install commands must say `qiankun@rc`, and the site shows a version banner (layout-top slot in `docs/.vitepress/theme/index.js`; remove it together with `--vp-layout-top-height` once stable ships).\n- **README GitHub alerts**: put the `[!WARNING]`/`[!NOTE]` marker on its own line followed by a blank `>` line. Prettier's `proseWrap: never` re-joins plain soft breaks, which silently breaks alert rendering.\n- VitePress compiles every `.md` as a Vue SFC: bare `<Tag>` in prose/link text or `{{` in inline code breaks the build (fenced code blocks are safe).\n\n## ANTI-PATTERNS (this project)\n\n- **NEVER** put more than one `entry` script in an HTML entry — the loader throws `QiankunError`.\n- **ALWAYS** unmount micro-apps; `loadMicroApp`/patchers return handles/`free()` — leaks break remount & multi-instance.\n- In sandbox code, **never touch the real `window`/`document.head`** — go through the proxied view.\n- The vendored `loader/src/writable-dom/` fork accepts **generic designs only** — never qiankun-coupled semantics; caller bookkeeping goes through its `assetTransformer` callback (see `packages/loader/AGENTS.md`).\n- Don't invert the package dependency graph above (e.g. `shared` must not import `sandbox`).\n- e2e: never `waitForTimeout`; use web-first assertions; all ports come from `e2e/ports.ts`.\n- Performance work is judged in this order: **proportional impact first** (does the win scale with asset size / network / app scale — e.g. eliminating a duplicate download), **readability second** (never trade it away for small wins), **constant absolute savings last** (fixed single-digit-ms pipeline costs are noise in real apps and rarely worth landing). Beware: the ~50ms benchmark fixture makes fixed milliseconds masquerade as percentages — convert to absolute ms against realistic load times before deciding. And fix root causes at the source, not with per-call-site markers/exemptions downstream.\n\n## NOTES\n\n- Firefox doesn't support dynamically injected import maps → ESM-sandbox e2e tests are annotated `test.fail(firefox, …)` (expected failure, not skip). See `e2e/README.md`.\n- Design decisions live in `docs/rfcs/` (e.g. the ESM-sandbox RFC).\n- v3 roadmap: github.com/umijs/qiankun/discussions/1378.\n","CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\nAll project guidance lives in `AGENTS.md` (imported below). It links out to package-scoped `packages/*/AGENTS.md` files for deep-dives — read those on demand when editing a given package.\n\n@AGENTS.md\n","packages/bundler-plugin/README.md":"# @qiankunjs/bundler-plugin\n\nBundler plugins for the [qiankun](https://github.com/umijs/qiankun) micro-frontend framework. Currently supports Webpack (4 & 5) and Vite (5+).\n\n## Installation\n\nUsing npm:\n\n```bash\nnpm install @qiankunjs/bundler-plugin --save-dev\n```\n\nOr using yarn:\n\n```bash\nyarn add @qiankunjs/bundler-plugin --dev\n```\n\n## Webpack Plugin\n\n### Features\n\n- Automatically sets the name and format of the output library\n- Ensures the uniqueness of the `jsonpFunction`/`chunkLoadingGlobal` name\n- Sets the global object to `window`, ensuring the library can run in the browser\n- Automatically adds an entry marker to the entry script tag in HTML\n- Supports both Webpack 4 and Webpack 5\n\n### Usage\n\nIn your `webpack.config.js` or other configuration files:\n\n```javascript\nconst { QiankunWebpackPlugin } = require('@qiankunjs/bundler-plugin');\n\nmodule.exports = {\n  plugins: [\n    new QiankunWebpackPlugin({\n      packageName: 'optionalPackageName',\n    }),\n  ],\n};\n```\n\n### Options\n\n- `packageName`: Specifies the name of the output library. If not provided, the name from `package.json` will be used.\n\n### Entry Script Detection\n\nThe plugin automatically detects and marks the entry script in your HTML:\n\n1. Uses webpack's `compilation.entrypoints` API to identify entry chunks\n2. For single entry builds: marks the main entry chunk's script\n3. For multi-entry builds: matches HTML filename to entrypoint name\n4. Falls back to the last script tag if automatic detection fails\n\n## Vite Plugin\n\nqiankun loads Vite apps natively through its ESM sandbox — no legacy/SystemJS transformation is required. The Vite plugin only takes care of the loading plumbing:\n\n### Features\n\n- Responds with permissive CORS headers from the dev and preview servers, so the main app can fetch the entry HTML and module graph cross-origin\n- Marks the entry module script with the `entry` attribute in the built HTML (same contract as the webpack plugin), so the qiankun loader picks the entry deterministically\n\n### Usage\n\nIn your `vite.config.ts`:\n\n```typescript\nimport { defineConfig } from 'vite';\nimport { qiankun } from '@qiankunjs/bundler-plugin/vite';\n\nexport default defineConfig({\n  plugins: [qiankun()],\n});\n```\n\nYour entry module just exports the qiankun lifecycles directly:\n\n```typescript\nexport async function bootstrap() {}\nexport async function mount(props) {\n  // render your app into props.container\n}\nexport async function unmount(props) {\n  // tear your app down\n}\n```\n\n## Contributing\n\nAny form of contribution is welcome! Please submit PRs or open issues for discussion.\n\n## License\n\nMIT\n","packages/loader/AGENTS.md":"# @qiankunjs/loader\n\nStreaming HTML-entry loader built on a forked `writable-dom`. Depends on `@qiankunjs/sandbox` + `@qiankunjs/shared`.\n\n## STRUCTURE\n\n```\nloader/\n├── index.ts              # loadEntry() — main API\n├── TagTransformStream.ts # <head> → <qiankun-head> string-level replacement\n├── writable-dom/         # forked streaming DOM engine (incremental parse + blocking scripts)\n│   └── index.ts\n└── parser.ts             # static HTML parsing for prefetch\n```\n\n## WHERE TO LOOK\n\n| Task | File | Notes |\n| --- | --- | --- |\n| Load micro-app | `index.ts` | `loadEntry(entry, container, opts)`; `opts.compartment` is the single sandbox/module facade |\n| Head virtualization | `TagTransformStream.ts` | rewrites `<head>` tags at the string level before DOM insertion |\n| Streaming DOM | `writable-dom/index.ts` | incremental parsing, blocks on sync scripts/styles, preloads the rest |\n\n## LOADING PIPELINE\n\n```\n1. fetch(entry)                              (decorated fetch: cacheable/retryable/throwable)\n2. TextDecoderStream                         bytes → string\n3. TagTransformStream                        <head> → <qiankun-head>\n4. WritableDOMStream                         stream → live DOM\n   ├─ nodeTransformer(node) per node         → shared/assets-transpilers rewrites script/link/style\n   ├─ classic <script entry> → synchronous Compartment host-adapter rewrite, then blob-URL execution\n   ├─ <script type=\"module\"> → registered through the compartment module facade\n   └─ blocks on sync scripts; preloads other assets while blocked\n5. resolve app export                        classic: compartment.latestSetProp · esm: compartment.importDocumentModules()\n```\n\n## KEY PATTERNS\n\n### Script classification (`index.ts`)\n\n```typescript\nisExternalScript; // has src or data-src\nisEntryScript; // external + [entry]  → resolves the load promise (app's main export point)\nisDeferScript; // external + [defer]  → ordered via shared prepareDeferredQueue\n```\n\n- Exactly **one** `entry` script is allowed per HTML entry; a second one throws `QiankunError`.\n- Loader and asset-transpiler interfaces must not mention `EsmSandboxEngine` or the concrete `Sandbox` class; consume structural `CompartmentLoaderFacade`.\n- `transformClassicScript()` is a stable sandbox-owned host adapter because detached node transformation is synchronous. `evaluateScript()` schedules a separate script asynchronously and cannot replace it in the streaming pipeline.\n\n### Detached parsing\n\nNodes are parsed/transformed in a detached document first, then moved to live DOM — this prevents premature script execution before the transpiler has rewritten the node.\n\n### writable-dom fork discipline\n\n`writable-dom/` is vendored from marko-js/writable-dom and periodically re-synced; every deviation carries a `[qiankun]` comment so syncs can re-apply them mechanically. Changes there demand deliberate thought and must stay **generic**: general-purpose hooks or upstream bug fixes only — never qiankun-specific semantics coupled to other packages (no `@qiankunjs/*` imports, no sandbox marks, no downstream contract knowledge). The fork's one integration seam is the `assetTransformer` callback (every element passes through it right before insertion); caller bookkeeping belongs in the callback `loadEntry` provides (`index.ts`), on the caller's side of that seam. The loader itself carries no sandbox semantics either: a sandbox-provided `nodeTransformer` stamps its own output (e.g. the native-passthrough mark its patcher consumes) — the loader just routes every element through whatever transformer it was given.\n\n## ANTI-PATTERNS\n\n- **NEVER** include more than one `entry` script per HTML entry (throws `QiankunError`).\n- **NEVER** add qiankun-coupled logic inside `writable-dom/` — generic designs only; hang caller bookkeeping on the `assetTransformer` callback in `loadEntry` (see the fork discipline above).\n- **FIXME** (in code): non-standard HTML chunks that lack a `<head>` tag.\n\n## EXPORTS (`src/index.ts`)\n\n```typescript\nexport { loadEntry, type LoaderOpts } from './index';\n// LoaderOpts = { fetch, compartment?, nodeTransformer?, streamTransformer?, onDOMStreamSettled? } & BaseTranspilerOpts\n// onDOMStreamSettled: notified exactly once when the DOM-write phase is over (stream piped, errored,\n// or never started) — distinct from the returned promise, which can settle at the entry script's\n// onload while tail nodes are still streaming; qiankun's container gate keys its release on it\n```\n","packages/loader/README.md":"# @qiankunjs/loader\n\nqiankun 的流式 HTML 入口加载器，负责将微应用的 HTML 逐步写入容器，并通过沙箱提供的接口完成资源转换和脚本执行。它是 qiankun 加载流程的底层组件；业务应用建议使用 `qiankun` 的公开 API，无需单独安装本包。\n\n文档：[HTML 入口](https://www.qiankunjs.com/zh-CN/concepts/html-entry-loading)。\n\n## English\n\nThe streaming HTML-entry loader for qiankun. It incrementally writes a micro-app's HTML into its container and uses sandbox-provided interfaces for asset transformation and script execution. This package is a low-level part of qiankun's loading pipeline; application code should use the public `qiankun` APIs without installing it separately.\n\nDocumentation: [HTML entry](https://www.qiankunjs.com/concepts/html-entry-loading).\n","packages/loader/src/writable-dom/README.md":"# Fork from https://github.com/marko-js/writable-dom\n","packages/qiankun/README.md":"# qiankun（乾坤）\n\n<p align=\"center\">\n  <a href=\"https://qiankun.umijs.org/\" target=\"_blank\" rel=\"noopener noreferrer\">\n    <img width=\"180\" src=\"https://gw.alipayobjects.com/zos/bmw-prod/8a74c1d3-16f3-4719-be63-15e467a68a24/km0cv8vn_w500_h500.png\" alt=\"qiankun logo\">\n  </a>\n</p>\nurl('')\n\n[![npm version](https://img.shields.io/npm/v/qiankun.svg?style=flat-square)](https://www.npmjs.com/package/qiankun) [![coverage](https://img.shields.io/codecov/c/github/umijs/qiankun.svg?style=flat-square)](https://codecov.io/gh/umijs/qiankun) [![npm downloads](https://img.shields.io/npm/dt/qiankun.svg?style=flat-square)](https://www.npmjs.com/package/qiankun) [![Build Status](https://img.shields.io/travis/umijs/qiankun.svg?style=flat-square)](https://travis-ci.com/umijs/qiankun)\n\n> In Chinese traditional culture `qian` means heaven and `kun` stands for earth, so `qiankun` is the universe.\n\nAn implementation of [Micro Frontends](https://micro-frontends.org/), based on [single-spa](https://github.com/CanopyTax/single-spa), but made it production-ready.\n\n## 🤔 Motivation\n\nAs we know what micro-frontends aims for:\n\n> Techniques, strategies and recipes for building a **modern web app** with **multiple teams** using **different JavaScript frameworks**. — [Micro Frontends](https://micro-frontends.org/)\n\nModularity is very important for large application. By breaking down a large system into individual sub-applications, we can achieve good divide-and-conquer between products and when necessary combination, especially for enterprise applications that usually involve multi-team collaboration. But if you're trying to implement such a micro frontends architecture system by yourself, you're likely to run into some tricky problems:\n\n- In what form do sub applications publish static resources?\n- How does the main application integrate individual sub-applications?\n- How do you ensure that sub-applications are independent of each other (development independent, deployment independent) and runtime isolated?\n- Performance issues? What about public dependencies?\n- And so on...\n\nAfter solving these common problems of micro frontends, we extracted the kernel of our solution after a lot of internal online application testing and polishing, and then named it `qiankun`.\n\n**Probably the most complete micro-frontends solution you ever met🧐.**\n\n## 📦 Installation\n\n```shell\n$ pnpm run add qiankun\n```\n\n## 📖 Documentation\n\nhttps://qiankun.umijs.org/\n\n## 💿 Getting started\n\nThis repo contains an `examples` folder with a sample Shell app and multiple mounted Micro FE apps. To run this app, first clone `qiankun`:\n\n```shell\n$ git clone https://github.com/umijs/qiankun.git\n$ cd qiankun\n```\n\nNow run the pnpm run scripts to install and run the examples project:\n\n```shell\n$ pnpm run install\n$ pnpm run examples:install\n$ pnpm run examples:start\n```\n\nVisit `http://localhost:7099`.\n\n![](./examples/example.gif)\n\n## :sparkles: Features\n\n- 📦 **Based On [single-spa](https://github.com/CanopyTax/single-spa)**\n- 📱 **Technology Agnostic**\n- 💪 **HTML Entry Access Mode**\n- 🛡 **Style Isolation**\n- 🧳 **JS Sandbox**\n- ⚡ **Prefetch Assets**\n- 🔌 **[Umi Plugin](https://github.com/umijs/plugins/tree/master/packages/plugin-qiankun) Integration**\n\n## 🎯 Roadmap\n\n- [x] Parcel apps integration (multiple sub apps displayed at the same time, but only one uses router at most)\n- [x] Communication development kits between master and sub apps\n- [ ] Custom side effects hijacker\n- [ ] Nested Microfrontends\n\n## ❓ FAQ\n\nhttps://qiankun.umijs.org/faq/\n\n## 👬 Community\n\nhttps://qiankun.umijs.org/#community\n\n## 🎁 Acknowledgements\n\n- [single-spa](https://github.com/CanopyTax/single-spa) What an awesome meta-framework for micro-frontends!\n- [import-html-entry](https://github.com/kuitos/import-html-entry/) An assets loader which supports html entry.\n","packages/sandbox/AGENTS.md":"# @qiankunjs/sandbox\n\nJS isolation engine: a Compartment-shaped facade owns the Proxy **Membrane**, classic-script evaluator, and module hooks. DOM behavior lives in independently registered **IsolationPlugin** instances. Depends only on `@qiankunjs/shared`.\n\n## STRUCTURE\n\n```\nsandbox/\n├── core/\n│   ├── sandbox/          # createSandbox() + StandardSandbox preset + container protocol\n│   ├── membrane/         # Proxy wrapper for global (window/document) isolation\n│   ├── compartment/      # globals + module hooks + blob classic evaluation facade\n│   ├── globals.ts        # global property definitions\n│   └── esm-globals.ts    # esmDestructurableGlobals — globals the ESM engine may destructure/rebind\n├── patchers/             # public IsolationPlugin protocol + built-in plugin presets\n│   ├── dynamicAppend/    # appendChild/insertBefore interception → redirect to app container\n│   ├── windowListener.ts # event listener tracking\n│   ├── interval.ts       # timer tracking\n│   └── historyListener.ts\n└── consts.ts             # qiankunHeadTagName / qiankunBodyTagName, nativeGlobal, nativeDocument\n```\n\n## WHERE TO LOOK\n\n| Task | File | Notes |\n| --- | --- | --- |\n| Create sandbox | `core/sandbox/index.ts` | Builds a `StandardSandbox`, installs the JS-only or DOM preset, and returns mount/unmount/dispose |\n| Prepare container | `core/sandbox/container.ts` | Owns `<qiankun-head>`, `data-name`, style scope, and cleanup contracts |\n| Proxy logic | `core/membrane/index.ts` | Write → local target; Read → local → configured globals → host window |\n| Compartment facade | `core/compartment/index.ts` | Owns the membrane, module facade, and CSP-safe blob classic evaluation |\n| Plugin protocol | `patchers/types.ts` | Public `IsolationPlugin`, context, `Free`, and `Rebuild` contracts |\n| Built-in plugins | `patchers/index.ts` | Default plugins; `dynamicAppend` joins only with a container; user plugins append after these |\n| DOM interception | `patchers/dynamicAppend/forStandardSandbox.ts` | Redirects dynamic script/style/link to app container |\n| Side-effect cleanup | `patchers/*.ts` | Each patcher returns a `free()` called on unmount |\n| ESM globals contract | `core/esm-globals.ts` | Consumed by `shared/esm-sandbox` engine, passed as `globalsBaseSet` |\n\n## KEY PATTERNS\n\n### Membrane (Proxy)\n\n- **Writes** are trapped and stored on a local `target` object (the sandbox's own globals).\n- **Reads** check local target → configured globals → fall back to the real host window.\n- **Native rebinding**: `fetch`, `console`, etc. are rebound to the real receiver to avoid \"Illegal invocation\".\n- `latestSetProp` records the last global the entry script assigned — that's how the loader recovers a classic app's exported lifecycles when no explicit export exists.\n- **Non-transitive by design**: unlike full membranes (es-membrane, Salesforce near-membrane), only the global (and document view) entry point is proxied — objects crossing the boundary keep their raw identity (`===`/`instanceof` hold across it). This is qiankun's deliberate isolation stance (isolate writes, share identity, for DOM compatibility and performance), not a gap to fill. Do NOT wrap crossing objects with proxies. The Membrane is a private mechanism of `Compartment` — the spec has no membrane concept, which is exactly why it must stay behind the facade.\n- `incubatorContext` (the host window the view reads through to) is a qiankun host extension: the Compartment spec and ses isolate by absence and have no read-through concept. The name borrows the ShadowRealm proposal's official \"incubator realm\" term for the creating context.\n\n### Compartment surface and deliberate differences\n\n`new Compartment({ globals, modules, resolveHook, importHook, loadHook, transforms })` owns its `globalThis`. StandardSandbox adds qiankun self-references (`window`, `self`, `globalThis`, `top`, `parent`) in a second phase with `defineUnshadowableGlobals()`.\n\n- Deliberately absent: synchronous `evaluate`, `harden`, and `lockdown`.\n- qiankun host extensions: DOM globals, `evaluateScript`, the synchronous `transformClassicScript` streaming adapter, lifecycle and entry-discovery facades, module-document registration, `defineUnshadowableGlobals`, `dispose`, `incubatorContext` / `moduleHost`, and the IsolationPlugin protocol. The executable list lives in `COMPARTMENT_HOST_EXTENSIONS`.\n- `evaluateScript()` uses a native blob `<script>` path. Do not introduce `eval` or `new Function` as an implementation shortcut.\n- `transformClassicScript()` exists because loader node transformation must finish synchronously before the original node enters live DOM. Loader consumes it through structural `CompartmentLoaderFacade`; never restore a concrete-class or private WeakMap bridge.\n- The pre-alignment names `Endowments` and `addIntrinsics()` have been removed entirely (the package has no external users, so no compatibility aliases). Use `CompartmentGlobals` / `globals` and `defineUnshadowableGlobals()`.\n\nThe module engine in `@qiankunjs/shared` is an implementation detail behind `Compartment.import()`, `load()`, and `importDocumentModules()`. Cross-package consumers must depend on the structural Compartment facade, never `EsmSandboxEngine`.\n\n`createSandbox().dispose()` is the terminal owner cleanup: it frees active plugin effects, restores container protocol state, deactivates the sandbox, and idempotently disposes the Compartment. Initial load failures must call it without replacing the original loading error.\n\nFetch precedence in `createSandbox()` is \"more specific wins\": module sources use `compartmentOptions.moduleHost.fetch ?? fetch ?? native`, dynamic assets use `fetch ?? native`. This is the one exception to the rule that promoted top-level options beat `compartmentOptions`; qiankun relies on it to give module sources a separate response cache.\n\n`createSandbox(appName)` is the JS-only preset: interval, window-listener, and history cleanup are available without a container. Providing `container` enables dynamic DOM interception; `mount()` prepares standalone containers lazily (`provisionContainerHead`, default `true` — qiankun's loader passes `false` because the entry HTML materializes the head in-stream). The controller keeps **two views of one configured transformer**, split by call-site identity: the public `nodeTransformer` is the pipeline variant — its output is stamped for native passthrough (`core/nativePassthrough.ts`), so patched mount points insert it untouched and it never enters the dynamic ledger — while the dynamic-append patcher receives the bare variant through the plugin config, whose output must NOT carry the mark (re-inserted nodes depend on re-entering the pipeline for ledger bookkeeping).\n\n### IsolationPlugin / Free pattern\n\n```typescript\nconst plugin: IsolationPlugin = {\n  name: 'example',\n  bootstrap: (ctx) => installBeforeAppCode(ctx),\n  mount: async (ctx) => installForThisMount(ctx),\n};\n\nconst free = await plugin.mount?.(context);\nif (free) {\n  const rebuild = free(); // unmount: remove this plugin's side effects\n  await rebuild(container); // remount: restore recorded side effects\n}\n```\n\n- `bootstrap` runs synchronously before any application script is evaluated.\n- `mount` runs on every mount and may asynchronously produce its `Free`.\n- Built-in plugins run in preset order; user plugins run afterward in registration order.\n- Prototype-level coordination belongs in browser-realm shared state with refcounts; it is not per-app context state. Use a stable `Symbol.for(...)` slot on `nativeGlobal` when independently bundled qiankun copies must coordinate the same prototype patch.\n- Code under `patchers/**` may use only the public Compartment surface. ESLint forbids imports from membrane internals.\n\n### Realm-shared metadata\n\n- `dynamicAppend.sharedState` is stored on `nativeGlobal` under `Symbol.for('qiankun.dynamicAppend.sharedState')`, so multiple qiankun copies in the same browser realm share prototype/CSSOM refcounts and ownership metadata.\n- `sandboxConfigs` — per-Compartment dynamic-append config.\n- `elementConfigs` — which Compartment owns each dynamically appended DOM node.\n- `containerOwners` — which Compartment currently patches a micro-app container.\n\n## ANTI-PATTERNS\n\n- **NEVER** access the real `window` / `document.head` directly — always the proxied view.\n- **NEVER** import membrane internals from a plugin or expose `EsmSandboxEngine` in a cross-package contract.\n- **NEVER** move dynamicAppend/style/timer/listener behavior into Compartment itself; those are DOM host plugins.\n- **FIXME** (in code): indirect `eval` in the membrane can let System.js escape sandbox scope.\n- **FIXME**: the runtime-container global may miss monkey-patched append logic.\n\n## REPLACEMENT BOUNDARY\n\nIf a native Compartment becomes available, it may replace only globalThis virtualization, the evaluator, and the module graph. Document proxying, dynamic append interception, style isolation, timers/listeners cleanup, and history integration remain qiankun-owned DOM plugins because ECMAScript Compartment proposals do not define DOM behavior. Keep the plugin layer dependent only on the public Compartment surface so this boundary remains real.\n\n## EXPORTS (`src/index.ts`)\n\n```typescript\nexport * from './core/sandbox'; // createSandbox, prepareSandboxContainer, StandardSandbox, controller/options types\nexport * from './core/compartment'; // Compartment, options, compatibility lists\nexport * from './consts'; // qiankunHeadTagName, qiankunBodyTagName, nativeGlobal, nativeDocument\nexport { esmDestructurableGlobals } from './core/esm-globals';\nexport type { IsolationPlugin, IsolationPluginContext, Free, Rebuild } from './patchers';\n```\n","packages/sandbox/README.md":"# @qiankunjs/sandbox\n\n`@qiankunjs/sandbox` provides qiankun's browser sandbox as a standalone package. It can isolate classic-script and ESM global writes, and can optionally contain dynamic DOM and styles inside an application container. It does not require `qiankun` or `@qiankunjs/loader`.\n\n> This package is browser-only. It uses DOM APIs, blob URLs, and dynamically injected import maps; it does not support Node.js or SSR execution.\n\n## Choose an API layer\n\n| Need | API | What it provides |\n| --- | --- | --- |\n| JavaScript isolation only | `StandardSandbox` | A safe preset for classic scripts and ESM, including `window`/`self` self-references |\n| Low-level Compartment mechanics | `Compartment` | A Compartment-shaped global and module-hook facade for advanced hosts |\n| Browser containment and lifecycle | `createSandbox()` | `StandardSandbox` plus optional DOM/style containment, timer and listener cleanup, and isolation plugins |\n\nUse `StandardSandbox` for direct classic-script evaluation. A bare `Compartment` deliberately does not install a sandboxed `window` self-reference. Calling `Compartment.evaluateScript()` without defining one emits a development warning, but it does not change execution semantics: code such as `window.answer = 42` may write to the host window.\n\n## JavaScript isolation\n\n### Classic scripts\n\n```ts\nimport { StandardSandbox } from '@qiankunjs/sandbox';\n\nconst sandbox = new StandardSandbox('report-widget', {\n  tenantId: 'acme',\n});\n\nawait sandbox.evaluateScript(\n  `\n    window.reportState = { tenantId };\n  `,\n  { sourceURL: 'https://widgets.example/report.js' },\n);\n\nconsole.log(Reflect.get(sandbox.globalThis, 'reportState')); // { tenantId: 'acme' }\nconsole.log(Reflect.get(window, 'reportState')); // undefined\n\nsandbox.dispose();\n```\n\n`evaluateScript()` executes through a native blob script. It does not use `eval` or `new Function`.\n\n### ESM\n\n```ts\nconst sandbox = new StandardSandbox('report-module');\nconst namespace = await sandbox.import(new URL('./report-entry.js', document.baseURI).href);\n\nconsole.log(namespace);\nsandbox.dispose();\n```\n\nRelative module resolution defaults to `document.baseURI`. `StandardSandbox` accepts custom module tables and hooks through its fourth, Compartment-options argument; `createSandbox()` promotes `modules`, `resolveHook`, `importHook`, and `loadHook` to top-level options. See the [sandbox plugins guide](../../docs/cookbook/sandbox-plugins.md) for an advanced example.\n\n`createSandbox()` fetches dynamic assets and module sources with its top-level `fetch` option, which defaults to the native fetch. To give module sources their own transport, such as a separate response cache, set `compartmentOptions.moduleHost.fetch`. It is the only `compartmentOptions` field that takes precedence over a top-level option: the more specific module-source fetch wins when loading modules, while dynamic assets keep using the top-level `fetch`.\n\n## Full browser sandbox\n\nPass a container to `createSandbox()` to enable document virtualization and dynamic DOM interception. Set `styleIsolation: true` to scope application styles to that container.\n\n```ts\nimport { createSandbox } from '@qiankunjs/sandbox';\n\nconst container = document.querySelector<HTMLElement>('#widget')!;\nconst controller = createSandbox('report-widget', {\n  container,\n  styleIsolation: true,\n});\n\n// Mount before application code so timers and listeners are tracked.\nawait controller.mount();\n\nconst response = await fetch('/vendor/report-widget.js');\nconst source = await response.text();\nawait controller.instance.evaluateScript(source, {\n  sourceURL: response.url,\n});\n\n// Releases active plugin effects and keeps the controller available for remount.\nawait controller.unmount();\nawait controller.mount();\n\n// Terminal cleanup. A disposed controller cannot be mounted or evaluated again.\nawait controller.dispose();\n```\n\nWhen `container` is a function, it is resolved again for each mount. This supports hosts that replace the mount element between activations:\n\n```ts\nlet currentContainer = document.querySelector<HTMLElement>('#first')!;\n\nconst controller = createSandbox('report-widget', {\n  container: () => currentContainer,\n});\n\nawait controller.mount();\nawait controller.unmount();\n\ncurrentContainer = document.querySelector<HTMLElement>('#second')!;\nawait controller.mount();\n```\n\nCalling `createSandbox('name')` without a container creates the JS-only preset. Its timer, window-listener, and history plugins can be activated with `mount()` without providing an arbitrary DOM element, and an element passed to `mount()` is left untouched — container preparation belongs to the container-backed preset. `styleIsolation: true` requires a configured container.\n\n### Container preparation\n\n`createSandbox()` establishes its container contract automatically. Advanced hosts that compose the lower-level APIs can prepare a container directly:\n\n```ts\nimport { prepareSandboxContainer } from '@qiankunjs/sandbox';\n\nconst { styleIsolation, cleanup } = prepareSandboxContainer(container, 'report-widget');\n\nconsole.log(styleIsolation);\n// { appName: 'report-widget', scopeRoot: '[data-name=\"report-widget\"]' }\n\n// Later, release only the attributes and virtual head created by this call.\ncleanup();\n```\n\nThe helper sets `data-name`, ensures a `<qiankun-head>` mount point for the sandboxed `document.head`, and returns the matching style-isolation options. Cleanup is idempotent: it preserves an existing virtual head, restores a previous `data-name`, and does not overwrite a later host update to that attribute.\n\n## Default asset transformation\n\nThe default `nodeTransformer` is isolation-preserving. A classic `<script>` dynamically appended by sandboxed code is fetched, wrapped with the controller's classic-script transformer, and executed through a blob URL. Dynamic styles and stylesheet links go through the same pipeline, including style scoping when enabled.\n\nYou may provide a custom transformer. Returning each node unchanged is not a neutral optimization:\n\n```ts\nconst controller = createSandbox('trusted-widget', {\n  container,\n  nodeTransformer: (node) => node,\n});\n```\n\nAn identity transformer disables the default classic-script wrapping. A dynamically appended classic script can then execute against the host global. Use this override only when the host supplies equivalent isolation or fully trusts every script. The internal asset transpiler is intentionally not a public API; the supported extension point is `nodeTransformer`.\n\nFor a custom HTML loading pipeline, use `controller.nodeTransformer`. It is the fully configured transformer shared with dynamic DOM interception, including the current Compartment, fetch implementation, classic-script wrapper, and style-isolation options.\n\n## Lifecycle ownership\n\n- `mount(container?)` activates or rebuilds timer, listener, history, DOM, and user-plugin effects. The argument overrides the configured container for that mount.\n- `unmount()` releases active effects and records any state required for a later mount. It does not run an application's own unmount function or clear caller-owned DOM.\n- `dispose()` permanently releases plugin effects, generated module resources, global accessors, and container preparation. It is safe to call more than once.\n\nMount the controller before evaluating application code when its timers and listeners must participate in cleanup. Run the application's own teardown before `unmount()` or `dispose()`, and clear application DOM according to the host's ownership rules.\n\n## Isolation boundary\n\nThis package is a compatibility sandbox, not a security boundary for hostile code.\n\n- Writes to the sandbox global stay local to that sandbox.\n- Reads fall through to the host window unless a value is configured or shadowed.\n- The membrane is non-transitive: objects crossing the boundary retain their identity. `===`, `instanceof`, DOM nodes, events, and shared library instances continue to work.\n- Mutating a host object obtained through read-through also mutates that shared object. Only the global property write is isolated.\n- DOM containment redirects the sandboxed `document.head` and `document.body`; it does not create a separate browser realm or origin.\n\nUse an iframe, Worker, or another origin/callable boundary for untrusted code. See the [Compartment Alignment RFC](../../docs/rfcs/compartment-alignment.md) for the rationale behind the write-isolation and identity-sharing model.\n\n## Content Security Policy\n\nClassic and ESM execution do not require `'unsafe-eval'`. They do require the page's `script-src` policy to allow `blob:` URLs. The ESM engine also injects an inline `script[type=\"importmap\"]`, which must be permitted by the page's script policy. ESM and external classic assets are fetched first, so their origins must satisfy CORS and `connect-src` as well.\n\nStyle isolation can create blob stylesheet URLs; allow `blob:` in `style-src` when external styles are used. Dynamically created inline `<style>` elements remain subject to the page's normal nonce, hash, or inline-style policy.\n\n```http\nContent-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' blob:; style-src 'self' 'unsafe-inline' blob:; connect-src 'self' https://widgets.example\n```\n\nThis illustrative policy allows the runtime import map and a widget-created inline style. Tighten it to the assets and execution path the host actually uses. Do not add `'unsafe-eval'` for this package.\n\n## Further reading\n\n- [Standalone sandbox cookbook](../../docs/cookbook/standalone-sandbox.md)\n- [Extending sandbox isolation](../../docs/cookbook/sandbox-plugins.md)\n- [Standalone Sandbox RFC](../../docs/rfcs/standalone-sandbox.md)\n","packages/shared/AGENTS.md":"# @qiankunjs/shared\n\nBase utilities shared by `loader`/`sandbox`/`qiankun`: asset transpilers, fetch decorators, module resolution, error reporting, and the **ESM-sandbox engine**. No internal `@qiankunjs/*` dependencies — keep it at the bottom of the graph (it must never import `sandbox` or `loader`).\n\n## STRUCTURE\n\n```\nshared/\n├── assets-transpilers/   # transform script/link/style DOM nodes for the micro-frontend runtime\n│   ├── index.ts          # transpileAssets(node, baseURI, opts) — dispatch by tagName\n│   ├── script.ts         # classic scripts → blob-URL wrapping, sandbox scope\n│   ├── link.ts           # external stylesheets → blob-<link> (enables @scope); preload/URL rewriting; cache\n│   └── style.ts          # inline <style> → CSS @scope rewriting (runtime style isolation)\n├── esm-sandbox/          # per-instance native-ESM execution engine (see below)\n├── fetch-utils/          # higher-order fetch decorators\n│   ├── makeFetchCacheable.ts   # two runtime-wide LRUs (assets 50 / modules 512), clones responses, prunes failures\n│   ├── makeFetchRetryable.ts   # automatic retries\n│   └── makeFetchThrowable.ts   # non-2xx → throw\n├── module-resolver/      # shared-dependency canonicalization (semver via <script type=\"dependencymap\">)\n├── reporter/             # QiankunError + logger (warn/…)\n├── deferred-queue/       # async task sequencing (defer-script ordering)\n├── common.ts / utils.ts  # Deferred, defineProperty, hasOwnProperty, keys, resolveUrl, …\n└── typings.d.ts\n```\n\n## ESM-SANDBOX (`esm-sandbox/`, the largest subsystem here)\n\n`EsmSandboxEngine` is the replaceable mechanism behind the sandbox package's Compartment module facade; it must not leak into loader or qiankun interfaces. It runs a micro-app's `<script type=\"module\">` graph inside the sandbox membrane without a bundler. Pipeline: resolve through `resolveHook` → obtain and memoize a descriptor from `modules` or `importHook` / `loadHook` → fetch/lexer-rewrite source when needed → give modules synthetic specifiers → inject an **import map** → evaluate in dependency order → keep dunder-globals (`__qk_track`) live on later global writes.\n\n| File | Responsibility |\n| --- | --- |\n| `engine.ts` | `EsmSandboxEngine` — module-graph orchestration, redeclaration probing, in-order eval, dynamic `import()` |\n| `types.ts` | Public structural module contracts: hooks, descriptors, `ModuleSource`, Compartment facade |\n| `lexer.ts` | CSP-safe `es-module-lexer/js` wrapper; preparation Promise is already resolved |\n| `rewrite.ts` | `rewriteModule`, `buildSyntheticSpecifier`, `esmInternalPrefix`, `runtimeModuleSubpath` |\n| `import-bindings.ts` | import statement binding analysis |\n| `identifier-scan.ts` | `scanReferencedGlobals`, `isLiveBindableDunderName` — which globals a module touches |\n| `import-map-registry.ts` | `injectImportMapEntries`, `resetImportMapRegistry` — the dynamic import map |\n| `instance-registry.ts` | Per-engine instance/view lookup used by generated runtime modules |\n| `vite-client-stub.ts` | `isViteClientUrl`, `viteClientStubSource` — stubs Vite dev-client HMR |\n| `source-map.ts` | rewritten-module source-map fixups |\n\n> ESM sandbox depends on **dynamically injected import maps** → unsupported on Firefox (see `e2e/README.md`).\n\n## WHERE TO LOOK\n\n| Task | File | Notes |\n| --- | --- | --- |\n| Transpile any asset node | `assets-transpilers/index.ts` | `transpileAssets()` — the single dispatch entry |\n| Classic script sandboxing | `assets-transpilers/script.ts` | blob-URL wrap + sandbox scope |\n| Style isolation | `assets-transpilers/style.ts` + `link.ts` | CSS `@scope` rewrite; external CSS → blob-`<link>` |\n| Fetch enhancements | `fetch-utils/*` | decorator chain (compose with `make*` HOFs) |\n| Shared dependency reuse | `module-resolver/index.ts` | `moduleResolver(url, container, head)` semver match |\n| ESM module execution | `esm-sandbox/engine.ts` | `EsmSandboxEngine` (see section above) |\n| Module facade contracts | `esm-sandbox/types.ts` | The only ESM mechanism types cross-package callers should consume |\n| Promise/util helpers | `utils.ts` | `Deferred`, `keys`, `defineProperty`, `hasOwnProperty` |\n\n## KEY PATTERNS\n\n### Module hooks and descriptors\n\n- `resolveHook(specifier, referrer)` synchronously returns a canonical full specifier.\n- A `modules` descriptor wins before `importHook`; descriptors may provide precompiled `ModuleSource`, a namespace, or a redirect specifier.\n- `importHook(fullSpecifier)` is Promise-memoized, including failures. Probe-driven rebuilds reuse its result and do not call the hook again, so hooks must be idempotent.\n- A precompiled `ModuleSource` is already linked/re-written and skips the default fetch and rewrite pipeline.\n- Redirect cycles must fail deterministically; namespace descriptors keep their object identity.\n\nThe engine may be imported by the sandbox implementation itself, but do not add it to `LoaderOpts`, asset-transpiler options, or other cross-package interfaces. Those use `CompartmentModuleFacade`.\n\n### Higher-order fetch (decorator composition)\n\n```typescript\n// makeFetchCacheable(fetch, cacheScope: 'assets' | 'modules' = 'assets')\nconst resourceFetch = makeFetchRetryable(makeFetchThrowable(fetch));\nconst enhancedFetch = makeFetchCacheable(resourceFetch); // entries, classic scripts, styles\nconst moduleSourceFetch = makeFetchCacheable(resourceFetch, 'modules'); // passed as moduleHost.fetch\n```\n\nEach `cacheScope` maps to one LRU shared by the whole runtime copy: `assets` holds 50 responses and `modules` holds 512, so a large module graph cannot evict entries and styles. Both key requests by canonical URL and effective `credentials`, cache in-flight Promises, clone responses per reader, and drop failed responses. `loadApp` passes the module-scoped fetch as `compartmentOptions.moduleHost.fetch`, which the sandbox prefers over the top-level `fetch` for module sources.\n\n### Deferred promise\n\n```typescript\nconst d = new Deferred<void>();\nd.resolve();\nawait d.promise;\n```\n\n### DOM as metadata store\n\n- `data-src` — original source URL of a transpiled node.\n- `data-consumed` — marks an already-processed element.\n\n## EXPORTS\n\n`src/index.ts` re-exports everything: `./assets-transpilers`, `./utils`, `./common`, `./module-resolver`, `./reporter`, `./esm-sandbox`, the three `fetch-utils/make*`, and `./deferred-queue`. Notable named exports: `transpileAssets`, the Compartment-shaped module contracts, the internal `EsmSandboxEngine` mechanism, `moduleResolver`, `makeFetchCacheable`, `makeFetchRetryable`, `makeFetchThrowable`, `Deferred`, `QiankunError`, `warn`.\n","packages/shared/README.md":"# @qiankunjs/shared\n\nqiankun 内部共用的基础模块，为加载器、沙箱和运行时提供资源转换、请求处理、模块解析及 ESM 沙箱引擎等能力。它位于 qiankun 内部依赖关系的底层，不建议业务应用直接依赖；接入微应用请使用 `qiankun` 的公开 API。\n\n文档：[加载一个微应用实例](https://www.qiankunjs.com/zh-CN/concepts/architecture)。\n\n## English\n\nInternal foundation modules shared by qiankun's loader, sandbox, and runtime, including asset transformation, fetch utilities, module resolution, and the ESM sandbox engine. This package sits at the bottom of qiankun's internal dependency graph and is not recommended as a direct application dependency; use the public `qiankun` APIs to integrate micro-apps.\n\nDocumentation: [Loading a micro-app instance](https://www.qiankunjs.com/concepts/architecture).\n","packages/single-spa/README.md":"# @qiankunjs/single-spa\n\nqiankun's vendored fork of [single-spa](https://github.com/single-spa/single-spa), consumed by the `qiankun` package as its application/parcel lifecycle router. See the vendoring RFC in [umijs/qiankun#3168](https://github.com/umijs/qiankun/issues/3168) for the full rationale and plan.\n\n## Fork baseline\n\n- Upstream: `single-spa/single-spa`, `7.0` branch @ [`ce0f925a`](https://github.com/single-spa/single-spa/commit/ce0f925a40a81809c598de21b36ad79706222a73) (tag `v7.0.0-beta.13`, 2025-09-22).\n- Imported verbatim (`src/`, `spec/`, `node-spec/`, `LICENSE`), then naturalized to this monorepo's toolchain (Vite 8 build, `tsc` declarations, vitest). Upstream rollup/babel/jest configs were not carried over; the compile-time constants they injected (`__DEV__`, `__PROFILE__`, `process.env.BABEL_ENV`) are provided by `vite.library.config.ts` (build) and the root `vitest.config.ts` (tests).\n- The profile variant entry (`src/single-spa.profile.ts` + `src/devtools/profiler-api.ts`) is kept in the tree but not built or published yet; `__PROFILE__` is compiled to `false`.\n- Versioning is independent of upstream beta numbering and follows this monorepo's changesets flow.\n\n## Behavioral divergences from upstream\n\nMaintained deliberately; this list is the contract (keep it in sync when adding divergences):\n\n1. **The upstream `bootstrap` → `init` rename ([single-spa#1307](https://github.com/single-spa/single-spa/pull/1307), completed by [#1333](https://github.com/single-spa/single-spa/pull/1333)) is reverted wholesale — this fork speaks pure v6 vocabulary, with no `init` aliases.** qiankun's ecosystem was built on the bootstrap vocabulary and there was no reason to break it. Concretely:\n   - the `bootstrap` lifecycle is the only recognized name for applications and parcels (a v7-style `init` export is ignored, exactly as v6 ignored unknown fields);\n   - `parcel.bootstrapPromise`, `setBootstrapMaxTime` and the `timeouts.bootstrap` key are back; `initPromise`, `setInitMaxTime` and `timeouts.init` do not exist;\n   - the `NOT_BOOTSTRAPPED` / `BOOTSTRAPPING` statuses are restored, both as `AppOrParcelStatus` member names and as the raw `getStatus()` string values (v7's `NOT_INITIALIZED` / `INITIALIZING` strings do not exist in this fork);\n   - error message texts use the bootstrap vocabulary again;\n   - the vendored upstream specs were migrated back to the bootstrap vocabulary along with the sources.\n\n   If upstream ever ships a stable (non-beta) v7 that still carries the rename, re-evaluate aligning with it then — until that day this fork does not track it.\n\n## Invariant contracts (never break)\n\n- All `single-spa:*` window events (`single-spa:before-routing-event`, `single-spa:routing-event`, `single-spa:no-app-change`, `single-spa:first-mount`, …) — qiankun and the wider ecosystem listen to them.\n- The `window.__SINGLE_SPA_DEVTOOLS__` hook (single-spa-inspector support).\n- Public API and type signatures stay 1:1 with the upstream baseline during phase one, except for the bootstrap-naming revert above — qiankun-observable behavior stays 1:1 with what its ecosystem shipped against, which is the higher-priority contract.\n- Zero runtime dependencies; this package sits at the bottom of the workspace dependency graph and must not import `@qiankunjs/shared`, `@qiankunjs/sandbox` or `@qiankunjs/loader`.\n\n## Known semantics now owned by this fork\n\n- `urlRerouteOnly` (documented at length in qiankun's `docs/api/start.md`) — qiankun passes it through untouched; the implementation and its semantics live here now.\n- **Single-instance assumption**: single-spa is a stateful routing singleton. Loading both the ESM and CJS builds in one page (or mixing this fork with a separately installed npm `single-spa`) silently creates two independent routers — two history patches, two event streams, two `started` flags. Don't.\n\n## Upstream sync\n\nExpected cadence ≈ zero (upstream is dormant). If a fix is worth absorbing:\n\n```bash\ngit remote add single-spa-upstream https://github.com/single-spa/single-spa.git\ngit fetch single-spa-upstream 7.0\ngit log ce0f925a..single-spa-upstream/7.0 --oneline   # review the delta\n# apply selected commits manually onto packages/single-spa/src/ (path prefixes differ)\n```\n\nUpdate the baseline note above after absorbing anything; on conflict with the divergence list, the divergence list wins.\n\n## License\n\nMIT — upstream's [LICENSE](./LICENSE) is preserved.\n","packages/ui-bindings/react/README.md":"# qiankun ui binding for react\n\n## Usage\n\n```bash\nnpm i @qiankunjs/react\n```\n\n## MicroApp component\n\nLoad (or unload) child apps directly through the `<MicroApp/>` component, which provides loading and error catching-related capabilities:\n\n```tsx\nimport { MicroApp } from '@qiankunjs/react';\n\nexport default function Page() {\n  return <MicroApp name=\"app1\" entry=\"http://localhost:8000\" />;\n}\n```\n\nWhen the sub-app loading animation or error capture capability is enabled, the sub-app accepts an additional style class `wrapperClassName`, and the rendered result is as follows:\n\n```tsx\n<div style={{ position: 'relative' }} className={`${wrapperClassName} qiankun-micro-app-wrapper`}>\n  <MicroApp className={`${className} qiankun-micro-app-container`} />\n  <MicroAppLoader loading={loading} />\n  <ErrorBoundary error={e} />\n</div>\n```\n\nThe container is rendered first so its position never shifts as the loader and error slots come and go — qiankun keys its per-container caches on that position. Because the slots come after it, they paint above the micro app without needing a `z-index`.\n\n### Load animation\n\nWhen this capability is enabled, loading animations are automatically displayed when child apps are loading. When the sub-application is mounted and changes to the `MOUNTED` state, the loading status ends and the sub-application content is displayed.\n\nJust pass `autoSetLoading` as a parameter:\n\n```tsx\nimport { MicroApp } from '@qiankunjs/react';\n\nexport default function Page() {\n  return <MicroApp name=\"app1\" entry=\"http://localhost:8000\" autoSetLoading />;\n}\n```\n\n#### Custom loading animation\n\nIf you want to override the default loading animation style, you can set a custom loading component `loader` as the loading animation for the child app.\n\n```tsx\nimport CustomLoader from '@/components/CustomLoader';\nimport { MicroApp } from '@qiankunjs/react';\n\nexport default function Page() {\n  return (\n    <MicroApp name=\"app1\" entry=\"http://localhost:8000\" loader={(loading) => <CustomLoader loading={loading} />} />\n  );\n}\n```\n\nwhere `loading` is the `boolean` type parameter, `true` indicates that the loading state is still being loaded, and `false` indicates that the loading state has ended.\n\n### Error catching\n\nWhen this capability is enabled, an error message is automatically displayed when a child app loads unexpectedly. You can pass the `autoCaptureError` property to the sub-app to enable sub-app error capture capabilities:\n\n```tsx\nimport { MicroApp } from '@qiankunjs/react';\n\nexport default function Page() {\n  return <MicroApp name=\"app1\" entry=\"http://localhost:8000\" autoCaptureError />;\n}\n```\n\n#### Custom error capture\n\nIf you want to override the default error capture component style, you can set a custom component `errorBoundary` as the error capture component for the child app:\n\n```tsx\nimport CustomErrorBoundary from '@/components/CustomErrorBoundary';\nimport { MicroApp } from '@qiankunjs/react';\n\nexport default function Page() {\n  return (\n    <MicroApp\n      name=\"app1\"\n      entry=\"http://localhost:8000\"\n      errorBoundary={(error) => <CustomErrorBoundary error={error} />}\n    />\n  );\n}\n```\n\n### Component Props\n\n| Name | Required | Description | Type | Default |\n| --- | --- | --- | --- | --- |\n| `name` | yes | The name of the microapp | `string` |\n| `entry` | yes | The HTML address of the microapp | `string` |\n| `settings` | no | qiankun configuration for this microapp — sandbox, style isolation, fetch, … | `AppConfiguration` | `undefined` |\n| `lifeCycles` | no | qiankun lifecycle hooks for this microapp. They are captured on the first load into a given container, so avoid closing over component state in them | `LifeCycles` | `undefined` |\n| `autoSetLoading` | no | Automatically set the loading state of your microapp | `boolean` | `false` |\n| `loader` | no | Custom microapps load state components | `(loading) => React.ReactNode` | `undefined` |\n| `autoCaptureError` | no | Automatically set up error capture for microapps | `boolean` | `false` |\n| `errorBoundary` | no | Custom microapp error capture component | `(error: Error) => React.ReactNode` | `undefined` |\n| `className` | no | The style class for the microapp | `string` | `undefined` |\n| `wrapperClassName` | no | Wrap the microapp loading component, error capture component, and microapp's style classes, and are only valid when the load component or error capture component is enabled | `string` | `undefined` |\n\n## Get the child app load status\n\nThe loading status includes: \"NOT_LOADED\" | \"LOADING_SOURCE_CODE\" | \"NOT_BOOTSTRAPPED\" | \"BOOTSTRAPPING\" | \"NOT_MOUNTED\" | \"MOUNTING\" | \"MOUNTED\" | \"UPDATING\" | \"UNMOUNTING\" | \"UNLOADING\" |\n\n```tsx\nimport { useEffect, useRef } from 'react';\nimport { MicroApp, type MicroAppType } from '@qiankunjs/react';\n\nexport default function Page() {\n  const microAppRef = useRef<MicroAppType>(undefined);\n\n  useEffect(() => {\n    // Get the child app load status\n    console.log(microAppRef.current?.getStatus());\n  }, []);\n\n  return <MicroApp name=\"app1\" entry=\"http://localhost:8000\" ref={microAppRef} />;\n}\n```\n","packages/ui-bindings/shared/README.md":"# @qiankunjs/ui-shared\n\nqiankun 的 React 和 Vue 组件共用的内部模块，封装微应用的挂载、更新、加载状态和错误处理，并提供共用类型。它连接组件层与 qiankun 运行时，不建议单独使用；React 和 Vue 主应用请分别使用 `@qiankunjs/react` 和 `@qiankunjs/vue`。\n\n文档：[生态概览](https://www.qiankunjs.com/zh-CN/ecosystem/)。\n\n## English\n\nInternal modules shared by qiankun's React and Vue components, covering micro-app mounting, updates, loading state, error handling, and shared types. This package connects the component layer to the qiankun runtime and is not recommended for standalone use; React and Vue host applications should use `@qiankunjs/react` and `@qiankunjs/vue`, respectively.\n\nDocumentation: [Ecosystem overview](https://www.qiankunjs.com/ecosystem/).\n","packages/ui-bindings/vue/README.md":"# qiankun vue binding\n\n## Usage\n\n```bash\nnpm i @qiankunjs/vue\n```\n\n## MicroApp Component\n\nLoad (or unload) a sub-application directly through the `<MicroApp />` component, which provides capabilities related to loading and error capturing:\n\n```vue\n<script setup>\nimport { MicroApp } from '@qiankunjs/vue';\n</script>\n<template>\n  <micro-app name=\"app1\" entry=\"http://localhost:8000\" />\n</template>\n```\n\nWhen enabling the sub-application loading animation or error capturing capabilities, an additional style class `wrapperClassName` is accepted by the sub-application. The rendered result is as follows:\n\n```vue\n<div :class=\"wrapperClassName\">\n  <MicroAppLoader :loading=\"loading\" />\n  <ErrorBoundary :error=\"e\" />\n  <MicroApp :class=\"className\" />\n</div>\n```\n\n### Loading Animation\n\nAfter enabling this feature, a loading animation will automatically be displayed while the sub-application is loading. When the sub-application finishes mounting and becomes in the MOUNTED state, the loading state ends, and the sub-application content is displayed.\n\nSimply pass `autoSetLoading` as a parameter:\n\n```vue\n<script setup>\nimport { MicroApp } from '@qiankunjs/vue';\n</script>\n<template>\n  <micro-app name=\"app1\" entry=\"http://localhost:8000\" autoSetLoading />\n</template>\n```\n\n#### Custom Loading Animation\n\nIf you wish to override the default loading animation style, you can customize the loading component by using the loader slot as the sub-application's loading animation.\n\n```vue\n<script setup>\nimport CustomLoader from '@/components/CustomLoader.vue';\nimport { MicroApp } from '@qiankunjs/vue';\n</script>\n<template>\n  <micro-app name=\"app1\" entry=\"http://localhost:8000\">\n    <template #loader=\"{ loading }\">\n      <custom-loader :loading=\"loading\" />\n    </template>\n  </micro-app>\n</template>\n```\n\nHere, `loading` is a boolean type parameter; when true, it indicates that it is still in the loading state, and when false, it indicates that the loading state has ended.\n\n### Error Capturing\n\nAfter enabling this feature, when the sub-application encounters an exception while loading, an error message will automatically be displayed. You can pass the `autoCaptureError` property to the sub-application to enable error capturing capabilities:\n\n```vue\n<script setup>\nimport { MicroApp } from '@qiankunjs/vue';\n</script>\n<template>\n  <micro-app name=\"app1\" entry=\"http://localhost:8000\" autoCaptureError />\n</template>\n```\n\n#### Custom Error Capturing\n\nIf you wish to override the default error capturing component style, you can customize the error capturing component for the sub-application using the errorBoundary slot:\n\n```vue\n<script setup>\nimport CustomErrorBoundary from '@/components/CustomErrorBoundary.vue';\nimport { MicroApp } from '@qiankunjs/vue';\n</script>\n<template>\n  <micro-app name=\"app1\" entry=\"http://localhost:8000\">\n    <template #error-boundary=\"{ error }\">\n      <custom-error-boundary :error=\"error\" />\n    </template>\n  </micro-app>\n</template>\n```\n\n### Component Properties\n\n| Property | Required | Description | Type | Default Value |\n| --- | --- | --- | --- | --- |\n| `name` | Yes | The name of the micro-application | `string` |  |\n| `entry` | Yes | The HTML address of the micro-application | `string` |  |\n| `settings` | No | qiankun configuration for this micro-application — sandbox, style isolation, fetch, … | `AppConfiguration` | `{ sandbox: true }` |\n| `lifeCycles` | No | qiankun lifecycle hooks for this micro-application. They are captured on the first load into a given container, so avoid closing over component state in them | `LifeCycles` | `undefined` |\n| `autoSetLoading` | No | Automatically set the loading status of the micro-application | `boolean` | `false` |\n| `autoCaptureError` | No | Automatically set error capturing for the micro-application | `boolean` | `false` |\n| `className` | No | The style class for the micro-application | `string` | `undefined` |\n| `wrapperClassName` | No | The style class wrapping the micro-application's loading and error components | `string` | `undefined` |\n| `appProps` | No | Properties passed to the sub-application | `Record<string, any>` | `undefined` |\n\n### Component Slots\n\n| Slot                               | Slot props             | Description          |\n| ---------------------------------- | ---------------------- | -------------------- |\n| `loader`                           | `{ loading: boolean }` | Loading state slot   |\n| `errorBoundary` / `error-boundary` | `{ error: Error }`     | Error capturing slot |\n\nVue does not normalize slot names the way it normalizes props, so both spellings of the error slot are accepted. Unlike the React binding, this component's wrapper is not positioned — give it a positioned class through `wrapperClassName` if your slot renders an overlay.\n","skills/qiankun/SKILL.md":"---\nname: qiankun\ndescription: >-\n  Usage manual for building micro-frontend projects with qiankun 3.x. Use when the user wants to create a qiankun main (host) app or sub (micro) app, convert an existing Vite app into a qiankun micro app, or wire up micro-frontend loading with React/Vue.\n---\n\n# qiankun\n\nqiankun is a micro-frontend framework: a main (host) app loads sub (micro) apps from their HTML entries at runtime, each inside a JS sandbox, with opt-in CSS isolation. This skill is the agent-facing manual for **qiankun 3.x**.\n\n## Task routing\n\nRead only the reference file(s) the task at hand needs:\n\n| Task | Read |\n| --- | --- |\n| Create a micro (sub) app, or convert an existing Vite app | [references/create-micro-app.md](references/create-micro-app.md) |\n| Create a main (host) app | [references/create-main-app.md](references/create-main-app.md) |\n| Anything else — migration, style isolation, sandbox, debugging | https://qiankun.umijs.org and `examples/` in https://github.com/umijs/qiankun |\n\n## Before you start\n\nDetermine these, asking the user only when not inferable from context:\n\n1. **App type** — main (host/shell) or sub (micro app).\n2. **App name** — lowercase letters, numbers, hyphens. The name the host registers must exactly match the name the sub app uses in its classic-mode fallback.\n3. **Framework** — React or Vue; TypeScript or JavaScript. Templates in the references are TS; strip types for JS.\n4. **Dev port** — must be fixed and unique per app, because the host hard-references it in `entry`. Convention: main app `7099`, sub apps `7101`, `7102`, …\n\nPackage versions — until qiankun 3.0 stable ships, `rc` is the dist-tag for the core packages:\n\n| Package                               | Dist-tag | Goes in                                       |\n| ------------------------------------- | -------- | --------------------------------------------- |\n| `qiankun`                             | `rc`     | main app `dependencies`                       |\n| `@qiankunjs/react` / `@qiankunjs/vue` | `rc`     | main app `dependencies` (optional UI binding) |\n| `@qiankunjs/bundler-plugin`           | `rc`     | sub app `devDependencies`                     |\n\n## Key facts\n\nThese invariants shape every task below:\n\n- **Vite sub apps need no dedicated build mode.** qiankun 3 loads `<script type=\"module\">` natively through its ESM sandbox — regular `vite dev` / `vite build` output is qiankun-ready as-is.\n- The JS sandbox is **on by default**. CSS isolation is opt-in per app via `styleIsolation: true` (runtime CSS `@scope`).\n- A sub app must stay **runnable standalone**: when not loaded by qiankun (`window.__POWERED_BY_QIANKUN__` is undefined) it renders itself directly.\n- **Always unmount** what you mount. `loadMicroApp` returns a handle whose `.unmount()` must be called when the app leaves; the `MicroApp` component bindings do this automatically on component unmount.\n- Webpack sub apps use `@qiankunjs/bundler-plugin/webpack` instead of the Vite plugin; see the docs.\n\n## After any creation task\n\nVerify the result following the checklist at the end of the reference you used. If a browser tool is available, perform the checks yourself instead of asking the user to.\n","skills/qiankun/references/create-main-app.md":"# Create a main (host) app\n\nPrerequisites: the shared facts in [SKILL.md](../SKILL.md) — app name, framework, port, package versions.\n\n1. Scaffold the shell (any framework; below assumes React + TS on port `7099`) and install qiankun:\n\n   ```bash\n   pnpm create vite <main-app-name> --template react-ts\n   pnpm add qiankun@rc @qiankunjs/react@rc   # vue shell: @qiankunjs/vue@rc\n   ```\n\n   Pin the port in `vite.config.ts` (`server: { port: 7099, strictPort: true }`).\n\n2. Pick ONE loading style:\n\n   **`MicroApp` component (recommended for React/Vue shells)** — mounts on render, unmounts on component unmount, with built-in loading/error slots. Extra props are forwarded to the sub app (delivered via its `update` lifecycle on change):\n\n   ```tsx\n   import { MicroApp } from '@qiankunjs/react'; // same component name in @qiankunjs/vue\n\n   export default function SubAppPage() {\n     return <MicroApp name=\"<app-name>\" entry=\"//localhost:7101\" autoSetLoading />;\n   }\n   ```\n\n   **Router-driven registration (framework-agnostic)** — qiankun mounts/unmounts apps as the URL matches `activeRule`. Put this in the shell entry, once:\n\n   ```ts\n   import { registerMicroApps, start } from 'qiankun';\n\n   registerMicroApps([\n     {\n       name: '<app-name>',\n       entry: '//localhost:7101',\n       container: '#micro-app-container', // an element the shell always renders\n       activeRule: '/<app-name>',\n     },\n   ]);\n\n   start();\n   ```\n\n   For full manual control there is also `loadMicroApp({ name, entry, container }, configuration)` — it returns a handle; you own calling `.unmount()` when the app leaves.\n\n3. Per-app configuration (third argument of `loadMicroApp`, `configuration` prop of the `MicroApp` component, or per-app fields in `registerMicroApps`): `sandbox` defaults to on; add `styleIsolation: true` to scope the sub app's CSS with `@scope`.\n\n## Verify\n\n1. Run both dev servers, open the main app at `http://localhost:7099`, navigate to the sub app route — it must mount inside the shell with no console errors.\n2. Leave the sub app route — its DOM must be removed (unmount actually ran).\n3. The sub app must still render standalone at `http://localhost:7101` — see [create-micro-app.md](create-micro-app.md).\n","skills/qiankun/references/create-micro-app.md":"# Create a micro (sub) app, or convert an existing Vite app\n\nPrerequisites: the shared facts in [SKILL.md](../SKILL.md) — app name, framework, port, package versions.\n\nFor a **new** app, start at step 1. To **convert an existing Vite app**, skip to step 2 — the conversion is exactly steps 2–4 and touches nothing else (no changes to `index.html`, `App` components, or build scripts).\n\n1. Scaffold with the official Vite scaffolder (templates: `react-ts`, `react`, `vue-ts`, `vue`):\n\n   ```bash\n   pnpm create vite <app-name> --template react-ts\n   ```\n\n2. Add the bundler plugin:\n\n   ```bash\n   pnpm add -D @qiankunjs/bundler-plugin@rc\n   ```\n\n3. Edit `vite.config.ts` — register the plugin and pin the port:\n\n   ```ts\n   import { defineConfig } from 'vite';\n   import react from '@vitejs/plugin-react'; // vue: @vitejs/plugin-vue\n   import { qiankun } from '@qiankunjs/bundler-plugin/vite';\n\n   export default defineConfig({\n     plugins: [react(), qiankun()],\n     server: {\n       port: 7101,\n       strictPort: true,\n     },\n   });\n   ```\n\n4. Rewrite the entry to export qiankun lifecycles, using the template below for the app's framework. Replace `<app-name>` with the actual registered name in both templates.\n\n## React entry template (`src/main.tsx`)\n\n```tsx\nimport React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport App from './App';\nimport './index.css';\n\ndeclare global {\n  interface Window {\n    __POWERED_BY_QIANKUN__?: boolean;\n    [key: string]: unknown;\n  }\n}\n\nlet root: ReactDOM.Root | undefined;\n\nfunction render(props: { container?: Element } = {}) {\n  // when loaded by qiankun, resolve #root inside the host-provided container, not the top document\n  const container = props.container?.querySelector('#root') ?? document.getElementById('root');\n  if (!container) return;\n\n  root = ReactDOM.createRoot(container);\n  root.render(\n    <React.StrictMode>\n      <App />\n    </React.StrictMode>,\n  );\n}\n\nexport async function bootstrap() {}\n\nexport async function mount(props: { container?: Element }) {\n  render(props);\n}\n\n// the host re-delivers its props here whenever they change — react to them without remounting\nexport async function update(_props: Record<string, unknown>) {}\n\nexport async function unmount(_props: { container?: Element }) {\n  root?.unmount();\n  root = undefined;\n}\n\nif (window.__POWERED_BY_QIANKUN__) {\n  // classic-mode fallback: expose the lifecycles on window under the REGISTERED app name\n  window['<app-name>'] = { bootstrap, mount, update, unmount };\n} else {\n  render();\n}\n```\n\n## Vue entry template (`src/main.ts`)\n\n```ts\nimport { createApp, reactive } from 'vue';\nimport App from './App.vue';\nimport './style.css';\n\ndeclare global {\n  interface Window {\n    __POWERED_BY_QIANKUN__?: boolean;\n    [key: string]: unknown;\n  }\n}\n\nlet app: ReturnType<typeof createApp> | undefined;\n\n// props the host hands over — seeded on mount, kept current by `update`\nconst hostProps = reactive<Record<string, unknown>>({});\n\nfunction render(props: { container?: Element } = {}) {\n  const container = props.container?.querySelector('#app') ?? document.getElementById('app');\n  if (!container) return;\n\n  app = createApp(App, { hostProps });\n  app.mount(container);\n}\n\nexport async function bootstrap() {}\n\nexport async function mount(props: { container?: Element }) {\n  Object.assign(hostProps, props);\n  render(props);\n}\n\nexport async function update(props: Record<string, unknown>) {\n  Object.assign(hostProps, props);\n}\n\nexport async function unmount(_props: { container?: Element }) {\n  app?.unmount();\n  app = undefined;\n}\n\nif (window.__POWERED_BY_QIANKUN__) {\n  // classic-mode fallback: expose the lifecycles on window under the REGISTERED app name\n  window['<app-name>'] = { bootstrap, mount, update, unmount };\n} else {\n  render();\n}\n```\n\n## Verify\n\n1. `pnpm dev` in the sub app, open `http://localhost:7101` — it must render on its own (the non-qiankun branch of the entry).\n2. If a main app exists, run it too and confirm the sub app mounts inside it with no console errors — see the verification checklist in [create-main-app.md](create-main-app.md).\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/umijs/qiankun/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# QIANKUN PROJECT KNOWLEDGE BASE\n\n**Updated:** 2026-07-06 · **Commit:** dcc42ae4 · **Branch:** next (qiankun 3.0, active dev)\n\nQiankun is a micro-frontend framework built on [single-spa](https://github.com/single-spa/single-spa). v3 rewrites the runtime around **streaming HTML-entry loading**, a **Proxy-membrane JS sandbox**, and native **ESM-sandbox** execution. It is a pnpm monorepo built with Vite 8; `tsc` emits package declarations.\n\n> Requires Node `^22.15 || >=24`, `pnpm@11.13.1` (see `packageManager`). Never use npm/yarn at the root.\n\n## STRUCTURE\n\n```\nqiankun/\n├── packages/\n│   ├── qiankun/         # Facade: public APIs (register/loadMicroApp, start, prefetch) + loadApp orchestration\n│   ├── sandbox/         # JS isolation: Proxy membrane + Compartment  → packages/sandbox/AGENTS.md\n│   ├── loader/          # Streaming HTML-entry loader (writable-dom)  → packages/loader/AGENTS.md\n│   ├── shared/          # Transpilers, fetch-utils, module-resolver, ESM-sandbox engine → packages/shared/AGENTS.md\n│   ├── single-spa/      # Vendored single-spa fork (@qiankunjs/single-spa, upstream 7.0 @ ce0f925a) → packages/single-spa/README.md\n│   ├── ui-bindings/     # <MicroApp> components: react/ vue/ shared/ (dogfooded by examples/{main,vue-host})\n│   └── bundler-plugin/  # Webpack(4/5) + Vite plugins: mark entry script, fix output library\n├── skills/              # Agent skills (installed by users via `npx skills add umijs/qiankun`) — content, not code: no build/publish\n├── e2e/                 # Playwright, runs against BUILT dist → e2e/README.md\n├── examples/            # Runnable demos: two hosts (main = @qiankunjs/react, vue-host = @qiankunjs/vue) + react/vue/purehtml/webpack\n└── docs/                # VitePress site (docs/rfcs holds design RFCs)\n```\n\n## PROGRESSIVE DISCLOSURE — read the scoped doc before editing a package\n\n| Working in… | Read first |\n| --- | --- |\n| `packages/sandbox/**` | [`packages/sandbox/AGENTS.md`](packages/sandbox/AGENTS.md) — membrane, compartment, patchers, ESM globals |\n| `packages/loader/**` | [`packages/loader/AGENTS.md`](packages/loader/AGENTS.md) — streaming pipeline, head virtualization |\n| `packages/shared/**` | [`packages/shared/AGENTS.md`](packages/shared/AGENTS.md) — transpilers, fetch decorators, **esm-sandbox** |\n| `e2e/**` | [`e2e/README.md`](e2e/README.md) — Playwright layout, fixtures, anti-flake rules |\n\n## ARCHITECTURE (big picture)\n\n`loadApp` (`packages/qiankun/src/core/loadApp.ts`) is the orchestrator. Per micro-app it wires:\n\n1. **fetch** — decorated `window.fetch`: `makeFetchCacheable(makeFetchRetryable(makeFetchThrowable(fetch)))`.\n2. **sandbox** — `createSandbox()` builds a Proxy-membrane `window`/`document` view; patchers (dynamicAppend, timers, listeners, history) each return a `free()` cleanup called on unmount. Without a container it uses the JS-only preset; a container enables DOM containment.\n3. **loader** — `loadEntry(entry, container, opts)` streams the HTML entry through `writable-dom`, virtualizing `<head>` → `<qiankun-head>` and running each node through a `nodeTransformer`.\n4. **transpilers** (`shared/assets-transpilers`) rewrite each script/link/style node before it hits live DOM.\n\nMicro apps sharing one container element take FIFO turns for their DOM writes through the **container occupancy gate** (`core/containerOccupancy.ts`, two critical sections: load-phase streaming and mount→unmount; see `docs/rfcs/container-occupancy-gate.md`).\n\nTwo execution paths, chosen per script type:\n\n- **Classic** (`<script entry>`, UMD/global): source is wrapped and run via a **blob URL** scoped to the sandbox membrane. The app's export = `sandbox.latestSetProp` (the last global the entry script set).\n- **ESM** (`<script type=\"module\">`): handled by `EsmSandboxEngine` (`shared/esm-sandbox`). Modules are fetched, lexer-rewritten to route globals through the membrane, given synthetic specifiers via a dynamically injected **import map**, and evaluated in order. The engine also handles dynamic `import()`.\n\n**Style isolation** (`shared/assets-transpilers/style.ts` + `link.ts`) uses CSS `@scope` at runtime; external stylesheets become blob-`<link>`s so `@scope` can wrap them. Opt-in via `styleIsolation`.\n\nInternal dependency graph (never invert it):\n\n```\nqiankun → loader → sandbox → shared\n                   sandbox → shared\nqiankun → single-spa   (vendored fork; zero deps, bottom of the graph — must not import shared/sandbox/loader)\nui-bindings/{react,vue} → ui-bindings/shared → qiankun\n```\n\n## COMMANDS\n\n```bash\npnpm install                 # install all workspace deps\n\n# build (Vite 8 → package JavaScript; tsc → declarations)\npnpm run build               # build everything (packages + examples)\npnpm run build:packages      # build only packages/** (prereq for e2e & examples)\n\n# unit tests — vitest + happy-dom, aliased to src (NO build needed, see vitest.config.ts)\npnpm run test                            # all packages\npnpm --filter @qiankunjs/shared run test # one package\npnpm --filter @qiankunjs/sandbox exec vitest run path/to.test.ts   # single file\npnpm --filter @qiankunjs/shared  exec vitest run -t \"test name\"    # single test by name\n\n# e2e — Playwright against BUILT dist (see e2e/README.md)\npnpm run test:e2e            # build all + run chromium suite\n\n# lint / format / full CI gate\npnpm run eslint              # eslint packages/\npnpm run prettier:check      # prettier -c .\npnpm run ci                  # build + eslint + prettier:check (what CI runs)\n\n# dev\npnpm run start:example       # build packages + run all example apps in parallel\npnpm run docs:dev            # VitePress docs\n```\n\n## CONVENTIONS (enforced by ESLint flat config — `pnpm run eslint` will reject violations)\n\nTypeScript is strict + type-checked (`typescript-eslint`'s type-checked flat config):\n\n- **No `any`** — `no-explicit-any` auto-fixes to `unknown`. No `as any`, `@ts-ignore`, `@ts-expect-error`.\n- **Inline type imports** — `import { type Foo, bar }`, not `import type { Foo }` on its own line (`consistent-type-imports`/`consistent-type-exports` with `fixStyle: inline-type-imports`).\n- `no-unnecessary-condition` is an error — don't guard values the types prove are always truthy.\n- Unused vars/args must be prefixed `_` (`argsIgnorePattern: ^_`).\n- `array-simple`: `T[]` for simple, `Array<T>` for complex element types.\n- JS-wide: `no-else-return` (no `else` after `return`), `object-shorthand`.\n- Path alias `@qiankunjs/*` → `packages/*/src` (tsconfig + vitest); imports resolve to **source**, not dist.\n\nBuild/release:\n\n- Vite 8 builds browser package JavaScript as ESM+CJS, while `tsc` emits declarations. Package entry fields and subpath exports must point at the corresponding `dist` outputs.\n- TypeScript 7 is installed as `@typescript/native` and owns the `tsc` binary. The `typescript` dependency intentionally aliases `@typescript/typescript6` because TypeScript 7.0 has no programmatic API yet and tools such as `typescript-eslint` and `vue-tsc` still require that compatibility API.\n- Versioning via **changesets**, but changesets are **auto-derived from Conventional Commits** in CI (`scripts/generate-changesets.mjs`) — do **not** hand-write `.changeset/*.md`. Just land a well-formed conventional commit (`feat`/`fix`/`feat!`…); the release job maps changed files → packages and infers the bump. Each sub-package keeps its own `CHANGELOG.md` (changeset default, visible on npm); on publish `scripts/generate-release-notes.mjs` aggregates them into **one GitHub Release**, which can be polished after the fact via the `/release-changelog` skill (`gh release edit`). Full flow: `.changeset/README.md`.\n- Conventional commits enforced by commitlint (`feat:`, `fix:`, `feat(esm-sandbox):`, …).\n\n## DOCS & README CONVENTIONS\n\n- **Chinese-first.** The primary audience is Chinese. `docs/zh-CN/` is the source of truth: write it to a native-author standard — no translationese, no AI-flavored filler (「让我们」「值得注意的是」「强大的/无缝的」), no calqued English syntax. The English pages align to the Chinese semantics, not the other way around; pure zh wording fixes don't require touching en. Same contract for `README.zh-CN.md` ↔ `README.md`: keep both editions in sync whenever either changes.\n- **Mainland reachability is a hard constraint.** Google Fonts only via the `.cn` mirrors (`fonts.googleapis.cn`/`fonts.gstatic.cn`); never reference resources on domains unreachable from mainland China.\n- **Domains**: canonical docs domain is `https://www.qiankunjs.com` (zh under `/zh-CN/`); `qiankun.umijs.org` stays alive and 307s to it; v2 docs live at `v2.qiankun.umijs.org`; live examples at `examples.qiankunjs.com`. Old-site URLs are kept working via `docs/public/_redirects` — extend it when moving pages, never break inbound links.\n- **Consistency rules**: terminology never drifts (主应用/微应用/沙箱/隔离膜; qiankun always lowercase); link text in reference lists (\"相关内容\"/\"继续阅读\"/\"延伸阅读\") equals the target page's H1; API pages use 「函数签名」/「默认值为 X」; `guide/browser-support` is the single source of truth for browser requirements — link it, don't restate version numbers elsewhere.\n- Until 3.0 reaches npm `latest`, install commands must say `qiankun@rc`, and the site shows a version banner (layout-top slot in `docs/.vitepress/theme/index.js`; remove it together with `--vp-layout-top-height` once stable ships).\n- **README GitHub alerts**: put the `[!WARNING]`/`[!NOTE]` marker on its own line followed by a blank `>` line. Prettier's `proseWrap: never` re-joins plain soft breaks, which silently breaks alert rendering.\n- VitePress compiles every `.md` as a Vue SFC: bare `<Tag>` in prose/link text or `{{` in inline code breaks the build (fenced code blocks are safe).\n\n## ANTI-PATTERNS (this project)\n\n- **NEVER** put more than one `entry` script in an HTML entry — the loader throws `QiankunError`.\n- **ALWAYS** unmount micro-apps; `loadMicroApp`/patchers return handles/`free()` — leaks break remount & multi-instance.\n- In sandbox code, **never touch the real `window`/`document.head`** — go through the proxied view.\n- The vendored `loader/src/writable-dom/` fork accepts **generic designs only** — never qiankun-coupled semantics; caller bookkeeping goes through its `assetTransformer` callback (see `packages/loader/AGENTS.md`).\n- Don't invert the package dependency graph above (e.g. `shared` must not import `sandbox`).\n- e2e: never `waitForTimeout`; use web-first assertions; all ports come from `e2e/ports.ts`.\n- Performance work is judged in this order: **proportional impact first** (does the win scale with asset size / network / app scale — e.g. eliminating a duplicate download), **readability second** (never trade it away for small wins), **constant absolute savings last** (fixed single-digit-ms pipeline costs are noise in real apps and rarely worth landing). Beware: the ~50ms benchmark fixture makes fixed milliseconds masquerade as percentages — convert to absolute ms against realistic load times before deciding. And fix root causes at the source, not with per-call-site markers/exemptions downstream.\n\n## NOTES\n\n- Firefox doesn't support dynamically injected import maps → ESM-sandbox e2e tests are annotated `test.fail(firefox, …)` (expected failure, not skip). See `e2e/README.md`.\n- Design decisions live in `docs/rfcs/` (e.g. the ESM-sandbox RFC).\n- v3 roadmap: github.com/umijs/qiankun/discussions/1378.\n","isInternal":false,"tokens":3027,"sizeBytes":11657},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/umijs/qiankun/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\nAll project guidance lives in `AGENTS.md` (imported below). It links out to package-scoped `packages/*/AGENTS.md` files for deep-dives — read those on demand when editing a given package.\n\n@AGENTS.md\n","isInternal":false,"tokens":77,"sizeBytes":319},{"name":"SKILL.md","path":".claude/skills/release-changelog/SKILL.md","rawUrl":"https://raw.githubusercontent.com/umijs/qiankun/HEAD/.claude/skills/release-changelog/SKILL.md","title":"release-changelog","category":"anthropic-skill","format":"markdown","content":"---\nname: release-changelog\ndescription: Polish a published qiankun GitHub Release's aggregated notes into a bilingual (English then Chinese) write-up with an agent. Use after a release goes out — turning the mechanically-aggregated, per-package release notes into a clean, human-readable, English + Chinese write-up (highlights, de-duped cross-package lines, clearer wording) via `gh release edit`. Triggers include \"polish the release notes\", \"clean up the GitHub release\", \"improve the changelog for the latest release\".\n---\n\n# Release changelog\n\nQiankun releases are commit-driven: CI auto-derives changesets from Conventional Commits, changesets writes each sub-package's own `CHANGELOG.md` (so npm shows a per-package changelog), and on publish `scripts/generate-release-notes.mjs` aggregates those into **one GitHub Release** per version.\n\nThat aggregated Release is **mechanical** (raw per-package changelog lines). This skill polishes it into a human-readable, **bilingual** write-up — an English version on top, a Chinese version below — after the fact, directly on the GitHub Release, without touching any committed changelog. Read `.changeset/README.md` for the surrounding flow.\n\n## When to run\n\nAfter a release has been published (the \"Version Packages\" PR was merged and CI created the GitHub Release). Typically: \"polish the latest release notes\".\n\n## Steps\n\n1. **Find the release to polish** (default: the most recent one):\n\n   ```bash\n   gh release list --limit 5\n   gh release view <tag> --json tagName,name,body\n   ```\n\n   Confirm the target tag with the user if it's not obviously the latest.\n\n2. **Read the mechanical notes** from the release `body`. This is the raw material — the aggregated per-package changelog. Do not invent anything not present in it (cross-check against `git log <prev-tag>..<tag>` only to understand, not to add unlisted changes).\n\n3. **Polish** the notes, keeping the per-package structure so consumers still see each sub-package and its version. Polishing means:\n   - Lead with a short **Highlights** list (2–5 bullets) when the release has notable user-facing changes; skip it for small patch releases.\n   - Rewrite terse lines into clear, user-facing wording; keep the `(#PR)` reference at the end of each line.\n   - De-duplicate: a change that touched many packages is often better stated once under the facade `qiankun` section than repeated under every package.\n   - Drop noise (pure \"Updated dependencies\" internal bumps).\n\n4. **Produce a bilingual document** — the full **English** version first, then the full **简体中文** version, separated by a `---` rule. Keep the identical per-package structure and `(#PR)` references in both halves; translate the prose (highlights, descriptions), never the package names, versions, or PR numbers. Template:\n\n   ```markdown\n   <!-- English -->\n\n   ## Highlights\n\n   - ...\n\n   ## qiankun `<version>`\n\n   - ...\n\n   ---\n\n   <!-- 简体中文 -->\n\n   ## 更新亮点\n\n   - ...\n\n   ## qiankun `<version>`\n\n   - ...\n   ```\n\n5. **Show the user the proposed notes** and get approval.\n\n6. **Update the release** (does not re-tag or re-publish npm):\n\n   ```bash\n   gh release edit <tag> --notes-file <polished.md>\n   ```\n\n## Do not\n\n- Do not run `gh release create`, re-tag, or re-publish to npm — only edit the notes of an existing release.\n- Do not edit any `CHANGELOG.md` or `.changeset/*` — the per-package changelogs are the committed record; this skill only refines the GitHub Release prose.\n- Do not add changes that aren't in the mechanical notes / commit history.\n","frontmatter":{"name":"release-changelog","description":"Polish a published qiankun GitHub Release's aggregated notes into a bilingual (English then Chinese) write-up with an agent. Use after a release goes out — turning the mechanically-aggregated, per-package release notes into a clean, human-readable, English + Chinese write-up (highlights, de-duped cross-package lines, clearer wording) via `gh release edit`. Triggers include \"polish the release notes\", \"clean up the GitHub release\", \"improve the changelog for the latest release\"."},"isInternal":false,"tokens":836,"sizeBytes":3579},{"name":"AGENTS.md","path":"packages/loader/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/umijs/qiankun/HEAD/packages/loader/AGENTS.md","title":"Loader Skill","category":"anthropic-skill","format":"markdown","content":"# @qiankunjs/loader\n\nStreaming HTML-entry loader built on a forked `writable-dom`. Depends on `@qiankunjs/sandbox` + `@qiankunjs/shared`.\n\n## STRUCTURE\n\n```\nloader/\n├── index.ts              # loadEntry() — main API\n├── TagTransformStream.ts # <head> → <qiankun-head> string-level replacement\n├── writable-dom/         # forked streaming DOM engine (incremental parse + blocking scripts)\n│   └── index.ts\n└── parser.ts             # static HTML parsing for prefetch\n```\n\n## WHERE TO LOOK\n\n| Task | File | Notes |\n| --- | --- | --- |\n| Load micro-app | `index.ts` | `loadEntry(entry, container, opts)`; `opts.compartment` is the single sandbox/module facade |\n| Head virtualization | `TagTransformStream.ts` | rewrites `<head>` tags at the string level before DOM insertion |\n| Streaming DOM | `writable-dom/index.ts` | incremental parsing, blocks on sync scripts/styles, preloads the rest |\n\n## LOADING PIPELINE\n\n```\n1. fetch(entry)                              (decorated fetch: cacheable/retryable/throwable)\n2. TextDecoderStream                         bytes → string\n3. TagTransformStream                        <head> → <qiankun-head>\n4. WritableDOMStream                         stream → live DOM\n   ├─ nodeTransformer(node) per node         → shared/assets-transpilers rewrites script/link/style\n   ├─ classic <script entry> → synchronous Compartment host-adapter rewrite, then blob-URL execution\n   ├─ <script type=\"module\"> → registered through the compartment module facade\n   └─ blocks on sync scripts; preloads other assets while blocked\n5. resolve app export                        classic: compartment.latestSetProp · esm: compartment.importDocumentModules()\n```\n\n## KEY PATTERNS\n\n### Script classification (`index.ts`)\n\n```typescript\nisExternalScript; // has src or data-src\nisEntryScript; // external + [entry]  → resolves the load promise (app's main export point)\nisDeferScript; // external + [defer]  → ordered via shared prepareDeferredQueue\n```\n\n- Exactly **one** `entry` script is allowed per HTML entry; a second one throws `QiankunError`.\n- Loader and asset-transpiler interfaces must not mention `EsmSandboxEngine` or the concrete `Sandbox` class; consume structural `CompartmentLoaderFacade`.\n- `transformClassicScript()` is a stable sandbox-owned host adapter because detached node transformation is synchronous. `evaluateScript()` schedules a separate script asynchronously and cannot replace it in the streaming pipeline.\n\n### Detached parsing\n\nNodes are parsed/transformed in a detached document first, then moved to live DOM — this prevents premature script execution before the transpiler has rewritten the node.\n\n### writable-dom fork discipline\n\n`writable-dom/` is vendored from marko-js/writable-dom and periodically re-synced; every deviation carries a `[qiankun]` comment so syncs can re-apply them mechanically. Changes there demand deliberate thought and must stay **generic**: general-purpose hooks or upstream bug fixes only — never qiankun-specific semantics coupled to other packages (no `@qiankunjs/*` imports, no sandbox marks, no downstream contract knowledge). The fork's one integration seam is the `assetTransformer` callback (every element passes through it right before insertion); caller bookkeeping belongs in the callback `loadEntry` provides (`index.ts`), on the caller's side of that seam. The loader itself carries no sandbox semantics either: a sandbox-provided `nodeTransformer` stamps its own output (e.g. the native-passthrough mark its patcher consumes) — the loader just routes every element through whatever transformer it was given.\n\n## ANTI-PATTERNS\n\n- **NEVER** include more than one `entry` script per HTML entry (throws `QiankunError`).\n- **NEVER** add qiankun-coupled logic inside `writable-dom/` — generic designs only; hang caller bookkeeping on the `assetTransformer` callback in `loadEntry` (see the fork discipline above).\n- **FIXME** (in code): non-standard HTML chunks that lack a `<head>` tag.\n\n## EXPORTS (`src/index.ts`)\n\n```typescript\nexport { loadEntry, type LoaderOpts } from './index';\n// LoaderOpts = { fetch, compartment?, nodeTransformer?, streamTransformer?, onDOMStreamSettled? } & BaseTranspilerOpts\n// onDOMStreamSettled: notified exactly once when the DOM-write phase is over (stream piped, errored,\n// or never started) — distinct from the returned promise, which can settle at the entry script's\n// onload while tail nodes are still streaming; qiankun's container gate keys its release on it\n```\n","isInternal":false,"tokens":1042,"sizeBytes":4569},{"name":"AGENTS.md","path":"packages/sandbox/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/umijs/qiankun/HEAD/packages/sandbox/AGENTS.md","title":"Sandbox Skill","category":"anthropic-skill","format":"markdown","content":"# @qiankunjs/sandbox\n\nJS isolation engine: a Compartment-shaped facade owns the Proxy **Membrane**, classic-script evaluator, and module hooks. DOM behavior lives in independently registered **IsolationPlugin** instances. Depends only on `@qiankunjs/shared`.\n\n## STRUCTURE\n\n```\nsandbox/\n├── core/\n│   ├── sandbox/          # createSandbox() + StandardSandbox preset + container protocol\n│   ├── membrane/         # Proxy wrapper for global (window/document) isolation\n│   ├── compartment/      # globals + module hooks + blob classic evaluation facade\n│   ├── globals.ts        # global property definitions\n│   └── esm-globals.ts    # esmDestructurableGlobals — globals the ESM engine may destructure/rebind\n├── patchers/             # public IsolationPlugin protocol + built-in plugin presets\n│   ├── dynamicAppend/    # appendChild/insertBefore interception → redirect to app container\n│   ├── windowListener.ts # event listener tracking\n│   ├── interval.ts       # timer tracking\n│   └── historyListener.ts\n└── consts.ts             # qiankunHeadTagName / qiankunBodyTagName, nativeGlobal, nativeDocument\n```\n\n## WHERE TO LOOK\n\n| Task | File | Notes |\n| --- | --- | --- |\n| Create sandbox | `core/sandbox/index.ts` | Builds a `StandardSandbox`, installs the JS-only or DOM preset, and returns mount/unmount/dispose |\n| Prepare container | `core/sandbox/container.ts` | Owns `<qiankun-head>`, `data-name`, style scope, and cleanup contracts |\n| Proxy logic | `core/membrane/index.ts` | Write → local target; Read → local → configured globals → host window |\n| Compartment facade | `core/compartment/index.ts` | Owns the membrane, module facade, and CSP-safe blob classic evaluation |\n| Plugin protocol | `patchers/types.ts` | Public `IsolationPlugin`, context, `Free`, and `Rebuild` contracts |\n| Built-in plugins | `patchers/index.ts` | Default plugins; `dynamicAppend` joins only with a container; user plugins append after these |\n| DOM interception | `patchers/dynamicAppend/forStandardSandbox.ts` | Redirects dynamic script/style/link to app container |\n| Side-effect cleanup | `patchers/*.ts` | Each patcher returns a `free()` called on unmount |\n| ESM globals contract | `core/esm-globals.ts` | Consumed by `shared/esm-sandbox` engine, passed as `globalsBaseSet` |\n\n## KEY PATTERNS\n\n### Membrane (Proxy)\n\n- **Writes** are trapped and stored on a local `target` object (the sandbox's own globals).\n- **Reads** check local target → configured globals → fall back to the real host window.\n- **Native rebinding**: `fetch`, `console`, etc. are rebound to the real receiver to avoid \"Illegal invocation\".\n- `latestSetProp` records the last global the entry script assigned — that's how the loader recovers a classic app's exported lifecycles when no explicit export exists.\n- **Non-transitive by design**: unlike full membranes (es-membrane, Salesforce near-membrane), only the global (and document view) entry point is proxied — objects crossing the boundary keep their raw identity (`===`/`instanceof` hold across it). This is qiankun's deliberate isolation stance (isolate writes, share identity, for DOM compatibility and performance), not a gap to fill. Do NOT wrap crossing objects with proxies. The Membrane is a private mechanism of `Compartment` — the spec has no membrane concept, which is exactly why it must stay behind the facade.\n- `incubatorContext` (the host window the view reads through to) is a qiankun host extension: the Compartment spec and ses isolate by absence and have no read-through concept. The name borrows the ShadowRealm proposal's official \"incubator realm\" term for the creating context.\n\n### Compartment surface and deliberate differences\n\n`new Compartment({ globals, modules, resolveHook, importHook, loadHook, transforms })` owns its `globalThis`. StandardSandbox adds qiankun self-references (`window`, `self`, `globalThis`, `top`, `parent`) in a second phase with `defineUnshadowableGlobals()`.\n\n- Deliberately absent: synchronous `evaluate`, `harden`, and `lockdown`.\n- qiankun host extensions: DOM globals, `evaluateScript`, the synchronous `transformClassicScript` streaming adapter, lifecycle and entry-discovery facades, module-document registration, `defineUnshadowableGlobals`, `dispose`, `incubatorContext` / `moduleHost`, and the IsolationPlugin protocol. The executable list lives in `COMPARTMENT_HOST_EXTENSIONS`.\n- `evaluateScript()` uses a native blob `<script>` path. Do not introduce `eval` or `new Function` as an implementation shortcut.\n- `transformClassicScript()` exists because loader node transformation must finish synchronously before the original node enters live DOM. Loader consumes it through structural `CompartmentLoaderFacade`; never restore a concrete-class or private WeakMap bridge.\n- The pre-alignment names `Endowments` and `addIntrinsics()` have been removed entirely (the package has no external users, so no compatibility aliases). Use `CompartmentGlobals` / `globals` and `defineUnshadowableGlobals()`.\n\nThe module engine in `@qiankunjs/shared` is an implementation detail behind `Compartment.import()`, `load()`, and `importDocumentModules()`. Cross-package consumers must depend on the structural Compartment facade, never `EsmSandboxEngine`.\n\n`createSandbox().dispose()` is the terminal owner cleanup: it frees active plugin effects, restores container protocol state, deactivates the sandbox, and idempotently disposes the Compartment. Initial load failures must call it without replacing the original loading error.\n\nFetch precedence in `createSandbox()` is \"more specific wins\": module sources use `compartmentOptions.moduleHost.fetch ?? fetch ?? native`, dynamic assets use `fetch ?? native`. This is the one exception to the rule that promoted top-level options beat `compartmentOptions`; qiankun relies on it to give module sources a separate response cache.\n\n`createSandbox(appName)` is the JS-only preset: interval, window-listener, and history cleanup are available without a container. Providing `container` enables dynamic DOM interception; `mount()` prepares standalone containers lazily (`provisionContainerHead`, default `true` — qiankun's loader passes `false` because the entry HTML materializes the head in-stream). The controller keeps **two views of one configured transformer**, split by call-site identity: the public `nodeTransformer` is the pipeline variant — its output is stamped for native passthrough (`core/nativePassthrough.ts`), so patched mount points insert it untouched and it never enters the dynamic ledger — while the dynamic-append patcher receives the bare variant through the plugin config, whose output must NOT carry the mark (re-inserted nodes depend on re-entering the pipeline for ledger bookkeeping).\n\n### IsolationPlugin / Free pattern\n\n```typescript\nconst plugin: IsolationPlugin = {\n  name: 'example',\n  bootstrap: (ctx) => installBeforeAppCode(ctx),\n  mount: async (ctx) => installForThisMount(ctx),\n};\n\nconst free = await plugin.mount?.(context);\nif (free) {\n  const rebuild = free(); // unmount: remove this plugin's side effects\n  await rebuild(container); // remount: restore recorded side effects\n}\n```\n\n- `bootstrap` runs synchronously before any application script is evaluated.\n- `mount` runs on every mount and may asynchronously produce its `Free`.\n- Built-in plugins run in preset order; user plugins run afterward in registration order.\n- Prototype-level coordination belongs in browser-realm shared state with refcounts; it is not per-app context state. Use a stable `Symbol.for(...)` slot on `nativeGlobal` when independently bundled qiankun copies must coordinate the same prototype patch.\n- Code under `patchers/**` may use only the public Compartment surface. ESLint forbids imports from membrane internals.\n\n### Realm-shared metadata\n\n- `dynamicAppend.sharedState` is stored on `nativeGlobal` under `Symbol.for('qiankun.dynamicAppend.sharedState')`, so multiple qiankun copies in the same browser realm share prototype/CSSOM refcounts and ownership metadata.\n- `sandboxConfigs` — per-Compartment dynamic-append config.\n- `elementConfigs` — which Compartment owns each dynamically appended DOM node.\n- `containerOwners` — which Compartment currently patches a micro-app container.\n\n## ANTI-PATTERNS\n\n- **NEVER** access the real `window` / `document.head` directly — always the proxied view.\n- **NEVER** import membrane internals from a plugin or expose `EsmSandboxEngine` in a cross-package contract.\n- **NEVER** move dynamicAppend/style/timer/listener behavior into Compartment itself; those are DOM host plugins.\n- **FIXME** (in code): indirect `eval` in the membrane can let System.js escape sandbox scope.\n- **FIXME**: the runtime-container global may miss monkey-patched append logic.\n\n## REPLACEMENT BOUNDARY\n\nIf a native Compartment becomes available, it may replace only globalThis virtualization, the evaluator, and the module graph. Document proxying, dynamic append interception, style isolation, timers/listeners cleanup, and history integration remain qiankun-owned DOM plugins because ECMAScript Compartment proposals do not define DOM behavior. Keep the plugin layer dependent only on the public Compartment surface so this boundary remains real.\n\n## EXPORTS (`src/index.ts`)\n\n```typescript\nexport * from './core/sandbox'; // createSandbox, prepareSandboxContainer, StandardSandbox, controller/options types\nexport * from './core/compartment'; // Compartment, options, compatibility lists\nexport * from './consts'; // qiankunHeadTagName, qiankunBodyTagName, nativeGlobal, nativeDocument\nexport { esmDestructurableGlobals } from './core/esm-globals';\nexport type { IsolationPlugin, IsolationPluginContext, Free, Rebuild } from './patchers';\n```\n","isInternal":false,"tokens":2184,"sizeBytes":9837},{"name":"AGENTS.md","path":"packages/shared/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/umijs/qiankun/HEAD/packages/shared/AGENTS.md","title":"Shared Skill","category":"anthropic-skill","format":"markdown","content":"# @qiankunjs/shared\n\nBase utilities shared by `loader`/`sandbox`/`qiankun`: asset transpilers, fetch decorators, module resolution, error reporting, and the **ESM-sandbox engine**. No internal `@qiankunjs/*` dependencies — keep it at the bottom of the graph (it must never import `sandbox` or `loader`).\n\n## STRUCTURE\n\n```\nshared/\n├── assets-transpilers/   # transform script/link/style DOM nodes for the micro-frontend runtime\n│   ├── index.ts          # transpileAssets(node, baseURI, opts) — dispatch by tagName\n│   ├── script.ts         # classic scripts → blob-URL wrapping, sandbox scope\n│   ├── link.ts           # external stylesheets → blob-<link> (enables @scope); preload/URL rewriting; cache\n│   └── style.ts          # inline <style> → CSS @scope rewriting (runtime style isolation)\n├── esm-sandbox/          # per-instance native-ESM execution engine (see below)\n├── fetch-utils/          # higher-order fetch decorators\n│   ├── makeFetchCacheable.ts   # two runtime-wide LRUs (assets 50 / modules 512), clones responses, prunes failures\n│   ├── makeFetchRetryable.ts   # automatic retries\n│   └── makeFetchThrowable.ts   # non-2xx → throw\n├── module-resolver/      # shared-dependency canonicalization (semver via <script type=\"dependencymap\">)\n├── reporter/             # QiankunError + logger (warn/…)\n├── deferred-queue/       # async task sequencing (defer-script ordering)\n├── common.ts / utils.ts  # Deferred, defineProperty, hasOwnProperty, keys, resolveUrl, …\n└── typings.d.ts\n```\n\n## ESM-SANDBOX (`esm-sandbox/`, the largest subsystem here)\n\n`EsmSandboxEngine` is the replaceable mechanism behind the sandbox package's Compartment module facade; it must not leak into loader or qiankun interfaces. It runs a micro-app's `<script type=\"module\">` graph inside the sandbox membrane without a bundler. Pipeline: resolve through `resolveHook` → obtain and memoize a descriptor from `modules` or `importHook` / `loadHook` → fetch/lexer-rewrite source when needed → give modules synthetic specifiers → inject an **import map** → evaluate in dependency order → keep dunder-globals (`__qk_track`) live on later global writes.\n\n| File | Responsibility |\n| --- | --- |\n| `engine.ts` | `EsmSandboxEngine` — module-graph orchestration, redeclaration probing, in-order eval, dynamic `import()` |\n| `types.ts` | Public structural module contracts: hooks, descriptors, `ModuleSource`, Compartment facade |\n| `lexer.ts` | CSP-safe `es-module-lexer/js` wrapper; preparation Promise is already resolved |\n| `rewrite.ts` | `rewriteModule`, `buildSyntheticSpecifier`, `esmInternalPrefix`, `runtimeModuleSubpath` |\n| `import-bindings.ts` | import statement binding analysis |\n| `identifier-scan.ts` | `scanReferencedGlobals`, `isLiveBindableDunderName` — which globals a module touches |\n| `import-map-registry.ts` | `injectImportMapEntries`, `resetImportMapRegistry` — the dynamic import map |\n| `instance-registry.ts` | Per-engine instance/view lookup used by generated runtime modules |\n| `vite-client-stub.ts` | `isViteClientUrl`, `viteClientStubSource` — stubs Vite dev-client HMR |\n| `source-map.ts` | rewritten-module source-map fixups |\n\n> ESM sandbox depends on **dynamically injected import maps** → unsupported on Firefox (see `e2e/README.md`).\n\n## WHERE TO LOOK\n\n| Task | File | Notes |\n| --- | --- | --- |\n| Transpile any asset node | `assets-transpilers/index.ts` | `transpileAssets()` — the single dispatch entry |\n| Classic script sandboxing | `assets-transpilers/script.ts` | blob-URL wrap + sandbox scope |\n| Style isolation | `assets-transpilers/style.ts` + `link.ts` | CSS `@scope` rewrite; external CSS → blob-`<link>` |\n| Fetch enhancements | `fetch-utils/*` | decorator chain (compose with `make*` HOFs) |\n| Shared dependency reuse | `module-resolver/index.ts` | `moduleResolver(url, container, head)` semver match |\n| ESM module execution | `esm-sandbox/engine.ts` | `EsmSandboxEngine` (see section above) |\n| Module facade contracts | `esm-sandbox/types.ts` | The only ESM mechanism types cross-package callers should consume |\n| Promise/util helpers | `utils.ts` | `Deferred`, `keys`, `defineProperty`, `hasOwnProperty` |\n\n## KEY PATTERNS\n\n### Module hooks and descriptors\n\n- `resolveHook(specifier, referrer)` synchronously returns a canonical full specifier.\n- A `modules` descriptor wins before `importHook`; descriptors may provide precompiled `ModuleSource`, a namespace, or a redirect specifier.\n- `importHook(fullSpecifier)` is Promise-memoized, including failures. Probe-driven rebuilds reuse its result and do not call the hook again, so hooks must be idempotent.\n- A precompiled `ModuleSource` is already linked/re-written and skips the default fetch and rewrite pipeline.\n- Redirect cycles must fail deterministically; namespace descriptors keep their object identity.\n\nThe engine may be imported by the sandbox implementation itself, but do not add it to `LoaderOpts`, asset-transpiler options, or other cross-package interfaces. Those use `CompartmentModuleFacade`.\n\n### Higher-order fetch (decorator composition)\n\n```typescript\n// makeFetchCacheable(fetch, cacheScope: 'assets' | 'modules' = 'assets')\nconst resourceFetch = makeFetchRetryable(makeFetchThrowable(fetch));\nconst enhancedFetch = makeFetchCacheable(resourceFetch); // entries, classic scripts, styles\nconst moduleSourceFetch = makeFetchCacheable(resourceFetch, 'modules'); // passed as moduleHost.fetch\n```\n\nEach `cacheScope` maps to one LRU shared by the whole runtime copy: `assets` holds 50 responses and `modules` holds 512, so a large module graph cannot evict entries and styles. Both key requests by canonical URL and effective `credentials`, cache in-flight Promises, clone responses per reader, and drop failed responses. `loadApp` passes the module-scoped fetch as `compartmentOptions.moduleHost.fetch`, which the sandbox prefers over the top-level `fetch` for module sources.\n\n### Deferred promise\n\n```typescript\nconst d = new Deferred<void>();\nd.resolve();\nawait d.promise;\n```\n\n### DOM as metadata store\n\n- `data-src` — original source URL of a transpiled node.\n- `data-consumed` — marks an already-processed element.\n\n## EXPORTS\n\n`src/index.ts` re-exports everything: `./assets-transpilers`, `./utils`, `./common`, `./module-resolver`, `./reporter`, `./esm-sandbox`, the three `fetch-utils/make*`, and `./deferred-queue`. Notable named exports: `transpileAssets`, the Compartment-shaped module contracts, the internal `EsmSandboxEngine` mechanism, `moduleResolver`, `makeFetchCacheable`, `makeFetchRetryable`, `makeFetchThrowable`, `Deferred`, `QiankunError`, `warn`.\n","isInternal":false,"tokens":1624,"sizeBytes":6737},{"name":"create-main-app.md","path":"skills/qiankun/references/create-main-app.md","rawUrl":"https://raw.githubusercontent.com/umijs/qiankun/HEAD/skills/qiankun/references/create-main-app.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Create a main (host) app\n\nPrerequisites: the shared facts in [SKILL.md](../SKILL.md) — app name, framework, port, package versions.\n\n1. Scaffold the shell (any framework; below assumes React + TS on port `7099`) and install qiankun:\n\n   ```bash\n   pnpm create vite <main-app-name> --template react-ts\n   pnpm add qiankun@rc @qiankunjs/react@rc   # vue shell: @qiankunjs/vue@rc\n   ```\n\n   Pin the port in `vite.config.ts` (`server: { port: 7099, strictPort: true }`).\n\n2. Pick ONE loading style:\n\n   **`MicroApp` component (recommended for React/Vue shells)** — mounts on render, unmounts on component unmount, with built-in loading/error slots. Extra props are forwarded to the sub app (delivered via its `update` lifecycle on change):\n\n   ```tsx\n   import { MicroApp } from '@qiankunjs/react'; // same component name in @qiankunjs/vue\n\n   export default function SubAppPage() {\n     return <MicroApp name=\"<app-name>\" entry=\"//localhost:7101\" autoSetLoading />;\n   }\n   ```\n\n   **Router-driven registration (framework-agnostic)** — qiankun mounts/unmounts apps as the URL matches `activeRule`. Put this in the shell entry, once:\n\n   ```ts\n   import { registerMicroApps, start } from 'qiankun';\n\n   registerMicroApps([\n     {\n       name: '<app-name>',\n       entry: '//localhost:7101',\n       container: '#micro-app-container', // an element the shell always renders\n       activeRule: '/<app-name>',\n     },\n   ]);\n\n   start();\n   ```\n\n   For full manual control there is also `loadMicroApp({ name, entry, container }, configuration)` — it returns a handle; you own calling `.unmount()` when the app leaves.\n\n3. Per-app configuration (third argument of `loadMicroApp`, `configuration` prop of the `MicroApp` component, or per-app fields in `registerMicroApps`): `sandbox` defaults to on; add `styleIsolation: true` to scope the sub app's CSS with `@scope`.\n\n## Verify\n\n1. Run both dev servers, open the main app at `http://localhost:7099`, navigate to the sub app route — it must mount inside the shell with no console errors.\n2. Leave the sub app route — its DOM must be removed (unmount actually ran).\n3. The sub app must still render standalone at `http://localhost:7101` — see [create-micro-app.md](create-micro-app.md).\n","isInternal":false,"tokens":588,"sizeBytes":2243},{"name":"create-micro-app.md","path":"skills/qiankun/references/create-micro-app.md","rawUrl":"https://raw.githubusercontent.com/umijs/qiankun/HEAD/skills/qiankun/references/create-micro-app.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Create a micro (sub) app, or convert an existing Vite app\n\nPrerequisites: the shared facts in [SKILL.md](../SKILL.md) — app name, framework, port, package versions.\n\nFor a **new** app, start at step 1. To **convert an existing Vite app**, skip to step 2 — the conversion is exactly steps 2–4 and touches nothing else (no changes to `index.html`, `App` components, or build scripts).\n\n1. Scaffold with the official Vite scaffolder (templates: `react-ts`, `react`, `vue-ts`, `vue`):\n\n   ```bash\n   pnpm create vite <app-name> --template react-ts\n   ```\n\n2. Add the bundler plugin:\n\n   ```bash\n   pnpm add -D @qiankunjs/bundler-plugin@rc\n   ```\n\n3. Edit `vite.config.ts` — register the plugin and pin the port:\n\n   ```ts\n   import { defineConfig } from 'vite';\n   import react from '@vitejs/plugin-react'; // vue: @vitejs/plugin-vue\n   import { qiankun } from '@qiankunjs/bundler-plugin/vite';\n\n   export default defineConfig({\n     plugins: [react(), qiankun()],\n     server: {\n       port: 7101,\n       strictPort: true,\n     },\n   });\n   ```\n\n4. Rewrite the entry to export qiankun lifecycles, using the template below for the app's framework. Replace `<app-name>` with the actual registered name in both templates.\n\n## React entry template (`src/main.tsx`)\n\n```tsx\nimport React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport App from './App';\nimport './index.css';\n\ndeclare global {\n  interface Window {\n    __POWERED_BY_QIANKUN__?: boolean;\n    [key: string]: unknown;\n  }\n}\n\nlet root: ReactDOM.Root | undefined;\n\nfunction render(props: { container?: Element } = {}) {\n  // when loaded by qiankun, resolve #root inside the host-provided container, not the top document\n  const container = props.container?.querySelector('#root') ?? document.getElementById('root');\n  if (!container) return;\n\n  root = ReactDOM.createRoot(container);\n  root.render(\n    <React.StrictMode>\n      <App />\n    </React.StrictMode>,\n  );\n}\n\nexport async function bootstrap() {}\n\nexport async function mount(props: { container?: Element }) {\n  render(props);\n}\n\n// the host re-delivers its props here whenever they change — react to them without remounting\nexport async function update(_props: Record<string, unknown>) {}\n\nexport async function unmount(_props: { container?: Element }) {\n  root?.unmount();\n  root = undefined;\n}\n\nif (window.__POWERED_BY_QIANKUN__) {\n  // classic-mode fallback: expose the lifecycles on window under the REGISTERED app name\n  window['<app-name>'] = { bootstrap, mount, update, unmount };\n} else {\n  render();\n}\n```\n\n## Vue entry template (`src/main.ts`)\n\n```ts\nimport { createApp, reactive } from 'vue';\nimport App from './App.vue';\nimport './style.css';\n\ndeclare global {\n  interface Window {\n    __POWERED_BY_QIANKUN__?: boolean;\n    [key: string]: unknown;\n  }\n}\n\nlet app: ReturnType<typeof createApp> | undefined;\n\n// props the host hands over — seeded on mount, kept current by `update`\nconst hostProps = reactive<Record<string, unknown>>({});\n\nfunction render(props: { container?: Element } = {}) {\n  const container = props.container?.querySelector('#app') ?? document.getElementById('app');\n  if (!container) return;\n\n  app = createApp(App, { hostProps });\n  app.mount(container);\n}\n\nexport async function bootstrap() {}\n\nexport async function mount(props: { container?: Element }) {\n  Object.assign(hostProps, props);\n  render(props);\n}\n\nexport async function update(props: Record<string, unknown>) {\n  Object.assign(hostProps, props);\n}\n\nexport async function unmount(_props: { container?: Element }) {\n  app?.unmount();\n  app = undefined;\n}\n\nif (window.__POWERED_BY_QIANKUN__) {\n  // classic-mode fallback: expose the lifecycles on window under the REGISTERED app name\n  window['<app-name>'] = { bootstrap, mount, update, unmount };\n} else {\n  render();\n}\n```\n\n## Verify\n\n1. `pnpm dev` in the sub app, open `http://localhost:7101` — it must render on its own (the non-qiankun branch of the entry).\n2. If a main app exists, run it too and confirm the sub app mounts inside it with no console errors — see the verification checklist in [create-main-app.md](create-main-app.md).\n","isInternal":false,"tokens":1038,"sizeBytes":4131},{"name":"SKILL.md","path":"skills/qiankun/SKILL.md","rawUrl":"https://raw.githubusercontent.com/umijs/qiankun/HEAD/skills/qiankun/SKILL.md","title":"qiankun","category":"anthropic-skill","format":"markdown","content":"---\nname: qiankun\ndescription: >-\n  Usage manual for building micro-frontend projects with qiankun 3.x. Use when the user wants to create a qiankun main (host) app or sub (micro) app, convert an existing Vite app into a qiankun micro app, or wire up micro-frontend loading with React/Vue.\n---\n\n# qiankun\n\nqiankun is a micro-frontend framework: a main (host) app loads sub (micro) apps from their HTML entries at runtime, each inside a JS sandbox, with opt-in CSS isolation. This skill is the agent-facing manual for **qiankun 3.x**.\n\n## Task routing\n\nRead only the reference file(s) the task at hand needs:\n\n| Task | Read |\n| --- | --- |\n| Create a micro (sub) app, or convert an existing Vite app | [references/create-micro-app.md](references/create-micro-app.md) |\n| Create a main (host) app | [references/create-main-app.md](references/create-main-app.md) |\n| Anything else — migration, style isolation, sandbox, debugging | https://qiankun.umijs.org and `examples/` in https://github.com/umijs/qiankun |\n\n## Before you start\n\nDetermine these, asking the user only when not inferable from context:\n\n1. **App type** — main (host/shell) or sub (micro app).\n2. **App name** — lowercase letters, numbers, hyphens. The name the host registers must exactly match the name the sub app uses in its classic-mode fallback.\n3. **Framework** — React or Vue; TypeScript or JavaScript. Templates in the references are TS; strip types for JS.\n4. **Dev port** — must be fixed and unique per app, because the host hard-references it in `entry`. Convention: main app `7099`, sub apps `7101`, `7102`, …\n\nPackage versions — until qiankun 3.0 stable ships, `rc` is the dist-tag for the core packages:\n\n| Package                               | Dist-tag | Goes in                                       |\n| ------------------------------------- | -------- | --------------------------------------------- |\n| `qiankun`                             | `rc`     | main app `dependencies`                       |\n| `@qiankunjs/react` / `@qiankunjs/vue` | `rc`     | main app `dependencies` (optional UI binding) |\n| `@qiankunjs/bundler-plugin`           | `rc`     | sub app `devDependencies`                     |\n\n## Key facts\n\nThese invariants shape every task below:\n\n- **Vite sub apps need no dedicated build mode.** qiankun 3 loads `<script type=\"module\">` natively through its ESM sandbox — regular `vite dev` / `vite build` output is qiankun-ready as-is.\n- The JS sandbox is **on by default**. CSS isolation is opt-in per app via `styleIsolation: true` (runtime CSS `@scope`).\n- A sub app must stay **runnable standalone**: when not loaded by qiankun (`window.__POWERED_BY_QIANKUN__` is undefined) it renders itself directly.\n- **Always unmount** what you mount. `loadMicroApp` returns a handle whose `.unmount()` must be called when the app leaves; the `MicroApp` component bindings do this automatically on component unmount.\n- Webpack sub apps use `@qiankunjs/bundler-plugin/webpack` instead of the Vite plugin; see the docs.\n\n## After any creation task\n\nVerify the result following the checklist at the end of the reference you used. If a browser tool is available, perform the checks yourself instead of asking the user to.\n","frontmatter":{"name":"qiankun","description":"Usage manual for building micro-frontend projects with qiankun 3.x. Use when the user wants to create a qiankun main (host) app or sub (micro) app, convert an existing Vite app into a qiankun micro app, or wire up micro-frontend loading with React/Vue."},"isInternal":false,"tokens":807,"sizeBytes":3227},{"name":"README.md","path":"packages/bundler-plugin/README.md","rawUrl":"https://raw.githubusercontent.com/umijs/qiankun/HEAD/packages/bundler-plugin/README.md","title":"bundler-plugin Documentation","category":"plugin-manifest","format":"markdown","content":"# @qiankunjs/bundler-plugin\n\nBundler plugins for the [qiankun](https://github.com/umijs/qiankun) micro-frontend framework. Currently supports Webpack (4 & 5) and Vite (5+).\n\n## Installation\n\nUsing npm:\n\n```bash\nnpm install @qiankunjs/bundler-plugin --save-dev\n```\n\nOr using yarn:\n\n```bash\nyarn add @qiankunjs/bundler-plugin --dev\n```\n\n## Webpack Plugin\n\n### Features\n\n- Automatically sets the name and format of the output library\n- Ensures the uniqueness of the `jsonpFunction`/`chunkLoadingGlobal` name\n- Sets the global object to `window`, ensuring the library can run in the browser\n- Automatically adds an entry marker to the entry script tag in HTML\n- Supports both Webpack 4 and Webpack 5\n\n### Usage\n\nIn your `webpack.config.js` or other configuration files:\n\n```javascript\nconst { QiankunWebpackPlugin } = require('@qiankunjs/bundler-plugin');\n\nmodule.exports = {\n  plugins: [\n    new QiankunWebpackPlugin({\n      packageName: 'optionalPackageName',\n    }),\n  ],\n};\n```\n\n### Options\n\n- `packageName`: Specifies the name of the output library. If not provided, the name from `package.json` will be used.\n\n### Entry Script Detection\n\nThe plugin automatically detects and marks the entry script in your HTML:\n\n1. Uses webpack's `compilation.entrypoints` API to identify entry chunks\n2. For single entry builds: marks the main entry chunk's script\n3. For multi-entry builds: matches HTML filename to entrypoint name\n4. Falls back to the last script tag if automatic detection fails\n\n## Vite Plugin\n\nqiankun loads Vite apps natively through its ESM sandbox — no legacy/SystemJS transformation is required. The Vite plugin only takes care of the loading plumbing:\n\n### Features\n\n- Responds with permissive CORS headers from the dev and preview servers, so the main app can fetch the entry HTML and module graph cross-origin\n- Marks the entry module script with the `entry` attribute in the built HTML (same contract as the webpack plugin), so the qiankun loader picks the entry deterministically\n\n### Usage\n\nIn your `vite.config.ts`:\n\n```typescript\nimport { defineConfig } from 'vite';\nimport { qiankun } from '@qiankunjs/bundler-plugin/vite';\n\nexport default defineConfig({\n  plugins: [qiankun()],\n});\n```\n\nYour entry module just exports the qiankun lifecycles directly:\n\n```typescript\nexport async function bootstrap() {}\nexport async function mount(props) {\n  // render your app into props.container\n}\nexport async function unmount(props) {\n  // tear your app down\n}\n```\n\n## Contributing\n\nAny form of contribution is welcome! Please submit PRs or open issues for discussion.\n\n## License\n\nMIT\n","isInternal":false,"tokens":625,"sizeBytes":2599},{"name":"README.md","path":"packages/loader/README.md","rawUrl":"https://raw.githubusercontent.com/umijs/qiankun/HEAD/packages/loader/README.md","title":"loader Documentation","category":"plugin-manifest","format":"markdown","content":"# @qiankunjs/loader\n\nqiankun 的流式 HTML 入口加载器，负责将微应用的 HTML 逐步写入容器，并通过沙箱提供的接口完成资源转换和脚本执行。它是 qiankun 加载流程的底层组件；业务应用建议使用 `qiankun` 的公开 API，无需单独安装本包。\n\n文档：[HTML 入口](https://www.qiankunjs.com/zh-CN/concepts/html-entry-loading)。\n\n## English\n\nThe streaming HTML-entry loader for qiankun. It incrementally writes a micro-app's HTML into its container and uses sandbox-provided interfaces for asset transformation and script execution. This package is a low-level part of qiankun's loading pipeline; application code should use the public `qiankun` APIs without installing it separately.\n\nDocumentation: [HTML entry](https://www.qiankunjs.com/concepts/html-entry-loading).\n","isInternal":false,"tokens":232,"sizeBytes":829},{"name":"README.md","path":"packages/loader/src/writable-dom/README.md","rawUrl":"https://raw.githubusercontent.com/umijs/qiankun/HEAD/packages/loader/src/writable-dom/README.md","title":"writable-dom Documentation","category":"plugin-manifest","format":"markdown","content":"# Fork from https://github.com/marko-js/writable-dom\n","isInternal":false,"tokens":14,"sizeBytes":53},{"name":"README.md","path":"packages/qiankun/README.md","rawUrl":"https://raw.githubusercontent.com/umijs/qiankun/HEAD/packages/qiankun/README.md","title":"qiankun Documentation","category":"plugin-manifest","format":"markdown","content":"# qiankun（乾坤）\n\n<p align=\"center\">\n  <a href=\"https://qiankun.umijs.org/\" target=\"_blank\" rel=\"noopener noreferrer\">\n    <img width=\"180\" src=\"https://gw.alipayobjects.com/zos/bmw-prod/8a74c1d3-16f3-4719-be63-15e467a68a24/km0cv8vn_w500_h500.png\" alt=\"qiankun logo\">\n  </a>\n</p>\nurl('')\n\n[![npm version](https://img.shields.io/npm/v/qiankun.svg?style=flat-square)](https://www.npmjs.com/package/qiankun) [![coverage](https://img.shields.io/codecov/c/github/umijs/qiankun.svg?style=flat-square)](https://codecov.io/gh/umijs/qiankun) [![npm downloads](https://img.shields.io/npm/dt/qiankun.svg?style=flat-square)](https://www.npmjs.com/package/qiankun) [![Build Status](https://img.shields.io/travis/umijs/qiankun.svg?style=flat-square)](https://travis-ci.com/umijs/qiankun)\n\n> In Chinese traditional culture `qian` means heaven and `kun` stands for earth, so `qiankun` is the universe.\n\nAn implementation of [Micro Frontends](https://micro-frontends.org/), based on [single-spa](https://github.com/CanopyTax/single-spa), but made it production-ready.\n\n## 🤔 Motivation\n\nAs we know what micro-frontends aims for:\n\n> Techniques, strategies and recipes for building a **modern web app** with **multiple teams** using **different JavaScript frameworks**. — [Micro Frontends](https://micro-frontends.org/)\n\nModularity is very important for large application. By breaking down a large system into individual sub-applications, we can achieve good divide-and-conquer between products and when necessary combination, especially for enterprise applications that usually involve multi-team collaboration. But if you're trying to implement such a micro frontends architecture system by yourself, you're likely to run into some tricky problems:\n\n- In what form do sub applications publish static resources?\n- How does the main application integrate individual sub-applications?\n- How do you ensure that sub-applications are independent of each other (development independent, deployment independent) and runtime isolated?\n- Performance issues? What about public dependencies?\n- And so on...\n\nAfter solving these common problems of micro frontends, we extracted the kernel of our solution after a lot of internal online application testing and polishing, and then named it `qiankun`.\n\n**Probably the most complete micro-frontends solution you ever met🧐.**\n\n## 📦 Installation\n\n```shell\n$ pnpm run add qiankun\n```\n\n## 📖 Documentation\n\nhttps://qiankun.umijs.org/\n\n## 💿 Getting started\n\nThis repo contains an `examples` folder with a sample Shell app and multiple mounted Micro FE apps. To run this app, first clone `qiankun`:\n\n```shell\n$ git clone https://github.com/umijs/qiankun.git\n$ cd qiankun\n```\n\nNow run the pnpm run scripts to install and run the examples project:\n\n```shell\n$ pnpm run install\n$ pnpm run examples:install\n$ pnpm run examples:start\n```\n\nVisit `http://localhost:7099`.\n\n![](./examples/example.gif)\n\n## :sparkles: Features\n\n- 📦 **Based On [single-spa](https://github.com/CanopyTax/single-spa)**\n- 📱 **Technology Agnostic**\n- 💪 **HTML Entry Access Mode**\n- 🛡 **Style Isolation**\n- 🧳 **JS Sandbox**\n- ⚡ **Prefetch Assets**\n- 🔌 **[Umi Plugin](https://github.com/umijs/plugins/tree/master/packages/plugin-qiankun) Integration**\n\n## 🎯 Roadmap\n\n- [x] Parcel apps integration (multiple sub apps displayed at the same time, but only one uses router at most)\n- [x] Communication development kits between master and sub apps\n- [ ] Custom side effects hijacker\n- [ ] Nested Microfrontends\n\n## ❓ FAQ\n\nhttps://qiankun.umijs.org/faq/\n\n## 👬 Community\n\nhttps://qiankun.umijs.org/#community\n\n## 🎁 Acknowledgements\n\n- [single-spa](https://github.com/CanopyTax/single-spa) What an awesome meta-framework for micro-frontends!\n- [import-html-entry](https://github.com/kuitos/import-html-entry/) An assets loader which supports html entry.\n","isInternal":false,"tokens":1028,"sizeBytes":3875},{"name":"README.md","path":"packages/sandbox/README.md","rawUrl":"https://raw.githubusercontent.com/umijs/qiankun/HEAD/packages/sandbox/README.md","title":"sandbox Documentation","category":"plugin-manifest","format":"markdown","content":"# @qiankunjs/sandbox\n\n`@qiankunjs/sandbox` provides qiankun's browser sandbox as a standalone package. It can isolate classic-script and ESM global writes, and can optionally contain dynamic DOM and styles inside an application container. It does not require `qiankun` or `@qiankunjs/loader`.\n\n> This package is browser-only. It uses DOM APIs, blob URLs, and dynamically injected import maps; it does not support Node.js or SSR execution.\n\n## Choose an API layer\n\n| Need | API | What it provides |\n| --- | --- | --- |\n| JavaScript isolation only | `StandardSandbox` | A safe preset for classic scripts and ESM, including `window`/`self` self-references |\n| Low-level Compartment mechanics | `Compartment` | A Compartment-shaped global and module-hook facade for advanced hosts |\n| Browser containment and lifecycle | `createSandbox()` | `StandardSandbox` plus optional DOM/style containment, timer and listener cleanup, and isolation plugins |\n\nUse `StandardSandbox` for direct classic-script evaluation. A bare `Compartment` deliberately does not install a sandboxed `window` self-reference. Calling `Compartment.evaluateScript()` without defining one emits a development warning, but it does not change execution semantics: code such as `window.answer = 42` may write to the host window.\n\n## JavaScript isolation\n\n### Classic scripts\n\n```ts\nimport { StandardSandbox } from '@qiankunjs/sandbox';\n\nconst sandbox = new StandardSandbox('report-widget', {\n  tenantId: 'acme',\n});\n\nawait sandbox.evaluateScript(\n  `\n    window.reportState = { tenantId };\n  `,\n  { sourceURL: 'https://widgets.example/report.js' },\n);\n\nconsole.log(Reflect.get(sandbox.globalThis, 'reportState')); // { tenantId: 'acme' }\nconsole.log(Reflect.get(window, 'reportState')); // undefined\n\nsandbox.dispose();\n```\n\n`evaluateScript()` executes through a native blob script. It does not use `eval` or `new Function`.\n\n### ESM\n\n```ts\nconst sandbox = new StandardSandbox('report-module');\nconst namespace = await sandbox.import(new URL('./report-entry.js', document.baseURI).href);\n\nconsole.log(namespace);\nsandbox.dispose();\n```\n\nRelative module resolution defaults to `document.baseURI`. `StandardSandbox` accepts custom module tables and hooks through its fourth, Compartment-options argument; `createSandbox()` promotes `modules`, `resolveHook`, `importHook`, and `loadHook` to top-level options. See the [sandbox plugins guide](../../docs/cookbook/sandbox-plugins.md) for an advanced example.\n\n`createSandbox()` fetches dynamic assets and module sources with its top-level `fetch` option, which defaults to the native fetch. To give module sources their own transport, such as a separate response cache, set `compartmentOptions.moduleHost.fetch`. It is the only `compartmentOptions` field that takes precedence over a top-level option: the more specific module-source fetch wins when loading modules, while dynamic assets keep using the top-level `fetch`.\n\n## Full browser sandbox\n\nPass a container to `createSandbox()` to enable document virtualization and dynamic DOM interception. Set `styleIsolation: true` to scope application styles to that container.\n\n```ts\nimport { createSandbox } from '@qiankunjs/sandbox';\n\nconst container = document.querySelector<HTMLElement>('#widget')!;\nconst controller = createSandbox('report-widget', {\n  container,\n  styleIsolation: true,\n});\n\n// Mount before application code so timers and listeners are tracked.\nawait controller.mount();\n\nconst response = await fetch('/vendor/report-widget.js');\nconst source = await response.text();\nawait controller.instance.evaluateScript(source, {\n  sourceURL: response.url,\n});\n\n// Releases active plugin effects and keeps the controller available for remount.\nawait controller.unmount();\nawait controller.mount();\n\n// Terminal cleanup. A disposed controller cannot be mounted or evaluated again.\nawait controller.dispose();\n```\n\nWhen `container` is a function, it is resolved again for each mount. This supports hosts that replace the mount element between activations:\n\n```ts\nlet currentContainer = document.querySelector<HTMLElement>('#first')!;\n\nconst controller = createSandbox('report-widget', {\n  container: () => currentContainer,\n});\n\nawait controller.mount();\nawait controller.unmount();\n\ncurrentContainer = document.querySelector<HTMLElement>('#second')!;\nawait controller.mount();\n```\n\nCalling `createSandbox('name')` without a container creates the JS-only preset. Its timer, window-listener, and history plugins can be activated with `mount()` without providing an arbitrary DOM element, and an element passed to `mount()` is left untouched — container preparation belongs to the container-backed preset. `styleIsolation: true` requires a configured container.\n\n### Container preparation\n\n`createSandbox()` establishes its container contract automatically. Advanced hosts that compose the lower-level APIs can prepare a container directly:\n\n```ts\nimport { prepareSandboxContainer } from '@qiankunjs/sandbox';\n\nconst { styleIsolation, cleanup } = prepareSandboxContainer(container, 'report-widget');\n\nconsole.log(styleIsolation);\n// { appName: 'report-widget', scopeRoot: '[data-name=\"report-widget\"]' }\n\n// Later, release only the attributes and virtual head created by this call.\ncleanup();\n```\n\nThe helper sets `data-name`, ensures a `<qiankun-head>` mount point for the sandboxed `document.head`, and returns the matching style-isolation options. Cleanup is idempotent: it preserves an existing virtual head, restores a previous `data-name`, and does not overwrite a later host update to that attribute.\n\n## Default asset transformation\n\nThe default `nodeTransformer` is isolation-preserving. A classic `<script>` dynamically appended by sandboxed code is fetched, wrapped with the controller's classic-script transformer, and executed through a blob URL. Dynamic styles and stylesheet links go through the same pipeline, including style scoping when enabled.\n\nYou may provide a custom transformer. Returning each node unchanged is not a neutral optimization:\n\n```ts\nconst controller = createSandbox('trusted-widget', {\n  container,\n  nodeTransformer: (node) => node,\n});\n```\n\nAn identity transformer disables the default classic-script wrapping. A dynamically appended classic script can then execute against the host global. Use this override only when the host supplies equivalent isolation or fully trusts every script. The internal asset transpiler is intentionally not a public API; the supported extension point is `nodeTransformer`.\n\nFor a custom HTML loading pipeline, use `controller.nodeTransformer`. It is the fully configured transformer shared with dynamic DOM interception, including the current Compartment, fetch implementation, classic-script wrapper, and style-isolation options.\n\n## Lifecycle ownership\n\n- `mount(container?)` activates or rebuilds timer, listener, history, DOM, and user-plugin effects. The argument overrides the configured container for that mount.\n- `unmount()` releases active effects and records any state required for a later mount. It does not run an application's own unmount function or clear caller-owned DOM.\n- `dispose()` permanently releases plugin effects, generated module resources, global accessors, and container preparation. It is safe to call more than once.\n\nMount the controller before evaluating application code when its timers and listeners must participate in cleanup. Run the application's own teardown before `unmount()` or `dispose()`, and clear application DOM according to the host's ownership rules.\n\n## Isolation boundary\n\nThis package is a compatibility sandbox, not a security boundary for hostile code.\n\n- Writes to the sandbox global stay local to that sandbox.\n- Reads fall through to the host window unless a value is configured or shadowed.\n- The membrane is non-transitive: objects crossing the boundary retain their identity. `===`, `instanceof`, DOM nodes, events, and shared library instances continue to work.\n- Mutating a host object obtained through read-through also mutates that shared object. Only the global property write is isolated.\n- DOM containment redirects the sandboxed `document.head` and `document.body`; it does not create a separate browser realm or origin.\n\nUse an iframe, Worker, or another origin/callable boundary for untrusted code. See the [Compartment Alignment RFC](../../docs/rfcs/compartment-alignment.md) for the rationale behind the write-isolation and identity-sharing model.\n\n## Content Security Policy\n\nClassic and ESM execution do not require `'unsafe-eval'`. They do require the page's `script-src` policy to allow `blob:` URLs. The ESM engine also injects an inline `script[type=\"importmap\"]`, which must be permitted by the page's script policy. ESM and external classic assets are fetched first, so their origins must satisfy CORS and `connect-src` as well.\n\nStyle isolation can create blob stylesheet URLs; allow `blob:` in `style-src` when external styles are used. Dynamically created inline `<style>` elements remain subject to the page's normal nonce, hash, or inline-style policy.\n\n```http\nContent-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' blob:; style-src 'self' 'unsafe-inline' blob:; connect-src 'self' https://widgets.example\n```\n\nThis illustrative policy allows the runtime import map and a widget-created inline style. Tighten it to the assets and execution path the host actually uses. Do not add `'unsafe-eval'` for this package.\n\n## Further reading\n\n- [Standalone sandbox cookbook](../../docs/cookbook/standalone-sandbox.md)\n- [Extending sandbox isolation](../../docs/cookbook/sandbox-plugins.md)\n- [Standalone Sandbox RFC](../../docs/rfcs/standalone-sandbox.md)\n","isInternal":false,"tokens":2026,"sizeBytes":9694},{"name":"README.md","path":"packages/shared/README.md","rawUrl":"https://raw.githubusercontent.com/umijs/qiankun/HEAD/packages/shared/README.md","title":"shared Documentation","category":"plugin-manifest","format":"markdown","content":"# @qiankunjs/shared\n\nqiankun 内部共用的基础模块，为加载器、沙箱和运行时提供资源转换、请求处理、模块解析及 ESM 沙箱引擎等能力。它位于 qiankun 内部依赖关系的底层，不建议业务应用直接依赖；接入微应用请使用 `qiankun` 的公开 API。\n\n文档：[加载一个微应用实例](https://www.qiankunjs.com/zh-CN/concepts/architecture)。\n\n## English\n\nInternal foundation modules shared by qiankun's loader, sandbox, and runtime, including asset transformation, fetch utilities, module resolution, and the ESM sandbox engine. This package sits at the bottom of qiankun's internal dependency graph and is not recommended as a direct application dependency; use the public `qiankun` APIs to integrate micro-apps.\n\nDocumentation: [Loading a micro-app instance](https://www.qiankunjs.com/concepts/architecture).\n","isInternal":false,"tokens":253,"sizeBytes":875},{"name":"README.md","path":"packages/single-spa/README.md","rawUrl":"https://raw.githubusercontent.com/umijs/qiankun/HEAD/packages/single-spa/README.md","title":"single-spa Documentation","category":"plugin-manifest","format":"markdown","content":"# @qiankunjs/single-spa\n\nqiankun's vendored fork of [single-spa](https://github.com/single-spa/single-spa), consumed by the `qiankun` package as its application/parcel lifecycle router. See the vendoring RFC in [umijs/qiankun#3168](https://github.com/umijs/qiankun/issues/3168) for the full rationale and plan.\n\n## Fork baseline\n\n- Upstream: `single-spa/single-spa`, `7.0` branch @ [`ce0f925a`](https://github.com/single-spa/single-spa/commit/ce0f925a40a81809c598de21b36ad79706222a73) (tag `v7.0.0-beta.13`, 2025-09-22).\n- Imported verbatim (`src/`, `spec/`, `node-spec/`, `LICENSE`), then naturalized to this monorepo's toolchain (Vite 8 build, `tsc` declarations, vitest). Upstream rollup/babel/jest configs were not carried over; the compile-time constants they injected (`__DEV__`, `__PROFILE__`, `process.env.BABEL_ENV`) are provided by `vite.library.config.ts` (build) and the root `vitest.config.ts` (tests).\n- The profile variant entry (`src/single-spa.profile.ts` + `src/devtools/profiler-api.ts`) is kept in the tree but not built or published yet; `__PROFILE__` is compiled to `false`.\n- Versioning is independent of upstream beta numbering and follows this monorepo's changesets flow.\n\n## Behavioral divergences from upstream\n\nMaintained deliberately; this list is the contract (keep it in sync when adding divergences):\n\n1. **The upstream `bootstrap` → `init` rename ([single-spa#1307](https://github.com/single-spa/single-spa/pull/1307), completed by [#1333](https://github.com/single-spa/single-spa/pull/1333)) is reverted wholesale — this fork speaks pure v6 vocabulary, with no `init` aliases.** qiankun's ecosystem was built on the bootstrap vocabulary and there was no reason to break it. Concretely:\n   - the `bootstrap` lifecycle is the only recognized name for applications and parcels (a v7-style `init` export is ignored, exactly as v6 ignored unknown fields);\n   - `parcel.bootstrapPromise`, `setBootstrapMaxTime` and the `timeouts.bootstrap` key are back; `initPromise`, `setInitMaxTime` and `timeouts.init` do not exist;\n   - the `NOT_BOOTSTRAPPED` / `BOOTSTRAPPING` statuses are restored, both as `AppOrParcelStatus` member names and as the raw `getStatus()` string values (v7's `NOT_INITIALIZED` / `INITIALIZING` strings do not exist in this fork);\n   - error message texts use the bootstrap vocabulary again;\n   - the vendored upstream specs were migrated back to the bootstrap vocabulary along with the sources.\n\n   If upstream ever ships a stable (non-beta) v7 that still carries the rename, re-evaluate aligning with it then — until that day this fork does not track it.\n\n## Invariant contracts (never break)\n\n- All `single-spa:*` window events (`single-spa:before-routing-event`, `single-spa:routing-event`, `single-spa:no-app-change`, `single-spa:first-mount`, …) — qiankun and the wider ecosystem listen to them.\n- The `window.__SINGLE_SPA_DEVTOOLS__` hook (single-spa-inspector support).\n- Public API and type signatures stay 1:1 with the upstream baseline during phase one, except for the bootstrap-naming revert above — qiankun-observable behavior stays 1:1 with what its ecosystem shipped against, which is the higher-priority contract.\n- Zero runtime dependencies; this package sits at the bottom of the workspace dependency graph and must not import `@qiankunjs/shared`, `@qiankunjs/sandbox` or `@qiankunjs/loader`.\n\n## Known semantics now owned by this fork\n\n- `urlRerouteOnly` (documented at length in qiankun's `docs/api/start.md`) — qiankun passes it through untouched; the implementation and its semantics live here now.\n- **Single-instance assumption**: single-spa is a stateful routing singleton. Loading both the ESM and CJS builds in one page (or mixing this fork with a separately installed npm `single-spa`) silently creates two independent routers — two history patches, two event streams, two `started` flags. Don't.\n\n## Upstream sync\n\nExpected cadence ≈ zero (upstream is dormant). If a fix is worth absorbing:\n\n```bash\ngit remote add single-spa-upstream https://github.com/single-spa/single-spa.git\ngit fetch single-spa-upstream 7.0\ngit log ce0f925a..single-spa-upstream/7.0 --oneline   # review the delta\n# apply selected commits manually onto packages/single-spa/src/ (path prefixes differ)\n```\n\nUpdate the baseline note above after absorbing anything; on conflict with the divergence list, the divergence list wins.\n\n## License\n\nMIT — upstream's [LICENSE](./LICENSE) is preserved.\n","isInternal":false,"tokens":1147,"sizeBytes":4461},{"name":"README.md","path":"packages/ui-bindings/react/README.md","rawUrl":"https://raw.githubusercontent.com/umijs/qiankun/HEAD/packages/ui-bindings/react/README.md","title":"react Documentation","category":"plugin-manifest","format":"markdown","content":"# qiankun ui binding for react\n\n## Usage\n\n```bash\nnpm i @qiankunjs/react\n```\n\n## MicroApp component\n\nLoad (or unload) child apps directly through the `<MicroApp/>` component, which provides loading and error catching-related capabilities:\n\n```tsx\nimport { MicroApp } from '@qiankunjs/react';\n\nexport default function Page() {\n  return <MicroApp name=\"app1\" entry=\"http://localhost:8000\" />;\n}\n```\n\nWhen the sub-app loading animation or error capture capability is enabled, the sub-app accepts an additional style class `wrapperClassName`, and the rendered result is as follows:\n\n```tsx\n<div style={{ position: 'relative' }} className={`${wrapperClassName} qiankun-micro-app-wrapper`}>\n  <MicroApp className={`${className} qiankun-micro-app-container`} />\n  <MicroAppLoader loading={loading} />\n  <ErrorBoundary error={e} />\n</div>\n```\n\nThe container is rendered first so its position never shifts as the loader and error slots come and go — qiankun keys its per-container caches on that position. Because the slots come after it, they paint above the micro app without needing a `z-index`.\n\n### Load animation\n\nWhen this capability is enabled, loading animations are automatically displayed when child apps are loading. When the sub-application is mounted and changes to the `MOUNTED` state, the loading status ends and the sub-application content is displayed.\n\nJust pass `autoSetLoading` as a parameter:\n\n```tsx\nimport { MicroApp } from '@qiankunjs/react';\n\nexport default function Page() {\n  return <MicroApp name=\"app1\" entry=\"http://localhost:8000\" autoSetLoading />;\n}\n```\n\n#### Custom loading animation\n\nIf you want to override the default loading animation style, you can set a custom loading component `loader` as the loading animation for the child app.\n\n```tsx\nimport CustomLoader from '@/components/CustomLoader';\nimport { MicroApp } from '@qiankunjs/react';\n\nexport default function Page() {\n  return (\n    <MicroApp name=\"app1\" entry=\"http://localhost:8000\" loader={(loading) => <CustomLoader loading={loading} />} />\n  );\n}\n```\n\nwhere `loading` is the `boolean` type parameter, `true` indicates that the loading state is still being loaded, and `false` indicates that the loading state has ended.\n\n### Error catching\n\nWhen this capability is enabled, an error message is automatically displayed when a child app loads unexpectedly. You can pass the `autoCaptureError` property to the sub-app to enable sub-app error capture capabilities:\n\n```tsx\nimport { MicroApp } from '@qiankunjs/react';\n\nexport default function Page() {\n  return <MicroApp name=\"app1\" entry=\"http://localhost:8000\" autoCaptureError />;\n}\n```\n\n#### Custom error capture\n\nIf you want to override the default error capture component style, you can set a custom component `errorBoundary` as the error capture component for the child app:\n\n```tsx\nimport CustomErrorBoundary from '@/components/CustomErrorBoundary';\nimport { MicroApp } from '@qiankunjs/react';\n\nexport default function Page() {\n  return (\n    <MicroApp\n      name=\"app1\"\n      entry=\"http://localhost:8000\"\n      errorBoundary={(error) => <CustomErrorBoundary error={error} />}\n    />\n  );\n}\n```\n\n### Component Props\n\n| Name | Required | Description | Type | Default |\n| --- | --- | --- | --- | --- |\n| `name` | yes | The name of the microapp | `string` |\n| `entry` | yes | The HTML address of the microapp | `string` |\n| `settings` | no | qiankun configuration for this microapp — sandbox, style isolation, fetch, … | `AppConfiguration` | `undefined` |\n| `lifeCycles` | no | qiankun lifecycle hooks for this microapp. They are captured on the first load into a given container, so avoid closing over component state in them | `LifeCycles` | `undefined` |\n| `autoSetLoading` | no | Automatically set the loading state of your microapp | `boolean` | `false` |\n| `loader` | no | Custom microapps load state components | `(loading) => React.ReactNode` | `undefined` |\n| `autoCaptureError` | no | Automatically set up error capture for microapps | `boolean` | `false` |\n| `errorBoundary` | no | Custom microapp error capture component | `(error: Error) => React.ReactNode` | `undefined` |\n| `className` | no | The style class for the microapp | `string` | `undefined` |\n| `wrapperClassName` | no | Wrap the microapp loading component, error capture component, and microapp's style classes, and are only valid when the load component or error capture component is enabled | `string` | `undefined` |\n\n## Get the child app load status\n\nThe loading status includes: \"NOT_LOADED\" | \"LOADING_SOURCE_CODE\" | \"NOT_BOOTSTRAPPED\" | \"BOOTSTRAPPING\" | \"NOT_MOUNTED\" | \"MOUNTING\" | \"MOUNTED\" | \"UPDATING\" | \"UNMOUNTING\" | \"UNLOADING\" |\n\n```tsx\nimport { useEffect, useRef } from 'react';\nimport { MicroApp, type MicroAppType } from '@qiankunjs/react';\n\nexport default function Page() {\n  const microAppRef = useRef<MicroAppType>(undefined);\n\n  useEffect(() => {\n    // Get the child app load status\n    console.log(microAppRef.current?.getStatus());\n  }, []);\n\n  return <MicroApp name=\"app1\" entry=\"http://localhost:8000\" ref={microAppRef} />;\n}\n```\n","isInternal":false,"tokens":1241,"sizeBytes":5085},{"name":"README.md","path":"packages/ui-bindings/shared/README.md","rawUrl":"https://raw.githubusercontent.com/umijs/qiankun/HEAD/packages/ui-bindings/shared/README.md","title":"shared Documentation","category":"plugin-manifest","format":"markdown","content":"# @qiankunjs/ui-shared\n\nqiankun 的 React 和 Vue 组件共用的内部模块，封装微应用的挂载、更新、加载状态和错误处理，并提供共用类型。它连接组件层与 qiankun 运行时，不建议单独使用；React 和 Vue 主应用请分别使用 `@qiankunjs/react` 和 `@qiankunjs/vue`。\n\n文档：[生态概览](https://www.qiankunjs.com/zh-CN/ecosystem/)。\n\n## English\n\nInternal modules shared by qiankun's React and Vue components, covering micro-app mounting, updates, loading state, error handling, and shared types. This package connects the component layer to the qiankun runtime and is not recommended for standalone use; React and Vue host applications should use `@qiankunjs/react` and `@qiankunjs/vue`, respectively.\n\nDocumentation: [Ecosystem overview](https://www.qiankunjs.com/ecosystem/).\n","isInternal":false,"tokens":243,"sizeBytes":835},{"name":"README.md","path":"packages/ui-bindings/vue/README.md","rawUrl":"https://raw.githubusercontent.com/umijs/qiankun/HEAD/packages/ui-bindings/vue/README.md","title":"vue Documentation","category":"plugin-manifest","format":"markdown","content":"# qiankun vue binding\n\n## Usage\n\n```bash\nnpm i @qiankunjs/vue\n```\n\n## MicroApp Component\n\nLoad (or unload) a sub-application directly through the `<MicroApp />` component, which provides capabilities related to loading and error capturing:\n\n```vue\n<script setup>\nimport { MicroApp } from '@qiankunjs/vue';\n</script>\n<template>\n  <micro-app name=\"app1\" entry=\"http://localhost:8000\" />\n</template>\n```\n\nWhen enabling the sub-application loading animation or error capturing capabilities, an additional style class `wrapperClassName` is accepted by the sub-application. The rendered result is as follows:\n\n```vue\n<div :class=\"wrapperClassName\">\n  <MicroAppLoader :loading=\"loading\" />\n  <ErrorBoundary :error=\"e\" />\n  <MicroApp :class=\"className\" />\n</div>\n```\n\n### Loading Animation\n\nAfter enabling this feature, a loading animation will automatically be displayed while the sub-application is loading. When the sub-application finishes mounting and becomes in the MOUNTED state, the loading state ends, and the sub-application content is displayed.\n\nSimply pass `autoSetLoading` as a parameter:\n\n```vue\n<script setup>\nimport { MicroApp } from '@qiankunjs/vue';\n</script>\n<template>\n  <micro-app name=\"app1\" entry=\"http://localhost:8000\" autoSetLoading />\n</template>\n```\n\n#### Custom Loading Animation\n\nIf you wish to override the default loading animation style, you can customize the loading component by using the loader slot as the sub-application's loading animation.\n\n```vue\n<script setup>\nimport CustomLoader from '@/components/CustomLoader.vue';\nimport { MicroApp } from '@qiankunjs/vue';\n</script>\n<template>\n  <micro-app name=\"app1\" entry=\"http://localhost:8000\">\n    <template #loader=\"{ loading }\">\n      <custom-loader :loading=\"loading\" />\n    </template>\n  </micro-app>\n</template>\n```\n\nHere, `loading` is a boolean type parameter; when true, it indicates that it is still in the loading state, and when false, it indicates that the loading state has ended.\n\n### Error Capturing\n\nAfter enabling this feature, when the sub-application encounters an exception while loading, an error message will automatically be displayed. You can pass the `autoCaptureError` property to the sub-application to enable error capturing capabilities:\n\n```vue\n<script setup>\nimport { MicroApp } from '@qiankunjs/vue';\n</script>\n<template>\n  <micro-app name=\"app1\" entry=\"http://localhost:8000\" autoCaptureError />\n</template>\n```\n\n#### Custom Error Capturing\n\nIf you wish to override the default error capturing component style, you can customize the error capturing component for the sub-application using the errorBoundary slot:\n\n```vue\n<script setup>\nimport CustomErrorBoundary from '@/components/CustomErrorBoundary.vue';\nimport { MicroApp } from '@qiankunjs/vue';\n</script>\n<template>\n  <micro-app name=\"app1\" entry=\"http://localhost:8000\">\n    <template #error-boundary=\"{ error }\">\n      <custom-error-boundary :error=\"error\" />\n    </template>\n  </micro-app>\n</template>\n```\n\n### Component Properties\n\n| Property | Required | Description | Type | Default Value |\n| --- | --- | --- | --- | --- |\n| `name` | Yes | The name of the micro-application | `string` |  |\n| `entry` | Yes | The HTML address of the micro-application | `string` |  |\n| `settings` | No | qiankun configuration for this micro-application — sandbox, style isolation, fetch, … | `AppConfiguration` | `{ sandbox: true }` |\n| `lifeCycles` | No | qiankun lifecycle hooks for this micro-application. They are captured on the first load into a given container, so avoid closing over component state in them | `LifeCycles` | `undefined` |\n| `autoSetLoading` | No | Automatically set the loading status of the micro-application | `boolean` | `false` |\n| `autoCaptureError` | No | Automatically set error capturing for the micro-application | `boolean` | `false` |\n| `className` | No | The style class for the micro-application | `string` | `undefined` |\n| `wrapperClassName` | No | The style class wrapping the micro-application's loading and error components | `string` | `undefined` |\n| `appProps` | No | Properties passed to the sub-application | `Record<string, any>` | `undefined` |\n\n### Component Slots\n\n| Slot                               | Slot props             | Description          |\n| ---------------------------------- | ---------------------- | -------------------- |\n| `loader`                           | `{ loading: boolean }` | Loading state slot   |\n| `errorBoundary` / `error-boundary` | `{ error: Error }`     | Error capturing slot |\n\nVue does not normalize slot names the way it normalizes props, so both spellings of the error slot are accepted. Unlike the React binding, this component's wrapper is not positioned — give it a positioned class through `wrapperClassName` if your slot renders an overlay.\n","isInternal":false,"tokens":1103,"sizeBytes":4798}],"systemPromptSnippet":"<yakaai_skills repo=\"umijs/qiankun\">\n<!-- File: AGENTS.md (Tokens: ~3027 | Category: root-instruction) -->\n# QIANKUN PROJECT KNOWLEDGE BASE\n\n**Updated:** 2026-07-06 · **Commit:** dcc42ae4 · **Branch:** next (qiankun 3.0, active dev)\n\nQiankun is a micro-frontend framework built on [single-spa](https://github.com/single-spa/single-spa). v3 rewrites the runtime around **streaming HTML-entry loading**, a **Proxy-membrane JS sandbox**, and native **ESM-sandbox** execution. It is a pnpm monorepo built with Vite 8; `tsc` emits package declarations.\n\n> Requires Node `^22.15 || >=24`, `pnpm@11.13.1` (see `packageManager`). Never use npm/yarn at the root.\n\n## STRUCTURE\n\n```\nqiankun/\n├── packages/\n│   ├── qiankun/         # Facade: public APIs (register/loadMicroApp, start, prefetch) + loadApp orchestration\n│   ├── sandbox/         # JS isolation: Proxy membrane + Compartment  → packages/sandbox/AGENTS.md\n│   ├── loader/          # Streaming HTML-entry loader (writable-dom)  → packages/loader/AGENTS.md\n│   ├── shared/          # Transpilers, fetch-utils, module-resolver, ESM-sandbox engine → packages/shared/AGENTS.md\n│   ├── single-spa/      # Vendored single-spa fork (@qiankunjs/single-spa, upstream 7.0 @ ce0f925a) → packages/single-spa/README.md\n│   ├── ui-bindings/     # <MicroApp> components: react/ vue/ shared/ (dogfooded by examples/{main,vue-host})\n│   └── bundler-plugin/  # Webpack(4/5) + Vite plugins: mark entry script, fix output library\n├── skills/              # Agent skills (installed by users via `npx skills add umijs/qiankun`) — content, not code: no build/publish\n├── e2e/                 # Playwright, runs against BUILT dist → e2e/README.md\n├── examples/            # Runnable demos: two hosts (main = @qiankunjs/react, vue-host = @qiankunjs/vue) + react/vue/purehtml/webpack\n└── docs/                # VitePress site (docs/rfcs holds design RFCs)\n```\n\n## PROGRESSIVE DISCLOSURE — read the scoped doc before editing a package\n\n| Working in… | Read first |\n| --- | --- |\n| `packages/sandbox/**` | [`packages/sandbox/AGENTS.md`](packages/sandbox/AGENTS.md) — membrane, compartment, patchers, ESM globals |\n| `packages/loader/**` | [`packages/loader/AGENTS.md`](packages/loader/AGENTS.md) — streaming pipeline, head virtualization |\n| `packages/shared/**` | [`packages/shared/AGENTS.md`](packages/shared/AGENTS.md) — transpilers, fetch decorators, **esm-sandbox** |\n| `e2e/**` | [`e2e/README.md`](e2e/README.md) — Playwright layout, fixtures, anti-flake rules |\n\n## ARCHITECTURE (big picture)\n\n`loadApp` (`packages/qiankun/src/core/loadApp.ts`) is the orchestrator. Per micro-app it wires:\n\n1. **fetch** — decorated `window.fetch`: `makeFetchCacheable(makeFetchRetryable(makeFetchThrowable(fetch)))`.\n2. **sandbox** — `createSandbox()` builds a Proxy-membrane `window`/`document` view; patchers (dynamicAppend, timers, listeners, history) each return a `free()` cleanup called on unmount. Without a container it uses the JS-only preset; a container enables DOM containment.\n3. **loader** — `loadEntry(entry, container, opts)` streams the HTML entry through `writable-dom`, virtualizing `<head>` → `<qiankun-head>` and running each node through a `nodeTransformer`.\n4. **transpilers** (`shared/assets-transpilers`) rewrite each script/link/style node before it hits live DOM.\n\nMicro apps sharing one container element take FIFO turns for their DOM writes through the **container occupancy gate** (`core/containerOccupancy.ts`, two critical sections: load-phase streaming and mount→unmount; see `docs/rfcs/container-occupancy-gate.md`).\n\nTwo execution paths, chosen per script type:\n\n- **Classic** (`<script entry>`, UMD/global): source is wrapped and run via a **blob URL** scoped to the sandbox membrane. The app's export = `sandbox.latestSetProp` (the last global the entry script set).\n- **ESM** (`<script type=\"module\">`): handled by `EsmSandboxEngine` (`shared/esm-sandbox`). Modules are fetched, lexer-rewritten to route globals through the membrane, given synthetic specifiers via a dynamically injected **import map**, and evaluated in order. The engine also handles dynamic `import()`.\n\n**Style isolation** (`shared/assets-transpilers/style.ts` + `link.ts`) uses CSS `@scope` at runtime; external stylesheets become blob-`<link>`s so `@scope` can wrap them. Opt-in via `styleIsolation`.\n\nInternal dependency graph (never invert it):\n\n```\nqiankun → loader → sandbox → shared\n                   sandbox → shared\nqiankun → single-spa   (vendored fork; zero deps, bottom of the graph — must not import shared/sandbox/loader)\nui-bindings/{react,vue} → ui-bindings/shared → qiankun\n```\n\n## COMMANDS\n\n```bash\npnpm install                 # install all workspace deps\n\n# build (Vite 8 → package JavaScript; tsc → declarations)\npnpm run build               # build everything (packages + examples)\npnpm run build:packages      # build only packages/** (prereq for e2e & examples)\n\n# unit tests — vitest + happy-dom, aliased to src (NO build needed, see vitest.config.ts)\npnpm run test                            # all packages\npnpm --filter @qiankunjs/shared run test # one package\npnpm --filter @qiankunjs/sandbox exec vitest run path/to.test.ts   # single file\npnpm --filter @qiankunjs/shared  exec vitest run -t \"test name\"    # single test by name\n\n# e2e — Playwright against BUILT dist (see e2e/README.md)\npnpm run test:e2e            # build all + run chromium suite\n\n# lint / format / full CI gate\npnpm run eslint              # eslint packages/\npnpm run prettier:check      # prettier -c .\npnpm run ci                  # build + eslint + prettier:check (what CI runs)\n\n# dev\npnpm run start:example       # build packages + run all example apps in parallel\npnpm run docs:dev            # VitePress docs\n```\n\n## CONVENTIONS (enforced by ESLint flat config — `pnpm run eslint` will reject violations)\n\nTypeScript is strict + type-checked (`typescript-eslint`'s type-checked flat config):\n\n- **No `any`** — `no-explicit-any` auto-fixes to `unknown`. No `as any`, `@ts-ignore`, `@ts-expect-error`.\n- **Inline type imports** — `import { type Foo, bar }`, not `import type { Foo }` on its own line (`consistent-type-imports`/`consistent-type-exports` with `fixStyle: inline-type-imports`).\n- `no-unnecessary-condition` is an error — don't guard values the types prove are always truthy.\n- Unused vars/args must be prefixed `_` (`argsIgnorePattern: ^_`).\n- `array-simple`: `T[]` for simple, `Array<T>` for complex element types.\n- JS-wide: `no-else-return` (no `else` after `return`), `object-shorthand`.\n- Path alias `@qiankunjs/*` → `packages/*/src` (tsconfig + vitest); imports resolve to **source**, not dist.\n\nBuild/release:\n\n- Vite 8 builds browser package JavaScript as ESM+CJS, while `tsc` emits declarations. Package entry fields and subpath exports must point at the corresponding `dist` outputs.\n- TypeScript 7 is installed as `@typescript/native` and owns the `tsc` binary. The `typescript` dependency intentionally aliases `@typescript/typescript6` because TypeScript 7.0 has no programmatic API yet and tools such as `typescript-eslint` and `vue-tsc` still require that compatibility API.\n- Versioning via **changesets**, but changesets are **auto-derived from Conventional Commits** in CI (`scripts/generate-changesets.mjs`) — do **not** hand-write `.changeset/*.md`. Just land a well-formed conventional commit (`feat`/`fix`/`feat!`…); the release job maps changed files → packages and infers the bump. Each sub-package keeps its own `CHANGELOG.md` (changeset default, visible on npm); on publish `scripts/generate-release-notes.mjs` aggregates them into **one GitHub Release**, which can be polished after the fact via the `/release-changelog` skill (`gh release edit`). Full flow: `.changeset/README.md`.\n- Conventional commits enforced by commitlint (`feat:`, `fix:`, `feat(esm-sandbox):`, …).\n\n## DOCS & README CONVENTIONS\n\n- **Chinese-first.** The primary audience is Chinese. `docs/zh-CN/` is the source of truth: write it to a native-author standard — no translationese, no AI-flavored filler (「让我们」「值得注意的是」「强大的/无缝的」), no calqued English syntax. The English pages align to the Chinese semantics, not the other way around; pure zh wording fixes don't require touching en. Same contract for `README.zh-CN.md` ↔ `README.md`: keep both editions in sync whenever either changes.\n- **Mainland reachability is a hard constraint.** Google Fonts only via the `.cn` mirrors (`fonts.googleapis.cn`/`fonts.gstatic.cn`); never reference resources on domains unreachable from mainland China.\n- **Domains**: canonical docs domain is `https://www.qiankunjs.com` (zh under `/zh-CN/`); `qiankun.umijs.org` stays alive and 307s to it; v2 docs live at `v2.qiankun.umijs.org`; live examples at `examples.qiankunjs.com`. Old-site URLs are kept working via `docs/public/_redirects` — extend it when moving pages, never break inbound links.\n- **Consistency rules**: terminology never drifts (主应用/微应用/沙箱/隔离膜; qiankun always lowercase); link text in reference lists (\"相关内容\"/\"继续阅读\"/\"延伸阅读\") equals the target page's H1; API pages use 「函数签名」/「默认值为 X」; `guide/browser-support` is the single source of truth for browser requirements — link it, don't restate version numbers elsewhere.\n- Until 3.0 reaches npm `latest`, install commands must say `qiankun@rc`, and the site shows a version banner (layout-top slot in `docs/.vitepress/theme/index.js`; remove it together with `--vp-layout-top-height` once stable ships).\n- **README GitHub alerts**: put the `[!WARNING]`/`[!NOTE]` marker on its own line followed by a blank `>` line. Prettier's `proseWrap: never` re-joins plain soft breaks, which silently breaks alert rendering.\n- VitePress compiles every `.md` as a Vue SFC: bare `<Tag>` in prose/link text or `{{` in inline code breaks the build (fenced code blocks are safe).\n\n## ANTI-PATTERNS (this project)\n\n- **NEVER** put more than one `entry` script in an HTML entry — the loader throws `QiankunError`.\n- **ALWAYS** unmount micro-apps; `loadMicroApp`/patchers return handles/`free()` — leaks break remount & multi-instance.\n- In sandbox code, **never touch the real `window`/`document.head`** — go through the proxied view.\n- The vendored `loader/src/writable-dom/` fork accepts **generic designs only** — never qiankun-coupled semantics; caller bookkeeping goes through its `assetTransformer` callback (see `packages/loader/AGENTS.md`).\n- Don't invert the package dependency graph above (e.g. `shared` must not import `sandbox`).\n- e2e: never `waitForTimeout`; use web-first assertions; all ports come from `e2e/ports.ts`.\n- Performance work is judged in this order: **proportional impact first** (does the win scale with asset size / network / app scale — e.g. eliminating a duplicate download), **readability second** (never trade it away for small wins), **constant absolute savings last** (fixed single-digit-ms pipeline costs are noise in real apps and rarely worth landing). Beware: the ~50ms benchmark fixture makes fixed milliseconds masquerade as percentages — convert to absolute ms against realistic load times before deciding. And fix root causes at the source, not with per-call-site markers/exemptions downstream.\n\n## NOTES\n\n- Firefox doesn't support dynamically injected import maps → ESM-sandbox e2e tests are annotated `test.fail(firefox, …)` (expected failure, not skip). See `e2e/README.md`.\n- Design decisions live in `docs/rfcs/` (e.g. the ESM-sandbox RFC).\n- v3 roadmap: github.com/umijs/qiankun/discussions/1378.\n\n<!-- File: CLAUDE.md (Tokens: ~77 | Category: claude-rule) -->\n# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\nAll project guidance lives in `AGENTS.md` (imported below). It links out to package-scoped `packages/*/AGENTS.md` files for deep-dives — read those on demand when editing a given package.\n\n@AGENTS.md\n\n<!-- File: .claude/skills/release-changelog/SKILL.md (Tokens: ~836 | Category: anthropic-skill) -->\n---\nname: release-changelog\ndescription: Polish a published qiankun GitHub Release's aggregated notes into a bilingual (English then Chinese) write-up with an agent. Use after a release goes out — turning the mechanically-aggregated, per-package release notes into a clean, human-readable, English + Chinese write-up (highlights, de-duped cross-package lines, clearer wording) via `gh release edit`. Triggers include \"polish the release notes\", \"clean up the GitHub release\", \"improve the changelog for the latest release\".\n---\n\n# Release changelog\n\nQiankun releases are commit-driven: CI auto-derives changesets from Conventional Commits, changesets writes each sub-package's own `CHANGELOG.md` (so npm shows a per-package changelog), and on publish `scripts/generate-release-notes.mjs` aggregates those into **one GitHub Release** per version.\n\nThat aggregated Release is **mechanical** (raw per-package changelog lines). This skill polishes it into a human-readable, **bilingual** write-up — an English version on top, a Chinese version below — after the fact, directly on the GitHub Release, without touching any committed changelog. Read `.changeset/README.md` for the surrounding flow.\n\n## When to run\n\nAfter a release has been published (the \"Version Packages\" PR was merged and CI created the GitHub Release). Typically: \"polish the latest release notes\".\n\n## Steps\n\n1. **Find the release to polish** (default: the most recent one):\n\n   ```bash\n   gh release list --limit 5\n   gh release view <tag> --json tagName,name,body\n   ```\n\n   Confirm the target tag with the user if it's not obviously the latest.\n\n2. **Read the mechanical notes** from the release `body`. This is the raw material — the aggregated per-package changelog. Do not invent anything not present in it (cross-check against `git log <prev-tag>..<tag>` only to understand, not to add unlisted changes).\n\n3. **Polish** the notes, keeping the per-package structure so consumers still see each sub-package and its version. Polishing means:\n   - Lead with a short **Highlights** list (2–5 bullets) when the release has notable user-facing changes; skip it for small patch releases.\n   - Rewrite terse lines into clear, user-facing wording; keep the `(#PR)` reference at the end of each line.\n   - De-duplicate: a change that touched many packages is often better stated once under the facade `qiankun` section than repeated under every package.\n   - Drop noise (pure \"Updated dependencies\" internal bumps).\n\n4. **Produce a bilingual document** — the full **English** version first, then the full **简体中文** version, separated by a `---` rule. Keep the identical per-package structure and `(#PR)` references in both halves; translate the prose (highlights, descriptions), never the package names, versions, or PR numbers. Template:\n\n   ```markdown\n   <!-- English -->\n\n   ## Highlights\n\n   - ...\n\n   ## qiankun `<version>`\n\n   - ...\n\n   ---\n\n   <!-- 简体中文 -->\n\n   ## 更新亮点\n\n   - ...\n\n   ## qiankun `<version>`\n\n   - ...\n   ```\n\n5. **Show the user the proposed notes** and get approval.\n\n6. **Update the release** (does not re-tag or re-publish npm):\n\n   ```bash\n   gh release edit <tag> --notes-file <polished.md>\n   ```\n\n## Do not\n\n- Do not run `gh release create`, re-tag, or re-publish to npm — only edit the notes of an existing release.\n- Do not edit any `CHANGELOG.md` or `.changeset/*` — the per-package changelogs are the committed record; this skill only refines the GitHub Release prose.\n- Do not add changes that aren't in the mechanical notes / commit history.\n\n<!-- File: packages/loader/AGENTS.md (Tokens: ~1042 | Category: anthropic-skill) -->\n# @qiankunjs/loader\n\nStreaming HTML-entry loader built on a forked `writable-dom`. Depends on `@qiankunjs/sandbox` + `@qiankunjs/shared`.\n\n## STRUCTURE\n\n```\nloader/\n├── index.ts              # loadEntry() — main API\n├── TagTransformStream.ts # <head> → <qiankun-head> string-level replacement\n├── writable-dom/         # forked streaming DOM engine (incremental parse + blocking scripts)\n│   └── index.ts\n└── parser.ts             # static HTML parsing for prefetch\n```\n\n## WHERE TO LOOK\n\n| Task | File | Notes |\n| --- | --- | --- |\n| Load micro-app | `index.ts` | `loadEntry(entry, container, opts)`; `opts.compartment` is the single sandbox/module facade |\n| Head virtualization | `TagTransformStream.ts` | rewrites `<head>` tags at the string level before DOM insertion |\n| Streaming DOM | `writable-dom/index.ts` | incremental parsing, blocks on sync scripts/styles, preloads the rest |\n\n## LOADING PIPELINE\n\n```\n1. fetch(entry)                              (decorated fetch: cacheable/retryable/throwable)\n2. TextDecoderStream                         bytes → string\n3. TagTransformStream                        <head> → <qiankun-head>\n4. WritableDOMStream                         stream → live DOM\n   ├─ nodeTransformer(node) per node         → shared/assets-transpilers rewrites script/link/style\n   ├─ classic <script entry> → synchronous Compartment host-adapter rewrite, then blob-URL execution\n   ├─ <script type=\"module\"> → registered through the compartment module facade\n   └─ blocks on sync scripts; preloads other assets while blocked\n5. resolve app export                        classic: compartment.latestSetProp · esm: compartment.importDocumentModules()\n```\n\n## KEY PATTERNS\n\n### Script classification (`index.ts`)\n\n```typescript\nisExternalScript; // has src or data-src\nisEntryScript; // external + [entry]  → resolves the load promise (app's main export point)\nisDeferScript; // external + [defer]  → ordered via shared prepareDeferredQueue\n```\n\n- Exactly **one** `entry` script is allowed per HTML entry; a second one throws `QiankunError`.\n- Loader and asset-transpiler interfaces must not mention `EsmSandboxEngine` or the concrete `Sandbox` class; consume structural `CompartmentLoaderFacade`.\n- `transformClassicScript()` is a stable sandbox-owned host adapter because detached node transformation is synchronous. `evaluateScript()` schedules a separate script asynchronously and cannot replace it in the streaming pipeline.\n\n### Detached parsing\n\nNodes are parsed/transformed in a detached document first, then moved to live DOM — this prevents premature script execution before the transpiler has rewritten the node.\n\n### writable-dom fork discipline\n\n`writable-dom/` is vendored from marko-js/writable-dom and periodically re-synced; every deviation carries a `[qiankun]` comment so syncs can re-apply them mechanically. Changes there demand deliberate thought and must stay **generic**: general-purpose hooks or upstream bug fixes only — never qiankun-specific semantics coupled to other packages (no `@qiankunjs/*` imports, no sandbox marks, no downstream contract knowledge). The fork's one integration seam is the `assetTransformer` callback (every element passes through it right before insertion); caller bookkeeping belongs in the callback `loadEntry` provides (`index.ts`), on the caller's side of that seam. The loader itself carries no sandbox semantics either: a sandbox-provided `nodeTransformer` stamps its own output (e.g. the native-passthrough mark its patcher consumes) — the loader just routes every element through whatever transformer it was given.\n\n## ANTI-PATTERNS\n\n- **NEVER** include more than one `entry` script per HTML entry (throws `QiankunError`).\n- **NEVER** add qiankun-coupled logic inside `writable-dom/` — generic designs only; hang caller bookkeeping on the `assetTransformer` callback in `loadEntry` (see the fork discipline above).\n- **FIXME** (in code): non-standard HTML chunks that lack a `<head>` tag.\n\n## EXPORTS (`src/index.ts`)\n\n```typescript\nexport { loadEntry, type LoaderOpts } from './index';\n// LoaderOpts = { fetch, compartment?, nodeTransformer?, streamTransformer?, onDOMStreamSettled? } & BaseTranspilerOpts\n// onDOMStreamSettled: notified exactly once when the DOM-write phase is over (stream piped, errored,\n// or never started) — distinct from the returned promise, which can settle at the entry script's\n// onload while tail nodes are still streaming; qiankun's container gate keys its release on it\n```\n\n<!-- File: packages/sandbox/AGENTS.md (Tokens: ~2184 | Category: anthropic-skill) -->\n# @qiankunjs/sandbox\n\nJS isolation engine: a Compartment-shaped facade owns the Proxy **Membrane**, classic-script evaluator, and module hooks. DOM behavior lives in independently registered **IsolationPlugin** instances. Depends only on `@qiankunjs/shared`.\n\n## STRUCTURE\n\n```\nsandbox/\n├── core/\n│   ├── sandbox/          # createSandbox() + StandardSandbox preset + container protocol\n│   ├── membrane/         # Proxy wrapper for global (window/document) isolation\n│   ├── compartment/      # globals + module hooks + blob classic evaluation facade\n│   ├── globals.ts        # global property definitions\n│   └── esm-globals.ts    # esmDestructurableGlobals — globals the ESM engine may destructure/rebind\n├── patchers/             # public IsolationPlugin protocol + built-in plugin presets\n│   ├── dynamicAppend/    # appendChild/insertBefore interception → redirect to app container\n│   ├── windowListener.ts # event listener tracking\n│   ├── interval.ts       # timer tracking\n│   └── historyListener.ts\n└── consts.ts             # qiankunHeadTagName / qiankunBodyTagName, nativeGlobal, nativeDocument\n```\n\n## WHERE TO LOOK\n\n| Task | File | Notes |\n| --- | --- | --- |\n| Create sandbox | `core/sandbox/index.ts` | Builds a `StandardSandbox`, installs the JS-only or DOM preset, and returns mount/unmount/dispose |\n| Prepare container | `core/sandbox/container.ts` | Owns `<qiankun-head>`, `data-name`, style scope, and cleanup contracts |\n| Proxy logic | `core/membrane/index.ts` | Write → local target; Read → local → configured globals → host window |\n| Compartment facade | `core/compartment/index.ts` | Owns the membrane, module facade, and CSP-safe blob classic evaluation |\n| Plugin protocol | `patchers/types.ts` | Public `IsolationPlugin`, context, `Free`, and `Rebuild` contracts |\n| Built-in plugins | `patchers/index.ts` | Default plugins; `dynamicAppend` joins only with a container; user plugins append after these |\n| DOM interception | `patchers/dynamicAppend/forStandardSandbox.ts` | Redirects dynamic script/style/link to app container |\n| Side-effect cleanup | `patchers/*.ts` | Each patcher returns a `free()` called on unmount |\n| ESM globals contract | `core/esm-globals.ts` | Consumed by `shared/esm-sandbox` engine, passed as `globalsBaseSet` |\n\n## KEY PATTERNS\n\n### Membrane (Proxy)\n\n- **Writes** are trapped and stored on a local `target` object (the sandbox's own globals).\n- **Reads** check local target → configured globals → fall back to the real host window.\n- **Native rebinding**: `fetch`, `console`, etc. are rebound to the real receiver to avoid \"Illegal invocation\".\n- `latestSetProp` records the last global the entry script assigned — that's how the loader recovers a classic app's exported lifecycles when no explicit export exists.\n- **Non-transitive by design**: unlike full membranes (es-membrane, Salesforce near-membrane), only the global (and document view) entry point is proxied — objects crossing the boundary keep their raw identity (`===`/`instanceof` hold across it). This is qiankun's deliberate isolation stance (isolate writes, share identity, for DOM compatibility and performance), not a gap to fill. Do NOT wrap crossing objects with proxies. The Membrane is a private mechanism of `Compartment` — the spec has no membrane concept, which is exactly why it must stay behind the facade.\n- `incubatorContext` (the host window the view reads through to) is a qiankun host extension: the Compartment spec and ses isolate by absence and have no read-through concept. The name borrows the ShadowRealm proposal's official \"incubator realm\" term for the creating context.\n\n### Compartment surface and deliberate differences\n\n`new Compartment({ globals, modules, resolveHook, importHook, loadHook, transforms })` owns its `globalThis`. StandardSandbox adds qiankun self-references (`window`, `self`, `globalThis`, `top`, `parent`) in a second phase with `defineUnshadowableGlobals()`.\n\n- Deliberately absent: synchronous `evaluate`, `harden`, and `lockdown`.\n- qiankun host extensions: DOM globals, `evaluateScript`, the synchronous `transformClassicScript` streaming adapter, lifecycle and entry-discovery facades, module-document registration, `defineUnshadowableGlobals`, `dispose`, `incubatorContext` / `moduleHost`, and the IsolationPlugin protocol. The executable list lives in `COMPARTMENT_HOST_EXTENSIONS`.\n- `evaluateScript()` uses a native blob `<script>` path. Do not introduce `eval` or `new Function` as an implementation shortcut.\n- `transformClassicScript()` exists because loader node transformation must finish synchronously before the original node enters live DOM. Loader consumes it through structural `CompartmentLoaderFacade`; never restore a concrete-class or private WeakMap bridge.\n- The pre-alignment names `Endowments` and `addIntrinsics()` have been removed entirely (the package has no external users, so no compatibility aliases). Use `CompartmentGlobals` / `globals` and `defineUnshadowableGlobals()`.\n\nThe module engine in `@qiankunjs/shared` is an implementation detail behind `Compartment.import()`, `load()`, and `importDocumentModules()`. Cross-package consumers must depend on the structural Compartment facade, never `EsmSandboxEngine`.\n\n`createSandbox().dispose()` is the terminal owner cleanup: it frees active plugin effects, restores container protocol state, deactivates the sandbox, and idempotently disposes the Compartment. Initial load failures must call it without replacing the original loading error.\n\nFetch precedence in `createSandbox()` is \"more specific wins\": module sources use `compartmentOptions.moduleHost.fetch ?? fetch ?? native`, dynamic assets use `fetch ?? native`. This is the one exception to the rule that promoted top-level options beat `compartmentOptions`; qiankun relies on it to give module sources a separate response cache.\n\n`createSandbox(appName)` is the JS-only preset: interval, window-listener, and history cleanup are available without a container. Providing `container` enables dynamic DOM interception; `mount()` prepares standalone containers lazily (`provisionContainerHead`, default `true` — qiankun's loader passes `false` because the entry HTML materializes the head in-stream). The controller keeps **two views of one configured transformer**, split by call-site identity: the public `nodeTransformer` is the pipeline variant — its output is stamped for native passthrough (`core/nativePassthrough.ts`), so patched mount points insert it untouched and it never enters the dynamic ledger — while the dynamic-append patcher receives the bare variant through the plugin config, whose output must NOT carry the mark (re-inserted nodes depend on re-entering the pipeline for ledger bookkeeping).\n\n### IsolationPlugin / Free pattern\n\n```typescript\nconst plugin: IsolationPlugin = {\n  name: 'example',\n  bootstrap: (ctx) => installBeforeAppCode(ctx),\n  mount: async (ctx) => installForThisMount(ctx),\n};\n\nconst free = await plugin.mount?.(context);\nif (free) {\n  const rebuild = free(); // unmount: remove this plugin's side effects\n  await rebuild(container); // remount: restore recorded side effects\n}\n```\n\n- `bootstrap` runs synchronously before any application script is evaluated.\n- `mount` runs on every mount and may asynchronously produce its `Free`.\n- Built-in plugins run in preset order; user plugins run afterward in registration order.\n- Prototype-level coordination belongs in browser-realm shared state with refcounts; it is not per-app context state. Use a stable `Symbol.for(...)` slot on `nativeGlobal` when independently bundled qiankun copies must coordinate the same prototype patch.\n- Code under `patchers/**` may use only the public Compartment surface. ESLint forbids imports from membrane internals.\n\n### Realm-shared metadata\n\n- `dynamicAppend.sharedState` is stored on `nativeGlobal` under `Symbol.for('qiankun.dynamicAppend.sharedState')`, so multiple qiankun copies in the same browser realm share prototype/CSSOM refcounts and ownership metadata.\n- `sandboxConfigs` — per-Compartment dynamic-append config.\n- `elementConfigs` — which Compartment owns each dynamically appended DOM node.\n- `containerOwners` — which Compartment currently patches a micro-app container.\n\n## ANTI-PATTERNS\n\n- **NEVER** access the real `window` / `document.head` directly — always the proxied view.\n- **NEVER** import membrane internals from a plugin or expose `EsmSandboxEngine` in a cross-package contract.\n- **NEVER** move dynamicAppend/style/timer/listener behavior into Compartment itself; those are DOM host plugins.\n- **FIXME** (in code): indirect `eval` in the membrane can let System.js escape sandbox scope.\n- **FIXME**: the runtime-container global may miss monkey-patched append logic.\n\n## REPLACEMENT BOUNDARY\n\nIf a native Compartment becomes available, it may replace only globalThis virtualization, the evaluator, and the module graph. Document proxying, dynamic append interception, style isolation, timers/listeners cleanup, and history integration remain qiankun-owned DOM plugins because ECMAScript Compartment proposals do not define DOM behavior. Keep the plugin layer dependent only on the public Compartment surface so this boundary remains real.\n\n## EXPORTS (`src/index.ts`)\n\n```typescript\nexport * from './core/sandbox'; // createSandbox, prepareSandboxContainer, StandardSandbox, controller/options types\nexport * from './core/compartment'; // Compartment, options, compatibility lists\nexport * from './consts'; // qiankunHeadTagName, qiankunBodyTagName, nativeGlobal, nativeDocument\nexport { esmDestructurableGlobals } from './core/esm-globals';\nexport type { IsolationPlugin, IsolationPluginContext, Free, Rebuild } from './patchers';\n```\n\n<!-- File: packages/shared/AGENTS.md (Tokens: ~1624 | Category: anthropic-skill) -->\n# @qiankunjs/shared\n\nBase utilities shared by `loader`/`sandbox`/`qiankun`: asset transpilers, fetch decorators, module resolution, error reporting, and the **ESM-sandbox engine**. No internal `@qiankunjs/*` dependencies — keep it at the bottom of the graph (it must never import `sandbox` or `loader`).\n\n## STRUCTURE\n\n```\nshared/\n├── assets-transpilers/   # transform script/link/style DOM nodes for the micro-frontend runtime\n│   ├── index.ts          # transpileAssets(node, baseURI, opts) — dispatch by tagName\n│   ├── script.ts         # classic scripts → blob-URL wrapping, sandbox scope\n│   ├── link.ts           # external stylesheets → blob-<link> (enables @scope); preload/URL rewriting; cache\n│   └── style.ts          # inline <style> → CSS @scope rewriting (runtime style isolation)\n├── esm-sandbox/          # per-instance native-ESM execution engine (see below)\n├── fetch-utils/          # higher-order fetch decorators\n│   ├── makeFetchCacheable.ts   # two runtime-wide LRUs (assets 50 / modules 512), clones responses, prunes failures\n│   ├── makeFetchRetryable.ts   # automatic retries\n│   └── makeFetchThrowable.ts   # non-2xx → throw\n├── module-resolver/      # shared-dependency canonicalization (semver via <script type=\"dependencymap\">)\n├── reporter/             # QiankunError + logger (warn/…)\n├── deferred-queue/       # async task sequencing (defer-script ordering)\n├── common.ts / utils.ts  # Deferred, defineProperty, hasOwnProperty, keys, resolveUrl, …\n└── typings.d.ts\n```\n\n## ESM-SANDBOX (`esm-sandbox/`, the largest subsystem here)\n\n`EsmSandboxEngine` is the replaceable mechanism behind the sandbox package's Compartment module facade; it must not leak into loader or qiankun interfaces. It runs a micro-app's `<script type=\"module\">` graph inside the sandbox membrane without a bundler. Pipeline: resolve through `resolveHook` → obtain and memoize a descriptor from `modules` or `importHook` / `loadHook` → fetch/lexer-rewrite source when needed → give modules synthetic specifiers → inject an **import map** → evaluate in dependency order → keep dunder-globals (`__qk_track`) live on later global writes.\n\n| File | Responsibility |\n| --- | --- |\n| `engine.ts` | `EsmSandboxEngine` — module-graph orchestration, redeclaration probing, in-order eval, dynamic `import()` |\n| `types.ts` | Public structural module contracts: hooks, descriptors, `ModuleSource`, Compartment facade |\n| `lexer.ts` | CSP-safe `es-module-lexer/js` wrapper; preparation Promise is already resolved |\n| `rewrite.ts` | `rewriteModule`, `buildSyntheticSpecifier`, `esmInternalPrefix`, `runtimeModuleSubpath` |\n| `import-bindings.ts` | import statement binding analysis |\n| `identifier-scan.ts` | `scanReferencedGlobals`, `isLiveBindableDunderName` — which globals a module touches |\n| `import-map-registry.ts` | `injectImportMapEntries`, `resetImportMapRegistry` — the dynamic import map |\n| `instance-registry.ts` | Per-engine instance/view lookup used by generated runtime modules |\n| `vite-client-stub.ts` | `isViteClientUrl`, `viteClientStubSource` — stubs Vite dev-client HMR |\n| `source-map.ts` | rewritten-module source-map fixups |\n\n> ESM sandbox depends on **dynamically injected import maps** → unsupported on Firefox (see `e2e/README.md`).\n\n## WHERE TO LOOK\n\n| Task | File | Notes |\n| --- | --- | --- |\n| Transpile any asset node | `assets-transpilers/index.ts` | `transpileAssets()` — the single dispatch entry |\n| Classic script sandboxing | `assets-transpilers/script.ts` | blob-URL wrap + sandbox scope |\n| Style isolation | `assets-transpilers/style.ts` + `link.ts` | CSS `@scope` rewrite; external CSS → blob-`<link>` |\n| Fetch enhancements | `fetch-utils/*` | decorator chain (compose with `make*` HOFs) |\n| Shared dependency reuse | `module-resolver/index.ts` | `moduleResolver(url, container, head)` semver match |\n| ESM module execution | `esm-sandbox/engine.ts` | `EsmSandboxEngine` (see section above) |\n| Module facade contracts | `esm-sandbox/types.ts` | The only ESM mechanism types cross-package callers should consume |\n| Promise/util helpers | `utils.ts` | `Deferred`, `keys`, `defineProperty`, `hasOwnProperty` |\n\n## KEY PATTERNS\n\n### Module hooks and descriptors\n\n- `resolveHook(specifier, referrer)` synchronously returns a canonical full specifier.\n- A `modules` descriptor wins before `importHook`; descriptors may provide precompiled `ModuleSource`, a namespace, or a redirect specifier.\n- `importHook(fullSpecifier)` is Promise-memoized, including failures. Probe-driven rebuilds reuse its result and do not call the hook again, so hooks must be idempotent.\n- A precompiled `ModuleSource` is already linked/re-written and skips the default fetch and rewrite pipeline.\n- Redirect cycles must fail deterministically; namespace descriptors keep their object identity.\n\nThe engine may be imported by the sandbox implementation itself, but do not add it to `LoaderOpts`, asset-transpiler options, or other cross-package interfaces. Those use `CompartmentModuleFacade`.\n\n### Higher-order fetch (decorator composition)\n\n```typescript\n// makeFetchCacheable(fetch, cacheScope: 'assets' | 'modules' = 'assets')\nconst resourceFetch = makeFetchRetryable(makeFetchThrowable(fetch));\nconst enhancedFetch = makeFetchCacheable(resourceFetch); // entries, classic scripts, styles\nconst moduleSourceFetch = makeFetchCacheable(resourceFetch, 'modules'); // passed as moduleHost.fetch\n```\n\nEach `cacheScope` maps to one LRU shared by the whole runtime copy: `assets` holds 50 responses and `modules` holds 512, so a large module graph cannot evict entries and styles. Both key requests by canonical URL and effective `credentials`, cache in-flight Promises, clone responses per reader, and drop failed responses. `loadApp` passes the module-scoped fetch as `compartmentOptions.moduleHost.fetch`, which the sandbox prefers over the top-level `fetch` for module sources.\n\n### Deferred promise\n\n```typescript\nconst d = new Deferred<void>();\nd.resolve();\nawait d.promise;\n```\n\n### DOM as metadata store\n\n- `data-src` — original source URL of a transpiled node.\n- `data-consumed` — marks an already-processed element.\n\n## EXPORTS\n\n`src/index.ts` re-exports everything: `./assets-transpilers`, `./utils`, `./common`, `./module-resolver`, `./reporter`, `./esm-sandbox`, the three `fetch-utils/make*`, and `./deferred-queue`. Notable named exports: `transpileAssets`, the Compartment-shaped module contracts, the internal `EsmSandboxEngine` mechanism, `moduleResolver`, `makeFetchCacheable`, `makeFetchRetryable`, `makeFetchThrowable`, `Deferred`, `QiankunError`, `warn`.\n\n<!-- File: skills/qiankun/references/create-main-app.md (Tokens: ~588 | Category: anthropic-skill) -->\n# Create a main (host) app\n\nPrerequisites: the shared facts in [SKILL.md](../SKILL.md) — app name, framework, port, package versions.\n\n1. Scaffold the shell (any framework; below assumes React + TS on port `7099`) and install qiankun:\n\n   ```bash\n   pnpm create vite <main-app-name> --template react-ts\n   pnpm add qiankun@rc @qiankunjs/react@rc   # vue shell: @qiankunjs/vue@rc\n   ```\n\n   Pin the port in `vite.config.ts` (`server: { port: 7099, strictPort: true }`).\n\n2. Pick ONE loading style:\n\n   **`MicroApp` component (recommended for React/Vue shells)** — mounts on render, unmounts on component unmount, with built-in loading/error slots. Extra props are forwarded to the sub app (delivered via its `update` lifecycle on change):\n\n   ```tsx\n   import { MicroApp } from '@qiankunjs/react'; // same component name in @qiankunjs/vue\n\n   export default function SubAppPage() {\n     return <MicroApp name=\"<app-name>\" entry=\"//localhost:7101\" autoSetLoading />;\n   }\n   ```\n\n   **Router-driven registration (framework-agnostic)** — qiankun mounts/unmounts apps as the URL matches `activeRule`. Put this in the shell entry, once:\n\n   ```ts\n   import { registerMicroApps, start } from 'qiankun';\n\n   registerMicroApps([\n     {\n       name: '<app-name>',\n       entry: '//localhost:7101',\n       container: '#micro-app-container', // an element the shell always renders\n       activeRule: '/<app-name>',\n     },\n   ]);\n\n   start();\n   ```\n\n   For full manual control there is also `loadMicroApp({ name, entry, container }, configuration)` — it returns a handle; you own calling `.unmount()` when the app leaves.\n\n3. Per-app configuration (third argument of `loadMicroApp`, `configuration` prop of the `MicroApp` component, or per-app fields in `registerMicroApps`): `sandbox` defaults to on; add `styleIsolation: true` to scope the sub app's CSS with `@scope`.\n\n## Verify\n\n1. Run both dev servers, open the main app at `http://localhost:7099`, navigate to the sub app route — it must mount inside the shell with no console errors.\n2. Leave the sub app route — its DOM must be removed (unmount actually ran).\n3. The sub app must still render standalone at `http://localhost:7101` — see [create-micro-app.md](create-micro-app.md).\n\n<!-- File: skills/qiankun/references/create-micro-app.md (Tokens: ~1038 | Category: anthropic-skill) -->\n# Create a micro (sub) app, or convert an existing Vite app\n\nPrerequisites: the shared facts in [SKILL.md](../SKILL.md) — app name, framework, port, package versions.\n\nFor a **new** app, start at step 1. To **convert an existing Vite app**, skip to step 2 — the conversion is exactly steps 2–4 and touches nothing else (no changes to `index.html`, `App` components, or build scripts).\n\n1. Scaffold with the official Vite scaffolder (templates: `react-ts`, `react`, `vue-ts`, `vue`):\n\n   ```bash\n   pnpm create vite <app-name> --template react-ts\n   ```\n\n2. Add the bundler plugin:\n\n   ```bash\n   pnpm add -D @qiankunjs/bundler-plugin@rc\n   ```\n\n3. Edit `vite.config.ts` — register the plugin and pin the port:\n\n   ```ts\n   import { defineConfig } from 'vite';\n   import react from '@vitejs/plugin-react'; // vue: @vitejs/plugin-vue\n   import { qiankun } from '@qiankunjs/bundler-plugin/vite';\n\n   export default defineConfig({\n     plugins: [react(), qiankun()],\n     server: {\n       port: 7101,\n       strictPort: true,\n     },\n   });\n   ```\n\n4. Rewrite the entry to export qiankun lifecycles, using the template below for the app's framework. Replace `<app-name>` with the actual registered name in both templates.\n\n## React entry template (`src/main.tsx`)\n\n```tsx\nimport React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport App from './App';\nimport './index.css';\n\ndeclare global {\n  interface Window {\n    __POWERED_BY_QIANKUN__?: boolean;\n    [key: string]: unknown;\n  }\n}\n\nlet root: ReactDOM.Root | undefined;\n\nfunction render(props: { container?: Element } = {}) {\n  // when loaded by qiankun, resolve #root inside the host-provided container, not the top document\n  const container = props.container?.querySelector('#root') ?? document.getElementById('root');\n  if (!container) return;\n\n  root = ReactDOM.createRoot(container);\n  root.render(\n    <React.StrictMode>\n      <App />\n    </React.StrictMode>,\n  );\n}\n\nexport async function bootstrap() {}\n\nexport async function mount(props: { container?: Element }) {\n  render(props);\n}\n\n// the host re-delivers its props here whenever they change — react to them without remounting\nexport async function update(_props: Record<string, unknown>) {}\n\nexport async function unmount(_props: { container?: Element }) {\n  root?.unmount();\n  root = undefined;\n}\n\nif (window.__POWERED_BY_QIANKUN__) {\n  // classic-mode fallback: expose the lifecycles on window under the REGISTERED app name\n  window['<app-name>'] = { bootstrap, mount, update, unmount };\n} else {\n  render();\n}\n```\n\n## Vue entry template (`src/main.ts`)\n\n```ts\nimport { createApp, reactive } from 'vue';\nimport App from './App.vue';\nimport './style.css';\n\ndeclare global {\n  interface Window {\n    __POWERED_BY_QIANKUN__?: boolean;\n    [key: string]: unknown;\n  }\n}\n\nlet app: ReturnType<typeof createApp> | undefined;\n\n// props the host hands over — seeded on mount, kept current by `update`\nconst hostProps = reactive<Record<string, unknown>>({});\n\nfunction render(props: { container?: Element } = {}) {\n  const container = props.container?.querySelector('#app') ?? document.getElementById('app');\n  if (!container) return;\n\n  app = createApp(App, { hostProps });\n  app.mount(container);\n}\n\nexport async function bootstrap() {}\n\nexport async function mount(props: { container?: Element }) {\n  Object.assign(hostProps, props);\n  render(props);\n}\n\nexport async function update(props: Record<string, unknown>) {\n  Object.assign(hostProps, props);\n}\n\nexport async function unmount(_props: { container?: Element }) {\n  app?.unmount();\n  app = undefined;\n}\n\nif (window.__POWERED_BY_QIANKUN__) {\n  // classic-mode fallback: expose the lifecycles on window under the REGISTERED app name\n  window['<app-name>'] = { bootstrap, mount, update, unmount };\n} else {\n  render();\n}\n```\n\n## Verify\n\n1. `pnpm dev` in the sub app, open `http://localhost:7101` — it must render on its own (the non-qiankun branch of the entry).\n2. If a main app exists, run it too and confirm the sub app mounts inside it with no console errors — see the verification checklist in [create-main-app.md](create-main-app.md).\n\n<!-- File: skills/qiankun/SKILL.md (Tokens: ~807 | Category: anthropic-skill) -->\n---\nname: qiankun\ndescription: >-\n  Usage manual for building micro-frontend projects with qiankun 3.x. Use when the user wants to create a qiankun main (host) app or sub (micro) app, convert an existing Vite app into a qiankun micro app, or wire up micro-frontend loading with React/Vue.\n---\n\n# qiankun\n\nqiankun is a micro-frontend framework: a main (host) app loads sub (micro) apps from their HTML entries at runtime, each inside a JS sandbox, with opt-in CSS isolation. This skill is the agent-facing manual for **qiankun 3.x**.\n\n## Task routing\n\nRead only the reference file(s) the task at hand needs:\n\n| Task | Read |\n| --- | --- |\n| Create a micro (sub) app, or convert an existing Vite app | [references/create-micro-app.md](references/create-micro-app.md) |\n| Create a main (host) app | [references/create-main-app.md](references/create-main-app.md) |\n| Anything else — migration, style isolation, sandbox, debugging | https://qiankun.umijs.org and `examples/` in https://github.com/umijs/qiankun |\n\n## Before you start\n\nDetermine these, asking the user only when not inferable from context:\n\n1. **App type** — main (host/shell) or sub (micro app).\n2. **App name** — lowercase letters, numbers, hyphens. The name the host registers must exactly match the name the sub app uses in its classic-mode fallback.\n3. **Framework** — React or Vue; TypeScript or JavaScript. Templates in the references are TS; strip types for JS.\n4. **Dev port** — must be fixed and unique per app, because the host hard-references it in `entry`. Convention: main app `7099`, sub apps `7101`, `7102`, …\n\nPackage versions — until qiankun 3.0 stable ships, `rc` is the dist-tag for the core packages:\n\n| Package                               | Dist-tag | Goes in                                       |\n| ------------------------------------- | -------- | --------------------------------------------- |\n| `qiankun`                             | `rc`     | main app `dependencies`                       |\n| `@qiankunjs/react` / `@qiankunjs/vue` | `rc`     | main app `dependencies` (optional UI binding) |\n| `@qiankunjs/bundler-plugin`           | `rc`     | sub app `devDependencies`                     |\n\n## Key facts\n\nThese invariants shape every task below:\n\n- **Vite sub apps need no dedicated build mode.** qiankun 3 loads `<script type=\"module\">` natively through its ESM sandbox — regular `vite dev` / `vite build` output is qiankun-ready as-is.\n- The JS sandbox is **on by default**. CSS isolation is opt-in per app via `styleIsolation: true` (runtime CSS `@scope`).\n- A sub app must stay **runnable standalone**: when not loaded by qiankun (`window.__POWERED_BY_QIANKUN__` is undefined) it renders itself directly.\n- **Always unmount** what you mount. `loadMicroApp` returns a handle whose `.unmount()` must be called when the app leaves; the `MicroApp` component bindings do this automatically on component unmount.\n- Webpack sub apps use `@qiankunjs/bundler-plugin/webpack` instead of the Vite plugin; see the docs.\n\n## After any creation task\n\nVerify the result following the checklist at the end of the reference you used. If a browser tool is available, perform the checks yourself instead of asking the user to.\n\n<!-- File: packages/bundler-plugin/README.md (Tokens: ~625 | Category: plugin-manifest) -->\n# @qiankunjs/bundler-plugin\n\nBundler plugins for the [qiankun](https://github.com/umijs/qiankun) micro-frontend framework. Currently supports Webpack (4 & 5) and Vite (5+).\n\n## Installation\n\nUsing npm:\n\n```bash\nnpm install @qiankunjs/bundler-plugin --save-dev\n```\n\nOr using yarn:\n\n```bash\nyarn add @qiankunjs/bundler-plugin --dev\n```\n\n## Webpack Plugin\n\n### Features\n\n- Automatically sets the name and format of the output library\n- Ensures the uniqueness of the `jsonpFunction`/`chunkLoadingGlobal` name\n- Sets the global object to `window`, ensuring the library can run in the browser\n- Automatically adds an entry marker to the entry script tag in HTML\n- Supports both Webpack 4 and Webpack 5\n\n### Usage\n\nIn your `webpack.config.js` or other configuration files:\n\n```javascript\nconst { QiankunWebpackPlugin } = require('@qiankunjs/bundler-plugin');\n\nmodule.exports = {\n  plugins: [\n    new QiankunWebpackPlugin({\n      packageName: 'optionalPackageName',\n    }),\n  ],\n};\n```\n\n### Options\n\n- `packageName`: Specifies the name of the output library. If not provided, the name from `package.json` will be used.\n\n### Entry Script Detection\n\nThe plugin automatically detects and marks the entry script in your HTML:\n\n1. Uses webpack's `compilation.entrypoints` API to identify entry chunks\n2. For single entry builds: marks the main entry chunk's script\n3. For multi-entry builds: matches HTML filename to entrypoint name\n4. Falls back to the last script tag if automatic detection fails\n\n## Vite Plugin\n\nqiankun loads Vite apps natively through its ESM sandbox — no legacy/SystemJS transformation is required. The Vite plugin only takes care of the loading plumbing:\n\n### Features\n\n- Responds with permissive CORS headers from the dev and preview servers, so the main app can fetch the entry HTML and module graph cross-origin\n- Marks the entry module script with the `entry` attribute in the built HTML (same contract as the webpack plugin), so the qiankun loader picks the entry deterministically\n\n### Usage\n\nIn your `vite.config.ts`:\n\n```typescript\nimport { defineConfig } from 'vite';\nimport { qiankun } from '@qiankunjs/bundler-plugin/vite';\n\nexport default defineConfig({\n  plugins: [qiankun()],\n});\n```\n\nYour entry module just exports the qiankun lifecycles directly:\n\n```typescript\nexport async function bootstrap() {}\nexport async function mount(props) {\n  // render your app into props.container\n}\nexport async function unmount(props) {\n  // tear your app down\n}\n```\n\n## Contributing\n\nAny form of contribution is welcome! Please submit PRs or open issues for discussion.\n\n## License\n\nMIT\n\n<!-- File: packages/loader/README.md (Tokens: ~232 | Category: plugin-manifest) -->\n# @qiankunjs/loader\n\nqiankun 的流式 HTML 入口加载器，负责将微应用的 HTML 逐步写入容器，并通过沙箱提供的接口完成资源转换和脚本执行。它是 qiankun 加载流程的底层组件；业务应用建议使用 `qiankun` 的公开 API，无需单独安装本包。\n\n文档：[HTML 入口](https://www.qiankunjs.com/zh-CN/concepts/html-entry-loading)。\n\n## English\n\nThe streaming HTML-entry loader for qiankun. It incrementally writes a micro-app's HTML into its container and uses sandbox-provided interfaces for asset transformation and script execution. This package is a low-level part of qiankun's loading pipeline; application code should use the public `qiankun` APIs without installing it separately.\n\nDocumentation: [HTML entry](https://www.qiankunjs.com/concepts/html-entry-loading).\n\n<!-- File: packages/loader/src/writable-dom/README.md (Tokens: ~14 | Category: plugin-manifest) -->\n# Fork from https://github.com/marko-js/writable-dom\n\n<!-- File: packages/qiankun/README.md (Tokens: ~1028 | Category: plugin-manifest) -->\n# qiankun（乾坤）\n\n<p align=\"center\">\n  <a href=\"https://qiankun.umijs.org/\" target=\"_blank\" rel=\"noopener noreferrer\">\n    <img width=\"180\" src=\"https://gw.alipayobjects.com/zos/bmw-prod/8a74c1d3-16f3-4719-be63-15e467a68a24/km0cv8vn_w500_h500.png\" alt=\"qiankun logo\">\n  </a>\n</p>\nurl('')\n\n[![npm version](https://img.shields.io/npm/v/qiankun.svg?style=flat-square)](https://www.npmjs.com/package/qiankun) [![coverage](https://img.shields.io/codecov/c/github/umijs/qiankun.svg?style=flat-square)](https://codecov.io/gh/umijs/qiankun) [![npm downloads](https://img.shields.io/npm/dt/qiankun.svg?style=flat-square)](https://www.npmjs.com/package/qiankun) [![Build Status](https://img.shields.io/travis/umijs/qiankun.svg?style=flat-square)](https://travis-ci.com/umijs/qiankun)\n\n> In Chinese traditional culture `qian` means heaven and `kun` stands for earth, so `qiankun` is the universe.\n\nAn implementation of [Micro Frontends](https://micro-frontends.org/), based on [single-spa](https://github.com/CanopyTax/single-spa), but made it production-ready.\n\n## 🤔 Motivation\n\nAs we know what micro-frontends aims for:\n\n> Techniques, strategies and recipes for building a **modern web app** with **multiple teams** using **different JavaScript frameworks**. — [Micro Frontends](https://micro-frontends.org/)\n\nModularity is very important for large application. By breaking down a large system into individual sub-applications, we can achieve good divide-and-conquer between products and when necessary combination, especially for enterprise applications that usually involve multi-team collaboration. But if you're trying to implement such a micro frontends architecture system by yourself, you're likely to run into some tricky problems:\n\n- In what form do sub applications publish static resources?\n- How does the main application integrate individual sub-applications?\n- How do you ensure that sub-applications are independent of each other (development independent, deployment independent) and runtime isolated?\n- Performance issues? What about public dependencies?\n- And so on...\n\nAfter solving these common problems of micro frontends, we extracted the kernel of our solution after a lot of internal online application testing and polishing, and then named it `qiankun`.\n\n**Probably the most complete micro-frontends solution you ever met🧐.**\n\n## 📦 Installation\n\n```shell\n$ pnpm run add qiankun\n```\n\n## 📖 Documentation\n\nhttps://qiankun.umijs.org/\n\n## 💿 Getting started\n\nThis repo contains an `examples` folder with a sample Shell app and multiple mounted Micro FE apps. To run this app, first clone `qiankun`:\n\n```shell\n$ git clone https://github.com/umijs/qiankun.git\n$ cd qiankun\n```\n\nNow run the pnpm run scripts to install and run the examples project:\n\n```shell\n$ pnpm run install\n$ pnpm run examples:install\n$ pnpm run examples:start\n```\n\nVisit `http://localhost:7099`.\n\n![](./examples/example.gif)\n\n## :sparkles: Features\n\n- 📦 **Based On [single-spa](https://github.com/CanopyTax/single-spa)**\n- 📱 **Technology Agnostic**\n- 💪 **HTML Entry Access Mode**\n- 🛡 **Style Isolation**\n- 🧳 **JS Sandbox**\n- ⚡ **Prefetch Assets**\n- 🔌 **[Umi Plugin](https://github.com/umijs/plugins/tree/master/packages/plugin-qiankun) Integration**\n\n## 🎯 Roadmap\n\n- [x] Parcel apps integration (multiple sub apps displayed at the same time, but only one uses router at most)\n- [x] Communication development kits between master and sub apps\n- [ ] Custom side effects hijacker\n- [ ] Nested Microfrontends\n\n## ❓ FAQ\n\nhttps://qiankun.umijs.org/faq/\n\n## 👬 Community\n\nhttps://qiankun.umijs.org/#community\n\n## 🎁 Acknowledgements\n\n- [single-spa](https://github.com/CanopyTax/single-spa) What an awesome meta-framework for micro-frontends!\n- [import-html-entry](https://github.com/kuitos/import-html-entry/) An assets loader which supports html entry.\n\n<!-- File: packages/sandbox/README.md (Tokens: ~2026 | Category: plugin-manifest) -->\n# @qiankunjs/sandbox\n\n`@qiankunjs/sandbox` provides qiankun's browser sandbox as a standalone package. It can isolate classic-script and ESM global writes, and can optionally contain dynamic DOM and styles inside an application container. It does not require `qiankun` or `@qiankunjs/loader`.\n\n> This package is browser-only. It uses DOM APIs, blob URLs, and dynamically injected import maps; it does not support Node.js or SSR execution.\n\n## Choose an API layer\n\n| Need | API | What it provides |\n| --- | --- | --- |\n| JavaScript isolation only | `StandardSandbox` | A safe preset for classic scripts and ESM, including `window`/`self` self-references |\n| Low-level Compartment mechanics | `Compartment` | A Compartment-shaped global and module-hook facade for advanced hosts |\n| Browser containment and lifecycle | `createSandbox()` | `StandardSandbox` plus optional DOM/style containment, timer and listener cleanup, and isolation plugins |\n\nUse `StandardSandbox` for direct classic-script evaluation. A bare `Compartment` deliberately does not install a sandboxed `window` self-reference. Calling `Compartment.evaluateScript()` without defining one emits a development warning, but it does not change execution semantics: code such as `window.answer = 42` may write to the host window.\n\n## JavaScript isolation\n\n### Classic scripts\n\n```ts\nimport { StandardSandbox } from '@qiankunjs/sandbox';\n\nconst sandbox = new StandardSandbox('report-widget', {\n  tenantId: 'acme',\n});\n\nawait sandbox.evaluateScript(\n  `\n    window.reportState = { tenantId };\n  `,\n  { sourceURL: 'https://widgets.example/report.js' },\n);\n\nconsole.log(Reflect.get(sandbox.globalThis, 'reportState')); // { tenantId: 'acme' }\nconsole.log(Reflect.get(window, 'reportState')); // undefined\n\nsandbox.dispose();\n```\n\n`evaluateScript()` executes through a native blob script. It does not use `eval` or `new Function`.\n\n### ESM\n\n```ts\nconst sandbox = new StandardSandbox('report-module');\nconst namespace = await sandbox.import(new URL('./report-entry.js', document.baseURI).href);\n\nconsole.log(namespace);\nsandbox.dispose();\n```\n\nRelative module resolution defaults to `document.baseURI`. `StandardSandbox` accepts custom module tables and hooks through its fourth, Compartment-options argument; `createSandbox()` promotes `modules`, `resolveHook`, `importHook`, and `loadHook` to top-level options. See the [sandbox plugins guide](../../docs/cookbook/sandbox-plugins.md) for an advanced example.\n\n`createSandbox()` fetches dynamic assets and module sources with its top-level `fetch` option, which defaults to the native fetch. To give module sources their own transport, such as a separate response cache, set `compartmentOptions.moduleHost.fetch`. It is the only `compartmentOptions` field that takes precedence over a top-level option: the more specific module-source fetch wins when loading modules, while dynamic assets keep using the top-level `fetch`.\n\n## Full browser sandbox\n\nPass a container to `createSandbox()` to enable document virtualization and dynamic DOM interception. Set `styleIsolation: true` to scope application styles to that container.\n\n```ts\nimport { createSandbox } from '@qiankunjs/sandbox';\n\nconst container = document.querySelector<HTMLElement>('#widget')!;\nconst controller = createSandbox('report-widget', {\n  container,\n  styleIsolation: true,\n});\n\n// Mount before application code so timers and listeners are tracked.\nawait controller.mount();\n\nconst response = await fetch('/vendor/report-widget.js');\nconst source = await response.text();\nawait controller.instance.evaluateScript(source, {\n  sourceURL: response.url,\n});\n\n// Releases active plugin effects and keeps the controller available for remount.\nawait controller.unmount();\nawait controller.mount();\n\n// Terminal cleanup. A disposed controller cannot be mounted or evaluated again.\nawait controller.dispose();\n```\n\nWhen `container` is a function, it is resolved again for each mount. This supports hosts that replace the mount element between activations:\n\n```ts\nlet currentContainer = document.querySelector<HTMLElement>('#first')!;\n\nconst controller = createSandbox('report-widget', {\n  container: () => currentContainer,\n});\n\nawait controller.mount();\nawait controller.unmount();\n\ncurrentContainer = document.querySelector<HTMLElement>('#second')!;\nawait controller.mount();\n```\n\nCalling `createSandbox('name')` without a container creates the JS-only preset. Its timer, window-listener, and history plugins can be activated with `mount()` without providing an arbitrary DOM element, and an element passed to `mount()` is left untouched — container preparation belongs to the container-backed preset. `styleIsolation: true` requires a configured container.\n\n### Container preparation\n\n`createSandbox()` establishes its container contract automatically. Advanced hosts that compose the lower-level APIs can prepare a container directly:\n\n```ts\nimport { prepareSandboxContainer } from '@qiankunjs/sandbox';\n\nconst { styleIsolation, cleanup } = prepareSandboxContainer(container, 'report-widget');\n\nconsole.log(styleIsolation);\n// { appName: 'report-widget', scopeRoot: '[data-name=\"report-widget\"]' }\n\n// Later, release only the attributes and virtual head created by this call.\ncleanup();\n```\n\nThe helper sets `data-name`, ensures a `<qiankun-head>` mount point for the sandboxed `document.head`, and returns the matching style-isolation options. Cleanup is idempotent: it preserves an existing virtual head, restores a previous `data-name`, and does not overwrite a later host update to that attribute.\n\n## Default asset transformation\n\nThe default `nodeTransformer` is isolation-preserving. A classic `<script>` dynamically appended by sandboxed code is fetched, wrapped with the controller's classic-script transformer, and executed through a blob URL. Dynamic styles and stylesheet links go through the same pipeline, including style scoping when enabled.\n\nYou may provide a custom transformer. Returning each node unchanged is not a neutral optimization:\n\n```ts\nconst controller = createSandbox('trusted-widget', {\n  container,\n  nodeTransformer: (node) => node,\n});\n```\n\nAn identity transformer disables the default classic-script wrapping. A dynamically appended classic script can then execute against the host global. Use this override only when the host supplies equivalent isolation or fully trusts every script. The internal asset transpiler is intentionally not a public API; the supported extension point is `nodeTransformer`.\n\nFor a custom HTML loading pipeline, use `controller.nodeTransformer`. It is the fully configured transformer shared with dynamic DOM interception, including the current Compartment, fetch implementation, classic-script wrapper, and style-isolation options.\n\n## Lifecycle ownership\n\n- `mount(container?)` activates or rebuilds timer, listener, history, DOM, and user-plugin effects. The argument overrides the configured container for that mount.\n- `unmount()` releases active effects and records any state required for a later mount. It does not run an application's own unmount function or clear caller-owned DOM.\n- `dispose()` permanently releases plugin effects, generated module resources, global accessors, and container preparation. It is safe to call more than once.\n\nMount the controller before evaluating application code when its timers and listeners must participate in cleanup. Run the application's own teardown before `unmount()` or `dispose()`, and clear application DOM according to the host's ownership rules.\n\n## Isolation boundary\n\nThis package is a compatibility sandbox, not a security boundary for hostile code.\n\n- Writes to the sandbox global stay local to that sandbox.\n- Reads fall through to the host window unless a value is configured or shadowed.\n- The membrane is non-transitive: objects crossing the boundary retain their identity. `===`, `instanceof`, DOM nodes, events, and shared library instances continue to work.\n- Mutating a host object obtained through read-through also mutates that shared object. Only the global property write is isolated.\n- DOM containment redirects the sandboxed `document.head` and `document.body`; it does not create a separate browser realm or origin.\n\nUse an iframe, Worker, or another origin/callable boundary for untrusted code. See the [Compartment Alignment RFC](../../docs/rfcs/compartment-alignment.md) for the rationale behind the write-isolation and identity-sharing model.\n\n## Content Security Policy\n\nClassic and ESM execution do not require `'unsafe-eval'`. They do require the page's `script-src` policy to allow `blob:` URLs. The ESM engine also injects an inline `script[type=\"importmap\"]`, which must be permitted by the page's script policy. ESM and external classic assets are fetched first, so their origins must satisfy CORS and `connect-src` as well.\n\nStyle isolation can create blob stylesheet URLs; allow `blob:` in `style-src` when external styles are used. Dynamically created inline `<style>` elements remain subject to the page's normal nonce, hash, or inline-style policy.\n\n```http\nContent-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' blob:; style-src 'self' 'unsafe-inline' blob:; connect-src 'self' https://widgets.example\n```\n\nThis illustrative policy allows the runtime import map and a widget-created inline style. Tighten it to the assets and execution path the host actually uses. Do not add `'unsafe-eval'` for this package.\n\n## Further reading\n\n- [Standalone sandbox cookbook](../../docs/cookbook/standalone-sandbox.md)\n- [Extending sandbox isolation](../../docs/cookbook/sandbox-plugins.md)\n- [Standalone Sandbox RFC](../../docs/rfcs/standalone-sandbox.md)\n\n<!-- File: packages/shared/README.md (Tokens: ~253 | Category: plugin-manifest) -->\n# @qiankunjs/shared\n\nqiankun 内部共用的基础模块，为加载器、沙箱和运行时提供资源转换、请求处理、模块解析及 ESM 沙箱引擎等能力。它位于 qiankun 内部依赖关系的底层，不建议业务应用直接依赖；接入微应用请使用 `qiankun` 的公开 API。\n\n文档：[加载一个微应用实例](https://www.qiankunjs.com/zh-CN/concepts/architecture)。\n\n## English\n\nInternal foundation modules shared by qiankun's loader, sandbox, and runtime, including asset transformation, fetch utilities, module resolution, and the ESM sandbox engine. This package sits at the bottom of qiankun's internal dependency graph and is not recommended as a direct application dependency; use the public `qiankun` APIs to integrate micro-apps.\n\nDocumentation: [Loading a micro-app instance](https://www.qiankunjs.com/concepts/architecture).\n\n<!-- File: packages/single-spa/README.md (Tokens: ~1147 | Category: plugin-manifest) -->\n# @qiankunjs/single-spa\n\nqiankun's vendored fork of [single-spa](https://github.com/single-spa/single-spa), consumed by the `qiankun` package as its application/parcel lifecycle router. See the vendoring RFC in [umijs/qiankun#3168](https://github.com/umijs/qiankun/issues/3168) for the full rationale and plan.\n\n## Fork baseline\n\n- Upstream: `single-spa/single-spa`, `7.0` branch @ [`ce0f925a`](https://github.com/single-spa/single-spa/commit/ce0f925a40a81809c598de21b36ad79706222a73) (tag `v7.0.0-beta.13`, 2025-09-22).\n- Imported verbatim (`src/`, `spec/`, `node-spec/`, `LICENSE`), then naturalized to this monorepo's toolchain (Vite 8 build, `tsc` declarations, vitest). Upstream rollup/babel/jest configs were not carried over; the compile-time constants they injected (`__DEV__`, `__PROFILE__`, `process.env.BABEL_ENV`) are provided by `vite.library.config.ts` (build) and the root `vitest.config.ts` (tests).\n- The profile variant entry (`src/single-spa.profile.ts` + `src/devtools/profiler-api.ts`) is kept in the tree but not built or published yet; `__PROFILE__` is compiled to `false`.\n- Versioning is independent of upstream beta numbering and follows this monorepo's changesets flow.\n\n## Behavioral divergences from upstream\n\nMaintained deliberately; this list is the contract (keep it in sync when adding divergences):\n\n1. **The upstream `bootstrap` → `init` rename ([single-spa#1307](https://github.com/single-spa/single-spa/pull/1307), completed by [#1333](https://github.com/single-spa/single-spa/pull/1333)) is reverted wholesale — this fork speaks pure v6 vocabulary, with no `init` aliases.** qiankun's ecosystem was built on the bootstrap vocabulary and there was no reason to break it. Concretely:\n   - the `bootstrap` lifecycle is the only recognized name for applications and parcels (a v7-style `init` export is ignored, exactly as v6 ignored unknown fields);\n   - `parcel.bootstrapPromise`, `setBootstrapMaxTime` and the `timeouts.bootstrap` key are back; `initPromise`, `setInitMaxTime` and `timeouts.init` do not exist;\n   - the `NOT_BOOTSTRAPPED` / `BOOTSTRAPPING` statuses are restored, both as `AppOrParcelStatus` member names and as the raw `getStatus()` string values (v7's `NOT_INITIALIZED` / `INITIALIZING` strings do not exist in this fork);\n   - error message texts use the bootstrap vocabulary again;\n   - the vendored upstream specs were migrated back to the bootstrap vocabulary along with the sources.\n\n   If upstream ever ships a stable (non-beta) v7 that still carries the rename, re-evaluate aligning with it then — until that day this fork does not track it.\n\n## Invariant contracts (never break)\n\n- All `single-spa:*` window events (`single-spa:before-routing-event`, `single-spa:routing-event`, `single-spa:no-app-change`, `single-spa:first-mount`, …) — qiankun and the wider ecosystem listen to them.\n- The `window.__SINGLE_SPA_DEVTOOLS__` hook (single-spa-inspector support).\n- Public API and type signatures stay 1:1 with the upstream baseline during phase one, except for the bootstrap-naming revert above — qiankun-observable behavior stays 1:1 with what its ecosystem shipped against, which is the higher-priority contract.\n- Zero runtime dependencies; this package sits at the bottom of the workspace dependency graph and must not import `@qiankunjs/shared`, `@qiankunjs/sandbox` or `@qiankunjs/loader`.\n\n## Known semantics now owned by this fork\n\n- `urlRerouteOnly` (documented at length in qiankun's `docs/api/start.md`) — qiankun passes it through untouched; the implementation and its semantics live here now.\n- **Single-instance assumption**: single-spa is a stateful routing singleton. Loading both the ESM and CJS builds in one page (or mixing this fork with a separately installed npm `single-spa`) silently creates two independent routers — two history patches, two event streams, two `started` flags. Don't.\n\n## Upstream sync\n\nExpected cadence ≈ zero (upstream is dormant). If a fix is worth absorbing:\n\n```bash\ngit remote add single-spa-upstream https://github.com/single-spa/single-spa.git\ngit fetch single-spa-upstream 7.0\ngit log ce0f925a..single-spa-upstream/7.0 --oneline   # review the delta\n# apply selected commits manually onto packages/single-spa/src/ (path prefixes differ)\n```\n\nUpdate the baseline note above after absorbing anything; on conflict with the divergence list, the divergence list wins.\n\n## License\n\nMIT — upstream's [LICENSE](./LICENSE) is preserved.\n\n<!-- File: packages/ui-bindings/react/README.md (Tokens: ~1241 | Category: plugin-manifest) -->\n# qiankun ui binding for react\n\n## Usage\n\n```bash\nnpm i @qiankunjs/react\n```\n\n## MicroApp component\n\nLoad (or unload) child apps directly through the `<MicroApp/>` component, which provides loading and error catching-related capabilities:\n\n```tsx\nimport { MicroApp } from '@qiankunjs/react';\n\nexport default function Page() {\n  return <MicroApp name=\"app1\" entry=\"http://localhost:8000\" />;\n}\n```\n\nWhen the sub-app loading animation or error capture capability is enabled, the sub-app accepts an additional style class `wrapperClassName`, and the rendered result is as follows:\n\n```tsx\n<div style={{ position: 'relative' }} className={`${wrapperClassName} qiankun-micro-app-wrapper`}>\n  <MicroApp className={`${className} qiankun-micro-app-container`} />\n  <MicroAppLoader loading={loading} />\n  <ErrorBoundary error={e} />\n</div>\n```\n\nThe container is rendered first so its position never shifts as the loader and error slots come and go — qiankun keys its per-container caches on that position. Because the slots come after it, they paint above the micro app without needing a `z-index`.\n\n### Load animation\n\nWhen this capability is enabled, loading animations are automatically displayed when child apps are loading. When the sub-application is mounted and changes to the `MOUNTED` state, the loading status ends and the sub-application content is displayed.\n\nJust pass `autoSetLoading` as a parameter:\n\n```tsx\nimport { MicroApp } from '@qiankunjs/react';\n\nexport default function Page() {\n  return <MicroApp name=\"app1\" entry=\"http://localhost:8000\" autoSetLoading />;\n}\n```\n\n#### Custom loading animation\n\nIf you want to override the default loading animation style, you can set a custom loading component `loader` as the loading animation for the child app.\n\n```tsx\nimport CustomLoader from '@/components/CustomLoader';\nimport { MicroApp } from '@qiankunjs/react';\n\nexport default function Page() {\n  return (\n    <MicroApp name=\"app1\" entry=\"http://localhost:8000\" loader={(loading) => <CustomLoader loading={loading} />} />\n  );\n}\n```\n\nwhere `loading` is the `boolean` type parameter, `true` indicates that the loading state is still being loaded, and `false` indicates that the loading state has ended.\n\n### Error catching\n\nWhen this capability is enabled, an error message is automatically displayed when a child app loads unexpectedly. You can pass the `autoCaptureError` property to the sub-app to enable sub-app error capture capabilities:\n\n```tsx\nimport { MicroApp } from '@qiankunjs/react';\n\nexport default function Page() {\n  return <MicroApp name=\"app1\" entry=\"http://localhost:8000\" autoCaptureError />;\n}\n```\n\n#### Custom error capture\n\nIf you want to override the default error capture component style, you can set a custom component `errorBoundary` as the error capture component for the child app:\n\n```tsx\nimport CustomErrorBoundary from '@/components/CustomErrorBoundary';\nimport { MicroApp } from '@qiankunjs/react';\n\nexport default function Page() {\n  return (\n    <MicroApp\n      name=\"app1\"\n      entry=\"http://localhost:8000\"\n      errorBoundary={(error) => <CustomErrorBoundary error={error} />}\n    />\n  );\n}\n```\n\n### Component Props\n\n| Name | Required | Description | Type | Default |\n| --- | --- | --- | --- | --- |\n| `name` | yes | The name of the microapp | `string` |\n| `entry` | yes | The HTML address of the microapp | `string` |\n| `settings` | no | qiankun configuration for this microapp — sandbox, style isolation, fetch, … | `AppConfiguration` | `undefined` |\n| `lifeCycles` | no | qiankun lifecycle hooks for this microapp. They are captured on the first load into a given container, so avoid closing over component state in them | `LifeCycles` | `undefined` |\n| `autoSetLoading` | no | Automatically set the loading state of your microapp | `boolean` | `false` |\n| `loader` | no | Custom microapps load state components | `(loading) => React.ReactNode` | `undefined` |\n| `autoCaptureError` | no | Automatically set up error capture for microapps | `boolean` | `false` |\n| `errorBoundary` | no | Custom microapp error capture component | `(error: Error) => React.ReactNode` | `undefined` |\n| `className` | no | The style class for the microapp | `string` | `undefined` |\n| `wrapperClassName` | no | Wrap the microapp loading component, error capture component, and microapp's style classes, and are only valid when the load component or error capture component is enabled | `string` | `undefined` |\n\n## Get the child app load status\n\nThe loading status includes: \"NOT_LOADED\" | \"LOADING_SOURCE_CODE\" | \"NOT_BOOTSTRAPPED\" | \"BOOTSTRAPPING\" | \"NOT_MOUNTED\" | \"MOUNTING\" | \"MOUNTED\" | \"UPDATING\" | \"UNMOUNTING\" | \"UNLOADING\" |\n\n```tsx\nimport { useEffect, useRef } from 'react';\nimport { MicroApp, type MicroAppType } from '@qiankunjs/react';\n\nexport default function Page() {\n  const microAppRef = useRef<MicroAppType>(undefined);\n\n  useEffect(() => {\n    // Get the child app load status\n    console.log(microAppRef.current?.getStatus());\n  }, []);\n\n  return <MicroApp name=\"app1\" entry=\"http://localhost:8000\" ref={microAppRef} />;\n}\n```\n\n<!-- File: packages/ui-bindings/shared/README.md (Tokens: ~243 | Category: plugin-manifest) -->\n# @qiankunjs/ui-shared\n\nqiankun 的 React 和 Vue 组件共用的内部模块，封装微应用的挂载、更新、加载状态和错误处理，并提供共用类型。它连接组件层与 qiankun 运行时，不建议单独使用；React 和 Vue 主应用请分别使用 `@qiankunjs/react` 和 `@qiankunjs/vue`。\n\n文档：[生态概览](https://www.qiankunjs.com/zh-CN/ecosystem/)。\n\n## English\n\nInternal modules shared by qiankun's React and Vue components, covering micro-app mounting, updates, loading state, error handling, and shared types. This package connects the component layer to the qiankun runtime and is not recommended for standalone use; React and Vue host applications should use `@qiankunjs/react` and `@qiankunjs/vue`, respectively.\n\nDocumentation: [Ecosystem overview](https://www.qiankunjs.com/ecosystem/).\n\n<!-- File: packages/ui-bindings/vue/README.md (Tokens: ~1103 | Category: plugin-manifest) -->\n# qiankun vue binding\n\n## Usage\n\n```bash\nnpm i @qiankunjs/vue\n```\n\n## MicroApp Component\n\nLoad (or unload) a sub-application directly through the `<MicroApp />` component, which provides capabilities related to loading and error capturing:\n\n```vue\n<script setup>\nimport { MicroApp } from '@qiankunjs/vue';\n</script>\n<template>\n  <micro-app name=\"app1\" entry=\"http://localhost:8000\" />\n</template>\n```\n\nWhen enabling the sub-application loading animation or error capturing capabilities, an additional style class `wrapperClassName` is accepted by the sub-application. The rendered result is as follows:\n\n```vue\n<div :class=\"wrapperClassName\">\n  <MicroAppLoader :loading=\"loading\" />\n  <ErrorBoundary :error=\"e\" />\n  <MicroApp :class=\"className\" />\n</div>\n```\n\n### Loading Animation\n\nAfter enabling this feature, a loading animation will automatically be displayed while the sub-application is loading. When the sub-application finishes mounting and becomes in the MOUNTED state, the loading state ends, and the sub-application content is displayed.\n\nSimply pass `autoSetLoading` as a parameter:\n\n```vue\n<script setup>\nimport { MicroApp } from '@qiankunjs/vue';\n</script>\n<template>\n  <micro-app name=\"app1\" entry=\"http://localhost:8000\" autoSetLoading />\n</template>\n```\n\n#### Custom Loading Animation\n\nIf you wish to override the default loading animation style, you can customize the loading component by using the loader slot as the sub-application's loading animation.\n\n```vue\n<script setup>\nimport CustomLoader from '@/components/CustomLoader.vue';\nimport { MicroApp } from '@qiankunjs/vue';\n</script>\n<template>\n  <micro-app name=\"app1\" entry=\"http://localhost:8000\">\n    <template #loader=\"{ loading }\">\n      <custom-loader :loading=\"loading\" />\n    </template>\n  </micro-app>\n</template>\n```\n\nHere, `loading` is a boolean type parameter; when true, it indicates that it is still in the loading state, and when false, it indicates that the loading state has ended.\n\n### Error Capturing\n\nAfter enabling this feature, when the sub-application encounters an exception while loading, an error message will automatically be displayed. You can pass the `autoCaptureError` property to the sub-application to enable error capturing capabilities:\n\n```vue\n<script setup>\nimport { MicroApp } from '@qiankunjs/vue';\n</script>\n<template>\n  <micro-app name=\"app1\" entry=\"http://localhost:8000\" autoCaptureError />\n</template>\n```\n\n#### Custom Error Capturing\n\nIf you wish to override the default error capturing component style, you can customize the error capturing component for the sub-application using the errorBoundary slot:\n\n```vue\n<script setup>\nimport CustomErrorBoundary from '@/components/CustomErrorBoundary.vue';\nimport { MicroApp } from '@qiankunjs/vue';\n</script>\n<template>\n  <micro-app name=\"app1\" entry=\"http://localhost:8000\">\n    <template #error-boundary=\"{ error }\">\n      <custom-error-boundary :error=\"error\" />\n    </template>\n  </micro-app>\n</template>\n```\n\n### Component Properties\n\n| Property | Required | Description | Type | Default Value |\n| --- | --- | --- | --- | --- |\n| `name` | Yes | The name of the micro-application | `string` |  |\n| `entry` | Yes | The HTML address of the micro-application | `string` |  |\n| `settings` | No | qiankun configuration for this micro-application — sandbox, style isolation, fetch, … | `AppConfiguration` | `{ sandbox: true }` |\n| `lifeCycles` | No | qiankun lifecycle hooks for this micro-application. They are captured on the first load into a given container, so avoid closing over component state in them | `LifeCycles` | `undefined` |\n| `autoSetLoading` | No | Automatically set the loading status of the micro-application | `boolean` | `false` |\n| `autoCaptureError` | No | Automatically set error capturing for the micro-application | `boolean` | `false` |\n| `className` | No | The style class for the micro-application | `string` | `undefined` |\n| `wrapperClassName` | No | The style class wrapping the micro-application's loading and error components | `string` | `undefined` |\n| `appProps` | No | Properties passed to the sub-application | `Record<string, any>` | `undefined` |\n\n### Component Slots\n\n| Slot                               | Slot props             | Description          |\n| ---------------------------------- | ---------------------- | -------------------- |\n| `loader`                           | `{ loading: boolean }` | Loading state slot   |\n| `errorBoundary` / `error-boundary` | `{ error: Error }`     | Error capturing slot |\n\nVue does not normalize slot names the way it normalizes props, so both spellings of the error slot are accepted. Unlike the React binding, this component's wrapper is not positioned — give it a positioned class through `wrapperClassName` if your slot renders an overlay.\n</yakaai_skills>"}