From ad8058123c7f5c234c52eb23b94599a9afa4fb36 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Sat, 10 Apr 2021 23:54:47 -0500 Subject: [PATCH] fix(#150): improve hydration props --- .../src/components/Clock.tsx | 8 ++-- .../src/components/Static/index.tsx | 4 +- .../partial-hydration/src/pages/index.tsx | 18 +++++++- .../microsite/assets/microsite-runtime.js | 21 +++++----- packages/microsite/package-lock.json | 30 ++++++++++++++ packages/microsite/package.json | 5 ++- packages/microsite/src/cli/microsite-dev.tsx | 33 ++++++++++++--- packages/microsite/src/document.tsx | 30 +++++++++++--- packages/microsite/src/hydrate.tsx | 41 +++++++++++++++---- packages/microsite/src/utils/build.tsx | 24 +++++++++-- packages/microsite/src/utils/serialize.ts | 11 +++++ yarn.lock | 30 ++++++++++++-- 12 files changed, 210 insertions(+), 45 deletions(-) create mode 100644 packages/microsite/package-lock.json create mode 100644 packages/microsite/src/utils/serialize.ts diff --git a/examples/partial-hydration/src/components/Clock.tsx b/examples/partial-hydration/src/components/Clock.tsx index 25c35093..d6aff31d 100644 --- a/examples/partial-hydration/src/components/Clock.tsx +++ b/examples/partial-hydration/src/components/Clock.tsx @@ -2,10 +2,10 @@ import { FunctionalComponent } from "preact"; import { useEffect, useState } from "preact/hooks"; import { withHydrate } from "microsite/hydrate"; -const Clock: FunctionalComponent<{ initialDate: string }> = ({ - initialDate, -}) => { - const [date, setDate] = useState(initialDate); +const Clock: FunctionalComponent<{ initialDate: Date }> = ({ initialDate }) => { + const [date, setDate] = useState( + (initialDate || new Date()).toLocaleString().replace(", ", " at ") + ); useEffect(() => { let id = setInterval(() => { diff --git a/examples/partial-hydration/src/components/Static/index.tsx b/examples/partial-hydration/src/components/Static/index.tsx index ab637712..565ba6e0 100644 --- a/examples/partial-hydration/src/components/Static/index.tsx +++ b/examples/partial-hydration/src/components/Static/index.tsx @@ -1,12 +1,12 @@ import { h, FunctionalComponent } from "preact"; -const Static: FunctionalComponent<{ renderedAt: string }> = ({ +const Static: FunctionalComponent<{ renderedAt: Date }> = ({ renderedAt, children, }) => { return (
-

Page rendered on {renderedAt}

+

Page rendered on {(renderedAt || new Date()).toLocaleString()}

{children} diff --git a/examples/partial-hydration/src/pages/index.tsx b/examples/partial-hydration/src/pages/index.tsx index ab1f9389..4ba3508d 100644 --- a/examples/partial-hydration/src/pages/index.tsx +++ b/examples/partial-hydration/src/pages/index.tsx @@ -18,7 +18,21 @@ const Index: FunctionalComponent = ({ renderedAt }) => {
- + @@ -37,7 +51,7 @@ export default definePage(Index, { async getStaticProps() { return { props: { - renderedAt: new Date().toLocaleString().replace(", ", " at "), + renderedAt: new Date(), }, }; }, diff --git a/packages/microsite/assets/microsite-runtime.js b/packages/microsite/assets/microsite-runtime.js index 57850071..b4e30da9 100644 --- a/packages/microsite/assets/microsite-runtime.js +++ b/packages/microsite/assets/microsite-runtime.js @@ -34,18 +34,23 @@ const createObserver = (hydrate) => { return io; }; -function attach(fragment, data, { key, name, source }, cb) { - const { p: { children = null, ...props } = {}, m: method = "idle", f: flush } = data; +function attach(fragment, data, { name, source }, cb) { + const { + p: propKey, + m: method = "idle", + f: flush, + } = data; const hydrate = async () => { if (window.__MICROSITE_DEBUG) console.log(`[Hydrate] <${key} /> hydrated via "${method}"`); const { [name]: Component } = await import(source); - + const props = window.__MICROSITE_PROPS[propKey] || {}; + if (flush) { - render(h(Component, props, children), fragment); + render(h(Component, props), fragment); } else { - rehydrate(h(Component, props, children), fragment); + rehydrate(h(Component, props), fragment); } if (cb) cb(); }; @@ -108,11 +113,7 @@ function parseHydrateBoundary(node) { let result = ATTR_REGEX.exec(text); while (result) { let [, attr, val] = result; - if (attr === "p") { - props[attr] = JSON.parse(val); - } else { - props[attr] = val; - } + props[attr] = val; result = ATTR_REGEX.exec(text); } return props; diff --git a/packages/microsite/package-lock.json b/packages/microsite/package-lock.json new file mode 100644 index 00000000..fb6946ad --- /dev/null +++ b/packages/microsite/package-lock.json @@ -0,0 +1,30 @@ +{ + "name": "microsite", + "version": "1.2.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "astring": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.7.4.tgz", + "integrity": "sha512-WiVqDJV0AayUUH65FfUrbnBO4KD10854cyU49lK30+2n/lEkJDRqBKj/2fYGhZSD3uIt1H1VfW/pQtO07kR2Xg==" + }, + "data-uri-to-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", + "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==" + }, + "fetch-blob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-2.1.1.tgz", + "integrity": "sha512-Uf+gxPCe1hTOFXwkxYyckn8iUSk6CFXGy5VENZKifovUTZC9eUODWSBhOBS7zICGrAetKzdwLMr85KhIcePMAQ==" + }, + "node-fetch": { + "version": "3.0.0-beta.9", + "requires": { + "data-uri-to-buffer": "^3.0.1", + "fetch-blob": "^2.1.1" + } + } + } +} diff --git a/packages/microsite/package.json b/packages/microsite/package.json index 000730d1..61da6da1 100644 --- a/packages/microsite/package.json +++ b/packages/microsite/package.json @@ -46,7 +46,9 @@ "@prefresh/snowpack": "^3.1.1", "@snowpack/plugin-dotenv": "^2.0.5", "arg": "^5.0.0", + "astring": "^1.7.4", "esbuild": "^0.9.5", + "estree-util-value-to-estree": "^1.2.0", "execa": "^4.0.3", "globby": "^11.0.1", "kleur": "^4.1.3", @@ -55,9 +57,10 @@ "path-browserify": "^1.0.1", "polka": "^0.5.2", "preact": "^10.5.13", - "preact-render-to-string": "^5.1.16", + "preact-render-to-string": "^5.1.19", "rollup": "^2.32.1", "rollup-plugin-styles": "^3.11.0", + "shorthash": "^0.0.2", "sirv": "^1.0.10", "snowpack": "^3.1.1" }, diff --git a/packages/microsite/src/cli/microsite-dev.tsx b/packages/microsite/src/cli/microsite-dev.tsx index 79384391..84cf26e7 100644 --- a/packages/microsite/src/cli/microsite-dev.tsx +++ b/packages/microsite/src/cli/microsite-dev.tsx @@ -9,6 +9,7 @@ import { readDir } from "../utils/fs.js"; import { promises as fsp } from "fs"; import { ErrorProps } from "error.js"; import { loadConfiguration } from "../utils/command.js"; +import { serializeToJsString } from "../utils/serialize.js"; import { h, FunctionalComponent } from "preact"; import { generateStaticPropsContext, @@ -23,6 +24,7 @@ let renderToString: any; let csrSrc: string; let Document: any; let __HeadContext: any; +let __PageContext: any; let __InternalDocContext: any; let ErrorPage: any; let errorSrc: string; @@ -69,10 +71,12 @@ const renderPage = async ( exports: { Document: InternalDocument, __HeadContext: __Head, + __PageContext: __Page, __InternalDocContext: __Doc, }, } = await runtime.importModule(documentSrc); __HeadContext = __Head; + __PageContext = __Page; __InternalDocContext = __Doc; try { const { @@ -152,24 +156,40 @@ const renderPage = async ( }, }; + const pageContext = { + props: { + current: {}, + }, + }; + const HeadProvider: FunctionalComponent = ({ children }) => { return <__HeadContext.Provider value={headContext} {...{ children }} />; }; + const PageProvider: FunctionalComponent = ({ children }) => { + return <__PageContext.Provider value={pageContext} {...{ children }} />; + }; + const { __renderPageResult, ...docProps } = await Document.prepare({ renderPage: async () => ({ __renderPageResult: renderToString( - - - + + + + + ), }), }); const docContext = { dev: componentPath, - devProps: pageProps ?? {}, + devProps: + pageProps && Object.keys(pageProps).length > 0 + ? serializeToJsString(pageProps) + : "{}", __csrUrl: csrSrc, + __renderPageProps: pageContext.props.current, __renderPageHead: headContext.head.current, __renderPageResult, }; @@ -351,8 +371,11 @@ export default async function dev( res.setHeader("Content-Type", result.contentType); const MIME_EXCLUDE = ["image", "font"]; + const isMeta = req.url.indexOf("/_snowpack/") !== -1; + const isMicrosite = req.url.indexOf("/microsite/") !== -1; if ( - req.url.indexOf("/_snowpack/pkg/microsite") === -1 && + !isMeta && + !isMicrosite && result.contentType && !MIME_EXCLUDE.includes(result.contentType.split("/")[0]) ) { diff --git a/packages/microsite/src/document.tsx b/packages/microsite/src/document.tsx index bd88b86e..5c1debd1 100644 --- a/packages/microsite/src/document.tsx +++ b/packages/microsite/src/document.tsx @@ -14,6 +14,10 @@ export const __HeadContext = createContext({ head: { current: [] }, }); +export const __PageContext = createContext({ + props: { current: {} }, +}); + /** @internal */ export const __InternalDocContext = createContext({}); @@ -58,10 +62,12 @@ export const Html: FunctionalComponent> = ({ ...props }) => ; -export const Main: FunctionalComponent, - "id" | "dangerouslySetInnerHTML" | "children" ->> = (props) => { +export const Main: FunctionalComponent< + Omit< + JSX.HTMLAttributes, + "id" | "dangerouslySetInnerHTML" | "children" + > +> = (props) => { const { __renderPageResult } = useContext(__InternalDocContext); return (
> = ({ export const MicrositeScript: FunctionalComponent = () => { const { __csrUrl, + __renderPageProps, debug, hasGlobalScript, basePath, @@ -136,6 +143,8 @@ export const MicrositeScript: FunctionalComponent = () => { devProps, } = useContext(__InternalDocContext); + const propsMap = __renderPageProps ? Object.entries(__renderPageProps) : []; + return ( {dev && ( @@ -152,7 +161,7 @@ export const MicrositeScript: FunctionalComponent = () => { dangerouslySetInnerHTML={{ __html: `import csr from '${__csrUrl}'; import Page from '${dev}'; -csr(Page, ${JSON.stringify(devProps)});`, +csr(Page, ${devProps});`, }} />