summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Florian Lefebvre <contact@florian-lefebvre.dev> 2025-04-22 13:05:13 +0200
committerGravatar GitHub <noreply@github.com> 2025-04-22 12:05:13 +0100
commita19a185efd75334f2f417b433fcfaa0017fe41ee (patch)
tree853831aa7f68a8e0ee1aab0b231ba5d22b42c61c
parent620d15d8483dfb1822cd47833bc1653e0b704ccb (diff)
downloadastro-a19a185efd75334f2f417b433fcfaa0017fe41ee.tar.gz
astro-a19a185efd75334f2f417b433fcfaa0017fe41ee.tar.zst
astro-a19a185efd75334f2f417b433fcfaa0017fe41ee.zip
feat: convert integrations to TS (#13663)
-rw-r--r--.changeset/better-carrots-attend.md7
-rw-r--r--packages/integrations/react/env.d.ts8
-rw-r--r--packages/integrations/react/package.json22
-rw-r--r--packages/integrations/react/server.d.ts4
-rw-r--r--packages/integrations/react/server17.d.ts2
-rw-r--r--packages/integrations/react/src/client-v17.ts (renamed from packages/integrations/react/client-v17.js)9
-rw-r--r--packages/integrations/react/src/client.ts (renamed from packages/integrations/react/client.js)29
-rw-r--r--packages/integrations/react/src/context.ts (renamed from packages/integrations/react/context.js)10
-rw-r--r--packages/integrations/react/src/jsx-runtime.ts (renamed from packages/integrations/react/jsx-runtime.js)2
-rw-r--r--packages/integrations/react/src/server-v17.ts (renamed from packages/integrations/react/server-v17.js)20
-rw-r--r--packages/integrations/react/src/server.ts (renamed from packages/integrations/react/server.js)48
-rw-r--r--packages/integrations/react/src/static-html.ts (renamed from packages/integrations/react/static-html.js)6
-rw-r--r--packages/integrations/react/src/types.ts4
-rw-r--r--packages/integrations/react/src/vnode-children.ts (renamed from packages/integrations/react/vnode-children.js)6
-rw-r--r--packages/integrations/react/test/parsed-react-children.test.js2
-rw-r--r--packages/integrations/react/tsconfig.json2
-rw-r--r--packages/integrations/svelte/package.json13
-rw-r--r--packages/integrations/svelte/server.d.ts4
-rw-r--r--packages/integrations/svelte/src/client.svelte.ts (renamed from packages/integrations/svelte/client.svelte.js)34
-rw-r--r--packages/integrations/svelte/src/context.ts (renamed from packages/integrations/svelte/context.js)10
-rw-r--r--packages/integrations/svelte/src/server.ts (renamed from packages/integrations/svelte/server.js)18
-rw-r--r--packages/integrations/svelte/src/types.ts4
-rw-r--r--packages/integrations/vue/context.js24
-rw-r--r--packages/integrations/vue/env.d.ts3
-rw-r--r--packages/integrations/vue/package.json14
-rw-r--r--packages/integrations/vue/server.d.ts4
-rw-r--r--packages/integrations/vue/src/client.ts (renamed from packages/integrations/vue/client.js)25
-rw-r--r--packages/integrations/vue/src/context.ts26
-rw-r--r--packages/integrations/vue/src/server.ts (renamed from packages/integrations/vue/server.js)14
-rw-r--r--packages/integrations/vue/src/static-html.ts (renamed from packages/integrations/vue/static-html.js)0
-rw-r--r--packages/integrations/vue/src/types.ts4
-rw-r--r--packages/integrations/vue/tsconfig.json2
32 files changed, 222 insertions, 158 deletions
diff --git a/.changeset/better-carrots-attend.md b/.changeset/better-carrots-attend.md
new file mode 100644
index 000000000..055de3c8f
--- /dev/null
+++ b/.changeset/better-carrots-attend.md
@@ -0,0 +1,7 @@
+---
+'@astrojs/svelte': patch
+'@astrojs/react': patch
+'@astrojs/vue': patch
+---
+
+Improves type-safety of renderers
diff --git a/packages/integrations/react/env.d.ts b/packages/integrations/react/env.d.ts
new file mode 100644
index 000000000..21e68d939
--- /dev/null
+++ b/packages/integrations/react/env.d.ts
@@ -0,0 +1,8 @@
+declare module 'astro:react:opts' {
+ type Options = Pick<
+ import('./src/index.js').ReactIntegrationOptions,
+ 'experimentalDisableStreaming' | 'experimentalReactChildren'
+ >;
+ const options: Options;
+ export = options;
+} \ No newline at end of file
diff --git a/packages/integrations/react/package.json b/packages/integrations/react/package.json
index 77a897a7e..ddae8825a 100644
--- a/packages/integrations/react/package.json
+++ b/packages/integrations/react/package.json
@@ -22,25 +22,15 @@
"exports": {
".": "./dist/index.js",
"./actions": "./dist/actions.js",
- "./client.js": "./client.js",
- "./client-v17.js": "./client-v17.js",
- "./server.js": "./server.js",
- "./server-v17.js": "./server-v17.js",
+ "./client.js": "./dist/client.js",
+ "./client-v17.js": "./dist/client-v17.js",
+ "./server.js": "./dist/server.js",
+ "./server-v17.js": "./dist/server-v17.js",
"./package.json": "./package.json",
- "./jsx-runtime": "./jsx-runtime.js"
+ "./jsx-runtime": "./dist/jsx-runtime.js"
},
"files": [
- "dist",
- "client.js",
- "client-v17.js",
- "context.js",
- "jsx-runtime.js",
- "server.js",
- "server.d.ts",
- "server-v17.js",
- "server-v17.d.ts",
- "static-html.js",
- "vnode-children.js"
+ "dist"
],
"scripts": {
"build": "astro-scripts build \"src/**/*.ts\" && tsc",
diff --git a/packages/integrations/react/server.d.ts b/packages/integrations/react/server.d.ts
deleted file mode 100644
index 75cc3eb64..000000000
--- a/packages/integrations/react/server.d.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import type { NamedSSRLoadedRendererValue } from 'astro';
-
-declare const renderer: NamedSSRLoadedRendererValue;
-export default renderer;
diff --git a/packages/integrations/react/server17.d.ts b/packages/integrations/react/server17.d.ts
deleted file mode 100644
index bb2f29556..000000000
--- a/packages/integrations/react/server17.d.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-import type { NamedSSRLoadedRendererValue } from 'astro';
-export default NamedSSRLoadedRendererValue;
diff --git a/packages/integrations/react/client-v17.js b/packages/integrations/react/src/client-v17.ts
index bd17050ea..4ba4bcf60 100644
--- a/packages/integrations/react/client-v17.js
+++ b/packages/integrations/react/src/client-v17.ts
@@ -2,8 +2,13 @@ import { createElement } from 'react';
import { hydrate, render, unmountComponentAtNode } from 'react-dom';
import StaticHtml from './static-html.js';
-export default (element) =>
- (Component, props, { default: children, ...slotted }, { client }) => {
+export default (element: HTMLElement) =>
+ (
+ Component: any,
+ props: Record<string, any>,
+ { default: children, ...slotted }: Record<string, any>,
+ { client }: Record<string, string>,
+ ) => {
for (const [key, value] of Object.entries(slotted)) {
props[key] = createElement(StaticHtml, { value, name: key });
}
diff --git a/packages/integrations/react/client.js b/packages/integrations/react/src/client.ts
index 044eaf26f..ea187a1a1 100644
--- a/packages/integrations/react/client.js
+++ b/packages/integrations/react/src/client.ts
@@ -1,17 +1,17 @@
import { createElement, startTransition } from 'react';
-import { createRoot, hydrateRoot } from 'react-dom/client';
+import { type Root, createRoot, hydrateRoot } from 'react-dom/client';
import StaticHtml from './static-html.js';
-function isAlreadyHydrated(element) {
+function isAlreadyHydrated(element: HTMLElement) {
for (const key in element) {
if (key.startsWith('__reactContainer')) {
- return key;
+ return key as keyof HTMLElement;
}
}
}
-function createReactElementFromDOMElement(element) {
- let attrs = {};
+function createReactElementFromDOMElement(element: any): any {
+ let attrs: Record<string, string> = {};
for (const attr of element.attributes) {
attrs[attr.name] = attr.value;
}
@@ -24,7 +24,7 @@ function createReactElementFromDOMElement(element) {
element.localName,
attrs,
Array.from(element.childNodes)
- .map((c) => {
+ .map((c: any) => {
if (c.nodeType === Node.TEXT_NODE) {
return c.data;
} else if (c.nodeType === Node.ELEMENT_NODE) {
@@ -37,7 +37,7 @@ function createReactElementFromDOMElement(element) {
);
}
-function getChildren(childString, experimentalReactChildren) {
+function getChildren(childString: string, experimentalReactChildren: boolean) {
if (experimentalReactChildren && childString) {
let children = [];
let template = document.createElement('template');
@@ -54,8 +54,8 @@ function getChildren(childString, experimentalReactChildren) {
}
// Keep a map of roots so we can reuse them on re-renders
-let rootMap = new WeakMap();
-const getOrCreateRoot = (element, creator) => {
+let rootMap = new WeakMap<HTMLElement, Root>();
+const getOrCreateRoot = (element: HTMLElement, creator: () => Root) => {
let root = rootMap.get(element);
if (!root) {
root = creator();
@@ -64,8 +64,13 @@ const getOrCreateRoot = (element, creator) => {
return root;
};
-export default (element) =>
- (Component, props, { default: children, ...slotted }, { client }) => {
+export default (element: HTMLElement) =>
+ (
+ Component: any,
+ props: Record<string, any>,
+ { default: children, ...slotted }: Record<string, any>,
+ { client }: Record<string, string>,
+ ) => {
if (!element.hasAttribute('ssr')) return;
const actionKey = element.getAttribute('data-action-key');
@@ -107,7 +112,7 @@ export default (element) =>
}
startTransition(() => {
const root = getOrCreateRoot(element, () => {
- const r = hydrateRoot(element, componentEl, renderOptions);
+ const r = hydrateRoot(element, componentEl, renderOptions as any);
element.addEventListener('astro:unmount', () => r.unmount(), { once: true });
return r;
});
diff --git a/packages/integrations/react/context.js b/packages/integrations/react/src/context.ts
index 2e3e37fd5..953c35c6a 100644
--- a/packages/integrations/react/context.js
+++ b/packages/integrations/react/src/context.ts
@@ -1,8 +1,10 @@
-const contexts = new WeakMap();
+import type { SSRResult } from 'astro';
+
+const contexts = new WeakMap<SSRResult, { currentIndex: number; readonly id: string }>();
const ID_PREFIX = 'r';
-function getContext(rendererContextResult) {
+function getContext(rendererContextResult: SSRResult) {
if (contexts.has(rendererContextResult)) {
return contexts.get(rendererContextResult);
}
@@ -16,8 +18,8 @@ function getContext(rendererContextResult) {
return ctx;
}
-export function incrementId(rendererContextResult) {
- const ctx = getContext(rendererContextResult);
+export function incrementId(rendererContextResult: SSRResult) {
+ const ctx = getContext(rendererContextResult)!;
const id = ctx.id;
ctx.currentIndex++;
return id;
diff --git a/packages/integrations/react/jsx-runtime.js b/packages/integrations/react/src/jsx-runtime.ts
index d86f698b9..3f0e51c65 100644
--- a/packages/integrations/react/jsx-runtime.js
+++ b/packages/integrations/react/src/jsx-runtime.ts
@@ -2,7 +2,7 @@
// it can run in Node ESM. 'react' doesn't declare this module as an export map
// So we have to use the .js. The .js is not added via the babel automatic JSX transform
// hence this module as a workaround.
-import jsxr from 'react/jsx-runtime.js';
+import jsxr from 'react/jsx-runtime';
const { jsx, jsxs, Fragment } = jsxr;
export { jsx, jsxs, Fragment };
diff --git a/packages/integrations/react/server-v17.js b/packages/integrations/react/src/server-v17.ts
index 4e577883a..a91b6e6d5 100644
--- a/packages/integrations/react/server-v17.js
+++ b/packages/integrations/react/src/server-v17.ts
@@ -1,11 +1,12 @@
+import type { AstroComponentMetadata } from 'astro';
import React from 'react';
-import ReactDOM from 'react-dom/server.js';
+import ReactDOM from 'react-dom/server';
import StaticHtml from './static-html.js';
-const slotName = (str) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
+const slotName = (str: string) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
const reactTypeof = Symbol.for('react.element');
-function check(Component, props, children) {
+function check(Component: any, props: Record<string, any>, children: any) {
// Note: there are packages that do some unholy things to create "components".
// Checking the $$typeof property catches most of these patterns.
if (typeof Component === 'object') {
@@ -19,7 +20,7 @@ function check(Component, props, children) {
}
let isReactComponent = false;
- function Tester(...args) {
+ function Tester(...args: Array<any>) {
try {
const vnode = Component(...args);
if (vnode && vnode['$$typeof'] === reactTypeof) {
@@ -30,14 +31,19 @@ function check(Component, props, children) {
return React.createElement('div');
}
- renderToStaticMarkup(Tester, props, children, {});
+ renderToStaticMarkup(Tester, props, children, {} as any);
return isReactComponent;
}
-function renderToStaticMarkup(Component, props, { default: children, ...slotted }, metadata) {
+function renderToStaticMarkup(
+ Component: any,
+ props: Record<string, any>,
+ { default: children, ...slotted }: Record<string, any>,
+ metadata: AstroComponentMetadata,
+) {
delete props['class'];
- const slots = {};
+ const slots: Record<string, any> = {};
for (const [key, value] of Object.entries(slotted)) {
const name = slotName(key);
slots[name] = React.createElement(StaticHtml, { value, name });
diff --git a/packages/integrations/react/server.js b/packages/integrations/react/src/server.ts
index 67d9e9386..5581a95db 100644
--- a/packages/integrations/react/server.js
+++ b/packages/integrations/react/src/server.ts
@@ -1,14 +1,21 @@
import opts from 'astro:react:opts';
+import type { AstroComponentMetadata } from 'astro';
import React from 'react';
import ReactDOM from 'react-dom/server';
import { incrementId } from './context.js';
import StaticHtml from './static-html.js';
+import type { RendererContext } from './types.js';
-const slotName = (str) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
+const slotName = (str: string) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
const reactTypeof = Symbol.for('react.element');
const reactTransitionalTypeof = Symbol.for('react.transitional.element');
-async function check(Component, props, children) {
+async function check(
+ this: RendererContext,
+ Component: any,
+ props: Record<string, any>,
+ children: any,
+) {
// Note: there are packages that do some unholy things to create "components".
// Checking the $$typeof property catches most of these patterns.
if (typeof Component === 'object') {
@@ -26,7 +33,7 @@ async function check(Component, props, children) {
}
let isReactComponent = false;
- function Tester(...args) {
+ function Tester(...args: Array<any>) {
try {
const vnode = Component(...args);
if (
@@ -40,31 +47,37 @@ async function check(Component, props, children) {
return React.createElement('div');
}
- await renderToStaticMarkup(Tester, props, children, {});
+ await renderToStaticMarkup.call(this, Tester, props, children, {} as any);
return isReactComponent;
}
-async function getNodeWritable() {
+async function getNodeWritable(): Promise<typeof import('node:stream').Writable> {
let nodeStreamBuiltinModuleName = 'node:stream';
let { Writable } = await import(/* @vite-ignore */ nodeStreamBuiltinModuleName);
return Writable;
}
-function needsHydration(metadata) {
+function needsHydration(metadata: AstroComponentMetadata) {
// 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) {
+async function renderToStaticMarkup(
+ this: RendererContext,
+ Component: any,
+ props: Record<string, any>,
+ { default: children, ...slotted }: Record<string, any>,
+ metadata: AstroComponentMetadata,
+) {
let prefix;
if (this && this.result) {
prefix = incrementId(this.result);
}
- const attrs = { prefix };
+ const attrs: Record<string, any> = { prefix };
delete props['class'];
- const slots = {};
+ const slots: Record<string, any> = {};
for (const [key, value] of Object.entries(slotted)) {
const name = slotName(key);
slots[name] = React.createElement(StaticHtml, {
@@ -111,10 +124,11 @@ async function renderToStaticMarkup(Component, props, { default: children, ...sl
return { html, attrs };
}
-/**
- * @returns {Promise<[actionResult: any, actionKey: string, actionName: string] | undefined>}
- */
-async function getFormState({ result }) {
+async function getFormState({
+ result,
+}: RendererContext): Promise<
+ [actionResult: any, actionKey: string, actionName: string] | undefined
+> {
const { request, actionResult } = result;
if (!actionResult) return undefined;
@@ -139,7 +153,7 @@ async function getFormState({ result }) {
return [actionResult, actionKey, actionName];
}
-async function renderToPipeableStreamAsync(vnode, options) {
+async function renderToPipeableStreamAsync(vnode: any, options: Record<string, any>) {
const Writable = await getNodeWritable();
let html = '';
return new Promise((resolve, reject) => {
@@ -171,7 +185,7 @@ async function renderToPipeableStreamAsync(vnode, options) {
* Use a while loop instead of "for await" due to cloudflare and Vercel Edge issues
* See https://github.com/facebook/react/issues/24169
*/
-async function readResult(stream) {
+async function readResult(stream: ReactDOM.ReactDOMServerReadableStream) {
const reader = stream.getReader();
let result = '';
const decoder = new TextDecoder('utf-8');
@@ -191,13 +205,13 @@ async function readResult(stream) {
}
}
-async function renderToReadableStreamAsync(vnode, options) {
+async function renderToReadableStreamAsync(vnode: any, options: Record<string, any>) {
return await readResult(await ReactDOM.renderToReadableStream(vnode, options));
}
const formContentTypes = ['application/x-www-form-urlencoded', 'multipart/form-data'];
-function isFormRequest(contentType) {
+function isFormRequest(contentType: string | null) {
// 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();
diff --git a/packages/integrations/react/static-html.js b/packages/integrations/react/src/static-html.ts
index e319a40c7..010896196 100644
--- a/packages/integrations/react/static-html.js
+++ b/packages/integrations/react/src/static-html.ts
@@ -7,7 +7,11 @@ import { createElement as h } from 'react';
* As a bonus, we can signal to React that this subtree is
* entirely static and will never change via `shouldComponentUpdate`.
*/
-const StaticHtml = ({ value, name, hydrate = true }) => {
+const StaticHtml = ({
+ value,
+ name,
+ hydrate = true,
+}: { value: string | null; name?: string; hydrate?: boolean }) => {
if (!value) return null;
const tagName = hydrate ? 'astro-slot' : 'astro-static-slot';
return h(tagName, {
diff --git a/packages/integrations/react/src/types.ts b/packages/integrations/react/src/types.ts
new file mode 100644
index 000000000..5dff5b0b4
--- /dev/null
+++ b/packages/integrations/react/src/types.ts
@@ -0,0 +1,4 @@
+import type { SSRResult } from 'astro';
+export type RendererContext = {
+ result: SSRResult;
+};
diff --git a/packages/integrations/react/vnode-children.js b/packages/integrations/react/src/vnode-children.ts
index e0751c95a..6aa9724c6 100644
--- a/packages/integrations/react/vnode-children.js
+++ b/packages/integrations/react/src/vnode-children.ts
@@ -2,15 +2,15 @@ import { Fragment, createElement } from 'react';
import { DOCUMENT_NODE, ELEMENT_NODE, TEXT_NODE, parse } from 'ultrahtml';
let ids = 0;
-export default function convert(children) {
+export default function convert(children: any) {
let doc = parse(children.toString().trim());
let id = ids++;
let key = 0;
- function createReactElementFromNode(node) {
+ function createReactElementFromNode(node: any) {
const childVnodes =
Array.isArray(node.children) && node.children.length
- ? node.children.map((child) => createReactElementFromNode(child)).filter(Boolean)
+ ? node.children.map((child: any) => createReactElementFromNode(child)).filter(Boolean)
: undefined;
if (node.type === DOCUMENT_NODE) {
diff --git a/packages/integrations/react/test/parsed-react-children.test.js b/packages/integrations/react/test/parsed-react-children.test.js
index 75604e5d3..7c81357b7 100644
--- a/packages/integrations/react/test/parsed-react-children.test.js
+++ b/packages/integrations/react/test/parsed-react-children.test.js
@@ -1,6 +1,6 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
-import convert from '../vnode-children.js';
+import convert from '../dist/vnode-children.js';
describe('experimental react children', () => {
it('has undefined as children for direct children', () => {
diff --git a/packages/integrations/react/tsconfig.json b/packages/integrations/react/tsconfig.json
index 1504b4b6d..c152f18f8 100644
--- a/packages/integrations/react/tsconfig.json
+++ b/packages/integrations/react/tsconfig.json
@@ -1,6 +1,6 @@
{
"extends": "../../../tsconfig.base.json",
- "include": ["src"],
+ "include": ["src", "env.d.ts"],
"compilerOptions": {
"outDir": "./dist"
}
diff --git a/packages/integrations/svelte/package.json b/packages/integrations/svelte/package.json
index 08233a060..6a5a1b17b 100644
--- a/packages/integrations/svelte/package.json
+++ b/packages/integrations/svelte/package.json
@@ -22,20 +22,15 @@
"exports": {
".": "./dist/index.js",
"./editor": "./dist/editor.cjs",
- "./*": "./*",
- "./client.js": "./client.svelte.js",
- "./server.js": "./server.js",
+ "./client.js": "./dist/client.svelte.js",
+ "./server.js": "./dist/server.js",
"./package.json": "./package.json"
},
"files": [
- "dist",
- "client.svelte.js",
- "server.js",
- "server.d.ts",
- "context.js"
+ "dist"
],
"scripts": {
- "build": "astro-scripts build \"src/index.ts\" && astro-scripts build \"src/editor.cts\" --force-cjs --no-clean-dist && tsc",
+ "build": "astro-scripts build \"src/**/*.ts\" && astro-scripts build \"src/editor.cts\" --force-cjs --no-clean-dist && tsc",
"build:ci": "astro-scripts build \"src/**/*.ts\" && astro-scripts build \"src/editor.cts\" --force-cjs --no-clean-dist",
"dev": "astro-scripts dev \"src/**/*.ts\""
},
diff --git a/packages/integrations/svelte/server.d.ts b/packages/integrations/svelte/server.d.ts
deleted file mode 100644
index 75cc3eb64..000000000
--- a/packages/integrations/svelte/server.d.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import type { NamedSSRLoadedRendererValue } from 'astro';
-
-declare const renderer: NamedSSRLoadedRendererValue;
-export default renderer;
diff --git a/packages/integrations/svelte/client.svelte.js b/packages/integrations/svelte/src/client.svelte.ts
index 3bc9369f8..102f99490 100644
--- a/packages/integrations/svelte/client.svelte.js
+++ b/packages/integrations/svelte/src/client.svelte.ts
@@ -1,15 +1,19 @@
import { createRawSnippet, hydrate, mount, unmount } from 'svelte';
-/** @type {WeakMap<any, ReturnType<typeof createComponent>} */
-const existingApplications = new WeakMap();
+const existingApplications = new WeakMap<HTMLElement, ReturnType<typeof createComponent>>();
-export default (element) => {
- return async (Component, props, slotted, { client }) => {
+export default (element: HTMLElement) => {
+ return async (
+ Component: any,
+ props: Record<string, any>,
+ slotted: Record<string, any>,
+ { client }: Record<string, string>,
+ ) => {
if (!element.hasAttribute('ssr')) return;
let children = undefined;
- let _$$slots = undefined;
- let renderFns = {};
+ let _$$slots: Record<string, any> | undefined = undefined;
+ let renderFns: Record<string, any> = {};
for (const [key, value] of Object.entries(slotted)) {
// Legacy slot support
@@ -43,7 +47,7 @@ export default (element) => {
...renderFns,
};
if (existingApplications.has(element)) {
- existingApplications.get(element).setProps(resolvedProps);
+ existingApplications.get(element)!.setProps(resolvedProps);
} else {
const component = createComponent(Component, element, resolvedProps, client !== 'only');
existingApplications.set(element, component);
@@ -52,13 +56,13 @@ export default (element) => {
};
};
-/**
- * @param {any} Component
- * @param {HTMLElement} target
- * @param {Record<string, any>} props
- * @param {boolean} shouldHydrate
- */
-function createComponent(Component, target, props, shouldHydrate) {
+
+function createComponent(
+ Component: any,
+ target: HTMLElement,
+ props: Record<string, any>,
+ shouldHydrate: boolean,
+) {
let propsState = $state(props);
const bootstrap = shouldHydrate ? hydrate : mount;
if (!shouldHydrate) {
@@ -66,7 +70,7 @@ function createComponent(Component, target, props, shouldHydrate) {
}
const component = bootstrap(Component, { target, props: propsState });
return {
- setProps(newProps) {
+ setProps(newProps: Record<string, any>) {
Object.assign(propsState, newProps);
// Remove props in `propsState` but not in `newProps`
for (const key in propsState) {
diff --git a/packages/integrations/svelte/context.js b/packages/integrations/svelte/src/context.ts
index faec823a0..833755044 100644
--- a/packages/integrations/svelte/context.js
+++ b/packages/integrations/svelte/src/context.ts
@@ -1,8 +1,10 @@
-const contexts = new WeakMap();
+import type { SSRResult } from 'astro';
+
+const contexts = new WeakMap<SSRResult, { currentIndex: number; readonly id: string }>();
const ID_PREFIX = 's';
-function getContext(rendererContextResult) {
+function getContext(rendererContextResult: SSRResult) {
if (contexts.has(rendererContextResult)) {
return contexts.get(rendererContextResult);
}
@@ -16,8 +18,8 @@ function getContext(rendererContextResult) {
return ctx;
}
-export function incrementId(rendererContextResult) {
- const ctx = getContext(rendererContextResult);
+export function incrementId(rendererContextResult: SSRResult) {
+ const ctx = getContext(rendererContextResult)!;
const id = ctx.id;
ctx.currentIndex++;
return id;
diff --git a/packages/integrations/svelte/server.js b/packages/integrations/svelte/src/server.ts
index 6ebb62adc..4b0fccb3d 100644
--- a/packages/integrations/svelte/server.js
+++ b/packages/integrations/svelte/src/server.ts
@@ -1,8 +1,10 @@
+import type { AstroComponentMetadata } from 'astro';
import { createRawSnippet } from 'svelte';
import { render } from 'svelte/server';
import { incrementId } from './context.js';
+import type { RendererContext } from './types.js';
-function check(Component) {
+function check(Component: any) {
if (typeof Component !== 'function') return false;
// Svelte 5 generated components always accept a `$$payload` prop.
// This assumes that the SSR build does not minify it (which Astro enforces by default).
@@ -11,21 +13,27 @@ function check(Component) {
return Component.toString().includes('$$payload');
}
-function needsHydration(metadata) {
+function needsHydration(metadata: AstroComponentMetadata) {
// Adjust how this is hydrated only when the version of Astro supports `astroStaticSlot`
return metadata.astroStaticSlot ? !!metadata.hydrate : true;
}
-async function renderToStaticMarkup(Component, props, slotted, metadata) {
+async function renderToStaticMarkup(
+ this: RendererContext,
+ Component: any,
+ props: Record<string, any>,
+ slotted: Record<string, any>,
+ metadata: AstroComponentMetadata,
+) {
const tagName = needsHydration(metadata) ? 'astro-slot' : 'astro-static-slot';
let children = undefined;
- let $$slots = undefined;
+ let $$slots: Record<string, any> | undefined = undefined;
let idPrefix;
if (this && this.result) {
idPrefix = incrementId(this.result);
}
- const renderProps = {};
+ const renderProps: Record<string, any> = {};
for (const [key, value] of Object.entries(slotted)) {
// Legacy slot support
$$slots ??= {};
diff --git a/packages/integrations/svelte/src/types.ts b/packages/integrations/svelte/src/types.ts
new file mode 100644
index 000000000..86834336e
--- /dev/null
+++ b/packages/integrations/svelte/src/types.ts
@@ -0,0 +1,4 @@
+import type { SSRResult } from 'astro';
+export type RendererContext = {
+ result: SSRResult;
+}; \ No newline at end of file
diff --git a/packages/integrations/vue/context.js b/packages/integrations/vue/context.js
deleted file mode 100644
index 80a569ce6..000000000
--- a/packages/integrations/vue/context.js
+++ /dev/null
@@ -1,24 +0,0 @@
-const contexts = new WeakMap();
-
-const ID_PREFIX = 'v';
-
-function getContext(rendererContextResult) {
- if (contexts.has(rendererContextResult)) {
- return contexts.get(rendererContextResult);
- }
- const ctx = {
- currentIndex: 0,
- get id() {
- return ID_PREFIX + this.currentIndex.toString();
- },
- };
- contexts.set(rendererContextResult, ctx);
- return ctx;
-}
-
-export function incrementId(rendererContextResult) {
- const ctx = getContext(rendererContextResult);
- const id = ctx.id;
- ctx.currentIndex++;
- return id;
-}
diff --git a/packages/integrations/vue/env.d.ts b/packages/integrations/vue/env.d.ts
new file mode 100644
index 000000000..3a0052dca
--- /dev/null
+++ b/packages/integrations/vue/env.d.ts
@@ -0,0 +1,3 @@
+declare module 'virtual:@astrojs/vue/app' {
+ export const setup: (app: import('vue').App<Element>) => void | Promise<void>;
+} \ No newline at end of file
diff --git a/packages/integrations/vue/package.json b/packages/integrations/vue/package.json
index 6b05637de..e113443b4 100644
--- a/packages/integrations/vue/package.json
+++ b/packages/integrations/vue/package.json
@@ -22,21 +22,15 @@
"exports": {
".": "./dist/index.js",
"./editor": "./dist/editor.cjs",
- "./*": "./*",
- "./client.js": "./client.js",
- "./server.js": "./server.js",
+ "./client.js": "./dist/client.js",
+ "./server.js": "./dist/server.js",
"./package.json": "./package.json"
},
"files": [
- "dist",
- "client.js",
- "context.js",
- "server.js",
- "server.d.ts",
- "static-html.js"
+ "dist"
],
"scripts": {
- "build": "astro-scripts build \"src/index.ts\" && astro-scripts build \"src/editor.cts\" --force-cjs --no-clean-dist && tsc",
+ "build": "astro-scripts build \"src/**/*.ts\" && astro-scripts build \"src/editor.cts\" --force-cjs --no-clean-dist && tsc",
"build:ci": "astro-scripts build \"src/**/*.ts\" && astro-scripts build \"src/editor.cts\" --force-cjs --no-clean-dist",
"dev": "astro-scripts dev \"src/**/*.ts\"",
"test": "astro-scripts test \"test/**/*.test.js\""
diff --git a/packages/integrations/vue/server.d.ts b/packages/integrations/vue/server.d.ts
deleted file mode 100644
index 75cc3eb64..000000000
--- a/packages/integrations/vue/server.d.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import type { NamedSSRLoadedRendererValue } from 'astro';
-
-declare const renderer: NamedSSRLoadedRendererValue;
-export default renderer;
diff --git a/packages/integrations/vue/client.js b/packages/integrations/vue/src/client.ts
index 4ec2b9e68..8f02d534e 100644
--- a/packages/integrations/vue/client.js
+++ b/packages/integrations/vue/src/client.ts
@@ -3,15 +3,23 @@ import { Suspense, createApp, createSSRApp, h } from 'vue';
import StaticHtml from './static-html.js';
// keep track of already initialized apps, so we don't hydrate again for view transitions
-let appMap = new WeakMap();
+let appMap = new WeakMap<
+ HTMLElement,
+ { props: Record<string, any>; slots: Record<string, any>; component?: any }
+>();
-export default (element) =>
- async (Component, props, slotted, { client }) => {
+export default (element: HTMLElement) =>
+ async (
+ Component: any,
+ props: Record<string, any>,
+ slotted: Record<string, any>,
+ { client }: Record<string, string>,
+ ) => {
if (!element.hasAttribute('ssr')) return;
// Expose name on host component for Vue devtools
const name = Component.name ? `${Component.name} Host` : undefined;
- const slots = {};
+ const slots: Record<string, any> = {};
for (const [key, value] of Object.entries(slotted)) {
slots[key] = () => h(StaticHtml, { value, name: key === 'default' ? undefined : key });
}
@@ -30,8 +38,9 @@ export default (element) =>
const app = bootstrap({
name,
render() {
- let content = h(Component, appInstance.props, appInstance.slots);
- appInstance.component = this;
+ // At this point, appInstance has been set so it's safe to use a non-null assertion
+ let content = h(Component, appInstance!.props, appInstance!.slots);
+ appInstance!.component = this;
// related to https://github.com/withastro/astro/issues/6549
// if the component is async, wrap it in a Suspense component
if (isAsync(Component.setup)) {
@@ -40,7 +49,7 @@ export default (element) =>
return content;
},
});
- app.config.idPrefix = element.getAttribute('prefix');
+ app.config.idPrefix = element.getAttribute('prefix') ?? undefined;
await setup(app);
app.mount(element, isHydrate);
appMap.set(element, appInstance);
@@ -52,7 +61,7 @@ export default (element) =>
}
};
-function isAsync(fn) {
+function isAsync(fn: () => any) {
const constructor = fn?.constructor;
return constructor && constructor.name === 'AsyncFunction';
}
diff --git a/packages/integrations/vue/src/context.ts b/packages/integrations/vue/src/context.ts
new file mode 100644
index 000000000..833755044
--- /dev/null
+++ b/packages/integrations/vue/src/context.ts
@@ -0,0 +1,26 @@
+import type { SSRResult } from 'astro';
+
+const contexts = new WeakMap<SSRResult, { currentIndex: number; readonly id: string }>();
+
+const ID_PREFIX = 's';
+
+function getContext(rendererContextResult: SSRResult) {
+ if (contexts.has(rendererContextResult)) {
+ return contexts.get(rendererContextResult);
+ }
+ const ctx = {
+ currentIndex: 0,
+ get id() {
+ return ID_PREFIX + this.currentIndex.toString();
+ },
+ };
+ contexts.set(rendererContextResult, ctx);
+ return ctx;
+}
+
+export function incrementId(rendererContextResult: SSRResult) {
+ const ctx = getContext(rendererContextResult)!;
+ const id = ctx.id;
+ ctx.currentIndex++;
+ return id;
+}
diff --git a/packages/integrations/vue/server.js b/packages/integrations/vue/src/server.ts
index 315909087..6b4c2a3f4 100644
--- a/packages/integrations/vue/server.js
+++ b/packages/integrations/vue/src/server.ts
@@ -1,21 +1,29 @@
import { setup } from 'virtual:@astrojs/vue/app';
+import type { AstroComponentMetadata } from 'astro';
import { createSSRApp, h } from 'vue';
import { renderToString } from 'vue/server-renderer';
import { incrementId } from './context.js';
import StaticHtml from './static-html.js';
+import type { RendererContext } from './types.js';
-function check(Component) {
+function check(Component: any) {
return !!Component['ssrRender'] || !!Component['__ssrInlineRender'];
}
-async function renderToStaticMarkup(Component, inputProps, slotted, metadata) {
+async function renderToStaticMarkup(
+ this: RendererContext,
+ Component: any,
+ inputProps: Record<string, any>,
+ slotted: Record<string, any>,
+ metadata: AstroComponentMetadata,
+) {
let prefix;
if (this && this.result) {
prefix = incrementId(this.result);
}
const attrs = { prefix };
- const slots = {};
+ const slots: Record<string, any> = {};
const props = { ...inputProps };
delete props.slot;
for (const [key, value] of Object.entries(slotted)) {
diff --git a/packages/integrations/vue/static-html.js b/packages/integrations/vue/src/static-html.ts
index 689b56a70..689b56a70 100644
--- a/packages/integrations/vue/static-html.js
+++ b/packages/integrations/vue/src/static-html.ts
diff --git a/packages/integrations/vue/src/types.ts b/packages/integrations/vue/src/types.ts
new file mode 100644
index 000000000..5dff5b0b4
--- /dev/null
+++ b/packages/integrations/vue/src/types.ts
@@ -0,0 +1,4 @@
+import type { SSRResult } from 'astro';
+export type RendererContext = {
+ result: SSRResult;
+};
diff --git a/packages/integrations/vue/tsconfig.json b/packages/integrations/vue/tsconfig.json
index 5742d1f6e..100f3c93b 100644
--- a/packages/integrations/vue/tsconfig.json
+++ b/packages/integrations/vue/tsconfig.json
@@ -1,6 +1,6 @@
{
"extends": "../../../tsconfig.base.json",
- "include": ["src"],
+ "include": ["src", "env.d.ts"],
"compilerOptions": {
"outDir": "./dist",
"verbatimModuleSyntax": false