From 9d5ec1c8c7abf10797c663768cc56710be88ab8c Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Tue, 30 Aug 2022 17:17:44 +0200 Subject: [PATCH] WIP: support shared app state --- examples/framework-preact/astro.config.mjs | 4 ++- .../src/components/Context.tsx | 4 +++ .../src/components/Counter.tsx | 12 ++++---- examples/framework-preact/src/pages/_app.tsx | 10 +++++++ .../framework-preact/src/pages/index.astro | 4 ++- packages/astro/src/@types/astro.ts | 2 ++ .../index.ts | 11 ++++++++ packages/integrations/preact/app.js | 1 + packages/integrations/preact/client.js | 13 +++++---- packages/integrations/preact/package.json | 1 + packages/integrations/preact/server.js | 5 +++- packages/integrations/preact/src/index.ts | 28 ++++++++++++++++--- 12 files changed, 76 insertions(+), 19 deletions(-) create mode 100644 examples/framework-preact/src/components/Context.tsx create mode 100644 examples/framework-preact/src/pages/_app.tsx create mode 100644 packages/integrations/preact/app.js diff --git a/examples/framework-preact/astro.config.mjs b/examples/framework-preact/astro.config.mjs index b1c8d1150..50e8b1299 100644 --- a/examples/framework-preact/astro.config.mjs +++ b/examples/framework-preact/astro.config.mjs @@ -4,5 +4,7 @@ import preact from '@astrojs/preact'; // https://astro.build/config export default defineConfig({ // Enable Preact to support Preact JSX components. - integrations: [preact()], + integrations: [preact({ + appEntrypoint: '/src/pages/_app.tsx' + })], }); diff --git a/examples/framework-preact/src/components/Context.tsx b/examples/framework-preact/src/components/Context.tsx new file mode 100644 index 000000000..2c697bd36 --- /dev/null +++ b/examples/framework-preact/src/components/Context.tsx @@ -0,0 +1,4 @@ +import { createContext } from 'preact'; + +const noop = () => {}; +export const Context = createContext({ count: 0, increment: noop, decrement: noop }); diff --git a/examples/framework-preact/src/components/Counter.tsx b/examples/framework-preact/src/components/Counter.tsx index 61a9f9d5a..39d8ca76e 100644 --- a/examples/framework-preact/src/components/Counter.tsx +++ b/examples/framework-preact/src/components/Counter.tsx @@ -1,18 +1,16 @@ -import { h, Fragment } from 'preact'; -import { useState } from 'preact/hooks'; +import { useContext } from 'preact/hooks'; +import { Context } from './Context'; import './Counter.css'; export default function Counter({ children }) { - const [count, setCount] = useState(0); - const add = () => setCount((i) => i + 1); - const subtract = () => setCount((i) => i - 1); + const { count, increment, decrement } = useContext(Context); return ( <>
- +
{count}
- +
{children}
diff --git a/examples/framework-preact/src/pages/_app.tsx b/examples/framework-preact/src/pages/_app.tsx new file mode 100644 index 000000000..6d73433bf --- /dev/null +++ b/examples/framework-preact/src/pages/_app.tsx @@ -0,0 +1,10 @@ +import { Context } from "../components/Context"; +import { useState } from "preact/hooks"; + +export default function ({ children }) { + const [count, setCount] = useState(0); + const increment = () => setCount(v => v + 1) + const decrement = () => setCount(v => v - 1); + + return {children} +} diff --git a/examples/framework-preact/src/pages/index.astro b/examples/framework-preact/src/pages/index.astro index a6565f6c1..6adbb2d15 100644 --- a/examples/framework-preact/src/pages/index.astro +++ b/examples/framework-preact/src/pages/index.astro @@ -1,7 +1,6 @@ --- // Component Imports import Counter from '../components/Counter'; - // Full Astro Component Syntax: // https://docs.astro.build/core-concepts/astro-components/ --- @@ -28,6 +27,9 @@ import Counter from '../components/Counter';

Hello, Preact!

+ +

Hello, Preact!

+
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 5cd7abe24..9c8bec4b1 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -1099,6 +1099,8 @@ export interface AstroRenderer { clientEntrypoint?: string; /** Import entrypoint for the server/build/ssr renderer. */ serverEntrypoint: string; + /** User-provided entrypoint for the browser app instance */ + appEntrypoint?: string; /** JSX identifier (e.g. 'react' or 'solid-js') */ jsxImportSource?: string; /** Babel transform options */ diff --git a/packages/astro/src/vite-plugin-integrations-container/index.ts b/packages/astro/src/vite-plugin-integrations-container/index.ts index f386cd055..1a242065b 100644 --- a/packages/astro/src/vite-plugin-integrations-container/index.ts +++ b/packages/astro/src/vite-plugin-integrations-container/index.ts @@ -16,5 +16,16 @@ export default function astroIntegrationsContainerPlugin({ configureServer(server) { runHookServerSetup({ config, server, logging }); }, + async resolveId(id, importer, options) { + if (id.startsWith('virtual:@astrojs/') && id.endsWith('/app')) { + const rendererName = id.slice('virtual:'.length, '/app'.length * -1); + const match = config._ctx.renderers.find(({ name }) => name === rendererName); + if (match && match.appEntrypoint) { + const app = await this.resolve(match.appEntrypoint, importer, { ...options, skipSelf: true }); + return app; + } + return id.slice('virtual:'.length) + } + } }; } diff --git a/packages/integrations/preact/app.js b/packages/integrations/preact/app.js new file mode 100644 index 000000000..7d0d3796b --- /dev/null +++ b/packages/integrations/preact/app.js @@ -0,0 +1 @@ +export { Fragment as default } from 'preact'; diff --git a/packages/integrations/preact/client.js b/packages/integrations/preact/client.js index 78d8720f0..072341ecb 100644 --- a/packages/integrations/preact/client.js +++ b/packages/integrations/preact/client.js @@ -1,14 +1,17 @@ -import { h, render } from 'preact'; +import { h } from 'preact'; +import { createPortal } from 'preact/compat'; import StaticHtml from './static-html.js'; export default (element) => (Component, props, { default: children, ...slotted }) => { if (!element.hasAttribute('ssr')) return; + const { addChild } = globalThis['@astrojs/preact']; + while (!!element.firstElementChild) { + element.firstElementChild.remove(); + } for (const [key, value] of Object.entries(slotted)) { props[key] = h(StaticHtml, { value, name: key }); } - render( - h(Component, props, children != null ? h(StaticHtml, { value: children }) : children), - element - ); + const Portal = createPortal(h(Component, props, children != null ? h(StaticHtml, { value: children }) : children), element) + addChild(Portal); }; diff --git a/packages/integrations/preact/package.json b/packages/integrations/preact/package.json index aa1fe4729..df567992e 100644 --- a/packages/integrations/preact/package.json +++ b/packages/integrations/preact/package.json @@ -21,6 +21,7 @@ "homepage": "https://docs.astro.build/en/guides/integrations-guide/preact/", "exports": { ".": "./dist/index.js", + "./app": "./app.js", "./client.js": "./client.js", "./server.js": "./server.js", "./package.json": "./package.json" diff --git a/packages/integrations/preact/server.js b/packages/integrations/preact/server.js index f5b1a34e5..f150ca230 100644 --- a/packages/integrations/preact/server.js +++ b/packages/integrations/preact/server.js @@ -1,6 +1,7 @@ import { h, Component as BaseComponent } from 'preact'; import render from 'preact-render-to-string'; import StaticHtml from './static-html.js'; +import Provider from 'virtual:@astrojs/preact/app'; const slotName = (str) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase()); @@ -44,7 +45,9 @@ function renderToStaticMarkup(Component, props, { default: children, ...slotted // Note: create newProps to avoid mutating `props` before they are serialized const newProps = { ...props, ...slots }; const html = render( - h(Component, newProps, children != null ? h(StaticHtml, { value: children }) : children) + h(Provider, {}, + h(Component, newProps, children != null ? h(StaticHtml, { value: children }) : children) + ) ); return { html }; } diff --git a/packages/integrations/preact/src/index.ts b/packages/integrations/preact/src/index.ts index 5c5ed0363..85bbcf9b3 100644 --- a/packages/integrations/preact/src/index.ts +++ b/packages/integrations/preact/src/index.ts @@ -1,10 +1,11 @@ import { AstroIntegration, AstroRenderer, ViteUserConfig } from 'astro'; -function getRenderer(): AstroRenderer { +function getRenderer(appEntrypoint?: string): AstroRenderer { return { name: '@astrojs/preact', clientEntrypoint: '@astrojs/preact/client.js', serverEntrypoint: '@astrojs/preact/server.js', + appEntrypoint, jsxImportSource: 'preact', jsxTransformOptions: async () => { const { @@ -92,13 +93,32 @@ function getViteConfiguration(compat?: boolean): ViteUserConfig { return viteConfig; } -export default function ({ compat }: { compat?: boolean } = {}): AstroIntegration { +export default function ({ compat, appEntrypoint }: { compat?: boolean, appEntrypoint?: string } = {}): AstroIntegration { return { name: '@astrojs/preact', hooks: { - 'astro:config:setup': ({ addRenderer, updateConfig }) => { + 'astro:config:setup': ({ addRenderer, updateConfig, injectScript }) => { if (compat) addRenderer(getCompatRenderer()); - addRenderer(getRenderer()); + injectScript('before-hydration', `import { h, Fragment, render } from "preact"; +import { useState } from "preact/hooks"; +import Provider from "virtual:@astrojs/preact/app"; + +let addChild = () => {}; +const App = ({ children: c }) => { + const [children, setChildren] = useState([c]); + addChild = (child) => setChildren(v => ([...v, child])); + return h(Fragment, {}, children) +} + +const el = document.createElement('astro-app'); +el.setAttribute('renderer', '@astrojs/preact'); +document.body.appendChild(el); + +render(h(Provider, {}, h(App, {})), el) +globalThis['@astrojs/preact'] = { + addChild +}`) + addRenderer(getRenderer(appEntrypoint)); updateConfig({ vite: getViteConfiguration(compat), });