[Serverless Framework] yarn workspaceを使ったmonorepo


## はじめに
[Serverless Framework](https://github.com/serverless/serverless)はサーバレスなアプリケーションを簡単にデプロイできるOSSのフレームワークです。
Serverless Frameworkを使うときには、複数のパッケージを一つのリポジトリで管理する、いわゆるモノレポな構成にしたいことがあると思います。モノレポなプロジェクトの管理方法にはyarn workspaceやlernaなどがあります。

ここでは、yarn workspaceを使ったServerless Frameworkのモノレポを構築する方法を紹介したいと思います。サンプルには、AWSのAPIGatewayとLambdaを使います。

構築中にいろいろと問題が発生したので、誰かの参考になればと思います。
初心者なので、おかしいところがあれば是非指摘お願いします。

githubにもサンプルを残したので、よければ参考にしてください。

serverless-webpackを使う方法: https://github.com/allJokin/serverless-webpack-service
serverless-bundleを使う方法: https://github.com/allJokin/serverless-bundle-service


環境
`serverless@2.16.0`

## yarn workspaceの設定
以下のようなディレクトリ構成でプロジェクトを作成します。
Serverless Frameworkではサービスという単位で環境を作っていきます。

```
├── packages # serviceで使うパッケージ
|   ├── package-1
|   └── ...
├── services # lambdaなど
|   ├── package-1
|   └── ...
├── package.json
└── yarn.lock
```

yarn workspaceを有効にするためにrootのpackage.jsonを以下のように記載します。

```json:package.json
{
  "name": "root",
  "private": true,
  "workspaces": [
    "packages/*",
    "services/*"
  ]
}
```

## Serviceの作成
[公式サイトの手順](https://www.serverless.com/framework/docs/providers/aws/cli-reference/create/)を参考にAPIGatewayとLamdaのServiceを作っていきます。

aws-nodejs-typescriptのテンプレを使って、servicesの配下にapi（名前は何でもいい）を作成します。

`serverless create --template aws-nodejs-typescript --path services/api`

コマンドを実行するとapiの配下に以下のようなファイルが作成されていると思います。

```
├── .gitignore
├── handler.ts
├── package.json
├── serverless.ts
├── tsconfig.ts
└── webpack.config.js
```

handler.tsにはLambdaの中身、serverless.tsにはインフラの設定が書いてあります。

Serviceを用意したので、rootで`yarn install`しましょう。yarn workspaceを有効にしているので、apiにもモジュールがインストールされます。

モジュールをインストールしたら、services/apiにcdしてデプロイしてみましょう！
`sls deploy`


## deployできない
deployしようとすると、以下のようなエラーが出ました…。

```
ERROR in ./handler.ts 1:0-37
Module not found: Error: Can't resolve 'source-map-support/register' in '/workspaces/services/serverless-webpack-service'
```

どうやら、[serverless-webpack](https://github.com/serverless-heaven/serverless-webpack)がyarn workspaceに対応していないようです。（2020年12月時点）

以下のissueを参考にwebpack.config.ts修正します。nodeExternalsにrootのnode_moduleを指定してあげると良いようです。

https://github.com/serverless-heaven/serverless-webpack/issues/494

```ts:webpack.config.ts
externals: [nodeExternals({
    modulesDir: path.resolve(__dirname, '../../node_modules'),
  })],
```

これでデプロイできるようになりました！
なぜこれでできるようになるのか、原理はわかりません…。

## 自作のpackageを追加
自作のpackageをpackagesフォルダに追加します。文字列を返すだけの簡単なサンプルです。

```ts:index.ts
export const mypackage = () => {
  return 'This is my pakcage.';
}
```

service/apiに自作パッケージを追加します。rootで以下のコマンドを実行します。(yarn addのworkspace版)
`yarn workspace api add my-package`

handler.tsでimportして、

```ts:handler.ts
import { APIGatewayProxyHandler } from 'aws-lambda';
import 'source-map-support/register';
import { mypackage } from 'my-package';

export const hello: APIGatewayProxyHandler = async (_event, _context) => {
  return {
    statusCode: 200,
    body: JSON.stringify({
      message: mypackage()
    }, null, 2),
  };
}
```

deployします！

## deployできない Part2

今度は以下のようなエラー。

```
  Error: npm install failed with code 1
      at ChildProcess.<anonymous> (/workspaces/node_modules/serverless-webpack/lib/utils.js:91:16)
      at ChildProcess.emit (events.js:314:20)
      at ChildProcess.EventEmitter.emit (domain.js:483:12)
      at maybeClose (internal/child_process.js:1021:16)
      at Process.ChildProcess._handle.onexit (internal/child_process.js:286:5)
```

今度は、以下のissueの最後にある修正をします。allowlistにpackage名を入れてあげる。

https://github.com/serverless-heaven/serverless-webpack/issues/370

```ts:webpack.config.ts
  ...
  externals: [
    nodeExternals({
      modulesDir: path.resolve(__dirname, '../../node_modules'),
      allowlist: ['my-package'],
    })
  ],
  ...
```

デプロイできた！URLにアクセスすると、ちゃんと表示されているので動いてそう。なぜこれでできるようになるのか、これも原理はわかりません…。

![deploy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/600358/210ef1d2-c913-dd53-b2a2-f45877a3ff8c.png)


## もっと簡単な対処法

webpack.config.tsをいろいろ修正して、なんとかデプロイできました。実はもっと簡単な方法がありました。
それは、[serverless-bundle](https://github.com/AnomalyInnovations/serverless-bundle)を使う方法です。

これはwebpackのconfigを書かなくてもいい感じにやってくれるプラグインのようです。

導入はとても簡単で、

`serverless create --template aws-nodejs-typescript --path services/api`

をした後に、不要なファイル(webpack.config.ts)を削除して、以下のファイルを変更するだけです。

```json:package.json
  "devDependencies": {
    "@serverless/typescript": "^2.12.0",
    "@types/aws-lambda": "^8.10.64",
    "@types/node": "^14.14.6",
    "serverless-bundle": "^4.1.0",
    "ts-loader": "^8.0.10",
    "ts-node": "^9.0.0",
    "typescript": "^4.0.5"
  }
```

```ts:serverless.ts
  custom: {
    bundle: {
      packager: 'yarn',
      tsConfig: './tsconfig.json',
      caching: false
    }
  },
```

```json:tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".", // 追加
    "lib": ["es2017"],
```

これでデプロイできるようになると思います！
webnpackのconfigを細かく調整する必要がないならこれが楽でいいかもしれません。

## 結論
Serverless Frameworkでyarn workspaceを使う方法について紹介しました。serverless-webpackでいろいろエラーが出ましたが、なんとか動くようになりました。issueでも議論されているので、いつか対応されるかもしれません。issue通りに修正してなぜ動くのかは今後勉強したいと思います。

serverless-bundleは導入するだけでいい感じにbuildしてくれるので、それまではserverless-webpackではなく、こっちを使うのがいいかもしれません。