fork: qwik-city + cloudflare-pages で cloudflare binding を触りながら高速に再ビルドしたい

これを引き継ぐぞい:
お試しリポジトリはここ:

プロジェクト名はqwik-on-cloudlflare
とし、全てデフォルトで作成:
$ pnpm create qwik@latest
試行:
$ pnpm start
動く。SSRのTodoサンプルアプリも動く。

Wranglerインストール:
$ pnpm add --save-dev wrangler
Qwik CityのCloudflare Pages Adapterをインストール:
$ pnpm run qwik add cloudflare-pages
試行:
$ pnpm wrangler pages dev -- pnpm vite --mode ssr
動く。HMRもちゃんとしてる。

wranglerコマンドでローカルKVをセットアップ:
$ pnpm wrangler kv:namespace create "QOC_KV_DEV" --preview
...
🌀 Creating namespace with title "worker-QOC_KV_DEV_preview"
✨ Success!
Add the following to your configuration file in your kv_namespaces array:
{ binding = "QOC_KV_DEV", preview_id = "<PREVIEW_ID>" }
なんか認証走る。ローカルだけの時は要らんやろ。<PREVIEW_ID>
は長めのハッシュ値です。
出力されたpreview_id
等をwrangler設定ファイルに書く:
kv_namespaces = [
{ binding = "QOC_KV_DEV", preview_id = "<PREVIEW_ID>" }
]
起動:
$ pnpm wrangler pages dev -- pnpm vite --mode ssr
...
[ERROR] Processing wrangler.toml configuration:
- "kv_namespaces[0]" bindings should have a string "id" field but got
{"binding":"QOC_KV_DEV","preview_id":"<PREVIEW_ID>"}.
If you think this is a bug then please create an issue at https://github.com/cloudflare/workers-sdk/issues/new/choose
なんでや。id
なんて出力されてへんやろ。
イシュー探索:id
とpreview_id
は同じにするととりあえず動くらしい。
再編集:
kv_namespaces = [
{ binding = "QOC_KV_DEV", id = "<PREVIEW_ID>", preview_id = "<PREVIEW_ID>" }
]
再起動:
$ pnpm wrangler pages dev -- pnpm vite --mode ssr
...
Your worker has access to the following bindings:
- KV Namespaces:
- QOC_KV_DEV: <PREVIEW_ID>
...
立ち上がったっぽい。
型入れとく
$ pnpm add --save-dev @cloudflare/workers-types
デモアプリのtodolistをKVからget/putするように改変する:
diff --git a/src/routes/demo/todolist/index.tsx b/src/routes/demo/todolist/index.tsx
index 943235e..3ced350 100644
--- a/src/routes/demo/todolist/index.tsx
+++ b/src/routes/demo/todolist/index.tsx
@@ -1,4 +1,5 @@
import { component$ } from "@builder.io/qwik";
+import type { RequestEventLoader } from "@builder.io/qwik-city";
import {
type DocumentHead,
routeLoader$,
@@ -8,6 +9,8 @@ import {
Form,
} from "@builder.io/qwik-city";
import styles from "./todolist.module.css";
+import { type PlatformCloudflarePages } from "@builder.io/qwik-city/middleware/cloudflare-pages";
+import { type KVNamespace } from "@cloudflare/workers-types";
interface ListItem {
text: string;
@@ -15,20 +18,27 @@ interface ListItem {
export const list: ListItem[] = [];
-export const useListLoader = routeLoader$(() => {
- return list;
-});
+export const useListLoader = routeLoader$(
+ async ({ platform }: RequestEventLoader<PlatformCloudflarePages>) => {
+ const kv: KVNamespace = platform.env && platform.env["QOC_KV_DEV"];
+ const todos = (await kv.get<ListItem[]>("todos", "json")) || [];
+ return todos;
+ }
+);
export const useAddToListAction = routeAction$(
- (item) => {
- list.push(item);
+ async (item, { platform }) => {
+ const kv: KVNamespace = platform.env && platform.env["QOC_KV_DEV"];
+ const prevTodos = await kv.get<ListItem[]>("todos", "json");
+ const nextTodos = prevTodos ? [...prevTodos, item] : [];
+ await kv.put("todos", JSON.stringify(nextTodos));
return {
success: true,
};
},
zod$({
text: z.string().trim().min(1),
- }),
+ })
);
export default component$(() => {
参考:
エラー。
まだできてないけど、長くなったので一旦投稿。

Cloudflare Pages Fuctions(実質Cloudflare Workers?)では、onRequest(context)
でcontext.env.MY_KV
のように、Cloudflare KVを参照できるらしいのですが、Qwik City側のAdapter/Middlewareは特になんもしとらんような……

reqev.platform.env
じゃなくてreqev.env
にアクセスしてるの見たので試行:
diff --git a/src/routes/demo/todolist/index.tsx b/src/routes/demo/todolist/index.tsx
index 943235e..925999f 100644
--- a/src/routes/demo/todolist/index.tsx
+++ b/src/routes/demo/todolist/index.tsx
@@ -1,4 +1,5 @@
import { component$ } from "@builder.io/qwik";
+import type { RequestEventLoader } from "@builder.io/qwik-city";
import {
type DocumentHead,
routeLoader$,
@@ -8,6 +9,12 @@ import {
Form,
} from "@builder.io/qwik-city";
import styles from "./todolist.module.css";
+import { type PlatformCloudflarePages } from "@builder.io/qwik-city/middleware/cloudflare-pages";
+import { type KVNamespace } from "@cloudflare/workers-types";
+
+export interface Env {
+ QOC_KV_DEV: KVNamespace;
+}
interface ListItem {
text: string;
@@ -15,20 +22,27 @@ interface ListItem {
export const list: ListItem[] = [];
-export const useListLoader = routeLoader$(() => {
- return list;
-});
+export const useListLoader = routeLoader$(
+ async (reqev: RequestEventLoader<PlatformCloudflarePages>) => {
+ const kv = reqev.env.get("QOC_KV_DEV") as unknown as KVNamespace;
+ const todos = (await kv.get<ListItem[]>("todos", "json")) || [];
+ return todos;
+ }
+);
export const useAddToListAction = routeAction$(
- (item) => {
- list.push(item);
+ async (item, reqev) => {
+ const kv = reqev.env.get("QOC_KV_DEV") as unknown as KVNamespace;
+ const prevTodos = await kv.get<ListItem[]>("todos", "json");
+ const nextTodos = prevTodos ? [...prevTodos, item] : [];
+ await kv.put("todos", JSON.stringify(nextTodos));
return {
success: true,
};
},
zod$({
text: z.string().trim().min(1),
- }),
+ })
);
export default component$(() => {
アカンね。

2023-03のイシューにローカル開発環境でD1やKVが読み取れないってあるわね:
pnpm build
すれば(productionなら)アクセスできるみたい。
一応👍&👁️しといたけど、望み薄かね……

これ使ってみる:
インストール:
$ pnpm add cf-bindings-proxy
コード更新:
diff --git a/src/routes/demo/todolist/index.tsx b/src/routes/demo/todolist/index.tsx
index 925999f..4551126 100644
--- a/src/routes/demo/todolist/index.tsx
+++ b/src/routes/demo/todolist/index.tsx
@@ -10,6 +10,7 @@ import {
} from "@builder.io/qwik-city";
import styles from "./todolist.module.css";
import { type PlatformCloudflarePages } from "@builder.io/qwik-city/middleware/cloudflare-pages";
+import { binding } from "cf-bindings-proxy";
import { type KVNamespace } from "@cloudflare/workers-types";
export interface Env {
@@ -23,16 +24,20 @@ interface ListItem {
export const list: ListItem[] = [];
export const useListLoader = routeLoader$(
- async (reqev: RequestEventLoader<PlatformCloudflarePages>) => {
- const kv = reqev.env.get("QOC_KV_DEV") as unknown as KVNamespace;
+ async ({ platform }: RequestEventLoader<PlatformCloudflarePages>) => {
+ const kv = binding<KVNamespace>("QOC_KV_DEV", {
+ fallback: platform.env as Record<string, unknown>,
+ });
const todos = (await kv.get<ListItem[]>("todos", "json")) || [];
return todos;
}
);
export const useAddToListAction = routeAction$(
- async (item, reqev) => {
- const kv = reqev.env.get("QOC_KV_DEV") as unknown as KVNamespace;
+ async (item, { platform }) => {
+ const kv = binding<KVNamespace>("QOC_KV_DEV", {
+ fallback: platform.env as Record<string, unknown>,
+ });
const prevTodos = await kv.get<ListItem[]>("todos", "json");
const nextTodos = prevTodos ? [...prevTodos, item] : [];
await kv.put("todos", JSON.stringify(nextTodos));
実行:
$ ENABLE_BINDINGS_PROXY=true pnpm exec cf-bindings-proxy --kv=QOC_KV_DEV
$ pnpm vite --mode ssr
動いた!
確認:
$ pnpm wrangler kv:key list --namespace-id=QOC_KV_DEV --local
[
{
"name": "todos"
}
]
$ pnpm wrangler kv:key get todos --namespace-id=QOC_KV_DEV --local
[{"text":"b"},{"text":"c"},{"text":"a"},{"text":"d"},{"text":"e"},{"text":"f"}]
ある。
bindingとnamespace-idがゴッチャになってるのは多分俺のkvの作り方が悪かったんだろうな:
tree .wrangler/
.wrangler/
└── state
└── v3
└── kv
├── 6ae710e2775143a0adaf97d3a9278a3d
│ └── db.sqlite
└── QOC_KV_DEV
├── blobs
│ └── 2e5fae1c0215852d61a8638b30e7150ca72feb475ad7a1cf18cfd2366becbd540005d3381d0ad147
├── db.sqlite
├── db.sqlite-shm
└── db.sqlite-wal
7 directories, 5 files
ターミナル2つ使うのが怠いので要改良だが、とりあえず動いた!!

あとは、binding名を直したり、wrangler.tomlを環境毎の設定を入れるとか整理したいわね。