UNPKG

remix-island

Version:

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

8 lines (7 loc) 5.36 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": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,mBAAmD;AACnD,uBAA6B;AAC7B,IAAAA,gBAA4B;AAC5B,oBAA+B;AAQ/B,IAAI,gBAAgB;AAKb,SAAS,WACd,MACA,EAAE,KAAK,gBAAgB,UAAU,KAAK,IAAoB,CAAC,GAC5C;AACf,QAAM,OAAsB,CAAC,UAAU;AACrC,UAAM,CAAC,SAAS,UAAU,QAAI,uBAAS,aAAa;AACpD,gCAAU,MAAM;AACd,UAAI,SAAS;AACX,sBAAc,IAAI;AAAA,MACpB;AACA,sBAAgB;AAChB,iBAAW,IAAI;AAAA,IACjB,GAAG,CAAC,CAAC;AAEL,QAAI,CAAC,WAAW,MAAM,8BAA8B;AAClD,iBAAO,4BAAc,IAAI;AAAA,IAC3B;AAEA,QAAI,SAAS;AACX,iBAAO,mCAAa,4BAAc,IAAI,GAAG,SAAS,IAAI;AAAA,IACxD;AAEA,WAAO;AAAA,EACT;AAEA,OAAK,cAAc;AACnB,OAAK,oBAAoB;AAEzB,SAAO;AACT;AAQO,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AACF,GAA2B;AACzB,QAAM,WAAO;AAAA,QACX,4BAAc,2BAAa;AAAA,MACzB,SAAS,oBAAoB,cAAc,IAAI;AAAA,MAC/C,KAAK,QAAQ;AAAA,IACf,CAAC;AAAA,EACH;AACA,QAAM,KAAK,KAAK;AAChB,SAAO,OAAO,EAAE,YAAY,IAAI,OAAO,EAAE;AAC3C;AAEO,SAAS,oBACd,cACA,MACc;AAzEhB;AA0EE,MAAI,sBAAsB,aAAa;AACvC,MAAI,qBAAqB;AACvB,UAAM,gBAAgB,KAAK,MAAM,mBAAmB;AAEpD,UAAO,+CAAe,UAAtB,wBAA6B;AAC7B,0BAAsB,KAAK,UAAU,aAAa;AAAA,EACpD;AAEA,QACE,kBAAa,aAAa,MADpB,SAlFV,IAmFI,IADiB,oCACjB,IADiB,CAAX;AAGR,SAAO,iCACF,eADE;AAAA,IAEL;AAAA,IACA,sBAAsB,iCACjB,aAAa,uBADI;AAAA,MAEpB,QAAQ;AAAA;AAAA,IACV;AAAA,IACA,cAAc,iCACT,aAAa,eADJ;AAAA,MAEZ,MAAM,iCACD,0BADC;AAAA,QAEJ,SAAS,UACP,4BAAc,MAAM,EAAE,8BAA8B,KAAK,CAAC;AAAA,MAC9D;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,cACd,MACA,SAAsB,SAAS,MAC/B;AACA,MAAI,iBAAiB;AACrB,QAAM,gBAA6B,CAAC;AACpC,QAAM,KAAK,KAAK;AAChB,aAAW,QAAQ,OAAO,YAAY;AACpC,QAAI,CAAC,kBAAkB,KAAK,aAAa,YAAY;AACnD;AAAA,IACF;AACA,QACE,kBACA,KAAK,aAAa,cAClB,KAAK,cAAc,GAAG,EAAE,QACxB;AACA,oBAAc,KAAK,IAAI;AACvB;AAAA,IACF;AACA,QACE,kBACC,KAAK,aAAa,cAAc,KAAK,cAAc,GAAG,EAAE,UACzD;AACA,uBAAiB;AACjB,oBAAc,KAAK,IAAI;AAAA,IACzB;AAAA,EACF;AACA,aAAW,QAAQ,eAAe;AAChC,SAAK,OAAO;AAAA,EACd;AACF;", "names": ["import_react"] }