diff options
Diffstat (limited to 'packages/integrations/preact')
-rw-r--r-- | packages/integrations/preact/client.js | 4 | ||||
-rw-r--r-- | packages/integrations/preact/package.json | 43 | ||||
-rw-r--r-- | packages/integrations/preact/server.js | 35 | ||||
-rw-r--r-- | packages/integrations/preact/src/index.ts | 45 | ||||
-rw-r--r-- | packages/integrations/preact/static-html.js | 24 | ||||
-rw-r--r-- | packages/integrations/preact/tsconfig.json | 10 |
6 files changed, 161 insertions, 0 deletions
diff --git a/packages/integrations/preact/client.js b/packages/integrations/preact/client.js new file mode 100644 index 000000000..85c18c76c --- /dev/null +++ b/packages/integrations/preact/client.js @@ -0,0 +1,4 @@ +import { h, render } from 'preact'; +import StaticHtml from './static-html.js'; + +export default (element) => (Component, props, children) => render(h(Component, props, children != null ? h(StaticHtml, { value: children }) : children), element); diff --git a/packages/integrations/preact/package.json b/packages/integrations/preact/package.json new file mode 100644 index 000000000..f360e91e6 --- /dev/null +++ b/packages/integrations/preact/package.json @@ -0,0 +1,43 @@ +{ + "name": "@astrojs/preact", + "description": "Use Preact components within Astro", + "version": "0.0.1", + "type": "module", + "types": "./dist/index.d.ts", + "author": "withastro", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/withastro/astro.git", + "directory": "packages/integrations/preact" + }, + "bugs": "https://github.com/withastro/astro/issues", + "homepage": "https://astro.build", + "exports": { + ".": "./dist/index.js", + "./client": "./client", + "./client.js": "./client.js", + "./server": "./server", + "./server.js": "./server.js", + "./package.json": "./package.json" + }, + "scripts": { + "build": "astro-scripts build \"src/**/*.ts\" && tsc", + "dev": "astro-scripts dev \"src/**/*.ts\"" + }, + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.16.7", + "preact-render-to-string": "^5.1.19" + }, + "devDependencies": { + "astro": "workspace:*", + "astro-scripts": "workspace:*", + "preact": "^10.6.5" + }, + "peerDependencies": { + "preact": "^10.6.5" + }, + "engines": { + "node": "^14.15.0 || >=16.0.0" + } +} diff --git a/packages/integrations/preact/server.js b/packages/integrations/preact/server.js new file mode 100644 index 000000000..25b1a1530 --- /dev/null +++ b/packages/integrations/preact/server.js @@ -0,0 +1,35 @@ +import { h, Component as BaseComponent } from 'preact'; +import render from 'preact-render-to-string'; +import StaticHtml from './static-html.js'; + +function check(Component, props, children) { + if (typeof Component !== 'function') return false; + + if (Component.prototype != null && typeof Component.prototype.render === 'function') { + return BaseComponent.isPrototypeOf(Component); + } + + try { + const { html } = renderToStaticMarkup(Component, props, children); + if (typeof html !== 'string') { + return false; + } + + // There are edge cases (SolidJS) where Preact *might* render a string, + // but components would be <undefined></undefined> + + return !/\<undefined\>/.test(html); + } catch (err) { + return false; + } +} + +function renderToStaticMarkup(Component, props, children) { + const html = render(h(Component, props, children != null ? h(StaticHtml, { value: children }) : children)); + return { html }; +} + +export default { + check, + renderToStaticMarkup, +}; diff --git a/packages/integrations/preact/src/index.ts b/packages/integrations/preact/src/index.ts new file mode 100644 index 000000000..113284c31 --- /dev/null +++ b/packages/integrations/preact/src/index.ts @@ -0,0 +1,45 @@ +import { AstroIntegration } from 'astro'; + +function getRenderer() { + return { + name: '@astrojs/preact', + clientEntrypoint: '@astrojs/preact/client', + serverEntrypoint: '@astrojs/preact/server', + jsxImportSource: 'preact', + jsxTransformOptions: async () => { + const { + default: { default: jsx }, + // @ts-expect-error types not found + } = await import('@babel/plugin-transform-react-jsx'); + return { + plugins: [jsx({}, { runtime: 'automatic', importSource: 'preact' })], + }; + }, + }; +} + +function getViteConfiguration() { + return { + optimizeDeps: { + include: ['@astrojs/preact/client', 'preact', 'preact/jsx-runtime', 'preact-render-to-string'], + exclude: ['@astrojs/preact/server'], + }, + ssr: { + external: ['preact-render-to-string'], + }, + }; +} + +export default function (): AstroIntegration { + return { + name: '@astrojs/preact', + hooks: { + 'astro:config:setup': ({ addRenderer }) => { + addRenderer(getRenderer()); + return { + vite: getViteConfiguration(), + }; + }, + }, + }; +} diff --git a/packages/integrations/preact/static-html.js b/packages/integrations/preact/static-html.js new file mode 100644 index 000000000..9af8002a7 --- /dev/null +++ b/packages/integrations/preact/static-html.js @@ -0,0 +1,24 @@ +import { h } from 'preact'; + +/** + * Astro passes `children` as a string of HTML, so we need + * a wrapper `div` to render that content as VNodes. + * + * As a bonus, we can signal to Preact that this subtree is + * entirely static and will never change via `shouldComponentUpdate`. + */ +const StaticHtml = ({ value }) => { + if (!value) return null; + return h('astro-fragment', { dangerouslySetInnerHTML: { __html: value } }); +}; + +/** + * This tells Preact to opt-out of re-rendering this subtree, + * In addition to being a performance optimization, + * this also allows other frameworks to attach to `children`. + * + * See https://preactjs.com/guide/v8/external-dom-mutations + */ +StaticHtml.shouldComponentUpdate = () => false; + +export default StaticHtml; diff --git a/packages/integrations/preact/tsconfig.json b/packages/integrations/preact/tsconfig.json new file mode 100644 index 000000000..44baf375c --- /dev/null +++ b/packages/integrations/preact/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.base.json", + "include": ["src"], + "compilerOptions": { + "allowJs": true, + "module": "ES2020", + "outDir": "./dist", + "target": "ES2020" + } +} |