diff options
31 files changed, 391 insertions, 93 deletions
diff --git a/.changeset/brown-numbers-prove.md b/.changeset/brown-numbers-prove.md deleted file mode 100644 index 96db75af0..000000000 --- a/.changeset/brown-numbers-prove.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -Displays a new config error if `outDir` is placed within `publicDir`. diff --git a/.changeset/many-ears-drum.md b/.changeset/many-ears-drum.md deleted file mode 100644 index f728408a0..000000000 --- a/.changeset/many-ears-drum.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -Fix missing type for `imageConfig` export from `astro:assets` diff --git a/.changeset/rich-tigers-march.md b/.changeset/rich-tigers-march.md deleted file mode 100644 index fb698048f..000000000 --- a/.changeset/rich-tigers-march.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@astrojs/node': patch ---- - -Fix an issue where `express` couldn't use the `handler` in `middleware` mode. diff --git a/.changeset/silent-snakes-shave.md b/.changeset/silent-snakes-shave.md new file mode 100644 index 000000000..3f8c8c3ee --- /dev/null +++ b/.changeset/silent-snakes-shave.md @@ -0,0 +1,5 @@ +--- +'@astrojs/cloudflare': patch +--- + +Improve documentation and export the types needed to type the `runtime` object. diff --git a/.changeset/unlucky-cougars-heal.md b/.changeset/unlucky-cougars-heal.md deleted file mode 100644 index a6579499e..000000000 --- a/.changeset/unlucky-cougars-heal.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -Make typing of `defineCollection` more permissive to support advanced union and intersection types diff --git a/examples/portfolio/src/components/Hero.astro b/examples/portfolio/src/components/Hero.astro index 21b5ed1bf..30460420a 100644 --- a/examples/portfolio/src/components/Hero.astro +++ b/examples/portfolio/src/components/Hero.astro @@ -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 { diff --git a/examples/portfolio/src/components/Nav.astro b/examples/portfolio/src/components/Nav.astro index 2e9717884..bf9ac7869 100644 --- a/examples/portfolio/src/components/Nav.astro +++ b/examples/portfolio/src/components/Nav.astro @@ -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"> { diff --git a/packages/astro/CHANGELOG.md b/packages/astro/CHANGELOG.md index eefbb2eef..d2161969f 100644 --- a/packages/astro/CHANGELOG.md +++ b/packages/astro/CHANGELOG.md @@ -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 diff --git a/packages/astro/components/ViewTransitions.astro b/packages/astro/components/ViewTransitions.astro index 4b7a46551..612b89659 100644 --- a/packages/astro/components/ViewTransitions.astro +++ b/packages/astro/components/ViewTransitions.astro @@ -20,22 +20,21 @@ const { fallback = 'animate' } = Astro.props as Props; type Events = 'astro:load' | 'astro:beforeload'; const persistState = (state: State) => history.replaceState(state, ''); + const supportsViewTransitions = !!document.startViewTransition; + const transitionEnabledOnThisPage = () => + !!document.querySelector('[name="astro-view-transitions-enabled"]'); + const triggerEvent = (name: Events) => document.dispatchEvent(new Event(name)); + 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) { + if (!history.state && transitionEnabledOnThisPage()) { persistState({ index: currentHistoryIndex, scrollY: 0 }); } - const supportsViewTransitions = !!document.startViewTransition; - const transitionEnabledOnThisPage = () => - !!document.querySelector('[name="astro-view-transitions-enabled"]'); - const triggerEvent = (name: Events) => document.dispatchEvent(new Event(name)); - const onload = () => triggerEvent('astro:load'); - const PERSIST_ATTR = 'data-astro-transition-persist'; - 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; } diff --git a/packages/astro/e2e/fixtures/view-transitions/src/pages/half-baked.astro b/packages/astro/e2e/fixtures/view-transitions/src/pages/half-baked.astro new file mode 100644 index 000000000..40298d125 --- /dev/null +++ b/packages/astro/e2e/fixtures/view-transitions/src/pages/half-baked.astro @@ -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> diff --git a/packages/astro/e2e/fixtures/view-transitions/src/pages/one.astro b/packages/astro/e2e/fixtures/view-transitions/src/pages/one.astro index b24338d9d..3f9666c1d 100644 --- a/packages/astro/e2e/fixtures/view-transitions/src/pages/one.astro +++ b/packages/astro/e2e/fixtures/view-transitions/src/pages/one.astro @@ -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> diff --git a/packages/astro/e2e/fixtures/view-transitions/src/pages/three.astro b/packages/astro/e2e/fixtures/view-transitions/src/pages/three.astro index 676e8b61b..eddc049a8 100644 --- a/packages/astro/e2e/fixtures/view-transitions/src/pages/three.astro +++ b/packages/astro/e2e/fixtures/view-transitions/src/pages/three.astro @@ -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> diff --git a/packages/astro/e2e/view-transitions.test.js b/packages/astro/e2e/view-transitions.test.js index 7aeb6502a..c681bfea0 100644 --- a/packages/astro/e2e/view-transitions.test.js +++ b/packages/astro/e2e/view-transitions.test.js @@ -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); + }); }); diff --git a/packages/astro/src/runtime/server/render/any.ts b/packages/astro/src/runtime/server/render/any.ts index 7c181fecb..89c927576 100644 --- a/packages/astro/src/runtime/server/render/any.ts +++ b/packages/astro/src/runtime/server/render/any.ts @@ -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. diff --git a/packages/astro/src/runtime/server/render/astro/render-template.ts b/packages/astro/src/runtime/server/render/astro/render-template.ts index 1d5af33fc..f12344fe8 100644 --- a/packages/astro/src/runtime/server/render/astro/render-template.ts +++ b/packages/astro/src/runtime/server/render/astro/render-template.ts @@ -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); } } } diff --git a/packages/astro/src/runtime/server/render/common.ts b/packages/astro/src/runtime/server/render/common.ts index d4c7a1242..7beaed2eb 100644 --- a/packages/astro/src/runtime/server/render/common.ts +++ b/packages/astro/src/runtime/server/render/common.ts @@ -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'); diff --git a/packages/astro/src/runtime/server/render/component.ts b/packages/astro/src/runtime/server/render/component.ts index c92316d13..1c7701fd2 100644 --- a/packages/astro/src/runtime/server/render/component.ts +++ b/packages/astro/src/runtime/server/render/component.ts @@ -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); }, }; } diff --git a/packages/astro/src/runtime/server/render/util.ts b/packages/astro/src/runtime/server/render/util.ts index e77f8ed8b..f9783fce3 100644 --- a/packages/astro/src/runtime/server/render/util.ts +++ b/packages/astro/src/runtime/server/render/util.ts @@ -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; + }, + }; +} diff --git a/packages/astro/test/astro-basic.test.js b/packages/astro/test/astro-basic.test.js index 92b6af410..3a75c1acc 100644 --- a/packages/astro/test/astro-basic.test.js +++ b/packages/astro/test/astro-basic.test.js @@ -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 () => { diff --git a/packages/astro/test/fixtures/astro-basic/src/components/OrderA.astro b/packages/astro/test/fixtures/astro-basic/src/components/OrderA.astro new file mode 100644 index 000000000..7caa0d868 --- /dev/null +++ b/packages/astro/test/fixtures/astro-basic/src/components/OrderA.astro @@ -0,0 +1,7 @@ +--- +globalThis.__ASTRO_TEST_ORDER__ ??= [] +globalThis.__ASTRO_TEST_ORDER__.push('A') +--- + +<p>A</p> +<slot /> diff --git a/packages/astro/test/fixtures/astro-basic/src/components/OrderB.astro b/packages/astro/test/fixtures/astro-basic/src/components/OrderB.astro new file mode 100644 index 000000000..b2984be5c --- /dev/null +++ b/packages/astro/test/fixtures/astro-basic/src/components/OrderB.astro @@ -0,0 +1,7 @@ +--- +globalThis.__ASTRO_TEST_ORDER__ ??= [] +globalThis.__ASTRO_TEST_ORDER__.push('B') +--- + +<p>B</p> +<slot /> diff --git a/packages/astro/test/fixtures/astro-basic/src/components/OrderLast.astro b/packages/astro/test/fixtures/astro-basic/src/components/OrderLast.astro new file mode 100644 index 000000000..35cd932dc --- /dev/null +++ b/packages/astro/test/fixtures/astro-basic/src/components/OrderLast.astro @@ -0,0 +1 @@ +<p id="rendered-order">Rendered order: {() => (globalThis.__ASTRO_TEST_ORDER__ ?? []).join(', ')}</p> diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/order.astro b/packages/astro/test/fixtures/astro-basic/src/pages/order.astro new file mode 100644 index 000000000..ca5b2fdd5 --- /dev/null +++ b/packages/astro/test/fixtures/astro-basic/src/pages/order.astro @@ -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> diff --git a/packages/integrations/cloudflare/README.md b/packages/integrations/cloudflare/README.md index d8c1fd197..25cd7c376 100644 --- a/packages/integrations/cloudflare/README.md +++ b/packages/integrations/cloudflare/README.md @@ -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). diff --git a/packages/integrations/cloudflare/src/index.ts b/packages/integrations/cloudflare/src/index.ts index 76e9092ca..718b1efa8 100644 --- a/packages/integrations/cloudflare/src/index.ts +++ b/packages/integrations/cloudflare/src/index.ts @@ -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; diff --git a/packages/integrations/cloudflare/src/server.advanced.ts b/packages/integrations/cloudflare/src/server.advanced.ts index 175756d6a..24358a5e0 100644 --- a/packages/integrations/cloudflare/src/server.advanced.ts +++ b/packages/integrations/cloudflare/src/server.advanced.ts @@ -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); diff --git a/packages/integrations/cloudflare/src/server.directory.ts b/packages/integrations/cloudflare/src/server.directory.ts index d4e4094de..64d820d99 100644 --- a/packages/integrations/cloudflare/src/server.directory.ts +++ b/packages/integrations/cloudflare/src/server.directory.ts @@ -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); diff --git a/packages/integrations/netlify/README.md b/packages/integrations/netlify/README.md index 0e4c3ec93..c29d2d126 100644 --- a/packages/integrations/netlify/README.md +++ b/packages/integrations/netlify/README.md @@ -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: diff --git a/packages/integrations/node/CHANGELOG.md b/packages/integrations/node/CHANGELOG.md index 0eec21df7..516834e77 100644 --- a/packages/integrations/node/CHANGELOG.md +++ b/packages/integrations/node/CHANGELOG.md @@ -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 diff --git a/packages/integrations/node/README.md b/packages/integrations/node/README.md index 5d3642be6..5b49a2716 100644 --- a/packages/integrations/node/README.md +++ b/packages/integrations/node/README.md @@ -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: diff --git a/packages/integrations/vercel/README.md b/packages/integrations/vercel/README.md index 7f13fe6e7..b01a7c716 100644 --- a/packages/integrations/vercel/README.md +++ b/packages/integrations/vercel/README.md @@ -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: |