TT Lab
はじめる
学ぶ 学習パス コース

CBA — Backstage認定アソシエイト

ソースを直したのにローカルのバックエンドの応答が変わらない

TT Labで続きを見る

目標

Backstageのアプリのリポジトリと同じ形のYarn 4ワークスペースを自分で組み立て、依存関係のインストールとロックファイル、TypeScriptのコンパイル、ビルドのアーティファクト、ローカルでの実行へとつながる開発の流れを、ひとめぐりします。採点ツールは、ファイルだけを見るのではなく、yarn・tsc・nodeを再実行した結果で判定します。

なぜ重要なのか

「自分のPCでは動くのに、CIでインストールが失敗する」「型エラーが出たのに、distはなぜできたのか」「ソースを直したのに、応答がそのままだ」。Backstageを開発していて最もよくぶつかるのは、プラグインのコードよりも、この流れです。ロックファイルが何を固定し、tscが何を捕まえて何を見逃し、バックエンドが実際にどのファイルを実行するかを知っていてはじめて、原因を素早く見つけられます。

最初のVMの起動に4分ほどかかり、ステップ2のインストールは、インターネットから数十秒かかります。実際のプロジェクトのbackstage-cli(yarn start・yarn build)とフロントエンドのアプリ(packages/app)は、インストールの容量とビルド時間が大きいので使わず、同じことをyarn・tsc・nodeで、手で行います。このVMにはdockerがないので、イメージのビルドは行いません。

ステップ

  1. ルート・backend・プラグインの3つのpackage.jsonと、.yarnrc.ymlで、ワークスペースを組み立てます。
  2. yarn installで、ロックファイルとワークスペースのリンクを作ります。
  3. ロックファイルとずれたpackage.jsonが、--immutableで止まることを記録して、元に戻します。
  4. 間違ったポリシーの値をtscが捕まえることと、それでもJSが作られることを記録します。
  5. noEmitOnErrorをオンにして直し、ビルドします。
  6. プラグインを付けたバックエンドを、ローカルで起動します。
  7. ソースだけを直して再起動しても変わらず、ビルドしてはじめて変わることを確認します。
  8. 今のリポジトリで測った値で、開発の流れの点検表を書きます。

参考

アプリのリポジトリと同じ形のワークスペースを組み立てる

/usr/local/cba-devにYarnワークスペースを作成してください。ルートのpackage.jsonは、private: true、packageManager: "yarn@4.9.2"、workspaces: ["packages/*", "plugins/*"]です。ルートの.yarnrc.ymlは、nodeLinker: node-modules、enableTelemetry: false、enableGlobalCache: false、enableMirror: false、globalFolder: /usr/local/cba-dev/.yarn-globalです。packages/backend/package.json(名前はbackend)のdependenciesは、@backstage/backend-defaults 0.17.8、better-sqlite3 12.4.1、@internal/plugin-hello-backend workspace:^です。plugins/hello-backend/package.json(名前は@internal/plugin-hello-backend、mainはdist/index.js、typesはdist/index.d.ts、scriptsはbuild: tsc -p tsconfig.json)のdependenciesは、@backstage/backend-plugin-api 1.10.0・express 4.22.3、devDependenciesは、typescript 5.9.3・@types/express 4.17.25です。すべての外部バージョンは、範囲指定なしで書きます。corepack yarn workspaces listに、3つのワークスペースが出る必要があります。

Backstageのアプリは、packages/app(フロントエンド)・packages/backendと、plugins/*に分かれたYarnワークスペースです。このVMは、アプリのバンドルを作らないので、backendとバックエンドプラグインだけを置きます。corepackがpackageManagerフィールドを見て、そのバージョンのyarnを取得して使います。ルートディスクが小さいので、シェルで先にexport COREPACK_ENABLE_DOWNLOAD_PROMPT=0 COREPACK_HOME=/usr/local/cba-dev/.corepack TMPDIR=/usr/local/cba-dev/.tmp(そしてmkdir -p /usr/local/cba-dev/.tmp)を実行しておいてください。グローバルキャッシュとミラーをオフにしないと、ホームディレクトリにコピーしている途中で、容量が足りなくなります。

1回のインストールで、ロックファイルとワークスペースのリンクができる

/usr/local/cba-devでcorepack yarn installを実行してください。ルートにyarn.lockができ、node_modules/@internal/plugin-hello-backendがplugins/hello-backendを指すリンクであり、node_modules/@backstage/backend-defaultsは0.17.8である必要があります。続けて、corepack yarn install --immutableも成功する必要があります。

Yarnワークスペースは、依存関係をルートのnode_modulesに集め、workspace:プロトコルの依存関係は、コピーせずにリンクします。ls -l node_modules/@internalとgrep -n 'backend-defaults@npm' yarn.lockで確認してください。peer依存関係の警告(YN0086)は、インストールの失敗ではありません。

ロックファイルとずれたpackage.jsonは、CIで止まる

plugins/hello-backend/package.jsonのexpressのバージョンだけを4.21.2に変え、yarn.lockはそのままにしてcorepack yarn install --immutableを実行し、その出力全体を/root/cba-dev/immutable.txtに保存してください(終了コードも見てください)。そのあと、expressを4.22.3に戻して、--immutableが再び成功するようにしてください。yarn.lockは、最後まで変わらない必要があります。

--immutableは、インストールの結果がロックファイルを変える必要があるなら、書き込まずに失敗します。CIでこのオプションを使う理由は、開発者のPCでだけ解決された依存関係のバージョンが、こっそりデプロイに混ざらないようにするためです。元に戻したあとは、オプションなしでinstallしないでください。そうすると、ずれたバージョンがロックファイルに記録されてしまいます。失敗のコードは、YNで始まる番号で出ます。

tscは間違ったポリシーの値を捕まえるが、JSはそれでも作る

plugins/hello-backend/tsconfig.json(target ES2022、module commonjs、moduleResolution node、strict、esModuleInterop、skipLibCheck、declaration、rootDir src、outDir dist、include ["src"]、noEmitOnErrorなし)と、src/index.tsを作成してください。index.tsは、createBackendPluginでpluginId helloのプラグインを作って、GET /pingが{"version": 1}を返すようにし、わざとhttp.addAuthPolicy({ path: '/ping', allow: 'public' })と書きます。helloPluginを、名前でもdefaultでもexportします。distを削除したあと、プラグインのディレクトリでcorepack yarn tsc -p tsconfig.jsonを実行して、出力を/root/cba-dev/tsc-error.txtに保存し、最後にemitted_despite_error=<dist/index.js 가 생겼으면 yes, 아니면 no>(プレースホルダーは、dist/index.jsが生成されたらyes、そうでなければnoです)を1行追記してください。

addAuthPolicyのallowは、任意の文字列ではなく、決まったリテラルのユニオン型です。JavaScriptなら、起動したあとではじめて気づいたはずのミスを、コンパイルの段階で捕まえることが、TypeScriptを使う理由です。ただし、tscのデフォルトは、型エラーがあっても出力を書きます。終了コードとdistを、一緒に見てください。

型エラーがあれば、何も出力しないようにビルドする

tsconfigに"noEmitOnError": trueを入れ、index.tsのポリシーの値を、正しい'unauthenticated'に直してから、corepack yarn workspace @internal/plugin-hello-backend buildでビルドしてください。dist/index.jsとdist/index.d.tsが、今のsrcから作られている必要があり、プラグインのディレクトリでcorepack yarn tsc -p tsconfig.json --noEmitが、エラーなしで終わる必要があります。

ビルドのアーティファクト(dist)は、ソースよりも新しい必要があります。yarn workspace <이름> <스크립트>(プレースホルダーは名前とスクリプトです)は、ルートから特定のパッケージのスクリプトを実行します。noEmitOnErrorをオンにしたまま、わざと間違えてみると、distが更新されないことも確認できます。

ワークスペースのプラグインを付けたバックエンドを、ローカルで起動する

packages/backend/index.jsで、createBackend()にrequire('@internal/plugin-hello-backend')をaddして、startしてください。app-config.yaml(backend.baseUrl http://localhost:7007、listen.port 7007、DBはbetter-sqlite3・':memory:')は、ワークスペースのルート/usr/local/cba-dev/app-config.yamlに置きます。packages/backendでnode index.jsとして起動し、出力はpackages/backend/backend.logに追記して、認証なしでGET http://127.0.0.1:7007/api/hello/pingが200 {"version":1}なら、完了です。

--configを与えなければ、バックエンドは、実行ディレクトリではなく、リポジトリのルートのapp-config.yamlを探します。設定をpackages/backendに置くと、どんなエラーが出るかを、ログの最初の行で見てください。requireは、node_modulesのリンクをたどって、プラグインのpackage.jsonのmainに行きます。node -p "require('fs').realpathSync(require.resolve('@internal/plugin-hello-backend'))"で、実際のファイルを確認してください。

ソースを直して、ビルドして、再び起動してはじめて、応答が変わる

index.tsの/pingの応答を{"version": 2}に直してください。まずビルドせずにバックエンドだけを再起動して、応答がまだ1であることを見てから、そのあとプラグインをビルドして、バックエンドを再起動し、2になるようにしてください。/root/cba-dev/dev-loop.txtに、without_build=<빌드 없이 재시작했을 때 version>、after_build=<빌드 뒤 재시작했을 때 version>(プレースホルダーは、ビルドなしで再起動したときのversionと、ビルドのあとで再起動したときのversionです)の2行を残します。

バックエンドが実行するのは、srcのTypeScriptではなく、mainが指すdistのJavaScriptです。実際のBackstageのリポジトリでは、yarn start(backstage-cli)が、この変換と再起動を代わりに行ってくれますが、このラボは、そのツールなしで、手順を手で踏みます。

開発の流れの点検表を、今のリポジトリで埋める

/root/cba-dev/report.mdの最初の5行に、今測った値を書いてください。yarn_version=(ルートでcorepack yarn --version)、workspace_count=(workspaces listの行数)、immutable_install=(今--immutableが成功すればpass、そうでなければfail)、plugin_entry=(packages/backendでrequire.resolveしたプラグインの実際のパス)、ping_version=(今の応答のversion。対象は/api/hello/ping)です。その下に、ロックファイル・型検査・ビルドのアーティファクトが、開発の流れでそれぞれ何を防ぐのか、そして、このVMでDockerイメージのビルドをしなかった理由を、書いてください。

すべての値は、今コマンドを実行して得ます。実際のパスは、シンボリックリンクをたどったrealpathです。command -v dockerで、ツールがあるかを見てください。