wip: support react context

This commit is contained in:
Nate Moore 2023-08-21 16:21:28 -05:00
parent 34c39a0c96
commit 8541e64be5
8 changed files with 131 additions and 50 deletions

View file

@ -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>
);
}

View 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>
);
}

View file

@ -0,0 +1,3 @@
import { createContext } from "react";
export const Context = createContext<{ count: number }>({ count: 0 });

View file

@ -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>

View file

@ -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))
}

View file

@ -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": {

View file

@ -0,0 +1,5 @@
import { createPortal } from 'react-dom';
export function Portal({ children, host }) {
return createPortal([children], host);
}

View file

@ -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