From ef3ea942cc699f7782d733e1fc4751327d76ad96 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Thu, 11 May 2023 15:19:38 -0400 Subject: [PATCH] Allow redirects in static mode --- packages/astro/src/@types/astro.ts | 1 + packages/astro/src/core/build/generate.ts | 5 ++- packages/integrations/netlify/src/index.ts | 1 + .../netlify/src/integration-static.ts | 28 ++++++++++++++ packages/integrations/netlify/src/shared.ts | 38 +++++++++++++++---- .../fixtures/redirects/src/pages/index.astro | 6 +++ .../fixtures/redirects/src/pages/nope.astro | 3 ++ .../netlify/test/static/redirects.test.js | 27 +++++++++++++ .../netlify/test/static/test-utils.js | 29 ++++++++++++++ 9 files changed, 129 insertions(+), 9 deletions(-) create mode 100644 packages/integrations/netlify/src/integration-static.ts create mode 100644 packages/integrations/netlify/test/static/fixtures/redirects/src/pages/index.astro create mode 100644 packages/integrations/netlify/test/static/fixtures/redirects/src/pages/nope.astro create mode 100644 packages/integrations/netlify/test/static/redirects.test.js create mode 100644 packages/integrations/netlify/test/static/test-utils.js diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 0f8cf4240..56d26415d 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -1724,6 +1724,7 @@ export interface RouteData { segments: RoutePart[][]; type: RouteType; prerender: boolean; + redirect?: string; } export type SerializedRouteData = Omit & { diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index c1adc7baf..6f12b9275 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -522,9 +522,10 @@ async function generatePath( case 302: { const location = response.headers.get("location"); if(!location) { - redirectWithNoLocation(); + return void redirectWithNoLocation(); } - body = `` + body = ``; + pageData.route.redirect = location; break; } default: { diff --git a/packages/integrations/netlify/src/index.ts b/packages/integrations/netlify/src/index.ts index fd7fd5fed..510e560f1 100644 --- a/packages/integrations/netlify/src/index.ts +++ b/packages/integrations/netlify/src/index.ts @@ -1,2 +1,3 @@ export { netlifyEdgeFunctions } from './integration-edge-functions.js'; export { netlifyFunctions as default, netlifyFunctions } from './integration-functions.js'; +export { netlifyStatic } from './integration-static.js'; diff --git a/packages/integrations/netlify/src/integration-static.ts b/packages/integrations/netlify/src/integration-static.ts new file mode 100644 index 000000000..ca9cc7db2 --- /dev/null +++ b/packages/integrations/netlify/src/integration-static.ts @@ -0,0 +1,28 @@ +import type { AstroAdapter, AstroConfig, AstroIntegration, RouteData } from 'astro'; +import type { Args } from './netlify-functions.js'; +import { createRedirects } from './shared.js'; + +export function netlifyStatic(): AstroIntegration { + let _config: any; + return { + name: '@astrojs/netlify', + hooks: { + 'astro:config:done': ({ config }) => { + _config = config; + }, + // 'astro:config:setup': ({ config, updateConfig }) => { + // const outDir = dist ?? new URL('./dist/', config.root); + // updateConfig({ + // outDir, + // build: { + // client: outDir, + // server: new URL('./.netlify/functions-internal/', config.root), + // }, + // }); + // }, + 'astro:build:done': async ({ dir, routes }) => { + await createRedirects(_config, routes, dir, '', 'static'); + } + } + }; +} diff --git a/packages/integrations/netlify/src/shared.ts b/packages/integrations/netlify/src/shared.ts index 78a61a800..8aaa1a34f 100644 --- a/packages/integrations/netlify/src/shared.ts +++ b/packages/integrations/netlify/src/shared.ts @@ -1,12 +1,12 @@ import type { AstroConfig, RouteData } from 'astro'; import fs from 'fs'; -type RedirectDefinition = { +export type RedirectDefinition = { dynamic: boolean; input: string; target: string; weight: 0 | 1; - status: 200 | 404; + status: 200 | 404 | 301; }; export async function createRedirects( @@ -14,7 +14,7 @@ export async function createRedirects( routes: RouteData[], dir: URL, entryFile: string, - type: 'functions' | 'edge-functions' | 'builders' + type: 'functions' | 'edge-functions' | 'builders' | 'static' ) { const _redirectsURL = new URL('./_redirects', dir); const kind = type ?? 'functions'; @@ -23,7 +23,19 @@ export async function createRedirects( for (const route of routes) { if (route.pathname) { - if (route.distURL) { + if( kind === 'static') { + if(route.redirect) { + definitions.push({ + dynamic: false, + input: route.pathname, + target: route.redirect, + status: 301, + weight: 1 + }); + } + continue; + } + else if (route.distURL) { definitions.push({ dynamic: false, input: route.pathname, @@ -68,7 +80,19 @@ export async function createRedirects( }) .join('/'); - if (route.distURL) { + if(kind === 'static') { + if(route.redirect) { + definitions.push({ + dynamic: true, + input: pattern, + target: route.redirect, + status: 301, + weight: 1 + }); + } + continue; + } + else if (route.distURL) { const target = `${pattern}` + (config.build.format === 'directory' ? '/index.html' : '.html'); definitions.push({ @@ -99,8 +123,8 @@ export async function createRedirects( } function prettify(definitions: RedirectDefinition[]) { - let minInputLength = 0, - minTargetLength = 0; + let minInputLength = 4, + minTargetLength = 4; definitions.sort((a, b) => { // Find the longest input, so we can format things nicely if (a.input.length > minInputLength) { diff --git a/packages/integrations/netlify/test/static/fixtures/redirects/src/pages/index.astro b/packages/integrations/netlify/test/static/fixtures/redirects/src/pages/index.astro new file mode 100644 index 000000000..53e029f04 --- /dev/null +++ b/packages/integrations/netlify/test/static/fixtures/redirects/src/pages/index.astro @@ -0,0 +1,6 @@ + +Testing + +

Testing

+ + diff --git a/packages/integrations/netlify/test/static/fixtures/redirects/src/pages/nope.astro b/packages/integrations/netlify/test/static/fixtures/redirects/src/pages/nope.astro new file mode 100644 index 000000000..f48d767ee --- /dev/null +++ b/packages/integrations/netlify/test/static/fixtures/redirects/src/pages/nope.astro @@ -0,0 +1,3 @@ +--- +return Astro.redirect('/'); +--- diff --git a/packages/integrations/netlify/test/static/redirects.test.js b/packages/integrations/netlify/test/static/redirects.test.js new file mode 100644 index 000000000..5e0dd12ba --- /dev/null +++ b/packages/integrations/netlify/test/static/redirects.test.js @@ -0,0 +1,27 @@ +import { expect } from 'chai'; +import { load as cheerioLoad } from 'cheerio'; +import { loadFixture, testIntegration } from './test-utils.js'; +import { netlifyStatic } from '../../dist/index.js'; +import { fileURLToPath } from 'url'; + +describe('SSG - Redirects', () => { + /** @type {import('../../../astro/test/test-utils').Fixture} */ + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: new URL('./fixtures/redirects/', import.meta.url).toString(), + output: 'static', + adapter: netlifyStatic(), + site: `http://example.com`, + integrations: [testIntegration()], + }); + await fixture.build(); + }); + + it('Creates a redirects file', async () => { + let redirects = await fixture.readFile('/_redirects'); + let parts = redirects.split(/\s+/); + expect(parts).to.deep.equal(['/nope', '/', '301']); + }); +}); diff --git a/packages/integrations/netlify/test/static/test-utils.js b/packages/integrations/netlify/test/static/test-utils.js new file mode 100644 index 000000000..02b5d2ad9 --- /dev/null +++ b/packages/integrations/netlify/test/static/test-utils.js @@ -0,0 +1,29 @@ +// @ts-check +import { fileURLToPath } from 'url'; + +export * from '../../../../astro/test/test-utils.js'; + +/** + * + * @returns {import('../../../../astro/dist/types/@types/astro').AstroIntegration} + */ +export function testIntegration() { + return { + name: '@astrojs/netlify/test-integration', + hooks: { + 'astro:config:setup': ({ updateConfig }) => { + updateConfig({ + vite: { + resolve: { + alias: { + '@astrojs/netlify/netlify-functions.js': fileURLToPath( + new URL('../../dist/netlify-functions.js', import.meta.url) + ), + }, + }, + }, + }); + }, + }, + }; +}