0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-16 21:46:22 -05:00

feat(@astrojs/cloudfalre): add cloudflare envs to Astro.locals (#7541)

* add support for advanced mode

* add support for directory mode

* use asset fallback as in cloudflare's docs

* update locals

* come up with new runtime in `Astro.locals`

* add overwrite protection

* minor cleanup

* changeset

* address review comments

* move overwrite protection to adapter

* fix types

* fix comment

* resolve review comments

* update changeset

* add test

* redo ts

* fix integration test port

* updated tests, add new port

* add TODO comment

* update changeset

* add JSDoc

* Update packages/integrations/cloudflare/src/runtime.ts

---------

Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
This commit is contained in:
Alexander Niebuhr 2023-08-10 17:19:00 +02:00 committed by GitHub
parent 22c944712c
commit ffcfcddb75
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 176 additions and 33 deletions

View file

@ -0,0 +1,12 @@
---
'@astrojs/cloudflare': minor
---
The `getRuntime` utility has been deprecated and should be updated to the new [`Astro.locals`](https://docs.astro.build/en/guides/middleware/#locals) API.
```diff
- import { getRuntime } from '@astrojs/cloudflare/runtime';
- getRuntime(Astro.request);
+ const runtime = Astro.locals.runtime;
```

View file

@ -73,12 +73,10 @@ It's then possible to update the preview script in your `package.json` to `"prev
## Access to the Cloudflare runtime
You can access all the Cloudflare bindings and environment variables from Astro components and API routes through the adapter API.
You can access all the Cloudflare bindings and environment variables from Astro components and API routes through `Astro.locals`.
```js
import { getRuntime } from '@astrojs/cloudflare/runtime';
getRuntime(Astro.request);
const env = Astro.locals.runtime.env;
```
Depending on your adapter mode (advanced = worker, directory = pages), the runtime object will look a little different due to differences in the Cloudflare API.

View file

@ -21,15 +21,15 @@ interface BuildConfig {
export function getAdapter(isModeDirectory: boolean): AstroAdapter {
return isModeDirectory
? {
name: '@astrojs/cloudflare',
serverEntrypoint: '@astrojs/cloudflare/server.directory.js',
exports: ['onRequest', 'manifest'],
}
name: '@astrojs/cloudflare',
serverEntrypoint: '@astrojs/cloudflare/server.directory.js',
exports: ['onRequest', 'manifest'],
}
: {
name: '@astrojs/cloudflare',
serverEntrypoint: '@astrojs/cloudflare/server.advanced.js',
exports: ['default'],
};
name: '@astrojs/cloudflare',
serverEntrypoint: '@astrojs/cloudflare/server.advanced.js',
exports: ['default'],
};
}
const SHIM = `globalThis.process = {
@ -210,7 +210,7 @@ export default function createIntegration(args?: Options): AstroIntegration {
}
}
// // // throw the server folder in the bin
// throw the server folder in the bin
const serverUrl = new URL(_buildConfig.server);
await fs.promises.rm(serverUrl, { recursive: true, force: true });

View file

@ -1,3 +1,4 @@
// TODO: remove `getRuntime()` in Astro 3.0
import type { Cache, CacheStorage, IncomingRequestCfProperties } from '@cloudflare/workers-types';
export type WorkerRuntime<T = unknown> = {
@ -21,6 +22,16 @@ export type PagesRuntime<T = unknown, U = unknown> = {
cf?: IncomingRequestCfProperties;
};
/**
* @deprecated since version 6.8.0
* The `getRuntime` utility has been deprecated and should be updated to the new [`Astro.locals`](https://docs.astro.build/en/guides/middleware/#locals) API.
* ```diff
* - import { getRuntime } from '@astrojs/cloudflare/runtime';
* - getRuntime(Astro.request);
*
* + const runtime = Astro.locals.runtime;
* ```
*/
export function getRuntime<T = unknown, U = unknown>(
request: Request
): WorkerRuntime<T> | PagesRuntime<T, U> {

View file

@ -12,10 +12,21 @@ type Env = {
name: string;
};
interface WorkerRuntime {
runtime: {
waitUntil: (promise: Promise<any>) => void;
env: Env;
cf: CFRequest['cf'];
caches: typeof caches;
};
}
export function createExports(manifest: SSRManifest) {
const app = new App(manifest);
const fetch = async (request: Request & CFRequest, env: Env, context: ExecutionContext) => {
// TODO: remove this any cast in the future
// REF: the type cast to any is needed because the Cloudflare Env Type is not assignable to type 'ProcessEnv'
process.env = env as any;
const { pathname } = new URL(request.url);
@ -32,6 +43,9 @@ export function createExports(manifest: SSRManifest) {
Symbol.for('astro.clientAddress'),
request.headers.get('cf-connecting-ip')
);
// `getRuntime()` is deprecated, currently available additionally to new Astro.locals.runtime
// TODO: remove `getRuntime()` in Astro 3.0
Reflect.set(request, Symbol.for('runtime'), {
env,
name: 'cloudflare',
@ -42,7 +56,19 @@ export function createExports(manifest: SSRManifest) {
context.waitUntil(promise);
},
});
let response = await app.render(request, routeData);
const locals: WorkerRuntime = {
runtime: {
waitUntil: (promise: Promise<any>) => {
context.waitUntil(promise);
},
env: env,
cf: request.cf,
caches: caches,
},
};
let response = await app.render(request, routeData, locals);
if (app.setCookieHeaders) {
for (const setCookieHeader of app.setCookieHeaders(response)) {

View file

@ -7,28 +7,30 @@ if (!isNode) {
process.env = getProcessEnvProxy();
}
interface FunctionRuntime {
runtime: {
waitUntil: (promise: Promise<any>) => void;
env: EventContext<unknown, string, unknown>['env'];
cf: CFRequest['cf'];
caches: typeof caches;
};
}
export function createExports(manifest: SSRManifest) {
const app = new App(manifest);
const onRequest = async ({
request,
next,
...runtimeEnv
}: {
request: Request & CFRequest;
next: (request: Request) => void;
waitUntil: EventContext<unknown, any, unknown>['waitUntil'];
} & Record<string, unknown>) => {
process.env = runtimeEnv.env as any;
const onRequest = async (context: EventContext<unknown, string, unknown>) => {
const request = context.request as CFRequest & Request;
const { next, env } = context;
// TODO: remove this any cast in the future
// REF: the type cast to any is needed because the Cloudflare Env Type is not assignable to type 'ProcessEnv'
process.env = env as any;
const { pathname } = new URL(request.url);
// static assets fallback, in case default _routes.json is not used
if (manifest.assets.has(pathname)) {
// we need this so the page does not error
// https://developers.cloudflare.com/pages/platform/functions/advanced-mode/#set-up-a-function
return (runtimeEnv.env as EventContext<unknown, string, unknown>['env']).ASSETS.fetch(
request
);
return env.ASSETS.fetch(request);
}
let routeData = app.match(request, { matchNotFound: true });
@ -38,17 +40,32 @@ export function createExports(manifest: SSRManifest) {
Symbol.for('astro.clientAddress'),
request.headers.get('cf-connecting-ip')
);
// `getRuntime()` is deprecated, currently available additionally to new Astro.locals.runtime
// TODO: remove `getRuntime()` in Astro 3.0
Reflect.set(request, Symbol.for('runtime'), {
...runtimeEnv,
...context,
waitUntil: (promise: Promise<any>) => {
runtimeEnv.waitUntil(promise);
context.waitUntil(promise);
},
name: 'cloudflare',
next,
caches,
cf: request.cf,
});
let response = await app.render(request, routeData);
const locals: FunctionRuntime = {
runtime: {
waitUntil: (promise: Promise<any>) => {
context.waitUntil(promise);
},
env: context.env,
cf: request.cf,
caches: caches,
},
};
let response = await app.render(request, routeData, locals);
if (app.setCookieHeaders) {
for (const setCookieHeader of app.setCookieHeaders(response)) {

View file

@ -17,7 +17,7 @@ describe('Cf metadata and caches', () => {
});
await fixture.build();
cli = runCLI('./fixtures/cf/', { silent: true, port: 8788 });
cli = runCLI('./fixtures/cf/', { silent: false, port: 8788 });
await cli.ready;
});

View file

@ -0,0 +1,8 @@
import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';
export default defineConfig({
adapter: cloudflare(),
output: 'server',
});

View file

@ -0,0 +1,9 @@
{
"name": "@test/astro-cloudflare-runtime",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/cloudflare": "workspace:*",
"astro": "workspace:*"
}
}

View file

@ -0,0 +1,15 @@
---
const runtime = Astro.locals.runtime;
const env = runtime.env;
---
<html>
<head>
<title>Testing</title>
</head>
<body>
<h1>Testing</h1>
<div id="cf">{JSON.stringify(runtime.cf)}</div>
<div id="env">{JSON.stringify(env)}</div>
<div id="hasCache">{!!runtime.caches}</div>
</body>
</html>

View file

@ -0,0 +1,38 @@
import { loadFixture, runCLI } from './test-utils.js';
import { expect } from 'chai';
import * as cheerio from 'cheerio';
import cloudflare from '../dist/index.js';
describe('Runtime Locals', () => {
/** @type {import('./test-utils.js').Fixture} */
let fixture;
/** @type {import('./test-utils.js').WranglerCLI} */
let cli;
before(async () => {
fixture = await loadFixture({
root: './fixtures/runtime/',
output: 'server',
adapter: cloudflare(),
});
await fixture.build();
cli = runCLI('./fixtures/runtime/', { silent: true, port: 8793 });
await cli.ready;
});
after(async () => {
await cli.stop();
});
it('has CF and Caches', async () => {
let res = await fetch(`http://localhost:8793/`);
expect(res.status).to.equal(200);
let html = await res.text();
let $ = cheerio.load(html);
expect($('#cf').text()).to.contain('city');
expect($('#env').text()).to.contain('SECRET_STUFF');
expect($('#env').text()).to.contain('secret');
expect($('#hasCache').text()).to.equal('true');
});
});

View file

@ -3715,6 +3715,15 @@ importers:
specifier: workspace:*
version: link:../../../../../astro
packages/integrations/cloudflare/test/fixtures/runtime:
dependencies:
'@astrojs/cloudflare':
specifier: workspace:*
version: link:../../..
astro:
specifier: workspace:*
version: link:../../../../../astro
packages/integrations/cloudflare/test/fixtures/split:
dependencies:
'@astrojs/cloudflare':