Skip to content

Commit e62af72

Browse files
enable getCloudflareContext to also work with next dev (#33)
* enable `getCloudflareContext` to also work with `next dev` * add hack to get standard build to work * add playwright test for testing dev mode too
1 parent af15fd1 commit e62af72

File tree

8 files changed

+135
-15
lines changed

8 files changed

+135
-15
lines changed

.github/workflows/playwright.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
run: npm install -g pnpm && pnpm install
1818
- name: Install Playwright browsers
1919
run: pnpm run install-playwright
20-
- name: Run playwright tests (api)
21-
run: pnpm -F api run e2e
22-
- name: Run playwright tests (create-next-app)
23-
run: pnpm -F create-next-app run e2e
20+
- name: Run playwright tests
21+
run: pnpm e2e
22+
- name: Run playwright dev tests
23+
run: pnpm e2e:dev

examples/api/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"dev:worker": "wrangler dev --port 8770",
1212
"preview:worker": "pnpm build:worker && pnpm dev:worker",
1313
"e2e": "playwright test",
14+
"e2e:dev": "playwright test -c playwright.dev.config.ts",
1415
"cf-typegen": "wrangler types --env-interface CloudflareEnv"
1516
},
1617
"dependencies": {
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { defineConfig, devices } from "@playwright/test";
2+
3+
declare var process: { env: Record<string, string> };
4+
5+
/**
6+
* Read environment variables from file.
7+
* https://github.com/motdotla/dotenv
8+
*/
9+
// import dotenv from 'dotenv';
10+
// dotenv.config({ path: path.resolve(__dirname, '.env') });
11+
12+
/**
13+
* See https://playwright.dev/docs/test-configuration.
14+
*/
15+
export default defineConfig({
16+
testDir: "./e2e-tests",
17+
/* Run tests in files in parallel */
18+
fullyParallel: true,
19+
/* Fail the build on CI if you accidentally left test.only in the source code. */
20+
forbidOnly: !!process.env.CI,
21+
/* Retry on CI only */
22+
retries: process.env.CI ? 2 : 0,
23+
/* Opt out of parallel tests on CI. */
24+
workers: process.env.CI ? 1 : undefined,
25+
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
26+
reporter: "html",
27+
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
28+
use: {
29+
/* Base URL to use in actions like `await page.goto('/')`. */
30+
baseURL: "http://localhost:3333",
31+
32+
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
33+
trace: "on-first-retry",
34+
},
35+
36+
/* Configure projects for major browsers */
37+
projects: [
38+
{
39+
name: "chromium",
40+
use: { ...devices["Desktop Chrome"] },
41+
},
42+
43+
{
44+
name: "firefox",
45+
use: { ...devices["Desktop Firefox"] },
46+
},
47+
48+
{
49+
name: "webkit",
50+
use: { ...devices["Desktop Safari"] },
51+
},
52+
53+
/* Test against mobile viewports. */
54+
// {
55+
// name: 'Mobile Chrome',
56+
// use: { ...devices['Pixel 5'] },
57+
// },
58+
// {
59+
// name: 'Mobile Safari',
60+
// use: { ...devices['iPhone 12'] },
61+
// },
62+
63+
/* Test against branded browsers. */
64+
// {
65+
// name: 'Microsoft Edge',
66+
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
67+
// },
68+
// {
69+
// name: 'Google Chrome',
70+
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
71+
// },
72+
],
73+
74+
/* Run your local dev server before starting the tests */
75+
webServer: {
76+
command: "pnpm dev --port 3333",
77+
url: "http://localhost:3333",
78+
reuseExistingServer: !process.env.CI,
79+
},
80+
});

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"prettier:fix": "prettier --write .",
1212
"postinstall": "pnpm --filter cloudflare build",
1313
"install-playwright": "playwright install --with-deps",
14-
"e2e": "pnpm -r e2e"
14+
"e2e": "pnpm -r e2e",
15+
"e2e:dev": "pnpm -r e2e:dev"
1516
}
1617
}

packages/cloudflare/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,8 @@
4848
},
4949
"dependencies": {
5050
"ts-morph": "catalog:"
51+
},
52+
"peerDependencies": {
53+
"wrangler": "catalog:"
5154
}
5255
}

packages/cloudflare/src/api/get-cloudflare-context.ts

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,55 @@ const cloudflareContextSymbol = Symbol.for("__cloudflare-context__");
2929
/**
3030
* Utility to get the current Cloudflare context
3131
*
32-
* Throws an error if the context could not be retrieved
33-
*
3432
* @returns the cloudflare context
3533
*/
3634
export async function getCloudflareContext<
3735
CfProperties extends Record<string, unknown> = IncomingRequestCfProperties,
3836
Context = ExecutionContext,
3937
>(): Promise<CloudflareContext<CfProperties, Context>> {
40-
const cloudflareContext = (
41-
globalThis as unknown as {
42-
[cloudflareContextSymbol]: CloudflareContext<CfProperties, Context> | undefined;
43-
}
44-
)[cloudflareContextSymbol];
38+
const global = globalThis as unknown as {
39+
[cloudflareContextSymbol]: CloudflareContext<CfProperties, Context> | undefined;
40+
};
41+
42+
const cloudflareContext = global[cloudflareContextSymbol];
4543

4644
if (!cloudflareContext) {
47-
// TODO: cloudflareContext should always be present in production/preview, if not it means that this
48-
// is running under `next dev`, in this case use `getPlatformProxy` to return local proxies
49-
throw new Error("Cloudflare context is not defined!");
45+
// the cloudflare context is initialized by the worker and is always present in production/preview,
46+
// so, it not being present means that the application is running under `next dev`
47+
return getCloudflareContextInNextDev();
5048
}
5149

5250
return cloudflareContext;
5351
}
52+
53+
const cloudflareContextInNextDevSymbol = Symbol.for("__next-dev/cloudflare-context__");
54+
55+
/**
56+
* Gets a local proxy version of the cloudflare context (created using `getPlatformProxy`) when
57+
* running in the standard next dev server (via `next dev`)
58+
*
59+
* @returns the local proxy version of the cloudflare context
60+
*/
61+
async function getCloudflareContextInNextDev<
62+
CfProperties extends Record<string, unknown> = IncomingRequestCfProperties,
63+
Context = ExecutionContext,
64+
>(): Promise<CloudflareContext<CfProperties, Context>> {
65+
const global = globalThis as unknown as {
66+
[cloudflareContextInNextDevSymbol]: CloudflareContext<CfProperties, Context> | undefined;
67+
};
68+
69+
if (!global[cloudflareContextInNextDevSymbol]) {
70+
// Note: we never want wrangler to be bundled in the Next.js app, that's why the import below looks like it does
71+
const { getPlatformProxy } = await import(
72+
/* webpackIgnore: true */ `${"__wrangler".replaceAll("_", "")}`
73+
);
74+
const { env, cf, ctx } = await getPlatformProxy();
75+
global[cloudflareContextInNextDevSymbol] = {
76+
env,
77+
cf: cf as unknown as CfProperties,
78+
ctx: ctx as Context,
79+
};
80+
}
81+
82+
return global[cloudflareContextInNextDevSymbol]!;
83+
}

packages/cloudflare/tsup.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const cliConfig = defineConfig({
88
format: ["esm"],
99
platform: "node",
1010
external: ["esbuild"],
11+
clean: true,
1112
onSuccess: async () => {
1213
await cp(`${__dirname}/src/cli/templates`, `${__dirname}/dist/cli/templates`, {
1314
recursive: true,
@@ -22,6 +23,7 @@ const apiConfig = defineConfig({
2223
format: ["esm"],
2324
platform: "node",
2425
external: ["server-only"],
26+
clean: true,
2527
});
2628

2729
export default [cliConfig, apiConfig];

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)