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;
|
||||
---
|
||||
|
||||
<header class:list={['hero stack gap-4', align]}>
|
||||
<div class:list={['hero stack gap-4', align]}>
|
||||
<div class="stack gap-2">
|
||||
<h1 class="title">{title}</h1>
|
||||
{tagline && <p class="tagline">{tagline}</p>}
|
||||
</div>
|
||||
<slot />
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.hero {
|
||||
|
|
|
@ -36,7 +36,7 @@ const iconLinks: { label: string; href: string; icon: keyof typeof iconPaths }[]
|
|||
</template>
|
||||
</menu-button>
|
||||
</div>
|
||||
<noscript class="menu-noscript">
|
||||
<noscript>
|
||||
<ul class="nav-items">
|
||||
{
|
||||
textLinks.map(({ label, href }) => (
|
||||
|
@ -60,7 +60,7 @@ const iconLinks: { label: string; href: string; icon: keyof typeof iconPaths }[]
|
|||
}
|
||||
</ul>
|
||||
</noscript>
|
||||
<noscript style="display: contents;">
|
||||
<noscript>
|
||||
<div class="menu-footer">
|
||||
<div class="socials">
|
||||
{
|
||||
|
|
|
@ -553,6 +553,24 @@
|
|||
- @astrojs/internal-helpers@0.2.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
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -20,15 +20,6 @@ const { fallback = 'animate' } = Astro.props as Props;
|
|||
type Events = 'astro:load' | 'astro:beforeload';
|
||||
|
||||
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 transitionEnabledOnThisPage = () =>
|
||||
!!document.querySelector('[name="astro-view-transitions-enabled"]');
|
||||
|
@ -36,6 +27,14 @@ const { fallback = 'animate' } = Astro.props as Props;
|
|||
const onload = () => triggerEvent('astro:load');
|
||||
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) => {
|
||||
let wait = false;
|
||||
// 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) {
|
||||
const id = decodeURIComponent(location.hash.slice(1));
|
||||
state.scrollY = document.getElementById(id)?.offsetTop || 0;
|
||||
}
|
||||
if (state?.scrollY != null) {
|
||||
scrollTo(0, state.scrollY);
|
||||
// Overwrite erroneous updates by the scroll handler during transition
|
||||
persistState(state);
|
||||
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'}
|
||||
}
|
||||
} else if (state && state.scrollY !== 0) {
|
||||
scrollTo(0, state.scrollY); // usings default scrollBehavior
|
||||
}
|
||||
|
||||
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
|
||||
// same-origin navigation, but omits special key combos for new tabs, etc.
|
||||
if (
|
||||
link &&
|
||||
link instanceof HTMLAnchorElement &&
|
||||
link.href &&
|
||||
(!link.target || link.target === '_self') &&
|
||||
link.origin === location.origin &&
|
||||
!(
|
||||
// Same page means same path and same query params
|
||||
(location.pathname === link.pathname && location.search === link.search)
|
||||
) &&
|
||||
ev.button === 0 && // left clicks only
|
||||
!ev.metaKey && // new tab (mac)
|
||||
!ev.ctrlKey && // new tab (windows)
|
||||
!ev.altKey && // download
|
||||
!ev.shiftKey &&
|
||||
!ev.defaultPrevented &&
|
||||
transitionEnabledOnThisPage()
|
||||
) {
|
||||
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);
|
||||
!link ||
|
||||
!(link instanceof HTMLAnchorElement) ||
|
||||
!link.href ||
|
||||
(link.target && link.target !== '_self') ||
|
||||
link.origin !== location.origin ||
|
||||
ev.button !== 0 || // left clicks only
|
||||
ev.metaKey || // new tab (mac)
|
||||
ev.ctrlKey || // new tab (windows)
|
||||
ev.altKey || // download
|
||||
ev.shiftKey || // new window
|
||||
ev.defaultPrevented ||
|
||||
!transitionEnabledOnThisPage()
|
||||
)
|
||||
// No page transitions in these cases,
|
||||
// Let the browser standard action handle this
|
||||
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();
|
||||
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) => {
|
||||
if (!transitionEnabledOnThisPage()) {
|
||||
if (!transitionEnabledOnThisPage() && ev.state) {
|
||||
// The current page doesn't haven't View Transitions,
|
||||
// respect that with a full page reload
|
||||
// -- but only for transition managed by us (ev.state is set)
|
||||
location.reload();
|
||||
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-three" href="/three">go to 3</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>
|
||||
</Layout>
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
<main>
|
||||
<p id="three">Page 3</p>
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -112,6 +112,40 @@ test.describe('View Transitions', () => {
|
|||
).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 }) => {
|
||||
const loads = [];
|
||||
page.addListener('load', (p) => {
|
||||
|
@ -190,6 +224,22 @@ test.describe('View Transitions', () => {
|
|||
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 }) => {
|
||||
// Go to page 1
|
||||
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);
|
||||
});
|
||||
|
||||
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 { isRenderInstance, type RenderDestination } from './common.js';
|
||||
import { SlotString } from './slot.js';
|
||||
import { renderToBufferDestination } from './util.js';
|
||||
|
||||
export async function renderChild(destination: RenderDestination, child: any) {
|
||||
child = await child;
|
||||
|
@ -10,8 +11,14 @@ export async function renderChild(destination: RenderDestination, child: any) {
|
|||
} else if (isHTMLString(child)) {
|
||||
destination.write(child);
|
||||
} else if (Array.isArray(child)) {
|
||||
for (const c of child) {
|
||||
await renderChild(destination, c);
|
||||
// Render all children eagerly and in parallel
|
||||
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') {
|
||||
// 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 { renderChild } from '../any.js';
|
||||
import type { RenderDestination } from '../common.js';
|
||||
import { renderToBufferDestination } from '../util.js';
|
||||
|
||||
const renderTemplateResultSym = Symbol.for('astro.renderTemplateResult');
|
||||
|
||||
|
@ -32,14 +33,23 @@ export class RenderTemplateResult {
|
|||
}
|
||||
|
||||
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++) {
|
||||
const html = this.htmlParts[i];
|
||||
const exp = this.expressions[i];
|
||||
const expRender = expRenders[i];
|
||||
|
||||
destination.write(markHTMLString(html));
|
||||
// Skip render if falsy, except the number 0
|
||||
if (exp || exp === 0) {
|
||||
await renderChild(destination, exp);
|
||||
if (expRender) {
|
||||
await expRender.renderToFinalDestination(destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,9 +37,11 @@ export interface RenderDestination {
|
|||
}
|
||||
|
||||
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 Renderer = Symbol.for('astro:renderer');
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ import {
|
|||
Renderer,
|
||||
chunkToString,
|
||||
type RenderDestination,
|
||||
type RenderDestinationChunk,
|
||||
type RenderInstance,
|
||||
} from './common.js';
|
||||
import { componentIsHTMLElement, renderHTMLElement } from './dom.js';
|
||||
|
@ -422,27 +421,12 @@ function renderAstroComponent(
|
|||
slots: any = {}
|
||||
): RenderInstance {
|
||||
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 {
|
||||
async render(destination) {
|
||||
// Write the buffered chunks to the real destination
|
||||
for (const chunk of bufferChunks) {
|
||||
destination.write(chunk);
|
||||
}
|
||||
// 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;
|
||||
// NOTE: This render call can't be pre-invoked outside of this function as it'll also initialize the slots
|
||||
// recursively, which causes each Astro components in the tree to be called bottom-up, and is incorrect.
|
||||
// The slots are initialized eagerly for head propagation.
|
||||
await instance.render(destination);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { SSRElement } from '../../../@types/astro';
|
||||
import type { RenderDestination, RenderDestinationChunk, RenderFunction } from './common.js';
|
||||
|
||||
import { HTMLString, markHTMLString } from '../escape.js';
|
||||
import { clsx } from 'clsx';
|
||||
|
@ -141,3 +142,56 @@ export function renderElement(
|
|||
}
|
||||
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
|
||||
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 () => {
|
||||
|
|
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`.
|
||||
|
||||
```js
|
||||
If you're inside an `.astro` file, you access the runtime using the `Astro.locals` global:
|
||||
|
||||
```astro
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
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 glob from 'tiny-glob';
|
||||
|
||||
export type { AdvancedRuntime } from './server.advanced';
|
||||
export type { DirectoryRuntime } from './server.directory';
|
||||
|
||||
type Options = {
|
||||
mode: 'directory' | 'advanced';
|
||||
functionPerRoute?: boolean;
|
||||
|
|
|
@ -12,7 +12,7 @@ type Env = {
|
|||
name: string;
|
||||
};
|
||||
|
||||
interface WorkerRuntime {
|
||||
export interface AdvancedRuntime {
|
||||
runtime: {
|
||||
waitUntil: (promise: Promise<any>) => void;
|
||||
env: Env;
|
||||
|
@ -57,7 +57,7 @@ export function createExports(manifest: SSRManifest) {
|
|||
},
|
||||
});
|
||||
|
||||
const locals: WorkerRuntime = {
|
||||
const locals: AdvancedRuntime = {
|
||||
runtime: {
|
||||
waitUntil: (promise: Promise<any>) => {
|
||||
context.waitUntil(promise);
|
||||
|
|
|
@ -7,7 +7,7 @@ if (!isNode) {
|
|||
process.env = getProcessEnvProxy();
|
||||
}
|
||||
|
||||
interface FunctionRuntime {
|
||||
export interface DirectoryRuntime {
|
||||
runtime: {
|
||||
waitUntil: (promise: Promise<any>) => void;
|
||||
env: EventContext<unknown, string, unknown>['env'];
|
||||
|
@ -54,7 +54,7 @@ export function createExports(manifest: SSRManifest) {
|
|||
cf: request.cf,
|
||||
});
|
||||
|
||||
const locals: FunctionRuntime = {
|
||||
const locals: DirectoryRuntime = {
|
||||
runtime: {
|
||||
waitUntil: (promise: Promise<any>) => {
|
||||
context.waitUntil(promise);
|
||||
|
|
|
@ -34,6 +34,8 @@ yarn astro add netlify
|
|||
pnpm astro add netlify
|
||||
```
|
||||
|
||||
### Add dependencies manually
|
||||
|
||||
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:
|
||||
|
|
|
@ -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)]:
|
||||
- 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
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -31,6 +31,8 @@ yarn astro add node
|
|||
pnpm astro add node
|
||||
```
|
||||
|
||||
### Add dependencies manually
|
||||
|
||||
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:
|
||||
|
|
|
@ -33,6 +33,8 @@ yarn astro add vercel
|
|||
pnpm astro add vercel
|
||||
```
|
||||
|
||||
### Add dependencies manually
|
||||
|
||||
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:
|
||||
|
|
Loading…
Reference in a new issue