everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
265 lines (263 loc) • 11.3 kB
JavaScript
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });
const require_ui_runtime = require('./runtime.cjs');
const require_ui_api = require('./api.cjs');
const require_ui_auth = require('./auth.cjs');
const require_ui_manifest_index = require('./manifest/index.cjs');
const require_ui_router_defaults = require('./router-defaults.cjs');
let react_jsx_runtime = require("react/jsx-runtime");
//#region src/ui/hydrate.tsx
const mark = (message) => {
const progress = window.__CLIENT_PROGRESS__ ?? [];
window.__CLIENT_PROGRESS__ = [...progress, message];
if ({}.env.DEV) console.log(`[Hydrate] ${message}`);
};
function isAbsoluteHttpUrl(value) {
if (!value) return false;
try {
const url = new URL(value);
return url.protocol === "http:" || url.protocol === "https:";
} catch {
return false;
}
}
/**
* Manifest composition: reconstruct the server's tree from the payload — the
* same manifests, the same MF names, the same construction code. Digest
* parity proves the SSR'd tree and this tree are identical before hydrate;
* any failure degrades to the core-only tree (still a real route tree — the
* page must render, just without plugin routes) rather than leaving the
* router without a tree.
*/
async function composeFromPayload(runtimeConfig, coreRouteConfig, coreManifest) {
const coreUiName = runtimeConfig.ui?.name ?? require_ui_manifest_index.manifest_exports.CORE_UI_PLUGIN_KEY;
const composeCoreOnly = async (candidateManifests) => {
try {
const core = candidateManifests.find((m) => m.name === require_ui_manifest_index.manifest_exports.CORE_UI_PLUGIN_KEY);
if (!core) return void 0;
const tree = await (0, require_ui_manifest_index.manifest_exports.constructTree)({
name: "core-fallback",
plugins: [{
key: core.name,
mfName: coreUiName
}],
resolve: async (ref) => ({
key: ref.key,
manifest: core,
routeConfig: coreRouteConfig
}),
rootOptions: coreRouteConfig.rootMeta
});
mark(`core-only tree constructed: ${tree.manifests.length} source(s), ${tree.nav.items.length} nav item(s)`);
return {
routeTree: tree.rootRoute,
nav: tree.nav,
degraded: true
};
} catch (error) {
console.error("[Hydrate] Core-only fallback failed:", error);
mark(`CORE-ONLY FALLBACK ERROR: ${error.message}`);
return;
}
};
const payload = runtimeConfig.ui?.compose;
if (!payload?.remotes) return coreManifest ? composeCoreOnly([coreManifest]) : void 0;
const parsed = require_ui_manifest_index.manifest_exports.ComposePayloadSchema.safeParse(payload);
if (!parsed.success) {
mark(`compose payload malformed: ${parsed.error.message}`);
return coreManifest ? composeCoreOnly([coreManifest]) : void 0;
}
const { manifests } = parsed.data;
const manifestByKey = new Map(manifests.map((m) => [m.name, m]));
try {
const { registerRemotes, loadRemote } = await import("@module-federation/enhanced/runtime");
mark(`compose payload: digest ${payload.digest}, ${payload.remotes.length} remote(s)`);
registerRemotes(payload.remotes.map((remote) => ({
name: remote.name,
alias: remote.name,
entry: isAbsoluteHttpUrl(remote.manifestUrl) ? remote.manifestUrl : remote.entry.replace(/\/?remoteEntry\.js$/, "/mf-manifest.json")
})));
for (const remote of payload.remotes) mark(`remote registered ${remote.name} @ ${remote.entry}`);
const loadedRouteConfigs = /* @__PURE__ */ new Map();
const dropped = [];
const healthyManifests = [];
for (const manifest of manifests) {
if (manifest.name === require_ui_manifest_index.manifest_exports.CORE_UI_PLUGIN_KEY) {
healthyManifests.push(manifest);
continue;
}
const remote = payload.remotes.find((r) => r.key === manifest.name);
if (!remote) {
dropped.push(manifest.name);
mark(`dropped ${manifest.name} — payload has no matching remote`);
continue;
}
try {
const mod = await loadRemote(`${remote.name}/routeConfig`, { from: "build" });
const routeConfig = mod?.default ?? mod;
if (!routeConfig?.routeConfigLoaders) throw new Error(`loadRemote(${remote.name}/routeConfig) returned no route config`);
loadedRouteConfigs.set(manifest.name, routeConfig);
healthyManifests.push(manifest);
mark(`loaded ${remote.name}/routeConfig`);
} catch (error) {
dropped.push(manifest.name);
mark(`dropped ${manifest.name} — ${error.message}`);
}
}
const tree = await (0, require_ui_manifest_index.manifest_exports.constructTree)({
name: "client",
plugins: healthyManifests.map((manifest) => {
const remote = payload.remotes.find((r) => r.key === manifest.name);
return {
key: manifest.name,
mfName: remote?.name ?? (manifest.name === require_ui_manifest_index.manifest_exports.CORE_UI_PLUGIN_KEY ? coreUiName : manifest.name)
};
}),
resolve: async (ref) => {
if (ref.key === require_ui_manifest_index.manifest_exports.CORE_UI_PLUGIN_KEY) {
const manifest = manifestByKey.get(ref.key);
if (!manifest) throw new Error(`compose payload has no manifest for "${ref.key}"`);
return {
key: ref.key,
manifest,
routeConfig: coreRouteConfig
};
}
const manifest = manifestByKey.get(ref.key);
const routeConfig = loadedRouteConfigs.get(ref.key);
if (!manifest || !routeConfig) throw new Error(`composition source "${ref.key}" is not fully resolved`);
return {
key: ref.key,
manifest,
routeConfig
};
},
rootOptions: coreRouteConfig.rootMeta
});
if (dropped.length > 0) {
mark(`degraded compose: ${dropped.join(", ")} dropped — client-render`);
return {
routeTree: tree.rootRoute,
nav: tree.nav,
degraded: true
};
}
if (tree.digest !== payload.digest) {
console.warn(`[Hydrate] Compose digest mismatch (client ${tree.digest} vs server ${payload.digest}); core-only fallback`);
mark(`DIGEST PARITY FAILURE server=${payload.digest} client=${tree.digest}`);
return composeCoreOnly(manifests);
}
mark(`tree constructed: ${tree.manifests.length} source(s), ${tree.nav.items.length} nav item(s)`);
return {
routeTree: tree.rootRoute,
nav: tree.nav,
degraded: false
};
} catch (error) {
console.error("[Hydrate] Client compose failed; core-only fallback:", error);
mark(`CLIENT COMPOSE ERROR: ${error.message}`);
return composeCoreOnly(manifests);
}
}
function isServerRendered() {
if (document.documentElement.hasAttribute("data-everything-ssr")) return true;
if (window.__EVERYTHING_DEV_SSR__ !== void 0) return window.__EVERYTHING_DEV_SSR__;
return window.$_TSR !== void 0;
}
function watchHydrationConsumed() {
const bootstrapWatch = setInterval(() => {
if (window.$_TSR === void 0) {
clearInterval(bootstrapWatch);
mark("hydration consumed ($_TSR deleted — h() ran)");
}
}, 50);
}
async function hydrate(options) {
if (window.__EVERYTHING_DEV_HYDRATE_PROMISE__) return window.__EVERYTHING_DEV_HYDRATE_PROMISE__;
const hydratePromise = (async () => {
console.log("[Hydrate] Starting...");
const runtimeConfig = options.config ?? require_ui_runtime.getRuntimeConfig();
const cspNonce = require_ui_runtime.getCspNonce();
if (!runtimeConfig.hostUrl || !runtimeConfig.rpcBase) throw new Error("Missing hostUrl or rpcBase in runtime config");
const [{ QueryClientProvider }, routerFactory] = await Promise.all([import("@tanstack/react-query"), options.createRouter ? Promise.resolve(options.createRouter) : Promise.resolve().then(() => require("./router-client.cjs")).then((m) => m.createRouter)]);
const coreRouteConfig = await options.routeConfig().then((mod) => "default" in mod ? mod.default : mod);
const coreManifest = options.manifest ? await options.manifest().then((raw) => typeof raw === "object" && raw !== null && "default" in raw ? raw.default : raw).then((raw) => (0, require_ui_manifest_index.manifest_exports.parsePluginManifest)(raw)) : void 0;
const client = options.createQueryClient?.() ?? require_ui_router_defaults.defaultQueryClient();
mark(`$_TSR present: ${Boolean(window.$_TSR)}`);
const composed = await composeFromPayload(runtimeConfig, coreRouteConfig, coreManifest);
const { router, queryClient: builtClient } = routerFactory({
routeTree: composed?.routeTree,
context: {
pluginNav: composed?.nav,
queryClient: client,
runtimeConfig,
cspNonce,
locale: isServerRendered() ? document.documentElement.lang : void 0,
apiClient: require_ui_api.createApiClient({
hostUrl: runtimeConfig.hostUrl,
rpcBase: runtimeConfig.rpcBase,
connectionError: options.apiConnectionError
}),
authClient: require_ui_auth.createAuthClient({
runtimeConfig,
cspNonce
})
}
});
const providerClient = builtClient ?? client;
if (isServerRendered() && Boolean(composed) && !composed.degraded) {
const { hydrateRoot } = await import("react-dom/client");
const { RouterClient } = await import("@tanstack/react-router/ssr/client");
console.log("[Hydrate] Calling hydrateRoot...");
hydrateRoot(document, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QueryClientProvider, {
client: providerClient,
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouterClient, { router })
}));
watchHydrationConsumed();
} else {
const { createRoot } = await import("react-dom/client");
const { RouterProvider } = await import("@tanstack/react-router");
console.log("[Hydrate] Calling createRoot...");
createRoot(document).render(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(QueryClientProvider, {
client: providerClient,
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouterProvider, { router })
}));
}
if (composed?.degraded) {
const { toast } = await import("sonner");
toast.error("Some application features couldn't load", {
id: "application-compose-error",
description: "Plugin pages are unavailable. Reload to try again.",
duration: Number.POSITIVE_INFINITY,
action: {
label: "Reload",
onClick: () => window.location.reload()
}
});
}
console.log("[Hydrate] Complete!");
})().catch((error) => {
console.error("[Hydrate] Failed:", error);
window.__EVERYTHING_DEV_HYDRATE_PROMISE__ = void 0;
const message = document.createElement("main");
message.setAttribute("role", "alert");
message.dataset.testid = "application-startup-error";
message.className = "min-h-screen flex flex-col items-center justify-center gap-4 bg-background text-foreground p-6";
const heading = document.createElement("h1");
heading.textContent = "The application couldn't load";
const detail = document.createElement("p");
detail.textContent = "Check your connection and reload to try again.";
const retry = document.createElement("button");
retry.textContent = "Reload";
retry.className = "rounded-md border border-border px-4 py-2";
retry.addEventListener("click", () => window.location.reload());
message.append(heading, detail, retry);
document.body.replaceChildren(message);
throw error;
});
window.__EVERYTHING_DEV_HYDRATE_PROMISE__ = hydratePromise;
return hydratePromise;
}
//#endregion
exports.default = hydrate;
exports.hydrate = hydrate;
//# sourceMappingURL=hydrate.cjs.map