Merge remote-tracking branch 'origin/main' into next
This commit is contained in:
commit
44bd0cd825
31 changed files with 392 additions and 94 deletions
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
'astro': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Displays a new config error if `outDir` is placed within `publicDir`.
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
'astro': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Fix missing type for `imageConfig` export from `astro:assets`
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
'@astrojs/node': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Fix an issue where `express` couldn't use the `handler` in `middleware` mode.
|
|
5
.changeset/silent-snakes-shave.md
Normal file
5
.changeset/silent-snakes-shave.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@astrojs/cloudflare': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Improve documentation and export the types needed to type the `runtime` object.
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
'astro': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Make typing of `defineCollection` more permissive to support advanced union and intersection types
|
|
|
@ -8,13 +8,13 @@ interface Props {
|
||||||
const { align = 'center', tagline, title } = Astro.props;
|
const { align = 'center', tagline, title } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<header class:list={['hero stack gap-4', align]}>
|
<div class:list={['hero stack gap-4', align]}>
|
||||||
<div class="stack gap-2">
|
<div class="stack gap-2">
|
||||||
<h1 class="title">{title}</h1>
|
<h1 class="title">{title}</h1>
|
||||||
{tagline && <p class="tagline">{tagline}</p>}
|
{tagline && <p class="tagline">{tagline}</p>}
|
||||||
</div>
|
</div>
|
||||||
<slot />
|
<slot />
|
||||||
</header>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.hero {
|
.hero {
|
||||||
|
|
|
@ -36,7 +36,7 @@ const iconLinks: { label: string; href: string; icon: keyof typeof iconPaths }[]
|
||||||
</template>
|
</template>
|
||||||
</menu-button>
|
</menu-button>
|
||||||
</div>
|
</div>
|
||||||
<noscript class="menu-noscript">
|
<noscript>
|
||||||
<ul class="nav-items">
|
<ul class="nav-items">
|
||||||
{
|
{
|
||||||
textLinks.map(({ label, href }) => (
|
textLinks.map(({ label, href }) => (
|
||||||
|
@ -60,7 +60,7 @@ const iconLinks: { label: string; href: string; icon: keyof typeof iconPaths }[]
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</noscript>
|
</noscript>
|
||||||
<noscript style="display: contents;">
|
<noscript>
|
||||||
<div class="menu-footer">
|
<div class="menu-footer">
|
||||||
<div class="socials">
|
<div class="socials">
|
||||||
{
|
{
|
||||||
|
|
|
@ -553,6 +553,24 @@
|
||||||
- @astrojs/internal-helpers@0.2.0-beta.0
|
- @astrojs/internal-helpers@0.2.0-beta.0
|
||||||
- @astrojs/markdown-remark@3.0.0-beta.0
|
- @astrojs/markdown-remark@3.0.0-beta.0
|
||||||
|
|
||||||
|
## 2.10.13
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- [#8152](https://github.com/withastro/astro/pull/8152) [`582132328`](https://github.com/withastro/astro/commit/5821323285646aee7ff9194a505f708028e4db57) Thanks [@andremralves](https://github.com/andremralves)! - Displays a new config error if `outDir` is placed within `publicDir`.
|
||||||
|
|
||||||
|
- [#8166](https://github.com/withastro/astro/pull/8166) [`fddd4dc71`](https://github.com/withastro/astro/commit/fddd4dc71af321bd6b4d01bb4b1b955284846e60) Thanks [@martrapp](https://github.com/martrapp)! - ViewTransitions: Fixes in the client-side router
|
||||||
|
|
||||||
|
- [#8182](https://github.com/withastro/astro/pull/8182) [`cfc465dde`](https://github.com/withastro/astro/commit/cfc465ddebcc58d20f29ecffaa857a77525435a9) Thanks [@martrapp](https://github.com/martrapp)! - View Transitions: self link (`href=""`) does not trigger page reload
|
||||||
|
|
||||||
|
- [#8171](https://github.com/withastro/astro/pull/8171) [`95120efbe`](https://github.com/withastro/astro/commit/95120efbe817163663492181cbeb225849354493) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Fix missing type for `imageConfig` export from `astro:assets`
|
||||||
|
|
||||||
|
- [#8187](https://github.com/withastro/astro/pull/8187) [`273335cb0`](https://github.com/withastro/astro/commit/273335cb01615c3c06d46c02464f4496a81f8d0b) Thanks [@bluwy](https://github.com/bluwy)! - Fix Astro components parent-child render order
|
||||||
|
|
||||||
|
- [#8184](https://github.com/withastro/astro/pull/8184) [`9142178b1`](https://github.com/withastro/astro/commit/9142178b113443749b87c1d259859b42a3d7a9c4) Thanks [@martrapp](https://github.com/martrapp)! - Fix: The scrolling behavior of ViewTransitions is now more similar to the expected browser behavior
|
||||||
|
|
||||||
|
- [#8163](https://github.com/withastro/astro/pull/8163) [`179796405`](https://github.com/withastro/astro/commit/179796405e053b559d83f84507e5a465861a029a) Thanks [@delucis](https://github.com/delucis)! - Make typing of `defineCollection` more permissive to support advanced union and intersection types
|
||||||
|
|
||||||
## 2.10.12
|
## 2.10.12
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|
|
@ -20,15 +20,6 @@ const { fallback = 'animate' } = Astro.props as Props;
|
||||||
type Events = 'astro:load' | 'astro:beforeload';
|
type Events = 'astro:load' | 'astro:beforeload';
|
||||||
|
|
||||||
const persistState = (state: State) => history.replaceState(state, '');
|
const persistState = (state: State) => history.replaceState(state, '');
|
||||||
|
|
||||||
// The History API does not tell you if navigation is forward or back, so
|
|
||||||
// you can figure it using an index. On pushState the index is incremented so you
|
|
||||||
// can use that to determine popstate if going forward or back.
|
|
||||||
let currentHistoryIndex = history.state?.index || 0;
|
|
||||||
if (!history.state) {
|
|
||||||
persistState({ index: currentHistoryIndex, scrollY: 0 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const supportsViewTransitions = !!document.startViewTransition;
|
const supportsViewTransitions = !!document.startViewTransition;
|
||||||
const transitionEnabledOnThisPage = () =>
|
const transitionEnabledOnThisPage = () =>
|
||||||
!!document.querySelector('[name="astro-view-transitions-enabled"]');
|
!!document.querySelector('[name="astro-view-transitions-enabled"]');
|
||||||
|
@ -36,6 +27,14 @@ const { fallback = 'animate' } = Astro.props as Props;
|
||||||
const onload = () => triggerEvent('astro:load');
|
const onload = () => triggerEvent('astro:load');
|
||||||
const PERSIST_ATTR = 'data-astro-transition-persist';
|
const PERSIST_ATTR = 'data-astro-transition-persist';
|
||||||
|
|
||||||
|
// The History API does not tell you if navigation is forward or back, so
|
||||||
|
// you can figure it using an index. On pushState the index is incremented so you
|
||||||
|
// can use that to determine popstate if going forward or back.
|
||||||
|
let currentHistoryIndex = history.state?.index || 0;
|
||||||
|
if (!history.state && transitionEnabledOnThisPage()) {
|
||||||
|
persistState({ index: currentHistoryIndex, scrollY: 0 });
|
||||||
|
}
|
||||||
|
|
||||||
const throttle = (cb: (...args: any[]) => any, delay: number) => {
|
const throttle = (cb: (...args: any[]) => any, delay: number) => {
|
||||||
let wait = false;
|
let wait = false;
|
||||||
// During the waiting time additional events are lost.
|
// During the waiting time additional events are lost.
|
||||||
|
@ -165,14 +164,21 @@ const { fallback = 'animate' } = Astro.props as Props;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Simulate scroll behavior of Safari and
|
||||||
|
// Chromium based browsers (Chrome, Edge, Opera, ...)
|
||||||
|
scrollTo({ left: 0, top: 0, behavior: 'instant' });
|
||||||
|
|
||||||
if (state?.scrollY === 0 && location.hash) {
|
if (state?.scrollY === 0 && location.hash) {
|
||||||
const id = decodeURIComponent(location.hash.slice(1));
|
const id = decodeURIComponent(location.hash.slice(1));
|
||||||
state.scrollY = document.getElementById(id)?.offsetTop || 0;
|
const elem = document.getElementById(id);
|
||||||
|
// prefer scrollIntoView() over scrollTo() because it takes scroll-padding into account
|
||||||
|
if (elem) {
|
||||||
|
state.scrollY = elem.offsetTop;
|
||||||
|
persistState(state); // first guess, later updated by scroll handler
|
||||||
|
elem.scrollIntoView(); // for Firefox, this should better be {behavior: 'instant'}
|
||||||
}
|
}
|
||||||
if (state?.scrollY != null) {
|
} else if (state && state.scrollY !== 0) {
|
||||||
scrollTo(0, state.scrollY);
|
scrollTo(0, state.scrollY); // usings default scrollBehavior
|
||||||
// Overwrite erroneous updates by the scroll handler during transition
|
|
||||||
persistState(state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerEvent('astro:beforeload');
|
triggerEvent('astro:beforeload');
|
||||||
|
@ -274,34 +280,59 @@ const { fallback = 'animate' } = Astro.props as Props;
|
||||||
// that is going to another page within the same origin. Basically it determines
|
// that is going to another page within the same origin. Basically it determines
|
||||||
// same-origin navigation, but omits special key combos for new tabs, etc.
|
// same-origin navigation, but omits special key combos for new tabs, etc.
|
||||||
if (
|
if (
|
||||||
link &&
|
!link ||
|
||||||
link instanceof HTMLAnchorElement &&
|
!(link instanceof HTMLAnchorElement) ||
|
||||||
link.href &&
|
!link.href ||
|
||||||
(!link.target || link.target === '_self') &&
|
(link.target && link.target !== '_self') ||
|
||||||
link.origin === location.origin &&
|
link.origin !== location.origin ||
|
||||||
!(
|
ev.button !== 0 || // left clicks only
|
||||||
// Same page means same path and same query params
|
ev.metaKey || // new tab (mac)
|
||||||
(location.pathname === link.pathname && location.search === link.search)
|
ev.ctrlKey || // new tab (windows)
|
||||||
) &&
|
ev.altKey || // download
|
||||||
ev.button === 0 && // left clicks only
|
ev.shiftKey || // new window
|
||||||
!ev.metaKey && // new tab (mac)
|
ev.defaultPrevented ||
|
||||||
!ev.ctrlKey && // new tab (windows)
|
!transitionEnabledOnThisPage()
|
||||||
!ev.altKey && // download
|
)
|
||||||
!ev.shiftKey &&
|
// No page transitions in these cases,
|
||||||
!ev.defaultPrevented &&
|
// Let the browser standard action handle this
|
||||||
transitionEnabledOnThisPage()
|
return;
|
||||||
) {
|
|
||||||
|
// We do not need to handle same page links because there are no page transitions
|
||||||
|
// Same page means same path and same query params (but different hash)
|
||||||
|
if (location.pathname === link.pathname && location.search === link.search) {
|
||||||
|
if (link.hash) {
|
||||||
|
// The browser default action will handle navigations with hash fragments
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// Special case: self link without hash
|
||||||
|
// If handed to the browser it will reload the page
|
||||||
|
// But we want to handle it like any other same page navigation
|
||||||
|
// So we scroll to the top of the page but do not start page transitions
|
||||||
|
ev.preventDefault();
|
||||||
|
persistState({ ...history.state, scrollY });
|
||||||
|
scrollTo({ left: 0, top: 0, behavior: 'instant' });
|
||||||
|
if (location.hash) {
|
||||||
|
// last target was different
|
||||||
|
const newState: State = { index: ++currentHistoryIndex, scrollY: 0 };
|
||||||
|
history.pushState(newState, '', link.href);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// these are the cases we will handle: same origin, different page
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
navigate('forward', link.href, { index: ++currentHistoryIndex, scrollY: 0 });
|
navigate('forward', link.href, { index: ++currentHistoryIndex, scrollY: 0 });
|
||||||
const newState: State = { index: currentHistoryIndex, scrollY };
|
const newState: State = { index: currentHistoryIndex, scrollY };
|
||||||
persistState({ index: currentHistoryIndex - 1, scrollY });
|
persistState({ index: currentHistoryIndex - 1, scrollY });
|
||||||
history.pushState(newState, '', link.href);
|
history.pushState(newState, '', link.href);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
addEventListener('popstate', (ev) => {
|
addEventListener('popstate', (ev) => {
|
||||||
if (!transitionEnabledOnThisPage()) {
|
if (!transitionEnabledOnThisPage() && ev.state) {
|
||||||
// The current page doesn't haven't View Transitions,
|
// The current page doesn't haven't View Transitions,
|
||||||
// respect that with a full page reload
|
// respect that with a full page reload
|
||||||
|
// -- but only for transition managed by us (ev.state is set)
|
||||||
location.reload();
|
location.reload();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
import { ViewTransitions } from 'astro:transitions';
|
||||||
|
|
||||||
|
// For the test fixture, we import the script but we don't use the <ViewTransitions /> component
|
||||||
|
// While this seems to be some strange mistake,
|
||||||
|
// it might be realistic, e.g. in a configurable CommenHead component
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
transitions?: string;
|
||||||
|
}
|
||||||
|
const { transitions } = Astro.props;
|
||||||
|
---
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Half-Baked</title>
|
||||||
|
{transitions && <ViewTransitions />}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<p id="half-baked">Half Baked</p>
|
||||||
|
<a id="click-hash" href="#click-hash">hash target</a>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -7,6 +7,7 @@ import Layout from '../components/Layout.astro';
|
||||||
<a id="click-two" href="/two">go to 2</a>
|
<a id="click-two" href="/two">go to 2</a>
|
||||||
<a id="click-three" href="/three">go to 3</a>
|
<a id="click-three" href="/three">go to 3</a>
|
||||||
<a id="click-longpage" href="/long-page">go to long page</a>
|
<a id="click-longpage" href="/long-page">go to long page</a>
|
||||||
|
<a id="click-self" href="">go to top</a>
|
||||||
|
|
||||||
<div id="test">test content</div>
|
<div id="test">test content</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
<main>
|
<main>
|
||||||
<p id="three">Page 3</p>
|
<p id="three">Page 3</p>
|
||||||
<a id="click-two" href="/two">go to 2</a>
|
<a id="click-two" href="/two">go to 2</a>
|
||||||
|
<br/>
|
||||||
|
<a id="click-hash" href="#click-hash">hash target</a>
|
||||||
|
<p style="height: 150vh">Long paragraph</p>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -112,6 +112,40 @@ test.describe('View Transitions', () => {
|
||||||
).toEqual(2);
|
).toEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Moving within a page without ViewTransitions does not trigger a full page navigation', async ({
|
||||||
|
page,
|
||||||
|
astro,
|
||||||
|
}) => {
|
||||||
|
const loads = [];
|
||||||
|
page.addListener('load', async (p) => {
|
||||||
|
loads.push(p.title());
|
||||||
|
});
|
||||||
|
// Go to page 1
|
||||||
|
await page.goto(astro.resolveUrl('/one'));
|
||||||
|
let p = page.locator('#one');
|
||||||
|
await expect(p, 'should have content').toHaveText('Page 1');
|
||||||
|
|
||||||
|
// Go to page 3 which does *not* have ViewTransitions enabled
|
||||||
|
await page.click('#click-three');
|
||||||
|
p = page.locator('#three');
|
||||||
|
await expect(p, 'should have content').toHaveText('Page 3');
|
||||||
|
|
||||||
|
// click a hash link to navigate further down the page
|
||||||
|
await page.click('#click-hash');
|
||||||
|
// still on page 3
|
||||||
|
p = page.locator('#three');
|
||||||
|
await expect(p, 'should have content').toHaveText('Page 3');
|
||||||
|
|
||||||
|
// check that we are further down the page
|
||||||
|
const Y = await page.evaluate(() => window.scrollY);
|
||||||
|
expect(Y, 'The target is further down the page').toBeGreaterThan(0);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
loads.length,
|
||||||
|
'There should be only 1 page load. The original, but no additional loads for the hash change'
|
||||||
|
).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
test('Moving from a page without ViewTransitions w/ back button', async ({ page, astro }) => {
|
test('Moving from a page without ViewTransitions w/ back button', async ({ page, astro }) => {
|
||||||
const loads = [];
|
const loads = [];
|
||||||
page.addListener('load', (p) => {
|
page.addListener('load', (p) => {
|
||||||
|
@ -190,6 +224,22 @@ test.describe('View Transitions', () => {
|
||||||
await expect(p, 'should have content').toHaveText('Page 1');
|
await expect(p, 'should have content').toHaveText('Page 1');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('click self link (w/o hash) does not do navigation', async ({ page, astro }) => {
|
||||||
|
const loads = [];
|
||||||
|
page.addListener('load', (p) => {
|
||||||
|
loads.push(p.title());
|
||||||
|
});
|
||||||
|
// Go to page 1
|
||||||
|
await page.goto(astro.resolveUrl('/one'));
|
||||||
|
const p = page.locator('#one');
|
||||||
|
await expect(p, 'should have content').toHaveText('Page 1');
|
||||||
|
|
||||||
|
// Clicking href="" stays on page
|
||||||
|
await page.click('#click-self');
|
||||||
|
await expect(p, 'should have content').toHaveText('Page 1');
|
||||||
|
expect(loads.length, 'There should only be 1 page load').toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
test('Scroll position restored on back button', async ({ page, astro }) => {
|
test('Scroll position restored on back button', async ({ page, astro }) => {
|
||||||
// Go to page 1
|
// Go to page 1
|
||||||
await page.goto(astro.resolveUrl('/long-page'));
|
await page.goto(astro.resolveUrl('/long-page'));
|
||||||
|
@ -316,4 +366,34 @@ test.describe('View Transitions', () => {
|
||||||
|
|
||||||
await expect(loads.length, 'There should only be 1 page load').toEqual(1);
|
await expect(loads.length, 'There should only be 1 page load').toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Importing ViewTransitions w/o using the component must not mess with history', async ({
|
||||||
|
page,
|
||||||
|
astro,
|
||||||
|
}) => {
|
||||||
|
const loads = [];
|
||||||
|
page.addListener('load', async (p) => {
|
||||||
|
loads.push(p);
|
||||||
|
});
|
||||||
|
// Go to the half bakeed page
|
||||||
|
await page.goto(astro.resolveUrl('/half-baked'));
|
||||||
|
let p = page.locator('#half-baked');
|
||||||
|
await expect(p, 'should have content').toHaveText('Half Baked');
|
||||||
|
|
||||||
|
// click a hash link to navigate further down the page
|
||||||
|
await page.click('#click-hash');
|
||||||
|
// still on page
|
||||||
|
p = page.locator('#half-baked');
|
||||||
|
await expect(p, 'should have content').toHaveText('Half Baked');
|
||||||
|
|
||||||
|
// go back within same page without reloading
|
||||||
|
await page.goBack();
|
||||||
|
p = page.locator('#half-baked');
|
||||||
|
await expect(p, 'should have content').toHaveText('Half Baked');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
loads.length,
|
||||||
|
'There should be only 1 page load. No additional loads for going back on same page'
|
||||||
|
).toEqual(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { escapeHTML, isHTMLString, markHTMLString } from '../escape.js';
|
||||||
import { isAstroComponentInstance, isRenderTemplateResult } from './astro/index.js';
|
import { isAstroComponentInstance, isRenderTemplateResult } from './astro/index.js';
|
||||||
import { isRenderInstance, type RenderDestination } from './common.js';
|
import { isRenderInstance, type RenderDestination } from './common.js';
|
||||||
import { SlotString } from './slot.js';
|
import { SlotString } from './slot.js';
|
||||||
|
import { renderToBufferDestination } from './util.js';
|
||||||
|
|
||||||
export async function renderChild(destination: RenderDestination, child: any) {
|
export async function renderChild(destination: RenderDestination, child: any) {
|
||||||
child = await child;
|
child = await child;
|
||||||
|
@ -10,8 +11,14 @@ export async function renderChild(destination: RenderDestination, child: any) {
|
||||||
} else if (isHTMLString(child)) {
|
} else if (isHTMLString(child)) {
|
||||||
destination.write(child);
|
destination.write(child);
|
||||||
} else if (Array.isArray(child)) {
|
} else if (Array.isArray(child)) {
|
||||||
for (const c of child) {
|
// Render all children eagerly and in parallel
|
||||||
await renderChild(destination, c);
|
const childRenders = child.map((c) => {
|
||||||
|
return renderToBufferDestination((bufferDestination) => {
|
||||||
|
return renderChild(bufferDestination, c);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
for (const childRender of childRenders) {
|
||||||
|
await childRender.renderToFinalDestination(destination);
|
||||||
}
|
}
|
||||||
} else if (typeof child === 'function') {
|
} else if (typeof child === 'function') {
|
||||||
// Special: If a child is a function, call it automatically.
|
// Special: If a child is a function, call it automatically.
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { markHTMLString } from '../../escape.js';
|
||||||
import { isPromise } from '../../util.js';
|
import { isPromise } from '../../util.js';
|
||||||
import { renderChild } from '../any.js';
|
import { renderChild } from '../any.js';
|
||||||
import type { RenderDestination } from '../common.js';
|
import type { RenderDestination } from '../common.js';
|
||||||
|
import { renderToBufferDestination } from '../util.js';
|
||||||
|
|
||||||
const renderTemplateResultSym = Symbol.for('astro.renderTemplateResult');
|
const renderTemplateResultSym = Symbol.for('astro.renderTemplateResult');
|
||||||
|
|
||||||
|
@ -32,14 +33,23 @@ export class RenderTemplateResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
async render(destination: RenderDestination) {
|
async render(destination: RenderDestination) {
|
||||||
for (let i = 0; i < this.htmlParts.length; i++) {
|
// Render all expressions eagerly and in parallel
|
||||||
const html = this.htmlParts[i];
|
const expRenders = this.expressions.map((exp) => {
|
||||||
const exp = this.expressions[i];
|
return renderToBufferDestination((bufferDestination) => {
|
||||||
|
|
||||||
destination.write(markHTMLString(html));
|
|
||||||
// Skip render if falsy, except the number 0
|
// Skip render if falsy, except the number 0
|
||||||
if (exp || exp === 0) {
|
if (exp || exp === 0) {
|
||||||
await renderChild(destination, exp);
|
return renderChild(bufferDestination, exp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 0; i < this.htmlParts.length; i++) {
|
||||||
|
const html = this.htmlParts[i];
|
||||||
|
const expRender = expRenders[i];
|
||||||
|
|
||||||
|
destination.write(markHTMLString(html));
|
||||||
|
if (expRender) {
|
||||||
|
await expRender.renderToFinalDestination(destination);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,9 +37,11 @@ export interface RenderDestination {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RenderInstance {
|
export interface RenderInstance {
|
||||||
render(destination: RenderDestination): Promise<void> | void;
|
render: RenderFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type RenderFunction = (destination: RenderDestination) => Promise<void> | void;
|
||||||
|
|
||||||
export const Fragment = Symbol.for('astro:fragment');
|
export const Fragment = Symbol.for('astro:fragment');
|
||||||
export const Renderer = Symbol.for('astro:renderer');
|
export const Renderer = Symbol.for('astro:renderer');
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,6 @@ import {
|
||||||
Renderer,
|
Renderer,
|
||||||
chunkToString,
|
chunkToString,
|
||||||
type RenderDestination,
|
type RenderDestination,
|
||||||
type RenderDestinationChunk,
|
|
||||||
type RenderInstance,
|
type RenderInstance,
|
||||||
} from './common.js';
|
} from './common.js';
|
||||||
import { componentIsHTMLElement, renderHTMLElement } from './dom.js';
|
import { componentIsHTMLElement, renderHTMLElement } from './dom.js';
|
||||||
|
@ -422,27 +421,12 @@ function renderAstroComponent(
|
||||||
slots: any = {}
|
slots: any = {}
|
||||||
): RenderInstance {
|
): RenderInstance {
|
||||||
const instance = createAstroComponentInstance(result, displayName, Component, props, slots);
|
const instance = createAstroComponentInstance(result, displayName, Component, props, slots);
|
||||||
|
|
||||||
// Eagerly render the component so they are rendered in parallel.
|
|
||||||
// Render to buffer for now until our returned render function is called.
|
|
||||||
const bufferChunks: RenderDestinationChunk[] = [];
|
|
||||||
const bufferDestination: RenderDestination = {
|
|
||||||
write: (chunk) => bufferChunks.push(chunk),
|
|
||||||
};
|
|
||||||
// Don't await for the render to finish to not block streaming
|
|
||||||
const renderPromise = instance.render(bufferDestination);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async render(destination) {
|
async render(destination) {
|
||||||
// Write the buffered chunks to the real destination
|
// NOTE: This render call can't be pre-invoked outside of this function as it'll also initialize the slots
|
||||||
for (const chunk of bufferChunks) {
|
// recursively, which causes each Astro components in the tree to be called bottom-up, and is incorrect.
|
||||||
destination.write(chunk);
|
// The slots are initialized eagerly for head propagation.
|
||||||
}
|
await instance.render(destination);
|
||||||
// Free memory
|
|
||||||
bufferChunks.length = 0;
|
|
||||||
// Re-assign the real destination so `instance.render` will continue and write to the new destination
|
|
||||||
bufferDestination.write = (chunk) => destination.write(chunk);
|
|
||||||
await renderPromise;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import type { SSRElement } from '../../../@types/astro';
|
import type { SSRElement } from '../../../@types/astro';
|
||||||
|
import type { RenderDestination, RenderDestinationChunk, RenderFunction } from './common.js';
|
||||||
|
|
||||||
import { HTMLString, markHTMLString } from '../escape.js';
|
import { HTMLString, markHTMLString } from '../escape.js';
|
||||||
import { clsx } from 'clsx';
|
import { clsx } from 'clsx';
|
||||||
|
@ -141,3 +142,56 @@ export function renderElement(
|
||||||
}
|
}
|
||||||
return `<${name}${internalSpreadAttributes(props, shouldEscape)}>${children}</${name}>`;
|
return `<${name}${internalSpreadAttributes(props, shouldEscape)}>${children}</${name}>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the `bufferRenderFunction` to prerender it into a buffer destination, and return a promise
|
||||||
|
* with an object containing the `renderToFinalDestination` function to flush the buffer to the final
|
||||||
|
* destination.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* // Render components in parallel ahead of time
|
||||||
|
* const finalRenders = [ComponentA, ComponentB].map((comp) => {
|
||||||
|
* return renderToBufferDestination(async (bufferDestination) => {
|
||||||
|
* await renderComponentToDestination(bufferDestination);
|
||||||
|
* });
|
||||||
|
* });
|
||||||
|
* // Render array of components serially
|
||||||
|
* for (const finalRender of finalRenders) {
|
||||||
|
* await finalRender.renderToFinalDestination(finalDestination);
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function renderToBufferDestination(bufferRenderFunction: RenderFunction): {
|
||||||
|
renderToFinalDestination: RenderFunction;
|
||||||
|
} {
|
||||||
|
// Keep chunks in memory
|
||||||
|
const bufferChunks: RenderDestinationChunk[] = [];
|
||||||
|
const bufferDestination: RenderDestination = {
|
||||||
|
write: (chunk) => bufferChunks.push(chunk),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Don't await for the render to finish to not block streaming
|
||||||
|
const renderPromise = bufferRenderFunction(bufferDestination);
|
||||||
|
|
||||||
|
// Return a closure that writes the buffered chunk
|
||||||
|
return {
|
||||||
|
async renderToFinalDestination(destination) {
|
||||||
|
// Write the buffered chunks to the real destination
|
||||||
|
for (const chunk of bufferChunks) {
|
||||||
|
destination.write(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: We don't empty `bufferChunks` after it's written as benchmarks show
|
||||||
|
// that it causes poorer performance, likely due to forced memory re-allocation,
|
||||||
|
// instead of letting the garbage collector handle it automatically.
|
||||||
|
// (Unsure how this affects on limited memory machines)
|
||||||
|
|
||||||
|
// Re-assign the real destination so `instance.render` will continue and write to the new destination
|
||||||
|
bufferDestination.write = (chunk) => destination.write(chunk);
|
||||||
|
|
||||||
|
// Wait for render to finish entirely
|
||||||
|
await renderPromise;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -102,6 +102,12 @@ describe('Astro basics', () => {
|
||||||
// will have already erred by now, but add test anyway
|
// will have already erred by now, but add test anyway
|
||||||
expect(await fixture.readFile('/special-“characters” -in-file/index.html')).to.be.ok;
|
expect(await fixture.readFile('/special-“characters” -in-file/index.html')).to.be.ok;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders the components top-down', async () => {
|
||||||
|
const html = await fixture.readFile('/order/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('#rendered-order').text()).to.eq('Rendered order: A, B');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Supports void elements whose name is a string (#2062)', async () => {
|
it('Supports void elements whose name is a string (#2062)', async () => {
|
||||||
|
|
7
packages/astro/test/fixtures/astro-basic/src/components/OrderA.astro
vendored
Normal file
7
packages/astro/test/fixtures/astro-basic/src/components/OrderA.astro
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
globalThis.__ASTRO_TEST_ORDER__ ??= []
|
||||||
|
globalThis.__ASTRO_TEST_ORDER__.push('A')
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>A</p>
|
||||||
|
<slot />
|
7
packages/astro/test/fixtures/astro-basic/src/components/OrderB.astro
vendored
Normal file
7
packages/astro/test/fixtures/astro-basic/src/components/OrderB.astro
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
globalThis.__ASTRO_TEST_ORDER__ ??= []
|
||||||
|
globalThis.__ASTRO_TEST_ORDER__.push('B')
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>B</p>
|
||||||
|
<slot />
|
1
packages/astro/test/fixtures/astro-basic/src/components/OrderLast.astro
vendored
Normal file
1
packages/astro/test/fixtures/astro-basic/src/components/OrderLast.astro
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<p id="rendered-order">Rendered order: {() => (globalThis.__ASTRO_TEST_ORDER__ ?? []).join(', ')}</p>
|
13
packages/astro/test/fixtures/astro-basic/src/pages/order.astro
vendored
Normal file
13
packages/astro/test/fixtures/astro-basic/src/pages/order.astro
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
import OrderA from "../components/OrderA.astro";
|
||||||
|
import OrderB from "../components/OrderB.astro";
|
||||||
|
import OrderLast from "../components/OrderLast.astro";
|
||||||
|
|
||||||
|
globalThis.__ASTRO_TEST_ORDER__ = [];
|
||||||
|
---
|
||||||
|
|
||||||
|
<OrderA>
|
||||||
|
<OrderB>
|
||||||
|
<OrderLast />
|
||||||
|
</OrderB>
|
||||||
|
</OrderA>
|
|
@ -89,12 +89,59 @@ It's then possible to update the preview script in your `package.json` to `"prev
|
||||||
|
|
||||||
You can access all the Cloudflare bindings and environment variables from Astro components and API routes through `Astro.locals`.
|
You can access all the Cloudflare bindings and environment variables from Astro components and API routes through `Astro.locals`.
|
||||||
|
|
||||||
```js
|
If you're inside an `.astro` file, you access the runtime using the `Astro.locals` global:
|
||||||
|
|
||||||
|
```astro
|
||||||
const env = Astro.locals.runtime.env;
|
const env = Astro.locals.runtime.env;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
From an endpoint:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// src/pages/api/someFile.js
|
||||||
|
export function get(context) {
|
||||||
|
const runtime = context.locals.runtime;
|
||||||
|
|
||||||
|
return new Response('Some body');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Depending on your adapter mode (advanced = worker, directory = pages), the runtime object will look a little different due to differences in the Cloudflare API.
|
Depending on your adapter mode (advanced = worker, directory = pages), the runtime object will look a little different due to differences in the Cloudflare API.
|
||||||
|
|
||||||
|
If you're using the `advanced` runtime, you can type the `runtime` object as following:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/env.d.ts
|
||||||
|
/// <reference types="astro/client" />
|
||||||
|
import type { AdvancedRuntime } from '@astrojs/cloudflare';
|
||||||
|
|
||||||
|
declare namespace App {
|
||||||
|
interface Locals extends AdvancedRuntime {
|
||||||
|
user: {
|
||||||
|
name: string;
|
||||||
|
surname: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're using the `directory` runtime, you can type the `runtime` object as following:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/env.d.ts
|
||||||
|
/// <reference types="astro/client" />
|
||||||
|
import type { DirectoryRuntime } from '@astrojs/cloudflare';
|
||||||
|
|
||||||
|
declare namespace App {
|
||||||
|
interface Locals extends DirectoryRuntime {
|
||||||
|
user: {
|
||||||
|
name: string;
|
||||||
|
surname: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
See Cloudflare's documentation for [working with environment variables](https://developers.cloudflare.com/pages/platform/functions/bindings/#environment-variables).
|
See Cloudflare's documentation for [working with environment variables](https://developers.cloudflare.com/pages/platform/functions/bindings/#environment-variables).
|
||||||
|
|
|
@ -7,6 +7,9 @@ import { sep } from 'node:path';
|
||||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||||
import glob from 'tiny-glob';
|
import glob from 'tiny-glob';
|
||||||
|
|
||||||
|
export type { AdvancedRuntime } from './server.advanced';
|
||||||
|
export type { DirectoryRuntime } from './server.directory';
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
mode: 'directory' | 'advanced';
|
mode: 'directory' | 'advanced';
|
||||||
functionPerRoute?: boolean;
|
functionPerRoute?: boolean;
|
||||||
|
|
|
@ -12,7 +12,7 @@ type Env = {
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface WorkerRuntime {
|
export interface AdvancedRuntime {
|
||||||
runtime: {
|
runtime: {
|
||||||
waitUntil: (promise: Promise<any>) => void;
|
waitUntil: (promise: Promise<any>) => void;
|
||||||
env: Env;
|
env: Env;
|
||||||
|
@ -57,7 +57,7 @@ export function createExports(manifest: SSRManifest) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const locals: WorkerRuntime = {
|
const locals: AdvancedRuntime = {
|
||||||
runtime: {
|
runtime: {
|
||||||
waitUntil: (promise: Promise<any>) => {
|
waitUntil: (promise: Promise<any>) => {
|
||||||
context.waitUntil(promise);
|
context.waitUntil(promise);
|
||||||
|
|
|
@ -7,7 +7,7 @@ if (!isNode) {
|
||||||
process.env = getProcessEnvProxy();
|
process.env = getProcessEnvProxy();
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FunctionRuntime {
|
export interface DirectoryRuntime {
|
||||||
runtime: {
|
runtime: {
|
||||||
waitUntil: (promise: Promise<any>) => void;
|
waitUntil: (promise: Promise<any>) => void;
|
||||||
env: EventContext<unknown, string, unknown>['env'];
|
env: EventContext<unknown, string, unknown>['env'];
|
||||||
|
@ -54,7 +54,7 @@ export function createExports(manifest: SSRManifest) {
|
||||||
cf: request.cf,
|
cf: request.cf,
|
||||||
});
|
});
|
||||||
|
|
||||||
const locals: FunctionRuntime = {
|
const locals: DirectoryRuntime = {
|
||||||
runtime: {
|
runtime: {
|
||||||
waitUntil: (promise: Promise<any>) => {
|
waitUntil: (promise: Promise<any>) => {
|
||||||
context.waitUntil(promise);
|
context.waitUntil(promise);
|
||||||
|
|
|
@ -34,6 +34,8 @@ yarn astro add netlify
|
||||||
pnpm astro add netlify
|
pnpm astro add netlify
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Add dependencies manually
|
||||||
|
|
||||||
If you prefer to install the adapter manually instead, complete the following two steps:
|
If you prefer to install the adapter manually instead, complete the following two steps:
|
||||||
|
|
||||||
1. Install the Netlify adapter to your project’s dependencies using your preferred package manager. If you’re using npm or aren’t sure, run this in the terminal:
|
1. Install the Netlify adapter to your project’s dependencies using your preferred package manager. If you’re using npm or aren’t sure, run this in the terminal:
|
||||||
|
|
|
@ -52,6 +52,15 @@
|
||||||
- Updated dependencies [[`1eae2e3f7`](https://github.com/withastro/astro/commit/1eae2e3f7d693c9dfe91c8ccfbe606d32bf2fb81), [`76ddef19c`](https://github.com/withastro/astro/commit/76ddef19ccab6e5f7d3a5740cd41acf10e334b38), [`9b4f70a62`](https://github.com/withastro/astro/commit/9b4f70a629f55e461759ba46f68af7097a2e9215), [`3fdf509b2`](https://github.com/withastro/astro/commit/3fdf509b2731a9b2f972d89291e57cf78d62c769), [`2f951cd40`](https://github.com/withastro/astro/commit/2f951cd403dfcc2c3ca6aae618ae3e1409516e32), [`c022a4217`](https://github.com/withastro/astro/commit/c022a4217a805d223c1494e9eda4e48bbf810388), [`67becaa58`](https://github.com/withastro/astro/commit/67becaa580b8f787df58de66b7008b7098f1209c), [`bc37331d8`](https://github.com/withastro/astro/commit/bc37331d8154e3e95a8df9131e4e014e78a7a9e7), [`dfc2d93e3`](https://github.com/withastro/astro/commit/dfc2d93e3c645995379358fabbdfa9aab99f43d8), [`3dc1ca2fa`](https://github.com/withastro/astro/commit/3dc1ca2fac8d9965cc5085a5d09e72ed87b4281a), [`1be84dfee`](https://github.com/withastro/astro/commit/1be84dfee3ce8e6f5cc624f99aec4e980f6fde37), [`35f01df79`](https://github.com/withastro/astro/commit/35f01df797d23315f2bee2fc3fd795adb0559c58), [`3fdf509b2`](https://github.com/withastro/astro/commit/3fdf509b2731a9b2f972d89291e57cf78d62c769), [`78de801f2`](https://github.com/withastro/astro/commit/78de801f21fd4ca1653950027d953bf08614566b), [`59d6e569f`](https://github.com/withastro/astro/commit/59d6e569f63e175c97e82e94aa7974febfb76f7c), [`7723c4cc9`](https://github.com/withastro/astro/commit/7723c4cc93298c2e6530e55da7afda048f22cf81), [`fb5cd6b56`](https://github.com/withastro/astro/commit/fb5cd6b56dc27a71366ed5e1ab8bfe9b8f96bac5), [`631b9c410`](https://github.com/withastro/astro/commit/631b9c410d5d66fa384674027ba95d69ebb5063f)]:
|
- Updated dependencies [[`1eae2e3f7`](https://github.com/withastro/astro/commit/1eae2e3f7d693c9dfe91c8ccfbe606d32bf2fb81), [`76ddef19c`](https://github.com/withastro/astro/commit/76ddef19ccab6e5f7d3a5740cd41acf10e334b38), [`9b4f70a62`](https://github.com/withastro/astro/commit/9b4f70a629f55e461759ba46f68af7097a2e9215), [`3fdf509b2`](https://github.com/withastro/astro/commit/3fdf509b2731a9b2f972d89291e57cf78d62c769), [`2f951cd40`](https://github.com/withastro/astro/commit/2f951cd403dfcc2c3ca6aae618ae3e1409516e32), [`c022a4217`](https://github.com/withastro/astro/commit/c022a4217a805d223c1494e9eda4e48bbf810388), [`67becaa58`](https://github.com/withastro/astro/commit/67becaa580b8f787df58de66b7008b7098f1209c), [`bc37331d8`](https://github.com/withastro/astro/commit/bc37331d8154e3e95a8df9131e4e014e78a7a9e7), [`dfc2d93e3`](https://github.com/withastro/astro/commit/dfc2d93e3c645995379358fabbdfa9aab99f43d8), [`3dc1ca2fa`](https://github.com/withastro/astro/commit/3dc1ca2fac8d9965cc5085a5d09e72ed87b4281a), [`1be84dfee`](https://github.com/withastro/astro/commit/1be84dfee3ce8e6f5cc624f99aec4e980f6fde37), [`35f01df79`](https://github.com/withastro/astro/commit/35f01df797d23315f2bee2fc3fd795adb0559c58), [`3fdf509b2`](https://github.com/withastro/astro/commit/3fdf509b2731a9b2f972d89291e57cf78d62c769), [`78de801f2`](https://github.com/withastro/astro/commit/78de801f21fd4ca1653950027d953bf08614566b), [`59d6e569f`](https://github.com/withastro/astro/commit/59d6e569f63e175c97e82e94aa7974febfb76f7c), [`7723c4cc9`](https://github.com/withastro/astro/commit/7723c4cc93298c2e6530e55da7afda048f22cf81), [`fb5cd6b56`](https://github.com/withastro/astro/commit/fb5cd6b56dc27a71366ed5e1ab8bfe9b8f96bac5), [`631b9c410`](https://github.com/withastro/astro/commit/631b9c410d5d66fa384674027ba95d69ebb5063f)]:
|
||||||
- astro@3.0.0-beta.0
|
- astro@3.0.0-beta.0
|
||||||
|
|
||||||
|
## 5.3.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- [#8176](https://github.com/withastro/astro/pull/8176) [`d08c83ee3`](https://github.com/withastro/astro/commit/d08c83ee3fe0f10374264f61ee473255dcf0cd06) Thanks [@ematipico](https://github.com/ematipico)! - Fix an issue where `express` couldn't use the `handler` in `middleware` mode.
|
||||||
|
|
||||||
|
- Updated dependencies [[`582132328`](https://github.com/withastro/astro/commit/5821323285646aee7ff9194a505f708028e4db57), [`fddd4dc71`](https://github.com/withastro/astro/commit/fddd4dc71af321bd6b4d01bb4b1b955284846e60), [`cfc465dde`](https://github.com/withastro/astro/commit/cfc465ddebcc58d20f29ecffaa857a77525435a9), [`95120efbe`](https://github.com/withastro/astro/commit/95120efbe817163663492181cbeb225849354493), [`273335cb0`](https://github.com/withastro/astro/commit/273335cb01615c3c06d46c02464f4496a81f8d0b), [`9142178b1`](https://github.com/withastro/astro/commit/9142178b113443749b87c1d259859b42a3d7a9c4), [`179796405`](https://github.com/withastro/astro/commit/179796405e053b559d83f84507e5a465861a029a)]:
|
||||||
|
- astro@2.10.13
|
||||||
|
|
||||||
## 5.3.5
|
## 5.3.5
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|
|
@ -31,6 +31,8 @@ yarn astro add node
|
||||||
pnpm astro add node
|
pnpm astro add node
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Add dependencies manually
|
||||||
|
|
||||||
If you prefer to install the adapter manually instead, complete the following two steps:
|
If you prefer to install the adapter manually instead, complete the following two steps:
|
||||||
|
|
||||||
1. Install the Node adapter to your project’s dependencies using your preferred package manager. If you’re using npm or aren’t sure, run this in the terminal:
|
1. Install the Node adapter to your project’s dependencies using your preferred package manager. If you’re using npm or aren’t sure, run this in the terminal:
|
||||||
|
|
|
@ -33,6 +33,8 @@ yarn astro add vercel
|
||||||
pnpm astro add vercel
|
pnpm astro add vercel
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Add dependencies manually
|
||||||
|
|
||||||
If you prefer to install the adapter manually instead, complete the following two steps:
|
If you prefer to install the adapter manually instead, complete the following two steps:
|
||||||
|
|
||||||
1. Install the Vercel adapter to your project’s dependencies using your preferred package manager. If you’re using npm or aren’t sure, run this in the terminal:
|
1. Install the Vercel adapter to your project’s dependencies using your preferred package manager. If you’re using npm or aren’t sure, run this in the terminal:
|
||||||
|
|
Loading…
Reference in a new issue