devDependenciesを削除してAWS Lambdaに高速アップロード

*この記事は[A JavaScript user’s First AWS Lambda function](https://medium.com/@wataruoguchi/a-javascript-users-first-aws-lambda-function-ed74c0441d6)からの一部転用です。

# TL;DR
`npm prune`コマンドでdevDependenciesを一旦削除したらいいよね。

# はじめに
Wataruです。カナダのバンクーバーというところでCivic Tech関連の小さな会社でソフトウェアデベロッパーとして働いています。なぜカナダで働いているか、もし興味があったら[note書きました](https://note.mu/wataruoguchi/n/n8992fd5d3cef)ので読んで頂けると嬉しいです。
仕事の半分以上はフロントエンドを担当しています。Firebaseを触って以来、サーバレスアーキテクチャに興味があり、AWSの勉強を始めました。日本で購入した[AWSによるサーバーレスアーキテクチャ](https://www.amazon.co.jp/AWS%E3%81%AB%E3%82%88%E3%82%8B%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%83%AC%E3%82%B9%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3-Peter-Sbarski/dp/4798155160)を参考に学習を進めています。

# 本題
上記の書籍の第3章にて、node.jsアプリケーションのAWS Lambdaへのデプロイについて触れられています。デプロイするアプリケーションの`package.json`(一部)は以下の通りです。

```json
// package.json
{
...
  "scripts": {
    ...
    "deploy": "aws lambda update-function-code --function-name arn:aws:lambda:us-east-1:000000000000:function:transcode-video --zip-file fileb://Lambda-Deployment.zip",
    "predeploy": "zip -r Lambda-Deployment.zip * -x *.zip *.log"
  }
...
}
```

デプロイは以下のnpmコマンドで行なっています。

```shell
$ npm run deploy
```

コマンドが実行されると、`package.json`に書かれた`predeploy`が実行され、`deploy`が実行される、という流れになっています。

1. `predeploy`: 作業ディレクトリの全てを"Lambda-Deployment.zip"という名前で圧縮。
2. `deploy`: AWSサーバーに圧縮したzipファイルをアップロード。

書籍で紹介されているサンプルプログラム程度ならこのままでいいのですが、unit testを書いたりESLintやprettierなんかを入れたりしていると、devDependenciesが増えてしまい、zipファイルのサイズが大きくなってしまいます。大きくなればなるほど、アップロードに時間もかかってしまいますね。devDependenciesはアップロードする必要が無いので、いっそのこと消してしまいましょう。

# npm prune
[npm prune](https://docs.npmjs.com/cli/prune.html)はnpmのCLIコマンドの一つです。ドキュメントを引用すると：

> This command removes “extraneous” packages. If a package name is provided, then only packages matching one of the supplied names are removed.
>
> Extraneous packages are packages that are not listed on the parent package’s dependencies list.

翻訳してみます。

> このコマンドは"余計な"パッケージを削除します。パッケージ名がコマンド実行時に与えられていたら、その名前に一致したものだけが削除されます。
>
> 余計なパッケージとは、そのパッケージを使っているpackage.json内のdependenciesの一覧にないパッケージのことです。

`npm prune`には`--production`フラグというものがあります。このフラグをつけてコマンドを実行すると、`devDependencies`で指定された全てのファイルが消去されます。

devDependenciesを削除できればzipファイルを軽量化できそうですね。しかし、このコマンドで削除したパッケージは開発には必要なものばかりです。削除した後でまたダウンロードするのも時間がかかりそうですよね。

# npm install --offline
`npm install`には[ドキュメント](https://docs.npmjs.com/cli/install.html)には記載されていない、おそらく非公式の`--offline`というフラグがあります。npm CLIのデベロッパー[@zkat](https://github.com/zkat)がそのフラグについて[npmのissueの中で言及しています](https://github.com/npm/npm/issues/2568#issuecomment-331753223)。以下、部分的に引用します。

> - `npm@5` will __automatically__ install from cache if it doesn't detect network, or if your router lacks external access. So if you want to use npm on a plane, you can just use it. You don't need `--offline`. It'll Just Work™.
> - `--offline` will ONLY be useful when you want to __guarantee__ that no network access is done, and you want npm to crash when it doesn't have something in cache (this does not include git dependencies right now). So, this is what you use if you've hit your data cap and want to make damn sure npm doesn't suck up more data. Or, like, if you're temporarily tethering off your phone.

翻訳してみます。

> - `npm@5`はネットワークが検知されなかったら自動的にキャッシュからインストールします。飛行機などwifiがない環境でnpmを使いたかったら普通に使うことができます。`--offline`フラグを使う必要はありません。
> - `--offline`フラグは、あなたがどうしてもネットワーク接続なしでnpmを使いたい時"だけ"便利です。キャッシュにパッケージがなかったらnpmコマンドはクラッシュするでしょう(git dependenciesは2017年9月時点でキャッシュサポートされていません)。あなたがインターネットのデータ使用量を超過してしまった時や、電話を使ってテザリングしているときなどに使えます。

`--offline`フラグを使って明示的にキャッシュからインストールするようにすれば、削除したdevDependenciesの再ダウンロードの時間を抑えることができそうです。

これらのnpmコマンドを使った`package.json`に書き換えてみます。

```json
// package.json
{
...
  "scripts": {
    ...
    "deploy": "aws lambda update-function-code --function-name arn:aws:lambda:us-east-1:000000000000:function:transcode-video --zip-file fileb://Lambda-Deployment.zip",
    "predeploy": "rm Lambda-Deployment.zip > /dev/null && npm prune --production && zip -r Lambda-Deployment.zip * -x *.zip *.log",
    "postdeploy": "npm install --offline"
  }
...
}
```

先程と少し異なり、`postdeploy`を追加しました。

1. `predeploy`:
  1. 今ある"Lambda-Deployment.zip"を削除。
  2. `npm prune --production`でdevDependenciesを削除。
  2. 作業ディレクトリの全てを"Lambda-Deployment.zip"という名前で圧縮。
2. `deploy`: AWSサーバーに圧縮したzipファイルをアップロード。
3. `postdeploy`: キャッシュから全てのパッケージをインストール。

# 結果
devDependenciesを削除することで、私の場合はアップロードするzipファイルのサイズを26.3 MBから5.6 MBまで抑えることに成功しました。

# おわりに
削除したり再インストールしたりするのでもしかしたら余計に時間がかかるかも、と思っていましたが、意外と時間をかけることなくデプロイすることができました。このあたりはdependenciesの量で変わったりするかもしれません。
[元の記事](https://medium.com/@wataruoguchi/a-javascript-users-first-aws-lambda-function-ed74c0441d6)では ESLint + prettier や husky + lint-staged + jest についても触れています。Qiitaではたくさんの方が書いているようですので省略しましたが、もし興味があれば教えてください。学び始めたばかりですが、共有できることもあるかと思います。

読んでいただきありがとうございました。