diff options
Diffstat (limited to 'packages/integrations/cloudflare/src')
10 files changed, 199 insertions, 194 deletions
diff --git a/packages/integrations/cloudflare/src/server.advanced.ts b/packages/integrations/cloudflare/src/entrypoints/server.advanced.ts index ac6e0fe55..957c1791d 100644 --- a/packages/integrations/cloudflare/src/server.advanced.ts +++ b/packages/integrations/cloudflare/src/entrypoints/server.advanced.ts @@ -1,7 +1,7 @@ import type { Request as CFRequest, ExecutionContext } from '@cloudflare/workers-types'; import type { SSRManifest } from 'astro'; import { App } from 'astro/app'; -import { getProcessEnvProxy, isNode } from './util.js'; +import { getProcessEnvProxy, isNode } from '../util.js'; if (!isNode) { process.env = getProcessEnvProxy(); diff --git a/packages/integrations/cloudflare/src/server.directory.ts b/packages/integrations/cloudflare/src/entrypoints/server.directory.ts index ffd4ba87a..3542279b0 100644 --- a/packages/integrations/cloudflare/src/server.directory.ts +++ b/packages/integrations/cloudflare/src/entrypoints/server.directory.ts @@ -1,7 +1,7 @@ import type { Request as CFRequest, EventContext } from '@cloudflare/workers-types'; import type { SSRManifest } from 'astro'; import { App } from 'astro/app'; -import { getProcessEnvProxy, isNode } from './util.js'; +import { getProcessEnvProxy, isNode } from '../util.js'; if (!isNode) { process.env = getProcessEnvProxy(); diff --git a/packages/integrations/cloudflare/src/getAdapter.ts b/packages/integrations/cloudflare/src/getAdapter.ts new file mode 100644 index 000000000..0cc1263a1 --- /dev/null +++ b/packages/integrations/cloudflare/src/getAdapter.ts @@ -0,0 +1,40 @@ +import type { AstroAdapter, AstroFeatureMap } from 'astro'; + +export function getAdapter({ + isModeDirectory, + functionPerRoute, +}: { + isModeDirectory: boolean; + functionPerRoute: boolean; +}): AstroAdapter { + const astroFeatures = { + hybridOutput: 'stable', + staticOutput: 'unsupported', + serverOutput: 'stable', + assets: { + supportKind: 'stable', + isSharpCompatible: false, + isSquooshCompatible: false, + }, + } satisfies AstroFeatureMap; + + if (isModeDirectory) { + return { + name: '@astrojs/cloudflare', + serverEntrypoint: '@astrojs/cloudflare/entrypoints/server.directory.js', + exports: ['onRequest', 'manifest'], + adapterFeatures: { + functionPerRoute, + edgeMiddleware: false, + }, + supportedAstroFeatures: astroFeatures, + }; + } + + return { + name: '@astrojs/cloudflare', + serverEntrypoint: '@astrojs/cloudflare/entrypoints/server.advanced.js', + exports: ['default'], + supportedAstroFeatures: astroFeatures, + }; +} diff --git a/packages/integrations/cloudflare/src/index.ts b/packages/integrations/cloudflare/src/index.ts index fa2ea3198..12ff00a54 100644 --- a/packages/integrations/cloudflare/src/index.ts +++ b/packages/integrations/cloudflare/src/index.ts @@ -1,5 +1,4 @@ -import type { IncomingRequestCfProperties } from '@cloudflare/workers-types/experimental'; -import type { AstroAdapter, AstroConfig, AstroIntegration, RouteData } from 'astro'; +import type { AstroConfig, AstroIntegration, RouteData } from 'astro'; import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects'; import { CacheStorage } from '@miniflare/cache'; @@ -9,14 +8,19 @@ import { AstroError } from 'astro/errors'; import esbuild from 'esbuild'; import * as fs from 'node:fs'; import * as os from 'node:os'; -import { basename, dirname, relative, sep } from 'node:path'; +import { dirname, relative, sep } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import glob from 'tiny-glob'; -import { getEnvVars } from './parser.js'; -import { wasmModuleLoader } from './wasm-module-loader.js'; +import { getAdapter } from './getAdapter.js'; +import { deduplicatePatterns } from './utils/deduplicatePatterns.js'; +import { getCFObject } from './utils/getCFObject.js'; +import { getEnvVars } from './utils/parser.js'; +import { prependForwardSlash } from './utils/prependForwardSlash.js'; +import { rewriteWasmImportPath } from './utils/rewriteWasmImportPath.js'; +import { wasmModuleLoader } from './utils/wasm-module-loader.js'; -export type { AdvancedRuntime } from './server.advanced.js'; -export type { DirectoryRuntime } from './server.directory.js'; +export type { AdvancedRuntime } from './entrypoints/server.advanced.js'; +export type { DirectoryRuntime } from './entrypoints/server.directory.js'; type Options = { mode?: 'directory' | 'advanced'; @@ -62,134 +66,13 @@ class StorageFactory { } } -export function getAdapter({ - isModeDirectory, - functionPerRoute, -}: { - isModeDirectory: boolean; - functionPerRoute: boolean; -}): AstroAdapter { - return isModeDirectory - ? { - name: '@astrojs/cloudflare', - serverEntrypoint: '@astrojs/cloudflare/server.directory.js', - exports: ['onRequest', 'manifest'], - adapterFeatures: { - functionPerRoute, - edgeMiddleware: false, - }, - supportedAstroFeatures: { - hybridOutput: 'stable', - staticOutput: 'unsupported', - serverOutput: 'stable', - assets: { - supportKind: 'stable', - isSharpCompatible: false, - isSquooshCompatible: false, - }, - }, - } - : { - name: '@astrojs/cloudflare', - serverEntrypoint: '@astrojs/cloudflare/server.advanced.js', - exports: ['default'], - supportedAstroFeatures: { - hybridOutput: 'stable', - staticOutput: 'unsupported', - serverOutput: 'stable', - assets: { - supportKind: 'stable', - isSharpCompatible: false, - isSquooshCompatible: false, - }, - }, - }; -} - -async function getCFObject(runtimeMode: string): Promise<IncomingRequestCfProperties | void> { - const CF_ENDPOINT = 'https://workers.cloudflare.com/cf.json'; - const CF_FALLBACK: IncomingRequestCfProperties = { - asOrganization: '', - asn: 395747, - colo: 'DFW', - city: 'Austin', - region: 'Texas', - regionCode: 'TX', - metroCode: '635', - postalCode: '78701', - country: 'US', - continent: 'NA', - timezone: 'America/Chicago', - latitude: '30.27130', - longitude: '-97.74260', - clientTcpRtt: 0, - httpProtocol: 'HTTP/1.1', - requestPriority: 'weight=192;exclusive=0', - tlsCipher: 'AEAD-AES128-GCM-SHA256', - tlsVersion: 'TLSv1.3', - tlsClientAuth: { - certPresented: '0', - certVerified: 'NONE', - certRevoked: '0', - certIssuerDN: '', - certSubjectDN: '', - certIssuerDNRFC2253: '', - certSubjectDNRFC2253: '', - certIssuerDNLegacy: '', - certSubjectDNLegacy: '', - certSerial: '', - certIssuerSerial: '', - certSKI: '', - certIssuerSKI: '', - certFingerprintSHA1: '', - certFingerprintSHA256: '', - certNotBefore: '', - certNotAfter: '', - }, - edgeRequestKeepAliveStatus: 0, - hostMetadata: undefined, - clientTrustScore: 99, - botManagement: { - corporateProxy: false, - verifiedBot: false, - ja3Hash: '25b4882c2bcb50cd6b469ff28c596742', - staticResource: false, - detectionIds: [], - score: 99, - }, - }; - - if (runtimeMode === 'local') { - return CF_FALLBACK; - } else if (runtimeMode === 'remote') { - try { - const res = await fetch(CF_ENDPOINT); - const cfText = await res.text(); - const storedCf = JSON.parse(cfText); - return storedCf; - } catch (e: any) { - return CF_FALLBACK; - } - } -} - -const SHIM = `globalThis.process = { - argv: [], - env: {}, -};`; - -const SERVER_BUILD_FOLDER = '/$server_build/'; - -/** - * These route types are candiates for being part of the `_routes.json` `include` array. - */ -const potentialFunctionRouteTypes = ['endpoint', 'page']; - export default function createIntegration(args?: Options): AstroIntegration { let _config: AstroConfig; let _buildConfig: BuildConfig; let _entryPoints = new Map<RouteData, URL>(); + const SERVER_BUILD_FOLDER = '/$server_build/'; + const isModeDirectory = args?.mode === 'directory'; const functionPerRoute = args?.functionPerRoute ?? false; const runtimeMode = args?.runtime ?? 'off'; @@ -221,13 +104,13 @@ export default function createIntegration(args?: Options): AstroIntegration { _config = config; _buildConfig = config.build; - if (config.output === 'static') { + if (_config.output === 'static') { throw new AstroError( '[@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) { + if (_config.base === SERVER_BUILD_FOLDER) { throw new AstroError( '[@astrojs/cloudflare] `base: "${SERVER_BUILD_FOLDER}"` is not allowed. Please change your `base` config to something else.' ); @@ -372,7 +255,10 @@ export default function createIntegration(args?: Options): AstroIntegration { bundle: true, minify: _config.vite?.build?.minify !== false, banner: { - js: SHIM, + js: `globalThis.process = { + argv: [], + env: {}, + };`, }, logOverride: { 'ignored-bare-import': 'silent', @@ -449,7 +335,10 @@ export default function createIntegration(args?: Options): AstroIntegration { bundle: true, minify: _config.vite?.build?.minify !== false, banner: { - js: SHIM, + js: `globalThis.process = { + argv: [], + env: {}, + };`, }, logOverride: { 'ignored-bare-import': 'silent', @@ -506,10 +395,14 @@ export default function createIntegration(args?: Options): AstroIntegration { // 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) { + /** + * These route types are candiates for being part of the `_routes.json` `include` array. + */ + const potentialFunctionRouteTypes = ['endpoint', 'page']; + const functionEndpoints = routes - // Certain route types, when their prerender option is set to false, a run on the server as function invocations + // Certain route types, when their prerender option is set to false, run on the server as function invocations .filter((route) => potentialFunctionRouteTypes.includes(route.type) && !route.prerender) .map((route) => { const includePattern = @@ -672,59 +565,3 @@ export default function createIntegration(args?: Options): AstroIntegration { }, }; } - -function prependForwardSlash(path: string) { - return path[0] === '/' ? path : '/' + path; -} - -/** - * Remove duplicates and redundant patterns from an `include` or `exclude` list. - * Otherwise Cloudflare will throw an error on deployment. Plus, it saves more entries. - * E.g. `['/foo/*', '/foo/*', '/foo/bar'] => ['/foo/*']` - * @param patterns a list of `include` or `exclude` patterns - * @returns a deduplicated list of patterns - */ -function deduplicatePatterns(patterns: string[]) { - const openPatterns: RegExp[] = []; - - return [...new Set(patterns)] - .sort((a, b) => a.length - b.length) - .filter((pattern) => { - if (openPatterns.some((p) => p.test(pattern))) { - return false; - } - - if (pattern.endsWith('*')) { - openPatterns.push(new RegExp(`^${pattern.replace(/(\*\/)*\*$/g, '.*')}`)); - } - - return true; - }); -} - -/** - * - * @param relativePathToAssets - relative path from the final location for the current esbuild output bundle, to the assets directory. - */ -function rewriteWasmImportPath({ - relativePathToAssets, -}: { - relativePathToAssets: string; -}): esbuild.Plugin { - return { - name: 'wasm-loader', - setup(build) { - build.onResolve({ filter: /.*\.wasm.mjs$/ }, (args) => { - const updatedPath = [ - relativePathToAssets.replaceAll('\\', '/'), - basename(args.path).replace(/\.mjs$/, ''), - ].join('/'); - - return { - path: updatedPath, // change the reference to the changed module - external: true, // mark it as external in the bundle - }; - }); - }, - }; -} diff --git a/packages/integrations/cloudflare/src/utils/deduplicatePatterns.ts b/packages/integrations/cloudflare/src/utils/deduplicatePatterns.ts new file mode 100644 index 000000000..37743fe55 --- /dev/null +++ b/packages/integrations/cloudflare/src/utils/deduplicatePatterns.ts @@ -0,0 +1,26 @@ +/** + * Remove duplicates and redundant patterns from an `include` or `exclude` list. + * Otherwise Cloudflare will throw an error on deployment. Plus, it saves more entries. + * E.g. `['/foo/*', '/foo/*', '/foo/bar'] => ['/foo/*']` + * @param patterns a list of `include` or `exclude` patterns + * @returns a deduplicated list of patterns + */ +export function deduplicatePatterns(patterns: string[]) { + const openPatterns: RegExp[] = []; + + // A value in the set may only occur once; it is unique in the set's collection. + // ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set + return [...new Set(patterns)] + .sort((a, b) => a.length - b.length) + .filter((pattern) => { + if (openPatterns.some((p) => p.test(pattern))) { + return false; + } + + if (pattern.endsWith('*')) { + openPatterns.push(new RegExp(`^${pattern.replace(/(\*\/)*\*$/g, '.*')}`)); + } + + return true; + }); +} diff --git a/packages/integrations/cloudflare/src/utils/getCFObject.ts b/packages/integrations/cloudflare/src/utils/getCFObject.ts new file mode 100644 index 000000000..7a4cd8a0c --- /dev/null +++ b/packages/integrations/cloudflare/src/utils/getCFObject.ts @@ -0,0 +1,70 @@ +import type { IncomingRequestCfProperties } from '@cloudflare/workers-types/experimental'; + +export async function getCFObject( + runtimeMode: string +): Promise<IncomingRequestCfProperties | void> { + const CF_ENDPOINT = 'https://workers.cloudflare.com/cf.json'; + const CF_FALLBACK: IncomingRequestCfProperties = { + asOrganization: '', + asn: 395747, + colo: 'DFW', + city: 'Austin', + region: 'Texas', + regionCode: 'TX', + metroCode: '635', + postalCode: '78701', + country: 'US', + continent: 'NA', + timezone: 'America/Chicago', + latitude: '30.27130', + longitude: '-97.74260', + clientTcpRtt: 0, + httpProtocol: 'HTTP/1.1', + requestPriority: 'weight=192;exclusive=0', + tlsCipher: 'AEAD-AES128-GCM-SHA256', + tlsVersion: 'TLSv1.3', + tlsClientAuth: { + certPresented: '0', + certVerified: 'NONE', + certRevoked: '0', + certIssuerDN: '', + certSubjectDN: '', + certIssuerDNRFC2253: '', + certSubjectDNRFC2253: '', + certIssuerDNLegacy: '', + certSubjectDNLegacy: '', + certSerial: '', + certIssuerSerial: '', + certSKI: '', + certIssuerSKI: '', + certFingerprintSHA1: '', + certFingerprintSHA256: '', + certNotBefore: '', + certNotAfter: '', + }, + edgeRequestKeepAliveStatus: 0, + hostMetadata: undefined, + clientTrustScore: 99, + botManagement: { + corporateProxy: false, + verifiedBot: false, + ja3Hash: '25b4882c2bcb50cd6b469ff28c596742', + staticResource: false, + detectionIds: [], + score: 99, + }, + }; + + if (runtimeMode === 'local') { + return CF_FALLBACK; + } else if (runtimeMode === 'remote') { + try { + const res = await fetch(CF_ENDPOINT); + const cfText = await res.text(); + const storedCf = JSON.parse(cfText); + return storedCf; + } catch (e: any) { + return CF_FALLBACK; + } + } +} diff --git a/packages/integrations/cloudflare/src/parser.ts b/packages/integrations/cloudflare/src/utils/parser.ts index e9a9cdd00..e9a9cdd00 100644 --- a/packages/integrations/cloudflare/src/parser.ts +++ b/packages/integrations/cloudflare/src/utils/parser.ts diff --git a/packages/integrations/cloudflare/src/utils/prependForwardSlash.ts b/packages/integrations/cloudflare/src/utils/prependForwardSlash.ts new file mode 100644 index 000000000..b66b588f3 --- /dev/null +++ b/packages/integrations/cloudflare/src/utils/prependForwardSlash.ts @@ -0,0 +1,3 @@ +export function prependForwardSlash(path: string) { + return path[0] === '/' ? path : '/' + path; +} diff --git a/packages/integrations/cloudflare/src/utils/rewriteWasmImportPath.ts b/packages/integrations/cloudflare/src/utils/rewriteWasmImportPath.ts new file mode 100644 index 000000000..ada19bb56 --- /dev/null +++ b/packages/integrations/cloudflare/src/utils/rewriteWasmImportPath.ts @@ -0,0 +1,29 @@ +import esbuild from 'esbuild'; +import { basename } from 'node:path'; + +/** + * + * @param relativePathToAssets - relative path from the final location for the current esbuild output bundle, to the assets directory. + */ +export function rewriteWasmImportPath({ + relativePathToAssets, +}: { + relativePathToAssets: string; +}): esbuild.Plugin { + return { + name: 'wasm-loader', + setup(build) { + build.onResolve({ filter: /.*\.wasm.mjs$/ }, (args) => { + const updatedPath = [ + relativePathToAssets.replaceAll('\\', '/'), + basename(args.path).replace(/\.mjs$/, ''), + ].join('/'); + + return { + path: updatedPath, + external: true, // mark it as external in the bundle + }; + }); + }, + }; +} diff --git a/packages/integrations/cloudflare/src/wasm-module-loader.ts b/packages/integrations/cloudflare/src/utils/wasm-module-loader.ts index 7d34d48c3..7d34d48c3 100644 --- a/packages/integrations/cloudflare/src/wasm-module-loader.ts +++ b/packages/integrations/cloudflare/src/utils/wasm-module-loader.ts |