diff options
author | 2021-09-05 21:44:43 -0700 | |
---|---|---|
committer | 2021-09-05 21:44:43 -0700 | |
commit | 2fc89a69e35525acbd3613779c31ad6b37ea885f (patch) | |
tree | 4d06b02306435cc823fb532ccf84cd93b01a4663 /packages/bun-framework-next/client.development.tsx | |
parent | cb70d08573e72a0c470f1aaf2aa3eed755912014 (diff) | |
download | bun-2fc89a69e35525acbd3613779c31ad6b37ea885f.tar.gz bun-2fc89a69e35525acbd3613779c31ad6b37ea885f.tar.zst bun-2fc89a69e35525acbd3613779c31ad6b37ea885f.zip |
Move
Former-commit-id: b795168d774fa0b3dcde7955e30baa0eefbc88ab
Diffstat (limited to 'packages/bun-framework-next/client.development.tsx')
-rw-r--r-- | packages/bun-framework-next/client.development.tsx | 415 |
1 files changed, 415 insertions, 0 deletions
diff --git a/packages/bun-framework-next/client.development.tsx b/packages/bun-framework-next/client.development.tsx new file mode 100644 index 000000000..b93b1fcce --- /dev/null +++ b/packages/bun-framework-next/client.development.tsx @@ -0,0 +1,415 @@ +globalThis.global = globalThis; +globalThis.Bun_disableCSSImports = true; + +import * as React from "react"; +var onlyChildPolyfill = React.Children.only; +React.Children.only = function (children) { + if (children && typeof children === "object" && children.length == 1) { + return onlyChildPolyfill(children[0]); + } + + return onlyChildPolyfill(children); +}; + +import * as ReactDOM from "react-dom"; +import App from "next/app"; +import mitt, { MittEmitter } from "next/dist/shared/lib/mitt"; +import { RouterContext } from "next/dist/shared/lib/router-context"; +import Router, { + AppComponent, + AppProps, + delBasePath, + hasBasePath, + PrivateRouteInfo, +} from "next/dist/shared/lib/router/router"; + +import * as NextRouteLoader from "next/dist/client/route-loader"; +import { isDynamicRoute } from "next/dist/shared/lib/router/utils/is-dynamic"; +import { + urlQueryToSearchParams, + assign, +} from "next/dist/shared/lib/router/utils/querystring"; +import { setConfig } from "next/dist/shared/lib/runtime-config"; +import { + getURL, + loadGetInitialProps, + NEXT_DATA, + ST, +} from "next/dist/shared/lib/utils"; +// import { Portal } from "next/dist/client/portal"; +import initHeadManager from "next/dist/client/head-manager"; +import { HeadManagerContext } from "next/dist/shared/lib/head-manager-context"; +import PageLoader from "./page-loader"; +import measureWebVitals from "next/dist/client/performance-relayer"; +import { RouteAnnouncer } from "next/dist/client/route-announcer"; +import { + createRouter, + makePublicRouterInstance, +} from "next/dist/client/router"; +export const emitter: MittEmitter<string> = mitt(); + +declare global { + interface Window { + /* test fns */ + __NEXT_HYDRATED?: boolean; + __NEXT_HYDRATED_CB?: () => void; + + /* prod */ + __NEXT_PRELOADREADY?: (ids?: (string | number)[]) => void; + __NEXT_DATA__: NEXT_DATA; + __NEXT_P: any[]; + } +} + +function nextDataFromBunData() { + const { + router: { routes, route, params: paramsList }, + problems, + } = globalThis.__BUN_DATA__; + + const paramsMap = new Map(); + for (let i = 0; i < paramsList.keys.length; i++) { + paramsMap.set( + decodeURIComponent(paramsList.keys[i]), + decodeURIComponent(paramsList.values[i]) + ); + } + + const params = {}; + var url = new URL(location.href); + Object.assign(params, Object.fromEntries(url.searchParams.entries())); + Object.assign(params, Object.fromEntries(paramsMap.entries())); + + const pages = routes.reduce((acc, route) => { + var name = route.substring(route.indexOf("_next") + "_next/".length); + + while (name.startsWith("/")) { + name = name.substring(1); + } + + if (name.startsWith("pages")) { + name = name.substring("pages".length); + } + + while (name.startsWith("/")) { + name = name.substring(1); + } + + if (name.endsWith(".jsx")) { + name = name.substring(0, name.length - ".jsx".length); + } + + if (name.endsWith(".tsx")) { + name = name.substring(0, name.length - ".tsx".length); + } + + if (name.endsWith(".ts")) { + name = name.substring(0, name.length - ".ts".length); + } + + if (name.endsWith(".js")) { + name = name.substring(0, name.length - ".js".length); + } + + acc["/" + name] = [route]; + return acc; + }, {}); + + return { + page: routes[route], + buildId: "1234", + assetPrefix: "", + isPreview: false, + locale: null, + locales: [], + isFallback: false, + err: null, + props: {}, + query: params, + pages, + }; +} + +type RenderRouteInfo = PrivateRouteInfo & { + App: AppComponent; + scroll?: { x: number; y: number } | null; +}; +type RenderErrorProps = Omit<RenderRouteInfo, "Component" | "styleSheets">; + +const nextDataTag = document.getElementById("__NEXT_DATA__"); + +const data: typeof window["__NEXT_DATA__"] = nextDataTag + ? JSON.parse(document.getElementById("__NEXT_DATA__")!.textContent!) + : nextDataFromBunData(); +window.__NEXT_DATA__ = data; + +const { + props: hydrateProps, + err: hydrateErr, + page, + query, + buildId, + assetPrefix, + runtimeConfig, + dynamicIds, + isFallback, + locale, + locales, + domainLocales, + isPreview, +} = data; + +const prefix: string = assetPrefix || ""; + +setConfig({ + serverRuntimeConfig: {}, + publicRuntimeConfig: runtimeConfig || {}, +}); + +let asPath: string = getURL(); + +// make sure not to attempt stripping basePath for 404s +if (hasBasePath(asPath)) { + asPath = delBasePath(asPath); +} + +export const pageLoader: PageLoader = new PageLoader( + buildId, + prefix, + data.pages +); + +const headManager: { + mountedInstances: Set<unknown>; + updateHead: (head: JSX.Element[]) => void; +} = initHeadManager(); +const appElement: HTMLElement | null = document.getElementById("__next"); + +let lastRenderReject: (() => void) | null; +let webpackHMR: any; +export let router: Router; +let CachedApp: AppComponent, onPerfEntry: (metric: any) => void; + +export default function boot(EntryPointNamespace, loader) { + _boot(EntryPointNamespace).then(() => {}, false); +} + +class Container extends React.Component<{ + fn: (err: Error, info?: any) => void; +}> { + componentDidCatch(componentErr: Error, info: any) { + this.props.fn(componentErr, info); + } + + componentDidMount() { + this.scrollToHash(); + + // We need to replace the router state if: + // - the page was (auto) exported and has a query string or search (hash) + // - it was auto exported and is a dynamic route (to provide params) + // - if it is a client-side skeleton (fallback render) + if ( + router.isSsr && + // We don't update for 404 requests as this can modify + // the asPath unexpectedly e.g. adding basePath when + // it wasn't originally present + page !== "/404" && + page !== "/_error" && + (isFallback || + (data.nextExport && + (isDynamicRoute(router.pathname) || + location.search || + process.env.__NEXT_HAS_REWRITES)) || + (hydrateProps && + hydrateProps.__N_SSG && + (location.search || process.env.__NEXT_HAS_REWRITES))) + ) { + // update query on mount for exported pages + router.replace( + router.pathname + + "?" + + String( + assign( + urlQueryToSearchParams(router.query), + new URLSearchParams(location.search) + ) + ), + asPath, + { + // @ts-ignore + // WARNING: `_h` is an internal option for handing Next.js + // client-side hydration. Your app should _never_ use this property. + // It may change at any time without notice. + _h: 1, + // Fallback pages must trigger the data fetch, so the transition is + // not shallow. + // Other pages (strictly updating query) happens shallowly, as data + // requirements would already be present. + shallow: !isFallback, + } + ); + } + } + + componentDidUpdate() { + this.scrollToHash(); + } + + scrollToHash() { + let { hash } = location; + hash = hash && hash.substring(1); + if (!hash) return; + + const el: HTMLElement | null = document.getElementById(hash); + if (!el) return; + + // If we call scrollIntoView() in here without a setTimeout + // it won't scroll properly. + setTimeout(() => el.scrollIntoView(), 0); + } + + render() { + return this.props.children; + } +} + +let CachedComponent: React.ComponentType; + +const wrapApp = + (App: AppComponent) => + (wrappedAppProps: Record<string, any>): JSX.Element => { + const appProps: AppProps = { + ...wrappedAppProps, + Component: CachedComponent, + err: hydrateErr, + router, + }; + return ( + <AppContainer> + <App {...appProps} /> + </AppContainer> + ); + }; + +function AppContainer({ + children, +}: React.PropsWithChildren<{}>): React.ReactElement { + return ( + <Container fn={(error) => <div>{JSON.stringify(error)}</div>}> + <RouterContext.Provider value={makePublicRouterInstance(router)}> + <HeadManagerContext.Provider value={headManager}> + {children} + </HeadManagerContext.Provider> + </RouterContext.Provider> + </Container> + ); +} + +export async function _boot(EntryPointNamespace, isError) { + NextRouteLoader.default.getClientBuildManifest = () => Promise.resolve({}); + + const PageComponent = EntryPointNamespace.default; + + const appScripts = globalThis.__NEXT_DATA__.pages["/_app"]; + if (appScripts && appScripts.length > 0) { + let appSrc; + for (let asset of appScripts) { + if (!asset.endsWith(".css")) { + appSrc = asset; + break; + } + } + + if (appSrc) { + const AppModule = await import(appSrc); + console.assert( + AppModule.default, + appSrc + " must have a default export'd React component" + ); + + CachedApp = AppModule.default; + } else { + CachedApp = App; + } + } + + router = createRouter(page, query, asPath, { + initialProps: hydrateProps, + pageLoader, + App: CachedApp, + Component: CachedComponent, + wrapApp, + err: null, + isFallback: Boolean(isFallback), + subscription: async (info, App, scroll) => { + return render( + Object.assign< + {}, + Omit<RenderRouteInfo, "App" | "scroll">, + Pick<RenderRouteInfo, "App" | "scroll"> + >({}, info, { + App, + scroll, + }) + ); + }, + locale, + locales, + defaultLocale: "", + domainLocales, + isPreview, + }); + + globalThis.next.router = router; + + if (isError) { + ReactDOM.render( + <TopLevelRender + App={CachedApp} + Component={PageComponent} + props={{ pageProps: hydrateProps }} + />, + document.querySelector("#__next") + ); + } else { + ReactDOM.hydrate( + <TopLevelRender + App={CachedApp} + Component={PageComponent} + props={{ pageProps: hydrateProps }} + />, + document.querySelector("#__next") + ); + } +} + +function TopLevelRender({ App, Component, props, scroll }) { + return ( + <AppContainer scroll={scroll}> + <App Component={Component} {...props}></App> + </AppContainer> + ); +} + +export function render(props) { + ReactDOM.render( + <TopLevelRender {...props} />, + document.querySelector("#__next") + ); +} + +export function renderError(e) { + ReactDOM.render( + <AppContainer> + <App Component={<div>UH OH!!!!</div>} pageProps={data.props}></App> + </AppContainer>, + document.querySelector("#__next") + ); +} + +globalThis.next = { + version: "11.1.0", + emitter, + render, + renderError, +}; |