astro 3.1.4 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client.d.ts +7 -0
- package/components/ViewTransitions.astro +9 -431
- package/dist/assets/build/generate.js +2 -1
- package/dist/assets/{image-endpoint.d.ts → endpoint/generic.d.ts} +1 -1
- package/dist/assets/{image-endpoint.js → endpoint/generic.js} +2 -2
- package/dist/assets/endpoint/node.d.ts +5 -0
- package/dist/assets/endpoint/node.js +68 -0
- package/dist/assets/internal.d.ts +1 -1
- package/dist/assets/internal.js +2 -2
- package/dist/assets/vite-plugin-assets.js +6 -0
- package/dist/content/types-generator.js +2 -1
- package/dist/core/build/index.js +1 -1
- package/dist/core/build/static-build.js +15 -4
- package/dist/core/constants.js +1 -1
- package/dist/core/create-vite.js +0 -1
- package/dist/core/dev/container.js +1 -1
- package/dist/core/dev/dev.js +1 -1
- package/dist/core/errors/errors-data.js +1 -1
- package/dist/core/messages.js +2 -2
- package/dist/core/render/params-and-props.js +4 -0
- package/dist/integrations/index.js +2 -1
- package/dist/runtime/client/hmr.js +0 -34
- package/dist/runtime/server/astro-island.js +13 -4
- package/dist/runtime/server/astro-island.prebuilt.d.ts +1 -1
- package/dist/runtime/server/astro-island.prebuilt.js +1 -1
- package/dist/runtime/server/render/component.js +1 -0
- package/dist/transitions/router.d.ts +8 -0
- package/dist/transitions/router.js +326 -0
- package/dist/transitions/vite-plugin-transitions.js +10 -0
- package/dist/vite-plugin-astro-server/css.js +1 -1
- package/dist/vite-plugin-astro-server/route.js +17 -24
- package/dist/vite-plugin-markdown/images.d.ts +6 -0
- package/dist/vite-plugin-markdown/images.js +31 -0
- package/dist/vite-plugin-markdown/index.js +6 -23
- package/package.json +4 -3
package/client.d.ts
CHANGED
|
@@ -120,6 +120,13 @@ declare module 'astro:transitions' {
|
|
|
120
120
|
export const ViewTransitions: ViewTransitionsModule['default'];
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
declare module 'astro:transitions/client' {
|
|
124
|
+
type TransitionRouterModule = typeof import('./dist/transitions/router.js');
|
|
125
|
+
export const supportsViewTransitions: TransitionRouterModule['supportsViewTransitions'];
|
|
126
|
+
export const transitionEnabledOnThisPage: TransitionRouterModule['transitionEnabledOnThisPage'];
|
|
127
|
+
export const navigate: TransitionRouterModule['navigate'];
|
|
128
|
+
}
|
|
129
|
+
|
|
123
130
|
declare module 'astro:middleware' {
|
|
124
131
|
export * from 'astro/middleware/namespace';
|
|
125
132
|
}
|
|
@@ -11,91 +11,12 @@ const { fallback = 'animate' } = Astro.props as Props;
|
|
|
11
11
|
<meta name="astro-view-transitions-enabled" content="true" />
|
|
12
12
|
<meta name="astro-view-transitions-fallback" content={fallback} />
|
|
13
13
|
<script>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
intraPage?: boolean;
|
|
21
|
-
};
|
|
22
|
-
type Events = 'astro:page-load' | 'astro:after-swap';
|
|
23
|
-
|
|
24
|
-
// only update history entries that are managed by us
|
|
25
|
-
// leave other entries alone and do not accidently add state.
|
|
26
|
-
const persistState = (state: State) => history.state && history.replaceState(state, '');
|
|
27
|
-
// @ts-expect-error: startViewTransition might exist
|
|
28
|
-
const supportsViewTransitions = !!document.startViewTransition;
|
|
29
|
-
const transitionEnabledOnThisPage = () =>
|
|
30
|
-
!!document.querySelector('[name="astro-view-transitions-enabled"]');
|
|
31
|
-
const triggerEvent = (name: Events) => document.dispatchEvent(new Event(name));
|
|
32
|
-
const onPageLoad = () => triggerEvent('astro:page-load');
|
|
33
|
-
const PERSIST_ATTR = 'data-astro-transition-persist';
|
|
34
|
-
const parser = new DOMParser();
|
|
35
|
-
// explained at its usage
|
|
36
|
-
let noopEl: HTMLDivElement;
|
|
37
|
-
if (import.meta.env.DEV) {
|
|
38
|
-
noopEl = document.createElement('div');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// The History API does not tell you if navigation is forward or back, so
|
|
42
|
-
// you can figure it using an index. On pushState the index is incremented so you
|
|
43
|
-
// can use that to determine popstate if going forward or back.
|
|
44
|
-
let currentHistoryIndex = 0;
|
|
45
|
-
if (history.state) {
|
|
46
|
-
// we reloaded a page with history state
|
|
47
|
-
// (e.g. history navigation from non-transition page or browser reload)
|
|
48
|
-
currentHistoryIndex = history.state.index;
|
|
49
|
-
scrollTo({ left: history.state.scrollX, top: history.state.scrollY });
|
|
50
|
-
} else if (transitionEnabledOnThisPage()) {
|
|
51
|
-
history.replaceState({ index: currentHistoryIndex, scrollX, scrollY, intraPage: false }, '');
|
|
52
|
-
}
|
|
53
|
-
const throttle = (cb: (...args: any[]) => any, delay: number) => {
|
|
54
|
-
let wait = false;
|
|
55
|
-
// During the waiting time additional events are lost.
|
|
56
|
-
// So repeat the callback at the end if we have swallowed events.
|
|
57
|
-
let onceMore = false;
|
|
58
|
-
return (...args: any[]) => {
|
|
59
|
-
if (wait) {
|
|
60
|
-
onceMore = true;
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
cb(...args);
|
|
64
|
-
wait = true;
|
|
65
|
-
setTimeout(() => {
|
|
66
|
-
if (onceMore) {
|
|
67
|
-
onceMore = false;
|
|
68
|
-
cb(...args);
|
|
69
|
-
}
|
|
70
|
-
wait = false;
|
|
71
|
-
}, delay);
|
|
72
|
-
};
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
// returns the contents of the page or null if the router can't deal with it.
|
|
76
|
-
async function fetchHTML(
|
|
77
|
-
href: string
|
|
78
|
-
): Promise<null | { html: string; redirected?: string; mediaType: DOMParserSupportedType }> {
|
|
79
|
-
try {
|
|
80
|
-
const res = await fetch(href);
|
|
81
|
-
// drop potential charset (+ other name/value pairs) as parser needs the mediaType
|
|
82
|
-
const mediaType = res.headers.get('content-type')?.replace(/;.*$/, '');
|
|
83
|
-
// the DOMParser can handle two types of HTML
|
|
84
|
-
if (mediaType !== 'text/html' && mediaType !== 'application/xhtml+xml') {
|
|
85
|
-
// everything else (e.g. audio/mp3) will be handled by the browser but not by us
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
const html = await res.text();
|
|
89
|
-
return {
|
|
90
|
-
html,
|
|
91
|
-
redirected: res.redirected ? res.url : undefined,
|
|
92
|
-
mediaType,
|
|
93
|
-
};
|
|
94
|
-
} catch (err) {
|
|
95
|
-
// can't fetch, let someone else deal with it.
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
14
|
+
import {
|
|
15
|
+
supportsViewTransitions,
|
|
16
|
+
transitionEnabledOnThisPage,
|
|
17
|
+
navigate,
|
|
18
|
+
} from 'astro:transitions/client';
|
|
19
|
+
export type Fallback = 'none' | 'animate' | 'swap';
|
|
99
20
|
|
|
100
21
|
function getFallback(): Fallback {
|
|
101
22
|
const el = document.querySelector('[name="astro-view-transitions-fallback"]');
|
|
@@ -105,263 +26,6 @@ const { fallback = 'animate' } = Astro.props as Props;
|
|
|
105
26
|
return 'animate';
|
|
106
27
|
}
|
|
107
28
|
|
|
108
|
-
function markScriptsExec() {
|
|
109
|
-
for (const script of document.scripts) {
|
|
110
|
-
script.dataset.astroExec = '';
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function runScripts() {
|
|
115
|
-
let wait = Promise.resolve();
|
|
116
|
-
for (const script of Array.from(document.scripts)) {
|
|
117
|
-
if (script.dataset.astroExec === '') continue;
|
|
118
|
-
const newScript = document.createElement('script');
|
|
119
|
-
newScript.innerHTML = script.innerHTML;
|
|
120
|
-
for (const attr of script.attributes) {
|
|
121
|
-
if (attr.name === 'src') {
|
|
122
|
-
const p = new Promise((r) => {
|
|
123
|
-
newScript.onload = r;
|
|
124
|
-
});
|
|
125
|
-
wait = wait.then(() => p as any);
|
|
126
|
-
}
|
|
127
|
-
newScript.setAttribute(attr.name, attr.value);
|
|
128
|
-
}
|
|
129
|
-
newScript.dataset.astroExec = '';
|
|
130
|
-
script.replaceWith(newScript);
|
|
131
|
-
}
|
|
132
|
-
return wait;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function isInfinite(animation: Animation) {
|
|
136
|
-
const effect = animation.effect;
|
|
137
|
-
if (!effect || !(effect instanceof KeyframeEffect) || !effect.target) return false;
|
|
138
|
-
const style = window.getComputedStyle(effect.target, effect.pseudoElement);
|
|
139
|
-
return style.animationIterationCount === 'infinite';
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const updateHistoryAndScrollPosition = (toLocation) => {
|
|
143
|
-
if (toLocation.href !== location.href) {
|
|
144
|
-
history.pushState(
|
|
145
|
-
{ index: ++currentHistoryIndex, scrollX: 0, scrollY: 0 },
|
|
146
|
-
'',
|
|
147
|
-
toLocation.href
|
|
148
|
-
);
|
|
149
|
-
// now we are on the new page for non-history navigations!
|
|
150
|
-
// (with history navigation page change happens before popstate is fired)
|
|
151
|
-
}
|
|
152
|
-
// freshly loaded pages start from the top
|
|
153
|
-
scrollTo({ left: 0, top: 0, behavior: 'instant' });
|
|
154
|
-
|
|
155
|
-
if (toLocation.hash) {
|
|
156
|
-
// because we are already on the target page ...
|
|
157
|
-
// ... what comes next is a intra-page navigation
|
|
158
|
-
// that won't reload the page but instead scroll to the fragment
|
|
159
|
-
location.href = toLocation.href;
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
// replace head and body of the windows document with contents from newDocument
|
|
164
|
-
// if !popstate, update the history entry and scroll position according to toLocation
|
|
165
|
-
// if popState is given, this holds the scroll position for history navigation
|
|
166
|
-
// if fallback === "animate" then simulate view transitions
|
|
167
|
-
async function updateDOM(
|
|
168
|
-
newDocument: Document,
|
|
169
|
-
toLocation: URL,
|
|
170
|
-
popState?: State,
|
|
171
|
-
fallback?: Fallback
|
|
172
|
-
) {
|
|
173
|
-
// Check for a head element that should persist and returns it,
|
|
174
|
-
// either because it has the data attribute or is a link el.
|
|
175
|
-
const persistedHeadElement = (el: HTMLElement): Element | null => {
|
|
176
|
-
const id = el.getAttribute(PERSIST_ATTR);
|
|
177
|
-
const newEl = id && newDocument.head.querySelector(`[${PERSIST_ATTR}="${id}"]`);
|
|
178
|
-
if (newEl) {
|
|
179
|
-
return newEl;
|
|
180
|
-
}
|
|
181
|
-
if (el.matches('link[rel=stylesheet]')) {
|
|
182
|
-
const href = el.getAttribute('href');
|
|
183
|
-
return newDocument.head.querySelector(`link[rel=stylesheet][href="${href}"]`);
|
|
184
|
-
}
|
|
185
|
-
// What follows is a fix for an issue (#8472) with missing client:only styles after transition.
|
|
186
|
-
// That problem exists only in dev mode where styles are injected into the page by Vite.
|
|
187
|
-
// Returning a noop element ensures that the styles are not removed from the old document.
|
|
188
|
-
// Guarding the code below with the dev mode check
|
|
189
|
-
// allows tree shaking to remove this code in production.
|
|
190
|
-
if (import.meta.env.DEV) {
|
|
191
|
-
if (el.tagName === 'STYLE' && el.dataset.viteDevId) {
|
|
192
|
-
const devId = el.dataset.viteDevId;
|
|
193
|
-
// If this same style tag exists, remove it from the new page
|
|
194
|
-
return (
|
|
195
|
-
newDocument.querySelector(`style[data-astro-dev-id="${devId}"]`) ||
|
|
196
|
-
// Otherwise, keep it anyways. This is client:only styles.
|
|
197
|
-
noopEl
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
return null;
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
const swap = () => {
|
|
205
|
-
// swap attributes of the html element
|
|
206
|
-
// - delete all attributes from the current document
|
|
207
|
-
// - insert all attributes from doc
|
|
208
|
-
// - reinsert all original attributes that are named 'data-astro-*'
|
|
209
|
-
const html = document.documentElement;
|
|
210
|
-
const astro = [...html.attributes].filter(
|
|
211
|
-
({ name }) => (html.removeAttribute(name), name.startsWith('data-astro-'))
|
|
212
|
-
);
|
|
213
|
-
[...newDocument.documentElement.attributes, ...astro].forEach(({ name, value }) =>
|
|
214
|
-
html.setAttribute(name, value)
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
// Replace scripts in both the head and body.
|
|
218
|
-
for (const s1 of document.scripts) {
|
|
219
|
-
for (const s2 of newDocument.scripts) {
|
|
220
|
-
if (
|
|
221
|
-
// Inline
|
|
222
|
-
(!s1.src && s1.textContent === s2.textContent) ||
|
|
223
|
-
// External
|
|
224
|
-
(s1.src && s1.type === s2.type && s1.src === s2.src)
|
|
225
|
-
) {
|
|
226
|
-
// the old script is in the new document: we mark it as executed to prevent re-execution
|
|
227
|
-
s2.dataset.astroExec = '';
|
|
228
|
-
break;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Swap head
|
|
234
|
-
for (const el of Array.from(document.head.children)) {
|
|
235
|
-
const newEl = persistedHeadElement(el as HTMLElement);
|
|
236
|
-
// If the element exists in the document already, remove it
|
|
237
|
-
// from the new document and leave the current node alone
|
|
238
|
-
if (newEl) {
|
|
239
|
-
newEl.remove();
|
|
240
|
-
} else {
|
|
241
|
-
// Otherwise remove the element in the head. It doesn't exist in the new page.
|
|
242
|
-
el.remove();
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Everything left in the new head is new, append it all.
|
|
247
|
-
document.head.append(...newDocument.head.children);
|
|
248
|
-
|
|
249
|
-
// Persist elements in the existing body
|
|
250
|
-
const oldBody = document.body;
|
|
251
|
-
|
|
252
|
-
// this will reset scroll Position
|
|
253
|
-
document.body.replaceWith(newDocument.body);
|
|
254
|
-
for (const el of oldBody.querySelectorAll(`[${PERSIST_ATTR}]`)) {
|
|
255
|
-
const id = el.getAttribute(PERSIST_ATTR);
|
|
256
|
-
const newEl = document.querySelector(`[${PERSIST_ATTR}="${id}"]`);
|
|
257
|
-
if (newEl) {
|
|
258
|
-
// The element exists in the new page, replace it with the element
|
|
259
|
-
// from the old page so that state is preserved.
|
|
260
|
-
newEl.replaceWith(el);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (popState) {
|
|
265
|
-
scrollTo(popState.scrollX, popState.scrollY); // usings 'auto' scrollBehavior
|
|
266
|
-
} else {
|
|
267
|
-
updateHistoryAndScrollPosition(toLocation);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
triggerEvent('astro:after-swap');
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
// Wait on links to finish, to prevent FOUC
|
|
274
|
-
const links: Promise<any>[] = [];
|
|
275
|
-
for (const el of newDocument.querySelectorAll('head link[rel=stylesheet]')) {
|
|
276
|
-
// Do not preload links that are already on the page.
|
|
277
|
-
if (
|
|
278
|
-
!document.querySelector(
|
|
279
|
-
`[${PERSIST_ATTR}="${el.getAttribute(PERSIST_ATTR)}"], link[rel=stylesheet]`
|
|
280
|
-
)
|
|
281
|
-
) {
|
|
282
|
-
const c = document.createElement('link');
|
|
283
|
-
c.setAttribute('rel', 'preload');
|
|
284
|
-
c.setAttribute('as', 'style');
|
|
285
|
-
c.setAttribute('href', el.getAttribute('href')!);
|
|
286
|
-
links.push(
|
|
287
|
-
new Promise<any>((resolve) => {
|
|
288
|
-
['load', 'error'].forEach((evName) => c.addEventListener(evName, resolve));
|
|
289
|
-
document.head.append(c);
|
|
290
|
-
})
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
links.length && (await Promise.all(links));
|
|
295
|
-
|
|
296
|
-
if (fallback === 'animate') {
|
|
297
|
-
// Trigger the animations
|
|
298
|
-
const currentAnimations = document.getAnimations();
|
|
299
|
-
document.documentElement.dataset.astroTransitionFallback = 'old';
|
|
300
|
-
const newAnimations = document
|
|
301
|
-
.getAnimations()
|
|
302
|
-
.filter((a) => !currentAnimations.includes(a) && !isInfinite(a));
|
|
303
|
-
const finished = Promise.all(newAnimations.map((a) => a.finished));
|
|
304
|
-
const fallbackSwap = () => {
|
|
305
|
-
swap();
|
|
306
|
-
document.documentElement.dataset.astroTransitionFallback = 'new';
|
|
307
|
-
};
|
|
308
|
-
await finished;
|
|
309
|
-
fallbackSwap();
|
|
310
|
-
} else {
|
|
311
|
-
swap();
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
async function transition(direction: Direction, toLocation: URL, popState?: State) {
|
|
316
|
-
let finished: Promise<void>;
|
|
317
|
-
const href = toLocation.href;
|
|
318
|
-
const response = await fetchHTML(href);
|
|
319
|
-
// If there is a problem fetching the new page, just do an MPA navigation to it.
|
|
320
|
-
if (response === null) {
|
|
321
|
-
location.href = href;
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
// if there was a redirection, show the final URL in the browser's address bar
|
|
325
|
-
if (response.redirected) {
|
|
326
|
-
toLocation = new URL(response.redirected);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const newDocument = parser.parseFromString(response.html, response.mediaType);
|
|
330
|
-
// The next line might look like a hack,
|
|
331
|
-
// but it is actually necessary as noscript elements
|
|
332
|
-
// and their contents are returned as markup by the parser,
|
|
333
|
-
// see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString
|
|
334
|
-
newDocument.querySelectorAll('noscript').forEach((el) => el.remove());
|
|
335
|
-
|
|
336
|
-
if (!newDocument.querySelector('[name="astro-view-transitions-enabled"]')) {
|
|
337
|
-
location.href = href;
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
if (!popState) {
|
|
342
|
-
// save the current scroll position before we change the DOM and transition to the new page
|
|
343
|
-
history.replaceState({ ...history.state, scrollX, scrollY }, '');
|
|
344
|
-
}
|
|
345
|
-
document.documentElement.dataset.astroTransition = direction;
|
|
346
|
-
if (supportsViewTransitions) {
|
|
347
|
-
// @ts-expect-error: startViewTransition exist
|
|
348
|
-
finished = document.startViewTransition(() =>
|
|
349
|
-
updateDOM(newDocument, toLocation, popState)
|
|
350
|
-
).finished;
|
|
351
|
-
} else {
|
|
352
|
-
finished = updateDOM(newDocument, toLocation, popState, getFallback());
|
|
353
|
-
}
|
|
354
|
-
try {
|
|
355
|
-
await finished;
|
|
356
|
-
} finally {
|
|
357
|
-
// skip this for the moment as it tends to stop fallback animations
|
|
358
|
-
// document.documentElement.removeAttribute('data-astro-transition');
|
|
359
|
-
await runScripts();
|
|
360
|
-
markScriptsExec();
|
|
361
|
-
onPageLoad();
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
29
|
// Prefetching
|
|
366
30
|
function maybePrefetch(pathname: string) {
|
|
367
31
|
if (document.querySelector(`link[rel=prefetch][href="${pathname}"]`)) return;
|
|
@@ -406,83 +70,9 @@ const { fallback = 'animate' } = Astro.props as Props;
|
|
|
406
70
|
return;
|
|
407
71
|
}
|
|
408
72
|
ev.preventDefault();
|
|
409
|
-
navigate(link.href
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
function navigate(href) {
|
|
413
|
-
// not ours
|
|
414
|
-
if (!transitionEnabledOnThisPage()) {
|
|
415
|
-
location.href = href;
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
const toLocation = new URL(href, location.href);
|
|
419
|
-
// We do not have page transitions on navigations to the same page (intra-page navigation)
|
|
420
|
-
// but we want to handle prevent reload on navigation to the same page
|
|
421
|
-
// Same page means same origin, path and query params (but maybe different hash)
|
|
422
|
-
if (
|
|
423
|
-
location.origin === toLocation.origin &&
|
|
424
|
-
location.pathname === toLocation.pathname &&
|
|
425
|
-
location.search === toLocation.search
|
|
426
|
-
) {
|
|
427
|
-
// mark current position as non transition intra-page scrolling
|
|
428
|
-
if (location.href !== toLocation.href) {
|
|
429
|
-
history.replaceState({ ...history.state, intraPage: true }, '');
|
|
430
|
-
history.pushState(
|
|
431
|
-
{ index: ++currentHistoryIndex, scrollX: 0, scrollY: 0 },
|
|
432
|
-
'',
|
|
433
|
-
toLocation.href
|
|
434
|
-
);
|
|
435
|
-
}
|
|
436
|
-
if (toLocation.hash) {
|
|
437
|
-
location.href = toLocation.href;
|
|
438
|
-
} else {
|
|
439
|
-
scrollTo({ left: 0, top: 0, behavior: 'instant' });
|
|
440
|
-
}
|
|
441
|
-
} else {
|
|
442
|
-
transition('forward', toLocation);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
addEventListener('popstate', (ev) => {
|
|
447
|
-
if (!transitionEnabledOnThisPage() && ev.state) {
|
|
448
|
-
// The current page doesn't have View Transitions enabled
|
|
449
|
-
// but the page we navigate to does (because it set the state).
|
|
450
|
-
// Do a full page refresh to reload the client-side router from the new page.
|
|
451
|
-
// Scroll restauration will then happen during the reload when the router's code is re-executed
|
|
452
|
-
if (history.scrollRestoration) {
|
|
453
|
-
history.scrollRestoration = 'manual';
|
|
454
|
-
}
|
|
455
|
-
location.reload();
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// History entries without state are created by the browser (e.g. for hash links)
|
|
460
|
-
// Our view transition entries always have state.
|
|
461
|
-
// Just ignore stateless entries.
|
|
462
|
-
// The browser will handle navigation fine without our help
|
|
463
|
-
if (ev.state === null) {
|
|
464
|
-
if (history.scrollRestoration) {
|
|
465
|
-
history.scrollRestoration = 'auto';
|
|
466
|
-
}
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// With the default "auto", the browser will jump to the old scroll position
|
|
471
|
-
// before the ViewTransition is complete.
|
|
472
|
-
if (history.scrollRestoration) {
|
|
473
|
-
history.scrollRestoration = 'manual';
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const state: State = history.state;
|
|
477
|
-
if (state.intraPage) {
|
|
478
|
-
// this is non transition intra-page scrolling
|
|
479
|
-
scrollTo(state.scrollX, state.scrollY);
|
|
480
|
-
} else {
|
|
481
|
-
const nextIndex = state.index;
|
|
482
|
-
const direction: Direction = nextIndex > currentHistoryIndex ? 'forward' : 'back';
|
|
483
|
-
currentHistoryIndex = nextIndex;
|
|
484
|
-
transition(direction, new URL(location.href), state);
|
|
485
|
-
}
|
|
73
|
+
navigate(link.href, {
|
|
74
|
+
history: link.dataset.astroHistory === 'replace' ? 'replace' : 'auto',
|
|
75
|
+
});
|
|
486
76
|
});
|
|
487
77
|
|
|
488
78
|
['mouseenter', 'touchstart', 'focus'].forEach((evName) => {
|
|
@@ -503,17 +93,5 @@ const { fallback = 'animate' } = Astro.props as Props;
|
|
|
503
93
|
{ passive: true, capture: true }
|
|
504
94
|
);
|
|
505
95
|
});
|
|
506
|
-
|
|
507
|
-
addEventListener('load', onPageLoad);
|
|
508
|
-
// There's not a good way to record scroll position before a back button.
|
|
509
|
-
// So the way we do it is by listening to scrollend if supported, and if not continuously record the scroll position.
|
|
510
|
-
const updateState = () => {
|
|
511
|
-
persistState({ ...history.state, scrollX, scrollY });
|
|
512
|
-
};
|
|
513
|
-
|
|
514
|
-
if ('onscrollend' in window) addEventListener('scrollend', updateState);
|
|
515
|
-
else addEventListener('scroll', throttle(updateState, 300));
|
|
516
|
-
|
|
517
|
-
markScriptsExec();
|
|
518
96
|
}
|
|
519
97
|
</script>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs, { readFileSync } from "node:fs";
|
|
2
2
|
import { basename, join } from "node:path/posix";
|
|
3
|
+
import { getOutDirWithinCwd } from "../../core/build/common.js";
|
|
3
4
|
import { prependForwardSlash } from "../../core/path.js";
|
|
4
5
|
import { isServerLikeOutput } from "../../prerender/utils.js";
|
|
5
6
|
import { getConfiguredImageService, isESMImportedImage } from "../internal.js";
|
|
@@ -23,7 +24,7 @@ async function generateImage(pipeline, options, filepath) {
|
|
|
23
24
|
serverRoot = config.build.server;
|
|
24
25
|
clientRoot = config.build.client;
|
|
25
26
|
} else {
|
|
26
|
-
serverRoot = config.outDir;
|
|
27
|
+
serverRoot = getOutDirWithinCwd(config.outDir);
|
|
27
28
|
clientRoot = config.outDir;
|
|
28
29
|
}
|
|
29
30
|
const isLocalImage = isESMImportedImage(options.src);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isRemotePath } from "@astrojs/internal-helpers/path";
|
|
2
2
|
import mime from "mime/lite.js";
|
|
3
|
-
import { getConfiguredImageService, isRemoteAllowed } from "
|
|
4
|
-
import { etag } from "
|
|
3
|
+
import { getConfiguredImageService, isRemoteAllowed } from "../internal.js";
|
|
4
|
+
import { etag } from "../utils/etag.js";
|
|
5
5
|
import { imageConfig } from "astro:assets";
|
|
6
6
|
async function loadRemoteImage(src) {
|
|
7
7
|
try {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { isRemotePath, removeQueryString } from "@astrojs/internal-helpers/path";
|
|
2
|
+
import { readFile } from "fs/promises";
|
|
3
|
+
import mime from "mime/lite.js";
|
|
4
|
+
import { getConfiguredImageService, isRemoteAllowed } from "../internal.js";
|
|
5
|
+
import { etag } from "../utils/etag.js";
|
|
6
|
+
import { assetsDir, imageConfig } from "astro:assets";
|
|
7
|
+
async function loadLocalImage(src, url) {
|
|
8
|
+
const filePath = import.meta.env.DEV ? removeQueryString(src.slice("/@fs".length)) : new URL("." + src, assetsDir);
|
|
9
|
+
let buffer = void 0;
|
|
10
|
+
try {
|
|
11
|
+
buffer = await readFile(filePath);
|
|
12
|
+
} catch (e) {
|
|
13
|
+
const sourceUrl = new URL(src, url.origin);
|
|
14
|
+
buffer = await loadRemoteImage(sourceUrl);
|
|
15
|
+
}
|
|
16
|
+
return buffer;
|
|
17
|
+
}
|
|
18
|
+
async function loadRemoteImage(src) {
|
|
19
|
+
try {
|
|
20
|
+
const res = await fetch(src);
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
return void 0;
|
|
23
|
+
}
|
|
24
|
+
return Buffer.from(await res.arrayBuffer());
|
|
25
|
+
} catch (err) {
|
|
26
|
+
return void 0;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const GET = async ({ request }) => {
|
|
30
|
+
try {
|
|
31
|
+
const imageService = await getConfiguredImageService();
|
|
32
|
+
if (!("transform" in imageService)) {
|
|
33
|
+
throw new Error("Configured image service is not a local service");
|
|
34
|
+
}
|
|
35
|
+
const url = new URL(request.url);
|
|
36
|
+
const transform = await imageService.parseURL(url, imageConfig);
|
|
37
|
+
if (!transform?.src) {
|
|
38
|
+
throw new Error("Incorrect transform returned by `parseURL`");
|
|
39
|
+
}
|
|
40
|
+
let inputBuffer = void 0;
|
|
41
|
+
if (isRemotePath(transform.src)) {
|
|
42
|
+
if (isRemoteAllowed(transform.src, imageConfig) === false) {
|
|
43
|
+
return new Response("Forbidden", { status: 403 });
|
|
44
|
+
}
|
|
45
|
+
inputBuffer = await loadRemoteImage(new URL(transform.src));
|
|
46
|
+
} else {
|
|
47
|
+
inputBuffer = await loadLocalImage(transform.src, url);
|
|
48
|
+
}
|
|
49
|
+
if (!inputBuffer) {
|
|
50
|
+
return new Response("Not Found", { status: 404 });
|
|
51
|
+
}
|
|
52
|
+
const { data, format } = await imageService.transform(inputBuffer, transform, imageConfig);
|
|
53
|
+
return new Response(data, {
|
|
54
|
+
status: 200,
|
|
55
|
+
headers: {
|
|
56
|
+
"Content-Type": mime.getType(format) ?? `image/${format}`,
|
|
57
|
+
"Cache-Control": "public, max-age=31536000",
|
|
58
|
+
ETag: etag(data.toString()),
|
|
59
|
+
Date: (/* @__PURE__ */ new Date()).toUTCString()
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
} catch (err) {
|
|
63
|
+
return new Response(`Server Error: ${err}`, { status: 500 });
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
export {
|
|
67
|
+
GET
|
|
68
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AstroConfig, AstroSettings } from '../@types/astro.js';
|
|
2
2
|
import { type ImageService } from './services/service.js';
|
|
3
3
|
import type { GetImageResult, ImageMetadata, UnresolvedImageTransform } from './types.js';
|
|
4
|
-
export declare function injectImageEndpoint(settings: AstroSettings): AstroSettings;
|
|
4
|
+
export declare function injectImageEndpoint(settings: AstroSettings, mode: 'dev' | 'build'): AstroSettings;
|
|
5
5
|
export declare function isESMImportedImage(src: ImageMetadata | string): src is ImageMetadata;
|
|
6
6
|
export declare function isRemoteImage(src: ImageMetadata | string): src is string;
|
|
7
7
|
export declare function isRemoteAllowed(src: string, { domains, remotePatterns, }: Partial<Pick<AstroConfig['image'], 'domains' | 'remotePatterns'>>): boolean;
|
package/dist/assets/internal.js
CHANGED
|
@@ -2,8 +2,8 @@ import { isRemotePath } from "@astrojs/internal-helpers/path";
|
|
|
2
2
|
import { AstroError, AstroErrorData } from "../core/errors/index.js";
|
|
3
3
|
import { isLocalService } from "./services/service.js";
|
|
4
4
|
import { matchHostname, matchPattern } from "./utils/remotePattern.js";
|
|
5
|
-
function injectImageEndpoint(settings) {
|
|
6
|
-
const endpointEntrypoint = settings.config.image.endpoint ?? "astro/assets/
|
|
5
|
+
function injectImageEndpoint(settings, mode) {
|
|
6
|
+
const endpointEntrypoint = settings.config.image.endpoint ?? (mode === "dev" ? "astro/assets/endpoint/node" : "astro/assets/endpoint/generic");
|
|
7
7
|
settings.injectedRoutes.push({
|
|
8
8
|
pattern: "/_image",
|
|
9
9
|
entryPoint: endpointEntrypoint,
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
prependForwardSlash,
|
|
9
9
|
removeQueryString
|
|
10
10
|
} from "../core/path.js";
|
|
11
|
+
import { isServerLikeOutput } from "../prerender/utils.js";
|
|
11
12
|
import { VALID_INPUT_FORMATS, VIRTUAL_MODULE_ID, VIRTUAL_SERVICE_ID } from "./consts.js";
|
|
12
13
|
import { emitESMImage } from "./utils/emitAsset.js";
|
|
13
14
|
import { hashTransform, propsToFilename } from "./utils/transformToPath.js";
|
|
@@ -48,6 +49,11 @@ function assets({
|
|
|
48
49
|
export { default as Image } from "astro/components/Image.astro";
|
|
49
50
|
|
|
50
51
|
export const imageConfig = ${JSON.stringify(settings.config.image)};
|
|
52
|
+
export const assetsDir = new URL(${JSON.stringify(
|
|
53
|
+
new URL(
|
|
54
|
+
isServerLikeOutput(settings.config) ? settings.config.build.client : settings.config.outDir
|
|
55
|
+
)
|
|
56
|
+
)});
|
|
51
57
|
export const getImage = async (options) => await getImageInternal(options, imageConfig);
|
|
52
58
|
`;
|
|
53
59
|
}
|
|
@@ -53,7 +53,8 @@ async function createContentTypesGenerator({
|
|
|
53
53
|
objectMode: true
|
|
54
54
|
});
|
|
55
55
|
for (const entry of globResult) {
|
|
56
|
-
const
|
|
56
|
+
const fullPath = path.join(fileURLToPath(contentPaths.contentDir), entry.path);
|
|
57
|
+
const entryURL = pathToFileURL(fullPath);
|
|
57
58
|
if (entryURL.href.startsWith(contentPaths.config.url.href))
|
|
58
59
|
continue;
|
|
59
60
|
if (entry.dirent.isFile()) {
|
package/dist/core/build/index.js
CHANGED
|
@@ -68,7 +68,7 @@ class AstroBuilder {
|
|
|
68
68
|
logger
|
|
69
69
|
});
|
|
70
70
|
if (isServerLikeOutput(this.settings.config)) {
|
|
71
|
-
this.settings = injectImageEndpoint(this.settings);
|
|
71
|
+
this.settings = injectImageEndpoint(this.settings, "build");
|
|
72
72
|
}
|
|
73
73
|
this.manifest = createRouteManifest({ settings: this.settings }, this.logger);
|
|
74
74
|
const viteConfig = await createVite(
|