Compare commits
1 commit
main
...
fix-encodi
Author | SHA1 | Date | |
---|---|---|---|
|
9884738094 |
23 changed files with 166 additions and 179 deletions
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
'astro': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Fixed an issue where the transitions router did not work within framework components.
|
|
5
.changeset/olive-laws-work.md
Normal file
5
.changeset/olive-laws-work.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix markdown page encoding to be utf-8 by default (same as Astro 2)
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
'@astrojs/cloudflare': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fixes `AdvancedRuntime` & `DirectoryRuntime` types to work woth Cloudflare caches
|
|
|
@ -1,5 +0,0 @@
|
||||||
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>;
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
---
|
|
||||||
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>
|
|
|
@ -753,21 +753,6 @@ test.describe('View Transitions', () => {
|
||||||
await expect(p, 'should have content').toHaveText('Page 1');
|
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 }) => {
|
test('body inline scripts do not re-execute on navigation', async ({ page, astro }) => {
|
||||||
const errors = [];
|
const errors = [];
|
||||||
page.addListener('pageerror', (err) => {
|
page.addListener('pageerror', (err) => {
|
||||||
|
|
|
@ -30,7 +30,7 @@ type RawContentEvent = { name: ChokidarEvent; entry: string };
|
||||||
type ContentEvent = { name: ChokidarEvent; entry: URL };
|
type ContentEvent = { name: ChokidarEvent; entry: URL };
|
||||||
|
|
||||||
type DataEntryMetadata = Record<string, never>;
|
type DataEntryMetadata = Record<string, never>;
|
||||||
type ContentEntryMetadata = { slug: string, path: string };
|
type ContentEntryMetadata = { slug: string };
|
||||||
type CollectionEntryMap = {
|
type CollectionEntryMap = {
|
||||||
[collection: string]:
|
[collection: string]:
|
||||||
| {
|
| {
|
||||||
|
@ -276,7 +276,7 @@ export async function createContentTypesGenerator({
|
||||||
if (!(entryKey in collectionEntryMap[collectionKey].entries)) {
|
if (!(entryKey in collectionEntryMap[collectionKey].entries)) {
|
||||||
collectionEntryMap[collectionKey] = {
|
collectionEntryMap[collectionKey] = {
|
||||||
type: 'content',
|
type: 'content',
|
||||||
entries: { ...collectionInfo.entries, [entryKey]: { slug: addedSlug, path: event.entry.toString() } },
|
entries: { ...collectionInfo.entries, [entryKey]: { slug: addedSlug } },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { shouldGenerateTypes: true };
|
return { shouldGenerateTypes: true };
|
||||||
|
@ -453,15 +453,7 @@ async function writeContentFiles({
|
||||||
)}] }`;
|
)}] }`;
|
||||||
|
|
||||||
const slugType = JSON.stringify(entryMetadata.slug);
|
const slugType = JSON.stringify(entryMetadata.slug);
|
||||||
contentTypesStr += [
|
contentTypesStr += `${entryKey}: {\n id: ${entryKey};\n slug: ${slugType};\n body: string;\n collection: ${collectionKey};\n data: ${dataType}\n} & ${renderType};\n`;
|
||||||
`${entryKey}: {`,
|
|
||||||
` id: ${entryKey};`,
|
|
||||||
` slug: ${slugType};`,
|
|
||||||
` path: ${JSON.stringify(entryMetadata.path)};`,
|
|
||||||
` body: string;`,
|
|
||||||
` collection: ${collectionKey};`,
|
|
||||||
` data: ${dataType}`,
|
|
||||||
`} & ${renderType};`].join("\n");
|
|
||||||
}
|
}
|
||||||
contentTypesStr += `};\n`;
|
contentTypesStr += `};\n`;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -63,6 +63,11 @@ export async function renderPage(
|
||||||
body = encoder.encode(body);
|
body = encoder.encode(body);
|
||||||
headers.set('Content-Length', body.byteLength.toString());
|
headers.set('Content-Length', body.byteLength.toString());
|
||||||
}
|
}
|
||||||
|
// TODO: Revisit if user should manually set charset by themselves in Astro 4
|
||||||
|
// This code preserves the existing behaviour for markdown pages since Astro 2
|
||||||
|
if (route?.component.endsWith('.md')) {
|
||||||
|
headers.set('Content-Type', 'text/html; charset=utf-8');
|
||||||
|
}
|
||||||
const response = new Response(body, { ...init, headers });
|
const response = new Response(body, { ...init, headers });
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,14 +13,9 @@ type Events = 'astro:page-load' | 'astro:after-swap';
|
||||||
// only update history entries that are managed by us
|
// only update history entries that are managed by us
|
||||||
// leave other entries alone and do not accidently add state.
|
// leave other entries alone and do not accidently add state.
|
||||||
const persistState = (state: State) => history.state && history.replaceState(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 = () =>
|
export const transitionEnabledOnThisPage = () =>
|
||||||
inBrowser && !!document.querySelector('[name="astro-view-transitions-enabled"]');
|
!!document.querySelector('[name="astro-view-transitions-enabled"]');
|
||||||
|
|
||||||
const samePage = (otherLocation: URL) =>
|
const samePage = (otherLocation: URL) =>
|
||||||
location.pathname === otherLocation.pathname && location.search === otherLocation.search;
|
location.pathname === otherLocation.pathname && location.search === otherLocation.search;
|
||||||
const triggerEvent = (name: Events) => document.dispatchEvent(new Event(name));
|
const triggerEvent = (name: Events) => document.dispatchEvent(new Event(name));
|
||||||
|
@ -45,27 +40,21 @@ const announce = () => {
|
||||||
60
|
60
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const PERSIST_ATTR = 'data-astro-transition-persist';
|
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
|
// 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
|
||||||
// can use that to determine popstate if going forward or back.
|
// can use that to determine popstate if going forward or back.
|
||||||
let currentHistoryIndex = 0;
|
let currentHistoryIndex = 0;
|
||||||
|
if (history.state) {
|
||||||
if (inBrowser) {
|
// we reloaded a page with history state
|
||||||
if (history.state) {
|
// (e.g. history navigation from non-transition page or browser reload)
|
||||||
// we reloaded a page with history state
|
currentHistoryIndex = history.state.index;
|
||||||
// (e.g. history navigation from non-transition page or browser reload)
|
scrollTo({ left: history.state.scrollX, top: history.state.scrollY });
|
||||||
currentHistoryIndex = history.state.index;
|
} else if (transitionEnabledOnThisPage()) {
|
||||||
scrollTo({ left: history.state.scrollX, top: history.state.scrollY });
|
history.replaceState({ index: currentHistoryIndex, scrollX, scrollY, intraPage: false }, '');
|
||||||
} else if (transitionEnabledOnThisPage()) {
|
|
||||||
history.replaceState({ index: currentHistoryIndex, scrollX, scrollY, intraPage: false }, '');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const throttle = (cb: (...args: any[]) => any, delay: number) => {
|
const throttle = (cb: (...args: any[]) => any, delay: number) => {
|
||||||
let wait = false;
|
let wait = false;
|
||||||
// During the waiting time additional events are lost.
|
// During the waiting time additional events are lost.
|
||||||
|
@ -347,8 +336,6 @@ async function transition(
|
||||||
toLocation = new URL(response.redirected);
|
toLocation = new URL(response.redirected);
|
||||||
}
|
}
|
||||||
|
|
||||||
parser ??= new DOMParser();
|
|
||||||
|
|
||||||
const newDocument = parser.parseFromString(response.html, response.mediaType);
|
const newDocument = parser.parseFromString(response.html, response.mediaType);
|
||||||
// The next line might look like a hack,
|
// The next line might look like a hack,
|
||||||
// but it is actually necessary as noscript elements
|
// but it is actually necessary as noscript elements
|
||||||
|
@ -385,22 +372,7 @@ async function transition(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let navigateOnServerWarned = false;
|
|
||||||
|
|
||||||
export function navigate(href: string, options?: Options) {
|
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
|
// not ours
|
||||||
if (!transitionEnabledOnThisPage()) {
|
if (!transitionEnabledOnThisPage()) {
|
||||||
location.href = href;
|
location.href = href;
|
||||||
|
@ -418,61 +390,58 @@ export function navigate(href: string, options?: Options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPopState(ev: PopStateEvent) {
|
if (supportsViewTransitions || getFallback() !== 'none') {
|
||||||
if (!transitionEnabledOnThisPage() && ev.state) {
|
addEventListener('popstate', (ev) => {
|
||||||
// The current page doesn't have View Transitions enabled
|
if (!transitionEnabledOnThisPage() && ev.state) {
|
||||||
// but the page we navigate to does (because it set the state).
|
// The current page doesn't have View Transitions enabled
|
||||||
// Do a full page refresh to reload the client-side router from the new page.
|
// but the page we navigate to does (because it set the state).
|
||||||
// Scroll restauration will then happen during the reload when the router's code is re-executed
|
// 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.
|
||||||
if (history.scrollRestoration) {
|
if (history.scrollRestoration) {
|
||||||
history.scrollRestoration = 'manual';
|
history.scrollRestoration = 'manual';
|
||||||
}
|
}
|
||||||
location.reload();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// History entries without state are created by the browser (e.g. for hash links)
|
const state: State = history.state;
|
||||||
// Our view transition entries always have state.
|
if (state.intraPage) {
|
||||||
// Just ignore stateless entries.
|
// this is non transition intra-page scrolling
|
||||||
// The browser will handle navigation fine without our help
|
scrollTo(state.scrollX, state.scrollY);
|
||||||
if (ev.state === null) {
|
} else {
|
||||||
if (history.scrollRestoration) {
|
const nextIndex = state.index;
|
||||||
history.scrollRestoration = 'auto';
|
const direction: Direction = nextIndex > currentHistoryIndex ? 'forward' : 'back';
|
||||||
|
currentHistoryIndex = nextIndex;
|
||||||
|
transition(direction, new URL(location.href), {}, state);
|
||||||
}
|
}
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// With the default "auto", the browser will jump to the old scroll position
|
addEventListener('load', onPageLoad);
|
||||||
// before the ViewTransition is complete.
|
// There's not a good way to record scroll position before a back button.
|
||||||
if (history.scrollRestoration) {
|
// So the way we do it is by listening to scrollend if supported, and if not continuously record the scroll position.
|
||||||
history.scrollRestoration = 'manual';
|
const updateState = () => {
|
||||||
}
|
persistState({ ...history.state, scrollX, scrollY });
|
||||||
|
};
|
||||||
|
|
||||||
const state: State = history.state;
|
if ('onscrollend' in window) addEventListener('scrollend', updateState);
|
||||||
if (state.intraPage) {
|
else addEventListener('scroll', throttle(updateState, 300));
|
||||||
// this is non transition intra-page scrolling
|
|
||||||
scrollTo(state.scrollX, state.scrollY);
|
markScriptsExec();
|
||||||
} 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,23 +3,31 @@ import * as cheerio from 'cheerio';
|
||||||
import { loadFixture } from './test-utils.js';
|
import { loadFixture } from './test-utils.js';
|
||||||
|
|
||||||
describe('Astro basics', () => {
|
describe('Astro basics', () => {
|
||||||
|
/** @type {import('./test-utils').Fixture} */
|
||||||
let fixture;
|
let fixture;
|
||||||
let previewServer;
|
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
fixture = await loadFixture({
|
fixture = await loadFixture({
|
||||||
root: './fixtures/astro-basic/',
|
root: './fixtures/astro-basic/',
|
||||||
});
|
});
|
||||||
await fixture.build();
|
|
||||||
previewServer = await fixture.preview();
|
|
||||||
});
|
|
||||||
|
|
||||||
// important: close preview server (free up port and connection)
|
|
||||||
after(async () => {
|
|
||||||
await previewServer.stop();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('build', () => {
|
describe('build', () => {
|
||||||
|
let previewServer;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
fixture = await loadFixture({
|
||||||
|
root: './fixtures/astro-basic/',
|
||||||
|
});
|
||||||
|
await fixture.build();
|
||||||
|
previewServer = await fixture.preview();
|
||||||
|
});
|
||||||
|
|
||||||
|
// important: close preview server (free up port and connection)
|
||||||
|
after(async () => {
|
||||||
|
await previewServer.stop();
|
||||||
|
});
|
||||||
|
|
||||||
it('Can load page', async () => {
|
it('Can load page', async () => {
|
||||||
const html = await fixture.readFile(`/index.html`);
|
const html = await fixture.readFile(`/index.html`);
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerio.load(html);
|
||||||
|
@ -108,6 +116,18 @@ describe('Astro basics', () => {
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerio.load(html);
|
||||||
expect($('#rendered-order').text()).to.eq('Rendered order: A, B');
|
expect($('#rendered-order').text()).to.eq('Rendered order: A, B');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders markdown in utf-8 by default', async () => {
|
||||||
|
const html = await fixture.readFile('/chinese-encoding-md/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('h1').text()).to.equal('我的第一篇博客文章');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders MDX in utf-8 by default', async () => {
|
||||||
|
const html = await fixture.readFile('/chinese-encoding-mdx/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('h1').text()).to.equal('我的第一篇博客文章');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Supports void elements whose name is a string (#2062)', async () => {
|
it('Supports void elements whose name is a string (#2062)', async () => {
|
||||||
|
@ -143,4 +163,40 @@ describe('Astro basics', () => {
|
||||||
expect(result.status).to.equal(404);
|
expect(result.status).to.equal(404);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('development', () => {
|
||||||
|
/** @type {import('./test-utils').DevServer} */
|
||||||
|
let devServer;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
devServer = await fixture.startDevServer();
|
||||||
|
});
|
||||||
|
after(async () => {
|
||||||
|
await devServer.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders markdown in utf-8 by default', async () => {
|
||||||
|
const res = await fixture.fetch('/chinese-encoding-md');
|
||||||
|
expect(res.status).to.equal(200);
|
||||||
|
const html = await res.text();
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('h1').text()).to.equal('我的第一篇博客文章');
|
||||||
|
const isUtf8 =
|
||||||
|
res.headers.get('content-type').includes('charset=utf-8') ||
|
||||||
|
html.includes('<meta charset="utf-8">');
|
||||||
|
expect(isUtf8).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders MDX in utf-8 by default', async () => {
|
||||||
|
const res = await fixture.fetch('/chinese-encoding-mdx');
|
||||||
|
expect(res.status).to.equal(200);
|
||||||
|
const html = await res.text();
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('h1').text()).to.equal('我的第一篇博客文章');
|
||||||
|
const isUtf8 =
|
||||||
|
res.headers.get('content-type').includes('charset=utf-8') ||
|
||||||
|
html.includes('<meta charset="utf-8">');
|
||||||
|
expect(isUtf8).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { defineConfig } from 'astro/config';
|
import { defineConfig } from 'astro/config';
|
||||||
import preact from '@astrojs/preact';
|
import preact from '@astrojs/preact';
|
||||||
|
import mdx from '@astrojs/mdx';
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
integrations: [preact()],
|
integrations: [preact(), mdx()],
|
||||||
// make sure CLI flags have precedence
|
// make sure CLI flags have precedence
|
||||||
server: () => ({ port: 4321 })
|
server: () => ({ port: 4321 })
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@astrojs/mdx": "workspace:*",
|
||||||
"@astrojs/preact": "workspace:*",
|
"@astrojs/preact": "workspace:*",
|
||||||
"astro": "workspace:*",
|
"astro": "workspace:*",
|
||||||
"preact": "^10.17.1"
|
"preact": "^10.17.1"
|
||||||
|
|
3
packages/astro/test/fixtures/astro-basic/src/pages/chinese-encoding-md.md
vendored
Normal file
3
packages/astro/test/fixtures/astro-basic/src/pages/chinese-encoding-md.md
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# 我的第一篇博客文章
|
||||||
|
|
||||||
|
发表于:2022-07-01
|
3
packages/astro/test/fixtures/astro-basic/src/pages/chinese-encoding-mdx.mdx
vendored
Normal file
3
packages/astro/test/fixtures/astro-basic/src/pages/chinese-encoding-mdx.mdx
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# 我的第一篇博客文章
|
||||||
|
|
||||||
|
发表于:2022-07-01
|
|
@ -90,7 +90,7 @@ describe('core/render', () => {
|
||||||
|
|
||||||
const PageModule = createAstroModule(Page);
|
const PageModule = createAstroModule(Page);
|
||||||
const ctx = await createRenderContext({
|
const ctx = await createRenderContext({
|
||||||
route: { type: 'page', pathname: '/index' },
|
route: { type: 'page', pathname: '/index', component: 'src/pages/index.astro' },
|
||||||
request: new Request('http://example.com/'),
|
request: new Request('http://example.com/'),
|
||||||
links: [{ name: 'link', props: { rel: 'stylesheet', href: '/main.css' }, children: '' }],
|
links: [{ name: 'link', props: { rel: 'stylesheet', href: '/main.css' }, children: '' }],
|
||||||
mod: PageModule,
|
mod: PageModule,
|
||||||
|
@ -171,7 +171,7 @@ describe('core/render', () => {
|
||||||
|
|
||||||
const PageModule = createAstroModule(Page);
|
const PageModule = createAstroModule(Page);
|
||||||
const ctx = await createRenderContext({
|
const ctx = await createRenderContext({
|
||||||
route: { type: 'page', pathname: '/index' },
|
route: { type: 'page', pathname: '/index', component: 'src/pages/index.astro' },
|
||||||
request: new Request('http://example.com/'),
|
request: new Request('http://example.com/'),
|
||||||
links: [{ name: 'link', props: { rel: 'stylesheet', href: '/main.css' }, children: '' }],
|
links: [{ name: 'link', props: { rel: 'stylesheet', href: '/main.css' }, children: '' }],
|
||||||
env,
|
env,
|
||||||
|
@ -218,7 +218,7 @@ describe('core/render', () => {
|
||||||
|
|
||||||
const PageModule = createAstroModule(Page);
|
const PageModule = createAstroModule(Page);
|
||||||
const ctx = await createRenderContext({
|
const ctx = await createRenderContext({
|
||||||
route: { type: 'page', pathname: '/index' },
|
route: { type: 'page', pathname: '/index', component: 'src/pages/index.astro' },
|
||||||
request: new Request('http://example.com/'),
|
request: new Request('http://example.com/'),
|
||||||
links: [{ name: 'link', props: { rel: 'stylesheet', href: '/main.css' }, children: '' }],
|
links: [{ name: 'link', props: { rel: 'stylesheet', href: '/main.css' }, children: '' }],
|
||||||
env,
|
env,
|
||||||
|
|
|
@ -45,7 +45,7 @@ describe('core/render', () => {
|
||||||
|
|
||||||
const mod = createAstroModule(Page);
|
const mod = createAstroModule(Page);
|
||||||
const ctx = await createRenderContext({
|
const ctx = await createRenderContext({
|
||||||
route: { type: 'page', pathname: '/index' },
|
route: { type: 'page', pathname: '/index', component: 'src/pages/index.mdx' },
|
||||||
request: new Request('http://example.com/'),
|
request: new Request('http://example.com/'),
|
||||||
env,
|
env,
|
||||||
mod,
|
mod,
|
||||||
|
@ -91,7 +91,7 @@ describe('core/render', () => {
|
||||||
|
|
||||||
const mod = createAstroModule(Page);
|
const mod = createAstroModule(Page);
|
||||||
const ctx = await createRenderContext({
|
const ctx = await createRenderContext({
|
||||||
route: { type: 'page', pathname: '/index' },
|
route: { type: 'page', pathname: '/index', component: 'src/pages/index.mdx' },
|
||||||
request: new Request('http://example.com/'),
|
request: new Request('http://example.com/'),
|
||||||
env,
|
env,
|
||||||
mod,
|
mod,
|
||||||
|
@ -117,7 +117,7 @@ describe('core/render', () => {
|
||||||
|
|
||||||
const mod = createAstroModule(Page);
|
const mod = createAstroModule(Page);
|
||||||
const ctx = await createRenderContext({
|
const ctx = await createRenderContext({
|
||||||
route: { type: 'page', pathname: '/index' },
|
route: { type: 'page', pathname: '/index', component: 'src/pages/index.mdx' },
|
||||||
request: new Request('http://example.com/'),
|
request: new Request('http://example.com/'),
|
||||||
env,
|
env,
|
||||||
mod,
|
mod,
|
||||||
|
|
|
@ -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.
|
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"
|
```diff lang="js"
|
||||||
// astro.config.mjs
|
// astro.config.mjs
|
||||||
|
@ -221,7 +221,7 @@ Currently supported bindings:
|
||||||
- [Cloudflare Workers KV](https://developers.cloudflare.com/kv/)
|
- [Cloudflare Workers KV](https://developers.cloudflare.com/kv/)
|
||||||
- [Cloudflare Durable Objects](https://developers.cloudflare.com/durable-objects/)
|
- [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
|
```astro
|
||||||
---
|
---
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
import type {
|
import type { Request as CFRequest, ExecutionContext } from '@cloudflare/workers-types';
|
||||||
Request as CFRequest,
|
|
||||||
CacheStorage,
|
|
||||||
ExecutionContext,
|
|
||||||
} from '@cloudflare/workers-types';
|
|
||||||
import type { SSRManifest } from 'astro';
|
import type { SSRManifest } from 'astro';
|
||||||
import { App } from 'astro/app';
|
import { App } from 'astro/app';
|
||||||
import { getProcessEnvProxy, isNode } from '../util.js';
|
import { getProcessEnvProxy, isNode } from '../util.js';
|
||||||
|
@ -20,7 +16,7 @@ export interface AdvancedRuntime<T extends object = object> {
|
||||||
waitUntil: (promise: Promise<any>) => void;
|
waitUntil: (promise: Promise<any>) => void;
|
||||||
env: Env & T;
|
env: Env & T;
|
||||||
cf: CFRequest['cf'];
|
cf: CFRequest['cf'];
|
||||||
caches: CacheStorage;
|
caches: typeof caches;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +50,7 @@ export function createExports(manifest: SSRManifest) {
|
||||||
},
|
},
|
||||||
env: env,
|
env: env,
|
||||||
cf: request.cf,
|
cf: request.cf,
|
||||||
caches: caches as unknown as CacheStorage,
|
caches: caches,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { Request as CFRequest, CacheStorage, EventContext } from '@cloudflare/workers-types';
|
import type { Request as CFRequest, EventContext } from '@cloudflare/workers-types';
|
||||||
import type { SSRManifest } from 'astro';
|
import type { SSRManifest } from 'astro';
|
||||||
import { App } from 'astro/app';
|
import { App } from 'astro/app';
|
||||||
import { getProcessEnvProxy, isNode } from '../util.js';
|
import { getProcessEnvProxy, isNode } from '../util.js';
|
||||||
|
@ -6,12 +6,13 @@ import { getProcessEnvProxy, isNode } from '../util.js';
|
||||||
if (!isNode) {
|
if (!isNode) {
|
||||||
process.env = getProcessEnvProxy();
|
process.env = getProcessEnvProxy();
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DirectoryRuntime<T extends object = object> {
|
export interface DirectoryRuntime<T extends object = object> {
|
||||||
runtime: {
|
runtime: {
|
||||||
waitUntil: (promise: Promise<any>) => void;
|
waitUntil: (promise: Promise<any>) => void;
|
||||||
env: EventContext<unknown, string, unknown>['env'] & T;
|
env: EventContext<unknown, string, unknown>['env'] & T;
|
||||||
cf: CFRequest['cf'];
|
cf: CFRequest['cf'];
|
||||||
caches: CacheStorage;
|
caches: typeof caches;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@ export function createExports(manifest: SSRManifest) {
|
||||||
},
|
},
|
||||||
env: context.env,
|
env: context.env,
|
||||||
cf: request.cf,
|
cf: request.cf,
|
||||||
caches: caches as unknown as CacheStorage,
|
caches: caches,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -116,14 +116,14 @@ export default function mdx(partialMdxOptions: Partial<MdxOptions> = {}): AstroI
|
||||||
if (!id.endsWith('.mdx')) return;
|
if (!id.endsWith('.mdx')) return;
|
||||||
|
|
||||||
// Read code from file manually to prevent Vite from parsing `import.meta.env` expressions
|
// Read code from file manually to prevent Vite from parsing `import.meta.env` expressions
|
||||||
const { fileId, fileUrl } = getFileInfo(id, config);
|
const { fileId } = getFileInfo(id, config);
|
||||||
const code = await fs.readFile(fileId, 'utf-8');
|
const code = await fs.readFile(fileId, 'utf-8');
|
||||||
|
|
||||||
const { data: frontmatter, content: pageContent } = parseFrontmatter(code, id);
|
const { data: frontmatter, content: pageContent } = parseFrontmatter(code, id);
|
||||||
|
|
||||||
const vfile = new VFile({ value: pageContent, path: id });
|
const vfile = new VFile({ value: pageContent, path: id });
|
||||||
// Ensure `data.astro` is available to all remark plugins
|
// Ensure `data.astro` is available to all remark plugins
|
||||||
setVfileFrontmatter(vfile, frontmatter, { fileURL: new URL(fileUrl) });
|
setVfileFrontmatter(vfile, frontmatter);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const compiled = await processor.process(vfile);
|
const compiled = await processor.process(vfile);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { VFileData as Data, VFile } from 'vfile';
|
import type { VFileData as Data, VFile } from 'vfile';
|
||||||
import type { MarkdownAstroData, MarkdownProcessorRenderOptions } from './types.js';
|
import type { MarkdownAstroData } from './types.js';
|
||||||
|
|
||||||
function isValidAstroData(obj: unknown): obj is MarkdownAstroData {
|
function isValidAstroData(obj: unknown): obj is MarkdownAstroData {
|
||||||
if (typeof obj === 'object' && obj !== null && obj.hasOwnProperty('frontmatter')) {
|
if (typeof obj === 'object' && obj !== null && obj.hasOwnProperty('frontmatter')) {
|
||||||
|
@ -27,15 +27,10 @@ export function safelyGetAstroData(vfileData: Data): MarkdownAstroData | Invalid
|
||||||
return astro;
|
return astro;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setVfileFrontmatter(
|
export function setVfileFrontmatter(vfile: VFile, frontmatter: Record<string, any>) {
|
||||||
vfile: VFile,
|
|
||||||
frontmatter: Record<string, any>,
|
|
||||||
renderOpts: MarkdownProcessorRenderOptions | undefined
|
|
||||||
) {
|
|
||||||
vfile.data ??= {};
|
vfile.data ??= {};
|
||||||
vfile.data.astro ??= {};
|
vfile.data.astro ??= {};
|
||||||
(vfile.data.astro as any).frontmatter = frontmatter;
|
(vfile.data.astro as any).frontmatter = frontmatter;
|
||||||
(vfile.data.astro as any).fileURL = renderOpts?.fileURL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -124,9 +124,8 @@ export async function createMarkdownProcessor(
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async render(content, renderOpts) {
|
async render(content, renderOpts) {
|
||||||
console.log('url', renderOpts?.fileURL);
|
|
||||||
const vfile = new VFile({ value: content, path: renderOpts?.fileURL });
|
const vfile = new VFile({ value: content, path: renderOpts?.fileURL });
|
||||||
setVfileFrontmatter(vfile, renderOpts?.frontmatter ?? {}, renderOpts);
|
setVfileFrontmatter(vfile, renderOpts?.frontmatter ?? {});
|
||||||
|
|
||||||
const result: MarkdownVFile = await parser.process(vfile).catch((err) => {
|
const result: MarkdownVFile = await parser.process(vfile).catch((err) => {
|
||||||
// Ensure that the error message contains the input filename
|
// Ensure that the error message contains the input filename
|
||||||
|
|
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
|
@ -1747,6 +1747,9 @@ importers:
|
||||||
|
|
||||||
packages/astro/test/fixtures/astro-basic:
|
packages/astro/test/fixtures/astro-basic:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@astrojs/mdx':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../../integrations/mdx
|
||||||
'@astrojs/preact':
|
'@astrojs/preact':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../../../integrations/preact
|
version: link:../../../../integrations/preact
|
||||||
|
|
Loading…
Add table
Reference in a new issue