wip: support react context
This commit is contained in:
parent
34c39a0c96
commit
8541e64be5
8 changed files with 131 additions and 50 deletions
|
@ -1,5 +1,6 @@
|
|||
import { useState } from 'react';
|
||||
import './Counter.css';
|
||||
import { Context } from './Provider.tsx';
|
||||
|
||||
export default function Counter({
|
||||
children,
|
||||
|
@ -11,15 +12,16 @@ export default function Counter({
|
|||
const [count, setCount] = useState(initialCount);
|
||||
const add = () => setCount((i) => i + 1);
|
||||
const subtract = () => setCount((i) => i - 1);
|
||||
console.log('Counter', { count });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Context.Provider value={{ count }}>
|
||||
<div className="counter">
|
||||
<button onClick={subtract}>-</button>
|
||||
<pre>{count}</pre>
|
||||
<button onClick={add}>+</button>
|
||||
</div>
|
||||
<div className="counter-message">{children}</div>
|
||||
</>
|
||||
</Context.Provider>
|
||||
);
|
||||
}
|
||||
|
|
14
examples/framework-react/src/components/Display.tsx
Normal file
14
examples/framework-react/src/components/Display.tsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { useContext } from 'react';
|
||||
import { Context } from './Provider.tsx';
|
||||
|
||||
export default function Display({ children }) {
|
||||
const { count } = useContext(Context);
|
||||
console.log('Display', { count });
|
||||
|
||||
return (
|
||||
<ul>
|
||||
<li><output>Current count is: {count}</output></li>
|
||||
<li>{children}</li>
|
||||
</ul>
|
||||
);
|
||||
}
|
3
examples/framework-react/src/components/Provider.tsx
Normal file
3
examples/framework-react/src/components/Provider.tsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
import { createContext } from "react";
|
||||
|
||||
export const Context = createContext<{ count: number }>({ count: 0 });
|
|
@ -1,8 +1,9 @@
|
|||
---
|
||||
// Component Imports
|
||||
import Counter from '../components/Counter';
|
||||
import Display from '../components/Display';
|
||||
const someProps = {
|
||||
count: 0,
|
||||
count: 0,
|
||||
};
|
||||
|
||||
// Full Astro Component Syntax:
|
||||
|
@ -10,27 +11,34 @@ const someProps = {
|
|||
---
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
font-family: system-ui;
|
||||
margin: 0;
|
||||
}
|
||||
body {
|
||||
padding: 2rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<Counter {...someProps} client:visible>
|
||||
<h1>Hello, React!</h1>
|
||||
</Counter>
|
||||
</main>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
font-family: system-ui;
|
||||
margin: 0;
|
||||
}
|
||||
body {
|
||||
padding: 2rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<Counter {...someProps} client:visible>
|
||||
<div>
|
||||
<h1>Hello, React!</h1>
|
||||
<Display client:idle>
|
||||
<Display client:idle>
|
||||
<Display client:idle />
|
||||
</Display>
|
||||
</Display>
|
||||
</div>
|
||||
</Counter>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { createElement, startTransition } from 'react';
|
||||
import { createRoot, hydrateRoot } from 'react-dom/client';
|
||||
import StaticHtml from './static-html.js';
|
||||
import { Portal } from './wrapper.js';
|
||||
import { Fragment } from 'react';
|
||||
|
||||
function isAlreadyHydrated(element) {
|
||||
for (const key in element) {
|
||||
|
@ -10,31 +12,58 @@ function isAlreadyHydrated(element) {
|
|||
}
|
||||
}
|
||||
|
||||
export default (element) =>
|
||||
(Component, props, { default: children, ...slotted }, { client }) => {
|
||||
const app = document.createElement('astro-app');
|
||||
const root = createRoot(app)
|
||||
const instances = new Map();
|
||||
const instanceChildren = new Map();
|
||||
const instanceParents = new Map();
|
||||
|
||||
function buildTree() {
|
||||
const vnodes = [];
|
||||
function addNode(node) {
|
||||
const parent = instanceParents.get(node);
|
||||
const children = instanceChildren.get(node);
|
||||
const self = node({ children: children.map(child => addNode(child) )});
|
||||
if (!parent) {
|
||||
vnodes.push(self);
|
||||
} else {
|
||||
return self;
|
||||
}
|
||||
}
|
||||
for (const i of instances.values()) {
|
||||
addNode(i);
|
||||
}
|
||||
return vnodes;
|
||||
}
|
||||
|
||||
export default (element) => (Component, props, { default: children, ...slotted }, meta) => {
|
||||
if (!element.hasAttribute('ssr')) return;
|
||||
if (element.parentElement.closest('astro-island[ssr]')) return;
|
||||
|
||||
const parentElement = element.parentElement.closest('astro-island');
|
||||
let parentInstance = null;
|
||||
if (parentElement) parentInstance = instances.get(parentElement);
|
||||
|
||||
const renderOptions = {
|
||||
identifierPrefix: element.getAttribute('prefix'),
|
||||
};
|
||||
for (const [key, value] of Object.entries(slotted)) {
|
||||
props[key] = createElement(StaticHtml, { value, name: key });
|
||||
}
|
||||
const componentEl = createElement(
|
||||
const instance = ({ children: _children = [] }) => createElement(Portal, { host: element }, createElement(
|
||||
Component,
|
||||
props,
|
||||
children != null ? createElement(StaticHtml, { value: children }) : children
|
||||
);
|
||||
const rootKey = isAlreadyHydrated(element);
|
||||
// HACK: delete internal react marker for nested components to suppress aggressive warnings
|
||||
if (rootKey) {
|
||||
delete element[rootKey];
|
||||
children != null ? createElement(StaticHtml, { value: children }) : children,
|
||||
..._children
|
||||
));
|
||||
|
||||
instances.set(element, instance);
|
||||
instanceChildren.set(instance, [])
|
||||
if (parentInstance) {
|
||||
instanceChildren.set(parentInstance, [...instanceChildren.get(parentInstance), instance])
|
||||
instanceParents.set(instance, parentInstance)
|
||||
}
|
||||
if (client === 'only') {
|
||||
return startTransition(() => {
|
||||
createRoot(element).render(componentEl);
|
||||
});
|
||||
}
|
||||
return startTransition(() => {
|
||||
hydrateRoot(element, componentEl, renderOptions);
|
||||
});
|
||||
};
|
||||
const tree = buildTree();
|
||||
element.replaceChildren();
|
||||
root.render(createElement(Fragment, {}, ...tree))
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
"./server-v17.js": "./server-v17.js",
|
||||
"./package.json": "./package.json",
|
||||
"./jsx-runtime": "./jsx-runtime.js",
|
||||
"./wrapper.js": "./wrapper.js",
|
||||
"./vnode-children.js": "./vnode-children.js"
|
||||
},
|
||||
"files": [
|
||||
|
@ -38,6 +39,7 @@
|
|||
"server.js",
|
||||
"server-v17.js",
|
||||
"static-html.js",
|
||||
"wrapper.js",
|
||||
"vnode-children.js"
|
||||
],
|
||||
"scripts": {
|
||||
|
@ -48,6 +50,7 @@
|
|||
"dependencies": {
|
||||
"@babel/core": "^7.22.5",
|
||||
"@babel/plugin-transform-react-jsx": "^7.22.5",
|
||||
"its-fine": "^1.1.1",
|
||||
"ultrahtml": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -55,10 +58,10 @@
|
|||
"@types/react-dom": "^17.0.20",
|
||||
"astro": "workspace:*",
|
||||
"astro-scripts": "workspace:*",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"chai": "^4.3.7",
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"vite": "^4.4.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
5
packages/integrations/react/wrapper.js
Normal file
5
packages/integrations/react/wrapper.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { createPortal } from 'react-dom';
|
||||
|
||||
export function Portal({ children, host }) {
|
||||
return createPortal([children], host);
|
||||
}
|
|
@ -4772,6 +4772,9 @@ importers:
|
|||
'@babel/plugin-transform-react-jsx':
|
||||
specifier: ^7.22.5
|
||||
version: 7.22.5(@babel/core@7.22.5)
|
||||
its-fine:
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.1(react@18.2.0)
|
||||
ultrahtml:
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0
|
||||
|
@ -9117,6 +9120,12 @@ packages:
|
|||
'@types/react': 18.2.13
|
||||
dev: false
|
||||
|
||||
/@types/react-reconciler@0.28.2:
|
||||
resolution: {integrity: sha512-8tu6lHzEgYPlfDf/J6GOQdIc+gs+S2yAqlby3zTsB3SP2svlqTYe5fwZNtZyfactP74ShooP2vvi1BOp9ZemWw==}
|
||||
dependencies:
|
||||
'@types/react': 18.2.13
|
||||
dev: false
|
||||
|
||||
/@types/react@17.0.62:
|
||||
resolution: {integrity: sha512-eANCyz9DG8p/Vdhr0ZKST8JV12PhH2ACCDYlFw6DIO+D+ca+uP4jtEDEpVqXZrh/uZdXQGwk7whJa3ah5DtyLw==}
|
||||
dependencies:
|
||||
|
@ -13105,6 +13114,18 @@ packages:
|
|||
/isexe@2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
|
||||
/its-fine@1.1.1(react@18.2.0):
|
||||
resolution: {integrity: sha512-v1Ia1xl20KbuSGlwoaGsW0oxsw8Be+TrXweidxD9oT/1lAh6O3K3/GIM95Tt6WCiv6W+h2M7RB1TwdoAjQyyKw==}
|
||||
peerDependencies:
|
||||
react: '>=18.0'
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react-reconciler': 0.28.2
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/jake@10.8.6:
|
||||
resolution: {integrity: sha512-G43Ub9IYEFfu72sua6rzooi8V8Gz2lkfk48rW20vEWCGizeaEPlKB1Kh8JIA84yQbiAEfqlPmSpGgCKKxH3rDA==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -18566,25 +18587,21 @@ packages:
|
|||
file:packages/astro/test/fixtures/css-assets/packages/font-awesome:
|
||||
resolution: {directory: packages/astro/test/fixtures/css-assets/packages/font-awesome, type: directory}
|
||||
name: '@test/astro-font-awesome-package'
|
||||
version: 0.0.1
|
||||
dev: false
|
||||
|
||||
file:packages/astro/test/fixtures/multiple-renderers/renderers/one:
|
||||
resolution: {directory: packages/astro/test/fixtures/multiple-renderers/renderers/one, type: directory}
|
||||
name: '@test/astro-renderer-one'
|
||||
version: 1.0.0
|
||||
dev: false
|
||||
|
||||
file:packages/astro/test/fixtures/multiple-renderers/renderers/two:
|
||||
resolution: {directory: packages/astro/test/fixtures/multiple-renderers/renderers/two, type: directory}
|
||||
name: '@test/astro-renderer-two'
|
||||
version: 1.0.0
|
||||
dev: false
|
||||
|
||||
file:packages/astro/test/fixtures/solid-component/deps/solid-jsx-component:
|
||||
resolution: {directory: packages/astro/test/fixtures/solid-component/deps/solid-jsx-component, type: directory}
|
||||
name: '@test/solid-jsx-component'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
solid-js: 1.7.6
|
||||
dev: false
|
||||
|
|
Loading…
Reference in a new issue