Skip to content

Commit a19a185

Browse files
feat: convert integrations to TS (#13663)
1 parent 620d15d commit a19a185

32 files changed

+222
-158
lines changed

.changeset/better-carrots-attend.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@astrojs/svelte': patch
3+
'@astrojs/react': patch
4+
'@astrojs/vue': patch
5+
---
6+
7+
Improves type-safety of renderers

packages/integrations/react/env.d.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
declare module 'astro:react:opts' {
2+
type Options = Pick<
3+
import('./src/index.js').ReactIntegrationOptions,
4+
'experimentalDisableStreaming' | 'experimentalReactChildren'
5+
>;
6+
const options: Options;
7+
export = options;
8+
}

packages/integrations/react/package.json

+6-16
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,15 @@
2222
"exports": {
2323
".": "./dist/index.js",
2424
"./actions": "./dist/actions.js",
25-
"./client.js": "./client.js",
26-
"./client-v17.js": "./client-v17.js",
27-
"./server.js": "./server.js",
28-
"./server-v17.js": "./server-v17.js",
25+
"./client.js": "./dist/client.js",
26+
"./client-v17.js": "./dist/client-v17.js",
27+
"./server.js": "./dist/server.js",
28+
"./server-v17.js": "./dist/server-v17.js",
2929
"./package.json": "./package.json",
30-
"./jsx-runtime": "./jsx-runtime.js"
30+
"./jsx-runtime": "./dist/jsx-runtime.js"
3131
},
3232
"files": [
33-
"dist",
34-
"client.js",
35-
"client-v17.js",
36-
"context.js",
37-
"jsx-runtime.js",
38-
"server.js",
39-
"server.d.ts",
40-
"server-v17.js",
41-
"server-v17.d.ts",
42-
"static-html.js",
43-
"vnode-children.js"
33+
"dist"
4434
],
4535
"scripts": {
4636
"build": "astro-scripts build \"src/**/*.ts\" && tsc",

packages/integrations/react/server.d.ts

-4
This file was deleted.

packages/integrations/react/server17.d.ts

-2
This file was deleted.

packages/integrations/react/client-v17.js renamed to packages/integrations/react/src/client-v17.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@ import { createElement } from 'react';
22
import { hydrate, render, unmountComponentAtNode } from 'react-dom';
33
import StaticHtml from './static-html.js';
44

5-
export default (element) =>
6-
(Component, props, { default: children, ...slotted }, { client }) => {
5+
export default (element: HTMLElement) =>
6+
(
7+
Component: any,
8+
props: Record<string, any>,
9+
{ default: children, ...slotted }: Record<string, any>,
10+
{ client }: Record<string, string>,
11+
) => {
712
for (const [key, value] of Object.entries(slotted)) {
813
props[key] = createElement(StaticHtml, { value, name: key });
914
}

packages/integrations/react/client.js renamed to packages/integrations/react/src/client.ts

+17-12
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import { createElement, startTransition } from 'react';
2-
import { createRoot, hydrateRoot } from 'react-dom/client';
2+
import { type Root, createRoot, hydrateRoot } from 'react-dom/client';
33
import StaticHtml from './static-html.js';
44

5-
function isAlreadyHydrated(element) {
5+
function isAlreadyHydrated(element: HTMLElement) {
66
for (const key in element) {
77
if (key.startsWith('__reactContainer')) {
8-
return key;
8+
return key as keyof HTMLElement;
99
}
1010
}
1111
}
1212

13-
function createReactElementFromDOMElement(element) {
14-
let attrs = {};
13+
function createReactElementFromDOMElement(element: any): any {
14+
let attrs: Record<string, string> = {};
1515
for (const attr of element.attributes) {
1616
attrs[attr.name] = attr.value;
1717
}
@@ -24,7 +24,7 @@ function createReactElementFromDOMElement(element) {
2424
element.localName,
2525
attrs,
2626
Array.from(element.childNodes)
27-
.map((c) => {
27+
.map((c: any) => {
2828
if (c.nodeType === Node.TEXT_NODE) {
2929
return c.data;
3030
} else if (c.nodeType === Node.ELEMENT_NODE) {
@@ -37,7 +37,7 @@ function createReactElementFromDOMElement(element) {
3737
);
3838
}
3939

40-
function getChildren(childString, experimentalReactChildren) {
40+
function getChildren(childString: string, experimentalReactChildren: boolean) {
4141
if (experimentalReactChildren && childString) {
4242
let children = [];
4343
let template = document.createElement('template');
@@ -54,8 +54,8 @@ function getChildren(childString, experimentalReactChildren) {
5454
}
5555

5656
// Keep a map of roots so we can reuse them on re-renders
57-
let rootMap = new WeakMap();
58-
const getOrCreateRoot = (element, creator) => {
57+
let rootMap = new WeakMap<HTMLElement, Root>();
58+
const getOrCreateRoot = (element: HTMLElement, creator: () => Root) => {
5959
let root = rootMap.get(element);
6060
if (!root) {
6161
root = creator();
@@ -64,8 +64,13 @@ const getOrCreateRoot = (element, creator) => {
6464
return root;
6565
};
6666

67-
export default (element) =>
68-
(Component, props, { default: children, ...slotted }, { client }) => {
67+
export default (element: HTMLElement) =>
68+
(
69+
Component: any,
70+
props: Record<string, any>,
71+
{ default: children, ...slotted }: Record<string, any>,
72+
{ client }: Record<string, string>,
73+
) => {
6974
if (!element.hasAttribute('ssr')) return;
7075

7176
const actionKey = element.getAttribute('data-action-key');
@@ -107,7 +112,7 @@ export default (element) =>
107112
}
108113
startTransition(() => {
109114
const root = getOrCreateRoot(element, () => {
110-
const r = hydrateRoot(element, componentEl, renderOptions);
115+
const r = hydrateRoot(element, componentEl, renderOptions as any);
111116
element.addEventListener('astro:unmount', () => r.unmount(), { once: true });
112117
return r;
113118
});

packages/integrations/react/context.js renamed to packages/integrations/react/src/context.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
const contexts = new WeakMap();
1+
import type { SSRResult } from 'astro';
2+
3+
const contexts = new WeakMap<SSRResult, { currentIndex: number; readonly id: string }>();
24

35
const ID_PREFIX = 'r';
46

5-
function getContext(rendererContextResult) {
7+
function getContext(rendererContextResult: SSRResult) {
68
if (contexts.has(rendererContextResult)) {
79
return contexts.get(rendererContextResult);
810
}
@@ -16,8 +18,8 @@ function getContext(rendererContextResult) {
1618
return ctx;
1719
}
1820

19-
export function incrementId(rendererContextResult) {
20-
const ctx = getContext(rendererContextResult);
21+
export function incrementId(rendererContextResult: SSRResult) {
22+
const ctx = getContext(rendererContextResult)!;
2123
const id = ctx.id;
2224
ctx.currentIndex++;
2325
return id;

packages/integrations/react/jsx-runtime.js renamed to packages/integrations/react/src/jsx-runtime.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// it can run in Node ESM. 'react' doesn't declare this module as an export map
33
// So we have to use the .js. The .js is not added via the babel automatic JSX transform
44
// hence this module as a workaround.
5-
import jsxr from 'react/jsx-runtime.js';
5+
import jsxr from 'react/jsx-runtime';
66
const { jsx, jsxs, Fragment } = jsxr;
77

88
export { jsx, jsxs, Fragment };

packages/integrations/react/server-v17.js renamed to packages/integrations/react/src/server-v17.ts

+13-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import type { AstroComponentMetadata } from 'astro';
12
import React from 'react';
2-
import ReactDOM from 'react-dom/server.js';
3+
import ReactDOM from 'react-dom/server';
34
import StaticHtml from './static-html.js';
45

5-
const slotName = (str) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
6+
const slotName = (str: string) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
67
const reactTypeof = Symbol.for('react.element');
78

8-
function check(Component, props, children) {
9+
function check(Component: any, props: Record<string, any>, children: any) {
910
// Note: there are packages that do some unholy things to create "components".
1011
// Checking the $$typeof property catches most of these patterns.
1112
if (typeof Component === 'object') {
@@ -19,7 +20,7 @@ function check(Component, props, children) {
1920
}
2021

2122
let isReactComponent = false;
22-
function Tester(...args) {
23+
function Tester(...args: Array<any>) {
2324
try {
2425
const vnode = Component(...args);
2526
if (vnode && vnode['$$typeof'] === reactTypeof) {
@@ -30,14 +31,19 @@ function check(Component, props, children) {
3031
return React.createElement('div');
3132
}
3233

33-
renderToStaticMarkup(Tester, props, children, {});
34+
renderToStaticMarkup(Tester, props, children, {} as any);
3435

3536
return isReactComponent;
3637
}
3738

38-
function renderToStaticMarkup(Component, props, { default: children, ...slotted }, metadata) {
39+
function renderToStaticMarkup(
40+
Component: any,
41+
props: Record<string, any>,
42+
{ default: children, ...slotted }: Record<string, any>,
43+
metadata: AstroComponentMetadata,
44+
) {
3945
delete props['class'];
40-
const slots = {};
46+
const slots: Record<string, any> = {};
4147
for (const [key, value] of Object.entries(slotted)) {
4248
const name = slotName(key);
4349
slots[name] = React.createElement(StaticHtml, { value, name });

packages/integrations/react/server.js renamed to packages/integrations/react/src/server.ts

+31-17
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
import opts from 'astro:react:opts';
2+
import type { AstroComponentMetadata } from 'astro';
23
import React from 'react';
34
import ReactDOM from 'react-dom/server';
45
import { incrementId } from './context.js';
56
import StaticHtml from './static-html.js';
7+
import type { RendererContext } from './types.js';
68

7-
const slotName = (str) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
9+
const slotName = (str: string) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
810
const reactTypeof = Symbol.for('react.element');
911
const reactTransitionalTypeof = Symbol.for('react.transitional.element');
1012

11-
async function check(Component, props, children) {
13+
async function check(
14+
this: RendererContext,
15+
Component: any,
16+
props: Record<string, any>,
17+
children: any,
18+
) {
1219
// Note: there are packages that do some unholy things to create "components".
1320
// Checking the $$typeof property catches most of these patterns.
1421
if (typeof Component === 'object') {
@@ -26,7 +33,7 @@ async function check(Component, props, children) {
2633
}
2734

2835
let isReactComponent = false;
29-
function Tester(...args) {
36+
function Tester(...args: Array<any>) {
3037
try {
3138
const vnode = Component(...args);
3239
if (
@@ -40,31 +47,37 @@ async function check(Component, props, children) {
4047
return React.createElement('div');
4148
}
4249

43-
await renderToStaticMarkup(Tester, props, children, {});
50+
await renderToStaticMarkup.call(this, Tester, props, children, {} as any);
4451

4552
return isReactComponent;
4653
}
4754

48-
async function getNodeWritable() {
55+
async function getNodeWritable(): Promise<typeof import('node:stream').Writable> {
4956
let nodeStreamBuiltinModuleName = 'node:stream';
5057
let { Writable } = await import(/* @vite-ignore */ nodeStreamBuiltinModuleName);
5158
return Writable;
5259
}
5360

54-
function needsHydration(metadata) {
61+
function needsHydration(metadata: AstroComponentMetadata) {
5562
// Adjust how this is hydrated only when the version of Astro supports `astroStaticSlot`
5663
return metadata.astroStaticSlot ? !!metadata.hydrate : true;
5764
}
5865

59-
async function renderToStaticMarkup(Component, props, { default: children, ...slotted }, metadata) {
66+
async function renderToStaticMarkup(
67+
this: RendererContext,
68+
Component: any,
69+
props: Record<string, any>,
70+
{ default: children, ...slotted }: Record<string, any>,
71+
metadata: AstroComponentMetadata,
72+
) {
6073
let prefix;
6174
if (this && this.result) {
6275
prefix = incrementId(this.result);
6376
}
64-
const attrs = { prefix };
77+
const attrs: Record<string, any> = { prefix };
6578

6679
delete props['class'];
67-
const slots = {};
80+
const slots: Record<string, any> = {};
6881
for (const [key, value] of Object.entries(slotted)) {
6982
const name = slotName(key);
7083
slots[name] = React.createElement(StaticHtml, {
@@ -111,10 +124,11 @@ async function renderToStaticMarkup(Component, props, { default: children, ...sl
111124
return { html, attrs };
112125
}
113126

114-
/**
115-
* @returns {Promise<[actionResult: any, actionKey: string, actionName: string] | undefined>}
116-
*/
117-
async function getFormState({ result }) {
127+
async function getFormState({
128+
result,
129+
}: RendererContext): Promise<
130+
[actionResult: any, actionKey: string, actionName: string] | undefined
131+
> {
118132
const { request, actionResult } = result;
119133

120134
if (!actionResult) return undefined;
@@ -139,7 +153,7 @@ async function getFormState({ result }) {
139153
return [actionResult, actionKey, actionName];
140154
}
141155

142-
async function renderToPipeableStreamAsync(vnode, options) {
156+
async function renderToPipeableStreamAsync(vnode: any, options: Record<string, any>) {
143157
const Writable = await getNodeWritable();
144158
let html = '';
145159
return new Promise((resolve, reject) => {
@@ -171,7 +185,7 @@ async function renderToPipeableStreamAsync(vnode, options) {
171185
* Use a while loop instead of "for await" due to cloudflare and Vercel Edge issues
172186
* See https://github.com/facebook/react/issues/24169
173187
*/
174-
async function readResult(stream) {
188+
async function readResult(stream: ReactDOM.ReactDOMServerReadableStream) {
175189
const reader = stream.getReader();
176190
let result = '';
177191
const decoder = new TextDecoder('utf-8');
@@ -191,13 +205,13 @@ async function readResult(stream) {
191205
}
192206
}
193207

194-
async function renderToReadableStreamAsync(vnode, options) {
208+
async function renderToReadableStreamAsync(vnode: any, options: Record<string, any>) {
195209
return await readResult(await ReactDOM.renderToReadableStream(vnode, options));
196210
}
197211

198212
const formContentTypes = ['application/x-www-form-urlencoded', 'multipart/form-data'];
199213

200-
function isFormRequest(contentType) {
214+
function isFormRequest(contentType: string | null) {
201215
// Split off parameters like charset or boundary
202216
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type#content-type_in_html_forms
203217
const type = contentType?.split(';')[0].toLowerCase();

packages/integrations/react/static-html.js renamed to packages/integrations/react/src/static-html.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import { createElement as h } from 'react';
77
* As a bonus, we can signal to React that this subtree is
88
* entirely static and will never change via `shouldComponentUpdate`.
99
*/
10-
const StaticHtml = ({ value, name, hydrate = true }) => {
10+
const StaticHtml = ({
11+
value,
12+
name,
13+
hydrate = true,
14+
}: { value: string | null; name?: string; hydrate?: boolean }) => {
1115
if (!value) return null;
1216
const tagName = hydrate ? 'astro-slot' : 'astro-static-slot';
1317
return h(tagName, {
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import type { SSRResult } from 'astro';
2+
export type RendererContext = {
3+
result: SSRResult;
4+
};

packages/integrations/react/vnode-children.js renamed to packages/integrations/react/src/vnode-children.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ import { Fragment, createElement } from 'react';
22
import { DOCUMENT_NODE, ELEMENT_NODE, TEXT_NODE, parse } from 'ultrahtml';
33

44
let ids = 0;
5-
export default function convert(children) {
5+
export default function convert(children: any) {
66
let doc = parse(children.toString().trim());
77
let id = ids++;
88
let key = 0;
99

10-
function createReactElementFromNode(node) {
10+
function createReactElementFromNode(node: any) {
1111
const childVnodes =
1212
Array.isArray(node.children) && node.children.length
13-
? node.children.map((child) => createReactElementFromNode(child)).filter(Boolean)
13+
? node.children.map((child: any) => createReactElementFromNode(child)).filter(Boolean)
1414
: undefined;
1515

1616
if (node.type === DOCUMENT_NODE) {

0 commit comments

Comments
 (0)