Process separation
LynxHub operates on a multi-process architecture based on Electron. Your extension runs code in two separate processes:Main process
The main process runs in a Node.js environment. It has full access to operating system APIs.-
Entrypoint:
src/main/lynxExtension.ts(compiled and bundled toscripts/main/mainEntry.cjsas a CommonJS module). -
Responsibility: Listens to system-level lifecycles, registers global IPC channels, adds tray menu items, and launches background processes (e.g., via
node-pty). -
Signature:
Renderer process
The renderer process displays the graphical interface and runs in a Chromium browser window.-
Entrypoint:
src/renderer/Extension.tsx(compiled and bundled toscripts/renderer/rendererEntry.mjsas an ES module). - Responsibility: Injects React UI components into predefined slots (Title Bar, Status Bar, Settings, Sidebar), adds custom pages/routes, and hooks into state management.
-
Signature:
Boot and loading sequence
LynxHub follows a structured sequence when loading extensions during application startup:1. Discovery and validation
During startup, the host scans the appPlugins/ folder. It checks for a valid subdirectory containing metadata.json and the required entrypoint files.
2. Main process initialization
The host imports the backend entrypoint of the extension. It calls the exportedinitialExtension function and passes the API wrappers. At this point, the extension registers callbacks for subsequent lifecycle events.
3. Application ready hook
When Electron completes its initialization, the host invokes all registeredonAppReady callbacks.
4. IPC channel setup
The host registers core IPC handlers. It then triggerslistenForChannels callbacks. This allows your extension to register custom IPC event handlers.
5. Main window display
When the main application window is ready to be shown, the host triggersonReadyToShow callbacks.
Module Federation in the renderer
LynxHub uses Module Federation via@originjs/vite-plugin-federation to dynamically mount extension interfaces in production. This approach avoids rebuilding the host application.
Runtime loading flow
- Discovery: The host requests the active plugin addresses from the main process over IPC.
- Registration: For each active extension, the host registers its remote entry URL (
<address>/scripts/renderer/rendererEntry.mjs) using the Virtual Module Federation API: - Mounting: The host dynamically loads the entry component using
__federation_method_getRemote(extensionId, 'Extension')and calls theInitialExtensionsinitializer.
Shared packages
To prevent loading duplicate library instances and to ensure shared context is maintained, the host shares core packages with all extensions:reactandreact-domreact-redux@heroui/react(HeroUI v3 React components)@heroui/styles(HeroUI v3 styling tokens)react-aria(React accessibility hooks)
Vite build configuration
To build your extension correctly for LynxHub, configure@originjs/vite-plugin-federation and electron-vite in your electron.vite.config.ts configuration. Below is the standard configuration structure used by the template:
Development vs. production environments
LynxHub provides two separate loading strategies to optimize the developer experience:Development mode
To bypass Module Federation and speed up updates, the host process attempts to hot-import the extension directly from your local source directory using the Vite alias:- Renderer:
@lynx_extension/renderer/Extension(mapped to/extension/src/renderer) - Main:
extension/src/main/lynxExtension
Production mode
In production, LynxHub launches local servers for installed extensions. It loads their entry files from disk:- Validation: LynxHub verifies the directory structure under
<AppData>/Plugins/<id>/. It ensuresscripts/main/mainEntry.cjsandscripts/renderer/rendererEntry.mjsexist. - Protocol: Static assets and entrypoints are served locally and registered as Module Federation remotes.