diff --git a/.changeset/nervous-chairs-check.md b/.changeset/nervous-chairs-check.md new file mode 100644 index 000000000..bce6918fc --- /dev/null +++ b/.changeset/nervous-chairs-check.md @@ -0,0 +1,6 @@ +--- +'astro': patch +'@astrojs/netlify': patch +--- + +Fixes dynamic routes in the Netlify adapter diff --git a/package.json b/package.json index 9883dfd85..65399af15 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "build:ci": "turbo run build:ci --no-deps --scope=astro --scope=create-astro --scope=\"@astrojs/*\"", "build:examples": "turbo run build --scope=\"@example/*\"", "dev": "turbo run dev --no-deps --no-cache --parallel --scope=astro --scope=create-astro --scope=\"@astrojs/*\"", - "test": "pnpm run test --filter astro --filter @astrojs/webapi --filter @astrojs/deno", + "test": "pnpm run test --filter astro --filter @astrojs/webapi --filter @astrojs/deno --filter @astrojs/netlify", "test:match": "cd packages/astro && pnpm run test:match", "test:templates": "pnpm run test --filter create-astro", "test:smoke": "node scripts/smoke/index.js", diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index b0563d9aa..76e24a4a0 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -798,12 +798,19 @@ export interface AstroIntegration { export type RouteType = 'page' | 'endpoint'; +export interface RoutePart { + content: string; + dynamic: boolean; + spread: boolean; +} + export interface RouteData { component: string; generate: (data?: any) => string; params: string[]; pathname?: string; pattern: RegExp; + segments: RoutePart[][]; type: RouteType; } diff --git a/packages/astro/src/core/routing/manifest/create.ts b/packages/astro/src/core/routing/manifest/create.ts index ec1d6a578..9555d9753 100644 --- a/packages/astro/src/core/routing/manifest/create.ts +++ b/packages/astro/src/core/routing/manifest/create.ts @@ -1,4 +1,4 @@ -import type { AstroConfig, ManifestData, RouteData } from '../../../@types/astro'; +import type { AstroConfig, ManifestData, RouteData, RoutePart } from '../../../@types/astro'; import type { LogOptions } from '../../logger/core'; import fs from 'fs'; @@ -9,16 +9,10 @@ import { fileURLToPath } from 'url'; import { warn } from '../../logger/core.js'; import { resolvePages } from '../../util.js'; -interface Part { - content: string; - dynamic: boolean; - spread: boolean; -} - interface Item { basename: string; ext: string; - parts: Part[]; + parts: RoutePart[]; file: string; isDir: boolean; isIndex: boolean; @@ -35,7 +29,7 @@ function countOccurrences(needle: string, haystack: string) { } function getParts(part: string, file: string) { - const result: Part[] = []; + const result: RoutePart[] = []; part.split(/\[(.+?\(.+?\)|.+?)\]/).map((str, i) => { if (!str) return; const dynamic = i % 2 === 1; @@ -56,7 +50,7 @@ function getParts(part: string, file: string) { return result; } -function getPattern(segments: Part[][], addTrailingSlash: AstroConfig['trailingSlash']) { +function getPattern(segments: RoutePart[][], addTrailingSlash: AstroConfig['trailingSlash']) { const pathname = segments .map((segment) => { return segment[0].spread @@ -94,7 +88,7 @@ function getTrailingSlashPattern(addTrailingSlash: AstroConfig['trailingSlash']) return '\\/?$'; } -function getGenerator(segments: Part[][], addTrailingSlash: AstroConfig['trailingSlash']) { +function getGenerator(segments: RoutePart[][], addTrailingSlash: AstroConfig['trailingSlash']) { const template = segments .map((segment) => { return segment[0].spread @@ -181,7 +175,7 @@ export function createRouteManifest( const validPageExtensions: Set = new Set(['.astro', '.md']); const validEndpointExtensions: Set = new Set(['.js', '.ts']); - function walk(dir: string, parentSegments: Part[][], parentParams: string[]) { + function walk(dir: string, parentSegments: RoutePart[][], parentParams: string[]) { let items: Item[] = []; fs.readdirSync(dir).forEach((basename) => { const resolved = path.join(dir, basename); @@ -285,6 +279,7 @@ export function createRouteManifest( routes.push({ type: item.isPage ? 'page' : 'endpoint', pattern, + segments, params, component, generate, diff --git a/packages/astro/src/core/routing/manifest/serialization.ts b/packages/astro/src/core/routing/manifest/serialization.ts index e167eb374..fcd932738 100644 --- a/packages/astro/src/core/routing/manifest/serialization.ts +++ b/packages/astro/src/core/routing/manifest/serialization.ts @@ -1,11 +1,12 @@ -import type { RouteData, SerializedRouteData } from '../../../@types/astro'; +import type { RouteData, SerializedRouteData, RoutePart } from '../../../@types/astro'; function createRouteData( pattern: RegExp, params: string[], component: string, pathname: string | undefined, - type: 'page' | 'endpoint' + type: 'page' | 'endpoint', + segments: RoutePart[][] ): RouteData { return { type, @@ -15,6 +16,7 @@ function createRouteData( // TODO bring back generate: () => '', pathname: pathname || undefined, + segments }; } @@ -26,7 +28,7 @@ export function serializeRouteData(routeData: RouteData): SerializedRouteData { } export function deserializeRouteData(rawRouteData: SerializedRouteData) { - const { component, params, pathname, type } = rawRouteData; + const { component, params, pathname, type, segments } = rawRouteData; const pattern = new RegExp(rawRouteData.pattern); - return createRouteData(pattern, params, component, pathname, type); + return createRouteData(pattern, params, component, pathname, type, segments); } diff --git a/packages/integrations/netlify/package.json b/packages/integrations/netlify/package.json index 4402076c1..ed3d1ff36 100644 --- a/packages/integrations/netlify/package.json +++ b/packages/integrations/netlify/package.json @@ -21,7 +21,8 @@ }, "scripts": { "build": "astro-scripts build \"src/**/*.ts\" && tsc", - "dev": "astro-scripts dev \"src/**/*.ts\"" + "dev": "astro-scripts dev \"src/**/*.ts\"", + "test": "mocha --exit --timeout 20000" }, "dependencies": { "@astrojs/webapi": "^0.11.0" diff --git a/packages/integrations/netlify/src/index.ts b/packages/integrations/netlify/src/index.ts index 181303e33..aa1b5d33c 100644 --- a/packages/integrations/netlify/src/index.ts +++ b/packages/integrations/netlify/src/index.ts @@ -53,6 +53,10 @@ function netlifyFunctions({ dist }: NetlifyFunctionsOptions = {}): AstroIntegrat if (route.pathname) { _redirects += ` ${route.pathname} /.netlify/functions/${entryFile} 200`; + } else { + const pattern = '/' + route.segments.map(([part]) => part.dynamic ? '*' : part.content).join('/'); + _redirects += ` +${pattern} /.netlify/functions/${entryFile} 200`; } } diff --git a/packages/integrations/netlify/test/dynamic-route.test.js b/packages/integrations/netlify/test/dynamic-route.test.js new file mode 100644 index 000000000..bc9335bfe --- /dev/null +++ b/packages/integrations/netlify/test/dynamic-route.test.js @@ -0,0 +1,37 @@ +import { expect } from 'chai'; +import { load as cheerioLoad } from 'cheerio'; +import { loadFixture } from '../../../astro/test/test-utils.js'; +import netlifyAdapter from '../dist/index.js'; +import { fileURLToPath } from 'url'; + +// Asset bundling +describe('Dynamic pages', () => { + /** @type {import('../../../astro/test/test-utils').Fixture} */ + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: new URL('./fixtures/dynamic-route/', import.meta.url).toString(), + experimental: { + ssr: true, + }, + adapter: netlifyAdapter({ + dist: new URL('./fixtures/dynamic-route/dist/', import.meta.url) + }), + site: `http://example.com`, + vite: { + resolve: { + alias: { + '@astrojs/netlify/netlify-functions.js': fileURLToPath(new URL('../dist/netlify-functions.js', import.meta.url)) + } + } + } + }); + await fixture.build(); + }); + + it('Dynamic pages are included in the redirects file', async () => { + const redir = await fixture.readFile('/_redirects'); + expect(redir).to.match(/\/products\/\*/); + }); +}); diff --git a/packages/integrations/netlify/test/fixtures/.gitignore b/packages/integrations/netlify/test/fixtures/.gitignore new file mode 100644 index 000000000..916f60644 --- /dev/null +++ b/packages/integrations/netlify/test/fixtures/.gitignore @@ -0,0 +1 @@ +**/netlify diff --git a/packages/integrations/netlify/test/fixtures/dynamic-route/src/pages/products/[id].astro b/packages/integrations/netlify/test/fixtures/dynamic-route/src/pages/products/[id].astro new file mode 100644 index 000000000..5ed06d251 --- /dev/null +++ b/packages/integrations/netlify/test/fixtures/dynamic-route/src/pages/products/[id].astro @@ -0,0 +1,11 @@ +--- + +--- + + + Testing + + +

Testing

+ +