summaryrefslogtreecommitdiff
path: root/packages/integrations/netlify/src/shared.ts
blob: 175b9d04f74f8d277eb4fdaed95ad9df756c7c84 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects';
import type { AstroConfig, RouteData } from 'astro';
import esbuild from 'esbuild';
import fs from 'node:fs';
import npath from 'node:path';
import { fileURLToPath } from 'node:url';

export const DENO_SHIM = `globalThis.process = {
	argv: [],
	env: Deno.env.toObject(),
};`;

export interface NetlifyEdgeFunctionsOptions {
	dist?: URL;
}

export interface NetlifyEdgeFunctionManifestFunctionPath {
	function: string;
	path: string;
}

export interface NetlifyEdgeFunctionManifestFunctionPattern {
	function: string;
	pattern: string;
}

export type NetlifyEdgeFunctionManifestFunction =
	| NetlifyEdgeFunctionManifestFunctionPath
	| NetlifyEdgeFunctionManifestFunctionPattern;

export interface NetlifyEdgeFunctionManifest {
	functions: NetlifyEdgeFunctionManifestFunction[];
	version: 1;
}

export async function createRedirects(
	config: AstroConfig,
	routeToDynamicTargetMap: Map<RouteData, string>,
	dir: URL
) {
	const _redirectsURL = new URL('./_redirects', dir);

	const _redirects = createRedirectsFromAstroRoutes({
		config,
		routeToDynamicTargetMap,
		dir,
	});
	const content = _redirects.print();

	// Always use appendFile() because the redirects file could already exist,
	// e.g. due to a `/public/_redirects` file that got copied to the output dir.
	// If the file does not exist yet, appendFile() automatically creates it.
	await fs.promises.appendFile(_redirectsURL, content, 'utf-8');
}

export async function createEdgeManifest(routes: RouteData[], entryFile: string, dir: URL) {
	const functions: NetlifyEdgeFunctionManifestFunction[] = [];
	for (const route of routes) {
		if (route.pathname) {
			functions.push({
				function: entryFile,
				path: route.pathname,
			});
		} else {
			functions.push({
				function: entryFile,
				// Make route pattern serializable to match expected
				// Netlify Edge validation format. Mirrors Netlify's own edge bundler:
				// https://github.com/netlify/edge-bundler/blob/main/src/manifest.ts#L34
				pattern: route.pattern.source.replace(/\\\//g, '/').toString(),
			});
		}
	}

	const manifest: NetlifyEdgeFunctionManifest = {
		functions,
		version: 1,
	};

	const baseDir = new URL('./.netlify/edge-functions/', dir);
	await fs.promises.mkdir(baseDir, { recursive: true });

	const manifestURL = new URL('./manifest.json', baseDir);
	const _manifest = JSON.stringify(manifest, null, '  ');
	await fs.promises.writeFile(manifestURL, _manifest, 'utf-8');
}

export async function bundleServerEntry(entryUrl: URL, serverUrl?: URL, vite?: any | undefined) {
	const pth = fileURLToPath(entryUrl);
	await esbuild.build({
		target: 'es2020',
		platform: 'browser',
		entryPoints: [pth],
		outfile: pth,
		allowOverwrite: true,
		format: 'esm',
		bundle: true,
		external: ['@astrojs/markdown-remark', 'astro/middleware'],
		banner: {
			js: DENO_SHIM,
		},
	});

	// Remove chunks, if they exist. Since we have bundled via esbuild these chunks are trash.
	if (vite && serverUrl) {
		try {
			const chunkFileNames =
				vite?.build?.rollupOptions?.output?.chunkFileNames ?? `chunks/chunk.[hash].mjs`;
			const chunkPath = npath.dirname(chunkFileNames);
			const chunksDirUrl = new URL(chunkPath + '/', serverUrl);
			await fs.promises.rm(chunksDirUrl, { recursive: true, force: true });
		} catch {}
	}
}