Add MFE dev-mode proxy for local development
What does this MR do and why?
Local-development flow for delivered MFEs: a dev-/test-only route /-/mfe/:name/dev/*path proxies the dev magic version to per-MFE local dev servers, so applications hot-reload in GDK without ever touching the production delivery path:
Mfe::DevProxyController— mounted only underGitlab.dev_or_test_env?, so it can never reach a production route table; kept separate from the artifact delivery endpoint so the production serve path never contains proxy logic.Gitlab::Mfedev API:GITLAB_MFE_DEV_APPS(which apps are served from dev servers),GITLAB_MFE_DEV_SERVERS(name=url,…map),GITLAB_MFE_DEV_SERVER(single-server fallback) — several MFEs can be developed simultaneously. An app in dev mode without any configured server is a configuration error and raises (Gitlab::Mfe::MissingDevServerError): there is deliberately no default server to silently guess at.- Dev-server websocket origins are appended to the development CSP
connect-src, so HMR connections are allowed.
How to verify
bin/rspec spec/requests/mfe/dev_proxy_spec.rb spec/lib/gitlab/mfe_spec.rb \
spec/lib/gitlab/content_security_policy/config_loader_spec.rbEnd-to-end in GDK with a real application (Duo Chat)
Browser-verified walkthrough; no consumer code is required — a console snippet proves serving, execution, and HMR.
1. Run the Duo Chat dev server (https://gitlab.com/gitlab-org/frontend/gitlab-mfe):
git clone https://gitlab.com/gitlab-org/frontend/gitlab-mfe && cd gitlab-mfe
pnpm install
pnpm dev:rspack # serves the remote on http://localhost:4001The dev server needs three small config fixes to be consumable through the monolith proxy (HMR socket target, Host/Origin check, rspack's lazy compilation posting to the page origin where Rails answers 422). They ship as a separate merge request to that project; until it merges, apply this diff before pnpm dev:rspack:
diff --git a/gitlab-shared/rspack-config/src/index.ts b/gitlab-shared/rspack-config/src/index.ts
index fb9318b..afd38fa 100644
--- a/gitlab-shared/rspack-config/src/index.ts
+++ b/gitlab-shared/rspack-config/src/index.ts
@@ -256,7 +256,22 @@ export function defineGitLabRspackConfig(options: GitLabRspackConfigOptions): Co
'Access-Control-Allow-Origin': '*',
},
hot: true,
+ // The websocket carries the monolith page's Origin (gdk.test), which
+ // the dev server's Host/Origin check would reject. The server only
+ // binds localhost, so relaxing the check is safe.
+ allowedHosts: 'all',
+ // The monolith consumes this server through its same-origin dev proxy
+ // (/-/mfe/<name>/dev/...), so the page origin is the monolith, not this
+ // server. The HMR client must therefore be pointed at this server
+ // explicitly; otherwise it derives the socket URL from the page origin.
+ client: {
+ webSocketURL: `ws://localhost:${port}/ws`,
+ },
},
+ // rspack serve enables lazy compilation; its client posts module
+ // activity to the page origin, which is the monolith when proxied -
+ // Rails answers 422 and the remote fails to mount.
+ lazyCompilation: false,
};
return config;2. Point GDK at it — in <gdk>/env.runit:
export GITLAB_MFE_DEV_APPS=duo_chat
export GITLAB_MFE_DEV_SERVERS=duo_chat=http://localhost:4001then gdk restart rails-web (delivery gates also apply: mfe.enabled: true in gitlab.yml + Feature.enable(:mfe_enabled)).
3. Proxy check:
curl -s http://gdk.test:3000/-/mfe/duo_chat/dev/mf-manifest.json | head -c 120 # the dev server's live manifest4. Execution check — on any GDK page, in the browser console:
s = document.createElement('script'); s.src = '/-/mfe/duo_chat/dev/remoteEntry.js'; document.head.append(s);
// a moment later:
window.mfe_duo_chat // => the federation container, served and executed through the proxyThe rspack dev client inside the bundle also connects its HMR websocket to ws://localhost:4001/ws — allowed by this MR's development-CSP change (visible in the Network tab, status 101).
5. HMR check: touch mfes/duo-chat/src/components/duo_chat/duo_chat_view.vue — the console logs [HMR] Updated modules: ... App is up to date. without a page reload.
6. Fail-loud misconfiguration: remove GITLAB_MFE_DEV_SERVERS (keeping GITLAB_MFE_DEV_APPS) and restart — any page raises Gitlab::Mfe::MissingDevServerError with instructions, by design: there is no default server to silently guess at.
In production builds none of this exists — the route is mounted only under Gitlab.dev_or_test_env?.
Stack
This is part of a stacked series extracted from a single 1700+ line MR for reviewability. Each part is independently verifiable and leaves master inert: nothing is user-reachable until an application is pinned.
| # | Scope | MR |
|---|---|---|
| 1 | Config plumbing + instance kill switch | !245043 (closed) |
| 2 | Sidecar manifest validation + pin-file catalog | !245044 (closed) |
| 3 | Verified bake task + baked manifest | !245045 (closed) |
| 4 | Same-origin delivery endpoint | !245046 (closed) |
| 5 | Dev-mode proxy for local development | !245301 (closed) |
Merging bottom-up; when a part merges, GitLab retargets the next one to master automatically.
A separate demonstration MR shows the whole stack working end to end with the real Duo Chat MFE (production bake + dev-mode HMR): !245014.
Related
- Phase 1 issue: #605798
- Epic: gitlab-org#22777