Skip to content

Commit febec37

Browse files
feat: add web-fragments skill
Skill for building, embedding, debugging, and deploying micro-frontends with the Web Fragments framework (web-fragments npm package). - SKILL.md hub: mental model, exact v0.8.x API, workflow, gotchas (progressive disclosure, token-optimized) - references/: api-reference, host-integration (per-framework middleware), fragment-authoring, troubleshooting, css-and-styling, csp-and-iframe, piercing-and-performance, frameworks-astro - scripts/: scaffold-fragment.mjs (generate a Vite fragment + gateway snippet), doctor.mjs (static-check a host/fragment project for v0.8.x gotchas) - scripts/experiments/: reproducible Playwright experiments backing the verified CSS-isolation and Astro findings Knowledge verified against the library source and Playwright; includes the head-style-accumulation bug filed upstream as web-fragments#297 and a verified smooth-transition-without-ClientRouter workaround.
1 parent 5b296b2 commit febec37

32 files changed

Lines changed: 2111 additions & 0 deletions

skills/web-fragments/SKILL.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
---
2+
name: web-fragments
3+
description: >-
4+
Build, embed, debug, and deploy micro-frontends with the Web Fragments framework
5+
(the `web-fragments` npm package by web-fragments.dev / Cloudflare). Use whenever the user
6+
works with web-fragments, the `<web-fragment>` element, `FragmentGateway`,
7+
`registerFragment`, `getWebMiddleware` / `getNodeMiddleware`, fragment "piercing", or the
8+
"reframed" iframe isolation — and more broadly when they want to incrementally migrate,
9+
decompose, or compose a web frontend from independently deployed micro-frontends that
10+
share one DOM, even if they don't name the library. Covers authoring a fragment, wiring it
11+
into a host app, gateway/route-pattern config, styling across the shadow boundary, and
12+
troubleshooting hydration/asset/type errors. Prefer this skill over guessing the API — the
13+
framework is beta (v0.8.x) and several public docs are stale.
14+
---
15+
16+
# Web Fragments
17+
18+
Framework-/platform-agnostic micro-frontend architecture. Each micro-frontend's client JS
19+
runs in an isolated context (a hidden iframe = *reframed*) while all fragments **share one
20+
DOM, navigation, and history** — "containers for web frontends." Beta v0.8.x, in production
21+
at Cloudflare.
22+
23+
**Don't read `node_modules/web-fragments/` source for the API — it's captured here and in
24+
`references/`.** web-fragments.dev docs are partly stale; when they disagree with this skill,
25+
trust the skill (verify via the package's `exports` map in `node_modules/web-fragments/package.json`).
26+
27+
## Mental model: 3 pieces
28+
29+
1. **Fragment app** — a *standalone* app (any stack) at an HTTP endpoint serving its own
30+
HTML + assets; doesn't know it's a fragment. → `references/fragment-authoring.md`
31+
2. **Host (shell)** — the existing app that embeds fragments: calls `initializeWebFragments()`
32+
once + places `<web-fragment fragment-id="…">`. → `references/host-integration.md`
33+
3. **Gateway****mandatory** middleware on the host server (issue #278): proxies fragment
34+
requests onto the host's single origin and *pierces* SSR markup into the shell.
35+
36+
Flow: browser → host origin → gateway matches `routePatterns` → proxies to fragment
37+
`endpoint` → response composed into the host page.
38+
39+
## Public API (v0.8.x) — exactly three entry points
40+
41+
There is **no** `web-fragments/middleware` entry point (a stale doc claims one).
42+
43+
| Import | Exports |
44+
|---|---|
45+
| `web-fragments` | `initializeWebFragments()`, `WebFragment`, `WebFragmentHost` |
46+
| `web-fragments/gateway` | `FragmentGateway`, `getWebMiddleware`, types `FragmentConfig`, `FragmentMiddlewareOptions` |
47+
| `web-fragments/gateway/node` | `getNodeMiddleware` |
48+
49+
**Client (host bootstrap):**
50+
```ts
51+
import { initializeWebFragments } from 'web-fragments';
52+
initializeWebFragments(); // defines the custom elements; call as early as possible
53+
```
54+
```html
55+
<web-fragment fragment-id="party-button"></web-fragment>
56+
```
57+
`fragment-id` is required (throws without it); `src` is optional. `<web-fragment-host>` is
58+
internal — you rarely author it.
59+
60+
**Gateway (host server):**
61+
```ts
62+
import { FragmentGateway } from 'web-fragments/gateway';
63+
const gateway = new FragmentGateway(/* { piercingStyles?: string } */);
64+
gateway.registerFragment({ // method is registerFragment, NOT register
65+
fragmentId: 'party-button', // required; must match the element's fragment-id
66+
endpoint: 'https://party-button.example.dev', // required; URL string OR a fetch-compatible fn
67+
routePatterns: ['/__wf/party-button/:_*', '/'], // required (path-to-regexp v6): assets + host route(s)
68+
piercing: true, // optional, default true; false = client-only
69+
// piercingClassNames, forwardFragmentHeaders, iframeHeaders, onSsrFetchError — see api-reference
70+
});
71+
```
72+
Full `FragmentConfig` + deprecated aliases (`upstream``endpoint`,
73+
`prePiercingClassNames``piercingClassNames`): `references/api-reference.md`.
74+
75+
**Middleware (pick ONE per host runtime):**
76+
```ts
77+
import { getWebMiddleware } from 'web-fragments/gateway'; // Web/Fetch: CF, Vercel, Netlify, Hono, SW
78+
const mw = getWebMiddleware(gateway, { mode: 'development' }); // 'production' | 'development'
79+
80+
import { getNodeMiddleware } from 'web-fragments/gateway/node'; // Node: Express / Connect
81+
app.use(getNodeMiddleware(gateway));
82+
```
83+
Per-framework wiring: `references/host-integration.md`.
84+
85+
## Workflow (add a fragment to an app)
86+
87+
1. **Fragment app** serves its own HTML+assets, emitting assets under a **unique** path
88+
(e.g. Vite `build.assetsDir: "__wf/<id>/"`) that lines up with the asset `routePattern`.
89+
2. **Bootstrap** the host client: `initializeWebFragments()` at the earliest entry.
90+
3. **Place** `<web-fragment fragment-id="…">` at the chosen host route.
91+
4. **Register**: `new FragmentGateway()` + `registerFragment(...)` with two patterns (assets +
92+
host route). The host-route pattern MUST match where the element lives (step 3).
93+
5. **Install** the middleware matching the runtime (Web vs Node).
94+
6. **Verify** (below), then deploy — fragment and host ship independently.
95+
96+
## Verify
97+
98+
Fragment renders as **nested shadow roots**: `<web-fragment>` ▸ shadowRoot ▸
99+
`<web-fragment-host>` ▸ shadowRoot ▸ `<wf-document>`, plus a hidden `<iframe name="wf:<id>">`
100+
running its scripts. In dev tools: fragment DOM is in the main document but inside a shadow
101+
root (style isolation); a `wf:<id>` context exists; removing the element tears it down;
102+
asset requests hit `/__wf/<id>/…` on the **host** origin (proxied).
103+
104+
Automated: `node scripts/doctor.mjs <project-dir>` flags the common wiring mistakes (wrong
105+
import path, `register` vs `registerFragment`, asset/routePattern mismatch, missing
106+
middleware). Use it whenever a fragment "doesn't show up" or assets 404.
107+
108+
## Bundled resources
109+
110+
Load only the reference matching the task:
111+
- `references/api-reference.md` — every export, full `FragmentConfig`/options, deprecations. Writing/reviewing gateway/element code.
112+
- `references/host-integration.md` — middleware wiring per framework (Express, CF Pages/Workers, Vercel, Netlify, Hono, Angular, Next.js, Vite SPA).
113+
- `references/fragment-authoring.md` — build a fragment from scratch: structure, Vite `assetsDir`, route patterns, deploy.
114+
- `references/troubleshooting.md` — known issues/gotchas (hydration, asset truncation, nodenext types, Angular htmlrewriter, style accumulation). Read FIRST when broken.
115+
- `references/css-and-styling.md`**verified** style behavior across the shadow boundary (`:root` vs `:host`, `@layer`, what inherits/leaks).
116+
- `references/csp-and-iframe.md` — CSP / `X-Frame-Options` / `frame-ancestors` so the gateway's hidden iframe isn't blocked. Read when a fragment silently fails to load or you're setting security headers.
117+
- `references/piercing-and-performance.md` — piercing internals (`sec-fetch-dest` hard-nav, `data-piercing` styling, HTMLRewriter), caching (`Vary`, stub TTL, `forwardFragmentHeaders`), `mode:production`, route specificity. Read for perf/piercing/CLS work.
118+
- `references/frameworks-astro.md`**verified** Astro recipe: embedding, `<ClientRouter />`, the head-accumulation bug (#297), and smooth transitions without ClientRouter.
119+
120+
Scripts (run, don't read): `scripts/scaffold-fragment.mjs` (generate a Vite fragment +
121+
gateway snippet); `scripts/doctor.mjs` (static-check a project).
122+
123+
**Official slash commands** (PR #294, ship with the package under `.claude/commands/`):
124+
`/web-fragments:init-fragment | gateway-config | debug-fragment | fragment-test |
125+
migrate-to-fragments | fragment-csp | fragment-perf` — explicit task runners. They complement
126+
this skill: the skill auto-triggers and supplies verified knowledge; the commands run a
127+
specific job on request. Use them when the user invokes one or wants a guided scaffold/audit.
128+
129+
## Top gotchas (full list: references/troubleshooting.md)
130+
131+
- **Wrong middleware import.** No `web-fragments/middleware`. Web = `web-fragments/gateway`; Node = `web-fragments/gateway/node`.
132+
- **`registerFragment`, not `register`.**
133+
- **Asset path ↔ routePattern mismatch**#1 cause of 404'd assets / blank fragments. Fragment asset dir and gateway asset `routePattern` must share the prefix.
134+
- **Gateway is mandatory** — no server-less mode yet (#278).
135+
- **`nodenext`** type resolution may fail (#284); **Angular host** needs `outputMode:"server"` + `externalDependencies` (#280).
136+
- **`:root` is dead inside a fragment; use `:host`.** Host `:root` vars + inherited props (font/color) flow in; selector rules don't. → `css-and-styling.md`.
137+
- **`X-Frame-Options: DENY` on the fragment endpoint silently kills it** — it blocks the gateway's hidden iframe. Allow framing (`frame-ancestors 'self' <gateway-origin>`). → `csp-and-iframe.md`.
138+
- **Piercing fires only on hard navigation** (`sec-fetch-dest: document`); set `mode:'production'` when deployed; register specific routePatterns before broad ones (first-match-wins). → `piercing-and-performance.md`.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"skill_name": "web-fragments",
3+
"evals": [
4+
{
5+
"id": 0,
6+
"name": "embed-react-widget-into-express-host",
7+
"prompt": "I run an existing Express app (Node) and I have a separately deployed React widget hosted at https://reviews.acme.dev. I want to embed that widget as a micro-frontend on my /products page using the web-fragments library. Show me everything I need: the gateway setup, the middleware, the client bootstrap, and where the element goes.",
8+
"expected_output": "Uses FragmentGateway + registerFragment with fragmentId/endpoint/routePatterns (asset pattern + /products host route), getNodeMiddleware from web-fragments/gateway/node, initializeWebFragments() in client bootstrap, and a <web-fragment fragment-id> on the /products page. Must NOT import web-fragments/middleware and must use registerFragment not register.",
9+
"assertions": [
10+
{"text": "Imports getNodeMiddleware from 'web-fragments/gateway/node' (Node host)", "type": "code"},
11+
{"text": "Calls gateway.registerFragment(...) and NOT gateway.register(...)", "type": "code"},
12+
{"text": "registerFragment config includes fragmentId, endpoint, and routePatterns", "type": "code"},
13+
{"text": "routePatterns include both an asset pattern (e.g. /__wf/...) and the /products host route", "type": "code"},
14+
{"text": "Calls initializeWebFragments() in the client bootstrap", "type": "code"},
15+
{"text": "Places a <web-fragment fragment-id=\"...\"> element on the /products page", "type": "code"},
16+
{"text": "Does NOT import from 'web-fragments/middleware' (non-existent entry point)", "type": "code"}
17+
],
18+
"files": []
19+
},
20+
{
21+
"id": 1,
22+
"name": "debug-blank-fragment-asset-404",
23+
"prompt": "I'm using web-fragments. My fragment renders blank on the host page and the browser console shows the fragment's JS and CSS files returning 404 under /__wf/. The host page itself loads fine and the fragment app works when I open its own URL directly. The fragment is a Vite app. What's going wrong and how do I fix it?",
24+
"expected_output": "Identifies the asset-path vs routePattern mismatch as the root cause: the fragment's Vite build.assetsDir must share the same /__wf/<unique>/ prefix as the gateway's asset routePattern. Gives a concrete fix aligning the two. Does not invent unrelated causes.",
25+
"assertions": [
26+
{"text": "Identifies asset path / routePattern prefix mismatch as the root cause", "type": "manual"},
27+
{"text": "References Vite build.assetsDir alignment with the /__wf/ asset routePattern", "type": "code"},
28+
{"text": "Gives a concrete corrective step (edit assetsDir or routePattern so prefixes match)", "type": "manual"},
29+
{"text": "Does not hallucinate an unrelated root cause (e.g. CORS, missing fragment-id) as primary", "type": "manual"}
30+
],
31+
"files": []
32+
},
33+
{
34+
"id": 2,
35+
"name": "author-fragment-and-register-on-cloudflare",
36+
"prompt": "Create a brand-new web fragment from scratch: a small standalone 'newsletter-signup' app. Then show me how to register and serve it from my host app, which is deployed on Cloudflare Pages.",
37+
"expected_output": "A standalone fragment app that does NOT import web-fragments, with a unique Vite build.assetsDir under __wf/. Host registration uses FragmentGateway + registerFragment with a matching asset routePattern, and Cloudflare Pages middleware uses getWebMiddleware from web-fragments/gateway (Web, not Node) in a functions/_middleware.ts onRequest handler.",
38+
"assertions": [
39+
{"text": "The fragment app does NOT import the web-fragments package (it's a plain standalone app)", "type": "manual"},
40+
{"text": "Sets a unique Vite build.assetsDir under __wf/ for the fragment", "type": "code"},
41+
{"text": "Uses getWebMiddleware from 'web-fragments/gateway' (Web runtime), NOT getNodeMiddleware", "type": "code"},
42+
{"text": "Cloudflare Pages wiring uses functions/_middleware.ts with an onRequest handler", "type": "code"},
43+
{"text": "registerFragment asset routePattern prefix matches the fragment's assetsDir", "type": "manual"}
44+
],
45+
"files": []
46+
}
47+
]
48+
}

0 commit comments

Comments
 (0)