fastly/next-compute-jsの内部アーキテクチャを調べる
Fastlyから新たなNext.jsインテグレーションツールがリリースされていたので調べてみた。
モチベーションとしてはServerless Nextjs Pluginに移植してCloudflare Workersでも動かしたい。
fastly/next-compute-jsとは
Compute@Edge でNext.jsアプリケーションを動かしたい時に使うツール。
以下に解説がある
使い方
- Next.jsプロジェクトを作成
-
npx @fastly/next-compute-js@latest init
でサブディレクトリcompute-js
以下にCompute@Edge固有の構成を追加する -
fastly compute serve
でローカルで起動する -
fastly compute publish
でデプロイする
compute-jsに何ができるのか
WebpackとFastly CLIでビルドとデプロイするための設定ファイル群ができる。
❯ tree . -L 1
.
├── bin
├── default-content-types.cjs
├── fastly.toml
├── node_modules
├── package-lock.json
├── package.json
├── pkg
├── src
├── static-publish.rc.js
└── webpack.config.js
この雛形はnext-compute-js/resources/compute-js に定義されている。
重要なのはfastly.toml
と webpack.config.js
と src/index.js
で fastly compute serve
を実行した時にこれらの設定ファイルが参照されて、実際にデプロイされるjsとwasmバイナリが生成される。
ビルドステップ
細かい検証フローは省略するけど fastly compute serve
を実行した時に内部的には fastly compute build
が実行され以下のコマンドが走っている(fastly.toml
に記述がある)
-
npx @fastly/compute-js-static-publish --build-static --suppress-framework-warnings
a. これによってsrc/statics.js
ができる -
$(npm bin)/webpack
a. これによって./bin/index.js
ができる -
$(npm bin)/js-compute-runtime ./bin/index.js ./bin/main.wasm
a. これによって./bin/main.wasm
ができてpkg/package.tar.gz
にindex.js
と一緒にパッケージされる
compute-js-static-publish
compute-js-static-publishで生成される src/statics.js
は next build
で生成された静的ファイルを読取り、パスのマッピングとメタ情報を含めている
import file0 from "../../.next/BUILD_ID?staticBinary";
import file1 from "../../.next/build-manifest.json?staticText";
import file2 from "../../.next/export-marker.json?staticText";
// ...
export const assets = {
"/.next/BUILD_ID": { contentType: undefined, content: Buffer.from(file0, "base64"), module: null, isStatic: false },
"/.next/build-manifest.json": { contentType: "application/json", content: file1, module: null, isStatic: false },
"/.next/export-marker.json": { contentType: "application/json", content: file2, module: null, isStatic: false },
// ...
これがfastly/next-compute-jsのエントリーポイントの./src/index.js
内部のリクエストハンドラーに渡されている
import createServer from "@fastly/next-compute-js";
import { assets } from './statics';
const server = await createServer({
dir: '../../',
computeJs: {
assets,
backends: {
'httpbin': { url: 'https://httpbin.org/anything/' },
},
}
});
webpack build
Webpackのレイヤーでは上記エントリーポイントをtarget: "webworker"
でビルドして、Fastly環境でのWeb API(streamやbufferやcryptoや)呼び出しをNode.js版のpolyfillを使うようにバンドルしている。
この時にNEXT_RUNTIME: 'edge'
を指定している。
すべてが詰め込まれた3MB超のbin/index.js
が吐き出される。
js-compute-runtime
js-compute-runtimeはjsを入力してwasmを吐くコマンド。
bin/main.wasm
の段階で30MBのファイルになるため .next/
から読み取ったアセットファイルがインライン化して内部に固められていると思われる。それを実行時にインメモリで解決している?(予想)
NextComputeJsServer
Compute@Edgeは任意のWasmファイルをランタイムで実行することでリクエストに応答する。それがビルドした時に生成されるbin/main.wasm
になる。
この実行ファイルの中でJavaScriptのレイヤーで見るとNext.js内部APIのNextNodeServerと互換性のあるNextComputeJsServerがリクエスト処理をハンドリングしている。
つまりCompute@Edge環境で動作するNextNodeServerが独自に実装されている(やば)。
ComputeJsAsset
静的ファイルの参照はsrc/server/require.tsに書かれていた。やっぱりパスを与えるとファイルの中身が取得できる仕組みのよう。それ以上のことは js-compute-runtime のコンパイラ側の実装を読まないと分からない。
Discussion