-
Notifications
You must be signed in to change notification settings - Fork 0
feat: set up module federation #40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
367af5d
cddd1fe
eda062d
13b7200
1b28600
9f0155a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| /** | ||
| * @license SPDX-FileCopyrightText: © 2026 Zenme Pty Ltd <info@zenme.com.au> | ||
| * @license SPDX-License-Identifier: MIT | ||
| */ | ||
| // @ts-nocheck | ||
|
|
||
| import { useRoutes } from "react-router-dom"; | ||
| import { useEffect, useState } from "react"; | ||
| import { useLocation, useParams } from "react-router-dom"; | ||
| import * as AddQuestionModule from "./routes/AddQuestion"; | ||
| import * as AnswerQuestionModule from "./routes/AnswerQuestion"; | ||
| import * as EditQuestionModule from "./routes/EditQuestion"; | ||
| import * as FollowUpQuestionModule from "./routes/FollowUpQuestion"; | ||
| import * as QuestionCombinationListModule from "./routes/QuestionCombinationList"; | ||
| import * as QuestionDetailModule from "./routes/QuestionDetail"; | ||
| import * as QuestionDetailAddModule from "./routes/QuestionDetailAdd"; | ||
| import * as ShareQuestionModule from "./routes/ShareQuestion"; | ||
|
|
||
| type FrameworkRouteModule = { | ||
| default: (props: { loaderData?: unknown }) => JSX.Element; | ||
| clientLoader?: (args: { params: Record<string, string | undefined> }) => Promise<unknown> | unknown; | ||
| }; | ||
|
|
||
| function RouteModuleElement({ routeModule }: { routeModule: FrameworkRouteModule }) { | ||
| const location = useLocation(); | ||
| const params = useParams(); | ||
| const [loaderData, setLoaderData] = useState<unknown>(undefined); | ||
| const [isLoading, setIsLoading] = useState(Boolean(routeModule.clientLoader)); | ||
|
|
||
| useEffect(() => { | ||
| let isMounted = true; | ||
|
|
||
| const load = async () => { | ||
| if (!routeModule.clientLoader) { | ||
| setIsLoading(false); | ||
| return; | ||
| } | ||
|
|
||
| setIsLoading(true); | ||
| const nextData = await routeModule.clientLoader({ params }); | ||
| if (isMounted) { | ||
| setLoaderData(nextData); | ||
| setIsLoading(false); | ||
| } | ||
| }; | ||
|
|
||
| load(); | ||
|
|
||
| return () => { | ||
| isMounted = false; | ||
| }; | ||
| }, [routeModule, location.pathname, location.search, params]); | ||
|
|
||
| if (isLoading) { | ||
| return null; | ||
| } | ||
|
|
||
| const RouteComponent = routeModule.default; | ||
| return <RouteComponent loaderData={loaderData} />; | ||
| } | ||
|
|
||
| export default function Quest3TierAppRoutes() { | ||
| return useRoutes([ | ||
| { index: true, element: <RouteModuleElement routeModule={QuestionCombinationListModule} /> }, | ||
| { path: "add", element: <RouteModuleElement routeModule={AddQuestionModule} /> }, | ||
| { path: ":id", element: <RouteModuleElement routeModule={QuestionDetailModule} /> }, | ||
| { path: ":id/add", element: <RouteModuleElement routeModule={QuestionDetailAddModule} /> }, | ||
| { path: ":id/answer", element: <RouteModuleElement routeModule={AnswerQuestionModule} /> }, | ||
| { path: ":id/followUp", element: <RouteModuleElement routeModule={FollowUpQuestionModule} /> }, | ||
| { path: ":id/edit", element: <RouteModuleElement routeModule={EditQuestionModule} /> }, | ||
| { path: ":id/share", element: <RouteModuleElement routeModule={ShareQuestionModule} /> }, | ||
| ]); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>quest3Tier remote</title> | ||
| </head> | ||
| <body> | ||
| <script type="module" src="./AppRoutes.tsx"></script> | ||
| </body> | ||
| </html> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| /** | ||
| * @license SPDX-FileCopyrightText: © 2026 Zenme Pty Ltd <info@zenme.com.au> | ||
| * @license SPDX-License-Identifier: MIT | ||
| */ | ||
|
|
||
| import { defineConfig } from "vite"; | ||
| import { fileURLToPath, URL } from "node:url"; | ||
| import federation from "@originjs/vite-plugin-federation"; | ||
|
|
||
| export default defineConfig({ | ||
| esbuild: { | ||
| jsx: "automatic", // this is just a workaround for not having tsconfig.json set up yet | ||
| }, | ||
| plugins: [ | ||
| federation({ | ||
| name: "quest3TierRemote", | ||
| filename: "remoteEntry.js", | ||
| exposes: { | ||
| "./AppRoutes": "./AppRoutes.tsx", | ||
| }, | ||
| shared: ["react", "react-dom", "react-router", "react-router-dom"], | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🌐 Web query:
💡 Result: In Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
# Locate the relevant Vite configs and the federation plugin version.
git ls-files 'module/quest3Tier/ui/vite.config.ts' 'ui/vite.config.ts' '**/package.json' '**/pnpm-lock.yaml' '**/package-lock.json' '**/yarn.lock' | sed -n '1,200p'
printf '\n--- vite.config.ts files ---\n'
for f in module/quest3Tier/ui/vite.config.ts ui/vite.config.ts; do
if [ -f "$f" ]; then
echo "### $f"
cat -n "$f" | sed -n '1,220p'
echo
fi
done
printf '\n--- package.json references ---\n'
rg -n '"`@originjs/vite-plugin-federation`"|singleton|react-router|react-dom|react-router"' -g 'package.json' -g 'pnpm-lock.yaml' -g 'package-lock.json' -g 'yarn.lock' -g 'vite.config.ts' .Repository: ZenMe-AU/ZBReactArchitecture Length of output: 31200 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect the package metadata for the federation plugin version in the workspace that owns the file.
fd -a 'package.json' .Repository: ZenMe-AU/ZBReactArchitecture Length of output: 954 🌐 Web query:
💡 Result: In Citations:
🌐 Web query:
💡 Result: In For React specifically, a GitHub issue reports that sharing If you want, I can turn that into the exact Citations:
Use singleton shared deps for React and router. The array shorthand here can still let host and remote load separate React/router instances; switch these entries to object form with 🤖 Prompt for AI Agents |
||
| }), | ||
| ], | ||
| build: { | ||
| target: "esnext", | ||
| minify: false, | ||
| modulePreload: false, | ||
| cssCodeSplit: false, | ||
| }, | ||
| resolve: { | ||
| alias: { | ||
| "@zenmechat/shared-ui": fileURLToPath(new URL("../../../ui", import.meta.url)), | ||
| }, | ||
| }, | ||
| server: { | ||
| port: 5174, | ||
| strictPort: true, | ||
| }, | ||
| }); | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,73 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @license SPDX-FileCopyrightText: © 2026 Zenme Pty Ltd <info@zenme.com.au> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @license SPDX-License-Identifier: MIT | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // @ts-nocheck | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useRoutes } from "react-router-dom"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useEffect, useState } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useLocation, useParams } from "react-router-dom"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as AddQuestionModule from "./routes/AddQuestion"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as AnswerQuestionModule from "./routes/AnswerQuestion"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as EditQuestionModule from "./routes/EditQuestion"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as FollowUpQuestionModule from "./routes/FollowUpQuestion"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as QuestionCombinationListModule from "./routes/QuestionCombinationList"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as QuestionDetailModule from "./routes/QuestionDetail"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as QuestionDetailAddModule from "./routes/QuestionDetailAdd"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as ShareQuestionModule from "./routes/ShareQuestion"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type FrameworkRouteModule = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| default: (props: { loaderData?: unknown }) => JSX.Element; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clientLoader?: (args: { params: Record<string, string | undefined> }) => Promise<unknown> | unknown; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function RouteModuleElement({ routeModule }: { routeModule: FrameworkRouteModule }) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const location = useLocation(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const params = useParams(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [loaderData, setLoaderData] = useState<unknown>(undefined); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [isLoading, setIsLoading] = useState(Boolean(routeModule.clientLoader)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let isMounted = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const load = async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!routeModule.clientLoader) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setIsLoading(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setIsLoading(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const nextData = await routeModule.clientLoader({ params }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isMounted) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setLoaderData(nextData); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setIsLoading(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+33
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Unhandled Per the upstream 🐛 Proposed fix const load = async () => {
if (!routeModule.clientLoader) {
setIsLoading(false);
return;
}
setIsLoading(true);
- const nextData = await routeModule.clientLoader({ params });
- if (isMounted) {
- setLoaderData(nextData);
- setIsLoading(false);
- }
+ try {
+ const nextData = await routeModule.clientLoader({ params });
+ if (isMounted) {
+ setLoaderData(nextData);
+ }
+ } catch (error) {
+ console.error("Route loader failed:", error);
+ if (isMounted) {
+ setLoaderData(undefined);
+ }
+ } finally {
+ if (isMounted) {
+ setIsLoading(false);
+ }
+ }
};📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| load(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isMounted = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [routeModule, location.pathname, location.search, params]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isLoading) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const RouteComponent = routeModule.default; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return <RouteComponent loaderData={loaderData} />; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default function Quest5TierAppRoutes() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return useRoutes([ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { index: true, element: <RouteModuleElement routeModule={QuestionCombinationListModule} /> }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { path: "add", element: <RouteModuleElement routeModule={AddQuestionModule} /> }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { path: ":id", element: <RouteModuleElement routeModule={QuestionDetailModule} /> }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { path: ":id/add", element: <RouteModuleElement routeModule={QuestionDetailAddModule} /> }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { path: ":id/answer", element: <RouteModuleElement routeModule={AnswerQuestionModule} /> }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { path: ":id/followUp", element: <RouteModuleElement routeModule={FollowUpQuestionModule} /> }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { path: ":id/edit", element: <RouteModuleElement routeModule={EditQuestionModule} /> }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { path: ":id/share", element: <RouteModuleElement routeModule={ShareQuestionModule} /> }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>quest5Tier remote</title> | ||
| </head> | ||
| <body> | ||
| <script type="module" src="./AppRoutes.tsx"></script> | ||
| </body> | ||
|
Comment on lines
+8
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🌐 Web query:
💡 Result: In the Module Federation architecture utilized by Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
printf 'ROOT FILES:\n'
git ls-files | sed -n '1,200p' | grep -E '^(module/quest5Tier/ui/|package.json|vite\.config|tsconfig|src/|module/quest5Tier/)'
printf '\nINDEX.HTML:\n'
cat -n module/quest5Tier/ui/index.html
printf '\nAPPROUTES SEARCH:\n'
rg -n "useRoutes|createRoot|ReactDOM|Router|BrowserRouter|HashRouter|Routes|Route|useParams|useLocation" module/quest5Tier/ui -S
printf '\nPACKAGE.JSON:\n'
cat -n package.jsonRepository: ZenMe-AU/ZBReactArchitecture Length of output: 178 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf 'FILE TREE (quest5Tier ui):\n'
find module/quest5Tier/ui -maxdepth 2 -type f | sort
printf '\nOUTLINE AppRoutes.tsx:\n'
ast-grep outline module/quest5Tier/ui/AppRoutes.tsx --view expanded || true
printf '\nREAD AppRoutes.tsx:\n'
cat -n module/quest5Tier/ui/AppRoutes.tsx
printf '\nREAD main-ish entry files:\n'
for f in module/quest5Tier/ui/main.tsx module/quest5Tier/ui/main.ts module/quest5Tier/ui/index.tsx module/quest5Tier/ui/index.ts; do
if [ -f "$f" ]; then
echo "--- $f ---"
cat -n "$f"
fi
doneRepository: ZenMe-AU/ZBReactArchitecture Length of output: 4496 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf 'SEARCH FOR VITE FEDERATION CONFIG / STANDALONE ENTRY:\n'
rg -n "federation|exposes|remotes|dev:federation|preview:federation|standalone|index.html|createRoot|hydrateRoot" -S . --glob '!**/node_modules/**'
printf '\nSEARCH index.html scripts in ui:\n'
rg -n "<script|type=\"module\"" module/quest5Tier/ui/index.html -n -S
printf '\nCHECK if AppRoutes.tsx imports ReactDOM or Router providers:\n'
python3 - <<'PY'
from pathlib import Path
p = Path("module/quest5Tier/ui/AppRoutes.tsx")
text = p.read_text()
for needle in ["ReactDOM", "createRoot", "BrowserRouter", "HashRouter", "RouterProvider", "useRoutes", "useParams", "useLocation"]:
print(needle, needle in text)
PYRepository: ZenMe-AU/ZBReactArchitecture Length of output: 11796 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- module/quest5Tier/ui/package.json ---'
cat -n module/quest5Tier/ui/package.json
echo
echo '--- module/quest5Tier/ui/vite.config.ts ---'
cat -n module/quest5Tier/ui/vite.config.ts
echo
echo '--- module/quest5Tier/ui/README.md (if present) ---'
if [ -f module/quest5Tier/ui/README.md ]; then
cat -n module/quest5Tier/ui/README.md
else
echo '(absent)'
fiRepository: ZenMe-AU/ZBReactArchitecture Length of output: 3894 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- host route references to quest5Tier remote ---'
rg -n "quest5Tier|Quest5TierAppRoutes|AppRoutes" ui module/quest5Tier -S
echo
echo '--- module/quest5Tier/ui/vite.config.ts (imports and exposes) ---'
ast-grep outline module/quest5Tier/ui/vite.config.ts --view expanded || true
cat -n module/quest5Tier/ui/vite.config.tsRepository: ZenMe-AU/ZBReactArchitecture Length of output: 8115 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path("module/quest5Tier/ui/vite.config.ts")
text = p.read_text()
for needle in ["exposes", "remotes", "build:federation", "preview:federation", "dev:federation", "base", "server", "preview"]:
idx = text.find(needle)
print(needle, idx)
PYRepository: ZenMe-AU/ZBReactArchitecture Length of output: 279 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- module/quest5Tier/ui/package.json ---'
cat -n module/quest5Tier/ui/package.json
echo
echo '--- module/quest5Tier/ui/vite.config.ts ---'
cat -n module/quest5Tier/ui/vite.config.ts
echo
echo '--- host / remote references ---'
rg -n "quest5Tier|Quest5TierAppRoutes|AppRoutes" ui module/quest5Tier -SRepository: ZenMe-AU/ZBReactArchitecture Length of output: 10422 Add a standalone mount for the federated UI. 🤖 Prompt for AI Agents |
||
| </html> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| /** | ||
| * @license SPDX-FileCopyrightText: © 2026 Zenme Pty Ltd <info@zenme.com.au> | ||
| * @license SPDX-License-Identifier: MIT | ||
| */ | ||
|
|
||
| import { defineConfig } from "vite"; | ||
| import { fileURLToPath, URL } from "node:url"; | ||
| import federation from "@originjs/vite-plugin-federation"; | ||
|
|
||
| export default defineConfig({ | ||
| esbuild: { | ||
| jsx: "automatic", // this is just a workaround for not having tsconfig.json set up yet | ||
| }, | ||
| plugins: [ | ||
| federation({ | ||
| name: "quest5TierRemote", | ||
| filename: "remoteEntry.js", | ||
| exposes: { | ||
| "./AppRoutes": "./AppRoutes.tsx", | ||
| }, | ||
| shared: ["react", "react-dom", "react-router", "react-router-dom"], | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== Files matching vite.config.ts ==\n'
git ls-files '**/vite.config.ts'
printf '\n== Relevant configs in module/quest5Tier/ui ==\n'
ast-grep outline module/quest5Tier/ui/vite.config.ts --view expanded || true
cat -n module/quest5Tier/ui/vite.config.ts | sed -n '1,120p'
printf '\n== Search for shared federation configs ==\n'
rg -n --glob 'vite.config.ts' 'shared\s*:' module ui . || true
rg -n --glob 'vite.config.ts' '`@originjs/vite-plugin-federation`|singleton|requiredVersion|shared\s*:\s*\[' module ui . || trueRepository: ZenMe-AU/ZBReactArchitecture Length of output: 4272 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== Package manifests ==\n'
git ls-files 'package.json' '**/package.json'
printf '\n== React/router dependency declarations ==\n'
for f in package.json ui/package.json module/quest3Tier/ui/package.json module/quest5Tier/ui/package.json module/quest5TierEg/ui/package.json; do
if [ -f "$f" ]; then
echo "--- $f"
cat "$f" | python3 - <<'PY'
import json,sys
data=json.load(sys.stdin)
for section in ("dependencies","devDependencies","peerDependencies"):
deps=data.get(section,{})
hits={k:v for k,v in deps.items() if k in {"react","react-dom","react-router","react-router-dom","`@originjs/vite-plugin-federation`"}}
if hits:
print(section, hits)
PY
fi
done
printf '\n== Any federation singleton examples ==\n'
rg -n 'singleton\s*:\s*true|requiredVersion\s*:\s*' . || trueRepository: ZenMe-AU/ZBReactArchitecture Length of output: 1495 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== Package manifests ==\n'
git ls-files 'package.json' '**/package.json'
printf '\n== React/router dependency declarations ==\n'
for f in package.json ui/package.json module/quest3Tier/ui/package.json module/quest5Tier/ui/package.json module/quest5TierEg/ui/package.json; do
if [ -f "$f" ]; then
echo "--- $f"
python3 - <<'PY' "$f"
import json,sys
path=sys.argv[1]
with open(path) as fh:
data=json.load(fh)
for section in ("dependencies","devDependencies","peerDependencies"):
deps=data.get(section,{})
hits={k:v for k,v in deps.items() if k in {"react","react-dom","react-router","react-router-dom","`@originjs/vite-plugin-federation`"}}
if hits:
print(section, hits)
PY
fi
done
printf '\n== Any federation singleton examples ==\n'
rg -n 'singleton\s*:\s*true|requiredVersion\s*:\s*' . || trueRepository: ZenMe-AU/ZBReactArchitecture Length of output: 1660 🌐 Web query:
💡 Result: In vite-plugin-federation, the shared configuration allows you to define dependencies shared between host and remote modules using either an array or an object [1][2]. When using an object configuration, each shared package can be defined with specific settings, including singleton and requiredVersion [1][2]. singleton: boolean When set to true, this option ensures that only one instance of the shared module is used across the host and all remotes [1][2]. This is commonly required for libraries that maintain internal state, such as React, to prevent issues like multiple instances of hooks (e.g., TypeError: Cannot read properties of null (reading 'useState')) [3][4]. requiredVersion: string This setting specifies the version range required from the host [5][6]. It is primarily used by the remote side [5][6]. If the version of the module provided by the host does not meet the specified requiredVersion, the remote will fall back to using its own bundled version, provided that import is not set to false [5][6]. Example configuration: shared: { react: { singleton: true, requiredVersion: '^18.3.1', }, 'react-dom': { singleton: true, requiredVersion: '^18.3.1', }, } Note: While older versions or specific forks (such as the jskits/vite-plugin-federation) explicitly document these fields in their API, users of the original Citations:
Use singleton shared deps for React/router 🤖 Prompt for AI Agents |
||
| }), | ||
| ], | ||
| build: { | ||
| target: "esnext", | ||
| minify: false, | ||
| modulePreload: false, | ||
| cssCodeSplit: false, | ||
| }, | ||
| resolve: { | ||
| alias: { | ||
| "@zenmechat/shared-ui": fileURLToPath(new URL("../../../ui", import.meta.url)), | ||
| }, | ||
| }, | ||
| server: { | ||
| port: 5175, | ||
| strictPort: true, | ||
| }, | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| /** | ||
| * @license SPDX-FileCopyrightText: © 2026 Zenme Pty Ltd <info@zenme.com.au> | ||
| * @license SPDX-License-Identifier: MIT | ||
| */ | ||
| // @ts-nocheck | ||
|
|
||
| import { useRoutes } from "react-router-dom"; | ||
| import { useEffect, useState } from "react"; | ||
| import { useLocation, useParams } from "react-router-dom"; | ||
| import * as AddQuestionModule from "./routes/AddQuestion"; | ||
| import * as AnswerQuestionModule from "./routes/AnswerQuestion"; | ||
| import * as EditQuestionModule from "./routes/EditQuestion"; | ||
| import * as FollowUpQuestionModule from "./routes/FollowUpQuestion"; | ||
| import * as QuestionCombinationListModule from "./routes/QuestionCombinationList"; | ||
| import * as QuestionDetailModule from "./routes/QuestionDetail"; | ||
| import * as QuestionDetailAddModule from "./routes/QuestionDetailAdd"; | ||
| import * as ShareQuestionModule from "./routes/ShareQuestion"; | ||
|
|
||
| type FrameworkRouteModule = { | ||
| default: (props: { loaderData?: unknown }) => JSX.Element; | ||
| clientLoader?: (args: { params: Record<string, string | undefined> }) => Promise<unknown> | unknown; | ||
| }; | ||
|
|
||
| function RouteModuleElement({ routeModule }: { routeModule: FrameworkRouteModule }) { | ||
| const location = useLocation(); | ||
| const params = useParams(); | ||
| const [loaderData, setLoaderData] = useState<unknown>(undefined); | ||
| const [isLoading, setIsLoading] = useState(Boolean(routeModule.clientLoader)); | ||
|
|
||
| useEffect(() => { | ||
| let isMounted = true; | ||
|
|
||
| const load = async () => { | ||
| if (!routeModule.clientLoader) { | ||
| setIsLoading(false); | ||
| return; | ||
| } | ||
|
|
||
| setIsLoading(true); | ||
| const nextData = await routeModule.clientLoader({ params }); | ||
| if (isMounted) { | ||
| setLoaderData(nextData); | ||
| setIsLoading(false); | ||
| } | ||
| }; | ||
|
|
||
| load(); | ||
|
|
||
| return () => { | ||
| isMounted = false; | ||
| }; | ||
| }, [routeModule, location.pathname, location.search, params]); | ||
|
|
||
| if (isLoading) { | ||
| return null; | ||
| } | ||
|
|
||
| const RouteComponent = routeModule.default; | ||
| return <RouteComponent loaderData={loaderData} />; | ||
| } | ||
|
|
||
| export default function Quest5TierEgAppRoutes() { | ||
| return useRoutes([ | ||
| { index: true, element: <RouteModuleElement routeModule={QuestionCombinationListModule} /> }, | ||
| { path: "add", element: <RouteModuleElement routeModule={AddQuestionModule} /> }, | ||
| { path: ":id", element: <RouteModuleElement routeModule={QuestionDetailModule} /> }, | ||
| { path: ":id/add", element: <RouteModuleElement routeModule={QuestionDetailAddModule} /> }, | ||
| { path: ":id/answer", element: <RouteModuleElement routeModule={AnswerQuestionModule} /> }, | ||
| { path: ":id/followUp", element: <RouteModuleElement routeModule={FollowUpQuestionModule} /> }, | ||
| { path: ":id/edit", element: <RouteModuleElement routeModule={EditQuestionModule} /> }, | ||
| { path: ":id/share", element: <RouteModuleElement routeModule={ShareQuestionModule} /> }, | ||
| ]); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>quest5TierEg remote</title> | ||
| </head> | ||
| <body> | ||
| <script type="module" src="./AppRoutes.tsx"></script> | ||
| </body> | ||
| </html> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: ZenMe-AU/ZBReactArchitecture
Length of output: 11384
🏁 Script executed:
Repository: ZenMe-AU/ZBReactArchitecture
Length of output: 10119
Handle loader errors and redirect responses in
RouteModuleElement(module/quest3Tier/ui/AppRoutes.tsx:34-45)await routeModule.clientLoader(...)can reject and leaveisLoadingstuck ontrue, and a returnedredirect()response is currently stored asloaderDatainstead of navigating. Catch failures, clear loading in afinally, and special-caseResponseredirects before rendering.🤖 Prompt for AI Agents