UNPKG

remix-island

Version:

utils to render remix into a dom-node instead of the whole document

8 lines (7 loc) 5.53 kB
{ "version": 3, "sources": ["../src/index.ts"], "sourcesContent": ["import type { EntryContext } from '@remix-run/server-runtime';\nimport type { ComponentType } from 'react';\nimport { createElement, useEffect, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { RemixServer } from '@remix-run/react';\nimport { renderToString } from 'react-dom/server';\n\ntype HeadComponent = ComponentType<{\n __remix_island_render_server?: boolean;\n}> & {\n __remix_island_id?: string;\n};\n\nlet globalMounted = false;\nexport interface CreateHeadOpts {\n id?: string;\n cleanup?: boolean;\n}\nexport function createHead(\n Comp: ComponentType,\n { id = 'remix-island', cleanup = true }: CreateHeadOpts = {},\n): HeadComponent {\n const Head: HeadComponent = (props) => {\n const [mounted, setMounted] = useState(globalMounted);\n useEffect(() => {\n if (cleanup) {\n removeOldHead(Head);\n }\n globalMounted = true;\n setMounted(true);\n }, []);\n\n if (!mounted && props.__remix_island_render_server) {\n return createElement(Comp);\n }\n\n if (mounted) {\n return createPortal(createElement(Comp), document.head);\n }\n\n return null;\n };\n\n Head.displayName = 'RemixIslandHead';\n Head.__remix_island_id = id;\n\n return Head;\n}\n\nexport interface RenderHeadToStringOpts {\n request: Request;\n remixContext: EntryContext;\n Head: HeadComponent;\n}\n\nexport function renderHeadToString({\n request,\n remixContext,\n Head,\n}: RenderHeadToStringOpts) {\n const head = renderToString(\n createElement(RemixServer, {\n context: switchRootComponent(remixContext, Head),\n url: request.url,\n }),\n );\n const id = Head.__remix_island_id;\n return `<!--${id}-start-->${head}<!--${id}-end-->`;\n}\n\nexport function switchRootComponent(\n remixContext: EntryContext,\n Head: HeadComponent,\n): EntryContext {\n let serverHandoffString = remixContext.serverHandoffString;\n if (serverHandoffString) {\n const serverHandoff = JSON.parse(serverHandoffString);\n // remove errors from JSON string\n delete serverHandoff?.state?.errors;\n serverHandoffString = JSON.stringify(serverHandoff);\n }\n\n const { Layout, ...rootModuleWithoutLayout } =\n remixContext.routeModules.root!;\n\n return {\n ...remixContext,\n serverHandoffString,\n staticHandlerContext: {\n ...remixContext.staticHandlerContext,\n errors: null, // remove errors from context\n },\n routeModules: {\n ...remixContext.routeModules,\n root: {\n ...rootModuleWithoutLayout,\n default: () =>\n createElement(Head, { __remix_island_render_server: true }),\n },\n },\n };\n}\n\nexport function removeOldHead(\n Head: HeadComponent,\n parent: HTMLElement = document.head,\n) {\n let foundOldHeader = false;\n const nodesToRemove: ChildNode[] = [];\n const id = Head.__remix_island_id;\n for (const node of parent.childNodes) {\n if (!foundOldHeader && node.nodeName !== '#comment') {\n continue;\n }\n if (\n foundOldHeader &&\n node.nodeName === '#comment' &&\n node.nodeValue === `${id}-end`\n ) {\n nodesToRemove.push(node);\n break;\n }\n if (\n foundOldHeader ||\n (node.nodeName === '#comment' && node.nodeValue === `${id}-start`)\n ) {\n foundOldHeader = true;\n nodesToRemove.push(node);\n }\n }\n for (const node of nodesToRemove) {\n node.remove();\n }\n}\n"], "mappings": "AAEA,OAAS,iBAAAA,EAAe,aAAAC,EAAW,YAAAC,MAAgB,QACnD,OAAS,gBAAAC,MAAoB,YAC7B,OAAS,eAAAC,MAAmB,mBAC5B,OAAS,kBAAAC,MAAsB,mBAQ/B,IAAIC,EAAgB,GAKb,SAASC,EACdC,EACA,CAAE,GAAAC,EAAK,eAAgB,QAAAC,EAAU,EAAK,EAAoB,CAAC,EAC5C,CACf,MAAMC,EAAuBC,GAAU,CACrC,KAAM,CAACC,EAASC,CAAU,EAAIZ,EAASI,CAAa,EASpD,OARAL,EAAU,IAAM,CACVS,GACFK,EAAcJ,CAAI,EAEpBL,EAAgB,GAChBQ,EAAW,EAAI,CACjB,EAAG,CAAC,CAAC,EAED,CAACD,GAAWD,EAAM,6BACbZ,EAAcQ,CAAI,EAGvBK,EACKV,EAAaH,EAAcQ,CAAI,EAAG,SAAS,IAAI,EAGjD,IACT,EAEA,OAAAG,EAAK,YAAc,kBACnBA,EAAK,kBAAoBF,EAElBE,CACT,CAQO,SAASK,EAAmB,CACjC,QAAAC,EACA,aAAAC,EACA,KAAAP,CACF,EAA2B,CACzB,MAAMQ,EAAOd,EACXL,EAAcI,EAAa,CACzB,QAASgB,EAAoBF,EAAcP,CAAI,EAC/C,IAAKM,EAAQ,GACf,CAAC,CACH,EACMR,EAAKE,EAAK,kBAChB,MAAO,OAAOF,CAAE,YAAYU,CAAI,OAAOV,CAAE,SAC3C,CAEO,SAASW,EACdF,EACAP,EACc,CACd,IAAIU,EAAsBH,EAAa,oBACvC,GAAIG,EAAqB,CACvB,MAAMC,EAAgB,KAAK,MAAMD,CAAmB,EAEpD,OAAOC,GAAe,OAAO,OAC7BD,EAAsB,KAAK,UAAUC,CAAa,CACpD,CAEA,KAAM,CAAE,OAAAC,EAAQ,GAAGC,CAAwB,EACzCN,EAAa,aAAa,KAE5B,MAAO,CACL,GAAGA,EACH,oBAAAG,EACA,qBAAsB,CACpB,GAAGH,EAAa,qBAChB,OAAQ,IACV,EACA,aAAc,CACZ,GAAGA,EAAa,aAChB,KAAM,CACJ,GAAGM,EACH,QAAS,IACPxB,EAAcW,EAAM,CAAE,6BAA8B,EAAK,CAAC,CAC9D,CACF,CACF,CACF,CAEO,SAASI,EACdJ,EACAc,EAAsB,SAAS,KAC/B,CACA,IAAIC,EAAiB,GACrB,MAAMC,EAA6B,CAAC,EAC9BlB,EAAKE,EAAK,kBAChB,UAAWiB,KAAQH,EAAO,WACxB,GAAI,GAACC,GAAkBE,EAAK,WAAa,YAGzC,IACEF,GACAE,EAAK,WAAa,YAClBA,EAAK,YAAc,GAAGnB,CAAE,OACxB,CACAkB,EAAc,KAAKC,CAAI,EACvB,KACF,EAEEF,GACCE,EAAK,WAAa,YAAcA,EAAK,YAAc,GAAGnB,CAAE,YAEzDiB,EAAiB,GACjBC,EAAc,KAAKC,CAAI,GAG3B,UAAWA,KAAQD,EACjBC,EAAK,OAAO,CAEhB", "names": ["createElement", "useEffect", "useState", "createPortal", "RemixServer", "renderToString", "globalMounted", "createHead", "Comp", "id", "cleanup", "Head", "props", "mounted", "setMounted", "removeOldHead", "renderHeadToString", "request", "remixContext", "head", "switchRootComponent", "serverHandoffString", "serverHandoff", "Layout", "rootModuleWithoutLayout", "parent", "foundOldHeader", "nodesToRemove", "node"] }