astro/packages/integrations/cloudflare/src/index.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

301 lines
9 KiB
TypeScript
Raw Normal View History

2023-06-05 13:05:47 +00:00
import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects';
import type { AstroAdapter, AstroConfig, AstroIntegration, RouteData } from 'astro';
import esbuild from 'esbuild';
import * as fs from 'fs';
import * as os from 'os';
import { dirname } from 'path';
import glob from 'tiny-glob';
import { fileURLToPath, pathToFileURL } from 'url';
type Options = {
mode: 'directory' | 'advanced';
};
interface BuildConfig {
server: URL;
client: URL;
serverEntry: string;
split?: boolean;
}
export function getAdapter(isModeDirectory: boolean): AstroAdapter {
return isModeDirectory
? {
2023-06-30 09:12:30 +00:00
name: '@astrojs/cloudflare',
serverEntrypoint: '@astrojs/cloudflare/server.directory.js',
exports: ['onRequest', 'manifest'],
}
: {
2023-06-30 09:12:30 +00:00
name: '@astrojs/cloudflare',
serverEntrypoint: '@astrojs/cloudflare/server.advanced.js',
exports: ['default'],
};
}
const SHIM = `globalThis.process = {
argv: [],
env: {},
};`;
const SERVER_BUILD_FOLDER = '/$server_build/';
export default function createIntegration(args?: Options): AstroIntegration {
let _config: AstroConfig;
let _buildConfig: BuildConfig;
const isModeDirectory = args?.mode === 'directory';
let _entryPoints = new Map<RouteData, URL>();
return {
name: '@astrojs/cloudflare',
hooks: {
'astro:config:setup': ({ config, updateConfig }) => {
updateConfig({
build: {
client: new URL(`.${config.base}`, config.outDir),
server: new URL(`.${SERVER_BUILD_FOLDER}`, config.outDir),
serverEntry: '_worker.mjs',
Redirects (#7067) * Redirects spike * Allow redirects in static mode * Support in Netlify as well * Adding a changeset * Rename file * Fix build problem * Refactor to be more modular * Fix location ref * Late test should only run in SSR * Support redirects in Netlify SSR configuration (#7167) * Implement support for dynamic routes in redirects (#7173) * Implement support for dynamic routes in redirects * Remove the .only * No need to special-case redirects in static build * Implement support for redirects config in the Vercel adapter (#7182) * Implement support for redirects config in the Vercel adapter * Remove unused condition * Move to a internal helper package * Add support for the object notation in redirects * Use status 308 for non-GET redirects (#7186) * Implement redirects in Cloudflare (#7198) * Implement redirects in Cloudflare * Fix build * Update tests b/c of new ordering * Debug issue * Use posix.join * Update packages/underscore-redirects/package.json Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> * Update based on review comments * Update broken test --------- Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> * Test that redirects can come from middleware (#7213) * Test that redirects can come from middleware * Allow non-promise returns for middleware * Implement priority (#7210) * Refactor * Fix netlify test ordering * Fix ordering again * Redirects: Allow preventing the output of the static HTML file (#7245) * Do a simple push for priority * Adding changesets * Put the implementation behind a flag. * Self review * Update .changeset/chatty-actors-stare.md Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/astro/src/@types/astro.ts Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/astro/src/@types/astro.ts Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/astro/src/@types/astro.ts Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/astro/src/@types/astro.ts Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update docs on dynamic restrictions. * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Code review changes * Document netlify static adapter * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Slight reword * Update .changeset/twenty-suns-vanish.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Add a note about public/_redirects file * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> --------- Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
2023-06-05 13:03:20 +00:00
redirects: false,
2022-10-12 21:27:56 +00:00
},
});
},
'astro:config:done': ({ setAdapter, config }) => {
setAdapter(getAdapter(isModeDirectory));
_config = config;
_buildConfig = config.build;
2022-07-25 04:20:38 +00:00
if (config.output === 'static') {
throw new Error(`
feat: hybrid output (#6991) * update config schema * adapt default route `prerender` value * adapt error message for hybrid output * core hybrid output support * add JSDocs for hybrid output * dev server hybrid output support * defer hybrid output check * update endpoint request warning * support `output=hybrid` in integrations * put constant variable out of for loop * revert: reapply back ssr plugin in ssr mode * change `prerender` option default * apply `prerender` by default in hybrid mode * simplfy conditional * update config schema * add `isHybridOutput` helper * more readable prerender condition * set default prerender value if no export is found * only add `pagesVirtualModuleId` ro rollup input in `output=static` * don't export vite plugin * remove unneeded check * don't prerender when it shouldn't * extract fallback `prerender` meta Extract the fallback `prerender` module meta out of the `scan` function. It shouldn't be its responsibility to handle that * pass missing argument to function * test: update cloudflare integration tests * test: update tests of vercel integration * test: update tests of node integration * test: update tests of netlify func integration * test: update tests of netlify edge integration * throw when `hybrid` mode is malconfigured * update node integraiton `output` warning * test(WIP): skip node prerendering tests for now * remove non-existant import * test: bring back prerendering tests * remove outdated comments * test: refactor test to support windows paths * remove outdated comments * apply sarah review Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * docs: `experiment.hybridOutput` jsodcs * test: prevent import from being cached * refactor: extract hybrid output check to function * add `hybrid` to output warning in adapter hooks * chore: changeset * add `.js` extension to import * chore: use spaces instead of tabs for gh formating * resolve merge conflict * chore: move test to another file for consitency --------- Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> Co-authored-by: Matthew Phillips <matthew@skypack.dev>
2023-05-17 13:23:20 +00:00
[@astrojs/cloudflare] \`output: "server"\` or \`output: "hybrid"\` is required to use this adapter. Otherwise, this adapter is not necessary to deploy a static site to Cloudflare.
`);
}
if (config.base === SERVER_BUILD_FOLDER) {
throw new Error(`
[@astrojs/cloudflare] \`base: "${SERVER_BUILD_FOLDER}"\` is not allowed. Please change your \`base\` config to something else.`);
}
},
'astro:build:setup': ({ vite, target }) => {
if (target === 'server') {
vite.resolve ||= {};
vite.resolve.alias ||= {};
const aliases = [{ find: 'react-dom/server', replacement: 'react-dom/server.browser' }];
if (Array.isArray(vite.resolve.alias)) {
vite.resolve.alias = [...vite.resolve.alias, ...aliases];
} else {
for (const alias of aliases) {
(vite.resolve.alias as Record<string, string>)[alias.find] = alias.replacement;
}
}
vite.ssr ||= {};
vite.ssr.target = 'webworker';
}
},
'astro:build:ssr': ({ entryPoints }) => {
_entryPoints = entryPoints;
},
Redirects (#7067) * Redirects spike * Allow redirects in static mode * Support in Netlify as well * Adding a changeset * Rename file * Fix build problem * Refactor to be more modular * Fix location ref * Late test should only run in SSR * Support redirects in Netlify SSR configuration (#7167) * Implement support for dynamic routes in redirects (#7173) * Implement support for dynamic routes in redirects * Remove the .only * No need to special-case redirects in static build * Implement support for redirects config in the Vercel adapter (#7182) * Implement support for redirects config in the Vercel adapter * Remove unused condition * Move to a internal helper package * Add support for the object notation in redirects * Use status 308 for non-GET redirects (#7186) * Implement redirects in Cloudflare (#7198) * Implement redirects in Cloudflare * Fix build * Update tests b/c of new ordering * Debug issue * Use posix.join * Update packages/underscore-redirects/package.json Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> * Update based on review comments * Update broken test --------- Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> * Test that redirects can come from middleware (#7213) * Test that redirects can come from middleware * Allow non-promise returns for middleware * Implement priority (#7210) * Refactor * Fix netlify test ordering * Fix ordering again * Redirects: Allow preventing the output of the static HTML file (#7245) * Do a simple push for priority * Adding changesets * Put the implementation behind a flag. * Self review * Update .changeset/chatty-actors-stare.md Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/astro/src/@types/astro.ts Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/astro/src/@types/astro.ts Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/astro/src/@types/astro.ts Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/astro/src/@types/astro.ts Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update docs on dynamic restrictions. * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Code review changes * Document netlify static adapter * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Slight reword * Update .changeset/twenty-suns-vanish.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Add a note about public/_redirects file * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> --------- Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
2023-06-05 13:03:20 +00:00
'astro:build:done': async ({ pages, routes, dir }) => {
const functionsUrl = new URL('functions/', _config.root);
if (isModeDirectory) {
await fs.promises.mkdir(functionsUrl, { recursive: true });
}
if (isModeDirectory && _buildConfig.split) {
2023-06-30 09:12:30 +00:00
const entryPointsRouteData = [..._entryPoints.keys()];
const entryPointsURL = [..._entryPoints.values()];
const entryPaths = entryPointsURL.map((entry) => fileURLToPath(entry));
const outputDir = fileURLToPath(new URL('.astro', _buildConfig.server));
// NOTE: AFAIK, esbuild keeps the order of the entryPoints array
const { outputFiles } = await esbuild.build({
target: 'es2020',
platform: 'browser',
conditions: ['workerd', 'worker', 'browser'],
entryPoints: entryPaths,
outdir: outputDir,
allowOverwrite: true,
format: 'esm',
bundle: true,
minify: _config.vite?.build?.minify !== false,
banner: {
js: SHIM,
},
logOverride: {
'ignored-bare-import': 'silent',
},
write: false,
});
// loop through all bundled files and write them to the functions folder
for (const [index, outputFile] of outputFiles.entries()) {
// we need to make sure the filename in the functions folder
// matches to cloudflares routing capabilities (see their docs)
// IN: src/pages/[language]/files/[...path].astro
// OUT: [language]/files/[[path]].js
const fileName = entryPointsRouteData[index].component
.replace('src/pages/', '')
.replace('.astro', '.js')
.replace(/(\[\.\.\.)(\w+)(\])/g, (_match, _p1, p2) => {
return `[[${p2}]]`;
});
2023-06-30 09:12:30 +00:00
const fileUrl = new URL(fileName, functionsUrl);
const newFileDir = dirname(fileURLToPath(fileUrl));
if (!fs.existsSync(newFileDir)) {
fs.mkdirSync(newFileDir, { recursive: true });
}
await fs.promises.writeFile(fileUrl, outputFile.contents);
}
} else {
const entryPath = fileURLToPath(new URL(_buildConfig.serverEntry, _buildConfig.server));
const entryUrl = new URL(_buildConfig.serverEntry, _config.outDir);
const buildPath = fileURLToPath(entryUrl);
// A URL for the final build path after renaming
const finalBuildUrl = pathToFileURL(buildPath.replace(/\.mjs$/, '.js'));
await esbuild.build({
target: 'es2020',
platform: 'browser',
conditions: ['workerd', 'worker', 'browser'],
entryPoints: [entryPath],
outfile: buildPath,
allowOverwrite: true,
format: 'esm',
bundle: true,
minify: _config.vite?.build?.minify !== false,
banner: {
js: SHIM,
},
logOverride: {
'ignored-bare-import': 'silent',
},
});
// Rename to worker.js
await fs.promises.rename(buildPath, finalBuildUrl);
if (isModeDirectory) {
const directoryUrl = new URL('[[path]].js', functionsUrl);
await fs.promises.rename(finalBuildUrl, directoryUrl);
}
}
// // // throw the server folder in the bin
const serverUrl = new URL(_buildConfig.server);
await fs.promises.rm(serverUrl, { recursive: true, force: true });
// move cloudflare specific files to the root
const cloudflareSpecialFiles = ['_headers', '_redirects', '_routes.json'];
if (_config.base !== '/') {
for (const file of cloudflareSpecialFiles) {
try {
await fs.promises.rename(
new URL(file, _buildConfig.client),
new URL(file, _config.outDir)
);
} catch (e) {
// ignore
}
}
}
const routesExists = await fs.promises
.stat(new URL('./_routes.json', _config.outDir))
.then((stat) => stat.isFile())
.catch(() => false);
// this creates a _routes.json, in case there is none present to enable
// cloudflare to handle static files and support _redirects configuration
// (without calling the function)
if (!routesExists) {
const staticPathList: Array<string> = (
await glob(`${fileURLToPath(_buildConfig.client)}/**/*`, {
cwd: fileURLToPath(_config.outDir),
filesOnly: true,
})
)
.filter((file: string) => cloudflareSpecialFiles.indexOf(file) < 0)
.map((file: string) => `/${file}`);
2023-01-26 17:45:39 +00:00
for (let page of pages) {
let pagePath = prependForwardSlash(page.pathname);
if (_config.base !== '/') {
2023-03-16 14:42:36 +00:00
const base = _config.base.endsWith('/') ? _config.base.slice(0, -1) : _config.base;
pagePath = `${base}${pagePath}`;
}
staticPathList.push(pagePath);
}
const redirectsExists = await fs.promises
.stat(new URL('./_redirects', _config.outDir))
.then((stat) => stat.isFile())
.catch(() => false);
2022-11-21 13:33:45 +00:00
// convert all redirect source paths into a list of routes
// and add them to the static path
if (redirectsExists) {
const redirects = (
await fs.promises.readFile(new URL('./_redirects', _config.outDir), 'utf-8')
)
.split(os.EOL)
.map((line) => {
const parts = line.split(' ');
if (parts.length < 2) {
return null;
} else {
// convert /products/:id to /products/*
return (
parts[0]
.replace(/\/:.*?(?=\/|$)/g, '/*')
// remove query params as they are not supported by cloudflare
.replace(/\?.*$/, '')
);
}
})
.filter(
(line, index, arr) => line !== null && arr.indexOf(line) === index
) as string[];
if (redirects.length > 0) {
staticPathList.push(...redirects);
}
}
2023-06-05 13:05:47 +00:00
const redirectRoutes = routes.filter((r) => r.type === 'redirect');
Redirects (#7067) * Redirects spike * Allow redirects in static mode * Support in Netlify as well * Adding a changeset * Rename file * Fix build problem * Refactor to be more modular * Fix location ref * Late test should only run in SSR * Support redirects in Netlify SSR configuration (#7167) * Implement support for dynamic routes in redirects (#7173) * Implement support for dynamic routes in redirects * Remove the .only * No need to special-case redirects in static build * Implement support for redirects config in the Vercel adapter (#7182) * Implement support for redirects config in the Vercel adapter * Remove unused condition * Move to a internal helper package * Add support for the object notation in redirects * Use status 308 for non-GET redirects (#7186) * Implement redirects in Cloudflare (#7198) * Implement redirects in Cloudflare * Fix build * Update tests b/c of new ordering * Debug issue * Use posix.join * Update packages/underscore-redirects/package.json Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> * Update based on review comments * Update broken test --------- Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> * Test that redirects can come from middleware (#7213) * Test that redirects can come from middleware * Allow non-promise returns for middleware * Implement priority (#7210) * Refactor * Fix netlify test ordering * Fix ordering again * Redirects: Allow preventing the output of the static HTML file (#7245) * Do a simple push for priority * Adding changesets * Put the implementation behind a flag. * Self review * Update .changeset/chatty-actors-stare.md Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/astro/src/@types/astro.ts Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/astro/src/@types/astro.ts Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/astro/src/@types/astro.ts Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/astro/src/@types/astro.ts Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update docs on dynamic restrictions. * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Code review changes * Document netlify static adapter * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Slight reword * Update .changeset/twenty-suns-vanish.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Add a note about public/_redirects file * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> --------- Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
2023-06-05 13:03:20 +00:00
const trueRedirects = createRedirectsFromAstroRoutes({
config: _config,
routes: redirectRoutes,
dir,
});
2023-06-05 13:05:47 +00:00
if (!trueRedirects.empty()) {
Redirects (#7067) * Redirects spike * Allow redirects in static mode * Support in Netlify as well * Adding a changeset * Rename file * Fix build problem * Refactor to be more modular * Fix location ref * Late test should only run in SSR * Support redirects in Netlify SSR configuration (#7167) * Implement support for dynamic routes in redirects (#7173) * Implement support for dynamic routes in redirects * Remove the .only * No need to special-case redirects in static build * Implement support for redirects config in the Vercel adapter (#7182) * Implement support for redirects config in the Vercel adapter * Remove unused condition * Move to a internal helper package * Add support for the object notation in redirects * Use status 308 for non-GET redirects (#7186) * Implement redirects in Cloudflare (#7198) * Implement redirects in Cloudflare * Fix build * Update tests b/c of new ordering * Debug issue * Use posix.join * Update packages/underscore-redirects/package.json Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> * Update based on review comments * Update broken test --------- Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> * Test that redirects can come from middleware (#7213) * Test that redirects can come from middleware * Allow non-promise returns for middleware * Implement priority (#7210) * Refactor * Fix netlify test ordering * Fix ordering again * Redirects: Allow preventing the output of the static HTML file (#7245) * Do a simple push for priority * Adding changesets * Put the implementation behind a flag. * Self review * Update .changeset/chatty-actors-stare.md Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/astro/src/@types/astro.ts Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/astro/src/@types/astro.ts Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/astro/src/@types/astro.ts Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/astro/src/@types/astro.ts Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update docs on dynamic restrictions. * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Code review changes * Document netlify static adapter * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Slight reword * Update .changeset/twenty-suns-vanish.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Add a note about public/_redirects file * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> --------- Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
2023-06-05 13:03:20 +00:00
await fs.promises.appendFile(
new URL('./_redirects', _config.outDir),
trueRedirects.print()
);
}
await fs.promises.writeFile(
new URL('./_routes.json', _config.outDir),
JSON.stringify(
{
version: 1,
include: ['/*'],
exclude: staticPathList,
},
null,
2
)
);
}
},
},
};
}
function prependForwardSlash(path: string) {
return path[0] === '/' ? path : '/' + path;
}