0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-01-06 22:10:10 -05:00

Merge branch 'feat/reroute' into feat/reroute-ssr

This commit is contained in:
Emanuele Stoppa 2024-04-22 15:30:58 +01:00
commit 4c6c61fec2
259 changed files with 1614 additions and 600 deletions

View file

@ -1,5 +0,0 @@
---
"astro": patch
---
Fixes `astro add` sometimes modifying `baseUrl` unintentionally

View file

@ -1,5 +0,0 @@
---
"@astrojs/mdx": patch
---
Removes internal MDX processor on `buildEnd` to free up memory

View file

@ -0,0 +1,5 @@
---
"astro": patch
---
Due to regression on mobile WebKit browsers, reverts a change made for JavaScript animations during view transitions.

28
.github/renovate.json5 vendored Normal file
View file

@ -0,0 +1,28 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
"schedule:weekly",
"group:allNonMajor",
":disablePeerDependencies",
],
"labels": ["dependencies"],
"rangeStrategy": "bump",
"ignoreDeps": [
// manually bumping deps
"@biomejs/biome",
"@types/node",
"sharp",
// manually bumping workflow actions
"actions/labeler",
// ignore "engines" update
"node",
"npm",
"pnpm",
// follow vite deps version
"postcss-load-config",
],
}

View file

@ -1,13 +0,0 @@
name: Nightly
on:
schedule:
# Run every Monday at 12:00 UTC
- cron: "0 12 * * 1"
workflow_dispatch:
jobs:
lockfile:
if: github.repository_owner == 'withastro'
uses: withastro/automation/.github/workflows/lockfile.yml@main
secrets: inherit

View file

@ -19,3 +19,12 @@ benchmark/results/
# Files
pnpm-lock.yaml
# Formatted by Biome
**/*.json
**/*.js
**/*.ts
**/*.tsx
**/*.jsx
**/*.mjs
**/*.cjs

View file

@ -1,16 +1,48 @@
{
"$schema": "https://biomejs.dev/schemas/1.5.3/schema.json",
"$schema": "https://biomejs.dev/schemas/1.6.4/schema.json",
"files": {
"include": ["test/**", "e2e/**", "packages/**"],
"ignore": ["vendor", "dist/**"]
"ignore": [
"vendor",
"**/dist/**",
"**/smoke/**",
"**/fixtures/**",
"**/vendor/**",
"**/.vercel/**"
],
"include": ["test/**", "e2e/**", "packages/**"]
},
"formatter": {
"indentStyle": "tab",
"indentWidth": 2,
"lineWidth": 100,
"ignore": [
"benchmark/projects/",
"benchmark/results/",
".changeset",
"pnpm-lock.yaml",
"package.json",
"*.astro"
]
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": false
"linter": { "enabled": false },
"javascript": {
"formatter": {
"trailingComma": "es5",
"quoteStyle": "single",
"semicolons": "always"
}
},
"formatter": {
"enabled": false
"json": {
"parser": {
"allowComments": true,
"allowTrailingCommas": true
},
"formatter": {
"indentStyle": "space",
"trailingCommas": "none"
}
}
}

View file

@ -11,6 +11,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^4.6.1"
"astro": "^4.6.3"
}
}

View file

@ -11,9 +11,9 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/mdx": "^2.3.0",
"@astrojs/mdx": "^2.3.1",
"@astrojs/rss": "^4.0.5",
"@astrojs/sitemap": "^3.1.2",
"astro": "^4.6.1"
"@astrojs/sitemap": "^3.1.4",
"astro": "^4.6.3"
}
}

View file

@ -30,7 +30,7 @@ import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
<li>Edit this page in <code>src/pages/index.astro</code></li>
<li>Edit the site header items in <code>src/components/Header.astro</code></li>
<li>Add your name to the footer in <code>src/components/Footer.astro</code></li>
<li>Check out the included blog posts in <code>src/pages/blog/</code></li>
<li>Check out the included blog posts in <code>src/content/blog/</code></li>
<li>Customize the blog post page layout in <code>src/layouts/BlogPost.astro</code></li>
</ul>
<p>

View file

@ -15,7 +15,7 @@
],
"scripts": {},
"devDependencies": {
"astro": "^4.6.1"
"astro": "^4.6.3"
},
"peerDependencies": {
"astro": "^4.0.0"

View file

@ -14,6 +14,6 @@
"@astrojs/alpinejs": "^0.4.0",
"@types/alpinejs": "^3.13.5",
"alpinejs": "^3.13.3",
"astro": "^4.6.1"
"astro": "^4.6.3"
}
}

View file

@ -13,7 +13,7 @@
"dependencies": {
"@astrojs/lit": "^4.0.1",
"@webcomponents/template-shadowroot": "^0.2.1",
"astro": "^4.6.1",
"astro": "^4.6.3",
"lit": "^3.1.2"
}
}

View file

@ -18,7 +18,7 @@
"@astrojs/vue": "^4.1.0",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"astro": "^4.6.1",
"astro": "^4.6.3",
"preact": "^10.19.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",

View file

@ -13,7 +13,7 @@
"dependencies": {
"@astrojs/preact": "^3.2.0",
"@preact/signals": "^1.2.1",
"astro": "^4.6.1",
"astro": "^4.6.3",
"preact": "^10.19.2"
}
}

View file

@ -14,7 +14,7 @@
"@astrojs/react": "^3.3.0",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"astro": "^4.6.1",
"astro": "^4.6.3",
"react": "^18.2.0",
"react-dom": "^18.2.0"
}

View file

@ -12,7 +12,7 @@
},
"dependencies": {
"@astrojs/solid-js": "^4.1.0",
"astro": "^4.6.1",
"astro": "^4.6.3",
"solid-js": "^1.8.5"
}
}

View file

@ -12,7 +12,7 @@
},
"dependencies": {
"@astrojs/svelte": "^5.4.0",
"astro": "^4.6.1",
"astro": "^4.6.3",
"svelte": "^4.2.5"
}
}

View file

@ -12,7 +12,7 @@
},
"dependencies": {
"@astrojs/vue": "^4.1.0",
"astro": "^4.6.1",
"astro": "^4.6.3",
"vue": "^3.3.8"
}
}

View file

@ -12,6 +12,6 @@
},
"dependencies": {
"@astrojs/node": "^8.2.5",
"astro": "^4.6.1"
"astro": "^4.6.3"
}
}

View file

@ -15,7 +15,7 @@
],
"scripts": {},
"devDependencies": {
"astro": "^4.6.1"
"astro": "^4.6.3"
},
"peerDependencies": {
"astro": "^4.0.0"

View file

@ -13,7 +13,7 @@
},
"dependencies": {
"@astrojs/node": "^8.2.5",
"astro": "^4.6.1",
"astro": "^4.6.3",
"html-minifier": "^4.0.0"
},
"devDependencies": {

View file

@ -11,6 +11,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^4.6.1"
"astro": "^4.6.3"
}
}

View file

@ -11,6 +11,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^4.6.1"
"astro": "^4.6.3"
}
}

View file

@ -11,6 +11,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^4.6.1"
"astro": "^4.6.3"
}
}

View file

@ -14,7 +14,7 @@
"dependencies": {
"@astrojs/node": "^8.2.5",
"@astrojs/svelte": "^5.4.0",
"astro": "^4.6.1",
"astro": "^4.6.3",
"svelte": "^4.2.5"
}
}

View file

@ -10,7 +10,7 @@
"astro": "astro"
},
"dependencies": {
"astro": "^4.6.1",
"astro": "^4.6.3",
"sass": "^1.69.5",
"sharp": "^0.32.6"
}

View file

@ -12,6 +12,6 @@
"devDependencies": {
"@astrojs/tailwind": "^5.1.0",
"@astrojs/node": "^8.2.5",
"astro": "^4.6.1"
"astro": "^4.6.3"
}
}

View file

@ -12,6 +12,6 @@
},
"dependencies": {
"@astrojs/markdoc": "^0.10.0",
"astro": "^4.6.1"
"astro": "^4.6.3"
}
}

View file

@ -12,7 +12,7 @@
},
"dependencies": {
"@astrojs/markdown-remark": "^5.1.0",
"astro": "^4.6.1",
"astro": "^4.6.3",
"hast-util-select": "^6.0.2",
"rehype-autolink-headings": "^7.1.0",
"rehype-slug": "^6.0.0",

View file

@ -11,6 +11,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^4.6.1"
"astro": "^4.6.3"
}
}

View file

@ -11,9 +11,9 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/mdx": "^2.3.0",
"@astrojs/mdx": "^2.3.1",
"@astrojs/preact": "^3.2.0",
"astro": "^4.6.1",
"astro": "^4.6.3",
"preact": "^10.19.2"
}
}

View file

@ -13,7 +13,7 @@
"dependencies": {
"@astrojs/preact": "^3.2.0",
"@nanostores/preact": "^0.5.0",
"astro": "^4.6.1",
"astro": "^4.6.3",
"nanostores": "^0.9.5",
"preact": "^10.19.2"
}

View file

@ -11,10 +11,10 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/mdx": "^2.3.0",
"@astrojs/mdx": "^2.3.1",
"@astrojs/tailwind": "^5.1.0",
"@types/canvas-confetti": "^1.6.3",
"astro": "^4.6.1",
"astro": "^4.6.3",
"autoprefixer": "^10.4.15",
"canvas-confetti": "^1.9.1",
"postcss": "^8.4.28",

View file

@ -12,7 +12,7 @@
"test": "vitest"
},
"dependencies": {
"astro": "^4.6.1",
"astro": "^4.6.3",
"vitest": "^1.3.1"
}
}

View file

@ -16,8 +16,8 @@
"dev": "turbo run dev --concurrency=40 --parallel --filter=astro --filter=create-astro --filter=\"@astrojs/*\" --filter=\"@benchmark/*\"",
"format": "pnpm run format:code && pnpm run format:imports",
"format:ci": "pnpm run format:code:ci && pnpm run format:imports:ci",
"format:code": "prettier -w \"**/*\" --ignore-unknown --cache",
"format:code:ci": "prettier -w \"**/*\" --ignore-unknown --cache --check",
"format:code": "biome format ./ --write && prettier -w \"**/*\" --ignore-unknown --cache",
"format:code:ci": "biome format ./ && prettier -w \"**/*\" --ignore-unknown --cache --check",
"format:imports": "biome check --apply .",
"format:imports:ci": "biome ci .",
"test": "turbo run test --concurrency=1 --filter=astro --filter=create-astro --filter=\"@astrojs/*\"",
@ -53,7 +53,7 @@
},
"devDependencies": {
"@astrojs/check": "^0.5.8",
"@biomejs/biome": "1.5.3",
"@biomejs/biome": "1.6.4",
"@changesets/changelog-github": "^0.4.8",
"@changesets/cli": "^2.26.2",
"@eslint/eslintrc": "^3.0.2",

View file

@ -19,17 +19,18 @@ import {
// note: I spent 30 minutes looking for a nice node-based snapshot tool
// ...and I gave up. Enjoy big strings!
// prettier-ignore
// biome-ignore format: keep in one line
const validXmlResult = `<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title><![CDATA[${title}]]></title><description><![CDATA[${description}]]></description><link>${site}/</link><item><title><![CDATA[${phpFeedItem.title}]]></title><link>${site}${phpFeedItem.link}/</link><guid isPermaLink="true">${site}${phpFeedItem.link}/</guid><description><![CDATA[${phpFeedItem.description}]]></description><pubDate>${new Date(phpFeedItem.pubDate).toUTCString()}</pubDate></item><item><title><![CDATA[${web1FeedItem.title}]]></title><link>${site}${web1FeedItem.link}/</link><guid isPermaLink="true">${site}${web1FeedItem.link}/</guid><description><![CDATA[${web1FeedItem.description}]]></description><pubDate>${new Date(web1FeedItem.pubDate).toUTCString()}</pubDate></item></channel></rss>`;
// prettier-ignore
// biome-ignore format: keep in one line
const validXmlWithContentResult = `<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title><![CDATA[${title}]]></title><description><![CDATA[${description}]]></description><link>${site}/</link><item><title><![CDATA[${phpFeedItemWithContent.title}]]></title><link>${site}${phpFeedItemWithContent.link}/</link><guid isPermaLink="true">${site}${phpFeedItemWithContent.link}/</guid><description><![CDATA[${phpFeedItemWithContent.description}]]></description><pubDate>${new Date(phpFeedItemWithContent.pubDate).toUTCString()}</pubDate><content:encoded><![CDATA[${phpFeedItemWithContent.content}]]></content:encoded></item><item><title><![CDATA[${web1FeedItemWithContent.title}]]></title><link>${site}${web1FeedItemWithContent.link}/</link><guid isPermaLink="true">${site}${web1FeedItemWithContent.link}/</guid><description><![CDATA[${web1FeedItemWithContent.description}]]></description><pubDate>${new Date(web1FeedItemWithContent.pubDate).toUTCString()}</pubDate><content:encoded><![CDATA[${web1FeedItemWithContent.content}]]></content:encoded></item></channel></rss>`;
// prettier-ignore
// biome-ignore format: keep in one line
const validXmlResultWithAllData = `<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title><![CDATA[${title}]]></title><description><![CDATA[${description}]]></description><link>${site}/</link><item><title><![CDATA[${phpFeedItem.title}]]></title><link>${site}${phpFeedItem.link}/</link><guid isPermaLink="true">${site}${phpFeedItem.link}/</guid><description><![CDATA[${phpFeedItem.description}]]></description><pubDate>${new Date(phpFeedItem.pubDate).toUTCString()}</pubDate></item><item><title><![CDATA[${web1FeedItemWithAllData.title}]]></title><link>${site}${web1FeedItemWithAllData.link}/</link><guid isPermaLink="true">${site}${web1FeedItemWithAllData.link}/</guid><description><![CDATA[${web1FeedItemWithAllData.description}]]></description><pubDate>${new Date(web1FeedItemWithAllData.pubDate).toUTCString()}</pubDate><category>${web1FeedItemWithAllData.categories[0]}</category><category>${web1FeedItemWithAllData.categories[1]}</category><author>${web1FeedItemWithAllData.author}</author><comments>${web1FeedItemWithAllData.commentsUrl}</comments><source url="${web1FeedItemWithAllData.source.url}">${web1FeedItemWithAllData.source.title}</source><enclosure url="${site}${web1FeedItemWithAllData.enclosure.url}" length="${web1FeedItemWithAllData.enclosure.length}" type="${web1FeedItemWithAllData.enclosure.type}"/></item></channel></rss>`;
// prettier-ignore
// biome-ignore format: keep in one line
const validXmlWithCustomDataResult = `<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title><![CDATA[${title}]]></title><description><![CDATA[${description}]]></description><link>${site}/</link><item><title><![CDATA[${phpFeedItemWithCustomData.title}]]></title><link>${site}${phpFeedItemWithCustomData.link}/</link><guid isPermaLink="true">${site}${phpFeedItemWithCustomData.link}/</guid><description><![CDATA[${phpFeedItemWithCustomData.description}]]></description><pubDate>${new Date(phpFeedItemWithCustomData.pubDate).toUTCString()}</pubDate>${phpFeedItemWithCustomData.customData}</item><item><title><![CDATA[${web1FeedItemWithContent.title}]]></title><link>${site}${web1FeedItemWithContent.link}/</link><guid isPermaLink="true">${site}${web1FeedItemWithContent.link}/</guid><description><![CDATA[${web1FeedItemWithContent.description}]]></description><pubDate>${new Date(web1FeedItemWithContent.pubDate).toUTCString()}</pubDate><content:encoded><![CDATA[${web1FeedItemWithContent.content}]]></content:encoded></item></channel></rss>`;
// prettier-ignore
// biome-ignore format: keep in one line
const validXmlWithStylesheet = `<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/feedstylesheet.css"?><rss version="2.0"><channel><title><![CDATA[${title}]]></title><description><![CDATA[${description}]]></description><link>${site}/</link></channel></rss>`;
// prettier-ignore
// biome-ignore format: keep in one line
const validXmlWithXSLStylesheet = `<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/feedstylesheet.xsl" type="text/xsl"?><rss version="2.0"><channel><title><![CDATA[${title}]]></title><description><![CDATA[${description}]]></description><link>${site}/</link></channel></rss>`;
function assertXmlDeepEqual(a, b) {

View file

@ -1,5 +1,37 @@
# astro
## 4.6.3
### Patch Changes
- [#10799](https://github.com/withastro/astro/pull/10799) [`dc74afca9f5eebc2d61331298d6ef187d92051e0`](https://github.com/withastro/astro/commit/dc74afca9f5eebc2d61331298d6ef187d92051e0) Thanks [@martrapp](https://github.com/martrapp)! - Fixes an issue with persisted non-text input fields that have the focus during view transition navigation.
- [#10773](https://github.com/withastro/astro/pull/10773) [`35e43ecdaae7adc4b9a0b974192a033568cfb3f0`](https://github.com/withastro/astro/commit/35e43ecdaae7adc4b9a0b974192a033568cfb3f0) Thanks [@lilnasy](https://github.com/lilnasy)! - Improves performance for frequent use of small components.
- [#10763](https://github.com/withastro/astro/pull/10763) [`63132771373ce1510be3e8814897accc0bf62ef8`](https://github.com/withastro/astro/commit/63132771373ce1510be3e8814897accc0bf62ef8) Thanks [@matthewp](https://github.com/matthewp)! - Invalidate CC cache manifest when lockfile or config changes
- [#10811](https://github.com/withastro/astro/pull/10811) [`77822a822b04b5113726f713df104e8667333c59`](https://github.com/withastro/astro/commit/77822a822b04b5113726f713df104e8667333c59) Thanks [@AvinashReddy3108](https://github.com/AvinashReddy3108)! - Update list of available integrations in the `astro add` CLI help.
## 4.6.2
### Patch Changes
- [#10732](https://github.com/withastro/astro/pull/10732) [`a92e263beb6e0166f1f13c97803d1861793e2a99`](https://github.com/withastro/astro/commit/a92e263beb6e0166f1f13c97803d1861793e2a99) Thanks [@rishi-raj-jain](https://github.com/rishi-raj-jain)! - Correctly sets `build.assets` directory during `vite` config setup
- [#10776](https://github.com/withastro/astro/pull/10776) [`1607face67051b16d4648555f1001b2a9308e377`](https://github.com/withastro/astro/commit/1607face67051b16d4648555f1001b2a9308e377) Thanks [@fshafiee](https://github.com/fshafiee)! - Fixes cookies type inference
- [#10796](https://github.com/withastro/astro/pull/10796) [`90669472df3a05b33f0de46fd2d039e3eba7f7dd`](https://github.com/withastro/astro/commit/90669472df3a05b33f0de46fd2d039e3eba7f7dd) Thanks [@bluwy](https://github.com/bluwy)! - Disables streaming when rendering site with `output: "static"`
- [#10782](https://github.com/withastro/astro/pull/10782) [`b0589d05538fcc77dd3c38198bf93f3548362cd8`](https://github.com/withastro/astro/commit/b0589d05538fcc77dd3c38198bf93f3548362cd8) Thanks [@nektro](https://github.com/nektro)! - Handles possible null value when calling `which-pm` during dynamic package installation
- [#10774](https://github.com/withastro/astro/pull/10774) [`308b5d8c122f44e7724bb2f3ad3aa5c43a83e584`](https://github.com/withastro/astro/commit/308b5d8c122f44e7724bb2f3ad3aa5c43a83e584) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Fixes `astro add` sometimes modifying `baseUrl` unintentionally
- [#10783](https://github.com/withastro/astro/pull/10783) [`4dbd545304d1a8af903c8c97f237eb55c988c40b`](https://github.com/withastro/astro/commit/4dbd545304d1a8af903c8c97f237eb55c988c40b) Thanks [@jurajkapsz](https://github.com/jurajkapsz)! - Fixes Picture component specialFormatsFallback fallback check
- [#10775](https://github.com/withastro/astro/pull/10775) [`06843121450899ecf0390ca4efaff6c9a6fe0f75`](https://github.com/withastro/astro/commit/06843121450899ecf0390ca4efaff6c9a6fe0f75) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Fixes assets endpoint in serverless returning 404 in certain situations where the website might be under a protected route
- [#10787](https://github.com/withastro/astro/pull/10787) [`699f4559a279b374bddb3e5e48c72afe2709e8e7`](https://github.com/withastro/astro/commit/699f4559a279b374bddb3e5e48c72afe2709e8e7) Thanks [@martrapp](https://github.com/martrapp)! - Fixes a timing issue in the view transition simulation.
## 4.6.1
### Patch Changes

View file

@ -9,8 +9,6 @@
* Adapted from Reacts TypeScript definition from DefinitelyTyped.
* @see https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts
*/
// BUG! Prettier 3.0 removes `declare`: https://github.com/prettier/prettier/issues/15207
// prettier-ignore
declare namespace astroHTML.JSX {
export type Child = Node | Node[] | string | number | boolean | null | undefined | unknown;
export type Children = Child | Child[];

View file

@ -1,5 +1,5 @@
---
import { getImage, type LocalImageProps, type RemoteImageProps } from 'astro:assets';
import { type LocalImageProps, type RemoteImageProps, getImage } from 'astro:assets';
import { AstroError, AstroErrorData } from '../dist/core/errors/index.js';
import type { HTMLAttributes } from '../types';

View file

@ -1,5 +1,5 @@
---
import { getImage, type LocalImageProps, type RemoteImageProps } from 'astro:assets';
import { type LocalImageProps, type RemoteImageProps, getImage } from 'astro:assets';
import type { GetImageResult, ImageOutputFormat } from '../dist/@types/astro';
import { isESMImportedImage, resolveSrc } from '../dist/assets/utils/imageKind';
import { AstroError, AstroErrorData } from '../dist/core/errors/index.js';
@ -45,7 +45,7 @@ let resultFallbackFormat = fallbackFormat ?? defaultFallbackFormat;
if (
!fallbackFormat &&
isESMImportedImage(originalSrc) &&
originalSrc.format in specialFormatsFallback
(specialFormatsFallback as ReadonlyArray<string>).includes(originalSrc.format)
) {
resultFallbackFormat = originalSrc.format;
}

View file

@ -1,7 +1,7 @@
---
import Hero from '../components/Hero.astro';
import LinkedLib from '@e2e/astro-linked-lib'
import HoistedScript from '@e2e/astro-linked-lib/HoistedScript'
import Hero from '../components/Hero.astro';
---
<html>

View file

@ -1,9 +1,9 @@
---
import * as react from '../components/react/ReactCounter.jsx';
import { PreactCounter } from '../components/preact/PreactCounter.jsx';
import * as react from '../components/react/ReactCounter.jsx';
import SolidCounter from '../components/solid/SolidCounter.jsx';
import VueCounter from '../components/vue/VueCounter.vue';
import SvelteCounter from '../components/svelte/SvelteCounter.svelte';
import VueCounter from '../components/vue/VueCounter.vue';
// Full Astro Component Syntax:
// https://docs.astro.build/basics/astro-components/

View file

@ -1,6 +1,6 @@
---
import One from './One.jsx';
import Deeper from './Deeper.astro';
import One from './One.jsx';
---

View file

@ -1,7 +1,7 @@
---
import ClientOnlyComponent from '../components/ClientOnlyComponent.js';
import MyCounter from '../components/Counter.js';
import NonDeferredCounter from '../components/NonDeferredCounter.js';
import ClientOnlyComponent from '../components/ClientOnlyComponent.js';
const someProps = {
count: 10,

View file

@ -13,7 +13,7 @@
},
"dependencies": {
"@webcomponents/template-shadowroot": "^0.2.1",
"lit": "^2.8.0",
"lit": "^3.1.0",
"preact": "^10.19.2",
"react": "^18.1.0",
"react-dom": "^18.1.0",

View file

@ -1,13 +1,13 @@
---
// Style Imports
import '../styles/global.css';
// Component Imports
import { A, B as Renamed } from '../components';
import * as react from '../components/react/ReactCounter.jsx';
import { PreactCounter } from '../components/preact/PreactCounter.tsx';
import * as react from '../components/react/ReactCounter.jsx';
import SolidCounter from '../components/solid/SolidCounter.tsx';
import VueCounter from '../components/vue/VueCounter.vue';
import SvelteCounter from '../components/svelte/SvelteCounter.svelte';
import VueCounter from '../components/vue/VueCounter.vue';
// Style Imports
import '../styles/global.css';
// Full Astro Component Syntax:
// https://docs.astro.build/basics/astro-components/

View file

@ -1,9 +1,9 @@
---
import ReactCounter from '../components/react/ReactCounter.jsx';
import { PreactCounter } from '../components/preact/PreactCounter.tsx';
import ReactCounter from '../components/react/ReactCounter.jsx';
import SolidCounter from '../components/solid/SolidCounter.tsx';
import VueCounter from '../components/vue/VueCounter.vue';
import SvelteCounter from '../components/svelte/SvelteCounter.svelte';
import VueCounter from '../components/vue/VueCounter.vue';
// Full Astro Component Syntax:
// https://docs.astro.build/basics/astro-components/

View file

@ -1,9 +1,9 @@
---
import ReactCounter from '../components/react/ReactCounter.jsx';
import { PreactCounter } from '../components/preact/PreactCounter.tsx';
import ReactCounter from '../components/react/ReactCounter.jsx';
import SolidCounter from '../components/solid/SolidCounter.tsx';
import VueCounter from '../components/vue/VueCounter.vue';
import SvelteCounter from '../components/svelte/SvelteCounter.svelte';
import VueCounter from '../components/vue/VueCounter.vue';
// Full Astro Component Syntax:
// https://docs.astro.build/basics/astro-components/

View file

@ -1,9 +1,9 @@
---
import { Counter as ReactCounter } from '../components/react/ReactCounter.jsx';
import { PreactCounter } from '../components/preact/PreactCounter.tsx';
import { Counter as ReactCounter } from '../components/react/ReactCounter.jsx';
import SolidCounter from '../components/solid/SolidCounter.tsx';
import VueCounter from '../components/vue/VueCounter.vue';
import SvelteCounter from '../components/svelte/SvelteCounter.svelte';
import VueCounter from '../components/vue/VueCounter.vue';
// Full Astro Component Syntax:
// https://docs.astro.build/basics/astro-components/

View file

@ -1,9 +1,9 @@
---
import { Counter as ReactCounter } from '../components/react/ReactCounter.jsx';
import { PreactCounter } from '../components/preact/PreactCounter.tsx';
import { Counter as ReactCounter } from '../components/react/ReactCounter.jsx';
import SolidCounter from '../components/solid/SolidCounter.tsx';
import VueCounter from '../components/vue/VueCounter.vue';
import SvelteCounter from '../components/svelte/SvelteCounter.svelte';
import VueCounter from '../components/vue/VueCounter.vue';
// Full Astro Component Syntax:
// https://docs.astro.build/basics/astro-components/

View file

@ -1,9 +1,9 @@
---
import { Counter as ReactCounter } from '../components/react/ReactCounter.jsx';
import { PreactCounter } from '../components/preact/PreactCounter.tsx';
import { Counter as ReactCounter } from '../components/react/ReactCounter.jsx';
import SolidCounter from '../components/solid/SolidCounter.tsx';
import VueCounter from '../components/vue/VueCounter.vue';
import SvelteCounter from '../components/svelte/SvelteCounter.svelte';
import VueCounter from '../components/vue/VueCounter.vue';
// Full Astro Component Syntax:
// https://docs.astro.build/basics/astro-components/

View file

@ -1,9 +1,9 @@
---
import ReactCounter from '../components/react/ReactCounter.jsx';
import PreactCounter from '../components/preact/PreactCounter.tsx';
import ReactCounter from '../components/react/ReactCounter.jsx';
import SolidCounter from '../components/solid/SolidCounter.tsx';
import VueCounter from '../components/vue/VueCounter.vue';
import SvelteCounter from '../components/svelte/SvelteCounter.svelte';
import VueCounter from '../components/vue/VueCounter.vue';
---
<html lang="en">

View file

@ -1,6 +1,6 @@
---
import type { BigNestedObject } from '../types';
import Component from '../components/React';
import type { BigNestedObject } from '../types';
const object: BigNestedObject = {
nested: {

View file

@ -1,6 +1,6 @@
---
import WrapperB from "../components/WrapperB.jsx";
import WrapperA from "../components/WrapperA.jsx";
import WrapperB from "../components/WrapperB.jsx";
---
<html>

View file

@ -1,8 +1,8 @@
---
// Component Imports
import Layout from '../components/Layout.astro';
import Button from '../components/Button.astro';
import Complex from '../components/Complex.astro';
// Component Imports
import Layout from '../components/Layout.astro';
---
<Layout>

View file

@ -1,5 +1,5 @@
import React from 'react';
import { navigate } from "astro:transitions/client";
import React from 'react';
export default function ClickToNavigate({ to, id }) {
return <button id={id} onClick={() => navigate(to)}>Navigate to `{to}`</button>;
}

View file

@ -1,6 +1,6 @@
---
import ClickToNavigate from "../components/ClickToNavigate.jsx"
import { ViewTransitions } from "astro:transitions";
import ClickToNavigate from "../components/ClickToNavigate.jsx"
---
<html>
<head>

View file

@ -1,8 +1,8 @@
---
import Layout from '../components/Layout.astro';
import Island from '../components/Island';
import VueCounter from '../components/VueCounter.vue';
import Layout from '../components/Layout.astro';
import SvelteCounter from '../components/SvelteCounter.svelte';
import VueCounter from '../components/VueCounter.vue';
---
<Layout>
<p id="page-four">Page 4</p>

View file

@ -1,6 +1,6 @@
---
import Layout from '../components/Layout.astro';
import Island from '../components/Island';
import Layout from '../components/Layout.astro';
---
<Layout>
<a id="click-two" href="/client-only-two">go to page 2</a>

View file

@ -1,8 +1,8 @@
---
import Layout from '../components/Layout.astro';
import Island from '../components/Island';
import VueCounter from '../components/VueCounter.vue';
import Layout from '../components/Layout.astro';
import SvelteCounter from '../components/SvelteCounter.svelte';
import VueCounter from '../components/VueCounter.vue';
---
<Layout>
<a id="click-four" href="/client-only-four">go to page 4</a>

View file

@ -1,6 +1,6 @@
---
import Layout from '../components/Layout.astro';
import Island from '../components/Island';
import Layout from '../components/Layout.astro';
---
<Layout>
<p id="page-two">Page 2</p>

View file

@ -1,6 +1,6 @@
---
import Layout from '../components/Layout.astro';
import InlineScript from '../components/InlineScript.astro';
import Layout from '../components/Layout.astro';
---
<Layout>
<InlineScript />

View file

@ -1,6 +1,6 @@
---
import Layout from '../components/Layout.astro';
import InlineScript from '../components/InlineScript.astro';
import Layout from '../components/Layout.astro';
---
<Layout>
<InlineScript />

View file

@ -1,6 +1,6 @@
---
import Layout from '../components/Layout.astro';
import Island from '../components/Island.jsx';
import Layout from '../components/Layout.astro';
export const prerender = false;
const persistProps = Astro.url.searchParams.has('persist');

View file

@ -1,6 +1,6 @@
---
import Layout from '../components/Layout.astro';
import Island from '../components/Island.jsx';
import Layout from '../components/Layout.astro';
---
<Layout>
<p id="island-two">Page 2</p>

View file

@ -0,0 +1,26 @@
---
import Layout from '../components/Layout.astro';
---
<Layout>
<p id="one">Persist 1</p>
<form transition:persist="form" action="/persist-2">
<input type="text" name="name" />
<input type="checkbox" />
</form>
<div id="test">test content</div>
</Layout>
<script>
const input = document.querySelector<HTMLInputElement>("form input")!;
input.focus();
input.value = "some cool text";
input.selectionStart=5;
input.selectionEnd=9;
document.addEventListener('astro:after-swap', () => {
const textInput = document.querySelector<HTMLInputElement>("form input:first-of-type")!;
console.log(textInput === document.activeElement, textInput.value, textInput.selectionStart, textInput.selectionEnd);
const checkBox = document.querySelector<HTMLInputElement>("form input:nth-of-type(2)")!;
checkBox.checked = true;
checkBox.focus();
}, {once:true});
</script>

View file

@ -0,0 +1,17 @@
---
import Layout from '../components/Layout.astro';
---
<Layout>
<p id="two">Persist 2</p>
<a id="click-persist-one" href="/persist-1">go to 3</a>
<div transition:persist="form"/>
<div id="test">test content</div>
</Layout>
<script>
document.addEventListener('astro:after-swap', () => {
const checkBox = document.querySelector<HTMLInputElement>("form input:nth-of-type(2)")!;
console.log(checkBox === document.activeElement, checkBox.checked);
}, {once:true});
</script>

View file

@ -1,8 +1,8 @@
---
import Counter from '../components/Counter.vue';
import VueComponent from '../components/VueComponent.vue';
import AsyncTest from '../components/Test.vue'
import State from '../components/State.vue'
import AsyncTest from '../components/Test.vue'
import VueComponent from '../components/VueComponent.vue';
const someProps = {
count: 0,

View file

@ -1,4 +1,4 @@
import { expect, test as base } from '@playwright/test';
import { test as base, expect } from '@playwright/test';
import { loadFixture, waitForHydrate } from './test-utils.js';
const test = base.extend({

View file

@ -1403,3 +1403,20 @@ test.describe('View Transitions', () => {
).toEqual(0);
});
});
test('transition:persist persists selection', async ({ page, astro }) => {
let text = '';
page.on('console', (msg) => {
text = msg.text();
});
await page.goto(astro.resolveUrl('/persist-1'));
await expect(page.locator('#one'), 'should have content').toHaveText('Persist 1');
// go to page 2
await page.press('input[name="name"]', 'Enter');
await expect(page.locator('#two'), 'should have content').toHaveText('Persist 2');
expect(text).toBe('true some cool text 5 9');
await page.goBack();
await expect(page.locator('#one'), 'should have content').toHaveText('Persist 1');
expect(text).toBe('true true');
});

View file

@ -1,6 +1,6 @@
{
"name": "astro",
"version": "4.6.1",
"version": "4.6.3",
"description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
"type": "module",
"author": "withastro",
@ -125,6 +125,7 @@
"@babel/traverse": "^7.23.3",
"@babel/types": "^7.23.3",
"@types/babel__core": "^7.20.4",
"@types/cookie": "^0.5.4",
"acorn": "^8.11.2",
"aria-query": "^5.3.0",
"axobject-query": "^4.0.0",
@ -137,7 +138,7 @@
"cssesc": "^3.0.0",
"debug": "^4.3.4",
"deterministic-object-hash": "^2.0.1",
"devalue": "^4.3.2",
"devalue": "^5.0.0",
"diff": "^5.1.0",
"dlv": "^1.1.3",
"dset": "^3.1.3",
@ -154,8 +155,8 @@
"js-yaml": "^4.1.0",
"kleur": "^4.1.4",
"magic-string": "^0.30.3",
"mime": "^3.0.0",
"ora": "^7.0.1",
"mrmime": "^2.0.0",
"ora": "^8.0.1",
"p-limit": "^5.0.0",
"p-queue": "^8.0.1",
"path-to-regexp": "^6.2.1",
@ -186,10 +187,8 @@
"@types/aria-query": "^5.0.4",
"@types/babel__generator": "^7.6.7",
"@types/babel__traverse": "^7.20.4",
"@types/chai": "^4.3.10",
"@types/common-ancestor-path": "^1.0.2",
"@types/connect": "^3.4.38",
"@types/cookie": "^0.5.4",
"@types/cssesc": "^3.0.2",
"@types/debug": "^4.1.12",
"@types/diff": "^5.0.8",
@ -199,7 +198,6 @@
"@types/html-escaper": "^3.0.2",
"@types/http-cache-semantics": "^4.0.4",
"@types/js-yaml": "^4.0.9",
"@types/mime": "^3.0.4",
"@types/mocha": "^10.0.4",
"@types/probe-image-size": "^7.2.3",
"@types/prompts": "^2.4.8",

View file

@ -1,6 +1,6 @@
---
import { Heading, Aside, LikeButton, HydratedLikeButton } from '@performance/utils';
import type { CollectionEntry } from 'astro:content';
import { Aside, Heading, HydratedLikeButton, LikeButton } from '@performance/utils';
type Props = {
entry: CollectionEntry<'generated'>;

View file

@ -1,6 +1,6 @@
---
import Title from './Title.astro';
import type { CollectionEntry } from 'astro:content';
import Title from './Title.astro';
type Props = {
entry: CollectionEntry<'generated'>;

View file

@ -12,9 +12,7 @@
"license": "ISC",
"devDependencies": {
"@types/yargs-parser": "^21.0.3",
"cross-env": "^7.0.3",
"kleur": "^4.1.5",
"npm-run-all": "^4.1.5",
"yargs-parser": "^21.1.1"
}
}

View file

@ -1500,9 +1500,8 @@ export interface AstroUserConfig {
* Controls the routing strategy to determine your site URLs. Set this based on your folder/URL path configuration for your default language.
*
*/
// prettier-ignore
routing?:
{
routing?:
| {
/**
* @docs
* @name i18n.routing.prefixDefaultLocale
@ -1519,18 +1518,18 @@ export interface AstroUserConfig {
* When `true`, all URLs will display a language prefix.
* URLs will be of the form `example.com/[locale]/content/` for every route, including the default language.
* Localized folders are used for every language, including the default.
*
* ```js
* export default defineConfig({
* i18n: {
* defaultLocale: "en",
* locales: ["en", "fr", "pt-br", "es"],
* routing: {
* prefixDefaultLocale: true,
* }
* }
* })
* ```
*
* ```js
* export default defineConfig({
* i18n: {
* defaultLocale: "en",
* locales: ["en", "fr", "pt-br", "es"],
* routing: {
* prefixDefaultLocale: true,
* }
* }
* })
* ```
*/
prefixDefaultLocale?: boolean;
@ -1573,32 +1572,32 @@ export interface AstroUserConfig {
* - `"pathname": The strategy is applied to the pathname of the URLs
*/
strategy?: 'pathname';
} |
/**
*
* @docs
* @name i18n.routing.manual
* @kind h4
* @type {string}
* @version 4.6.0
* @description
* When this option is enabled, Astro will **disable** its i18n middleware so that you can implement your own custom logic. No other `routing` options (e.g. `prefixDefaultLocale`) may be configured with `routing: "manual"`.
*
* You will be responsible for writing your own routing logic, or executing Astro's i18n middleware manually alongside your own.
*
* ```js
* export default defineConfig({
* i18n: {
* defaultLocale: "en",
* locales: ["en", "fr", "pt-br", "es"],
* routing: {
* prefixDefaultLocale: true,
* }
* }
* })
* ```
*/
'manual';
}
/**
*
* @docs
* @name i18n.routing.manual
* @kind h4
* @type {string}
* @version 4.6.0
* @description
* When this option is enabled, Astro will **disable** its i18n middleware so that you can implement your own custom logic. No other `routing` options (e.g. `prefixDefaultLocale`) may be configured with `routing: "manual"`.
*
* You will be responsible for writing your own routing logic, or executing Astro's i18n middleware manually alongside your own.
*
* ```js
* export default defineConfig({
* i18n: {
* defaultLocale: "en",
* locales: ["en", "fr", "pt-br", "es"],
* routing: {
* prefixDefaultLocale: true,
* }
* }
* })
* ```
*/
| 'manual';
/**
* @name i18n.domains
@ -2801,6 +2800,7 @@ export interface AstroIntegration {
dir: URL;
routes: RouteData[];
logger: AstroIntegrationLogger;
cacheManifest: boolean;
}) => void | Promise<void>;
};
}

View file

@ -1,15 +1,18 @@
// @ts-expect-error
import { imageConfig } from 'astro:assets';
import { isRemotePath } from '@astrojs/internal-helpers/path';
import mime from 'mime/lite.js';
import * as mime from 'mrmime';
import type { APIRoute } from '../../@types/astro.js';
import { getConfiguredImageService } from '../internal.js';
import { etag } from '../utils/etag.js';
import { isRemoteAllowed } from '../utils/remotePattern.js';
// @ts-expect-error
import { imageConfig } from 'astro:assets';
async function loadRemoteImage(src: URL) {
async function loadRemoteImage(src: URL, headers: Headers) {
try {
const res = await fetch(src);
const res = await fetch(src, {
// Forward all headers from the original request
headers,
});
if (!res.ok) {
return undefined;
@ -41,15 +44,14 @@ export const GET: APIRoute = async ({ request }) => {
let inputBuffer: ArrayBuffer | undefined = undefined;
const sourceUrl = isRemotePath(transform.src)
? new URL(transform.src)
: new URL(transform.src, url.origin);
const isRemoteImage = isRemotePath(transform.src);
const sourceUrl = isRemoteImage ? new URL(transform.src) : new URL(transform.src, url.origin);
if (isRemotePath(transform.src) && isRemoteAllowed(transform.src, imageConfig) === false) {
if (isRemoteImage && isRemoteAllowed(transform.src, imageConfig) === false) {
return new Response('Forbidden', { status: 403 });
}
inputBuffer = await loadRemoteImage(sourceUrl);
inputBuffer = await loadRemoteImage(sourceUrl, isRemoteImage ? new Headers() : request.headers);
if (!inputBuffer) {
return new Response('Not Found', { status: 404 });
@ -64,7 +66,7 @@ export const GET: APIRoute = async ({ request }) => {
return new Response(data, {
status: 200,
headers: {
'Content-Type': mime.getType(format) ?? `image/${format}`,
'Content-Type': mime.lookup(format) ?? `image/${format}`,
'Cache-Control': 'public, max-age=31536000',
ETag: etag(data.toString()),
Date: new Date().toUTCString(),

View file

@ -2,15 +2,15 @@
import os from 'node:os';
import { isAbsolute } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
// @ts-expect-error
import { assetsDir, imageConfig, outDir } from 'astro:assets';
import { isRemotePath, removeQueryString } from '@astrojs/internal-helpers/path';
import { readFile } from 'fs/promises';
import mime from 'mime/lite.js';
import * as mime from 'mrmime';
import type { APIRoute } from '../../@types/astro.js';
import { getConfiguredImageService } from '../internal.js';
import { etag } from '../utils/etag.js';
import { isRemoteAllowed } from '../utils/remotePattern.js';
// @ts-expect-error
import { assetsDir, imageConfig, outDir } from 'astro:assets';
function replaceFileSystemReferences(src: string) {
return os.platform().includes('win32') ? src.replace(/^\/@fs\//, '') : src.replace(/^\/@fs/, '');
@ -110,7 +110,7 @@ export const GET: APIRoute = async ({ request }) => {
return new Response(data, {
status: 200,
headers: {
'Content-Type': mime.getType(format) ?? `image/${format}`,
'Content-Type': mime.lookup(format) ?? `image/${format}`,
'Cache-Control': 'public, max-age=31536000',
ETag: etag(data.toString()),
Date: new Date().toUTCString(),

View file

@ -147,7 +147,9 @@ export default function assets({
: settings.config.outDir
)
)});
export const assetsDir = /* #__PURE__ */ new URL(${JSON.stringify(settings.config.build.assets)}, outDir);
export const assetsDir = /* #__PURE__ */ new URL(${JSON.stringify(
settings.config.build.assets
)}, outDir);
export const getImage = async (options) => await getImageInternal(options, imageConfig);
`;
}

View file

@ -148,12 +148,12 @@ export async function add(names: string[], { flags }: AddOptions) {
['node', 'astro add node'],
],
Others: [
['db', 'astro add db'],
['tailwind', 'astro add tailwind'],
['image', 'astro add image'],
['mdx', 'astro add mdx'],
['markdoc', 'astro add markdoc'],
['partytown', 'astro add partytown'],
['sitemap', 'astro add sitemap'],
['prefetch', 'astro add prefetch'],
],
},
description: `For more integrations, check out: ${cyan('https://astro.build/integrations')}`,

View file

@ -39,7 +39,9 @@ export async function getPackage<T>(
return packageImport as T;
} catch (e) {
if (options.optional) return undefined;
let message = `To continue, Astro requires the following dependency to be installed: ${bold(packageName)}.`;
let message = `To continue, Astro requires the following dependency to be installed: ${bold(
packageName
)}.`;
if (ci.isCI) {
message += ` Packages cannot be installed automatically in CI environments.`;
@ -101,7 +103,7 @@ async function installPackage(
logger: Logger
): Promise<boolean> {
const cwd = options.cwd ?? process.cwd();
const packageManager = (await whichPm(cwd)).name ?? 'npm';
const packageManager = (await whichPm(cwd))?.name ?? 'npm';
const installCommand = getInstallCommand(packageNames, packageManager);
if (!installCommand) {

View file

@ -1,16 +1,11 @@
import type {
ComponentInstance,
ManifestData,
ReroutePayload,
RouteData,
SSRElement,
SSRResult,
} from '../../@types/astro.js';
import { Pipeline } from '../base-pipeline.js';
import { createModuleScriptElement, createStylesheetElementSet } from '../render/ssr-element.js';
import type { SinglePageBuiltModule } from '../build/types.js';
import { DEFAULT_404_COMPONENT } from '../constants.js';
import { RedirectSinglePageBuiltModule } from '../redirects/index.js';
export class AppPipeline extends Pipeline {
#manifestData: ManifestData | undefined;

View file

@ -0,0 +1 @@
export const CHUNKS_PATH = 'chunks/';

View file

@ -218,6 +218,7 @@ class AstroBuilder {
.flat()
.map((pageData) => pageData.route),
logging: this.logger,
cacheManifest: internals.cacheManifestUsed,
});
if (this.logger.level && levels[this.logger.level()] <= levels['info']) {

View file

@ -89,6 +89,7 @@ export interface BuildInternals {
discoveredScripts: Set<string>;
cachedClientEntries: string[];
cacheManifestUsed: boolean;
propagatedStylesMap: Map<string, Set<StylesheetAsset>>;
propagatedScriptsMap: Map<string, Set<string>>;
@ -140,6 +141,7 @@ export function createBuildInternals(): BuildInternals {
componentMetadata: new Map(),
ssrSplitEntryChunks: new Map(),
entryPoints: new Map(),
cacheManifestUsed: false,
};
}

View file

@ -81,7 +81,8 @@ export class BuildPipeline extends Pipeline {
return assetLink;
}
const serverLike = isServerLikeOutput(config);
const streaming = true;
// We can skip streaming in SSG for performance as writing as strings are faster
const streaming = serverLike;
super(
options.logger,
manifest,

View file

@ -1,4 +1,4 @@
import type { Plugin as VitePlugin, Rollup } from 'vite';
import type { Rollup, Plugin as VitePlugin } from 'vite';
import type { BuildInternals } from './internal.js';
import type { StaticBuildOptions, ViteBuildReturn } from './types.js';

View file

@ -10,8 +10,16 @@ import {
generateLookupMap,
} from '../../../content/vite-plugin-content-virtual-mod.js';
import { isServerLikeOutput } from '../../../prerender/utils.js';
import { joinPaths, removeFileExtension, removeLeadingForwardSlash } from '../../path.js';
import { configPaths } from '../../config/index.js';
import { emptyDir } from '../../fs/index.js';
import {
appendForwardSlash,
joinPaths,
removeFileExtension,
removeLeadingForwardSlash,
} from '../../path.js';
import { addRollupInput } from '../add-rollup-input.js';
import { CHUNKS_PATH } from '../consts.js';
import { type BuildInternals } from '../internal.js';
import type { AstroBuildPlugin } from '../plugin.js';
import { copyFiles } from '../static-build.js';
@ -23,7 +31,7 @@ const CONTENT_CACHE_DIR = './content/';
const CONTENT_MANIFEST_FILE = './manifest.json';
// IMPORTANT: Update this version when making significant changes to the manifest format.
// Only manifests generated with the same version number can be compared.
const CONTENT_MANIFEST_VERSION = 0;
const CONTENT_MANIFEST_VERSION = 1;
interface ContentManifestKey {
collection: string;
@ -39,40 +47,51 @@ interface ContentManifest {
// Tracks components that should be passed to the client build
// When the cache is restored, these might no longer be referenced
clientEntries: string[];
// Hash of the lockfiles, pnpm-lock.yaml, package-lock.json, etc.
// Kept so that installing new packages results in a full rebuild.
lockfiles: string;
// Hash of the Astro config. Changing options results in invalidating the cache.
configs: string;
}
const virtualEmptyModuleId = `virtual:empty-content`;
const resolvedVirtualEmptyModuleId = `\0${virtualEmptyModuleId}`;
const NO_MANIFEST_VERSION = -1 as const;
function createContentManifest(): ContentManifest {
return { version: -1, entries: [], serverEntries: [], clientEntries: [] };
return {
version: NO_MANIFEST_VERSION,
entries: [],
serverEntries: [],
clientEntries: [],
lockfiles: '',
configs: '',
};
}
function vitePluginContent(
opts: StaticBuildOptions,
lookupMap: ContentLookupMap,
internals: BuildInternals
internals: BuildInternals,
cachedBuildOutput: Array<{ cached: URL; dist: URL }>
): VitePlugin {
const { config } = opts.settings;
const { cacheDir } = config;
const distRoot = config.outDir;
const distContentRoot = new URL('./content/', distRoot);
const cachedChunks = new URL('./chunks/', opts.settings.config.cacheDir);
const distChunks = new URL('./chunks/', opts.settings.config.outDir);
const contentCacheDir = new URL(CONTENT_CACHE_DIR, cacheDir);
const contentManifestFile = new URL(CONTENT_MANIFEST_FILE, contentCacheDir);
const cache = contentCacheDir;
const cacheTmp = new URL('./.tmp/', cache);
const cacheTmp = new URL('./.tmp/', contentCacheDir);
let oldManifest = createContentManifest();
let newManifest = createContentManifest();
let entries: ContentEntries;
let injectedEmptyFile = false;
let currentManifestState: ReturnType<typeof manifestState> = 'valid';
if (fsMod.existsSync(contentManifestFile)) {
try {
const data = fsMod.readFileSync(contentManifestFile, { encoding: 'utf8' });
oldManifest = JSON.parse(data);
internals.cachedClientEntries = oldManifest.clientEntries;
} catch {}
}
@ -84,6 +103,32 @@ function vitePluginContent(
newManifest = await generateContentManifest(opts, lookupMap);
entries = getEntriesFromManifests(oldManifest, newManifest);
// If the manifest is valid, use the cached client entries as nothing has changed
currentManifestState = manifestState(oldManifest, newManifest);
if (currentManifestState === 'valid') {
internals.cachedClientEntries = oldManifest.clientEntries;
} else {
let logReason = '';
switch (currentManifestState) {
case 'config-mismatch':
logReason = 'Astro config has changed';
break;
case 'lockfile-mismatch':
logReason = 'Lockfiles have changed';
break;
case 'no-entries':
logReason = 'No content collections entries cached';
break;
case 'version-mismatch':
logReason = 'The cache manifest version has changed';
break;
case 'no-manifest':
logReason = 'No content manifest was found in the cache';
break;
}
opts.logger.info('build', `Cache invalid, rebuilding from source. Reason: ${logReason}.`);
}
// Of the cached entries, these ones need to be rebuilt
for (const { type, entry } of entries.buildFromSource) {
const fileURL = encodeURI(joinPaths(opts.settings.config.root.toString(), entry));
@ -96,10 +141,18 @@ function vitePluginContent(
}
newOptions = addRollupInput(newOptions, inputs);
}
// Restores cached chunks from the previous build
if (fsMod.existsSync(cachedChunks)) {
await copyFiles(cachedChunks, distChunks, true);
// Restores cached chunks and assets from the previous build
// If the manifest state is not valid then it needs to rebuild everything
// so don't do that in this case.
if (currentManifestState === 'valid') {
for (const { cached, dist } of cachedBuildOutput) {
if (fsMod.existsSync(cached)) {
await copyFiles(cached, dist, true);
}
}
}
// If nothing needs to be rebuilt, we inject a fake entrypoint to appease Rollup
if (entries.buildFromSource.length === 0) {
newOptions = addRollupInput(newOptions, [virtualEmptyModuleId]);
@ -199,16 +252,20 @@ function vitePluginContent(
]);
newManifest.serverEntries = Array.from(serverComponents);
newManifest.clientEntries = Array.from(clientComponents);
const cacheExists = fsMod.existsSync(contentCacheDir);
// If the manifest is invalid, empty the cache so that we can create a new one.
if (cacheExists && currentManifestState !== 'valid') {
emptyDir(contentCacheDir);
}
await fsMod.promises.mkdir(contentCacheDir, { recursive: true });
await fsMod.promises.writeFile(contentManifestFile, JSON.stringify(newManifest), {
encoding: 'utf8',
});
const cacheExists = fsMod.existsSync(cache);
fsMod.mkdirSync(cache, { recursive: true });
await fsMod.promises.mkdir(cacheTmp, { recursive: true });
await copyFiles(distContentRoot, cacheTmp, true);
if (cacheExists) {
if (cacheExists && currentManifestState === 'valid') {
await copyFiles(contentCacheDir, distContentRoot, false);
}
await copyFiles(cacheTmp, contentCacheDir);
@ -242,12 +299,12 @@ function getEntriesFromManifests(
oldManifest: ContentManifest,
newManifest: ContentManifest
): ContentEntries {
const { version: oldVersion, entries: oldEntries } = oldManifest;
const { version: newVersion, entries: newEntries } = newManifest;
const { entries: oldEntries } = oldManifest;
const { entries: newEntries } = newManifest;
let entries: ContentEntries = { restoreFromCache: [], buildFromSource: [] };
const newEntryMap = new Map<ContentManifestKey, string>(newEntries);
if (oldVersion !== newVersion || oldEntries.length === 0) {
if (manifestState(oldManifest, newManifest) !== 'valid') {
entries.buildFromSource = Array.from(newEntryMap.keys());
return entries;
}
@ -265,16 +322,43 @@ function getEntriesFromManifests(
return entries;
}
type ManifestState =
| 'valid'
| 'no-manifest'
| 'version-mismatch'
| 'no-entries'
| 'lockfile-mismatch'
| 'config-mismatch';
function manifestState(oldManifest: ContentManifest, newManifest: ContentManifest): ManifestState {
// There isn't an existing manifest.
if (oldManifest.version === NO_MANIFEST_VERSION) {
return 'no-manifest';
}
// Version mismatch, always invalid
if (oldManifest.version !== newManifest.version) {
return 'version-mismatch';
}
if (oldManifest.entries.length === 0) {
return 'no-entries';
}
// Lockfiles have changed or there is no lockfile at all.
if (oldManifest.lockfiles !== newManifest.lockfiles || newManifest.lockfiles === '') {
return 'lockfile-mismatch';
}
// Config has changed.
if (oldManifest.configs !== newManifest.configs) {
return 'config-mismatch';
}
return 'valid';
}
async function generateContentManifest(
opts: StaticBuildOptions,
lookupMap: ContentLookupMap
): Promise<ContentManifest> {
let manifest: ContentManifest = {
version: CONTENT_MANIFEST_VERSION,
entries: [],
serverEntries: [],
clientEntries: [],
};
let manifest = createContentManifest();
manifest.version = CONTENT_MANIFEST_VERSION;
const limit = pLimit(10);
const promises: Promise<void>[] = [];
@ -291,12 +375,62 @@ async function generateContentManifest(
}
}
const [lockfiles, configs] = await Promise.all([
lockfilesHash(opts.settings.config.root),
configHash(opts.settings.config.root),
]);
manifest.lockfiles = lockfiles;
manifest.configs = configs;
await Promise.all(promises);
return manifest;
}
function checksum(data: string): string {
return createHash('sha1').update(data).digest('base64');
async function pushBufferInto(fileURL: URL, buffers: Uint8Array[]) {
try {
const handle = await fsMod.promises.open(fileURL, 'r');
const data = await handle.readFile();
buffers.push(data);
await handle.close();
} catch {
// File doesn't exist, ignore
}
}
async function lockfilesHash(root: URL) {
// Order is important so don't change this.
const lockfiles = ['package-lock.json', 'pnpm-lock.yaml', 'yarn.lock', 'bun.lockb'];
const datas: Uint8Array[] = [];
const promises: Promise<void>[] = [];
for (const lockfileName of lockfiles) {
const fileURL = new URL(`./${lockfileName}`, root);
promises.push(pushBufferInto(fileURL, datas));
}
await Promise.all(promises);
return checksum(...datas);
}
async function configHash(root: URL) {
const configFileNames = configPaths;
for (const configPath of configFileNames) {
try {
const fileURL = new URL(`./${configPath}`, root);
const data = await fsMod.promises.readFile(fileURL);
const hash = checksum(data);
return hash;
} catch {
// File doesn't exist
}
}
// No config file, still create a hash since we can compare nothing against nothing.
return checksum(`export default {}`);
}
function checksum(...datas: string[] | Uint8Array[]): string {
const hash = createHash('sha1');
datas.forEach((data) => hash.update(data));
return hash.digest('base64');
}
function collectionTypeToFlag(type: 'content' | 'data') {
@ -308,8 +442,15 @@ export function pluginContent(
opts: StaticBuildOptions,
internals: BuildInternals
): AstroBuildPlugin {
const cachedChunks = new URL('./chunks/', opts.settings.config.cacheDir);
const distChunks = new URL('./chunks/', opts.settings.config.outDir);
const { cacheDir, outDir } = opts.settings.config;
const chunksFolder = './' + CHUNKS_PATH;
const assetsFolder = './' + appendForwardSlash(opts.settings.config.build.assets);
// These are build output that is kept in the cache.
const cachedBuildOutput = [
{ cached: new URL(chunksFolder, cacheDir), dist: new URL(chunksFolder, outDir) },
{ cached: new URL(assetsFolder, cacheDir), dist: new URL(assetsFolder, outDir) },
];
return {
targets: ['server'],
@ -321,10 +462,9 @@ export function pluginContent(
if (isServerLikeOutput(opts.settings.config)) {
return { vitePlugin: undefined };
}
const lookupMap = await generateLookupMap({ settings: opts.settings, fs: fsMod });
return {
vitePlugin: vitePluginContent(opts, lookupMap, internals),
vitePlugin: vitePluginContent(opts, lookupMap, internals, cachedBuildOutput),
};
},
@ -335,8 +475,11 @@ export function pluginContent(
if (isServerLikeOutput(opts.settings.config)) {
return;
}
if (fsMod.existsSync(distChunks)) {
await copyFiles(distChunks, cachedChunks, true);
// Cache build output of chunks and assets
for (const { cached, dist } of cachedBuildOutput) {
if (fsMod.existsSync(dist)) {
await copyFiles(dist, cached, true);
}
}
},
},

View file

@ -1,5 +1,5 @@
import type { GetModuleInfo } from 'rollup';
import type { BuildOptions, Plugin as VitePlugin, ResolvedConfig, Rollup } from 'vite';
import type { BuildOptions, ResolvedConfig, Rollup, Plugin as VitePlugin } from 'vite';
import { isBuildableCSSRequest } from '../../../vite-plugin-astro-server/util.js';
import type { BuildInternals } from '../internal.js';
import type { AstroBuildPlugin, BuildTarget } from '../plugin.js';

View file

@ -1,5 +1,5 @@
import { extname } from 'node:path';
import type { BuildOptions, Plugin as VitePlugin, Rollup } from 'vite';
import type { BuildOptions, Rollup, Plugin as VitePlugin } from 'vite';
// eslint-disable-next-line @typescript-eslint/ban-types
type OutputOptionsHook = Extract<VitePlugin['outputOptions'], Function>;

View file

@ -23,6 +23,7 @@ import { PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
import { AstroError, AstroErrorData } from '../errors/index.js';
import { routeIsRedirect } from '../redirects/index.js';
import { getOutDirWithinCwd } from './common.js';
import { CHUNKS_PATH } from './consts.js';
import { generatePages } from './generate.js';
import { trackPageData } from './internal.js';
import { type AstroBuildPluginContainer, createPluginContainer } from './plugin.js';
@ -196,7 +197,7 @@ async function ssrBuild(
// We need to keep these separate
chunkFileNames(chunkInfo) {
const { name } = chunkInfo;
let prefix = 'chunks/';
let prefix = CHUNKS_PATH;
let suffix = '_[hash].mjs';
if (isContentCache) {
@ -454,7 +455,7 @@ export async function copyFiles(fromFolder: URL, toFolder: URL, includeDotfiles
dot: includeDotfiles,
});
if (files.length === 0) return;
await Promise.all(
return await Promise.all(
files.map(async function copyFile(filename) {
const from = new URL(filename, fromFolder);
const to = new URL(filename, toFolder);

View file

@ -78,15 +78,19 @@ export function resolveRoot(cwd?: string | URL): string {
return cwd ? path.resolve(cwd) : process.cwd();
}
// Config paths to search for. In order of likely appearance
// to speed up the check.
export const configPaths = Object.freeze([
'astro.config.mjs',
'astro.config.js',
'astro.config.ts',
'astro.config.mts',
'astro.config.cjs',
'astro.config.cts',
]);
async function search(fsMod: typeof fs, root: string) {
const paths = [
'astro.config.mjs',
'astro.config.js',
'astro.config.ts',
'astro.config.mts',
'astro.config.cjs',
'astro.config.cts',
].map((p) => path.join(root, p));
const paths = configPaths.map((p) => path.join(root, p));
for (const file of paths) {
if (fsMod.existsSync(file)) {

View file

@ -1,4 +1,10 @@
export { resolveConfig, resolveConfigPath, resolveFlags, resolveRoot } from './config.js';
export {
configPaths,
resolveConfig,
resolveConfigPath,
resolveFlags,
resolveRoot,
} from './config.js';
export { createNodeLogger } from './logging.js';
export { mergeConfig } from './merge.js';
export type { AstroConfigType } from './schema.js';

View file

@ -1,8 +1,8 @@
import type {
ShikiConfig,
RehypePlugin as _RehypePlugin,
RemarkPlugin as _RemarkPlugin,
RemarkRehype as _RemarkRehype,
ShikiConfig,
} from '@astrojs/markdown-remark';
import { markdownConfigDefaults } from '@astrojs/markdown-remark';
import { type BuiltinTheme, bundledThemes } from 'shiki';

View file

@ -208,6 +208,7 @@ export async function createVite(
noExternal: [...ALWAYS_NOEXTERNAL, ...astroPkgsConfig.ssr.noExternal],
external: [...(mode === 'dev' ? ONLY_DEV_EXTERNAL : []), ...astroPkgsConfig.ssr.external],
},
build: { assetsDir: settings.config.build.assets },
};
// If the user provides a custom assets prefix, make sure assets handled by Vite

View file

@ -171,7 +171,9 @@ function collectInfoFromStacktrace(error: SSRError & { stack: string }): StackIn
error.pluginCode ||
error.id ||
// TODO: this could be better, `src` might be something else
stackText.split('\n').find((ln) => ln.includes('src') || ln.includes('node_modules'));
stackText
.split('\n')
.find((ln) => ln.includes('src') || ln.includes('node_modules'));
// Disable eslint as we're not sure how to improve this regex yet
// eslint-disable-next-line regexp/no-super-linear-backtracking
const source = possibleFilePath?.replace?.(/^[^(]+\(([^)]+).*$/, '$1').replace(/^\s+at\s+/, '');

View file

@ -168,9 +168,7 @@ export const NoMatchingRenderer = {
${
validRenderersCount > 0
? `There ${plural ? 'are' : 'is'} ${validRenderersCount} renderer${
plural ? 's' : ''
} configured in your \`astro.config.mjs\` file,
? `There ${plural ? 'are' : 'is'} ${validRenderersCount} renderer${plural ? 's' : ''} configured in your \`astro.config.mjs\` file,
but ${plural ? 'none were' : 'it was not'} able to server-side render \`${componentName}\`.`
: `No valid renderer was found ${
componentExtension

View file

@ -1,6 +1,6 @@
import { fileURLToPath } from 'url';
import stripAnsi from 'strip-ansi';
import type { LogLevel, Logger as ViteLogger, Rollup } from 'vite';
import type { LogLevel, Rollup, Logger as ViteLogger } from 'vite';
import { isAstroError } from '../errors/errors.js';
import { serverShortcuts as formatServerShortcuts } from '../messages.js';
import { type Logger as AstroLogger, isLogLevelEnabled } from './core.js';

View file

@ -317,12 +317,57 @@ export class RenderContext {
return result;
}
#astroPagePartial?: Omit<AstroGlobal, 'props' | 'self' | 'slots'>;
/**
* The Astro global is sourced in 3 different phases:
* - **Static**: `.generator` and `.glob` is printed by the compiler, instantiated once per process per astro file
* - **Page-level**: `.request`, `.cookies`, `.locals` etc. These remain the same for the duration of the request.
* - **Component-level**: `.props`, `.slots`, and `.self` are unique to each _use_ of each component.
*
* The page level partial is used as the prototype of the user-visible `Astro` global object, which is instantiated once per use of a component.
*/
createAstro(
result: SSRResult,
astroGlobalPartial: AstroGlobalPartial,
astroStaticPartial: AstroGlobalPartial,
props: Record<string, any>,
slotValues: Record<string, any> | null
): AstroGlobal {
// Create page partial with static partial so they can be cached together.
const astroPagePartial = (this.#astroPagePartial ??= this.createAstroPagePartial(
result,
astroStaticPartial
));
// Create component-level partials. `Astro.self` is added by the compiler.
const astroComponentPartial = { props, self: null };
// Create final object. `Astro.slots` will be lazily created.
const Astro: Omit<AstroGlobal, 'self' | 'slots'> = Object.assign(
Object.create(astroPagePartial),
astroComponentPartial
);
// Handle `Astro.slots`
let _slots: AstroGlobal['slots'];
Object.defineProperty(Astro, 'slots', {
get: () => {
if (!_slots) {
_slots = new Slots(
result,
slotValues,
this.pipeline.logger
) as unknown as AstroGlobal['slots'];
}
return _slots;
},
});
return Astro as AstroGlobal;
}
createAstroPagePartial(
result: SSRResult,
astroStaticPartial: AstroGlobalPartial
): Omit<AstroGlobal, 'props' | 'self' | 'slots'> {
const renderContext = this;
const { cookies, locals, params, pipeline, url } = this;
const { response } = result;
@ -363,12 +408,10 @@ export class RenderContext {
}
};
const slots = new Slots(result, slotValues, pipeline.logger) as unknown as AstroGlobal['slots'];
// `Astro.self` is added by the compiler
const astroGlobalCombined: Omit<AstroGlobal, 'self'> = {
generator: astroGlobalPartial.generator,
glob: astroGlobalPartial.glob,
return {
generator: astroStaticPartial.generator,
glob: astroStaticPartial.glob,
cookies,
get clientAddress() {
return renderContext.clientAddress();
@ -383,18 +426,14 @@ export class RenderContext {
get preferredLocaleList() {
return renderContext.computePreferredLocaleList();
},
props,
locals,
redirect,
reroute,
request: this.request,
response,
slots,
site: pipeline.site,
url,
};
return astroGlobalCombined as AstroGlobal;
}
clientAddress() {

View file

@ -477,9 +477,16 @@ type RunHookBuildDone = {
pages: string[];
routes: RouteData[];
logging: Logger;
cacheManifest: boolean;
};
export async function runHookBuildDone({ config, pages, routes, logging }: RunHookBuildDone) {
export async function runHookBuildDone({
config,
pages,
routes,
logging,
cacheManifest,
}: RunHookBuildDone) {
const dir = isServerLikeOutput(config) ? config.build.client : config.outDir;
await fs.promises.mkdir(dir, { recursive: true });
@ -495,6 +502,7 @@ export async function runHookBuildDone({ config, pages, routes, logging }: RunHo
dir,
routes,
logger,
cacheManifest,
}),
logger: logging,
});

Some files were not shown because too many files have changed in this diff Show more