webpackとApexを使ったLambda開発 – ClassmethodサーバーレスAdvent Calendar 2017 #serverless #adventcalendar

2017.12.02

背景

Lambda Functionでサーバーレスアプリケーションを作ったり、公式のAlexa Skills Kit SDKを使ってコードを書く機会が増えています。
ある程度の規模や複雑さを超えると、以下のような問題に直面するでしょう。

  • FlowやBabelなどの開発ツールを使いたい
  • npmを使って独自の機能を使おうとすると、Lambda Functionにアプロードするzipファイル容量が大きくなるので、抑制したい

このような問題は、webpackを使うことで解決できます。

今回は、有名なLambda FunctionデプロイツールApexと、webpackを併用する方法をご紹介します。

本文

前提

Lambdaに合わせたNode.js(v6.10.3)と、npmまたはyarnが利用可能であることを前提とします。
今回のファイルは以下のようなディレクトリ構成です。

.
├── .apexignore
├── .eslintrc
├── .gitignore
├── README.md
├── functions
│   └── entrypoint
│       └── src
│           └── index.js <== Lambda Functionのエントリポイント
├── package.json
├── project.json
├── webpack.config.js
└── yarn.lock

package.json

webpackはdevDependenciesに加えます。   以下のpackage.jsonにあるとおり、今回はeslintの設定も加えてみます。また、実行時エラーでスタックトレースを追えるように、source mapも追加してみます。

{
  "private": true,
  "license": "UNLICENSED",
  "engines": {
    "node": "6.10.3"
  },
  "scripts": {
    "lint": "eslint functions/entrypoint/"
  },
  "dependencies": {
    "source-map-support": "^0.5.0"
  },
  "devDependencies": {
    "eslint": "^4.3.0",
    "eslint-loader": "^1.9.0",
    "eslint-plugin-node": "^5.1.1",
    "webpack": "^3.3.0"
  }
}

webpack.config.js

webpack.config.js は、webpackの動作オプションを設定するファイルです。各設定項目の詳細は公式ドキュメントをご覧ください。

const path = require('path');
const Webpack = require('webpack');

exports.default = {
  entry: './src/index.js',
  target: 'node',
  output: {
    path: path.join(process.cwd(), 'build'),
    filename: 'index.js',
    libraryTarget: 'commonjs2'
  },
  externals: ['aws-sdk'],
  module: {
    rules: [
      {
        test: /\.js$/,
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
          fix: true,
          failOnError: true,
          failOnWarning: true,
          emitError: true
        },
        exclude: [/node_modules/]
      }
    ]
  },
  plugins: [
    new Webpack.NoEmitOnErrorsPlugin(),
    new Webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('production') // 環境変数はwebpackで展開されるので、ここで設定します
      }
    }),
    new Webpack.LoaderOptionsPlugin({
      minimize: true,
      debug: false
    })
  ],
  devtool: 'inline-source-map',
  bail: true
};

Apex設定ファイル

デプロイツールApexの設定ファイルです。詳細は公式ドキュメントをご覧ください。

project.json

{
  "name": "myapp",
  "description": "",
  "memory": 128,
  "timeout": 10,
  "role": "arn:aws:iam::01234567890123:role/lambda_basic_role",
  "runtime": "nodejs6.10",
  "handler": "build.handler",
  "hooks": {
    "build": "../../node_modules/.bin/webpack --config ../../webpack.config.js",
    "clean": "rm -rf build"
  },
  "environment": {
  }
}

.apexignore

apexで作成するzipファイルには、webpackを通したファイルのみを含めるので、ソースコードそのものは除外します。

src/

index.js

'use strict';

require('source-map-support').install();

exports.handler = function(event, context, callback) {
    // do something
};

apex deploy時の動作

apex deployコマンドを実行する際、project.jsonのhooks.buildで設定したフックが実行されます。今回の場合、「index.jsがあるビルドフック実行時のカレントディレクトリから2つ上の/node_modules/.bin/webpack を実行する」となります。   webpack実行時に指定した設定ファイルのoutputにもとづき、 functions/entrypoint/build/index.js がビルド結果としてwebpackにより出力されます。
これでビルドフックが終了したので、apexはzipファイルを作成してAWSにアップロードし、バージョンとエイリアスの処理を行います。
その後、cleanフックで指定したとおり、webpackの出力用ディレクトリ(build/)を削除します。

まとめ

私の経験ですが、webpackを使うことで、zipファイルのサイズが10MBから500KB程度に削減され、開発時の快適な動作確認環境を作ることができました。
webpackの設定によっては、FlowTypeやBabelなどのトランスパイラを使用して、複雑なFunctionを作ることができます。  

参考: https://github.com/apex/apex/tree/master/_examples/babel-webpack2