Open12

Cloudflare Workers やる

おーみーおーみー

PrettierとJestの設定は完璧なのにESLintの設定がないの謎すぎる。

{
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "prettier"
  ]
}
おーみーおーみー

tsconfig.json間違ってますよ。

tsconfig.json
{
  "compilerOptions": {
    "outDir": "./dist",
    "module": "commonjs",
    "target": "esnext",
    "lib": ["esnext"],
    "alwaysStrict": true,
    "strict": true,
    "preserveConstEnums": true,
    "moduleResolution": "node",
    "sourceMap": true,
    "esModuleInterop": true,
    "types": [
      "@cloudflare/workers-types",
      "@types/jest",
      "@types/service-worker-mock"
    ]
  },
-  "include": ["src"],
+  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "test"]
}
おーみーおーみー

.prettierrcに semi:truesingleQuote:true が入っていて怒りの変更を行った。

おーみーおーみー

主な型定義は @cloudflare/workers-types パッケージで行われているようだ。

なお、"lib": ["esnext", "webworker"], とする資料があるが、
https://github.com/cloudflare/workers-types#cloudflare-workers-types
には

⚠️ If you're upgrading from version 2, make sure to remove webworker from the lib array in your tsconfig.json. These types are now included in @cloudflare/workers-types.

とある。Cloudflare Workers は Web Worker に倣ったAPIらしいがそれそのものではないと思うので型定義が全部 Cloudflare の管理下にあることはおそらくいいことなのだろう。

おーみーおーみー

moduleResolutionを見て、そういえばCF Workersは何で動いているんだろうという疑問が出てきた。トランスパイル先バージョンを気にしなければいけない。で、Docsを探し回った結果

https://developers.cloudflare.com/workers/runtime-apis/web-standards

Cloudflare Workers uses the V8 JavaScript engine from Google Chrome. The Workers runtime is updated at least once a week, to at least the version that is currently used by Chrome's stable release. This means you can safely use the latest JavaScript features, with no need for transpilers.

All of the standard built-in objects supported by the current Google Chrome stable release are supported, with a few notable exceptions:

と書いてあった。「from Google Chrome」というのは「Google Chromeでも使われている」みたいな表現ではなく本当にGoogle ChromeのV8を使っているということだと思う。

おーみーおーみー

これもしかして本物のWeb Workerが動いてるってこと?WorkerGlobalScopeにあるものが全部使えるらしい。

おーみーおーみー

esbuildを導入してこんな感じのコードを書く。Nodeの現行LTS(14.18.0)ではTLA[1]できないのでIIAFE[2]を挟んでおくが、Node v17がついさっき出てもうすぐLTSがv16に移行する頃合いなので剥がせるはず。

scripts/bundle.js
/* eslint-env node */

import esbuild from "esbuild";

(async () => {
  try {
    await esbuild.build({
      entryPoints: ["src/index.ts"],
      bundle: true,
      minify: true,
      outfile: "dist/worker.js",
      format: "esm",
    });

    console.log(`✅ successfully bundled`);
  } catch (reason) {
    console.error(reason);
    console.error(`❌ bundle failed`);
  }
})();
脚注
  1. top-level await ↩︎

  2. immediately invoked async function expression ↩︎

おーみーおーみー

Workers KV といって、WorkerからKey-Value storeが利用できる。wrangler kv:namespace create "COUNT" --preview のようなコマンドを叩くとストアができる。同時にCLIから { binding = "COUNT", preview_id = "hogehogefugafuga", id = "foobarbaz" } のようなオブジェクトが吐き出されるのでこれを wrangler.toml に追記する。

wrangler.toml
name = "aumy-playground"
type = "javascript"
zone_id = ""
account_id = ""
route = ""
workers_dev = true
compatibility_date = "2021-10-21"

+ kv_namespaces = [
+   { binding = "COUNT", preview_id = "hogehogefugafuga", id = "foobarbaz" },
+ ]

[build]
command = "npm install && npm run build"
[build.upload]
format = "service-worker"
おーみーおーみー

binding するとWorkerで動かすコードのグローバル変数として束縛されるので、declare を使ってその存在を明示しておく。wrangler.toml を読んで自動的にこれ追加してくれるようにしたら面白そう。TypeScript compiler pluginでできたりするだろうか?

handler.ts
declare let COUNT: KVNamespace;

export async function handleRequest(request: Request): Promise<Response> {
  let count = await COUNT.get(request.method, "json");

  if (typeof count !== "number") count = 0;

  const newCount = (count as number) + 1;

  COUNT.put(request.method, String(newCount));
  return new Response(`${request.method}: ${newCount}`);
}

ちなみにこのコードはHTTPメソッドごとのアクセス数を数えてくれるアプリ。