Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .changeset/open-monkeys-boil.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,25 @@
Development server now runs in workerd

`astro dev` now runs your Cloudflare application using Cloudflare's workerd runtime instead of Node.js. This means your development environment is now a near-exact replica of your production environment—the same JavaScript engine, the same APIs, the same behavior. You'll catch issues during development that would have only appeared in production, and features like Durable Objects, Workers Analytics Engine, and R2 bindings work exactly as they do on Cloudflare's platform.

**Breaking Changes:**

- `Astro.locals.runtime` no longer contains the `env` object. Instead, import it directly:
```js
import { env } from 'cloudflare:workers';
```

- `Astro.locals.runtime` no longer contains the `cf` object. Instead, access it directly from the request:
```js
Astro.request.cf
```

- `Astro.locals.runtime` no longer contains the `caches` object. Instead, use the global `caches` object directly:
```js
caches.default.put(request, response)
```

- `Astro.locals.runtime` object is replaced with `Astro.locals.cfContext` which contains the Cloudflare `ExecutionContext`:
```js
const cfContext = Astro.locals.cfContext;
```
5 changes: 3 additions & 2 deletions packages/integrations/cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@
"./entrypoints/server": "./dist/entrypoints/server.js",
"./entrypoints/preview": "./dist/entrypoints/preview.js",
"./entrypoints/server.js": "./dist/entrypoints/server.js",
"./entrypoints/middleware.js": "./dist/entrypoints/middleware.js",
"./image-service": "./dist/entrypoints/image-service.js",
"./image-endpoint": "./dist/entrypoints/image-endpoint.js",
"./image-transform-endpoint": "./dist/entrypoints/image-transform-endpoint.js",
"./handler": "./dist/utils/handler.js",
"./types.d.ts": "./types.d.ts",
"./package.json": "./package.json"
},
"files": [
"dist"
"dist",
"types.d.ts"
],
"scripts": {
"dev": "astro-scripts dev \"src/**/*.ts\"",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const prerender = false;

// @ts-expect-error The Header types between libdom and @cloudflare/workers-types are causing issues
export const GET: APIRoute = async (ctx) => {
const { env } = await import('cloudflare:workers');
// @ts-expect-error The runtime locals types are not populated here
return transform(ctx.request.url, ctx.locals.runtime.env.IMAGES, ctx.locals.runtime.env.ASSETS);
return transform(ctx.request.url, env.IMAGES, env.ASSETS);
};
12 changes: 0 additions & 12 deletions packages/integrations/cloudflare/src/entrypoints/middleware.ts

This file was deleted.

90 changes: 18 additions & 72 deletions packages/integrations/cloudflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createReadStream, writeFileSync } from 'node:fs';
import { appendFile, stat } from 'node:fs/promises';
import { createRequire } from 'node:module';
import { createInterface } from 'node:readline/promises';
import { pathToFileURL } from 'node:url';
import { pathToFileURL, fileURLToPath } from 'node:url';
import {
appendForwardSlash,
prependForwardSlash,
Expand All @@ -16,12 +16,9 @@ import type {
HookParameters,
IntegrationResolvedRoute,
} from 'astro';
import { AstroError } from 'astro/errors';
import type { PluginOption } from 'vite';
import { defaultClientConditions } from 'vite';
import { type GetPlatformProxyOptions, getPlatformProxy } from 'wrangler';
import { cloudflareModuleLoader } from './utils/cloudflare-module-loader.js';
import { createGetEnv } from './utils/env.js';
import { createRoutesFile, getParts } from './utils/generate-routes-json.js';
import { type ImageService, setImageConfig } from './utils/image-config.js';
import { createConfigPlugin } from './vite-plugin-config.js';
Expand Down Expand Up @@ -56,13 +53,6 @@ export type Options = {
}[];
};
};
/**
* Proxy configuration for the platform.
*/
platformProxy?: GetPlatformProxyOptions & {
/** Toggle the proxy. Default `undefined`, which equals to `true`. */
enabled?: boolean;
};

/**
* Allow bundling cloudflare worker specific file types as importable modules. Defaults to true.
Expand Down Expand Up @@ -143,19 +133,6 @@ function wrapWithSlashes(path: string): string {
return prependForwardSlash(appendForwardSlash(path));
}

function setProcessEnv(config: AstroConfig, env: Record<string, unknown>) {
const getEnv = createGetEnv(env);

if (config.env?.schema) {
for (const key of Object.keys(config.env.schema)) {
const value = getEnv(key);
if (value !== undefined) {
process.env[key] = value;
}
}
}
}

export default function createIntegration(args?: Options): AstroIntegration {
let _config: AstroConfig;
let finalBuildOutput: HookParameters<'astro:config:done'>['buildOutput'];
Expand All @@ -177,7 +154,6 @@ export default function createIntegration(args?: Options): AstroIntegration {
updateConfig,
logger,
addWatchFile,
addMiddleware,
createCodegenDir,
}) => {
let session = config.session;
Expand Down Expand Up @@ -215,7 +191,7 @@ export default function createIntegration(args?: Options): AstroIntegration {
const codegenDir = createCodegenDir();
const cachedFile = new URL('wrangler.json', codegenDir);
writeFileSync(cachedFile, wranglerTemplate(), 'utf-8');
cfPluginConfig.configPath = cachedFile.pathname;
cfPluginConfig.configPath = fileURLToPath(cachedFile);
}

updateConfig({
Expand All @@ -227,6 +203,13 @@ export default function createIntegration(args?: Options): AstroIntegration {
},
session,
vite: {
ssr: {
optimizeDeps: {
// Disabled to prevent "prebundle" errors on first dev
// This can be removed when the issue is resolved with Cloudflare
noDiscovery: true,
},
},
plugins: [
cfVitePlugin(cfPluginConfig),
// https://developers.cloudflare.com/pages/functions/module-support/
Expand Down Expand Up @@ -262,26 +245,23 @@ export default function createIntegration(args?: Options): AstroIntegration {
image: setImageConfig(args?.imageService ?? 'compile', config.image, command, logger),
});

if (args?.platformProxy?.configPath) {
addWatchFile(new URL(args.platformProxy.configPath, config.root));
} else {
addWatchFile(new URL('./wrangler.toml', config.root));
addWatchFile(new URL('./wrangler.json', config.root));
addWatchFile(new URL('./wrangler.jsonc', config.root));
}
addWatchFile(new URL('./wrangler.toml', config.root));
addWatchFile(new URL('./wrangler.json', config.root));
addWatchFile(new URL('./wrangler.jsonc', config.root));

addMiddleware({
entrypoint: '@astrojs/cloudflare/entrypoints/middleware.js',
order: 'pre',
});
},
'astro:routes:resolved': ({ routes }) => {
_routes = routes;
},
'astro:config:done': ({ setAdapter, config, buildOutput }) => {
'astro:config:done': ({ setAdapter, config, buildOutput, injectTypes }) => {
_config = config;
finalBuildOutput = buildOutput;

injectTypes({
filename: 'cloudflare.d.ts',
content: '/// <reference types="@astrojs/cloudflare/types.d.ts" />',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may need to update the package.json exports to make it work

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done, thanks

});

let customWorkerEntryPoint: URL | undefined;
if (args?.workerEntryPoint && typeof args.workerEntryPoint.path === 'string') {
const require = createRequire(config.root);
Expand Down Expand Up @@ -318,40 +298,6 @@ export default function createIntegration(args?: Options): AstroIntegration {
},
});
},
'astro:server:setup': async ({ server }) => {
if ((args?.platformProxy?.enabled ?? true) === true) {
const platformProxy = await getPlatformProxy(args?.platformProxy);

// Ensures the dev server doesn't hang
server.httpServer?.on('close', async () => {
await platformProxy.dispose();
});

setProcessEnv(_config, platformProxy.env);

const clientLocalsSymbol = Symbol.for('astro.locals');

server.middlewares.use(async function middleware(req, _res, next) {
Reflect.set(req, clientLocalsSymbol, {
runtime: {
env: platformProxy.env,
cf: platformProxy.cf,
caches: platformProxy.caches,
ctx: {
waitUntil: (promise: Promise<any>) => platformProxy.ctx.waitUntil(promise),
// Currently not available: https://developers.cloudflare.com/pages/platform/known-issues/#pages-functions
passThroughOnException: () => {
throw new AstroError(
'`passThroughOnException` is currently not available in Cloudflare Pages. See https://developers.cloudflare.com/pages/platform/known-issues/#pages-functions.',
);
},
},
},
});
next();
});
}
},
'astro:build:setup': ({ vite, target }) => {
if (target === 'server') {
vite.resolve ||= {};
Expand Down
27 changes: 3 additions & 24 deletions packages/integrations/cloudflare/src/utils/handler.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
// @ts-expect-error - It is safe to expect the error here.
import { env as globalEnv } from 'cloudflare:workers';
import { sessionKVBindingName } from 'virtual:astro-cloudflare:config';
import type {
Response as CfResponse,
CacheStorage as CloudflareCacheStorage,
ExecutionContext,
ExportedHandlerFetchHandler,
} from '@cloudflare/workers-types';
Expand All @@ -18,13 +16,8 @@ export type Env = {

setGetEnv(createGetEnv(globalEnv as Env));

export interface Runtime<T extends object = object> {
runtime: {
env: Env & T;
cf: Parameters<ExportedHandlerFetchHandler>[0]['cf'];
caches: CloudflareCacheStorage;
ctx: ExecutionContext;
};
export interface Runtime {
cfContext: ExecutionContext;
}

declare global {
Expand Down Expand Up @@ -64,21 +57,7 @@ export async function handle(
}

const locals: Runtime = {
runtime: {
env: env,
cf: request.cf,
caches: caches as unknown as CloudflareCacheStorage,
ctx: {
waitUntil: (promise: Promise<any>) => context.waitUntil(promise),
// Currently not available: https://developers.cloudflare.com/pages/platform/known-issues/#pages-functions
passThroughOnException: () => {
throw new Error(
'`passThroughOnException` is currently not available in Cloudflare Pages. See https://developers.cloudflare.com/pages/platform/known-issues/#pages-functions.',
);
},
props: {},
},
},
cfContext: context,
};

const response = await app.render(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,14 @@ describe('AstroDevPlatform', () => {
logLevel: 'debug',
});
devServer = await fixture.startDevServer();
// Do an initial request to prime preloading
await fixture.fetch('/');
});

after(async () => {
await devServer.stop();
});

it('exists', async () => {
const res = await fixture.fetch('/');
const html = await res.text();
const $ = cheerio.load(html);
assert.equal($('#hasRuntime').text().includes('true'), true);
});

it('adds cf object', async () => {
const res = await fixture.fetch('/');
const html = await res.text();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe(
const res = await fixture.fetch('/_image?href=//placehold.co/600x400');
const html = await res.text();
const status = res.status;
assert.equal(html, 'Forbidden');
assert.equal(html, 'Blocked');
assert.equal(status, 403);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
---
const runtime = Astro.locals.runtime;
---

<!doctype html>
Expand All @@ -10,6 +9,6 @@ const runtime = Astro.locals.runtime;
<title>CACHES</title>
</head>
<body>
<pre id="hasCACHE">{!!runtime.caches}</pre>
<pre id="hasCACHE">{!!caches}</pre>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
const runtime = Astro.locals.runtime;
const db = runtime.env?.D1;
import { env } from 'cloudflare:workers';
const db = env.D1;
await db.exec("CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)");
await db.exec("INSERT INTO test (name) VALUES ('true')");
const result = await db.prepare("SELECT * FROM test").all();
Expand All @@ -14,8 +14,8 @@ const result = await db.prepare("SELECT * FROM test").all();
<title>D1</title>
</head>
<body>
<pre id="hasDB">{!!runtime.env?.D1}</pre>
<pre id="hasPRODDB">{!!runtime.env?.D1_PROD}</pre>
<pre id="hasDB">{!!env.D1}</pre>
<pre id="hasPRODDB">{!!env.D1_PROD}</pre>
<pre id="hasACCESS">{!!result.results[0].name}</pre>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
---
const runtime = Astro.locals.runtime;
const request = Astro.request;
---
<html>
<head>
<title>Testing</title>
</head>
<body>
<h1>Testing</h1>
<div id="hasRuntime">{!!runtime}</div>
<div id="hasCF">{!!runtime.cf?.colo}</div>
<div id="hasCF">{!!request.cf?.colo}</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
const runtime = Astro.locals.runtime;
const kv = runtime.env?.KV;
import { env } from "cloudflare:workers";
const kv = env.KV;
await kv.put("test", "true");
const result = await kv.get("test")
---
Expand All @@ -13,8 +13,8 @@ const result = await kv.get("test")
<title>KV</title>
</head>
<body>
<pre id="hasKV">{!!runtime.env?.KV}</pre>
<pre id="hasPRODKV">{!!runtime.env?.KV_PROD}</pre>
<pre id="hasKV">{!!env.KV}</pre>
<pre id="hasPRODKV">{!!env.KV_PROD}</pre>
<pre id="hasACCESS">{!!result}</pre>
</body>
</html>
Loading