Merge branch 'main' into upgrade-zod
This commit is contained in:
commit
637ce12695
32 changed files with 282 additions and 59 deletions
6
.changeset/early-ghosts-hang.md
Normal file
6
.changeset/early-ghosts-hang.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
'@astrojs/tailwind': patch
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
The `@astrojs/tailwind` integration now creates a `tailwind.config.mjs` file by default
|
9
.changeset/great-bears-watch.md
Normal file
9
.changeset/great-bears-watch.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Revert fix #8472
|
||||||
|
|
||||||
|
[#8472](https://github.com/withastro/astro/pull/8472) caused some style files from previous pages to not be cleanly deleted on view transitions. For a discussion of a future fix for the original issue [#8144](https://github.com/withastro/astro/issues/8114) see [#8745](https://github.com/withastro/astro/pull/8745).
|
||||||
|
|
||||||
|
|
5
.changeset/heavy-elephants-tan.md
Normal file
5
.changeset/heavy-elephants-tan.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@astrojs/cloudflare': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Adds `cloudflare:sockets` compile support
|
5
.changeset/large-clouds-sip.md
Normal file
5
.changeset/large-clouds-sip.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fixed an issue on Windows where lowercase drive letters in current working directory led to missing scripts and styles.
|
5
.changeset/seven-seas-hide.md
Normal file
5
.changeset/seven-seas-hide.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix flickering during view transitions
|
5
.changeset/sharp-insects-yawn.md
Normal file
5
.changeset/sharp-insects-yawn.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Make CSS chunk names less confusing
|
5
.changeset/wise-lions-lay.md
Normal file
5
.changeset/wise-lions-lay.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix transition attributes on islands
|
5
.changeset/young-taxis-battle.md
Normal file
5
.changeset/young-taxis-battle.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix an issue where HTML attributes do not render if getHTMLAttributes in an image service returns a Promise
|
5
.github/workflows/snapshot-release.yml
vendored
5
.github/workflows/snapshot-release.yml
vendored
|
@ -19,6 +19,11 @@ jobs:
|
||||||
name: Create a snapshot release of a pull request
|
name: Create a snapshot release of a pull request
|
||||||
if: ${{ github.repository_owner == 'withastro' && github.event.issue.pull_request && startsWith(github.event.comment.body, '!preview') }}
|
if: ${{ github.repository_owner == 'withastro' && github.event.issue.pull_request && startsWith(github.event.comment.body, '!preview') }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: "Check if user has admin access (only admins can publish snapshot releases)."
|
- name: "Check if user has admin access (only admins can publish snapshot releases)."
|
||||||
uses: "lannonbr/repo-permission-check-action@2.0.0"
|
uses: "lannonbr/repo-permission-check-action@2.0.0"
|
||||||
|
|
|
@ -32,6 +32,13 @@ async function main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// windows drive letters can sometimes be lowercase, which vite cannot process
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
const cwd = process.cwd();
|
||||||
|
const correctedCwd = cwd.slice(0, 1).toUpperCase() + cwd.slice(1);
|
||||||
|
if (correctedCwd !== cwd) process.chdir(correctedCwd);
|
||||||
|
}
|
||||||
|
|
||||||
return import('./dist/cli/index.js')
|
return import('./dist/cli/index.js')
|
||||||
.then(({ cli }) => cli(process.argv))
|
.then(({ cli }) => cli(process.argv))
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
import { ViewTransitions } from 'astro:transitions';
|
||||||
|
---
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<ViewTransitions/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Local transitions</p>
|
||||||
|
<slot/>
|
||||||
|
<script>
|
||||||
|
document.addEventListener("astro:after-swap", () => {
|
||||||
|
document.querySelector("p").addEventListener("transitionstart", () => {
|
||||||
|
console.info("transitionstart");
|
||||||
|
});
|
||||||
|
document.documentElement.setAttribute("class", "blue");
|
||||||
|
});
|
||||||
|
document.dispatchEvent(new Event("astro:after-swap"));
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
<style>
|
||||||
|
p {
|
||||||
|
transition: background-color 1s;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
background-color: #0ee;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.blue p {
|
||||||
|
background-color: #ee0;
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</html>
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
import ListenerLayout from '../components/listener-layout.astro';
|
||||||
|
---
|
||||||
|
<ListenerLayout>
|
||||||
|
<a id="totwo" href="/listener-two">Go to listener two</a>
|
||||||
|
</ListenerLayout>
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
import ListenerLayout from '../components/listener-layout.astro';
|
||||||
|
---
|
||||||
|
<ListenerLayout>
|
||||||
|
<a id="toone" href="/listener-one">Go to listener one</a>
|
||||||
|
</ListenerLayout>
|
|
@ -230,6 +230,28 @@ test.describe('View Transitions', () => {
|
||||||
await expect(h, 'imported CSS updated').toHaveCSS('background-color', 'rgba(0, 0, 0, 0)');
|
await expect(h, 'imported CSS updated').toHaveCSS('background-color', 'rgba(0, 0, 0, 0)');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('No page rendering during swap()', async ({ page, astro }) => {
|
||||||
|
let transitions = 0;
|
||||||
|
page.on('console', (msg) => {
|
||||||
|
if (msg.type() === 'info' && msg.text() === 'transitionstart') ++transitions;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Go to page 1
|
||||||
|
await page.goto(astro.resolveUrl('/listener-one'));
|
||||||
|
let p = page.locator('#totwo');
|
||||||
|
await expect(p, 'should have content').toHaveText('Go to listener two');
|
||||||
|
// on load a CSS transition is started triggered by a class on the html element
|
||||||
|
expect(transitions).toEqual(1);
|
||||||
|
|
||||||
|
// go to page 2
|
||||||
|
await page.click('#totwo');
|
||||||
|
p = page.locator('#toone');
|
||||||
|
await expect(p, 'should have content').toHaveText('Go to listener one');
|
||||||
|
// swap() resets that class, the after-swap listener sets it again.
|
||||||
|
// the temporarily missing class must not trigger page rendering
|
||||||
|
expect(transitions).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
test('click hash links does not do navigation', async ({ page, astro }) => {
|
test('click hash links does not do navigation', async ({ page, astro }) => {
|
||||||
// Go to page 1
|
// Go to page 1
|
||||||
await page.goto(astro.resolveUrl('/one'));
|
await page.goto(astro.resolveUrl('/one'));
|
||||||
|
@ -648,7 +670,7 @@ test.describe('View Transitions', () => {
|
||||||
expect(loads.length, 'There should be 2 page loads').toEqual(2);
|
expect(loads.length, 'There should be 2 page loads').toEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('client:only styles are retained on transition', async ({ page, astro }) => {
|
test.skip('client:only styles are retained on transition', async ({ page, astro }) => {
|
||||||
const totalExpectedStyles = 7;
|
const totalExpectedStyles = 7;
|
||||||
|
|
||||||
// Go to page 1
|
// Go to page 1
|
||||||
|
|
|
@ -111,7 +111,7 @@ export async function getImage(
|
||||||
src: imageURL,
|
src: imageURL,
|
||||||
attributes:
|
attributes:
|
||||||
service.getHTMLAttributes !== undefined
|
service.getHTMLAttributes !== undefined
|
||||||
? service.getHTMLAttributes(validatedOptions, imageConfig)
|
? await service.getHTMLAttributes(validatedOptions, imageConfig)
|
||||||
: {},
|
: {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ const ALIASES = new Map([
|
||||||
]);
|
]);
|
||||||
const ASTRO_CONFIG_STUB = `import { defineConfig } from 'astro/config';\n\nexport default defineConfig({});`;
|
const ASTRO_CONFIG_STUB = `import { defineConfig } from 'astro/config';\n\nexport default defineConfig({});`;
|
||||||
const TAILWIND_CONFIG_STUB = `/** @type {import('tailwindcss').Config} */
|
const TAILWIND_CONFIG_STUB = `/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
export default {
|
||||||
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
|
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
|
@ -160,7 +160,7 @@ export async function add(names: string[], { flags }: AddOptions) {
|
||||||
'./tailwind.config.mjs',
|
'./tailwind.config.mjs',
|
||||||
'./tailwind.config.js',
|
'./tailwind.config.js',
|
||||||
],
|
],
|
||||||
defaultConfigFile: './tailwind.config.cjs',
|
defaultConfigFile: './tailwind.config.mjs',
|
||||||
defaultConfigContent: TAILWIND_CONFIG_STUB,
|
defaultConfigContent: TAILWIND_CONFIG_STUB,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { GetModuleInfo } from 'rollup';
|
import type { GetModuleInfo, ModuleInfo } from 'rollup';
|
||||||
|
|
||||||
import crypto from 'node:crypto';
|
import crypto from 'node:crypto';
|
||||||
import npath from 'node:path';
|
import npath from 'node:path';
|
||||||
|
@ -6,20 +6,29 @@ import type { AstroSettings } from '../../@types/astro.js';
|
||||||
import { viteID } from '../util.js';
|
import { viteID } from '../util.js';
|
||||||
import { getTopLevelPages } from './graph.js';
|
import { getTopLevelPages } from './graph.js';
|
||||||
|
|
||||||
|
// These pages could be used as base names for the chunk hashed name, but they are confusing
|
||||||
|
// and should be avoided it possible
|
||||||
|
const confusingBaseNames = ['404', '500'];
|
||||||
|
|
||||||
// The short name for when the hash can be included
|
// The short name for when the hash can be included
|
||||||
// We could get rid of this and only use the createSlugger implementation, but this creates
|
// We could get rid of this and only use the createSlugger implementation, but this creates
|
||||||
// slightly prettier names.
|
// slightly prettier names.
|
||||||
export function shortHashedName(id: string, ctx: { getModuleInfo: GetModuleInfo }): string {
|
export function shortHashedName(id: string, ctx: { getModuleInfo: GetModuleInfo }): string {
|
||||||
const parents = Array.from(getTopLevelPages(id, ctx));
|
const parents = Array.from(getTopLevelPages(id, ctx));
|
||||||
const firstParentId = parents[0]?.[0].id;
|
return createNameHash(
|
||||||
const firstParentName = firstParentId ? npath.parse(firstParentId).name : 'index';
|
getFirstParentId(parents),
|
||||||
|
parents.map(([page]) => page.id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createNameHash(baseId: string | undefined, hashIds: string[]): string {
|
||||||
|
const baseName = baseId ? prettifyBaseName(npath.parse(baseId).name) : 'index';
|
||||||
const hash = crypto.createHash('sha256');
|
const hash = crypto.createHash('sha256');
|
||||||
for (const [page] of parents) {
|
for (const id of hashIds) {
|
||||||
hash.update(page.id, 'utf-8');
|
hash.update(id, 'utf-8');
|
||||||
}
|
}
|
||||||
const h = hash.digest('hex').slice(0, 8);
|
const h = hash.digest('hex').slice(0, 8);
|
||||||
const proposedName = firstParentName + '.' + h;
|
const proposedName = baseName + '.' + h;
|
||||||
return proposedName;
|
return proposedName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +43,7 @@ export function createSlugger(settings: AstroSettings) {
|
||||||
.map(([page]) => page.id)
|
.map(([page]) => page.id)
|
||||||
.sort()
|
.sort()
|
||||||
.join('-');
|
.join('-');
|
||||||
const firstParentId = parents[0]?.[0].id || indexPage;
|
const firstParentId = getFirstParentId(parents) || indexPage;
|
||||||
|
|
||||||
// Use the last two segments, for ex /docs/index
|
// Use the last two segments, for ex /docs/index
|
||||||
let dir = firstParentId;
|
let dir = firstParentId;
|
||||||
|
@ -45,7 +54,7 @@ export function createSlugger(settings: AstroSettings) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = npath.parse(npath.basename(dir)).name;
|
const name = prettifyBaseName(npath.parse(npath.basename(dir)).name);
|
||||||
key = key.length ? name + sep + key : name;
|
key = key.length ? name + sep + key : name;
|
||||||
dir = npath.dirname(dir);
|
dir = npath.dirname(dir);
|
||||||
i++;
|
i++;
|
||||||
|
@ -76,3 +85,32 @@ export function createSlugger(settings: AstroSettings) {
|
||||||
return name;
|
return name;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the first parent id from `parents` where its name is not confusing.
|
||||||
|
* Returns undefined if there's no parents.
|
||||||
|
*/
|
||||||
|
function getFirstParentId(parents: [ModuleInfo, number, number][]) {
|
||||||
|
for (const parent of parents) {
|
||||||
|
const id = parent[0].id;
|
||||||
|
const baseName = npath.parse(id).name;
|
||||||
|
if (!confusingBaseNames.includes(baseName)) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If all parents are confusing, just use the first one. Or if there's no
|
||||||
|
// parents, this will return undefined.
|
||||||
|
return parents[0]?.[0].id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const charsToReplaceRe = /[.\[\]]/g;
|
||||||
|
const underscoresRe = /_+/g;
|
||||||
|
/**
|
||||||
|
* Prettify base names so they're easier to read:
|
||||||
|
* - index -> index
|
||||||
|
* - [slug] -> _slug_
|
||||||
|
* - [...spread] -> _spread_
|
||||||
|
*/
|
||||||
|
function prettifyBaseName(str: string) {
|
||||||
|
return str.replace(charsToReplaceRe, '_').replace(underscoresRe, '_');
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import * as crypto from 'node:crypto';
|
|
||||||
import * as npath from 'node:path';
|
|
||||||
import type { GetModuleInfo } from 'rollup';
|
import type { GetModuleInfo } from 'rollup';
|
||||||
import { type ResolvedConfig, type Plugin as VitePlugin } from 'vite';
|
import { type ResolvedConfig, type Plugin as VitePlugin } from 'vite';
|
||||||
import { isBuildableCSSRequest } from '../../../vite-plugin-astro-server/util.js';
|
import { isBuildableCSSRequest } from '../../../vite-plugin-astro-server/util.js';
|
||||||
|
@ -93,7 +91,7 @@ function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] {
|
||||||
if (new URL(pageInfo.id, 'file://').searchParams.has(PROPAGATED_ASSET_FLAG)) {
|
if (new URL(pageInfo.id, 'file://').searchParams.has(PROPAGATED_ASSET_FLAG)) {
|
||||||
// Split delayed assets to separate modules
|
// Split delayed assets to separate modules
|
||||||
// so they can be injected where needed
|
// so they can be injected where needed
|
||||||
const chunkId = createNameHash(id, [id]);
|
const chunkId = assetName.createNameHash(id, [id]);
|
||||||
internals.cssModuleToChunkIdMap.set(id, chunkId);
|
internals.cssModuleToChunkIdMap.set(id, chunkId);
|
||||||
return chunkId;
|
return chunkId;
|
||||||
}
|
}
|
||||||
|
@ -272,17 +270,6 @@ function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] {
|
||||||
|
|
||||||
/***** UTILITY FUNCTIONS *****/
|
/***** UTILITY FUNCTIONS *****/
|
||||||
|
|
||||||
function createNameHash(baseId: string, hashIds: string[]): string {
|
|
||||||
const baseName = baseId ? npath.parse(baseId).name : 'index';
|
|
||||||
const hash = crypto.createHash('sha256');
|
|
||||||
for (const id of hashIds) {
|
|
||||||
hash.update(id, 'utf-8');
|
|
||||||
}
|
|
||||||
const h = hash.digest('hex').slice(0, 8);
|
|
||||||
const proposedName = baseName + '.' + h;
|
|
||||||
return proposedName;
|
|
||||||
}
|
|
||||||
|
|
||||||
function* getParentClientOnlys(
|
function* getParentClientOnlys(
|
||||||
id: string,
|
id: string,
|
||||||
ctx: { getModuleInfo: GetModuleInfo },
|
ctx: { getModuleInfo: GetModuleInfo },
|
||||||
|
|
|
@ -15,10 +15,13 @@ export interface HydrationMetadata {
|
||||||
componentExport: { value: string };
|
componentExport: { value: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Props = Record<string | number | symbol, any>;
|
||||||
|
|
||||||
interface ExtractedProps {
|
interface ExtractedProps {
|
||||||
isPage: boolean;
|
isPage: boolean;
|
||||||
hydration: HydrationMetadata | null;
|
hydration: HydrationMetadata | null;
|
||||||
props: Record<string | number | symbol, any>;
|
props: Props;
|
||||||
|
propsWithoutTransitionAttributes: Props;
|
||||||
}
|
}
|
||||||
|
|
||||||
const transitionDirectivesToCopyOnIsland = Object.freeze([
|
const transitionDirectivesToCopyOnIsland = Object.freeze([
|
||||||
|
@ -29,13 +32,14 @@ const transitionDirectivesToCopyOnIsland = Object.freeze([
|
||||||
// Used to extract the directives, aka `client:load` information about a component.
|
// Used to extract the directives, aka `client:load` information about a component.
|
||||||
// Finds these special props and removes them from what gets passed into the component.
|
// Finds these special props and removes them from what gets passed into the component.
|
||||||
export function extractDirectives(
|
export function extractDirectives(
|
||||||
inputProps: Record<string | number | symbol, any>,
|
inputProps: Props,
|
||||||
clientDirectives: SSRResult['clientDirectives']
|
clientDirectives: SSRResult['clientDirectives']
|
||||||
): ExtractedProps {
|
): ExtractedProps {
|
||||||
let extracted: ExtractedProps = {
|
let extracted: ExtractedProps = {
|
||||||
isPage: false,
|
isPage: false,
|
||||||
hydration: null,
|
hydration: null,
|
||||||
props: {},
|
props: {},
|
||||||
|
propsWithoutTransitionAttributes: {},
|
||||||
};
|
};
|
||||||
for (const [key, value] of Object.entries(inputProps)) {
|
for (const [key, value] of Object.entries(inputProps)) {
|
||||||
if (key.startsWith('server:')) {
|
if (key.startsWith('server:')) {
|
||||||
|
@ -96,10 +100,14 @@ export function extractDirectives(
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
extracted.props[key] = value;
|
extracted.props[key] = value;
|
||||||
|
if (!transitionDirectivesToCopyOnIsland.includes(key)) {
|
||||||
|
extracted.propsWithoutTransitionAttributes[key] = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const sym of Object.getOwnPropertySymbols(inputProps)) {
|
for (const sym of Object.getOwnPropertySymbols(inputProps)) {
|
||||||
extracted.props[sym] = inputProps[sym];
|
extracted.props[sym] = inputProps[sym];
|
||||||
|
extracted.propsWithoutTransitionAttributes[sym] = inputProps[sym];
|
||||||
}
|
}
|
||||||
|
|
||||||
return extracted;
|
return extracted;
|
||||||
|
|
|
@ -92,7 +92,10 @@ async function renderFrameworkComponent(
|
||||||
displayName,
|
displayName,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { hydration, isPage, props } = extractDirectives(_props, clientDirectives);
|
const { hydration, isPage, props, propsWithoutTransitionAttributes } = extractDirectives(
|
||||||
|
_props,
|
||||||
|
clientDirectives
|
||||||
|
);
|
||||||
let html = '';
|
let html = '';
|
||||||
let attrs: Record<string, string> | undefined = undefined;
|
let attrs: Record<string, string> | undefined = undefined;
|
||||||
|
|
||||||
|
@ -217,7 +220,7 @@ async function renderFrameworkComponent(
|
||||||
({ html, attrs } = await renderer.ssr.renderToStaticMarkup.call(
|
({ html, attrs } = await renderer.ssr.renderToStaticMarkup.call(
|
||||||
{ result },
|
{ result },
|
||||||
Component,
|
Component,
|
||||||
props,
|
propsWithoutTransitionAttributes,
|
||||||
children,
|
children,
|
||||||
metadata
|
metadata
|
||||||
));
|
));
|
||||||
|
@ -242,7 +245,7 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr
|
||||||
({ html, attrs } = await renderer.ssr.renderToStaticMarkup.call(
|
({ html, attrs } = await renderer.ssr.renderToStaticMarkup.call(
|
||||||
{ result },
|
{ result },
|
||||||
Component,
|
Component,
|
||||||
props,
|
propsWithoutTransitionAttributes,
|
||||||
children,
|
children,
|
||||||
metadata
|
metadata
|
||||||
));
|
));
|
||||||
|
|
|
@ -42,11 +42,6 @@ const announce = () => {
|
||||||
};
|
};
|
||||||
const PERSIST_ATTR = 'data-astro-transition-persist';
|
const PERSIST_ATTR = 'data-astro-transition-persist';
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
// explained at its usage
|
|
||||||
let noopEl: HTMLDivElement;
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
noopEl = document.createElement('div');
|
|
||||||
}
|
|
||||||
|
|
||||||
// The History API does not tell you if navigation is forward or back, so
|
// 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
|
// you can figure it using an index. On pushState the index is incremented so you
|
||||||
|
@ -151,18 +146,24 @@ function isInfinite(animation: Animation) {
|
||||||
|
|
||||||
const updateHistoryAndScrollPosition = (toLocation: URL, replace: boolean, intraPage: boolean) => {
|
const updateHistoryAndScrollPosition = (toLocation: URL, replace: boolean, intraPage: boolean) => {
|
||||||
const fresh = !samePage(toLocation);
|
const fresh = !samePage(toLocation);
|
||||||
|
let scrolledToTop = false;
|
||||||
if (toLocation.href !== location.href) {
|
if (toLocation.href !== location.href) {
|
||||||
if (replace) {
|
if (replace) {
|
||||||
history.replaceState({ ...history.state }, '', toLocation.href);
|
history.replaceState({ ...history.state }, '', toLocation.href);
|
||||||
} else {
|
} else {
|
||||||
history.replaceState({ ...history.state, intraPage }, '');
|
history.replaceState({ ...history.state, intraPage }, '');
|
||||||
history.pushState({ index: ++currentHistoryIndex, scrollX, scrollY }, '', toLocation.href);
|
history.pushState(
|
||||||
|
{ index: ++currentHistoryIndex, scrollX: 0, scrollY: 0 },
|
||||||
|
'',
|
||||||
|
toLocation.href
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// now we are on the new page for non-history navigations!
|
// now we are on the new page for non-history navigations!
|
||||||
// (with history navigation page change happens before popstate is fired)
|
// (with history navigation page change happens before popstate is fired)
|
||||||
// freshly loaded pages start from the top
|
// freshly loaded pages start from the top
|
||||||
if (fresh) {
|
if (fresh) {
|
||||||
scrollTo({ left: 0, top: 0, behavior: 'instant' });
|
scrollTo({ left: 0, top: 0, behavior: 'instant' });
|
||||||
|
scrolledToTop = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (toLocation.hash) {
|
if (toLocation.hash) {
|
||||||
|
@ -171,7 +172,9 @@ const updateHistoryAndScrollPosition = (toLocation: URL, replace: boolean, intra
|
||||||
// that won't reload the page but instead scroll to the fragment
|
// that won't reload the page but instead scroll to the fragment
|
||||||
location.href = toLocation.href;
|
location.href = toLocation.href;
|
||||||
} else {
|
} else {
|
||||||
scrollTo({ left: 0, top: 0, behavior: 'instant' });
|
if (!scrolledToTop) {
|
||||||
|
scrollTo({ left: 0, top: 0, behavior: 'instant' });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -198,22 +201,6 @@ async function updateDOM(
|
||||||
const href = el.getAttribute('href');
|
const href = el.getAttribute('href');
|
||||||
return newDocument.head.querySelector(`link[rel=stylesheet][href="${href}"]`);
|
return newDocument.head.querySelector(`link[rel=stylesheet][href="${href}"]`);
|
||||||
}
|
}
|
||||||
// What follows is a fix for an issue (#8472) with missing client:only styles after transition.
|
|
||||||
// That problem exists only in dev mode where styles are injected into the page by Vite.
|
|
||||||
// Returning a noop element ensures that the styles are not removed from the old document.
|
|
||||||
// Guarding the code below with the dev mode check
|
|
||||||
// allows tree shaking to remove this code in production.
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
if (el.tagName === 'STYLE' && el.dataset.viteDevId) {
|
|
||||||
const devId = el.dataset.viteDevId;
|
|
||||||
// If this same style tag exists, remove it from the new page
|
|
||||||
return (
|
|
||||||
newDocument.querySelector(`style[data-vite-dev-id="${devId}"]`) ||
|
|
||||||
// Otherwise, keep it anyways. This is client:only styles.
|
|
||||||
noopEl
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
7
packages/astro/test/fixtures/view-transitions/astro.config.mjs
vendored
Normal file
7
packages/astro/test/fixtures/view-transitions/astro.config.mjs
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
import react from '@astrojs/react';
|
||||||
|
|
||||||
|
// https://astro.build/config
|
||||||
|
export default defineConfig({
|
||||||
|
integrations: [react()],
|
||||||
|
});
|
|
@ -3,6 +3,9 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "workspace:*"
|
"astro": "workspace:*",
|
||||||
|
"@astrojs/react": "workspace:*",
|
||||||
|
"react": "^18.1.0",
|
||||||
|
"react-dom": "^18.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
packages/astro/test/fixtures/view-transitions/src/components/Island.css
vendored
Normal file
11
packages/astro/test/fixtures/view-transitions/src/components/Island.css
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
.counter {
|
||||||
|
display: grid;
|
||||||
|
font-size: 2em;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
margin-top: 2em;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counter-message {
|
||||||
|
text-align: center;
|
||||||
|
}
|
19
packages/astro/test/fixtures/view-transitions/src/components/Island.jsx
vendored
Normal file
19
packages/astro/test/fixtures/view-transitions/src/components/Island.jsx
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import './Island.css';
|
||||||
|
|
||||||
|
export default function Counter({ children, count: initialCount, id }) {
|
||||||
|
const [count, setCount] = useState(initialCount);
|
||||||
|
const add = () => setCount((i) => i + 1);
|
||||||
|
const subtract = () => setCount((i) => i - 1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div id={id} className="counter">
|
||||||
|
<button className="decrement" onClick={subtract}>-</button>
|
||||||
|
<pre>{count}</pre>
|
||||||
|
<button className="increment" onClick={add}>+</button>
|
||||||
|
</div>
|
||||||
|
<div className="counter-message">{children}</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
10
packages/astro/test/fixtures/view-transitions/src/pages/hasIsland.astro
vendored
Normal file
10
packages/astro/test/fixtures/view-transitions/src/pages/hasIsland.astro
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
import Island from '../components/Island.jsx';
|
||||||
|
---
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<Island id="1" count="{1}" children="Greetings!" transition:persist="here" client:load/>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -22,4 +22,14 @@ describe('View Transitions styles', () => {
|
||||||
|
|
||||||
expect($('head style')).to.have.a.lengthOf(3);
|
expect($('head style')).to.have.a.lengthOf(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not duplicate transition attributes on island contents', async () => {
|
||||||
|
let res = await fixture.fetch('/hasIsland');
|
||||||
|
let html = await res.text();
|
||||||
|
let $ = cheerio.load(html);
|
||||||
|
expect($('astro-island[data-astro-transition-persist]')).to.have.a.lengthOf(1);
|
||||||
|
expect(
|
||||||
|
$('astro-island[data-astro-transition-persist] > [data-astro-transition-persist]')
|
||||||
|
).to.have.a.lengthOf(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -357,6 +357,10 @@ import { Buffer } from 'node:buffer';
|
||||||
|
|
||||||
Additionally, you'll need to enable the Compatibility Flag in Cloudflare. The configuration for this flag may vary based on where you deploy your Astro site. For detailed guidance, please refer to the [Cloudflare documentation on enabling Node.js compatibility](https://developers.cloudflare.com/workers/runtime-apis/nodejs).
|
Additionally, you'll need to enable the Compatibility Flag in Cloudflare. The configuration for this flag may vary based on where you deploy your Astro site. For detailed guidance, please refer to the [Cloudflare documentation on enabling Node.js compatibility](https://developers.cloudflare.com/workers/runtime-apis/nodejs).
|
||||||
|
|
||||||
|
## Cloudflare module support
|
||||||
|
|
||||||
|
All Cloudflare namespaced packages (e.g. `cloudflare:sockets`) are allowlisted for use. Note that the package `cloudflare:sockets` does not work locally without using Wrangler dev mode.
|
||||||
|
|
||||||
## Preview with Wrangler
|
## Preview with Wrangler
|
||||||
|
|
||||||
To use [`wrangler`](https://developers.cloudflare.com/workers/wrangler/) to run your application locally, update the preview script:
|
To use [`wrangler`](https://developers.cloudflare.com/workers/wrangler/) to run your application locally, update the preview script:
|
||||||
|
|
|
@ -290,6 +290,7 @@ export default function createIntegration(args?: Options): AstroIntegration {
|
||||||
'node:stream',
|
'node:stream',
|
||||||
'node:string_decoder',
|
'node:string_decoder',
|
||||||
'node:util',
|
'node:util',
|
||||||
|
'cloudflare:*',
|
||||||
],
|
],
|
||||||
entryPoints: pathsGroup,
|
entryPoints: pathsGroup,
|
||||||
outbase: absolutePagesDirname,
|
outbase: absolutePagesDirname,
|
||||||
|
@ -371,6 +372,7 @@ export default function createIntegration(args?: Options): AstroIntegration {
|
||||||
'node:stream',
|
'node:stream',
|
||||||
'node:string_decoder',
|
'node:string_decoder',
|
||||||
'node:util',
|
'node:util',
|
||||||
|
'cloudflare:*',
|
||||||
],
|
],
|
||||||
entryPoints: [entryPath],
|
entryPoints: [entryPath],
|
||||||
outfile: buildPath,
|
outfile: buildPath,
|
||||||
|
|
|
@ -94,7 +94,7 @@ https://user-images.githubusercontent.com/4033662/169918388-8ed153b2-0ba0-4b24-b
|
||||||
|
|
||||||
### Configuring Tailwind
|
### Configuring Tailwind
|
||||||
|
|
||||||
If you used the Quick Install instructions and said yes to each prompt, you'll see a `tailwind.config.cjs` file in your project's root directory. Use this file for your Tailwind configuration changes. You can learn how to customize Tailwind using this file [in the Tailwind docs](https://tailwindcss.com/docs/configuration).
|
If you used the Quick Install instructions and said yes to each prompt, you'll see a `tailwind.config.mjs` file in your project's root directory. Use this file for your Tailwind configuration changes. You can learn how to customize Tailwind using this file [in the Tailwind docs](https://tailwindcss.com/docs/configuration).
|
||||||
|
|
||||||
If it isn't there, you add your own `tailwind.config.(js|cjs|mjs)` file to the root directory and the integration will use its configurations. This can be great if you already have Tailwind configured in another project and want to bring those settings over to this one.
|
If it isn't there, you add your own `tailwind.config.(js|cjs|mjs)` file to the root directory and the integration will use its configurations. This can be great if you already have Tailwind configured in another project and want to bring those settings over to this one.
|
||||||
|
|
||||||
|
@ -178,8 +178,8 @@ error The `text-special` class does not exist. If `text-special` is a custom c
|
||||||
[Instead of using `@layer` directives in a global stylesheet](https://tailwindcss.com/docs/functions-and-directives#using-apply-with-per-component-css), define your custom styles by adding a plugin to your Tailwind config to fix it:
|
[Instead of using `@layer` directives in a global stylesheet](https://tailwindcss.com/docs/functions-and-directives#using-apply-with-per-component-css), define your custom styles by adding a plugin to your Tailwind config to fix it:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// tailwind.config.cjs
|
// tailwind.config.mjs
|
||||||
module.exports = {
|
export default {
|
||||||
// ...
|
// ...
|
||||||
plugins: [
|
plugins: [
|
||||||
function ({ addComponents, theme }) {
|
function ({ addComponents, theme }) {
|
||||||
|
|
|
@ -50,7 +50,7 @@ async function getViteConfiguration(
|
||||||
type TailwindOptions = {
|
type TailwindOptions = {
|
||||||
/**
|
/**
|
||||||
* Path to your tailwind config file
|
* Path to your tailwind config file
|
||||||
* @default 'tailwind.config.js'
|
* @default 'tailwind.config.mjs'
|
||||||
*/
|
*/
|
||||||
configFile?: string;
|
configFile?: string;
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3531,9 +3531,18 @@ importers:
|
||||||
|
|
||||||
packages/astro/test/fixtures/view-transitions:
|
packages/astro/test/fixtures/view-transitions:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@astrojs/react':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../../integrations/react
|
||||||
astro:
|
astro:
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../..
|
version: link:../../..
|
||||||
|
react:
|
||||||
|
specifier: ^18.1.0
|
||||||
|
version: 18.2.0
|
||||||
|
react-dom:
|
||||||
|
specifier: ^18.1.0
|
||||||
|
version: 18.2.0(react@18.2.0)
|
||||||
|
|
||||||
packages/astro/test/fixtures/virtual-astro-file:
|
packages/astro/test/fixtures/virtual-astro-file:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
Loading…
Reference in a new issue