Skip to content

Commit

Permalink
fix: prioritize dynamic prerendered routes over dynamic server routes (
Browse files Browse the repository at this point in the history
…#7235)

* test: add unit tests

* fix: prioritize prerendered routes

* chore: fix test

* add comment

* test: try avoiding race condition

* chore: changeset

* try avoiding race conditions attempt #2

* try avoiding race conditions (attempt 3)

* final fix hopefuly

* tet: add more tests

* sort conflicting dynamic routes aplhabetically

* test: fix test
  • Loading branch information
MoustaphaDev committed May 30, 2023
1 parent e20a3b2 commit ee2aca8
Show file tree
Hide file tree
Showing 6 changed files with 361 additions and 19 deletions.
5 changes: 5 additions & 0 deletions .changeset/hungry-peaches-speak.md
@@ -0,0 +1,5 @@
---
'astro': patch
---

Prioritize dynamic prerendered routes over dynamic server routes
3 changes: 1 addition & 2 deletions packages/astro/src/core/render/dev/index.ts
@@ -1,4 +1,3 @@
import { fileURLToPath } from 'url';
import type {
AstroMiddlewareInstance,
AstroSettings,
Expand Down Expand Up @@ -65,7 +64,7 @@ export async function preload({

try {
// Load the module from the Vite SSR Runtime.
const mod = (await env.loader.import(fileURLToPath(filePath))) as ComponentInstance;
const mod = (await env.loader.import(viteID(filePath))) as ComponentInstance;

return [renderers, mod];
} catch (error) {
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/core/routing/manifest/create.ts
Expand Up @@ -173,6 +173,7 @@ function comparator(a: Item, b: Item) {
}
}

// endpoints are prioritized over pages
if (a.isPage !== b.isPage) {
return a.isPage ? 1 : -1;
}
Expand Down
67 changes: 67 additions & 0 deletions packages/astro/src/prerender/routing.ts
@@ -0,0 +1,67 @@
import type { AstroSettings, RouteData } from '../@types/astro';
import { preload, type DevelopmentEnvironment } from '../core/render/dev/index.js';
import { getPrerenderStatus } from './metadata.js';

type GetSortedPreloadedMatchesParams = {
env: DevelopmentEnvironment;
matches: RouteData[];
settings: AstroSettings;
};
export async function getSortedPreloadedMatches({
env,
matches,
settings,
}: GetSortedPreloadedMatchesParams) {
return (
await preloadAndSetPrerenderStatus({
env,
matches,
settings,
})
).sort((a, b) => prioritizePrerenderedMatchesComparator(a.route, b.route));
}

type PreloadAndSetPrerenderStatusParams = {
env: DevelopmentEnvironment;
matches: RouteData[];
settings: AstroSettings;
};
async function preloadAndSetPrerenderStatus({
env,
matches,
settings,
}: PreloadAndSetPrerenderStatusParams) {
const preloaded = await Promise.all(
matches.map(async (route) => {
const filePath = new URL(`./${route.component}`, settings.config.root);
const preloadedComponent = await preload({ env, filePath });

// gets the prerender metadata set by the `astro:scanner` vite plugin
const prerenderStatus = getPrerenderStatus({
filePath,
loader: env.loader,
});

if (prerenderStatus !== undefined) {
route.prerender = prerenderStatus;
}

return { preloadedComponent, route, filePath } as const;
})
);
return preloaded;
}

function prioritizePrerenderedMatchesComparator(a: RouteData, b: RouteData): number {
if (areRegexesEqual(a.pattern, b.pattern)) {
if (a.prerender !== b.prerender) {
return a.prerender ? -1 : 1;
}
return a.component < b.component ? -1 : 1;
}
return 0;
}

function areRegexesEqual(regexp1: RegExp, regexp2: RegExp) {
return regexp1.source === regexp2.source && regexp1.global === regexp2.global;
}
22 changes: 5 additions & 17 deletions packages/astro/src/vite-plugin-astro-server/route.ts
Expand Up @@ -16,10 +16,10 @@ import { preload, renderPage } from '../core/render/dev/index.js';
import { getParamsAndProps, GetParamsAndPropsError } from '../core/render/index.js';
import { createRequest } from '../core/request.js';
import { matchAllRoutes } from '../core/routing/index.js';
import { getPrerenderStatus } from '../prerender/metadata.js';
import { isHybridOutput } from '../prerender/utils.js';
import { log404 } from './common.js';
import { handle404Response, writeSSRResult, writeWebResponse } from './response.js';
import { getSortedPreloadedMatches } from '../prerender/routing.js';

type AsyncReturnType<T extends (...args: any) => Promise<any>> = T extends (
...args: any
Expand Down Expand Up @@ -47,24 +47,12 @@ export async function matchRoute(
): Promise<MatchedRoute | undefined> {
const { logging, settings, routeCache } = env;
const matches = matchAllRoutes(pathname, manifest);
const preloadedMatches = await getSortedPreloadedMatches({ env, matches, settings });

for await (const maybeRoute of matches) {
const filePath = new URL(`./${maybeRoute.component}`, settings.config.root);
const preloadedComponent = await preload({ env, filePath });

// gets the prerender metadata set by the `astro:scanner` vite plugin
const prerenderStatus = getPrerenderStatus({
filePath,
loader: env.loader,
});

if (prerenderStatus !== undefined) {
maybeRoute.prerender = prerenderStatus;
}

const [, mod] = preloadedComponent;
for await (const { preloadedComponent, route: maybeRoute, filePath } of preloadedMatches) {
// attempt to get static paths
// if this fails, we have a bad URL match!
const [, mod] = preloadedComponent;
const paramsAndPropsRes = await getParamsAndProps({
mod,
route: maybeRoute,
Expand Down Expand Up @@ -210,7 +198,7 @@ export async function handleRoute(
await writeWebResponse(res, result.response);
} else {
let contentType = 'text/plain';
// Dynamic routes dont include `route.pathname`, so synthesize a path for these (e.g. 'src/pages/[slug].svg')
// Dynamic routes don't include `route.pathname`, so synthesize a path for these (e.g. 'src/pages/[slug].svg')
const filepath =
route.pathname ||
route.segments.map((segment) => segment.map((p) => p.content).join('')).join('/');
Expand Down

0 comments on commit ee2aca8

Please sign in to comment.