🏃‍♂️

Serverless Framework V4でビルドしたコードの実行時にエラーが発生した時の対処法

に公開

こんにちは。株式会社シータグCTOの @y_okady です。

シータグでは社内業務の自動化にAWS Lambda + Serverless Framework V4を使用しています。従来はJavaScriptでLambda関数を記述していましたが、つい先日シータグ社内の社内標準プログラミング言語にTypeScriptを採用したことを受けて、TypeScriptへの移行を実施しました。

Serverless Framework V4ではLambda利用時にTypeScriptがネイティブでサポートされ、プラグインなしでTypeScriptファイルを扱うことができるようになりました。
https://www.serverless.com/framework/docs/guides/upgrading-v4#native-typescript-support

これは、Serverless Framework V4に esbuild が組み込まれたことによって実現されており、TypeScriptでLambda関数を記述したらデフォルトで自動ビルドされるそうです。
https://www.serverless.com/framework/docs/providers/aws/guide/building

実際に試してみたところ、ビルド環境を整備しなくてもTypeScriptを扱うことができるのはとても便利でした。一方で、依存関係によってビルドしたコードの実行時にエラーが発生する場合がありました。

この記事では、Serverless Framework V4 TypeScriptネイティブサポートの使い方と、ビルドしたコードの実行時にエラーが発生した時の対処法をご紹介します。

サンプルファイル

以下のファイルを用いて動作を確認してみます。今回はESMで出力したいので package.jsontypemodule を指定しています。

package.json
{
  "main": "test.js",
  "type": "module",
  "devDependencies": {
    "serverless": "^4.14.3"
  },
  "dependencies": {
    "date-fns": "^4.1.0"
  }
}
serverless.yml
service: ctag

provider:
  name: aws
  runtime: nodejs22.x
  region: ap-northeast-1

functions:
  test:
    handler: test.default
    events:
      - schedule: cron(0 0 * * ? *) # 毎日JST9:00
test.ts
import { format } from "date-fns";

export default () => {
  console.log(format(new Date(), "現在 HH 時 mm 分 ss 秒 (O) です"));
};

コマンドを試してみる

ローカルでの関数実行

$ npx sls invoke local --function test

現在 16 時 02 分 54 秒 (GMT+9) です

TypeScriptやビルドの設定をしなくても、sls invoke local コマンドを実行するだけでローカルで関数を実行できました。

Lambdaへのデプロイ

$ npx sls deploy

Lambdaへのデプロイも、TypeScriptやビルドの設定は不要です。

Lambda関数の実行

$ npx sls invoke --function test --log

null

----------------------

START - 10151cdc-56be-4b3a-8ba4-f4ccc9c96252 - Version: $LATEST
2025-05-20 16:14:27.323 - 10151cdc-56be-4b3a-8ba4-f4ccc9c96252 - 
INFO    現在 7 時 14 分 27 秒 (GMT+0) です

REPORT - 10151cdc-56be-4b3a-8ba4-f4ccc9c96252
Duration: 7.63 ms       Billed Duration: 8 ms   Memory Size: 1024 MB    Max Memory Used: 83 MB  Init Duration: 150.83 ms

定期実行関数であっても、sls invoke コマンドまたはAWS マネジメントコンソールで即時実行可能です。

Lambda関数の実行時にエラーが発生するケースその1(axios を利用)

test.tsimport axios from "axios";console.log(axios); を追記し、Lambdaにデプロイして関数を実行するとエラーが発生しました。

$ npm install --save axios
$ npx sls deploy && npx sls invoke -f test

{
    "errorType": "Error",
    "errorMessage": "Dynamic require of \"util\" is not supported",
    "trace": [
        "Error: Dynamic require of \"util\" is not supported",
        "    at file:///var/task/test.js:11:9",
        "    at node_modules/combined-stream/lib/combined_stream.js (/node_modules/combined-stream/lib/combined_stream.js:1:12)",
        "    at __require2 (file:///var/task/test.js:14:50)",
        "    at node_modules/form-data/lib/form_data.js (/node_modules/form-data/lib/form_data.js:1:22)",
        "    at __require2 (file:///var/task/test.js:14:50)",
        "    at <anonymous> (/node_modules/axios/lib/platform/node/classes/FormData.js:1:22)",
        "    at ModuleJob.run (node:internal/modules/esm/module_job:271:25)",
        "    at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:578:26)",
        "    at async _tryAwaitImport (file:///var/runtime/index.mjs:1008:16)",
        "    at async _tryRequire (file:///var/runtime/index.mjs:1057:37)"
    ]
}

ビルドされたファイルが保存される .serverless/build ディレクトリ以下にある test.js を確認すると以下の記述がありました。requireundefined のためにエラーが発生しているようです。

(function(x) {
  if (typeof require !== "undefined") return require.apply(this, arguments);
  throw Error('Dynamic require of "' + x + '" is not supported');
});

axios の内部で require が利用されているにも関わらず、ESMのコードを生成しようとしてエラーになっているものと思われます。そこで、package.jsontypecommonjs に変更することで正常に実行することができました。

Lambda関数の実行時にエラーが発生するケースその2(@kintone/rest-api-client を利用)

同じように test.tsimport { KintoneRestAPIClient } from "@kintone/rest-api-client";console.log(KintoneRestAPIClient); を追記し、Lambdaにデプロイして関数を実行するとエラーが発生しました。

$ npm install --save @kintone/rest-api-client
$ npx sls deploy && npx sls invoke -f test

{
    "errorType": "Error",
    "errorMessage": "require() of ES Module /var/task/test.js from /var/task/test.js not supported.\nInstead change the require of /var/task/test.js in /var/task/test.js to a dynamic import() which is available in all CommonJS modules.",
    "trace": [
        "Error [ERR_REQUIRE_ESM]: require() of ES Module /var/task/test.js from /var/task/test.js not supported.",
        "Instead change the require of /var/task/test.js in /var/task/test.js to a dynamic import() which is available in all CommonJS modules.",
        "    at TracingChannel.traceSync (node:diagnostics_channel:322:14)",
        "    at file:///var/task/test.js:1578:5"
    ]
}

.serverless/build/test.js を確認すると以下の記述がありました。module.createRequire が返す関数の内部で require が利用されているようです。

import module from "module";
var require2 = module.createRequire(import.meta.url);
var {
  KintoneRestAPIClient,
  KintoneAbortSearchError,
  KintoneAllRecordsError,
  KintoneRestAPIError
} = require2(".");

それならばと package.jsontypecommonjs に変更したところ、今後は別のエラーが発生しました。

$ npx sls deploy && npx sls invoke -f test

{
    "errorType": "TypeError",
    "errorMessage": "The argument 'filename' must be a file URL object, file URL string, or absolute path string. Received undefined",
    "trace": [
        "TypeError [ERR_INVALID_ARG_VALUE]: The argument 'filename' must be a file URL object, file URL string, or absolute path string. Received undefined",
        "    at Function.createRequire (node:internal/modules/cjs/loader:1777:11)",
        "    at Object.<anonymous> (/node_modules/@kintone/rest-api-client/index.mjs:3:24)",
        "    at Module._compile (node:internal/modules/cjs/loader:1554:14)",
        "    at Object..js (node:internal/modules/cjs/loader:1706:10)",
        "    at Module.load (node:internal/modules/cjs/loader:1289:32)",
        "    at Function._load (node:internal/modules/cjs/loader:1108:12)",
        "    at TracingChannel.traceSync (node:diagnostics_channel:322:14)",
        "    at wrapModuleLoad (node:internal/modules/cjs/loader:220:24)",
        "    at Module.require (node:internal/modules/cjs/loader:1311:12)",
        "    at require (node:internal/modules/helpers:136:16)"
    ]
}

.serverless/build/test.js を確認すると以下の記述がありました。import_meta.url を使っているにも関わらず、import_meta が空オブジェクトになってしまっています。

var import_meta = {};
var require2 = import_module.default.createRequire(import_meta.url);

以下の記事によると、import.meta.url が含まれるコードを esbuild でCJSに変換するとこのような現象が起こるそうです。
https://zenn.dev/sosukesuzuki/articles/44992fc109ddb2

axios のケースではESMをCJSに変更することでエラーを回避できましたが、@kintone/rest-api-client のケースではESMでもCJSでもエラーが発生してしまいます。こういう時は esbuild のビルドオプション external を利用します。

serverless.yml に以下を追記してデプロイします。

serverless.yml
build:
  esbuild:
    external:
      - "@kintone/rest-api-client"

すると、.serverless/build/test.js@kintone/rest-api-client はバンドルされず、以下のように import 文がそのまま残ります。また、.serverless/build/node_modules が新たに作成され、その中に @kintone/rest-api-client の依存関係が格納されていることが確認できます。これで無事に正常に実行することができました。

test.js
import { KintoneRestAPIClient } from "@kintone/rest-api-client";

まとめ

Serverless Framework V4でビルドしたコードの実行時にエラーが発生したら、次のことを試してみてください!

  • package.jsontype を変更する
  • esbuild のビルドオプション external を利用する
株式会社シータグ

Discussion