Merge remote-tracking branch 'origin/main' into next

This commit is contained in:
Emanuele Stoppa 2023-08-24 13:02:27 +01:00
commit 44bd0cd825
31 changed files with 392 additions and 94 deletions

View file

@ -1,5 +0,0 @@
---
'astro': patch
---
Displays a new config error if `outDir` is placed within `publicDir`.

View file

@ -1,5 +0,0 @@
---
'astro': patch
---
Fix missing type for `imageConfig` export from `astro:assets`

View file

@ -1,5 +0,0 @@
---
'@astrojs/node': patch
---
Fix an issue where `express` couldn't use the `handler` in `middleware` mode.

View file

@ -0,0 +1,5 @@
---
'@astrojs/cloudflare': patch
---
Improve documentation and export the types needed to type the `runtime` object.

View file

@ -1,5 +0,0 @@
---
'astro': patch
---
Make typing of `defineCollection` more permissive to support advanced union and intersection types

View file

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

View file

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

View file

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

View file

@ -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 (state?.scrollY != null) { if (elem) {
scrollTo(0, state.scrollY); state.scrollY = elem.offsetTop;
// Overwrite erroneous updates by the scroll handler during transition persistState(state); // first guess, later updated by scroll handler
persistState(state); elem.scrollIntoView(); // for Firefox, this should better be {behavior: 'instant'}
}
} else if (state && state.scrollY !== 0) {
scrollTo(0, state.scrollY); // usings default scrollBehavior
} }
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;
) {
ev.preventDefault(); // We do not need to handle same page links because there are no page transitions
navigate('forward', link.href, { index: ++currentHistoryIndex, scrollY: 0 }); // Same page means same path and same query params (but different hash)
const newState: State = { index: currentHistoryIndex, scrollY }; if (location.pathname === link.pathname && location.search === link.search) {
persistState({ index: currentHistoryIndex - 1, scrollY }); if (link.hash) {
history.pushState(newState, '', link.href); // 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();
navigate('forward', link.href, { index: ++currentHistoryIndex, scrollY: 0 });
const newState: State = { index: currentHistoryIndex, scrollY };
persistState({ index: currentHistoryIndex - 1, scrollY });
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;
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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) {
// Render all expressions eagerly and in parallel
const expRenders = this.expressions.map((exp) => {
return renderToBufferDestination((bufferDestination) => {
// Skip render if falsy, except the number 0
if (exp || exp === 0) {
return renderChild(bufferDestination, exp);
}
});
});
for (let i = 0; i < this.htmlParts.length; i++) { for (let i = 0; i < this.htmlParts.length; i++) {
const html = this.htmlParts[i]; const html = this.htmlParts[i];
const exp = this.expressions[i]; const expRender = expRenders[i];
destination.write(markHTMLString(html)); destination.write(markHTMLString(html));
// Skip render if falsy, except the number 0 if (expRender) {
if (exp || exp === 0) { await expRender.renderToFinalDestination(destination);
await renderChild(destination, exp);
} }
} }
} }

View file

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

View file

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

View file

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

View file

@ -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 () => {

View file

@ -0,0 +1,7 @@
---
globalThis.__ASTRO_TEST_ORDER__ ??= []
globalThis.__ASTRO_TEST_ORDER__.push('A')
---
<p>A</p>
<slot />

View file

@ -0,0 +1,7 @@
---
globalThis.__ASTRO_TEST_ORDER__ ??= []
globalThis.__ASTRO_TEST_ORDER__.push('B')
---
<p>B</p>
<slot />

View file

@ -0,0 +1 @@
<p id="rendered-order">Rendered order: {() => (globalThis.__ASTRO_TEST_ORDER__ ?? []).join(', ')}</p>

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 projects dependencies using your preferred package manager. If youre using npm or arent sure, run this in the terminal: 1. Install the Netlify adapter to your projects dependencies using your preferred package manager. If youre using npm or arent sure, run this in the terminal:

View file

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

View file

@ -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 projects dependencies using your preferred package manager. If youre using npm or arent sure, run this in the terminal: 1. Install the Node adapter to your projects dependencies using your preferred package manager. If youre using npm or arent sure, run this in the terminal:

View file

@ -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 projects dependencies using your preferred package manager. If youre using npm or arent sure, run this in the terminal: 1. Install the Vercel adapter to your projects dependencies using your preferred package manager. If youre using npm or arent sure, run this in the terminal: