diff --git a/.changeset/afraid-suits-beam.md b/.changeset/afraid-suits-beam.md deleted file mode 100644 index d93cde160563..000000000000 --- a/.changeset/afraid-suits-beam.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@astrojs/partytown": patch ---- - -Fixes an issue where Partytown scripts didn't execute after view transition diff --git a/.changeset/blue-pets-battle.md b/.changeset/blue-pets-battle.md deleted file mode 100644 index 1752627435dc..000000000000 --- a/.changeset/blue-pets-battle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"astro": patch ---- - -Disables internal file watcher for one-off Vite servers to improve start-up performance diff --git a/.changeset/calm-socks-shake.md b/.changeset/calm-socks-shake.md new file mode 100644 index 000000000000..79f462d2902b --- /dev/null +++ b/.changeset/calm-socks-shake.md @@ -0,0 +1,6 @@ +--- +"@astrojs/markdown-remark": minor +"astro": minor +--- + +Allows remark plugins to pass options specifying how images in `.md` files will be optimized diff --git a/.changeset/cool-foxes-talk.md b/.changeset/cool-foxes-talk.md new file mode 100644 index 000000000000..4513661a8482 --- /dev/null +++ b/.changeset/cool-foxes-talk.md @@ -0,0 +1,32 @@ +--- +"astro": minor +--- + +Adds new helper functions for adapter developers. + +- `Astro.clientAddress` can now be passed directly to the `app.render()` method. +```ts +const response = await app.render(request, { clientAddress: "012.123.23.3" }) +``` + +- Helper functions for converting Node.js HTTP request and response objects to web-compatible `Request` and `Response` objects are now provided as static methods on the `NodeApp` class. +```ts +http.createServer((nodeReq, nodeRes) => { + const request: Request = NodeApp.createRequest(nodeReq) + const response = await app.render(request) + await NodeApp.writeResponse(response, nodeRes) +}) +``` + +- Cookies added via `Astro.cookies.set()` can now be automatically added to the `Response` object by passing the `addCookieHeader` option to `app.render()`. +```diff +-const response = await app.render(request) +-const setCookieHeaders: Array = Array.from(app.setCookieHeaders(webResponse)); + +-if (setCookieHeaders.length) { +- for (const setCookieHeader of setCookieHeaders) { +- headers.append('set-cookie', setCookieHeader); +- } +-} ++const response = await app.render(request, { addCookieHeader: true }) +``` diff --git a/.changeset/curvy-seas-explain.md b/.changeset/curvy-seas-explain.md new file mode 100644 index 000000000000..debdb45a92e6 --- /dev/null +++ b/.changeset/curvy-seas-explain.md @@ -0,0 +1,5 @@ +--- +"@astrojs/mdx": patch +--- + +Removes redundant HMR handling code diff --git a/.changeset/early-cups-poke.md b/.changeset/early-cups-poke.md new file mode 100644 index 000000000000..d4f816dceef9 --- /dev/null +++ b/.changeset/early-cups-poke.md @@ -0,0 +1,7 @@ +--- +"@astrojs/vercel": major +--- + +**Breaking**: Minimum required Astro version is now 4.2.0. +Reorganizes internals to be more maintainable. +--- diff --git a/.changeset/itchy-clouds-invite.md b/.changeset/itchy-clouds-invite.md new file mode 100644 index 000000000000..67dc73f0864f --- /dev/null +++ b/.changeset/itchy-clouds-invite.md @@ -0,0 +1,16 @@ +--- +"astro": minor +--- + +Removes the requirement for non-content files and assets inside content collections to be prefixed with an underscore. For files with extensions like `.astro` or `.css`, you can now remove underscores without seeing a warning in the terminal. + +```diff +src/content/blog/ +post.mdx +- _styles.css +- _Component.astro ++ styles.css ++ Component.astro +``` + +Continue to use underscores in your content collections to exclude individual content files, such as drafts, from the build output. diff --git a/.changeset/long-mangos-walk.md b/.changeset/long-mangos-walk.md deleted file mode 100644 index efa85cb9afb0..000000000000 --- a/.changeset/long-mangos-walk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"astro": patch ---- - -Fix the passthrough image service not generating `srcset` values properly diff --git a/.changeset/orange-trainers-learn.md b/.changeset/orange-trainers-learn.md new file mode 100644 index 000000000000..8b6e059bd1b7 --- /dev/null +++ b/.changeset/orange-trainers-learn.md @@ -0,0 +1,5 @@ +--- +"astro": minor +--- + +Improves the a11y-missing-content rule and error message for audit feature of dev-overlay. This also fixes an error where this check was falsely reporting accessibility errors. diff --git a/.changeset/polite-dogs-join.md b/.changeset/polite-dogs-join.md new file mode 100644 index 000000000000..3bb0128d620f --- /dev/null +++ b/.changeset/polite-dogs-join.md @@ -0,0 +1,5 @@ +--- +"@astrojs/markdoc": patch +--- + +Removes unnecessary `shikiji` dependency diff --git a/.changeset/poor-cherries-buy.md b/.changeset/poor-cherries-buy.md deleted file mode 100644 index d14cff15846c..000000000000 --- a/.changeset/poor-cherries-buy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"astro": patch ---- - -Disables View Transition form handling when the `action` property points to an external URL diff --git a/.changeset/proud-guests-bake.md b/.changeset/proud-guests-bake.md deleted file mode 100644 index 9788786a9469..000000000000 --- a/.changeset/proud-guests-bake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"astro": patch ---- - -Adds an error during the build phase in case `i18n.routing.prefixDefaultLocale` is set to `true` and the index page is missing. diff --git a/.changeset/six-scissors-worry.md b/.changeset/six-scissors-worry.md new file mode 100644 index 000000000000..ebba0da66d63 --- /dev/null +++ b/.changeset/six-scissors-worry.md @@ -0,0 +1,8 @@ +--- +"@astrojs/markdown-remark": minor +"astro": minor +--- + +Adds a new `markdown.shikiConfig.transformers` config option. You can use this option to transform the Shikiji hast (AST format of the generated HTML) to customize the final HTML. Also updates Shikiji to the latest stable version. + +See [Shikiji's documentation](https://shikiji.netlify.app/guide/transformers) for more details about creating your own custom transformers, and [a list of common transformers](https://shikiji.netlify.app/packages/transformers) you can add directly to your project. diff --git a/.changeset/sixty-dogs-sneeze.md b/.changeset/sixty-dogs-sneeze.md new file mode 100644 index 000000000000..13f89232898d --- /dev/null +++ b/.changeset/sixty-dogs-sneeze.md @@ -0,0 +1,24 @@ +--- +"astro": minor +--- + +Adds an experimental flag `clientPrerender` to prerender your prefetched pages on the client with the [Speculation Rules API](https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API). + +```js +// astro.config.mjs +{ + prefetch: { + prefetchAll: true, + defaultStrategy: 'viewport', + }, + experimental: { + clientPrerender: true, + }, +} +``` + +Enabling this feature overrides the default `prefetch` behavior globally to prerender links on the client according to your `prefetch` configuration. Instead of appending a `` tag to the head of the document or fetching the page with JavaScript, a ` + + + + + + diff --git a/packages/astro/e2e/fixtures/tailwindcss/src/pages/index.astro b/packages/astro/e2e/fixtures/tailwindcss/src/pages/index.astro index d901b4233a9c..948fa51df036 100644 --- a/packages/astro/e2e/fixtures/tailwindcss/src/pages/index.astro +++ b/packages/astro/e2e/fixtures/tailwindcss/src/pages/index.astro @@ -1,18 +1,11 @@ --- // Component Imports +import Layout from '../components/Layout.astro'; import Button from '../components/Button.astro'; import Complex from '../components/Complex.astro'; --- - - - - - Astro + TailwindCSS - - - - - - - + + + + diff --git a/packages/astro/e2e/prefetch.test.js b/packages/astro/e2e/prefetch.test.js index 12aa770029c2..72713a0fc67a 100644 --- a/packages/astro/e2e/prefetch.test.js +++ b/packages/astro/e2e/prefetch.test.js @@ -284,3 +284,106 @@ test.describe("Prefetch (prefetchAll: true, defaultStrategy: 'load')", () => { expect(page.locator('link[rel="prefetch"][href$="/prefetch-load"]')).toBeDefined(); }); }); + +// Playwrights `request` event does not appear to fire when using the speculation rules API +// Instead of checking for the added url, each test checks to see if `document.head` +// contains a `script[type=speculationrules]` that has the `url` in it. +test.describe('Prefetch (default), Experimental ({ clientPrerender: true })', () => { + /** + * @param {import('@playwright/test').Page} page + * @param {string} url + * @returns the number of script[type=speculationrules] that have the url + */ + async function scriptIsInHead(page, url) { + return await page.evaluate((testUrl) => { + const scripts = document.head.querySelectorAll('script[type="speculationrules"]'); + let count = 0; + for (const script of scripts) { + /** @type {{ prerender: { urls: string[] }[] }} */ + const speculationRules = JSON.parse(script.textContent); + const specUrl = speculationRules.prerender.at(0).urls.at(0); + const indexOf = specUrl.indexOf(testUrl); + if (indexOf > -1) count++; + } + return count; + }, url); + } + + let devServer; + + test.beforeAll(async ({ astro }) => { + devServer = await astro.startDevServer({ + experimental: { + clientPrerender: true, + }, + }); + }); + + test.afterAll(async () => { + await devServer.stop(); + }); + + test('Link without data-astro-prefetch should not prefetch', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/')); + expect(await scriptIsInHead(page, '/prefetch-default')).toBeFalsy(); + }); + + test('data-astro-prefetch="false" should not prefetch', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/')); + expect(await scriptIsInHead(page, '/prefetch-false')).toBeFalsy(); + }); + + test('Link with search param should prefetch', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/')); + expect(await scriptIsInHead(page, '?search-param=true')).toBeFalsy(); + await page.locator('#prefetch-search-param').hover(); + await page.waitForFunction( + () => document.querySelectorAll('script[type=speculationrules]').length === 2 + ); + expect(await scriptIsInHead(page, '?search-param=true')).toBeTruthy(); + }); + + test('data-astro-prefetch="tap" should prefetch on tap', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/')); + expect(await scriptIsInHead(page, '/prefetch-tap')).toBeFalsy(); + await page.locator('#prefetch-tap').dragTo(page.locator('#prefetch-hover')); + expect(await scriptIsInHead(page, '/prefetch-tap')).toBeTruthy(); + }); + + test('data-astro-prefetch="hover" should prefetch on hover', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/')); + expect(await scriptIsInHead(page, '/prefetch-hover')).toBeFalsy(); + await page.locator('#prefetch-hover').hover(); + await page.waitForFunction( + () => document.querySelectorAll('script[type=speculationrules]').length === 2 + ); + expect(await scriptIsInHead(page, '/prefetch-hover')).toBeTruthy(); + }); + + test('data-astro-prefetch="viewport" should prefetch on viewport', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/')); + expect(await scriptIsInHead(page, '/prefetch-viewport')).toBeFalsy(); + // Scroll down to show the element + await page.locator('#prefetch-viewport').scrollIntoViewIfNeeded(); + await page.waitForFunction( + () => document.querySelectorAll('script[type=speculationrules]').length === 2 + ); + expect(await scriptIsInHead(page, '/prefetch-viewport')).toBeTruthy(); + }); + + test('manual prefetch() works once', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/')); + expect(await scriptIsInHead(page, '/prefetch-manual')).toEqual(0); + await page.locator('#prefetch-manual').click(); + expect(await scriptIsInHead(page, '/prefetch-manual')).toEqual(1); + + // prefetch again should have no effect + await page.locator('#prefetch-manual').click(); + expect(await scriptIsInHead(page, '/prefetch-manual')).toEqual(1); + }); + + test('data-astro-prefetch="load" should prefetch', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/')); + expect(await scriptIsInHead(page, 'prefetch-load')).toBeTruthy(); + }); +}); diff --git a/packages/astro/package.json b/packages/astro/package.json index d9a2f2c1b428..e1743f8f6421 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -1,6 +1,6 @@ { "name": "astro", - "version": "4.1.2", + "version": "4.1.3", "description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.", "type": "module", "author": "withastro", @@ -166,7 +166,7 @@ "resolve": "^1.22.4", "semver": "^7.5.4", "server-destroy": "^1.0.1", - "shikiji": "^0.6.13", + "shikiji": "^0.9.18", "string-width": "^7.0.0", "strip-ansi": "^7.1.0", "tsconfck": "^3.0.0", @@ -224,6 +224,7 @@ "remark-code-titles": "^0.1.2", "rollup": "^4.5.0", "sass": "^1.69.5", + "shikiji-core": "^0.9.18", "srcset-parse": "^1.1.0", "unified": "^11.0.4" }, diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 9db1eddd8ea0..b22c7d9bc04b 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -1567,6 +1567,42 @@ export interface AstroUserConfig { */ contentCollectionCache?: boolean; + /** + * @docs + * @name experimental.clientPrerender + * @type {boolean} + * @default `false` + * @version: 4.2.0 + * @description + * Enables pre-rendering your prefetched pages on the client in supported browsers. + * + * This feature uses the experimental [Speculation Rules Web API](https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API) and overrides the default `prefetch` behavior globally to prerender links on the client. + * You may wish to review the [possible risks when prerendering on the client](https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API#unsafe_prefetching) before enabling this feature. + * + * Enable client side prerendering in your `astro.config.mjs` along with any desired `prefetch` configuration options: + * + * ```js + * // astro.config.mjs + * { + * prefetch: { + * prefetchAll: true, + * defaultStrategy: 'viewport', + * }, + * experimental: { + * clientPrerender: true, + * }, + * } + * ``` + * + * Continue to use the `data-astro-prefetch` attribute on any `` link on your site to opt in to prefetching. + * Instead of appending a `` tag to the head of the document or fetching the page with JavaScript, a `