webdino / lyceum-pokemon

ポケモン API を使った Nuxt+Express アプリ/サーバの開発演習
3 stars 28 forks source link

環境変数として BUCKET_NAME を設定していないと npm start で動作しない #78

Open dynamis opened 2 years ago

dynamis commented 2 years ago

環境変数ではなく .env にて BUCKET_NAME を設定するように指示されているが、npm run dev はそれで問題ないが npm start する場合には s3 のライブラリが .env を読みにいかずに環境変数 (や ~/.aws/config) などだけを見に行くようで、環境変数として BUCKET_NAME を設定していなければ Bucket 指定がされておらず Empty value provided for input HTTP label: Bucket. エラーになる

/ へのアクセス時のサーバログ

Empty value provided for input HTTP label: Bucket.
  at Object.serializeAws_restXmlListObjectsCommand (./.output/server/node_modules/@aws-sdk/client-s3/dist-cjs/protocols/Aws_restXml.js:2399:19)
  at processTicksAndRejections (internal/process/task_queues.js:93:5)
  at async ./.output/server/node_modules/@aws-sdk/middleware-serde/dist-cjs/serializerMiddleware.js:5:21
  at async ./.output/server/node_modules/@aws-sdk/middleware-logger/dist-cjs/loggerMiddleware.js:6:22
  at async findTrainers (file://./.output/server/chunks/middleware/index.mjs:21:19)
  at async file://./.output/server/chunks/middleware/index.mjs:69:22
Empty value provided for input HTTP label: Bucket.
  at Object.serializeAws_restXmlListObjectsCommand (./.output/server/node_modules/@aws-sdk/client-s3/dist-cjs/protocols/Aws_restXml.js:2399:19)
  at processTicksAndRejections (internal/process/task_queues.js:93:5)
  at async ./.output/server/node_modules/@aws-sdk/middleware-serde/dist-cjs/serializerMiddleware.js:5:21
  at async ./.output/server/node_modules/@aws-sdk/middleware-logger/dist-cjs/loggerMiddleware.js:6:22
  at async findTrainers (file://./.output/server/chunks/middleware/index.mjs:21:19)
  at async file://./.output/server/chunks/middleware/index.mjs:69:22
Cannot read property 'length' of null
  at file://./.output/server/chunks/app/server.mjs:3995:29
  at renderFnWithContext (file://./.output/server/chunks/index.mjs:2348:21)
  at Object.ssrRenderSlot (file://./.output/server/chunks/index.mjs:10284:21)
  at _sfc_ssrRender$8 (file://./.output/server/chunks/app/server.mjs:3941:26)
  at renderComponentSubTree (file://./.output/server/chunks/index.mjs:9912:13)
  at renderComponentVNode (file://./.output/server/chunks/index.mjs:9857:16)
  at Object.ssrRenderComponent (file://./.output/server/chunks/index.mjs:10272:12)
  at _sfc_ssrRender$6 (file://./.output/server/chunks/app/server.mjs:3992:32)
  at renderComponentSubTree (file://./.output/server/chunks/index.mjs:9912:13)
  at file://./.output/server/chunks/index.mjs:9854:29

/api/trainers へのアクセス時のサーバログ:

Listening on http://localhost:3000/
[HPM] Proxy created: /  -> https://pokeapi.co
[HPM] Proxy rewrite rule created: "^/api/pokeapi" ~> "/api/v2"
Empty value provided for input HTTP label: Bucket.
  at Object.serializeAws_restXmlListObjectsCommand (./.output/server/node_modules/@aws-sdk/client-s3/dist-cjs/protocols/Aws_restXml.js:2399:19)
  at async ./.output/server/node_modules/@aws-sdk/middleware-serde/dist-cjs/serializerMiddleware.js:5:21
  at async ./.output/server/node_modules/@aws-sdk/middleware-logger/dist-cjs/loggerMiddleware.js:6:22
  at async findTrainers (file://./.output/server/chunks/middleware/index.mjs:21:19)
  at async file://./.output/server/chunks/middleware/index.mjs:69:22

.output/server/chunks/middleware/index.mjs の冒頭部分:

import express from 'express';
import { createProxyMiddleware } from 'http-proxy-middleware';
import { S3Client, ListObjectsCommand, GetObjectCommand, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3';
import fetch from 'node-fetch';

var _a, _b;
const REGION = (_a = process.env.REGION) != null ? _a : "ap-northeast-1";
const BUCKET_NAME = (_b = process.env.BUCKET_NAME) != null ? _b : "";

const s3Client = new S3Client({ region: REGION });
const s3Client$1 = s3Client;

const streamToString = (stream) => new Promise((resolve, reject) => {
  const chunks = [];
  stream.on("data", (chunk) => chunks.push(chunk));
  stream.on("error", reject);
  stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
});
const findTrainers = async () => {
  var _a;
  const objects = await s3Client$1.send(new ListObjectsCommand({ Bucket: BUCKET_NAME }));
  return (_a = objects.Contents) != null ? _a : [];
};

process.env.BUCKET_NAME を参照してますね。こちらのコードを webpack した結果っぽい: https://github.com/webdino/lyceum-pokemon/blob/42d644afffc2c4cfc7cf5b71ba0156fd28c0d24a/express/utils/env.js

vite で .env をロードする といってるが vite がビルドした後の node .output/... を実行するときに読み込んでくれるケアまではしてくれないと言うことか。

knokmki612 commented 2 years ago

process.env.BUCKET_NAME を参照してますね

ビルド前のソースコードも process.env を参照しています nitro server側はなぜか import.meta.env が参照できませんでした(気のせいかもしれませんが)

knokmki612 commented 2 years ago

import.meta.env を使っていないためにプロダクションビルド時に vite が置換してくれず、実行時に BUCKET_NAME を引き続き環境変数として渡す必要があるということかなと思いました

kou029w commented 2 years ago

環境変数が誤ってクライアントに漏れてしまうことを防ぐために、VITE_ から始まる変数のみが Vite で処理されたコードに公開されます。

とあるとおり、環境変数を展開してバンドルするには VITE_ からはじまる名前にする必要があるかと思います。

knokmki612 commented 2 years ago

環境変数が誤ってクライアントに漏れてしまうことを防ぐために、VITE_ から始まる変数のみが Vite で処理されたコードに公開されます。

こちらはクライアント(Nuxt /pages 下など)での環境変数の参照が可能か否かに影響がある話だと思っていました

プロダクションでは、これらの環境変数は、静的に置換されます。

https://ja.vitejs.dev/guide/env-and-mode.html#production-replacement

上記引用によると VITE_ から始まるかに関わらず環境変数はバンドルに含まれるのではないでしょうか


VITE_BUCKET_NAME に環境変数名を変更してプロダクションビルドの動作検証をしてみました

diff --git a/express/utils/env.js b/express/utils/env.js
index b3ae588..d12cf19 100644
--- a/express/utils/env.js
+++ b/express/utils/env.js
@@ -1,2 +1,2 @@
 export const REGION = process.env.REGION ?? "ap-northeast-1";
-export const BUCKET_NAME = process.env.BUCKET_NAME ?? "";
+export const BUCKET_NAME = process.env.VITE_BUCKET_NAME ?? ""
  1. 上記のように変更
  2. .env の値を適切に変更
  3. yarn build && yarn start を実行
  4. http://localhost:3000/ にブラウザでアクセス

しましたが Empty value provided for input HTTP label: Bucket. が表示されました

実行結果 ```shell $ yarn start yarn run v1.22.15 $ node .output/server/index.mjs Listening on http://localhost:3000/ [HPM] Proxy created: / -> https://pokeapi.co [HPM] Proxy rewrite rule created: "^/api/pokeapi" ~> "/api/v2" Empty value provided for input HTTP label: Bucket. at Object.serializeAws_restXmlListObjectsCommand (./.output/server/node_modules/@aws-sdk/client-s3/dist-cjs/protocols/Aws_restXml.js:2399:19) at async ./.output/server/node_modules/@aws-sdk/middleware-serde/dist-cjs/serializerMiddleware.js:5:21 at async ./.output/server/node_modules/@aws-sdk/middleware-logger/dist-cjs/loggerMiddleware.js:6:22 at async findTrainers (file://./.output/server/chunks/middleware/index.mjs:21:19) at async file://./.output/server/chunks/middleware/index.mjs:69:22 Empty value provided for input HTTP label: Bucket. at Object.serializeAws_restXmlListObjectsCommand (./.output/server/node_modules/@aws-sdk/client-s3/dist-cjs/protocols/Aws_restXml.js:2399:19) at processTicksAndRejections (node:internal/process/task_queues:96:5) at async ./.output/server/node_modules/@aws-sdk/middleware-serde/dist-cjs/serializerMiddleware.js:5:21 at async ./.output/server/node_modules/@aws-sdk/middleware-logger/dist-cjs/loggerMiddleware.js:6:22 at async findTrainers (file://./.output/server/chunks/middleware/index.mjs:21:19) at async file://./.output/server/chunks/middleware/index.mjs:69:22 Cannot read properties of null (reading 'length') at file://./.output/server/chunks/app/server.mjs:3994:29 at renderFnWithContext (file://./.output/server/chunks/index.mjs:2348:21) at Object.ssrRenderSlot (file://./.output/server/chunks/index.mjs:10296:21) at _sfc_ssrRender$8 (file://./.output/server/chunks/app/server.mjs:3940:26) at renderComponentSubTree (file://./.output/server/chunks/index.mjs:9924:13) at renderComponentVNode (file://./.output/server/chunks/index.mjs:9869:16) at Object.ssrRenderComponent (file://./.output/server/chunks/index.mjs:10284:12) at _sfc_ssrRender$6 (file://./.output/server/chunks/app/server.mjs:3991:32) at renderComponentSubTree (file://./.output/server/chunks/index.mjs:9924:13) at file://./.output/server/chunks/index.mjs:9866:29 ```
kou029w commented 2 years ago

プロダクションでは、これらの環境変数は、静的に置換されます。

この「これらの環境変数」とは、その上の import.meta.env.MODE をはじめとするいくつかの特別な環境変数のことだと思います。(そういう意味では VITE_ からはじまる必要がある、という点は誤りですね。。。訂正します。)

kou029w commented 2 years ago
$ grep -nr VITE_BUCKET_ .output
.output/server/chunks/middleware/index.mjs:8:const BUCKET_NAME = (_b = process.env.VITE_BUCKET_NAME) != null ? _b : "";

これをみると確かにサーバーサイドの環境変数への参照は展開してバンドルされず、そのままのようですね。

kou029w commented 2 years ago

プロダクションビルド時に vite が置換してくれず、実行時に BUCKET_NAME を引き続き環境変数として渡す必要があるということかなと思いました

これは正しいです。

dynamis commented 2 years ago

nitro server側はなぜか import.meta.env が参照できませんでした

結局ここが困りものなわけですよね。 すべて import.meta.env で統一したコードを書けて process.env 使わずに済めば全てビルド時の静的置き換えで済むのに...

dynamis commented 2 years ago

念のため: process.env は BUCKET_NAME 以外に REGION の読み込みにも使っていた

こちらは初期値があるから問題が顕在化しないことも多いと思うが、東京リージョン以外を使う場合、REGION も環境変数設定しないと動かないという意味では同じ。

knokmki612 commented 2 years ago

26b6bbe0e3139da5f1215cfced4bc47786c5e920 で注意事項としてドキュメンテーションされたので、issueとしては対応完了になるでしょうか

dynamis commented 2 years ago

nitro server側はなぜか import.meta.env が参照できませんでした

server middleware だから Node 標準ではないフレームワークやバンドル/ビルドツールである Nuxt/Vite のビルドによる変換処理は挟まらない。なので当然 import.meta.env 発変えないという話でしたね。

process.env があるか、.env ファイルを読むかという処理を明示的に書けそうな気もするけど取りあえず環境変数も Nuxt 側と Express 側で性質が違い、それぞれに何処でどのように設定すべきか違うというのは無理に解消しなくとも良いかと思いました。

issueとしては対応完了になるでしょうか

bug ではないしそういう仕様だと思えば何ら問題ないですが、使い勝手としては何とかしたい (.env 書くだけで良い形に express 側のコードを改善したい) という要望としては残ってても良いかも。(使い勝手を便利にするために必要以上にコードが複雑化するのは初心者向けの説明・サンプルコードとしては不適と考えることも出来るけど)

knokmki612 commented 1 year ago

https://github.com/webdino/lyceum-pokemon/issues/105 の変更により、Expressのコードはtsupでビルドするようにしましたので、定数置換可能になったかと思います https://esbuild.github.io/api/#define

knokmki612 commented 1 year ago

119 を取り込むと元の状態にもどる気がしますね

dynamis commented 1 year ago

119 (と #120) をマージしたので reopen します