@hank-repo/frontend-modules
v1.1.0
Published
Frontend Module config, Manifest v1 producer, Vite discovery, React Host runtime and realtime lifecycle
Readme
@hank-repo/frontend-modules
Overview
@hank-repo/frontend-modules is the React 19 Frontend Module contract and Host runtime for hank-repo applications. It owns Manifest v1, Host API 1.0.0, Vite build-time discovery, publishable Module production, Host integration capabilities, React Host boundaries, the Host-scoped realtime lifecycle, and the shared Orval mutator.
Installed Modules are trusted npm ESM dependencies discovered during the Vite build. They run in the Host's JavaScript realm; the package's error boundaries contain failures but are not a security sandbox.
Installation
For a Host, install exact versions of both platform runtime packages:
pnpm add @hank-repo/frontend-modules@<exact-version> @hank-repo/frontend-react@<exact-version>The recommended way to create a correctly wired Host or publishable Module is the @hank-repo/frontend-platform CLI. A publishable Module must receive Host runtime packages through compatible peerDependencies, with exact versions only in devDependencies for local authoring.
Quick Start
Host author
Add the official discovery plugin to the Host's Vite configuration:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import { FRONTEND_MODULE_DEFAULT_LOCALE } from "@hank-repo/frontend-modules/core";
import { frontendModulesPlugin } from "@hank-repo/frontend-modules/vite";
import frontendHostRoutes from "./frontend-host-routes.json" with { type: "json" };
export default defineConfig({
plugins: [
react(),
tailwindcss(),
frontendModulesPlugin({
trustedScopes: ["@hank-repo"],
fallbackLocale: FRONTEND_MODULE_DEFAULT_LOCALE,
hostPagePaths: Object.values(frontendHostRoutes),
}),
],
});Create the single Host runtime from the generated virtual module and customer-owned infrastructure:
import { QueryClient } from "@tanstack/react-query";
import axios from "axios";
import { toast } from "@hank-repo/frontend-react/ui/sonner";
import { createFrontendHost, type FrontendHostAdapters } from "@hank-repo/frontend-modules/host-react";
import {
discoveredHostIntegrations,
discoveredModules,
frontendHostLocalization,
frontendModuleFallbackLocale,
} from "virtual:frontend-modules";
import frontendHostRoutes from "../frontend-host-routes.json";
import { router } from "./router";
export const http = axios.create();
export const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
const adapters: FrontendHostAdapters = {
navigation: {
getCurrent: () => ({ pathname: router.history.location.pathname, search: router.history.location.search }),
navigate: (url, options) => router.history[options.replace ? "replace" : "push"](url),
subscribe: (listener) => router.history.subscribe(() => listener()),
},
feedback: {
notify: ({ level, message, description }) =>
toast[level](message, description ? { description } : undefined),
},
telemetry: {
report: (event) => console.error(event.context.kind ?? "frontend-host-error", event),
},
dom: {
getOverlayContainer: () => document.querySelector<HTMLElement>("#root") ?? document.body,
},
};
export const frontendHost = createFrontendHost({
modules: discoveredModules,
integrations: discoveredHostIntegrations,
hostPagePaths: Object.values(frontendHostRoutes),
hostLocalization: frontendHostLocalization,
fallbackLocale: frontendModuleFallbackLocale,
http,
queryClient,
adapters,
});Call await frontendHost.initialize(), render FrontendHostProvider with the returned runtime, then call frontendHost.start(). Call frontendHost.dispose() during HMR disposal or application shutdown.
Import the Host styles once:
@import "tailwindcss";
@import "@hank-repo/frontend-react/styles.css";
@import "@hank-repo/frontend-modules/styles.css";Module author
Define routes and resources in one typed config:
import type { FrontendModuleConfig } from "@hank-repo/frontend-modules/core";
export default {
routes: [
{
path: "/orders",
titleKey: "Orders::Menu:Root",
page: "./src/pages/orders-page.tsx",
},
],
} satisfies FrontendModuleConfig;Use the official Producer in the publishable Module's Vite configuration:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import { frontendModuleProducerPlugin } from "@hank-repo/frontend-modules/vite";
import frontendModuleConfig from "./frontend-module.config.ts";
export default defineConfig({
plugins: [react(), tailwindcss(), frontendModuleProducerPlugin({ config: frontendModuleConfig })],
build: {
sourcemap: false,
rollupOptions: {
external: [
/^react(?:\/.*)?$/,
/^react-dom(?:\/.*)?$/,
/^react-i18next(?:\/.*)?$/,
/^i18next(?:\/.*)?$/,
/^zod(?:\/.*)?$/,
/^@tanstack\/react-(?:query|table)(?:\/.*)?$/,
/^(?:axios|zustand|immer)(?:\/.*)?$/,
/^@hank-repo\/frontend-(?:react|modules)(?:\/.*)?$/,
],
},
},
});The Producer generates ./frontend-module, ./frontend-module.manifest.json, and ./styles.css. Add hostIntegrationFile only when the Module implements a reviewed Host capability.
Features and Public API
| Public entry | Purpose |
| --- | --- |
| @hank-repo/frontend-modules/core | Module config types, Manifest v1 and Host API 1.0.0 contracts, route/locale/resource rules, validation helpers, and fail-closed authorization primitives. |
| @hank-repo/frontend-modules/vite | Host discovery plugin, Module Producer, virtual-module generation, public artifact validation, and Host runtime peer checks. |
| @hank-repo/frontend-modules/host-react | createFrontendHost, Provider and Hooks, access and error boundaries, resource authorization, and the Router-neutral Module outlet. |
| @hank-repo/frontend-modules/host-integration | Versioned Session, Authorization, Localization Override, and Menu Order capability contracts. |
| @hank-repo/frontend-modules/realtime | Host-scoped SignalR lifecycle that starts only for an authenticated Session and stops connections when the Session changes. |
| @hank-repo/frontend-modules/orval | hostHttpMutator, HostHttpRequestOptions, ErrorType, and BodyType for Module-owned Orval clients using the Host Axios instance. |
| @hank-repo/frontend-modules/client | Ambient types for virtual:frontend-modules and virtual:frontend-module-runtime; include this entry in the generated project's TypeScript types. |
| @hank-repo/frontend-modules/styles.css | Host React runtime Tailwind source registration. |
| @hank-repo/frontend-modules/frontend-module-v1.schema.json | JSON Schema for Manifest v1 tooling. |
There is no root JavaScript export. Use only the public subpaths declared in package.json.
Requirements
- The Host must declare exact direct dependencies on the shared runtime packages:
@hank-repo/frontend-modules,@hank-repo/frontend-react, React/ReactDOM, Axios, i18next/react-i18next, TanStack Query/Table, Zod, Zustand, and Immer. - A publishable Module must declare those runtime packages as compatible peers, use exact compatible versions only for development, and exclude them from its production bundle.
- Host and Module resolution must reach the same physical runtime installations. A second React, Axios, i18next, or platform runtime is rejected.
- Package Modules must be exact-version direct dependencies under a configured trusted scope. Discovery validates package identity, exports, Manifest, routes, locales, Host API version, integrations, and peers before generating
virtual:frontend-modules. - Supported locales are
zh-CNanden-US;zh-CNis the required fallback. Missing fallback text, duplicate keys, and interpolation mismatches fail the build. - Session and Authorization capabilities must be provided together and be unique in a Host. Menu Order depends on Session. Unknown, malformed, unavailable, or non-
allowauthorization states remain denied.
Troubleshooting
- TypeScript cannot resolve
virtual:frontend-modules: add@hank-repo/frontend-modules/clienttocompilerOptions.typesand ensure the Host usesfrontendModulesPlugin. - Vite rejects an installed Module: run the platform validator and check exact dependency placement, trusted scope, required exports, Manifest identity, Host API version, peer ranges, and duplicate physical installations.
- A protected route never opens: verify that the Host has healthy Session and Authorization capabilities and that the exact route/resource result is
allow; all other states intentionally fail closed. - Module styles are missing: import both public Host stylesheets and keep the Module's
./styles.cssexport. Do not scan or importdist/directly. - Realtime fails before connecting: authenticate first, provide a complete root-relative Hub path and named handlers, and confirm the Host Session still exposes an in-memory access token.
License
This package is provided under the terms in LICENSE.
