aboutsummaryrefslogtreecommitdiff
path: root/packages/bun-framework-next/client.development.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/bun-framework-next/client.development.tsx')
-rw-r--r--packages/bun-framework-next/client.development.tsx123
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,