Compare commits
2 commits
main
...
spike/app-
Author | SHA1 | Date | |
---|---|---|---|
|
5ccb873096 | ||
|
9d5ec1c8c7 |
20 changed files with 140 additions and 33 deletions
|
@ -4,5 +4,7 @@ import preact from '@astrojs/preact';
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
// Enable Preact to support Preact JSX components.
|
// Enable Preact to support Preact JSX components.
|
||||||
integrations: [preact()],
|
integrations: [preact({
|
||||||
|
appEntrypoint: '/src/pages/_app.tsx'
|
||||||
|
})],
|
||||||
});
|
});
|
||||||
|
|
4
examples/framework-preact/src/components/Context.tsx
Normal file
4
examples/framework-preact/src/components/Context.tsx
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import { createContext } from 'preact';
|
||||||
|
|
||||||
|
const noop = () => {};
|
||||||
|
export const Context = createContext({ count: 0, increment: noop, decrement: noop });
|
|
@ -1,18 +1,16 @@
|
||||||
import { h, Fragment } from 'preact';
|
import { useContext } from 'preact/hooks';
|
||||||
import { useState } from 'preact/hooks';
|
import { Context } from './Context';
|
||||||
import './Counter.css';
|
import './Counter.css';
|
||||||
|
|
||||||
export default function Counter({ children }) {
|
export default function Counter({ children }) {
|
||||||
const [count, setCount] = useState(0);
|
const { count, increment, decrement } = useContext(Context);
|
||||||
const add = () => setCount((i) => i + 1);
|
|
||||||
const subtract = () => setCount((i) => i - 1);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div class="counter">
|
<div class="counter">
|
||||||
<button onClick={subtract}>-</button>
|
<button onClick={decrement}>-</button>
|
||||||
<pre>{count}</pre>
|
<pre>{count}</pre>
|
||||||
<button onClick={add}>+</button>
|
<button onClick={increment}>+</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="counter-message">{children}</div>
|
<div class="counter-message">{children}</div>
|
||||||
</>
|
</>
|
||||||
|
|
10
examples/framework-preact/src/pages/_app.tsx
Normal file
10
examples/framework-preact/src/pages/_app.tsx
Normal file
|
@ -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 <Context.Provider value={{ count, increment, decrement }}>{children}</Context.Provider>
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
---
|
---
|
||||||
// Component Imports
|
// Component Imports
|
||||||
import Counter from '../components/Counter';
|
import Counter from '../components/Counter';
|
||||||
|
|
||||||
// Full Astro Component Syntax:
|
// Full Astro Component Syntax:
|
||||||
// https://docs.astro.build/core-concepts/astro-components/
|
// https://docs.astro.build/core-concepts/astro-components/
|
||||||
---
|
---
|
||||||
|
@ -28,6 +27,9 @@ import Counter from '../components/Counter';
|
||||||
<Counter client:visible>
|
<Counter client:visible>
|
||||||
<h1>Hello, Preact!</h1>
|
<h1>Hello, Preact!</h1>
|
||||||
</Counter>
|
</Counter>
|
||||||
|
<Counter client:visible>
|
||||||
|
<h1>Hello, Preact!</h1>
|
||||||
|
</Counter>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -4,5 +4,7 @@ import vue from '@astrojs/vue';
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
// Enable Vue to support Vue components.
|
// Enable Vue to support Vue components.
|
||||||
integrations: [vue()],
|
integrations: [vue({
|
||||||
|
appEntrypoint: '/src/pages/_app.ts'
|
||||||
|
})],
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<pre>{{ count }}</pre>
|
<pre>{{ count }}</pre>
|
||||||
<button @click="add()">+</button>
|
<button @click="add()">+</button>
|
||||||
</div>
|
</div>
|
||||||
|
<Test />
|
||||||
<div class="counter-message">
|
<div class="counter-message">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
15
examples/framework-vue/src/components/Test.vue
Normal file
15
examples/framework-vue/src/components/Test.vue
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<template>
|
||||||
|
<div>Hello world!</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, onMounted } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
setup() {
|
||||||
|
onMounted(() => {
|
||||||
|
console.log("Hello world!")
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
6
examples/framework-vue/src/pages/_app.ts
Normal file
6
examples/framework-vue/src/pages/_app.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import type { App } from 'vue';
|
||||||
|
import Test from "../components/Test.vue"
|
||||||
|
|
||||||
|
export default (app: App) => {
|
||||||
|
app.component('Test', Test)
|
||||||
|
}
|
|
@ -1099,6 +1099,8 @@ export interface AstroRenderer {
|
||||||
clientEntrypoint?: string;
|
clientEntrypoint?: string;
|
||||||
/** Import entrypoint for the server/build/ssr renderer. */
|
/** Import entrypoint for the server/build/ssr renderer. */
|
||||||
serverEntrypoint: string;
|
serverEntrypoint: string;
|
||||||
|
/** User-provided entrypoint for the browser app instance */
|
||||||
|
appEntrypoint?: string;
|
||||||
/** JSX identifier (e.g. 'react' or 'solid-js') */
|
/** JSX identifier (e.g. 'react' or 'solid-js') */
|
||||||
jsxImportSource?: string;
|
jsxImportSource?: string;
|
||||||
/** Babel transform options */
|
/** Babel transform options */
|
||||||
|
|
|
@ -16,5 +16,16 @@ export default function astroIntegrationsContainerPlugin({
|
||||||
configureServer(server) {
|
configureServer(server) {
|
||||||
runHookServerSetup({ config, server, logging });
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
1
packages/integrations/preact/app.js
Normal file
1
packages/integrations/preact/app.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { Fragment as default } from 'preact';
|
|
@ -1,14 +1,17 @@
|
||||||
import { h, render } from 'preact';
|
import { h } from 'preact';
|
||||||
|
import { createPortal } from 'preact/compat';
|
||||||
import StaticHtml from './static-html.js';
|
import StaticHtml from './static-html.js';
|
||||||
|
|
||||||
export default (element) =>
|
export default (element) =>
|
||||||
(Component, props, { default: children, ...slotted }) => {
|
(Component, props, { default: children, ...slotted }) => {
|
||||||
if (!element.hasAttribute('ssr')) return;
|
if (!element.hasAttribute('ssr')) return;
|
||||||
|
const { addChild } = globalThis['@astrojs/preact'];
|
||||||
|
while (!!element.firstElementChild) {
|
||||||
|
element.firstElementChild.remove();
|
||||||
|
}
|
||||||
for (const [key, value] of Object.entries(slotted)) {
|
for (const [key, value] of Object.entries(slotted)) {
|
||||||
props[key] = h(StaticHtml, { value, name: key });
|
props[key] = h(StaticHtml, { value, name: key });
|
||||||
}
|
}
|
||||||
render(
|
const Portal = createPortal(h(Component, props, children != null ? h(StaticHtml, { value: children }) : children), element)
|
||||||
h(Component, props, children != null ? h(StaticHtml, { value: children }) : children),
|
addChild(Portal);
|
||||||
element
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
"homepage": "https://docs.astro.build/en/guides/integrations-guide/preact/",
|
"homepage": "https://docs.astro.build/en/guides/integrations-guide/preact/",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist/index.js",
|
".": "./dist/index.js",
|
||||||
|
"./app": "./app.js",
|
||||||
"./client.js": "./client.js",
|
"./client.js": "./client.js",
|
||||||
"./server.js": "./server.js",
|
"./server.js": "./server.js",
|
||||||
"./package.json": "./package.json"
|
"./package.json": "./package.json"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { h, Component as BaseComponent } from 'preact';
|
import { h, Component as BaseComponent } from 'preact';
|
||||||
import render from 'preact-render-to-string';
|
import render from 'preact-render-to-string';
|
||||||
import StaticHtml from './static-html.js';
|
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());
|
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
|
// Note: create newProps to avoid mutating `props` before they are serialized
|
||||||
const newProps = { ...props, ...slots };
|
const newProps = { ...props, ...slots };
|
||||||
const html = render(
|
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 };
|
return { html };
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { AstroIntegration, AstroRenderer, ViteUserConfig } from 'astro';
|
import { AstroIntegration, AstroRenderer, ViteUserConfig } from 'astro';
|
||||||
|
|
||||||
function getRenderer(): AstroRenderer {
|
function getRenderer(appEntrypoint?: string): AstroRenderer {
|
||||||
return {
|
return {
|
||||||
name: '@astrojs/preact',
|
name: '@astrojs/preact',
|
||||||
clientEntrypoint: '@astrojs/preact/client.js',
|
clientEntrypoint: '@astrojs/preact/client.js',
|
||||||
serverEntrypoint: '@astrojs/preact/server.js',
|
serverEntrypoint: '@astrojs/preact/server.js',
|
||||||
|
appEntrypoint,
|
||||||
jsxImportSource: 'preact',
|
jsxImportSource: 'preact',
|
||||||
jsxTransformOptions: async () => {
|
jsxTransformOptions: async () => {
|
||||||
const {
|
const {
|
||||||
|
@ -92,13 +93,32 @@ function getViteConfiguration(compat?: boolean): ViteUserConfig {
|
||||||
return viteConfig;
|
return viteConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ({ compat }: { compat?: boolean } = {}): AstroIntegration {
|
export default function ({ compat, appEntrypoint }: { compat?: boolean, appEntrypoint?: string } = {}): AstroIntegration {
|
||||||
return {
|
return {
|
||||||
name: '@astrojs/preact',
|
name: '@astrojs/preact',
|
||||||
hooks: {
|
hooks: {
|
||||||
'astro:config:setup': ({ addRenderer, updateConfig }) => {
|
'astro:config:setup': ({ addRenderer, updateConfig, injectScript }) => {
|
||||||
if (compat) addRenderer(getCompatRenderer());
|
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({
|
updateConfig({
|
||||||
vite: getViteConfiguration(compat),
|
vite: getViteConfiguration(compat),
|
||||||
});
|
});
|
||||||
|
|
1
packages/integrations/vue/app.js
Normal file
1
packages/integrations/vue/app.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export default app => app;
|
|
@ -1,22 +1,24 @@
|
||||||
import { h, createSSRApp, createApp } from 'vue';
|
import { h, Teleport, defineComponent } from 'vue';
|
||||||
import StaticHtml from './static-html.js';
|
import StaticHtml from './static-html.js';
|
||||||
|
|
||||||
export default (element) =>
|
export default (element) =>
|
||||||
(Component, props, slotted, { client }) => {
|
(Component, props, slotted) => {
|
||||||
delete props['class'];
|
delete props['class'];
|
||||||
if (!element.hasAttribute('ssr')) return;
|
if (!element.hasAttribute('ssr')) return;
|
||||||
|
|
||||||
// Expose name on host component for Vue devtools
|
// Expose name on host component for Vue devtools
|
||||||
const name = Component.name ? `${Component.name} Host` : undefined;
|
const name = Component.name ? `${Component.name} Host` : undefined;
|
||||||
const slots = {};
|
const slots = {};
|
||||||
|
const { addChild } = globalThis['@astrojs/vue']
|
||||||
for (const [key, value] of Object.entries(slotted)) {
|
for (const [key, value] of Object.entries(slotted)) {
|
||||||
slots[key] = () => h(StaticHtml, { value, name: key === 'default' ? undefined : key });
|
slots[key] = () => h(StaticHtml, { value, name: key === 'default' ? undefined : key });
|
||||||
}
|
}
|
||||||
if (client === 'only') {
|
// h(Teleport, { to: element }, ["AHHHHHH"])
|
||||||
const app = createApp({ name, render: () => h(Component, props, slots) });
|
let host = defineComponent({
|
||||||
app.mount(element, false);
|
name,
|
||||||
} else {
|
setup() {
|
||||||
const app = createSSRApp({ name, render: () => h(Component, props, slots) });
|
return () => h(Component, props, slots)
|
||||||
app.mount(element, true);
|
}
|
||||||
}
|
});
|
||||||
|
addChild(host)
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { h, createSSRApp } from 'vue';
|
import { h, createSSRApp } from 'vue';
|
||||||
import { renderToString } from 'vue/server-renderer';
|
import { renderToString } from 'vue/server-renderer';
|
||||||
import StaticHtml from './static-html.js';
|
import StaticHtml from './static-html.js';
|
||||||
|
import setup from 'virtual:@astrojs/vue/app';
|
||||||
|
|
||||||
function check(Component) {
|
function check(Component) {
|
||||||
return !!Component['ssrRender'];
|
return !!Component['ssrRender'];
|
||||||
|
@ -12,6 +13,7 @@ async function renderToStaticMarkup(Component, props, slotted) {
|
||||||
slots[key] = () => h(StaticHtml, { value, name: key === 'default' ? undefined : key });
|
slots[key] = () => h(StaticHtml, { value, name: key === 'default' ? undefined : key });
|
||||||
}
|
}
|
||||||
const app = createSSRApp({ render: () => h(Component, props, slots) });
|
const app = createSSRApp({ render: () => h(Component, props, slots) });
|
||||||
|
setup(app)
|
||||||
const html = await renderToString(app);
|
const html = await renderToString(app);
|
||||||
return { html };
|
return { html };
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,12 @@ import type { Options } from '@vitejs/plugin-vue';
|
||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue';
|
||||||
import type { AstroIntegration, AstroRenderer } from 'astro';
|
import type { AstroIntegration, AstroRenderer } from 'astro';
|
||||||
|
|
||||||
function getRenderer(): AstroRenderer {
|
function getRenderer(appEntrypoint?: string): AstroRenderer {
|
||||||
return {
|
return {
|
||||||
name: '@astrojs/vue',
|
name: '@astrojs/vue',
|
||||||
clientEntrypoint: '@astrojs/vue/client.js',
|
clientEntrypoint: '@astrojs/vue/client.js',
|
||||||
serverEntrypoint: '@astrojs/vue/server.js',
|
serverEntrypoint: '@astrojs/vue/server.js',
|
||||||
|
appEntrypoint,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,12 +24,32 @@ function getViteConfiguration(options?: Options) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function (options?: Options): AstroIntegration {
|
export default function (options?: Options & { appEntrypoint?: string }): AstroIntegration {
|
||||||
return {
|
return {
|
||||||
name: '@astrojs/vue',
|
name: '@astrojs/vue',
|
||||||
hooks: {
|
hooks: {
|
||||||
'astro:config:setup': ({ addRenderer, updateConfig }) => {
|
'astro:config:setup': ({ addRenderer, updateConfig, injectScript }) => {
|
||||||
addRenderer(getRenderer());
|
injectScript('before-hydration', `import { h, Fragment, createApp } from 'vue';
|
||||||
|
import setup from "virtual:@astrojs/vue/app";
|
||||||
|
|
||||||
|
const el = document.createElement('astro-app');
|
||||||
|
el.setAttribute('renderer', '@astrojs/vue');
|
||||||
|
document.body.appendChild(el);
|
||||||
|
|
||||||
|
const app = createApp({
|
||||||
|
setup: () => {
|
||||||
|
console.log('setup');
|
||||||
|
const children = ref([]);
|
||||||
|
return () => h(Fragment, {}, [])
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setup(app);
|
||||||
|
app.mount(el, false)
|
||||||
|
|
||||||
|
globalThis['@astrojs/preact'] = {
|
||||||
|
addChild
|
||||||
|
}`)
|
||||||
|
addRenderer(getRenderer(options?.appEntrypoint));
|
||||||
updateConfig({ vite: getViteConfiguration(options) });
|
updateConfig({ vite: getViteConfiguration(options) });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue