diff options
author | 2021-08-13 02:01:41 -0700 | |
---|---|---|
committer | 2021-08-13 02:01:41 -0700 | |
commit | f59892f647ceef1c05e40c9cdef4f79d0a530c2f (patch) | |
tree | 6fed07dc02e722de634618a6f2d246f52510d141 /demos | |
parent | 86642cbdd528bbf37708dbb30a815c9a68b6aea1 (diff) | |
download | bun-f59892f647ceef1c05e40c9cdef4f79d0a530c2f.tar.gz bun-f59892f647ceef1c05e40c9cdef4f79d0a530c2f.tar.zst bun-f59892f647ceef1c05e40c9cdef4f79d0a530c2f.zip |
late
Former-commit-id: 1d598bb05a3bac62d86063125e1fe2962f0b5cc6
Diffstat (limited to 'demos')
-rw-r--r-- | demos/css-stress-test/framework.tsx | 38 | ||||
-rw-r--r-- | demos/css-stress-test/next-env.d.ts | 4 | ||||
-rw-r--r-- | demos/css-stress-test/nexty/client.development.tsx | 33 | ||||
-rw-r--r-- | demos/css-stress-test/nexty/next-server.tsx | 0 | ||||
-rw-r--r-- | demos/css-stress-test/nexty/render.tsx | 0 | ||||
-rw-r--r-- | demos/css-stress-test/nexty/renderDocument.tsx | 568 | ||||
-rw-r--r-- | demos/css-stress-test/nexty/server.development.tsx | 103 | ||||
-rw-r--r-- | demos/css-stress-test/nexty/tsconfig.json | 23 |
8 files changed, 676 insertions, 93 deletions
diff --git a/demos/css-stress-test/framework.tsx b/demos/css-stress-test/framework.tsx deleted file mode 100644 index 22b1a3109..000000000 --- a/demos/css-stress-test/framework.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import ReactDOMServer from "react-dom/server.browser"; - -addEventListener("fetch", async (event: FetchEvent) => { - var route = Wundle.match(event); - console.log("Route", JSON.stringify(route.query, null, 2)); - const { default: PageComponent } = await import(route.filepath); - // const router = Wundle.Router.match(event); - // console.log("Route", router.name); - - // const { Base: Page } = await router.import(); - - const response = new Response(` - <!DOCTYPE html> -<html> - <head> - <link rel="stylesheet" href="./src/index.css" /> - <link - rel="stylesheet" - crossorigin="anonymous" - href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;700&family=Space+Mono:wght@400;700" - /> - </head> - <body> - <link rel="stylesheet" href="./src/index.css" /> - <div id="reactroot">${ReactDOMServer.renderToString( - <PageComponent /> - )}</div> - - <script src="./src/index.tsx" async type="module"></script> - </body> -</html> - `); - - event.respondWith(response); -}); - -// typescript isolated modules -export {}; diff --git a/demos/css-stress-test/next-env.d.ts b/demos/css-stress-test/next-env.d.ts index 7b7aa2c77..9bc3dd46b 100644 --- a/demos/css-stress-test/next-env.d.ts +++ b/demos/css-stress-test/next-env.d.ts @@ -1,2 +1,6 @@ /// <reference types="next" /> /// <reference types="next/types/global" /> +/// <reference types="next/image-types/global" /> + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/demos/css-stress-test/nexty/client.development.tsx b/demos/css-stress-test/nexty/client.development.tsx index 1bc63a0c7..f15ccee28 100644 --- a/demos/css-stress-test/nexty/client.development.tsx +++ b/demos/css-stress-test/nexty/client.development.tsx @@ -1,5 +1,34 @@ -import ReactDOM from "react-dom"; +globalThis.process = { + platform: "posix", + env: {}, +}; + +import * as ReactDOM from "react-dom"; +import App from "next/app"; export default function boot(EntryPointNamespace, loader) { - + _boot(EntryPointNamespace); +} + +function _boot(EntryPointNamespace) { + const next_data_node = document.querySelector("#__NEXT_DATA__"); + if (!next_data_node) { + throw new Error( + "__NEXT_DATA__ is missing. That means something went wrong while rendering on the server." + ); + } + + try { + globalThis.NEXT_DATA = JSON.parse(next_data_node.innerHTML); + } catch (error) { + error.message = `Error parsing __NEXT_DATA__\n${error.message}`; + throw error; + } + + const props = { ...globalThis.NEXT_DATA.props }; + const PageComponent = EntryPointNamespace.default; + ReactDOM.hydrate( + <App Component={PageComponent} pageProps={props.pageProps}></App>, + document.querySelector("#__next") + ); } diff --git a/demos/css-stress-test/nexty/next-server.tsx b/demos/css-stress-test/nexty/next-server.tsx new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/demos/css-stress-test/nexty/next-server.tsx diff --git a/demos/css-stress-test/nexty/render.tsx b/demos/css-stress-test/nexty/render.tsx new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/demos/css-stress-test/nexty/render.tsx diff --git a/demos/css-stress-test/nexty/renderDocument.tsx b/demos/css-stress-test/nexty/renderDocument.tsx new file mode 100644 index 000000000..3ada60c86 --- /dev/null +++ b/demos/css-stress-test/nexty/renderDocument.tsx @@ -0,0 +1,568 @@ +import * as App from "next/app"; +import { AmpStateContext } from "next/dist/shared/lib/amp-context"; +import { HeadManagerContext } from "next/dist/shared/lib/head-manager-context"; +import Loadable from "next/dist/shared/lib/loadable"; +import { LoadableContext } from "next/dist/shared/lib/loadable-context"; +import { RouterContext } from "next/dist/shared/lib/router-context"; +import { NextRouter } from "next/dist/shared/lib/router/router"; +import { + AppType, + ComponentsEnhancer, + DocumentInitialProps, + DocumentProps, + DocumentType, + getDisplayName, + loadGetInitialProps, + NextComponentType, + RenderPage, + RenderPageResult, +} from "next/dist/shared/lib/utils"; +import * as NextDocument from "next/document"; +import * as ReactDOMServer from "react-dom/server.browser"; +import * as url from "url"; + +const dev = process.env.NODE_ENV === "development"; + +type ParsedUrlQuery = Record<string, string | string[]>; + +const isJSFile = (file: string) => + file.endsWith(".js") || + file.endsWith(".mjs") || + file.endsWith(".ts") || + file.endsWith(".tsx"); + +function getScripts(files: DocumentFiles) { + const { context, props } = this; + const { + assetPrefix, + buildManifest, + isDevelopment, + devOnlyCacheBusterQueryString, + disableOptimizedLoading, + } = context; + const normalScripts = files.allFiles.filter(isJSFile); + const lowPriorityScripts = buildManifest.lowPriorityFiles?.filter(isJSFile); + + return [...normalScripts, ...lowPriorityScripts].map((file) => { + return ( + <script + key={file} + src={`${assetPrefix}/_next/${encodeURI( + file + )}${devOnlyCacheBusterQueryString}`} + nonce={props.nonce} + async + crossOrigin={props.crossOrigin || process.env.__NEXT_CROSS_ORIGIN} + type="module" + /> + ); + }); +} + +interface DomainLocale { + defaultLocale: string; + domain: string; + http?: true; + locales?: string[]; +} + +function renderDocument( + Document: DocumentType, + { + buildManifest, + docComponentsRendered, + props, + docProps, + pathname, + query, + buildId, + canonicalBase, + assetPrefix, + runtimeConfig, + nextExport, + autoExport, + isFallback, + dynamicImportsIds, + dangerousAsPath, + err, + dev, + ampPath, + ampState, + inAmpMode, + hybridAmp, + dynamicImports, + headTags, + gsp, + gssp, + customServer, + gip, + appGip, + unstable_runtimeJS, + unstable_JsPreload, + devOnlyCacheBusterQueryString, + scriptLoader, + locale, + locales, + defaultLocale, + domainLocales, + isPreview, + disableOptimizedLoading, + }: RenderOpts & { + props: any; + docComponentsRendered: DocumentProps["docComponentsRendered"]; + docProps: DocumentInitialProps; + pathname: string; + query: ParsedUrlQuery; + dangerousAsPath: string; + ampState: any; + ampPath: string; + inAmpMode: boolean; + hybridAmp: boolean; + dynamicImportsIds: (string | number)[]; + dynamicImports: string[]; + headTags: any; + isFallback?: boolean; + gsp?: boolean; + gssp?: boolean; + customServer?: boolean; + gip?: boolean; + appGip?: boolean; + devOnlyCacheBusterQueryString: string; + scriptLoader: any; + isPreview?: boolean; + autoExport?: boolean; + } +): string { + return ( + "<!DOCTYPE html>" + + ReactDOMServer.renderToStaticMarkup( + <AmpStateContext.Provider value={ampState}> + {Document.renderDocument(Document, { + __NEXT_DATA__: { + props, // The result of getInitialProps + page: pathname, // The rendered page + query, // querystring parsed / passed by the user + buildId, // buildId is used to facilitate caching of page bundles, we send it to the client so that pageloader knows where to load bundles + assetPrefix: assetPrefix === "" ? undefined : assetPrefix, // send assetPrefix to the client side when configured, otherwise don't sent in the resulting HTML + runtimeConfig, // runtimeConfig if provided, otherwise don't sent in the resulting HTML + nextExport, // If this is a page exported by `next export` + autoExport, // If this is an auto exported page + isFallback, + dynamicIds: + dynamicImportsIds.length === 0 ? undefined : dynamicImportsIds, + err: err || undefined, // err: err ? serializeError(dev, err) : undefined, // Error if one happened, otherwise don't sent in the resulting HTML + gsp, // whether the page is getStaticProps + gssp, // whether the page is getServerSideProps + customServer, // whether the user is using a custom server + gip, // whether the page has getInitialProps + appGip, // whether the _app has getInitialProps + locale, + locales, + defaultLocale, + domainLocales, + isPreview, + }, + buildManifest, + docComponentsRendered, + dangerousAsPath, + canonicalBase, + ampPath, + inAmpMode, + isDevelopment: !!dev, + hybridAmp, + dynamicImports, + assetPrefix, + headTags, + unstable_runtimeJS, + unstable_JsPreload, + devOnlyCacheBusterQueryString, + scriptLoader, + locale, + disableOptimizedLoading, + ...docProps, + })} + </AmpStateContext.Provider> + ) + ); +} + +class ServerRouter implements NextRouter { + route: string; + pathname: string; + query: ParsedUrlQuery; + asPath: string; + basePath: string; + events: any; + isFallback: boolean; + locale?: string; + isReady: boolean; + locales?: string[]; + defaultLocale?: string; + domainLocales?: DomainLocale[]; + isPreview: boolean; + isLocaleDomain: boolean; + + constructor( + pathname: string, + query: ParsedUrlQuery, + as: string, + { isFallback }: { isFallback: boolean }, + isReady: boolean, + basePath: string, + locale?: string, + locales?: string[], + defaultLocale?: string, + domainLocales?: DomainLocale[], + isPreview?: boolean, + isLocaleDomain?: boolean + ) { + this.route = pathname.replace(/\/$/, "") || "/"; + this.pathname = pathname; + this.query = query; + this.asPath = as; + this.isFallback = isFallback; + this.basePath = basePath; + this.locale = locale; + this.locales = locales; + this.defaultLocale = defaultLocale; + this.isReady = isReady; + this.domainLocales = domainLocales; + this.isPreview = !!isPreview; + this.isLocaleDomain = !!isLocaleDomain; + } + + push(): any { + noRouter(); + } + replace(): any { + noRouter(); + } + reload() { + noRouter(); + } + back() { + noRouter(); + } + prefetch(): any { + noRouter(); + } + beforePopState() { + noRouter(); + } +} + +function noRouter() { + const message = + 'No router instance found. you should only use "next/router" inside the client side of your app. https://nextjs.org/docs/messages/no-router-instance'; + throw new Error(message); +} + +function enhanceComponents( + options: ComponentsEnhancer, + App: AppType, + Component: NextComponentType +): { + App: AppType; + Component: NextComponentType; +} { + // For backwards compatibility + if (typeof options === "function") { + return { + App, + Component: options(Component), + }; + } + + return { + App: options.enhanceApp ? options.enhanceApp(App) : App, + Component: options.enhanceComponent + ? options.enhanceComponent(Component) + : Component, + }; +} + +Object.defineProperty(NextDocument.Head.prototype, "getScripts", { + get() { + return getScripts; + }, +}); +Object.defineProperty(NextDocument.NextScript.prototype, "getScripts", { + get() { + return getScripts; + }, +}); + +export async function render({ + route, + PageNamespace, + AppNamespace, + appStylesheets = [], + pageStylesheets = [], + DocumentNamespace = null, + buildId, +}: { + buildId: number; + route: any; + PageNamespace: { default: NextComponentType<any> }; + AppNamespace: { default: NextComponentType<any> } | null; + DocumentNamespace: Object | null; + appStylesheets: string[]; + pageStylesheets: string[]; +}): Promise<Response> { + const { default: Component, getStaticProps = null } = PageNamespace || {}; + const { default: AppComponent_ } = AppNamespace || {}; + var query = Object.assign({}, route.query); + + // These are reversed in our Router versus Next.js...mostly due to personal preference. + const pathname = "/" + route.name; + var asPath = route.pathname; + + const AppComponent = AppComponent_ || App.default; + const Document = + (DocumentNamespace && DocumentNamespace.default) || NextDocument.default; + // Document.Html.prototype.getScripts = getScripts; + // } + + const callMiddleware = async (method: string, args: any[], props = false) => { + let results: any = props ? {} : []; + + if ((Document as any)[`${method}Middleware`]) { + let middlewareFunc = await (Document as any)[`${method}Middleware`]; + middlewareFunc = middlewareFunc.default || middlewareFunc; + + const curResults = await middlewareFunc(...args); + if (props) { + for (const result of curResults) { + results = { + ...results, + ...result, + }; + } + } else { + results = curResults; + } + } + return results; + }; + + const headTags = (...args: any) => callMiddleware("headTags", args); + + const isFallback = !!query.__nextFallback; + delete query.__nextFallback; + delete query.__nextLocale; + delete query.__nextDefaultLocale; + + const isSSG = !!getStaticProps; + const isBuildTimeSSG = isSSG && false; + const defaultAppGetInitialProps = + App.getInitialProps === (App as any).origGetInitialProps; + + const hasPageGetInitialProps = !!(Component as any).getInitialProps; + const pageIsDynamic = route.kind === "dynamic"; + const isAutoExport = false; + + if (isAutoExport || isFallback) { + // remove query values except ones that will be set during export + query = { + ...(query.amp + ? { + amp: query.amp, + } + : {}), + }; + asPath = `${asPath}${ + // ensure trailing slash is present for non-dynamic auto-export pages + asPath.endsWith("/") && asPath !== "/" && !pageIsDynamic ? "/" : "" + }`; + } + + let head: JSX.Element[] = [ + <meta charSet="utf-8" />, + <meta name="viewport" content="width=device-width" />, + ]; + + const nextExport = isAutoExport || isFallback; + const reactLoadableModules: string[] = []; + var scriptLoader = {}; + const AppContainer = ({ children }: any) => ( + <RouterContext.Provider value={router}> + {/* <AmpStateContext.Provider value={ampState}> */} + <HeadManagerContext.Provider + value={{ + updateHead: (state) => { + head = state; + }, + updateScripts: (scripts) => { + scriptLoader = scripts; + }, + scripts: {}, + mountedInstances: new Set(), + }} + > + <LoadableContext.Provider + value={(moduleName) => reactLoadableModules.push(moduleName)} + > + {children} + </LoadableContext.Provider> + </HeadManagerContext.Provider> + {/* </AmpStateContext.Provider> */} + </RouterContext.Provider> + ); + + await Loadable.preloadAll(); // Make sure all dynamic imports are loaded + + const router = new ServerRouter( + pathname, + query, + asPath, + { + isFallback: isFallback, + }, + true, + Wundle.origin, + null, + [], // renderOpts.locales, + null, //renderOpts.defaultLocale, + [], // renderOpts.domainLocales, + false, + false + ); + + const ctx = { + err: null, + req: undefined, + res: undefined, + pathname, + query, + asPath, + locale: null, + locales: [], + defaultLocale: null, + AppTree: (props: any) => { + return ( + <AppContainer> + <App {...props} Component={Component} router={router} /> + </AppContainer> + ); + }, + }; + + var props = await loadGetInitialProps(AppComponent, { + AppTree: ctx.AppTree, + Component, + router, + ctx, + }); + + const renderToString = ReactDOMServer.renderToString; + const ErrorDebug = null; + + const renderPage: RenderPage = ( + options: ComponentsEnhancer = {} + ): RenderPageResult | Promise<RenderPageResult> => { + if (ctx.err && ErrorDebug) { + const htmlOrPromise = renderToString(<ErrorDebug error={ctx.err} />); + return typeof htmlOrPromise === "string" + ? { html: htmlOrPromise, head } + : htmlOrPromise.then((html) => ({ + html, + head, + })); + } + + if (dev && (props.router || props.Component)) { + throw new Error( + `'router' and 'Component' can not be returned in getInitialProps from _app.js https://nextjs.org/docs/messages/cant-override-next-props` + ); + } + + const { App: EnhancedApp, Component: EnhancedComponent } = + enhanceComponents(options, AppComponent, Component); + + const htmlOrPromise = renderToString( + <AppContainer> + <EnhancedApp Component={EnhancedComponent} router={router} {...props} /> + </AppContainer> + ); + return typeof htmlOrPromise === "string" + ? { html: htmlOrPromise, head } + : htmlOrPromise.then((html) => ({ + html, + head, + })); + }; + + const documentCtx = { ...ctx, renderPage }; + const docProps: DocumentInitialProps = await loadGetInitialProps( + Document, + documentCtx + ); + + if (!docProps || typeof docProps.html !== "string") { + const message = `"${getDisplayName( + Document + )}.getInitialProps()" should resolve to an object with a "html" prop set with a valid html string`; + throw new Error(message); + } + + const renderOpts = { + params: route.params, + }; + // renderOpts.params = _params || params; + + // parsedUrl.pathname = denormalizePagePath(parsedUrl.pathname!); + // renderOpts.resolvedUrl = formatUrl({ + // ...parsedUrl, + // query: origQuery, + // }); + const docComponentsRendered: DocumentProps["docComponentsRendered"] = {}; + const isPreview = false; + const getServerSideProps = PageNamespace.getServerSideProps; + let html = renderDocument(Document, { + docComponentsRendered, + ...renderOpts, + disableOptimizedLoading: false, + canonicalBase: Wundle.origin, + buildManifest: { + devFiles: [], + allFiles: [], + polyfillFiles: [], + lowPriorityFiles: [], + pages: { + "/_app": [Wundle.routesDir + "_app", ...appStylesheets], + [pathname]: [...pageStylesheets, route.scriptSrc], + }, + }, + // Only enabled in production as development mode has features relying on HMR (style injection for example) + unstable_runtimeJS: true, + // process.env.NODE_ENV === "production" + // ? pageConfig.unstable_runtimeJS + // : undefined, + // unstable_JsPreload: pageConfig.unstable_JsPreload, + unstable_JsPreload: true, + dangerousAsPath: router.asPath, + ampState: undefined, + props, + assetPrefix: "", + headTags: await headTags(documentCtx), + isFallback, + docProps, + pathname, + ampPath: undefined, + query, + inAmpMode: false, + hybridAmp: undefined, + dynamicImportsIds: [], // Array.from(dynamicImportsIds), + dynamicImports: [], //Array.from(dynamicImports), + gsp: !!getStaticProps ? true : undefined, + gssp: !!getServerSideProps ? true : undefined, + gip: hasPageGetInitialProps ? true : undefined, + appGip: !defaultAppGetInitialProps ? true : undefined, + devOnlyCacheBusterQueryString: "", + scriptLoader, + isPreview: isPreview === true ? true : undefined, + autoExport: isAutoExport === true ? true : undefined, + nextExport: nextExport === true ? true : undefined, + }).replaceAll("/_next//_next", "/_next"); + return new Response(html); +} diff --git a/demos/css-stress-test/nexty/server.development.tsx b/demos/css-stress-test/nexty/server.development.tsx index ab330359e..78017f885 100644 --- a/demos/css-stress-test/nexty/server.development.tsx +++ b/demos/css-stress-test/nexty/server.development.tsx @@ -1,70 +1,67 @@ -import ReactDOMServer from "react-dom/server.browser"; +import { render } from "./renderDocument"; -addEventListener( - "fetch", +let buildId = 0; - // Anything imported in here will automatically reload in development. - // The module registry cache is reset at the end of each page load - async (event: FetchEvent) => { - var appRoute; +var DocumentNamespacePromise; +DocumentNamespacePromise = import(Wundle.routesDir + "_document"); +var DocumentLoaded = false; +var DocumentNamespace; + +addEventListener("fetch", async (event: FetchEvent) => { + if (!DocumentLoaded) { + DocumentLoaded = true; try { - appRoute = await import(Wundle.routesDir + "_app"); + DocumentNamespace = await DocumentNamespacePromise; } catch (exception) { - appRoute = null; + DocumentNamespace = null; } + } - var route = Wundle.match(event); - - // This imports the currently matched route. - const { default: PageComponent } = await import(route.filePath); - - // This returns all .css files that were imported in the line above. - // It's recursive, so any file that imports a CSS file will be included. - const stylesheets = Wundle.getImportedStyles() as string[]; - - // Ordinarily, this is just the formatted filepath URL (rewritten to match the public url of the HTTP server) - // But, when you set `client` in the package.json for the framework, this becomes a path like this: - // "/pages/index.js" -> "pages/index.entry.js" ("entry" is for entry point) - const src = route.scriptSrc; - - // From there, the inside of that script like this: - // ``` - // import * as Framework from 'framework-path'; - // import * as EntryPoint from 'entry-point'; - // - // Framework.default(EntryPoint); - // ``` - // That's how the client-side framework loads + var appRoute; - const response = new Response(` - <!DOCTYPE html> -<html> - <head> - ${stylesheets - .map((style) => `<link rel="stylesheet" href="${style}">`) - .join("\n")} + try { + appRoute = await import(Wundle.routesDir + "_app"); + } catch (exception) { + appRoute = null; + } + const appStylesheets = Wundle.getImportedStyles() as string[]; - <link - rel="stylesheet" - crossorigin="anonymous" - href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;700&family=Space+Mono:wght@400;700" - /> - </head> - <body> + var route = Wundle.match(event); - <div id="#__next">${ReactDOMServer.renderToString(<PageComponent />)}</div> + // This imports the currently matched route. + const PageNamespace = await import(route.filePath); - <script src="${src}" async type="module"></script> - </body> -</html> - `); + // This returns all .css files that were imported in the line above. + // It's recursive, so any file that imports a CSS file will be included. + const pageStylesheets = Wundle.getImportedStyles() as string[]; - event.respondWith(response); - } -); + event.respondWith( + await render({ + route, + PageNamespace, + appStylesheets, + pageStylesheets, + DocumentNamespace, + AppNamespace: appRoute, + buildId, + }) + ); + buildId++; +}); // typescript isolated modules export {}; declare var Wundle: any; + +function getNextData(request: Request, route) { + return { + NEXT_DATA: { + query: route.query, + props: {}, + page: route.path, + buildId: buildId.toString(16), + }, + }; +} diff --git a/demos/css-stress-test/nexty/tsconfig.json b/demos/css-stress-test/nexty/tsconfig.json new file mode 100644 index 000000000..679268d71 --- /dev/null +++ b/demos/css-stress-test/nexty/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": ["dom", "dom.iterable", "esnext", "WebWorker"], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "baseUrl": ".", + "paths": { + "path": ["node_modules/path-browserify"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} |