Serverless Frameworkで作ったNode.jsプロジェクトをTypeScriptへリプレイスする

基本的にServerless FrameworkでAPIや処理系等を実装しているのですが、同梱するライブラリが増えてくるとどうしてもデプロイサイズが気になってきます。 webpackでビルドしてサイズを減らすことも考えた […]

広告ここから
広告ここまで

目次

    基本的にServerless FrameworkでAPIや処理系等を実装しているのですが、同梱するライブラリが増えてくるとどうしてもデプロイサイズが気になってきます。

    webpackでビルドしてサイズを減らすことも考えたのですが、どうせならTypeScriptに段階的に切り替えてみようかなと思ったのでトライしました。

    準備

    まずはマイグレーション対象のプロジェクトを作りましょう。

    $ sls create -t aws-nodejs -p sls-migration
    $ cd sls-migration
    $ sls deploy
    Service Information
    service: sls-migration
    stage: dev
    region: us-east-1
    stack: sls-migration-dev
    api keys:
      None
    endpoints:
      None
    functions:
      hello: sls-migration-dev-hello

    素のままだとファイル削減の効果も見えにくいので、SDKをいくつか入れて重しにしておきます。

    $ npm init -y
    $ npm i -S ask-sdk watson-developer-cloud
    $ sls deploy function -f hello
    Serverless: Packaging function: hello...
    Serverless: Excluding development dependencies...
    Serverless: Uploading function: hello (11.34 MB)...
    Serverless: Successfully deployed function: hello

    11MB。いい感じです。選んだSDKはAWS SDK以外で使ったり興味があったりするもので、特に他意はありませんのでご了承ください。

    $ sls invoke -f hello -d '{"key": "value"}'
    {
        "statusCode": 200,
        "body": "{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":{\"key\":\"value\"}}"
    }

    動作も問題ない様子です。

    TypeScriptを導入する

    ここからいよいよTypeScriptを入れていきます。ここからはビルドしたものをデプロイする形になりますので、そのあたりの処理も入れる必要があります。

    幸いsls createのテンプレートにaws-nodejs-typescriptがありますので、ここから部品を取る形で進めます。

    $ cd ../
    $ sls create -t aws-nodejs-typescript -p ts-sls
    $ cd ts-sls
    $ tree -L 1 -I node_module
    .
    ├── handler.ts
    ├── package.json
    ├── serverless.yml
    ├── source-map-install.js
    ├── tsconfig.json
    └── webpack.config.js
    
    0 directories, 6 files

    source-map-install.js, tsconfig.js,webpack.config.jsが新しく追加されています。また、package.jsonを見るといくつかのライブラリが含まれています。

    $ cat package.json  | jq .devDependencies
    {
      "@types/aws-lambda": "8.10.1",
      "@types/node": "^8.0.57",
      "serverless-webpack": "^5.1.1",
      "source-map-support": "^0.5.6",
      "ts-loader": "^4.2.0",
      "typescript": "^2.9.2",
      "webpack": "^4.5.0"
    }

    これらを先ほどのプロジェクトに入れていきましょう。

    # プロジェクトに戻る
    $ cd ../sls-migration
    
    # ライブラリインストール(webpackは5系を入れるとうまく行かないので要注意)
    $ npm i -D serverless-webpack source-map-support ts-loader typescript webpack@4
    
    # ファイルをコピー
    $ cp ../ts-sls/source-map-install.js ./
    $ cp ../ts-sls/webpack.config.js ./
    $ cp ../ts-sls/tsconfig.json ./

    また、serverless.ymlを開いて以下の2行を追加しましょう。

    plugins:
      - serverless-webpack

    これでTypeScript(+webpack)を使う準備ができました。この状態でデプロイすると、webpackによるビルドが実行されます。

    $ sls deploy function -f hello
    <中略>
     @ multi ./source-map-install.js ./handler.js
    Serverless: Packaging function: hello...
    Serverless: Uploading function: hello (2.15 MB)...
    Serverless: Successfully deployed function: hello

    2.15MBまで減りましたね。およそ1/5。これだけでも十分に大きいのですが、せっかくなのでTypeScriptの力を借りてみましょう。

    TypeScriptのハンドラーを追加する

    ここまででは、TypeScriptをビルドに使用していますが対象となるファイルがない状態です。ですので.tsファイルを追加してみましょう。

    $ vim handler2.ts
    'use strict';
    module.exports.hello = async (event, context) => {
      return {
        statusCode: 200,
        body: JSON.stringify({
          message: 'Go Serverless v1.0! Your function executed successfully!',
          input: event
        }),
      };
    };

    まずは先ほどのhandler.jsに書いている内容をそのままコピーします。これだけでは対象ファイルに含まれませんので、serverless.ymlにも定義を追加しましょう。

    functions:
      hello:
        handler: handler.hello
      hello2:
        handler: handler2.hello

    これでTypeScriptファイルもビルドされるようになります。また、tsconfig.jsonを以下のようにすることでより厳密なチェックを受けることが可能です。

    {
      "compilerOptions": {
        "sourceMap": true,
        "target": "es6",
        "lib": [
          "esnext"
        ],
        "strict": true,
        "moduleResolution": "node"
      },
      "exclude": [
        "node_modules"
      ]
    }

    strictをtrueにすると、webpackのビルドがこけるようになります。

    $ sls deploy
    ERROR in /Users/develop/sandbox/sls-migration/handler2.ts
    ./handler2.ts
    [tsl] ERROR in /Users/develop/sandbox/sls-migration/handler2.ts(2,31)
          TS7006: Parameter 'event' implicitly has an 'any' type.
    
    ERROR in /Users/develop/sandbox/sls-migration/handler2.ts
    ./handler2.ts
    [tsl] ERROR in /Users/develop/sandbox/sls-migration/handler2.ts(2,38)
          TS7006: Parameter 'context' implicitly has an 'any' type.
    
      Error --------------------------------------------------
    
      Webpack compilation error, see above
    
         For debugging logs, run again after setting the "SLS_DEBUG=*" environment variable.
    
      Get Support --------------------------------------------
         Docs:          docs.serverless.com
         Bugs:          github.com/serverless/serverless/issues
         Issues:        forum.serverless.com
    
      Your Environment Information -----------------------------
         OS:                     darwin
         Node Version:           10.1.0
         Serverless Version:     1.32.0

    「引数に型が付いてないぞ!」ということなので、追加してやりましょう。

    $ npm i -D @types/aws-lambda @type/node

    handler2.tsも以下のように変更します。

    import { APIGatewayEvent, Handler } from 'aws-lambda';
    export const hello: Handler = async (event: APIGatewayEvent) => ({
      statusCode: 200,
      body: JSON.stringify({
        message: 'Go Serverless v1.0! Your function executed successfully!',
        input: event
      })
    })

    eventに設定する型は使用するイベントによって変わります。今回のサンプルはAPI Gatewayのバックエンドとして利用するものを設定しました。

    これで再度deployを実行すると、今度はエラーなくビルドが成功します。

    おわりに

    allowJs の設定をしていない状態ですが、今のところこの状態のままでJS / TypeScriptの共存が可能な様子です。

    段階的に移行するということが可能ですので、ファイルサイズが気になったり型が欲しいなと思った方はぜひお試しください。

    広告ここから
    広告ここまで
    Home
    Search
    Bookmark