Skip to content

Commit 02a0c53

Browse files
committed
Add auto-detection of route param naming
1 parent 275586f commit 02a0c53

9 files changed

Lines changed: 105 additions & 33 deletions

File tree

.changeset/young-cases-matter.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'astro-og-canvas': minor
3+
---
4+
5+
Adds auto-detection for the route parameter name to `OGImageRoute()`.
6+
7+
**⚠️ BREAKING CHANGE:** The `param` option to `OGImageRoute()` has been removed and your code should be updated to remove it:
8+
9+
```diff
10+
export const { getStaticPaths, GET } = await OGImageRoute({
11+
- param: 'slug',
12+
pages: {
13+
// ...
14+
},
15+
getImageOptions: () => {/* ... */},
16+
});
17+
```
18+
19+
`astro-og-canvas` now detects the `param` value from your image endpoint’s filename automatically.

demo/src/pages/background-test/[path].ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ const logoPath = './src/astro-docs-logo.png';
44
const bgPath = './src/bgPattern.png';
55

66
export const { getStaticPaths, GET } = await OGImageRoute({
7-
param: 'path',
87
pages: {
98
'contain.md': {
109
bgImage: { path: bgPath, fit: 'contain' },

demo/src/pages/font-test/[path].ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { OGImageRoute } from 'astro-og-canvas';
22
import { pages } from './_pages';
33

44
export const { getStaticPaths, GET } = await OGImageRoute({
5-
param: 'path',
65
pages,
76
getImageOptions: (_path, page) => ({
87
title: page.title,

demo/src/pages/formats/[path].ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { OGImageRoute, type OGImageOptions } from 'astro-og-canvas';
22

33
export const { getStaticPaths, GET } = await OGImageRoute({
4-
param: 'path',
54
pages: {
65
'png.md': {
76
title: 'Test image (PNG)',

demo/src/pages/local-font-test/[path].ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { OGImageRoute } from 'astro-og-canvas';
22

33
export const { getStaticPaths, GET } = await OGImageRoute({
4-
param: 'path',
54
pages: {
65
'local.md': {
76
title: 'Local fonts',

demo/src/pages/og/[...route].ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import type { MarkdownInstance } from 'astro';
22
import { OGImageRoute } from 'astro-og-canvas';
33

44
export const { getStaticPaths, GET } = await OGImageRoute({
5-
param: 'route',
65
pages: import.meta.glob<MarkdownInstance<{ title?: string; description?: string }>>(
76
'/src/pages/**/*.md',
87
{ eager: true }

packages/astro-og-canvas/README.md

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,6 @@ npm i astro-og-canvas
1414
pnpm i canvaskit-wasm
1515
```
1616

17-
## Version compatibility
18-
19-
| astro | astro-og-canvas |
20-
| ------ | --------------------------------------------------------------------------------------------- |
21-
| `≤2.x` | [`0.1.x`](https://github.com/delucis/astro-og-canvas/blob/astro-og-canvas%400.1.8/README.md) |
22-
| `≥3.x` | [`≥0.2.x`](https://github.com/delucis/astro-og-canvas/blob/astro-og-canvas%400.2.0/README.md) |
23-
2417
## Usage
2518

2619
### Creating an OpenGraph image endpoint
@@ -35,10 +28,6 @@ pnpm i canvaskit-wasm
3528
import { OGImageRoute } from 'astro-og-canvas';
3629

3730
export const { getStaticPaths, GET } = await OGImageRoute({
38-
// Tell us the name of your dynamic route segment.
39-
// In this case it’s `route`, because the file is named `[...route].ts`.
40-
param: 'route',
41-
4231
// A collection of pages to generate images for.
4332
// The keys of this object are used to generate the path for that image.
4433
// In this example, we generate one image at `/open-graph/example.png`.
@@ -79,10 +68,6 @@ const collectionEntries = await getCollection('my-collection');
7968
const pages = Object.fromEntries(collectionEntries.map(({ slug, data }) => [slug, data]));
8069

8170
export const { getStaticPaths, GET } = await OGImageRoute({
82-
// Tell us the name of your dynamic route segment.
83-
// In this case it’s `route`, because the file is named `[...route].ts`.
84-
param: 'route',
85-
8671
pages: pages,
8772

8873
getImageOptions: (path, page) => ({
@@ -102,10 +87,6 @@ In the following example, every Markdown file in the project’s `src/pages/` di
10287
import { OGImageRoute } from 'astro-og-canvas';
10388

10489
export const { getStaticPaths, GET } = await OGImageRoute({
105-
// Tell us the name of your dynamic route segment.
106-
// In this case it’s `route`, because the file is named `[...route].ts`.
107-
param: 'route',
108-
10990
// Pass the glob result to pages
11091
pages: await import.meta.glob('/src/pages/**/*.md', { eager: true }),
11192

packages/astro-og-canvas/src/routing.ts

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { APIRoute, GetStaticPaths } from 'astro';
2+
import { AstroError } from 'astro/errors';
23
import { generateOpenGraphImage } from './generateOpenGraphImage.js';
34
import type { OGImageOptions } from './types';
45

@@ -13,7 +14,6 @@ const pathToSlug = (path: string, _page: any, imageOptions: OGImageOptions): str
1314

1415
async function makeGetStaticPaths({
1516
pages,
16-
param,
1717
getSlug = pathToSlug,
1818
getImageOptions,
1919
}: OGImageRouteConfig<any>): Promise<GetStaticPaths> {
@@ -22,14 +22,14 @@ async function makeGetStaticPaths({
2222
const imageOptions = await getImageOptions(...page);
2323
const slug = getSlug(...page, imageOptions);
2424
return { slug, imageOptions };
25-
})
25+
}),
2626
);
27-
const paths = entries.map(({ slug, imageOptions }) => ({
28-
params: { [param]: slug },
29-
props: { imageOptions },
30-
}));
31-
return function getStaticPaths() {
32-
return paths;
27+
return function getStaticPaths({ routePattern }) {
28+
const param = routePatternToParam(routePattern);
29+
return entries.map(({ slug, imageOptions }) => ({
30+
params: { [param]: slug },
31+
props: { imageOptions },
32+
}));
3333
};
3434
}
3535

@@ -51,7 +51,37 @@ export async function OGImageRoute<T>(opts: OGImageRouteConfig<T>): Promise<{
5151

5252
interface OGImageRouteConfig<T extends unknown> {
5353
pages: { [path: string]: T };
54-
param: string;
5554
getSlug?: (path: string, page: T, imageOptions: OGImageOptions) => string;
5655
getImageOptions: (path: string, page: T) => OGImageOptions | Promise<OGImageOptions>;
5756
}
57+
58+
/**
59+
* Converts a `routePattern` from Astro's `getStaticPaths()` to a parameter name.
60+
* For example, extracts `slug` from both `/og/[slug].png` and `/og/[...slug].png`.
61+
*/
62+
function routePatternToParam(routePattern: string): string {
63+
const matches = routePattern.matchAll(/\[(?:\.{3})?(?<param>.+?)\]/g);
64+
let param: string | undefined;
65+
let paramCount = 0;
66+
for (const { groups } of matches) {
67+
if (groups?.param) {
68+
param = groups.param;
69+
paramCount++;
70+
}
71+
}
72+
if (!param) {
73+
throw new AstroError(
74+
`No parameter found in route: \`${routePattern}\``,
75+
'Make sure the open graph image file name contains a dynamic route parameter, e.g. `src/pages/og/[...slug].ts`.\n\n' +
76+
'See https://docs.astro.build/en/guides/routing/#dynamic-routes for more information on dynamic routes.',
77+
);
78+
}
79+
if (paramCount > 1) {
80+
throw new AstroError(
81+
`Multiple parameters found in route: \`${routePattern}\``,
82+
'Make sure the open graph image file name only contains one dynamic route parameter, e.g. `src/pages/og/[...slug].ts`.\n\n' +
83+
'See https://docs.astro.build/en/guides/routing/#dynamic-routes for more information on dynamic routes.',
84+
);
85+
}
86+
return param;
87+
}

test/astro-og-canvas.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import assert from 'node:assert';
22
import { readFileSync } from 'node:fs';
33
import { describe, test } from 'node:test';
4+
import { type OGImageOptions, OGImageRoute } from '../packages/astro-og-canvas/dist/index.js';
45

56
function loadRoute(path: string): string {
67
const rel = `../demo/dist/${path}/index.html`.replaceAll(/\/{2,}/g, '/');
@@ -27,3 +28,49 @@ describe('build output', () => {
2728
assert.notEqual(buff.length, 0);
2829
});
2930
});
31+
32+
describe('OGImageRoute', () => {
33+
const imageOptions: OGImageOptions = { title: 'Test' };
34+
const routeConfig: Parameters<typeof OGImageRoute>[0] = {
35+
pages: { example: {} },
36+
getImageOptions: () => imageOptions,
37+
};
38+
39+
test('it should create static paths from config', async () => {
40+
const { getStaticPaths } = await OGImageRoute(routeConfig);
41+
const paths = await getStaticPaths({ routePattern: '/og/[slug].png' } as any);
42+
assert.deepStrictEqual(paths, [{ params: { slug: 'example.png' }, props: { imageOptions } }]);
43+
});
44+
45+
test('it should detect param name from routePattern', async () => {
46+
const { getStaticPaths } = await OGImageRoute(routeConfig);
47+
const [slugPath] = await getStaticPaths({ routePattern: '/og/[slug].png' } as any);
48+
assert.equal(slugPath.params.slug, 'example.png');
49+
const [routePath] = await getStaticPaths({ routePattern: '/og/[route].png' } as any);
50+
assert.equal(routePath.params.route, 'example.png');
51+
});
52+
53+
test('it should detect param name from routePattern with spread', async () => {
54+
const { getStaticPaths } = await OGImageRoute(routeConfig);
55+
const [slugPath] = await getStaticPaths({ routePattern: '/og/[...slug].png' } as any);
56+
assert.equal(slugPath.params.slug, 'example.png');
57+
const [routePath] = await getStaticPaths({ routePattern: '/og/[...route].png' } as any);
58+
assert.equal(routePath.params.route, 'example.png');
59+
});
60+
61+
test('it should throw if route pattern has no parameter', async () => {
62+
const { getStaticPaths } = await OGImageRoute(routeConfig);
63+
await assert.rejects(
64+
async () => getStaticPaths({ routePattern: '/og/index' } as any),
65+
/No parameter found in route: `\/og\/index`/,
66+
);
67+
});
68+
69+
test('it should throw if route pattern has multiple parameters', async () => {
70+
const { getStaticPaths } = await OGImageRoute(routeConfig);
71+
await assert.rejects(
72+
async () => getStaticPaths({ routePattern: '/og/[slug]/[id]' } as any),
73+
/Multiple parameters found in route: `\/og\/\[slug\]\/\[id\]`/,
74+
);
75+
});
76+
});

0 commit comments

Comments
 (0)