Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@astrojs/react

Package Overview
Dependencies
Maintainers
3
Versions
116
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@astrojs/react - npm Package Compare versions

Comparing version 0.0.0-cloudcannon-fix-20230306211609 to 0.0.0-container-20240524151016

context.js

13

client-v17.js
import { createElement } from 'react';
import { render, hydrate } from 'react-dom';
import { hydrate, render, unmountComponentAtNode } from 'react-dom';
import StaticHtml from './static-html.js';

@@ -15,6 +15,9 @@

);
if (client === 'only') {
return render(componentEl, element);
}
return hydrate(componentEl, element);
const isHydrate = client !== 'only';
const bootstrap = isHydrate ? hydrate : render;
bootstrap(componentEl, element);
element.addEventListener('astro:unmount', () => unmountComponentAtNode(element), {
once: true,
});
};

@@ -13,12 +13,81 @@ import { createElement, startTransition } from 'react';

function createReactElementFromDOMElement(element) {
let attrs = {};
for (const attr of element.attributes) {
attrs[attr.name] = attr.value;
}
// If the element has no children, we can create a simple React element
if (element.firstChild === null) {
return createElement(element.localName, attrs);
}
return createElement(
element.localName,
attrs,
Array.from(element.childNodes)
.map((c) => {
if (c.nodeType === Node.TEXT_NODE) {
return c.data;
} else if (c.nodeType === Node.ELEMENT_NODE) {
return createReactElementFromDOMElement(c);
} else {
return undefined;
}
})
.filter((a) => !!a)
);
}
function getChildren(childString, experimentalReactChildren) {
if (experimentalReactChildren && childString) {
let children = [];
let template = document.createElement('template');
template.innerHTML = childString;
for (let child of template.content.children) {
children.push(createReactElementFromDOMElement(child));
}
return children;
} else if (childString) {
return createElement(StaticHtml, { value: childString });
} else {
return undefined;
}
}
// Keep a map of roots so we can reuse them on re-renders
let rootMap = new WeakMap();
const getOrCreateRoot = (element, creator) => {
let root = rootMap.get(element);
if (!root) {
root = creator();
rootMap.set(element, root);
}
return root;
};
export default (element) =>
(Component, props, { default: children, ...slotted }, { client }) => {
if (!element.hasAttribute('ssr')) return;
const actionKey = element.getAttribute('data-action-key');
const actionName = element.getAttribute('data-action-name');
const stringifiedActionResult = element.getAttribute('data-action-result');
const formState =
actionKey && actionName && stringifiedActionResult
? [JSON.parse(stringifiedActionResult), actionKey, actionName]
: undefined;
const renderOptions = {
identifierPrefix: element.getAttribute('prefix'),
formState,
};
for (const [key, value] of Object.entries(slotted)) {
props[key] = createElement(StaticHtml, { value, name: key });
}
const componentEl = createElement(
Component,
props,
children != null ? createElement(StaticHtml, { value: children }) : children
getChildren(children, element.hasAttribute('data-react-children'))
);

@@ -32,8 +101,18 @@ const rootKey = isAlreadyHydrated(element);

return startTransition(() => {
createRoot(element).render(componentEl);
const root = getOrCreateRoot(element, () => {
const r = createRoot(element);
element.addEventListener('astro:unmount', () => r.unmount(), { once: true });
return r;
});
root.render(componentEl);
});
}
return startTransition(() => {
hydrateRoot(element, componentEl);
startTransition(() => {
const root = getOrCreateRoot(element, () => {
const r = hydrateRoot(element, componentEl, renderOptions);
element.addEventListener('astro:unmount', () => r.unmount(), { once: true });
return r;
});
root.render(componentEl);
});
};

@@ -1,2 +0,27 @@

import { AstroIntegration } from 'astro';
export default function (): AstroIntegration;
import { type Options as ViteReactPluginOptions } from '@vitejs/plugin-react';
import type { AstroIntegration, ContainerRenderer } from 'astro';
export type ReactIntegrationOptions = Pick<ViteReactPluginOptions, 'include' | 'exclude' | 'babel'> & {
experimentalReactChildren?: boolean;
};
declare const versionsConfig: {
17: {
server: string;
client: string;
externals: string[];
};
18: {
server: string;
client: string;
externals: string[];
};
19: {
server: string;
client: string;
externals: string[];
};
};
type SupportedReactVersion = keyof typeof versionsConfig;
type ReactVersionConfig = (typeof versionsConfig)[SupportedReactVersion];
export declare function getContainerRenderer(reactVersion: ReactVersionConfig): ContainerRenderer;
export default function ({ include, exclude, babel, experimentalReactChildren, }?: ReactIntegrationOptions): AstroIntegration;
export {};

@@ -0,31 +1,70 @@

import react, {} from "@vitejs/plugin-react";
import { version as ReactVersion } from "react-dom";
function getRenderer() {
const FAST_REFRESH_PREAMBLE = react.preambleCode;
const versionsConfig = {
17: {
server: "@astrojs/react/server-v17.js",
client: "@astrojs/react/client-v17.js",
externals: ["react-dom/server.js", "react-dom/client.js"]
},
18: {
server: "@astrojs/react/server.js",
client: "@astrojs/react/client.js",
externals: ["react-dom/server", "react-dom/client"]
},
19: {
server: "@astrojs/react/server.js",
client: "@astrojs/react/client.js",
externals: ["react-dom/server", "react-dom/client"]
}
};
function getReactMajorVersion() {
const matches = /\d+\./.exec(ReactVersion);
if (!matches) {
return NaN;
}
return Number(matches[0]);
}
function isUnsupportedVersion(majorVersion) {
return majorVersion < 17 || majorVersion > 19 || Number.isNaN(majorVersion);
}
function getRenderer(reactConfig) {
return {
name: "@astrojs/react",
clientEntrypoint: ReactVersion.startsWith("18.") ? "@astrojs/react/client.js" : "@astrojs/react/client-v17.js",
serverEntrypoint: ReactVersion.startsWith("18.") ? "@astrojs/react/server.js" : "@astrojs/react/server-v17.js",
jsxImportSource: "react",
jsxTransformOptions: async () => {
var _a;
const babelPluginTransformReactJsxModule = await import("@babel/plugin-transform-react-jsx");
const jsx = ((_a = babelPluginTransformReactJsxModule == null ? void 0 : babelPluginTransformReactJsxModule.default) == null ? void 0 : _a.default) ?? (babelPluginTransformReactJsxModule == null ? void 0 : babelPluginTransformReactJsxModule.default);
return {
plugins: [
jsx(
{},
{
runtime: "automatic",
importSource: ReactVersion.startsWith("18.") ? "react" : "@astrojs/react"
}
)
]
};
clientEntrypoint: reactConfig.client,
serverEntrypoint: reactConfig.server
};
}
function getContainerRenderer(reactVersion) {
return {
name: "@astrojs/react",
serverEntrypoint: reactVersion.server
};
}
function optionsPlugin(experimentalReactChildren) {
const virtualModule = "astro:react:opts";
const virtualModuleId = "\0" + virtualModule;
return {
name: "@astrojs/react:opts",
resolveId(id) {
if (id === virtualModule) {
return virtualModuleId;
}
},
load(id) {
if (id === virtualModuleId) {
return {
code: `export default {
experimentalReactChildren: ${JSON.stringify(experimentalReactChildren)}
}`
};
}
}
};
}
function getViteConfiguration() {
function getViteConfiguration({ include, exclude, babel, experimentalReactChildren } = {}, reactConfig) {
return {
optimizeDeps: {
include: [
ReactVersion.startsWith("18.") ? "@astrojs/react/client.js" : "@astrojs/react/client-v17.js",
reactConfig.client,
"react",

@@ -36,15 +75,17 @@ "react/jsx-runtime",

],
exclude: [
ReactVersion.startsWith("18.") ? "@astrojs/react/server.js" : "@astrojs/react/server-v17.js"
]
exclude: [reactConfig.server]
},
plugins: [react({ include, exclude, babel }), optionsPlugin(!!experimentalReactChildren)],
resolve: {
dedupe: ["react", "react-dom"]
dedupe: ["react", "react-dom", "react-dom/server"]
},
ssr: {
external: ReactVersion.startsWith("18.") ? ["react-dom/server", "react-dom/client"] : ["react-dom/server.js", "react-dom/client.js"],
external: reactConfig.externals,
noExternal: [
// These are all needed to get mui to work.
"@mui/material",
"@mui/base",
"@babel/runtime"
"@babel/runtime",
"use-immer",
"@material-tailwind/react"
]

@@ -54,9 +95,28 @@ }

}
function src_default() {
function src_default({
include,
exclude,
babel,
experimentalReactChildren
} = {}) {
const majorVersion = getReactMajorVersion();
if (isUnsupportedVersion(majorVersion)) {
throw new Error(`Unsupported React version: ${majorVersion}.`);
}
const versionConfig = versionsConfig[majorVersion];
return {
name: "@astrojs/react",
hooks: {
"astro:config:setup": ({ addRenderer, updateConfig }) => {
addRenderer(getRenderer());
updateConfig({ vite: getViteConfiguration() });
"astro:config:setup": ({ command, addRenderer, updateConfig, injectScript }) => {
addRenderer(getRenderer(versionConfig));
updateConfig({
vite: getViteConfiguration(
{ include, exclude, babel, experimentalReactChildren },
versionConfig
)
});
if (command === "dev") {
const preamble = FAST_REFRESH_PREAMBLE.replace(`__BASE__`, "/");
injectScript("before-hydration", preamble);
}
}

@@ -67,3 +127,4 @@ }

export {
src_default as default
src_default as default,
getContainerRenderer
};
{
"name": "@astrojs/react",
"description": "Use React components within Astro",
"version": "0.0.0-cloudcannon-fix-20230306211609",
"version": "0.0.0-container-20240524151016",
"type": "module",

@@ -24,2 +24,3 @@ "types": "./dist/index.d.ts",

".": "./dist/index.js",
"./actions": "./dist/actions.js",
"./client.js": "./client.js",

@@ -32,28 +33,45 @@ "./client-v17.js": "./client-v17.js",

},
"files": [
"dist",
"client.js",
"client-v17.js",
"context.js",
"jsx-runtime.js",
"server.js",
"server-v17.js",
"static-html.js",
"vnode-children.js"
],
"dependencies": {
"@babel/core": ">=7.0.0-0 <8.0.0",
"@babel/plugin-transform-react-jsx": "^7.17.12"
"@vitejs/plugin-react": "^4.2.1",
"ultrahtml": "^1.5.3"
},
"devDependencies": {
"@types/react": "^17.0.45",
"@types/react-dom": "^17.0.17",
"astro": "0.0.0-cloudcannon-fix-20230306211609",
"astro-scripts": "0.0.0-cloudcannon-fix-20230306211609",
"react": "^18.1.0",
"react-dom": "^18.1.0"
"@types/react": "^18.3.2",
"@types/react-dom": "^18.3.0",
"cheerio": "1.0.0-rc.12",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"vite": "^5.2.11",
"astro": "0.0.0-container-20240524151016",
"astro-scripts": "0.0.14"
},
"peerDependencies": {
"react": "^17.0.2 || ^18.0.0",
"react-dom": "^17.0.2 || ^18.0.0",
"@types/react": "^17.0.50 || ^18.0.21",
"@types/react-dom": "^17.0.17 || ^18.0.6"
"@types/react-dom": "^17.0.17 || ^18.0.6",
"react": "^17.0.2 || ^18.0.0 || ^19.0.0-beta",
"react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0-beta"
},
"engines": {
"node": ">=16.12.0"
"node": "^18.17.1 || ^20.3.0 || >=21.0.0"
},
"publishConfig": {
"provenance": true
},
"scripts": {
"build": "astro-scripts build \"src/**/*.ts\" && tsc",
"build:ci": "astro-scripts build \"src/**/*.ts\"",
"dev": "astro-scripts dev \"src/**/*.ts\""
"dev": "astro-scripts dev \"src/**/*.ts\"",
"test": "astro-scripts test \"test/**/*.test.js\""
}
}
# @astrojs/react ⚛️
This **[Astro integration][astro-integration]** enables server-side rendering and client-side hydration for your [React](https://reactjs.org/) components.
This **[Astro integration][astro-integration]** enables server-side rendering and client-side hydration for your [React](https://react.dev/) components.
## Installation
## Documentation
There are two ways to add integrations to your project. Let's try the most convenient option first!
Read the [`@astrojs/react` docs][docs]
### `astro add` command
## Support
Astro includes a CLI tool for adding first party integrations: `astro add`. This command will:
1. (Optionally) Install all necessary dependencies and peer dependencies
2. (Also optionally) Update your `astro.config.*` file to apply this integration
- Get help in the [Astro Discord][discord]. Post questions in our `#support` forum, or visit our dedicated `#dev` channel to discuss current development and more!
To install `@astrojs/react`, run the following from your project directory and follow the prompts:
- Check our [Astro Integration Documentation][astro-integration] for more on integrations.
```sh
# Using NPM
npx astro add react
# Using Yarn
yarn astro add react
# Using PNPM
pnpm astro add react
```
- Submit bug reports and feature requests as [GitHub issues][issues].
If you run into any issues, [feel free to report them to us on GitHub](https://github.com/withastro/astro/issues) and try the manual installation steps below.
## Contributing
### Install dependencies manually
This package is maintained by Astro's Core team. You're welcome to submit an issue or PR! These links will help you get started:
First, install the `@astrojs/react` integration like so:
- [Contributor Manual][contributing]
- [Code of Conduct][coc]
- [Community Guide][community]
```sh
npm install @astrojs/react
```
## License
Most package managers will install associated peer dependencies as well. Still, if you see a "Cannot find package 'react'" (or similar) warning when you start up Astro, you'll need to install `react` and `react-dom`:
MIT
```sh
npm install react react-dom
```
Copyright (c) 2023–present [Astro][astro]
Now, apply this integration to your `astro.config.*` file using the `integrations` property:
__`astro.config.mjs`__
```js ins={2} "react()"
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
export default defineConfig({
// ...
integrations: [react()],
});
```
## Getting started
To use your first React component in Astro, head to our [UI framework documentation][astro-ui-frameworks]. You'll explore:
- 📦 how framework components are loaded,
- 💧 client-side hydration options, and
- 🤝 opportunities to mix and nest frameworks together
## Troubleshooting
For help, check out the `#support` channel on [Discord](https://astro.build/chat). Our friendly Support Squad members are here to help!
You can also check our [Astro Integration Documentation][astro-integration] for more on integrations.
## Contributing
This package is maintained by Astro's Core team. You're welcome to submit an issue or PR!
[astro]: https://astro.build/
[docs]: https://docs.astro.build/en/guides/integrations-guide/react/
[contributing]: https://github.com/withastro/astro/blob/main/CONTRIBUTING.md
[coc]: https://github.com/withastro/.github/blob/main/CODE_OF_CONDUCT.md
[community]: https://github.com/withastro/.github/blob/main/COMMUNITY_GUIDE.md
[discord]: https://astro.build/chat/
[issues]: https://github.com/withastro/astro/issues
[astro-integration]: https://docs.astro.build/en/guides/integrations-guide/
[astro-ui-frameworks]: https://docs.astro.build/en/core-concepts/framework-components/#using-framework-components

@@ -20,6 +20,6 @@ import React from 'react';

if (typeof Component === 'object') {
const $$typeof = Component['$$typeof'];
return $$typeof && $$typeof.toString().slice('Symbol('.length).startsWith('react');
return Component['$$typeof']?.toString().slice('Symbol('.length).startsWith('react');
}
if (typeof Component !== 'function') return false;
if (Component.name === 'QwikComponent') return false;

@@ -69,7 +69,11 @@ if (Component.prototype != null && typeof Component.prototype.render === 'function') {

if (newChildren != null) {
newProps.children = React.createElement(StaticHtml, { value: newChildren });
newProps.children = React.createElement(StaticHtml, {
// Adjust how this is hydrated only when the version of Astro supports `astroStaticSlot`
hydrate: metadata.astroStaticSlot ? !!metadata.hydrate : true,
value: newChildren,
});
}
const vnode = React.createElement(Component, newProps);
let html;
if (metadata && metadata.hydrate) {
if (metadata?.hydrate) {
html = ReactDOM.renderToString(vnode);

@@ -85,2 +89,3 @@ } else {

renderToStaticMarkup,
supportsAstroStaticSlot: true,
};

@@ -0,3 +1,6 @@

import opts from 'astro:react:opts';
import { AstroError } from 'astro/errors';
import React from 'react';
import ReactDOM from 'react-dom/server';
import { incrementId } from './context.js';
import StaticHtml from './static-html.js';

@@ -20,7 +23,11 @@

if (typeof Component === 'object') {
const $$typeof = Component['$$typeof'];
return $$typeof && $$typeof.toString().slice('Symbol('.length).startsWith('react');
return Component['$$typeof'].toString().slice('Symbol('.length).startsWith('react');
}
if (typeof Component !== 'function') return false;
if (Component.name === 'QwikComponent') return false;
// Preact forwarded-ref components can be functions, which React does not support
if (typeof Component === 'function' && Component['$$typeof'] === Symbol.for('react.forward_ref'))
return false;
if (Component.prototype != null && typeof Component.prototype.render === 'function') {

@@ -56,3 +63,3 @@ return React.Component.isPrototypeOf(Component) || React.PureComponent.isPrototypeOf(Component);

async function getNodeWritable() {
let nodeStreamBuiltinModuleName = 'stream';
let nodeStreamBuiltinModuleName = 'node:stream';
let { Writable } = await import(/* @vite-ignore */ nodeStreamBuiltinModuleName);

@@ -62,3 +69,14 @@ return Writable;

function needsHydration(metadata) {
// Adjust how this is hydrated only when the version of Astro supports `astroStaticSlot`
return metadata.astroStaticSlot ? !!metadata.hydrate : true;
}
async function renderToStaticMarkup(Component, props, { default: children, ...slotted }, metadata) {
let prefix;
if (this && this.result) {
prefix = incrementId(this.result);
}
const attrs = { prefix };
delete props['class'];

@@ -68,3 +86,7 @@ const slots = {};

const name = slotName(key);
slots[name] = React.createElement(StaticHtml, { value, name });
slots[name] = React.createElement(StaticHtml, {
hydrate: needsHydration(metadata),
value,
name,
});
}

@@ -77,24 +99,70 @@ // Note: create newProps to avoid mutating `props` before they are serialized

const newChildren = children ?? props.children;
if (newChildren != null) {
newProps.children = React.createElement(StaticHtml, { value: newChildren });
if (children && opts.experimentalReactChildren) {
attrs['data-react-children'] = true;
const convert = await import('./vnode-children.js').then((mod) => mod.default);
newProps.children = convert(children);
} else if (newChildren != null) {
newProps.children = React.createElement(StaticHtml, {
hydrate: needsHydration(metadata),
value: newChildren,
});
}
const formState = this ? await getFormState(this) : undefined;
if (formState) {
attrs['data-action-result'] = JSON.stringify(formState[0]);
attrs['data-action-key'] = formState[1];
attrs['data-action-name'] = formState[2];
}
const vnode = React.createElement(Component, newProps);
const renderOptions = {
identifierPrefix: prefix,
formState,
};
let html;
if (metadata && metadata.hydrate) {
if ('renderToReadableStream' in ReactDOM) {
html = await renderToReadableStreamAsync(vnode);
} else {
html = await renderToPipeableStreamAsync(vnode);
}
if ('renderToReadableStream' in ReactDOM) {
html = await renderToReadableStreamAsync(vnode, renderOptions);
} else {
if ('renderToReadableStream' in ReactDOM) {
html = await renderToReadableStreamAsync(vnode);
} else {
html = await renderToStaticNodeStreamAsync(vnode);
}
html = await renderToPipeableStreamAsync(vnode, renderOptions);
}
return { html };
return { html, attrs };
}
async function renderToPipeableStreamAsync(vnode) {
/**
* @returns {Promise<[actionResult: any, actionKey: string, actionName: string] | undefined>}
*/
async function getFormState({ result }) {
const { request, actionResult } = result;
if (!actionResult) return undefined;
if (!isFormRequest(request.headers.get('content-type'))) return undefined;
const formData = await request.clone().formData();
/**
* The key generated by React to identify each `useActionState()` call.
* @example "k511f74df5a35d32e7cf266450d85cb6c"
*/
const actionKey = formData.get('$ACTION_KEY')?.toString();
/**
* The action name returned by an action's `toString()` property.
* This matches the endpoint path.
* @example "/_actions/blog.like"
*/
const actionName = formData.get('_astroAction')?.toString();
if (!actionKey || !actionName) return undefined;
const isUsingSafe = formData.has('_astroActionSafe');
if (!isUsingSafe && actionResult.error) {
throw new AstroError(
`Unhandled error calling action ${actionName.replace(/^\/_actions\//, '')}:\n[${
actionResult.error.code
}] ${actionResult.error.message}`,
'use `.safe()` to handle from your React component.'
);
}
return [isUsingSafe ? actionResult : actionResult.data, actionKey, actionName];
}
async function renderToPipeableStreamAsync(vnode, options) {
const Writable = await getNodeWritable();

@@ -105,2 +173,3 @@ let html = '';

let stream = ReactDOM.renderToPipeableStream(vnode, {
...options,
onError(err) {

@@ -127,24 +196,2 @@ error = err;

async function renderToStaticNodeStreamAsync(vnode) {
const Writable = await getNodeWritable();
let html = '';
return new Promise((resolve, reject) => {
let stream = ReactDOM.renderToStaticNodeStream(vnode);
stream.on('error', (err) => {
reject(err);
});
stream.pipe(
new Writable({
write(chunk, _encoding, callback) {
html += chunk.toString('utf-8');
callback();
},
destroy() {
resolve(html);
},
})
);
});
}
/**

@@ -174,9 +221,20 @@ * Use a while loop instead of "for await" due to cloudflare and Vercel Edge issues

async function renderToReadableStreamAsync(vnode) {
return await readResult(await ReactDOM.renderToReadableStream(vnode));
async function renderToReadableStreamAsync(vnode, options) {
return await readResult(await ReactDOM.renderToReadableStream(vnode, options));
}
const formContentTypes = ['application/x-www-form-urlencoded', 'multipart/form-data'];
function isFormRequest(contentType) {
// Split off parameters like charset or boundary
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type#content-type_in_html_forms
const type = contentType?.split(';')[0].toLowerCase();
return formContentTypes.some((t) => type === t);
}
export default {
check,
renderToStaticMarkup,
supportsAstroStaticSlot: true,
};

@@ -10,5 +10,6 @@ import { createElement as h } from 'react';

*/
const StaticHtml = ({ value, name }) => {
const StaticHtml = ({ value, name, hydrate = true }) => {
if (!value) return null;
return h('astro-slot', {
const tagName = hydrate ? 'astro-slot' : 'astro-static-slot';
return h(tagName, {
name,

@@ -15,0 +16,0 @@ suppressHydrationWarning: true,

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc