Skip to content

Commit

Permalink
chore(cloudflare): refactor structure, optimize patterns (#8654)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
Co-authored-by: 100gle <loogle.space@gmail.com>
  • Loading branch information
3 people authored Sep 28, 2023
1 parent 23e7b25 commit f6ba533
Show file tree
Hide file tree
Showing 13 changed files with 313 additions and 308 deletions.
5 changes: 5 additions & 0 deletions .changeset/olive-jeans-reply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/cloudflare': patch
---

Refactor codebase to enhance code readability and structure, to prioritize maintainability for long-term.
212 changes: 106 additions & 106 deletions packages/integrations/cloudflare/README.md

Large diffs are not rendered by default.

11 changes: 3 additions & 8 deletions packages/integrations/cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,12 @@
"homepage": "https://docs.astro.build/en/guides/integrations-guide/cloudflare/",
"exports": {
".": "./dist/index.js",
"./runtime": {
"types": "./runtime.d.ts",
"default": "./dist/runtime.js"
},
"./server.advanced.js": "./dist/server.advanced.js",
"./server.directory.js": "./dist/server.directory.js",
"./entrypoints/server.advanced.js": "./dist/entrypoints/server.advanced.js",
"./entrypoints/server.directory.js": "./dist/entrypoints/server.directory.js",
"./package.json": "./package.json"
},
"files": [
"dist",
"runtime.d.ts"
"dist"
],
"scripts": {
"build": "astro-scripts build \"src/**/*.ts\" && tsc",
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
Expand Down
40 changes: 40 additions & 0 deletions packages/integrations/cloudflare/src/getAdapter.ts
Original file line number Diff line number Diff line change
@@ -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,
};
}
221 changes: 29 additions & 192 deletions packages/integrations/cloudflare/src/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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.'
);
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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
};
});
},
};
}
26 changes: 26 additions & 0 deletions packages/integrations/cloudflare/src/utils/deduplicatePatterns.ts
Original file line number Diff line number Diff line change
@@ -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;
});
}
Loading

0 comments on commit f6ba533

Please sign in to comment.