diff options
Diffstat (limited to 'packages/bun-framework-next/client.development.tsx')
| -rw-r--r-- | packages/bun-framework-next/client.development.tsx | 123 |
1 files changed, 108 insertions, 15 deletions
diff --git a/packages/bun-framework-next/client.development.tsx b/packages/bun-framework-next/client.development.tsx index 1c51a97e8..fd8e43a20 100644 --- a/packages/bun-framework-next/client.development.tsx +++ b/packages/bun-framework-next/client.development.tsx @@ -41,6 +41,8 @@ import { createRouter, makePublicRouterInstance, } from "next/dist/client/router"; +import { packageVersion } from "macro:./packageVersion"; +import NextHead from "next/head"; export const emitter: MittEmitter<string> = mitt(); @@ -61,6 +63,7 @@ function nextDataFromBunData() { const { router: { routes, route, params: paramsList }, } = globalThis.__BUN_DATA__; + const appStyles = globalThis.__BUN_APP_STYLES || []; const paramsMap = new Map(); for (let i = 0; i < paramsList.keys.length; i++) { @@ -76,8 +79,19 @@ function nextDataFromBunData() { Object.assign(params, Object.fromEntries(paramsMap.entries())); const pages = routes.keys.reduce((acc, routeName, i) => { + if (!routes.values[i].startsWith("/_next/")) { + routes.values[i] = + "/_next/" + + routes.values[i].substring(routes.values[i].startsWith("/") ? 1 : 0); + } + const routePath = routes.values[i]; - acc[routeName] = [routePath]; + if (routeName === "/_app" && appStyles.length) { + acc[routeName] = [routePath, ...appStyles]; + } else { + acc[routeName] = [routePath]; + } + return acc; }, {}); @@ -108,6 +122,11 @@ const data: NEXT_DATA & { pages: Record<string, string[]> } = nextDataTag ? JSON.parse(document.getElementById("__NEXT_DATA__")!.textContent!) : nextDataFromBunData(); +var headManager: { + mountedInstances: Set<unknown>; + updateHead: (head: JSX.Element[]) => void; +} = initHeadManager(); + window.__NEXT_DATA__ = data; const { @@ -142,19 +161,33 @@ if (hasBasePath(asPath)) { asPath = delBasePath(asPath); } +window.__DEV_PAGES_MANIFEST = pages; export const pageLoader: PageLoader = new PageLoader(buildId, prefix, pages); -const headManager: { - mountedInstances: Set<unknown>; - updateHead: (head: JSX.Element[]) => void; -} = initHeadManager(); - export let router: Router; let CachedApp: AppComponent = null; - +var ranBoot = false; export default function boot(EntryPointNamespace) { - _boot(EntryPointNamespace, false); + if (ranBoot) return; + ranBoot = true; + switch (document.readyState) { + case "loading": { + document.addEventListener( + "DOMContentLoaded", + () => boot(EntryPointNamespace), + { + once: true, + passive: true, + } + ); + break; + } + case "interactive": + case "complete": { + return _boot(EntryPointNamespace, false); + } + } } class Container extends React.Component<{ @@ -280,7 +313,9 @@ class BootError extends Error { } export async function _boot(EntryPointNamespace, isError) { - NextRouteLoader.getClientBuildManifest = () => Promise.resolve({}); + NextRouteLoader.getClientBuildManifest = () => { + return Promise.resolve({}); + }; const PageComponent = EntryPointNamespace.default; @@ -291,6 +326,7 @@ export async function _boot(EntryPointNamespace, isError) { // @ts-expect-error CachedApp = NextApp; CachedComponent = PageComponent; + const styleSheets = []; if (appScripts && appScripts.length > 0) { let appSrc; @@ -302,6 +338,7 @@ export async function _boot(EntryPointNamespace, isError) { } if (appSrc) { + const initialHeadCount = document?.head?.children?.length ?? 0; const AppModule = await import(appSrc); console.assert( @@ -312,9 +349,50 @@ export async function _boot(EntryPointNamespace, isError) { if ("default" in AppModule) { CachedApp = AppModule.default; } + + if (pageLoader.cssQueue.length > 0) { + await Promise.allSettled(pageLoader.cssQueue.slice()); + pageLoader.cssQueue.length = 0; + } + + const newCount = document?.head?.children?.length ?? 0; + if (newCount > initialHeadCount && newCount > 1) { + // Move any <App />-inserted nodes to the beginning, preserving the order + // This way if there are stylesheets they appear in the expected order + var firstNonMetaTag = document.head.children.length; + + for (let i = 0; i < document.head.childNodes.length; i++) { + if (document.head.children[i].tagName !== "META") { + firstNonMetaTag = i; + break; + } + } + + if (firstNonMetaTag !== document.head.children.length) { + outer: for (let i = newCount - 1; i > initialHeadCount - 1; i--) { + const node = document.head.children[i]; + if ( + node.tagName === "LINK" && + node.hasAttribute("href") && + node.href + ) { + const normalized = new URL(node.href, location.origin).href; + for (let script of appScripts) { + if (new URL(script, location.origin).href === normalized) + continue outer; + } + + appScripts.push(normalized); + } + styleSheets.push(node); + } + } + } } } + headManager = initHeadManager(); + router = createRouter(page, query, asPath, { initialProps: hydrateProps, pageLoader, @@ -356,12 +434,27 @@ export async function _boot(EntryPointNamespace, isError) { domEl = nextEl; } + const StylePreserver = () => { + React.useEffect(() => { + for (let i = 0; i < styleSheets.length; i++) { + if (!document.head.contains(styleSheets[i])) { + document.head.appendChild(styleSheets[i]); + } + } + }, []); + + return null; + }; + const reactEl = ( - <TopLevelRender - App={CachedApp} - Component={PageComponent} - props={hydrateProps} - /> + <> + <TopLevelRender + App={CachedApp} + Component={PageComponent} + props={hydrateProps} + /> + <StylePreserver /> + </> ); if (USE_REACT_18) { @@ -436,7 +529,7 @@ export function renderError(e) { } globalThis.next = { - version: "12.0.4", + version: packageVersion("next"), emitter, render, renderError, |
