feat!: migrate to TypeScript and implement emulate using Miniflare
Signed-off-by: Pascal Vorwerk <info@fossores.de>
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/files
|
||||
index.js
|
||||
/node_modules
|
||||
16
.prettierrc
Normal file
16
.prettierrc
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"tabWidth": 4,
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"*.yml",
|
||||
"*.yaml"
|
||||
],
|
||||
"options": {
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
8
LICENSE.md
Normal file
8
LICENSE.md
Normal file
@@ -0,0 +1,8 @@
|
||||
Copyright (c) 2024 Pascal Vorwerk
|
||||
Copyright (c) 2020 [these people](https://github.com/sveltejs/kit/graphs/contributors)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
8
README.md
Normal file
8
README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# adapter-cloudflare
|
||||
|
||||
Extended version of the default SvelteKit [Adapter](https://kit.svelte.dev/docs/building-your-app) for [Cloudflare Pages](https://developers.cloudflare.com/pages/) with support for mocking most bindings supported by Cloudflare Workers.
|
||||
|
||||
## Credits
|
||||
|
||||
This adapter is based on the [official adapter](https://github.com/sveltejs/kit/tree/main/packages/adapter-cloudflare) for Cloudflare Pages.
|
||||
Cloudflare Workers bindings are emulated using [Miniflare](https://github.com/cloudflare/miniflare).
|
||||
454
index.ts
Normal file
454
index.ts
Normal file
@@ -0,0 +1,454 @@
|
||||
import { writeFileSync } from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { build, buildSync, formatMessages } from "esbuild";
|
||||
import type { BuildFailure } from "esbuild";
|
||||
import type {
|
||||
Adapter,
|
||||
Builder,
|
||||
Emulator,
|
||||
PrerenderOption,
|
||||
} from "@sveltejs/kit";
|
||||
import { Miniflare } from "miniflare";
|
||||
|
||||
import {
|
||||
Cache,
|
||||
CacheStorage,
|
||||
IncomingRequestCfProperties,
|
||||
} from "@cloudflare/workers-types";
|
||||
|
||||
declare global {
|
||||
namespace App {
|
||||
export interface Platform {
|
||||
context?: {
|
||||
waitUntil(promise: Promise<any>): void;
|
||||
};
|
||||
caches?: CacheStorage & { default: Cache };
|
||||
cf?: IncomingRequestCfProperties;
|
||||
env?: Record<string, any>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type Environment = {
|
||||
variables?: Record<string, string>;
|
||||
kv?: string[];
|
||||
kvPersist?: boolean;
|
||||
d1?: string[];
|
||||
d1Persist?: boolean;
|
||||
durableObjects?: Record<string, { class: string; src: string }>;
|
||||
durableObjectsPersist?: boolean;
|
||||
queueConsumers?: string[];
|
||||
queueProducers?: string[];
|
||||
queuesPersist?: boolean;
|
||||
r2?: string[];
|
||||
r2Persist?: boolean;
|
||||
};
|
||||
|
||||
export type AdapterOptions = {
|
||||
/**
|
||||
* Whether to render a plaintext 404.html page, or a rendered SPA fallback page. This page will
|
||||
* only be served when a request that matches an entry in `routes.exclude` fails to match an asset.
|
||||
*
|
||||
* Most of the time `plaintext` is sufficient, but if you are using `routes.exclude` to manually
|
||||
* exclude a set of prerendered pages without exceeding the 100 route limit, you may wish to
|
||||
* use `spa` instead to avoid showing an unstyled 404 page to users.
|
||||
*
|
||||
* @default 'plaintext'
|
||||
*/
|
||||
fallback?: "plaintext" | "spa";
|
||||
|
||||
/**
|
||||
* Customize the automatically-generated `_routes.json` file
|
||||
* https://developers.cloudflare.com/pages/platform/functions/routing/#create-a-_routesjson-file
|
||||
*/
|
||||
routes?: {
|
||||
/**
|
||||
* Routes that will be invoked by functions. Accepts wildcards.
|
||||
* @default ["/*"]
|
||||
*/
|
||||
include?: string[];
|
||||
|
||||
/**
|
||||
* Routes that will not be invoked by functions. Accepts wildcards.
|
||||
* `exclude` takes priority over `include`.
|
||||
*
|
||||
* To have the adapter automatically exclude certain things, you can use these placeholders:
|
||||
*
|
||||
* - `<build>` to exclude build artifacts (files generated by Vite)
|
||||
* - `<files>` for the contents of your `static` directory
|
||||
* - `<prerendered>` for prerendered routes
|
||||
* - `<all>` to exclude all of the above
|
||||
*
|
||||
* @default ["<all>"]
|
||||
*/
|
||||
exclude?: string[];
|
||||
};
|
||||
|
||||
env?: Environment;
|
||||
};
|
||||
|
||||
export type RoutesJSONSpec = {
|
||||
version: 1;
|
||||
description: string;
|
||||
include: string[];
|
||||
exclude: string[];
|
||||
};
|
||||
|
||||
// list from https://developers.cloudflare.com/workers/runtime-apis/nodejs/
|
||||
const compatible_node_modules = [
|
||||
"assert",
|
||||
"async_hooks",
|
||||
"buffer",
|
||||
"crypto",
|
||||
"diagnostics_channel",
|
||||
"events",
|
||||
"path",
|
||||
"process",
|
||||
"stream",
|
||||
"string_decoder",
|
||||
"util",
|
||||
];
|
||||
|
||||
class CloudflareAdapter implements Adapter {
|
||||
public name = "@sveltejs/adapter-cloudflare";
|
||||
private options: AdapterOptions;
|
||||
constructor(options: AdapterOptions) {
|
||||
this.options = options;
|
||||
}
|
||||
async adapt(builder: Builder) {
|
||||
const files = fileURLToPath(new URL("./files", import.meta.url).href);
|
||||
const dest = builder.getBuildDirectory("cloudflare");
|
||||
const tmp = builder.getBuildDirectory("cloudflare-tmp");
|
||||
|
||||
builder.rimraf(dest);
|
||||
builder.rimraf(tmp);
|
||||
|
||||
builder.mkdirp(dest);
|
||||
builder.mkdirp(tmp);
|
||||
|
||||
// generate plaintext 404.html first which can then be overridden by prerendering, if the user defined such a page
|
||||
const fallback = path.join(dest, "404.html");
|
||||
if (this.options.fallback === "spa") {
|
||||
await builder.generateFallback(fallback);
|
||||
} else {
|
||||
writeFileSync(fallback, "Not Found");
|
||||
}
|
||||
|
||||
const dest_dir = `${dest}${builder.config.kit.paths.base}`;
|
||||
const written_files = builder.writeClient(dest_dir);
|
||||
builder.writePrerendered(dest_dir);
|
||||
|
||||
const relativePath = path.posix.relative(
|
||||
tmp,
|
||||
builder.getServerDirectory()
|
||||
);
|
||||
|
||||
writeFileSync(
|
||||
`${tmp}/manifest.js`,
|
||||
`export const manifest = ${builder.generateManifest({
|
||||
relativePath,
|
||||
})};\n\n` +
|
||||
`export const prerendered = new Set(${JSON.stringify(
|
||||
builder.prerendered.paths
|
||||
)});\n\n` +
|
||||
`export const app_path = ${JSON.stringify(
|
||||
builder.getAppPath()
|
||||
)};\n`
|
||||
);
|
||||
|
||||
writeFileSync(
|
||||
`${dest}/_routes.json`,
|
||||
JSON.stringify(
|
||||
get_routes_json(
|
||||
builder,
|
||||
written_files,
|
||||
this.options.routes ?? {}
|
||||
),
|
||||
null,
|
||||
"\t"
|
||||
)
|
||||
);
|
||||
|
||||
writeFileSync(
|
||||
`${dest}/_headers`,
|
||||
generate_headers(builder.getAppPath()),
|
||||
{ flag: "a" }
|
||||
);
|
||||
|
||||
builder.copy(`${files}/worker.js`, `${tmp}/_worker.js`, {
|
||||
replace: {
|
||||
SERVER: `${relativePath}/index.js`,
|
||||
MANIFEST: "./manifest.js",
|
||||
},
|
||||
});
|
||||
|
||||
const external = [
|
||||
"cloudflare:*",
|
||||
...compatible_node_modules.map((id) => `node:${id}`),
|
||||
];
|
||||
|
||||
try {
|
||||
const result = await build({
|
||||
platform: "browser",
|
||||
conditions: ["worker", "browser"],
|
||||
sourcemap: "linked",
|
||||
target: "es2022",
|
||||
entryPoints: [`${tmp}/_worker.js`],
|
||||
outfile: `${dest}/_worker.js`,
|
||||
allowOverwrite: true,
|
||||
format: "esm",
|
||||
bundle: true,
|
||||
loader: {
|
||||
".wasm": "copy",
|
||||
},
|
||||
external,
|
||||
alias: Object.fromEntries(
|
||||
compatible_node_modules.map((id) => [id, `node:${id}`])
|
||||
),
|
||||
logLevel: "silent",
|
||||
});
|
||||
|
||||
if (result.warnings.length > 0) {
|
||||
const formatted = await formatMessages(result.warnings, {
|
||||
kind: "warning",
|
||||
color: true,
|
||||
});
|
||||
|
||||
console.error(formatted.join("\n"));
|
||||
}
|
||||
} catch (ex) {
|
||||
const error = ex as BuildFailure;
|
||||
for (const e of error.errors) {
|
||||
for (const node of e.notes) {
|
||||
const match =
|
||||
/The package "(.+)" wasn't found on the file system but is built into node/.exec(
|
||||
node.text
|
||||
);
|
||||
|
||||
if (match) {
|
||||
node.text = `Cannot use "${match[1]}" when deploying to Cloudflare.`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const formatted = await formatMessages(error.errors, {
|
||||
kind: "error",
|
||||
color: true,
|
||||
});
|
||||
|
||||
console.error(formatted.join("\n"));
|
||||
|
||||
throw new Error(
|
||||
`Bundling with esbuild failed with ${error.errors.length} ${
|
||||
error.errors.length === 1 ? "error" : "errors"
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
async emulate(): Promise<Emulator> {
|
||||
return new CloudflareEmulator(this.options.env);
|
||||
}
|
||||
}
|
||||
|
||||
class CloudflareEmulator implements Emulator {
|
||||
private miniflare: Miniflare;
|
||||
private bindings: Record<string, any>;
|
||||
private caches: CacheStorage & { default: Cache };
|
||||
private cf: IncomingRequestCfProperties = {
|
||||
clientTcpRtt: 36,
|
||||
longitude: "13.42720",
|
||||
latitude: "52.49410",
|
||||
tlsCipher: "AEAD-AES128-GCM-SHA256",
|
||||
continent: "EU",
|
||||
asn: 64496,
|
||||
clientAcceptEncoding: "gzip, deflate, br",
|
||||
country: "DE",
|
||||
isEUCountry: "1",
|
||||
tlsClientAuth: {
|
||||
certIssuerDNLegacy: "",
|
||||
certIssuerSKI: "",
|
||||
certSubjectDNRFC2253: "",
|
||||
certSubjectDNLegacy: "",
|
||||
certFingerprintSHA256: "",
|
||||
certNotBefore: "",
|
||||
certSKI: "",
|
||||
certSerial: "",
|
||||
certIssuerDN: "",
|
||||
certVerified: "NONE",
|
||||
certNotAfter: "",
|
||||
certSubjectDN: "",
|
||||
certPresented: "0",
|
||||
certRevoked: "0",
|
||||
certIssuerSerial: "",
|
||||
certIssuerDNRFC2253: "",
|
||||
certFingerprintSHA1: "",
|
||||
},
|
||||
tlsExportedAuthenticator: {
|
||||
clientFinished:
|
||||
"a0a93610f5034d0ab72d1f258cffaf85dd8a2a7297d1d38e74552419d1865a54",
|
||||
clientHandshake:
|
||||
"eecba94dbc48ea43ed63bb082e9498e60d0458d986311119600dcb48ecb20647",
|
||||
serverHandshake:
|
||||
"320728f3fa8514c9233f38bdb730c858b24043e3af5beddc723c4fb6d38152d3",
|
||||
serverFinished:
|
||||
"3503a40dd2267ac11ce57993016b007215454b6c935f91e9d2cac56d4b29d580",
|
||||
},
|
||||
tlsVersion: "TLSv1.3",
|
||||
colo: "LHR",
|
||||
timezone: "Europe/Berlin",
|
||||
city: "Berlin",
|
||||
verifiedBotCategory: "",
|
||||
edgeRequestKeepAliveStatus: 1,
|
||||
requestPriority: "weight=256;exclusive=1",
|
||||
httpProtocol: "HTTP/2",
|
||||
region: "Land Berlin",
|
||||
regionCode: "BE",
|
||||
asOrganization: "Example AS Organization",
|
||||
postalCode: "10999",
|
||||
botManagement: {
|
||||
ja3Hash: "",
|
||||
score: 0,
|
||||
corporateProxy: false,
|
||||
detectionIds: [],
|
||||
staticResource: false,
|
||||
verifiedBot: false,
|
||||
},
|
||||
clientTrustScore: 0,
|
||||
hostMetadata: {},
|
||||
};
|
||||
constructor(env: Environment = {}) {
|
||||
let durableObjects: Record<string, { className: string }> = {};
|
||||
let scripts = [];
|
||||
for (const [name, obj] of Object.entries(env.durableObjects ?? {})) {
|
||||
durableObjects[name] = { className: obj.class };
|
||||
scripts.push(obj.src);
|
||||
}
|
||||
const script = buildSync({
|
||||
bundle: true,
|
||||
format: "esm",
|
||||
target: "esnext",
|
||||
write: false,
|
||||
entryPoints: scripts,
|
||||
});
|
||||
this.miniflare = new Miniflare({
|
||||
script:
|
||||
`export default {async fetch() {return new Response("Hello world!");}};` +
|
||||
script.outputFiles[0].text,
|
||||
modules: true,
|
||||
cache: true,
|
||||
kvNamespaces: env.kv,
|
||||
kvPersist: env.kvPersist,
|
||||
durableObjects: env.durableObjects ? durableObjects : undefined,
|
||||
durableObjectsPersist: env.durableObjectsPersist,
|
||||
d1Databases: env.d1,
|
||||
d1Persist: env.d1Persist,
|
||||
queueConsumers: env.queueConsumers,
|
||||
queueProducers: env.queueProducers,
|
||||
r2Buckets: env.r2,
|
||||
r2Persist: env.r2Persist,
|
||||
bindings: env.variables,
|
||||
});
|
||||
this.bindings = this.miniflare.getBindings();
|
||||
// @ts-ignore
|
||||
this.caches = this.miniflare.getCaches();
|
||||
}
|
||||
async platform(details: {
|
||||
config: any;
|
||||
prerender: PrerenderOption;
|
||||
}): Promise<App.Platform> {
|
||||
return {
|
||||
env: await this.bindings,
|
||||
caches: await this.caches,
|
||||
cf: this.cf,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default function cloudflare(options: AdapterOptions = {}): Adapter {
|
||||
return new CloudflareAdapter(options);
|
||||
}
|
||||
|
||||
function get_routes_json(
|
||||
builder: Builder,
|
||||
assets: string[],
|
||||
{ include = ["/*"], exclude = ["<all>"] }: AdapterOptions["routes"] = {}
|
||||
): RoutesJSONSpec {
|
||||
if (!Array.isArray(include) || !Array.isArray(exclude)) {
|
||||
throw new Error("routes.include and routes.exclude must be arrays");
|
||||
}
|
||||
|
||||
if (include.length === 0) {
|
||||
throw new Error("routes.include must contain at least one route");
|
||||
}
|
||||
|
||||
if (include.length > 100) {
|
||||
throw new Error("routes.include must contain 100 or fewer routes");
|
||||
}
|
||||
|
||||
exclude = exclude
|
||||
.flatMap((rule) =>
|
||||
rule === "<all>" ? ["<build>", "<files>", "<prerendered>"] : rule
|
||||
)
|
||||
.flatMap((rule) => {
|
||||
if (rule === "<build>") {
|
||||
return `/${builder.getAppPath()}/*`;
|
||||
}
|
||||
|
||||
if (rule === "<files>") {
|
||||
return assets
|
||||
.filter(
|
||||
(file) =>
|
||||
!(
|
||||
file.startsWith(
|
||||
`${builder.config.kit.appDir}/`
|
||||
) ||
|
||||
file === "_headers" ||
|
||||
file === "_redirects"
|
||||
)
|
||||
)
|
||||
.map((file) => `/${file}`);
|
||||
}
|
||||
|
||||
if (rule === "<prerendered>") {
|
||||
const prerendered = [];
|
||||
for (const path of builder.prerendered.paths) {
|
||||
if (!builder.prerendered.redirects.has(path)) {
|
||||
prerendered.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
return prerendered;
|
||||
}
|
||||
|
||||
return rule;
|
||||
});
|
||||
|
||||
const excess = include.length + exclude.length - 100;
|
||||
if (excess > 0) {
|
||||
const message = `Function includes/excludes exceeds _routes.json limits (see https://developers.cloudflare.com/pages/platform/functions/routing/#limits). Dropping ${excess} exclude rules — this will cause unnecessary function invocations.`;
|
||||
builder.log.warn(message);
|
||||
|
||||
exclude.length -= excess;
|
||||
}
|
||||
|
||||
return {
|
||||
version: 1,
|
||||
description: "Generated by @sveltejs/adapter-cloudflare",
|
||||
include,
|
||||
exclude,
|
||||
};
|
||||
}
|
||||
|
||||
function generate_headers(app_dir: string) {
|
||||
return `
|
||||
# === START AUTOGENERATED SVELTE IMMUTABLE HEADERS ===
|
||||
/${app_dir}/*
|
||||
X-Robots-Tag: noindex
|
||||
Cache-Control: no-cache
|
||||
/${app_dir}/immutable/*
|
||||
! Cache-Control
|
||||
Cache-Control: public, immutable, max-age=31536000
|
||||
# === END AUTOGENERATED SVELTE IMMUTABLE HEADERS ===
|
||||
`.trimEnd();
|
||||
}
|
||||
1613
package-lock.json
generated
Normal file
1613
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
51
package.json
Normal file
51
package.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "@fossoreslp/sveltekit-adapter-cloudflare",
|
||||
"version": "5.0.0",
|
||||
"description": "Adapter for building SvelteKit applications on Cloudflare Pages with Workers integration",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://git.vorwerk.dev/fossoreslp/sveltekit-adapter-cloudflare.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"homepage": "https://kit.svelte.dev",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./index.ts",
|
||||
"import": "./index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"types": "index.ts",
|
||||
"files": [
|
||||
"files",
|
||||
"index.js"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "npm run build:worker && npm run build:adapter",
|
||||
"build:adapter": "esbuild index.ts --outfile=index.js --format=esm",
|
||||
"build:worker": "esbuild src/worker.ts --bundle --outfile=files/worker.js --external:SERVER --external:MANIFEST --format=esm",
|
||||
"lint": "prettier --check .",
|
||||
"format": "npm lint --write",
|
||||
"check": "tsc --skipLibCheck",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cloudflare/workers-types": "^4.20231121.0",
|
||||
"esbuild": "^0.19.11",
|
||||
"miniflare": "^3.20231218.4",
|
||||
"worktop": "0.8.0-next.18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@types/node": "^18.19.3",
|
||||
"@types/ws": "^8.5.10",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@sveltejs/kit": "^2.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
11
placeholders.d.ts
vendored
Normal file
11
placeholders.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
declare module "SERVER" {
|
||||
export { Server } from "@sveltejs/kit";
|
||||
}
|
||||
|
||||
declare module "MANIFEST" {
|
||||
import { SSRManifest } from "@sveltejs/kit";
|
||||
|
||||
export const manifest: SSRManifest;
|
||||
export const prerendered: Set<string>;
|
||||
export const app_path: string;
|
||||
}
|
||||
75
src/worker.ts
Normal file
75
src/worker.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Server } from "SERVER";
|
||||
import { manifest, prerendered, app_path } from "MANIFEST";
|
||||
import * as Cache from "worktop/cfw.cache";
|
||||
import type { Module } from "worktop/cfw";
|
||||
import type { Durable } from "worktop/cfw.durable";
|
||||
|
||||
const server = new Server(manifest);
|
||||
|
||||
const immutable = `/${app_path}/immutable/`;
|
||||
const version_file = `/${app_path}/version.json`;
|
||||
|
||||
const worker: Module.Worker<{ ASSETS: Durable.Object }> = {
|
||||
async fetch(req: Request, env, context): Promise<Response> {
|
||||
// @ts-ignore
|
||||
await server.init({ env });
|
||||
// skip cache if "cache-control: no-cache" in request
|
||||
let pragma = req.headers.get("cache-control") || "";
|
||||
let res = !pragma.includes("no-cache") && (await Cache.lookup(req));
|
||||
if (res) return res;
|
||||
|
||||
let { pathname, search } = new URL(req.url);
|
||||
try {
|
||||
pathname = decodeURIComponent(pathname);
|
||||
} catch {
|
||||
// ignore invalid URI
|
||||
}
|
||||
|
||||
const stripped_pathname = pathname.replace(/\/$/, "");
|
||||
|
||||
// prerendered pages and /static files
|
||||
let is_static_asset = false;
|
||||
const filename = stripped_pathname.substring(1);
|
||||
if (filename) {
|
||||
is_static_asset =
|
||||
manifest.assets.has(filename) ||
|
||||
manifest.assets.has(filename + "/index.html");
|
||||
}
|
||||
|
||||
let location =
|
||||
pathname.at(-1) === "/" ? stripped_pathname : pathname + "/";
|
||||
|
||||
if (
|
||||
is_static_asset ||
|
||||
prerendered.has(pathname) ||
|
||||
pathname === version_file ||
|
||||
pathname.startsWith(immutable)
|
||||
) {
|
||||
res = await env.ASSETS.fetch(req);
|
||||
} else if (location && prerendered.has(location)) {
|
||||
if (search) location += search;
|
||||
res = new Response("", {
|
||||
status: 308,
|
||||
headers: {
|
||||
location,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// dynamically-generated pages
|
||||
res = await server.respond(req, {
|
||||
// @ts-ignore
|
||||
platform: { env, context, caches, cf: req.cf },
|
||||
getClientAddress() {
|
||||
return req.headers.get("cf-connecting-ip")!;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// write to `Cache` only if response is not an error,
|
||||
// let `Cache.save` handle the Cache-Control and Vary headers
|
||||
pragma = res.headers.get("cache-control") || "";
|
||||
return pragma && res.status < 400 ? Cache.save(req, res, context) : res;
|
||||
},
|
||||
};
|
||||
|
||||
export default worker;
|
||||
15
tsconfig.json
Normal file
15
tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"noEmit": true,
|
||||
"noImplicitAny": true,
|
||||
"target": "es2022",
|
||||
"module": "node16",
|
||||
"moduleResolution": "node16",
|
||||
"baseUrl": ".",
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["index.ts", "placeholders.d.ts", "src/worker.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user