Compare commits

..

6 commits

Author SHA1 Message Date
993c085952 expose file url to markdown renderer 2023-10-11 11:46:58 -05:00
Alexander Niebuhr
3f231cefed
port https://github.com/withastro/docs/pull/4980 (#8799) 2023-10-11 07:06:20 +02:00
lilnasy
a8b979ef40 [ci] format 2023-10-10 16:40:46 +00:00
Arsh
bd5aa1cd35
fix(transitions router): no-op on the server (#8771)
* fix(transitions router): no-op on the server

* factor out onPopState

* add e2e test case

* Apply suggestions from code review

Co-authored-by: Martin Trapp <94928215+martrapp@users.noreply.github.com>

* use supportsViewTransitions

* add changeset

* warn on navigate() use during ssr

* switch supportsViewTransitions to import.meta.env

* correct typo

* bring back import.meta.env

* !import.meta.env.SSR -> inBrowser

* Apply suggestions from code review

Co-authored-by: Martin Trapp <94928215+martrapp@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Martin Trapp <94928215+martrapp@users.noreply.github.com>

---------

Co-authored-by: Martin Trapp <94928215+martrapp@users.noreply.github.com>
2023-10-10 22:08:35 +05:30
alexanderniebuhr
03e6979c28 [ci] format 2023-10-10 16:11:20 +00:00
Mikkel Ricafrente
75781643a2
fix(cloudflare): runtime types for Cloudflare caches (#8782)
* fix cachestorage reference in cloudflare integration

* add cachestorage to serverdirectorymode

* add changeset

* remove global caches type

* update unlucky-avocados-brake.md
2023-10-10 18:08:17 +02:00
13 changed files with 147 additions and 73 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fixed an issue where the transitions router did not work within framework components.

View file

@ -0,0 +1,5 @@
---
'@astrojs/cloudflare': patch
---
fixes `AdvancedRuntime` & `DirectoryRuntime` types to work woth Cloudflare caches

View file

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

View file

@ -0,0 +1,12 @@
---
import ClickToNavigate from "../components/ClickToNavigate.jsx"
import { ViewTransitions } from "astro:transitions";
---
<html>
<head>
<ViewTransitions />
</head>
<body>
<ClickToNavigate id="react-client-load-navigate-button" to="/two" client:load/>
</body>
</html>

View file

@ -753,6 +753,21 @@ test.describe('View Transitions', () => {
await expect(p, 'should have content').toHaveText('Page 1');
});
test('Use the client side router in framework components', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/client-load'));
// the button is set to naviagte() to /two
const button = page.locator('#react-client-load-navigate-button');
await expect(button, 'should have content').toHaveText('Navigate to `/two`');
await button.click();
const p = page.locator('#two');
await expect(p, 'should have content').toHaveText('Page 2');
});
test('body inline scripts do not re-execute on navigation', async ({ page, astro }) => {
const errors = [];
page.addListener('pageerror', (err) => {

View file

@ -13,9 +13,14 @@ type Events = 'astro:page-load' | 'astro:after-swap';
// only update history entries that are managed by us
// leave other entries alone and do not accidently add state.
const persistState = (state: State) => history.state && history.replaceState(state, '');
export const supportsViewTransitions = !!document.startViewTransition;
const inBrowser = import.meta.env.SSR === false;
export const supportsViewTransitions = inBrowser && !!document.startViewTransition;
export const transitionEnabledOnThisPage = () =>
!!document.querySelector('[name="astro-view-transitions-enabled"]');
inBrowser && !!document.querySelector('[name="astro-view-transitions-enabled"]');
const samePage = (otherLocation: URL) =>
location.pathname === otherLocation.pathname && location.search === otherLocation.search;
const triggerEvent = (name: Events) => document.dispatchEvent(new Event(name));
@ -40,21 +45,27 @@ const announce = () => {
60
);
};
const PERSIST_ATTR = 'data-astro-transition-persist';
const parser = new DOMParser();
let parser: DOMParser;
// The History API does not tell you if navigation is forward or back, so
// you can figure it using an index. On pushState the index is incremented so you
// can use that to determine popstate if going forward or back.
let currentHistoryIndex = 0;
if (history.state) {
// we reloaded a page with history state
// (e.g. history navigation from non-transition page or browser reload)
currentHistoryIndex = history.state.index;
scrollTo({ left: history.state.scrollX, top: history.state.scrollY });
} else if (transitionEnabledOnThisPage()) {
history.replaceState({ index: currentHistoryIndex, scrollX, scrollY, intraPage: false }, '');
if (inBrowser) {
if (history.state) {
// we reloaded a page with history state
// (e.g. history navigation from non-transition page or browser reload)
currentHistoryIndex = history.state.index;
scrollTo({ left: history.state.scrollX, top: history.state.scrollY });
} else if (transitionEnabledOnThisPage()) {
history.replaceState({ index: currentHistoryIndex, scrollX, scrollY, intraPage: false }, '');
}
}
const throttle = (cb: (...args: any[]) => any, delay: number) => {
let wait = false;
// During the waiting time additional events are lost.
@ -336,6 +347,8 @@ async function transition(
toLocation = new URL(response.redirected);
}
parser ??= new DOMParser();
const newDocument = parser.parseFromString(response.html, response.mediaType);
// The next line might look like a hack,
// but it is actually necessary as noscript elements
@ -372,7 +385,22 @@ async function transition(
}
}
let navigateOnServerWarned = false;
export function navigate(href: string, options?: Options) {
if (inBrowser === false) {
if (!navigateOnServerWarned) {
// instantiate an error for the stacktrace to show to user.
const warning = new Error(
'The view transtions client API was called during a server side render. This may be unintentional as the navigate() function is expected to be called in response to user interactions. Please make sure that your usage is correct.'
);
warning.name = 'Warning';
console.warn(warning);
navigateOnServerWarned = true;
}
return;
}
// not ours
if (!transitionEnabledOnThisPage()) {
location.href = href;
@ -390,58 +418,61 @@ export function navigate(href: string, options?: Options) {
}
}
if (supportsViewTransitions || getFallback() !== 'none') {
addEventListener('popstate', (ev) => {
if (!transitionEnabledOnThisPage() && ev.state) {
// The current page doesn't have View Transitions enabled
// but the page we navigate to does (because it set the state).
// Do a full page refresh to reload the client-side router from the new page.
// Scroll restauration will then happen during the reload when the router's code is re-executed
if (history.scrollRestoration) {
history.scrollRestoration = 'manual';
}
location.reload();
return;
}
// History entries without state are created by the browser (e.g. for hash links)
// Our view transition entries always have state.
// Just ignore stateless entries.
// The browser will handle navigation fine without our help
if (ev.state === null) {
if (history.scrollRestoration) {
history.scrollRestoration = 'auto';
}
return;
}
// With the default "auto", the browser will jump to the old scroll position
// before the ViewTransition is complete.
function onPopState(ev: PopStateEvent) {
if (!transitionEnabledOnThisPage() && ev.state) {
// The current page doesn't have View Transitions enabled
// but the page we navigate to does (because it set the state).
// Do a full page refresh to reload the client-side router from the new page.
// Scroll restauration will then happen during the reload when the router's code is re-executed
if (history.scrollRestoration) {
history.scrollRestoration = 'manual';
}
location.reload();
return;
}
const state: State = history.state;
if (state.intraPage) {
// this is non transition intra-page scrolling
scrollTo(state.scrollX, state.scrollY);
} else {
const nextIndex = state.index;
const direction: Direction = nextIndex > currentHistoryIndex ? 'forward' : 'back';
currentHistoryIndex = nextIndex;
transition(direction, new URL(location.href), {}, state);
// History entries without state are created by the browser (e.g. for hash links)
// Our view transition entries always have state.
// Just ignore stateless entries.
// The browser will handle navigation fine without our help
if (ev.state === null) {
if (history.scrollRestoration) {
history.scrollRestoration = 'auto';
}
});
return;
}
addEventListener('load', onPageLoad);
// There's not a good way to record scroll position before a back button.
// So the way we do it is by listening to scrollend if supported, and if not continuously record the scroll position.
const updateState = () => {
persistState({ ...history.state, scrollX, scrollY });
};
// With the default "auto", the browser will jump to the old scroll position
// before the ViewTransition is complete.
if (history.scrollRestoration) {
history.scrollRestoration = 'manual';
}
if ('onscrollend' in window) addEventListener('scrollend', updateState);
else addEventListener('scroll', throttle(updateState, 300));
markScriptsExec();
const state: State = history.state;
if (state.intraPage) {
// this is non transition intra-page scrolling
scrollTo(state.scrollX, state.scrollY);
} else {
const nextIndex = state.index;
const direction: Direction = nextIndex > currentHistoryIndex ? 'forward' : 'back';
currentHistoryIndex = nextIndex;
transition(direction, new URL(location.href), {}, state);
}
}
if (inBrowser) {
if (supportsViewTransitions || getFallback() !== 'none') {
addEventListener('popstate', onPopState);
addEventListener('load', onPageLoad);
// There's not a good way to record scroll position before a back button.
// So the way we do it is by listening to scrollend if supported, and if not continuously record the scroll position.
const updateState = () => {
persistState({ ...history.state, scrollX, scrollY });
};
if ('onscrollend' in window) addEventListener('scrollend', updateState);
else addEventListener('scroll', throttle(updateState, 300));
markScriptsExec();
}
}

View file

@ -169,7 +169,7 @@ default: `false`
Whether or not to import `.wasm` files [directly as ES modules](https://github.com/WebAssembly/esm-integration/tree/main/proposals/esm-integration) using the `.wasm?module` import syntax.
Add `wasmModuleImports: true` to `astro.config.mjs` to enable this functionality in both the Cloudflare build and the Astro dev server. Read more about [using Wasm modules](#use-wasm-modules)
Add `wasmModuleImports: true` to `astro.config.mjs` to enable this functionality in both the Cloudflare build and the Astro dev server. Read more about [using Wasm modules](#use-wasm-modules).
```diff lang="js"
// astro.config.mjs
@ -221,7 +221,7 @@ Currently supported bindings:
- [Cloudflare Workers KV](https://developers.cloudflare.com/kv/)
- [Cloudflare Durable Objects](https://developers.cloudflare.com/durable-objects/)
You can access the runtime from Astro components through `Astro.locals` inside any .astro` file.
You can access the runtime from Astro components through `Astro.locals` inside any `.astro` file.
```astro
---

View file

@ -1,4 +1,8 @@
import type { Request as CFRequest, ExecutionContext } from '@cloudflare/workers-types';
import type {
Request as CFRequest,
CacheStorage,
ExecutionContext,
} from '@cloudflare/workers-types';
import type { SSRManifest } from 'astro';
import { App } from 'astro/app';
import { getProcessEnvProxy, isNode } from '../util.js';
@ -16,7 +20,7 @@ export interface AdvancedRuntime<T extends object = object> {
waitUntil: (promise: Promise<any>) => void;
env: Env & T;
cf: CFRequest['cf'];
caches: typeof caches;
caches: CacheStorage;
};
}
@ -50,7 +54,7 @@ export function createExports(manifest: SSRManifest) {
},
env: env,
cf: request.cf,
caches: caches,
caches: caches as unknown as CacheStorage,
},
};

View file

@ -1,4 +1,4 @@
import type { Request as CFRequest, EventContext } from '@cloudflare/workers-types';
import type { Request as CFRequest, CacheStorage, EventContext } from '@cloudflare/workers-types';
import type { SSRManifest } from 'astro';
import { App } from 'astro/app';
import { getProcessEnvProxy, isNode } from '../util.js';
@ -6,13 +6,12 @@ import { getProcessEnvProxy, isNode } from '../util.js';
if (!isNode) {
process.env = getProcessEnvProxy();
}
export interface DirectoryRuntime<T extends object = object> {
runtime: {
waitUntil: (promise: Promise<any>) => void;
env: EventContext<unknown, string, unknown>['env'] & T;
cf: CFRequest['cf'];
caches: typeof caches;
caches: CacheStorage;
};
}
@ -48,7 +47,7 @@ export function createExports(manifest: SSRManifest) {
},
env: context.env,
cf: request.cf,
caches: caches,
caches: caches as unknown as CacheStorage,
},
};

View file

@ -116,14 +116,14 @@ export default function mdx(partialMdxOptions: Partial<MdxOptions> = {}): AstroI
if (!id.endsWith('.mdx')) return;
// Read code from file manually to prevent Vite from parsing `import.meta.env` expressions
const { fileId } = getFileInfo(id, config);
const { fileId, fileUrl } = getFileInfo(id, config);
const code = await fs.readFile(fileId, 'utf-8');
const { data: frontmatter, content: pageContent } = parseFrontmatter(code, id);
const vfile = new VFile({ value: pageContent, path: id });
// Ensure `data.astro` is available to all remark plugins
setVfileFrontmatter(vfile, frontmatter, { filePath: id });
setVfileFrontmatter(vfile, frontmatter, { fileURL: new URL(fileUrl) });
try {
const compiled = await processor.process(vfile);

View file

@ -35,7 +35,7 @@ export function setVfileFrontmatter(
vfile.data ??= {};
vfile.data.astro ??= {};
(vfile.data.astro as any).frontmatter = frontmatter;
(vfile.data.astro as any).filePath = renderOpts?.filePath;
(vfile.data.astro as any).fileURL = renderOpts?.fileURL;
}
/**

View file

@ -124,8 +124,8 @@ export async function createMarkdownProcessor(
return {
async render(content, renderOpts) {
console.log('RENDER OPTS', renderOpts);
const vfile = new VFile({ value: content, path: renderOpts?.filePath });
console.log('url', renderOpts?.fileURL);
const vfile = new VFile({ value: content, path: renderOpts?.fileURL });
setVfileFrontmatter(vfile, renderOpts?.frontmatter ?? {}, renderOpts);
const result: MarkdownVFile = await parser.process(vfile).catch((err) => {
@ -171,7 +171,6 @@ export async function renderMarkdown(
const result = await processor.render(content, {
fileURL: opts.fileURL,
filePath: opts.filePath,
frontmatter: opts.frontmatter,
});

View file

@ -68,7 +68,6 @@ export interface MarkdownProcessor {
export interface MarkdownProcessorRenderOptions {
/** @internal */
fileURL?: URL;
filePath?: string;
/** Used for frontmatter injection plugins */
frontmatter?: Record<string, any>;
}