Serverless Framework + webpack-pluginでTypeScriptなwebサイト基盤

このページは  [Wanoグループ　Advent Calendar 2016](http://qiita.com/advent-calendar/2016/wano-group) の3日目の記事となります。
ギリギリになってしまった。

Serverless Framewok v1.1 でtypescript & babelなベースを整えるとこまでやったのでメモっておきます。
S3の静的サイトホスティング以外でのどケチなサイト運営を模索していきたい。
今回は jsonを返すデモ と HTMLを返すデモを作るとこまで目指します。

## serverless frameworkでのプロジェクトセットアップ

```
serverless create プロジェクト名

```
でプロジェクトのひな形を作っておきます。


プロジェクトの設定で、webpackプラグインやbabel,typescriptを適用するためのモジュールをインストールします。

```zsh

npm install babel-loader　babel-core　babel-preset-es2015　ntypescript　serverless-webpack　ts-loader
```

serverless コマンドでプロジェクトを作ったあと、serverless.ymlで
プロジェクトの設定を行います。
書いてないけどLambda関数のタイムアウト時間やメモリ設定もここ。

```yaml

service: serverless-webpack-advent-demo

plugins:
  - serverless-webpack # ここでプラグインを指定します

provider:
  name: aws
  runtime: nodejs4.3
  stage  : production
  profile: xxxx # aws-cliにおけるprofileを設定します。
  regeon : ap-northeast-1


functions:
  apiDemo:
    handler: handler.apiDemo 　# 動かすlambda関数名
    events:
      - http:
          method: get
          path: apiDemo
          response:
            headers:
              Content-Type: "'application/json'"
            template: $input.path('$')
  htmlDemo:
    handler: handler.htmlDemo # 動かすlambda関数名
    events:
      - http:
          method: get
          path: htmlDemo
          response:
            headers:
              Content-Type: "'text/html'"
            template: $input.path('$')
```


## webpack設定

[serverless-webpack](https://github.com/elastic-coders/serverless-webpack)
webpackプラグインが入ったので、ビルド段階で typescriptやbabelを通して、AWS lambda上のnodejs4.3で使えない段階の機能にも対応できるようにしておきます。

個人的にgulpやgrunt等のタスクランナーはすぐに使わなくなりましたが、
webpackはビルド速度の速さや cssの多段sourcemap作成のためにもとても便利なので、しばらく離れられそうにないです。

また,serverless-webpackは

```
serverless webpack serve
```

してサーバーを動かすことで、ローカルでapi gateway + lambdaのテストもできます。

development.webpack.config.js

```javascript

const path = require('path');

module.exports = {
    entry: './ts/handler.ts',
    output: {
        libraryTarget: 'commonjs',
        path: path.join(__dirname, '.webpack'),
        filename: 'handler.js'
    },
    target: 'node',
    resolve: {
        // Add `.ts` and `.tsx` as a resolvable extension.
        extensions: ['', '.webpack.js', '.web.js', '.ts', '.tsx', '.js']
    },
    module: {
        loaders: [
            {
                test: /\.ts(x?)$/,　// .ts or .tsx にフックさせてbabelやtypescriptを動かす
                exclude: /node_modules/,
                loader: 'babel-loader?presets[]=es2015!ts-loader'
            }
        ]
    },

    ts: {
        compiler: 'ntypescript'// 最新版のtypescriptもts-loaderで使えるように入れておきます
    }
};


```
## typescript設定
tsconfig.json　を書きます。webpackでts-loaderを書く場合、コンパイル時の設定としてここを見に来るようになります。

```json
{
    "compilerOptions": {
        "module": "commonjs",
        "removeComments": true,
        "target" : "es6",
        "allowJs" : true,   
        "pretty" : true,
        "noImplicitThis" : true,
        "jsx" : "react",
        sourceMap": true"
    }
}

```


## エントリーポイントになるhandler.ts

serverless.ymlで定義したLambda関数の実装を作ってみます。
非同期チェーンの記述に便利な async/await も使えるようになったので意味もなく試しておきます。
TypeScriptって言っといて今回は型がガバガバです。

```javascript

export const apiDemo = (event, context, callback) => {
    const response = {
        headers: {
            "Content-Type":"application/json"
        },
        statusCode: 200,
        body: JSON.stringify({
            message: 'TypeScriptでコンパイルしますた'
        }),
    };

    if (context.succeed != null){
        context.succeed(response); //<= LAMBDA-PROXYとしてAPIを作成するときはこっちを使わないといけない。

    } else {
        callback(null , JSON.parse(response.body));
    }


};

export const htmlDemo = (event , context , callback) => {
    const response = {
        headers: {
            "Content-Type":"text/html"
        },
        statusCode: 200,
        body: `
            <html>
                <head>
                    <meta charset="utf-8">
                    <title>lambda html sample</title>
                </head>
                <body>
                    <div id="content">
                      デモ
                    </div>
                </body>
            </html>
        `
    };

    // test
    (async()=>{
        await new Promise((resolve)=>{
            setTimeout(resolve, 1);
        });

        if (context.succeed != null){
            context.succeed(response);
        } else {
            callback(null , response.body); // <-- でもHTML返すときはこっち使うみたい
        }
    })();
};

```

## ローカルでのテスト
前述の serverless-webpack の機能を使ってローカルでコンパイルの自動化とブラウザ上のテストができるようにしておきます。
この手のシェルスクリプトは npm scriptとしてpackage.jsonに記述して便利にしておきます。

package.json

```javascript
{
  ....(略)
  "scripts": {
    "watch": "./node_modules/.bin/webpack --watch",
    "serve": "serverless webpack serve",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  ....(略)

```


これで

```
npm run watch
```

でコードの自動的な差分ビルドができ、

```
npm run serve
```
でローカルサーバーが立ち上がります。
あとはこのみで livereload　など入れれば、自動でコード変更後のブラウザリロードも行うようになります。

これで

http://localhost:8000/htmlDemo
http://localhost:8000/apiDemo

にアクセスすることで、それぞれの api gateway / lambda function が　ローカルで動くようになりました。

デプロイは

```
serverless deploy
```
を叩くだけです。

今回はここまでですが、今後 URLのルーティング や テンプレートの読み込み、DynamoDBなどとの連携も模索していこうかと考えています。(Reactのサーバーサイドレンダリングとかもね)