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:
parent
5966accdc1
commit
05139ef8b4
10 changed files with 145 additions and 0 deletions
25
.changeset/nasty-crabs-worry.md
Normal file
25
.changeset/nasty-crabs-worry.md
Normal 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" })
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -61,6 +61,7 @@ function createContext({
|
|||
generator: `Astro v${ASTRO_VERSION}`,
|
||||
props: {},
|
||||
rewrite,
|
||||
routePattern: "",
|
||||
redirect(path, status) {
|
||||
return new Response(null, {
|
||||
status: status || 302,
|
||||
|
|
|
@ -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("/");
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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/);
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
|
|
6
packages/astro/test/fixtures/astro-global/src/components/Route.astro
vendored
Normal file
6
packages/astro/test/fixtures/astro-global/src/components/Route.astro
vendored
Normal 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>
|
8
packages/astro/test/fixtures/astro-global/src/middleware.js
vendored
Normal file
8
packages/astro/test/fixtures/astro-global/src/middleware.js
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
|
||||
|
||||
export function onRequest(ctx, next) {
|
||||
ctx.locals = {
|
||||
localsPattern: ctx.routePattern
|
||||
};
|
||||
return next()
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue