- Build-time plugins configured via REACT_ENABLED_APPS env var
- format: org/repo or org/repo@host/path/to/remoteEntry.js
- Build-time plugins: loaded from pluginMap.ts (generated at setup time), become read-only in UI
- API plugins: fetched from /api/v1/plug_config/, editable in UI
- Both merged at runtime by mergePlugConfigs() in src/Utils/plugConfig.ts
- Main app (vite.config.mts): federation with name "core", dummy remote, shared: react/react-dom/i18next/react-query/raviger/sonner/decimal.js
- Per-app (apps/care_hello_fe/vite.config.ts): exposes ./manifest pointing to src/manifest.tsx, builds remoteEntry.js to dist/assets/
- RemoteEntry URL resolving: GitHub Pages if no @ suffix, otherwise http:// for localhost, https:// elsewhere
- Runtime: PluginEngine.tsx uses setFederationRemote() to register each plugin slug dynamically, then getFederationRemote() to load manifest
- Main app: npm run dev → checks/generates pluginMap.ts, runs vite on :4000
- Sample plugin (apps/care_hello_fe): npm run dev → vite preview :4173 + vite build --watch for remoteEntry.js
- Local testing: REACT_ENABLED_APPS=ohcnetwork/care_hello_fe@localhost:4173/assets/remoteEntry.js
- Care_hello_fe exposes remoteEntry.js at dist/assets/ and serves on preview port 4173
- Main app server has watch config ignoring tests/playwright/dist folders
- care_hello_fe has vite build --watch in dev script to keep dist/assets/remoteEntry.js fresh
- No automatic HMR for remoteEntry changes back to main app - requires manual reload after plugin rebuild
- No mechanism for main app to watch apps/ directly or trigger rebuilds
- Goal: Auto-discover apps/ plugins in dev-only mode for direct HMR without separate builds/previews
- Approach: Detect mode === 'dev', scan apps/ directory for
src/manifest.*files, manage two pluginMap paths - Shared deps already configured: federation shares react, react-dom, react-i18next, @tanstack/react-query, raviger, sonner, decimal.js
- Tailwind content: Already includes ./apps/*/ so CSS should work
- Risk: Re-export cycles if apps/ import from src/ (already happens - care_hello_fe imports Page component)
- Federation globals: federation methods already in globals.d.ts and usable in dev mode
- Implementation points: vite.config.mts, care.config.ts (optional), src/PluginEngine.tsx (routing logic)
When a plugin needs to reuse a component from the host app, use the
clone-component CLI to copy the file along with every local file it
transitively imports into the plugin's src/ tree.
# via npm script
npm run clone-component -- <source> <target-app> [flags]
# or directly with tsx
npx tsx scripts/clone-component.ts <source> <target-app> [flags]Arguments:
<source>— the entry component. Accepts:- workspace-relative path:
src/components/Common/Loading.tsx - absolute path
- host alias:
@/components/ui/button,@core/components/ui/button,@careConfig
- workspace-relative path:
<target-app>— directory name underapps/(e.g.care_voice_fe,care_ask_fe).
Flags:
-f, --force— overwrite files that already exist in the plugin.-n, --dry-run— report what would be copied without writing anything.-h, --help— show usage.
- Walks the import graph starting from
<source>throughimport,export … from, dynamicimport(), andrequire()statements. - Resolves each specifier the same way Vite/TS does (extension probing,
index.*for directories) for.ts/.tsx/.js/.jsx/.mjs/.cjs/.json/.css/.scssand common image/asset extensions. - Copies every resolved file into
apps/<target-app>/src/...preserving the path undersrc/. - Rewrites host-only path aliases to ones the plugin tsconfig understands:
@core/foo→@/foo@careConfig→@/care.config(and copiescare.config.tsintoapps/<target-app>/src/)@/foois left as-is (plugins use the same@/*alias).
- Skips existing files unless
--forceis passed. - Copies binary assets (images, fonts, lottie, etc.) byte-for-byte without rewriting.
- Collects bare-specifier imports and syncs any missing packages into the target app's
package.jsonusing the version and dependency section from the root workspacepackage.json. - Ignores imports that only appear inside comments.
A summary is printed at the end:
✓ Copied— files written (or that would be written under--dry-run).• Skipped— files already present in the target app; re-run with--forceto replace.• External packages referenced— bare-specifier imports encountered (e.g.react,@radix-ui/react-slot).✓ Synced— packages added to the target app'spackage.json(or that would be added under--dry-run).• Packages already present— dependencies the target app already declares.• Skipped Node builtins— built-in Node modules that do not belong inpackage.json.! External packages missing from the root package.json— packages the script could not source a version for automatically.! Unresolved imports— specifiers that could not be resolved to a file insrc/orcare.config.ts. These need manual attention (often host-only modules outsidesrc/such asvite-env.d.ts-style globals).
# Preview what cloning a button would pull in.
npm run clone-component -- @/components/ui/button care_voice_fe --dry-run
# Actually copy a page component into a plugin and overwrite collisions.
npm run clone-component -- src/pages/Appointments/BookAppointment/BookAppointmentDetails.tsx care_ask_fe --force
# Copy the host care.config.ts shim into a plugin.
npm run clone-component -- @careConfig care_voice_fe- Only files under
src/(andcare.config.ts) are followed. Imports that resolve outside those roots are reported as unresolved. - The CLI updates the target app's
package.json, but it does not runnpm install. - Auto-sync only works for packages that already exist in the root workspace
package.json; anything else is still reported for manual follow-up. - Once cloned, files are independent copies. They will not stay in sync with the host; re-run with
--forceto refresh.