Fix Netlify adapter and dynamic routes (#3011)
* Fix Netlify adapter and dynamic routes * Changeset
This commit is contained in:
parent
8bd49c9536
commit
c6f8bce7c3
10 changed files with 82 additions and 18 deletions
6
.changeset/nervous-chairs-check.md
Normal file
6
.changeset/nervous-chairs-check.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
'@astrojs/netlify': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fixes dynamic routes in the Netlify adapter
|
|
@ -12,7 +12,7 @@
|
||||||
"build:ci": "turbo run build:ci --no-deps --scope=astro --scope=create-astro --scope=\"@astrojs/*\"",
|
"build:ci": "turbo run build:ci --no-deps --scope=astro --scope=create-astro --scope=\"@astrojs/*\"",
|
||||||
"build:examples": "turbo run build --scope=\"@example/*\"",
|
"build:examples": "turbo run build --scope=\"@example/*\"",
|
||||||
"dev": "turbo run dev --no-deps --no-cache --parallel --scope=astro --scope=create-astro --scope=\"@astrojs/*\"",
|
"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:match": "cd packages/astro && pnpm run test:match",
|
||||||
"test:templates": "pnpm run test --filter create-astro",
|
"test:templates": "pnpm run test --filter create-astro",
|
||||||
"test:smoke": "node scripts/smoke/index.js",
|
"test:smoke": "node scripts/smoke/index.js",
|
||||||
|
|
|
@ -798,12 +798,19 @@ export interface AstroIntegration {
|
||||||
|
|
||||||
export type RouteType = 'page' | 'endpoint';
|
export type RouteType = 'page' | 'endpoint';
|
||||||
|
|
||||||
|
export interface RoutePart {
|
||||||
|
content: string;
|
||||||
|
dynamic: boolean;
|
||||||
|
spread: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface RouteData {
|
export interface RouteData {
|
||||||
component: string;
|
component: string;
|
||||||
generate: (data?: any) => string;
|
generate: (data?: any) => string;
|
||||||
params: string[];
|
params: string[];
|
||||||
pathname?: string;
|
pathname?: string;
|
||||||
pattern: RegExp;
|
pattern: RegExp;
|
||||||
|
segments: RoutePart[][];
|
||||||
type: RouteType;
|
type: RouteType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 type { LogOptions } from '../../logger/core';
|
||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
@ -9,16 +9,10 @@ import { fileURLToPath } from 'url';
|
||||||
import { warn } from '../../logger/core.js';
|
import { warn } from '../../logger/core.js';
|
||||||
import { resolvePages } from '../../util.js';
|
import { resolvePages } from '../../util.js';
|
||||||
|
|
||||||
interface Part {
|
|
||||||
content: string;
|
|
||||||
dynamic: boolean;
|
|
||||||
spread: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Item {
|
interface Item {
|
||||||
basename: string;
|
basename: string;
|
||||||
ext: string;
|
ext: string;
|
||||||
parts: Part[];
|
parts: RoutePart[];
|
||||||
file: string;
|
file: string;
|
||||||
isDir: boolean;
|
isDir: boolean;
|
||||||
isIndex: boolean;
|
isIndex: boolean;
|
||||||
|
@ -35,7 +29,7 @@ function countOccurrences(needle: string, haystack: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getParts(part: string, file: string) {
|
function getParts(part: string, file: string) {
|
||||||
const result: Part[] = [];
|
const result: RoutePart[] = [];
|
||||||
part.split(/\[(.+?\(.+?\)|.+?)\]/).map((str, i) => {
|
part.split(/\[(.+?\(.+?\)|.+?)\]/).map((str, i) => {
|
||||||
if (!str) return;
|
if (!str) return;
|
||||||
const dynamic = i % 2 === 1;
|
const dynamic = i % 2 === 1;
|
||||||
|
@ -56,7 +50,7 @@ function getParts(part: string, file: string) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPattern(segments: Part[][], addTrailingSlash: AstroConfig['trailingSlash']) {
|
function getPattern(segments: RoutePart[][], addTrailingSlash: AstroConfig['trailingSlash']) {
|
||||||
const pathname = segments
|
const pathname = segments
|
||||||
.map((segment) => {
|
.map((segment) => {
|
||||||
return segment[0].spread
|
return segment[0].spread
|
||||||
|
@ -94,7 +88,7 @@ function getTrailingSlashPattern(addTrailingSlash: AstroConfig['trailingSlash'])
|
||||||
return '\\/?$';
|
return '\\/?$';
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGenerator(segments: Part[][], addTrailingSlash: AstroConfig['trailingSlash']) {
|
function getGenerator(segments: RoutePart[][], addTrailingSlash: AstroConfig['trailingSlash']) {
|
||||||
const template = segments
|
const template = segments
|
||||||
.map((segment) => {
|
.map((segment) => {
|
||||||
return segment[0].spread
|
return segment[0].spread
|
||||||
|
@ -181,7 +175,7 @@ export function createRouteManifest(
|
||||||
const validPageExtensions: Set<string> = new Set(['.astro', '.md']);
|
const validPageExtensions: Set<string> = new Set(['.astro', '.md']);
|
||||||
const validEndpointExtensions: Set<string> = new Set(['.js', '.ts']);
|
const validEndpointExtensions: Set<string> = new Set(['.js', '.ts']);
|
||||||
|
|
||||||
function walk(dir: string, parentSegments: Part[][], parentParams: string[]) {
|
function walk(dir: string, parentSegments: RoutePart[][], parentParams: string[]) {
|
||||||
let items: Item[] = [];
|
let items: Item[] = [];
|
||||||
fs.readdirSync(dir).forEach((basename) => {
|
fs.readdirSync(dir).forEach((basename) => {
|
||||||
const resolved = path.join(dir, basename);
|
const resolved = path.join(dir, basename);
|
||||||
|
@ -285,6 +279,7 @@ export function createRouteManifest(
|
||||||
routes.push({
|
routes.push({
|
||||||
type: item.isPage ? 'page' : 'endpoint',
|
type: item.isPage ? 'page' : 'endpoint',
|
||||||
pattern,
|
pattern,
|
||||||
|
segments,
|
||||||
params,
|
params,
|
||||||
component,
|
component,
|
||||||
generate,
|
generate,
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import type { RouteData, SerializedRouteData } from '../../../@types/astro';
|
import type { RouteData, SerializedRouteData, RoutePart } from '../../../@types/astro';
|
||||||
|
|
||||||
function createRouteData(
|
function createRouteData(
|
||||||
pattern: RegExp,
|
pattern: RegExp,
|
||||||
params: string[],
|
params: string[],
|
||||||
component: string,
|
component: string,
|
||||||
pathname: string | undefined,
|
pathname: string | undefined,
|
||||||
type: 'page' | 'endpoint'
|
type: 'page' | 'endpoint',
|
||||||
|
segments: RoutePart[][]
|
||||||
): RouteData {
|
): RouteData {
|
||||||
return {
|
return {
|
||||||
type,
|
type,
|
||||||
|
@ -15,6 +16,7 @@ function createRouteData(
|
||||||
// TODO bring back
|
// TODO bring back
|
||||||
generate: () => '',
|
generate: () => '',
|
||||||
pathname: pathname || undefined,
|
pathname: pathname || undefined,
|
||||||
|
segments
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +28,7 @@ export function serializeRouteData(routeData: RouteData): SerializedRouteData {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deserializeRouteData(rawRouteData: 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);
|
const pattern = new RegExp(rawRouteData.pattern);
|
||||||
return createRouteData(pattern, params, component, pathname, type);
|
return createRouteData(pattern, params, component, pathname, type, segments);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,8 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "astro-scripts build \"src/**/*.ts\" && tsc",
|
"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": {
|
"dependencies": {
|
||||||
"@astrojs/webapi": "^0.11.0"
|
"@astrojs/webapi": "^0.11.0"
|
||||||
|
|
|
@ -53,6 +53,10 @@ function netlifyFunctions({ dist }: NetlifyFunctionsOptions = {}): AstroIntegrat
|
||||||
if (route.pathname) {
|
if (route.pathname) {
|
||||||
_redirects += `
|
_redirects += `
|
||||||
${route.pathname} /.netlify/functions/${entryFile} 200`;
|
${route.pathname} /.netlify/functions/${entryFile} 200`;
|
||||||
|
} else {
|
||||||
|
const pattern = '/' + route.segments.map(([part]) => part.dynamic ? '*' : part.content).join('/');
|
||||||
|
_redirects += `
|
||||||
|
${pattern} /.netlify/functions/${entryFile} 200`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
37
packages/integrations/netlify/test/dynamic-route.test.js
Normal file
37
packages/integrations/netlify/test/dynamic-route.test.js
Normal file
|
@ -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\/\*/);
|
||||||
|
});
|
||||||
|
});
|
1
packages/integrations/netlify/test/fixtures/.gitignore
vendored
Normal file
1
packages/integrations/netlify/test/fixtures/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
**/netlify
|
11
packages/integrations/netlify/test/fixtures/dynamic-route/src/pages/products/[id].astro
vendored
Normal file
11
packages/integrations/netlify/test/fixtures/dynamic-route/src/pages/products/[id].astro
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Testing</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Testing</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in a new issue