0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-03-31 23:31:30 -05:00

feat(next): add Astro.routePattern (#11698)

* feat: add `Astro.route`

* change logic and add test

* rebase

* rebase

* rename to `Astro.routePattern`

* chore: added more tests

* update test

* add leading slash
This commit is contained in:
Emanuele Stoppa 2024-08-27 15:45:16 +01:00 committed by GitHub
parent 5966accdc1
commit 05139ef8b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 145 additions and 0 deletions

View file

@ -0,0 +1,25 @@
---
'astro': minor
---
Adds a new property to the globals `Astro` and `APIContext` called `routePattern`. The `routePattern` represents the current route (component)
that is being rendered by Astro. It's usually a path pattern will look like this: `blog/[slug]`:
```asto
---
// src/pages/blog/[slug].astro
const route = Astro.routePattern;
console.log(route); // it will log "blog/[slug]"
---
```
```js
// src/pages/index.js
export const GET = (ctx) => {
console.log(ctx.routePattern) // it will log src/pages/index.js
return new Response.json({ loreum: "ipsum" })
}
```

View file

@ -61,6 +61,7 @@ function createContext({
generator: `Astro v${ASTRO_VERSION}`,
props: {},
rewrite,
routePattern: "",
redirect(path, status) {
return new Response(null, {
status: status || 302,

View file

@ -37,6 +37,9 @@ import { type Pipeline, Slots, getParams, getProps } from './render/index.js';
export class RenderContext {
// The first route that this instance of the context attempts to render
originalRoute: RouteData;
// The component pattern to send to the users
routePattern: string;
private constructor(
readonly pipeline: Pipeline,
@ -52,6 +55,7 @@ export class RenderContext {
public props: Props = {},
) {
this.originalRoute = routeData;
this.routePattern = getAstroRoutePattern(routeData.component);
}
/**
@ -234,6 +238,7 @@ export class RenderContext {
this.isRewriting = true;
// we found a route and a component, we can change the status code to 200
this.status = 200;
this.routePattern = getAstroRoutePattern(routeData.component);
return await this.render(component);
}
@ -250,6 +255,7 @@ export class RenderContext {
return {
cookies,
routePattern: this.routePattern,
get clientAddress() {
return renderContext.clientAddress();
},
@ -435,6 +441,7 @@ export class RenderContext {
return {
generator: astroStaticPartial.generator,
glob: astroStaticPartial.glob,
routePattern: this.routePattern,
cookies,
get clientAddress() {
return renderContext.clientAddress();
@ -566,3 +573,31 @@ export class RenderContext {
});
}
}
/**
* Return the component path without the `srcDir` and `pages`
* @param component
*/
function getAstroRoutePattern(component: RouteData['component']): string {
let splitComponent = component.split("/");
while (true) {
const currentPart = splitComponent.shift();
if (!currentPart) {break}
// "pages" isn't configurable, so it's safe to stop here
if (currentPart === "pages") {
break
}
}
const pathWithoutPages = splitComponent.join("/");
// This covers cases where routes don't have extensions, so they can be: [slug] or [...slug]
if (pathWithoutPages.endsWith("]")) {
return pathWithoutPages;
}
splitComponent = splitComponent.join("/").split(".");
// this should remove the extension
splitComponent.pop();
return "/" + splitComponent.join("/");
}

View file

@ -137,6 +137,16 @@ export interface AstroGlobal<
* ```
*/
rewrite: AstroSharedContext['rewrite'];
/**
* The route currently rendered. It's stripped of the `srcDir` and the `pages` folder, and it doesn't contain the extension.
*
* ## Example
* - The value when rendering `src/pages/index.astro` will `index`.
* - The value when rendering `src/pages/blog/[slug].astro` will `blog/[slug]`.
* - The value when rendering `src/pages/[...path].astro` will `[...path]`.
*/
routePattern: string;
/**
* The <Astro.self /> element allows a component to reference itself recursively.
*
@ -498,4 +508,14 @@ export interface APIContext<
* The current locale computed from the URL of the request. It matches the locales in `i18n.locales`, and returns `undefined` otherwise.
*/
currentLocale: string | undefined;
/**
* The route currently rendered. It's stripped of the `srcDir` and the `pages` folder, and it doesn't contain the extension.
*
* ## Example
* - The value when rendering `src/pages/index.astro` will `index`.
* - The value when rendering `src/pages/blog/[slug].astro` will `blog/[slug]`.
* - The value when rendering `src/pages/[...path].astro` will `[...path]`.
*/
routePattern: string
}

View file

@ -46,6 +46,17 @@ describe('Astro Global', () => {
false,
);
});
it("Astro.route.pattern has the right value in pages and components", async () => {
let html = await fixture.fetch('/blog').then((res) => res.text());
let $ = cheerio.load(html);
assert.match($("#pattern").text(), /Astro route pattern: \/index/);
assert.match($("#pattern-middleware").text(), /Astro route pattern middleware: \/index/);
html = await fixture.fetch('/blog/omit-markdown-extensions/').then((res) => res.text());
$ = cheerio.load(html);
assert.match($("#pattern").text(), /Astro route pattern: \/omit-markdown-extensions/);
assert.match($("#pattern-middleware").text(), /Astro route pattern middleware: \/omit-markdown-extensions/);
})
});
describe('build', () => {
@ -81,6 +92,24 @@ describe('Astro Global', () => {
assert.equal($('[data-file]').length, 8);
assert.equal($('.post-url[href]').length, 8);
});
it("Astro.route.pattern has the right value in pages and components", async () => {
let html = await fixture.readFile('/index.html');
let $ = cheerio.load(html);
assert.match($("#pattern").text(), /Astro route pattern: \/index/);
assert.match($("#pattern-middleware").text(), /Astro route pattern middleware: \/index/);
html =await fixture.readFile('/omit-markdown-extensions/index.html');
$ = cheerio.load(html);
assert.match($("#pattern").text(), /Astro route pattern: \/omit-markdown-extensions/);
assert.match($("#pattern-middleware").text(), /Astro route pattern middleware: \/omit-markdown-extensions/);
html = await fixture.readFile('/posts/1/index.html');
$ = cheerio.load(html);
assert.equal($("#pattern").text(), "Astro route pattern: /posts/[page]");
assert.equal($("#pattern-middleware").text(), "Astro route pattern middleware: /posts/[page]");
})
});
describe('app', () => {
@ -105,6 +134,19 @@ describe('Astro Global', () => {
const $ = cheerio.load(html);
assert.equal($('#site').attr('href'), 'https://mysite.dev/subsite/');
});
it("Astro.route.pattern has the right value in pages and components", async () => {
let response = await app.render(new Request('https://example.com/'));
let html = await response.text();
let $ = cheerio.load(html);
assert.match($("#pattern").text(), /Astro route pattern: \/index/);
assert.match($("#pattern-middleware").text(), /Astro route pattern middleware: \/index/);
response = await app.render(new Request('https://example.com/omit-markdown-extensions'));
html = await response.text();
$ = cheerio.load(html);
assert.match($("#pattern").text(), /Astro route pattern: \/omit-markdown-extensions/);
assert.match($("#pattern-middleware").text(), /Astro route pattern middleware: \/omit-markdown-extensions/);
})
});
});

View file

@ -0,0 +1,6 @@
---
const pattern = Astro.routePattern;
const localsPattern = Astro.locals.localsPattern;
---
<p id="pattern">Astro route pattern: {pattern}</p>
<p id="pattern-middleware">Astro route pattern middleware: {localsPattern}</p>

View file

@ -0,0 +1,8 @@
export function onRequest(ctx, next) {
ctx.locals = {
localsPattern: ctx.routePattern
};
return next()
}

View file

@ -1,5 +1,6 @@
---
import Child from '../components/Child.astro';
import Route from '../components/Route.astro';
const canonicalURL = new URL(Astro.url.pathname, Astro.site ?? `http://example.com`);
---
<html>
@ -13,5 +14,6 @@ const canonicalURL = new URL(Astro.url.pathname, Astro.site ?? `http://example.c
<a id="site" href={Astro.site}>Home</a>
<Child />
<Route />
</body>
</html>

View file

@ -1,4 +1,6 @@
---
import Route from "../components/Route.astro";
const markdownPosts = await Astro.glob('./post/**/*.{markdown,mdown,mkdn,mkd,mdwn,md}');
const markdownExtensions = /(\.(markdown|mdown|mkdn|mkd|mdwn|md))$/g
const aUrlContainsExtension = markdownPosts.some((page:any)=> {
@ -12,5 +14,6 @@ const aUrlContainsExtension = markdownPosts.some((page:any)=> {
</head>
<body>
<p data-any-url-contains-extension={JSON.stringify(aUrlContainsExtension)}>Placeholder</p>
<Route />
</body>
</html>

View file

@ -1,4 +1,6 @@
---
import Route from "../../components/Route.astro";
export async function getStaticPaths({paginate}) {
const data = await Astro.glob('../post/*.md');
return paginate(data, {pageSize: 1});
@ -19,5 +21,6 @@ const canonicalURL = new URL(Astro.url.pathname, Astro.site ?? `http://example.c
<a class="post-url" href={data.url}>Read</a>
</div>
))}
<Route />
</body>
</html>