ServerlessFrameworkのテンプレをYarn Workspacesで扱うチュートリアル


## 概要

複数のServerlessFrameworkプロジェクトをYarn Workspacesを利用して１リポジトリに収めた際の備忘録です。

GitHubにコードサンプルを上げています：https://github.com/shu000/serverless-ts-monorepo-sample

### Dependencies

- Node.js v14.15.1
- Yarn v1.22.5
- ServerlessFramework v2.52.1

## Step 1. テンプレートのインストール

まずは、ServerlessFrameworkのテンプレートを利用して、`hello`パッケージを作成します。

```shell
sls create --template aws-nodejs-typescript --path hello
```

複数のパッケージを用意したいので、同様に`bye`パッケージも作成します。

```shell
sls create --template aws-nodejs-typescript --path bye
```

これでディレクトリはこのような状態です（一部ファイル省略）。

```
bye/
    src/
        functions/
        libs/
    package.json
    serverless.ts
    webpack.config.js
hello/
    src/
        functions/
        libs/
    package.json
    serverless.ts
    webpack.config.js
```

（以下、helloパッケージのコードサンプルが登場しますが、byeパッケージでも同一の変更が必要です。）

## Step 2. モノレポ化

ここまでで１リポジトリ内に２つのnpmパッケージを設置しました。
ここからYarn Workspacesを活用してモノレポと呼べる構成にしていきます。

```
packages/
    bye/
        src/
            functions/
            libs/
        package.json
        serverless.ts
        webpack.config.js
    hello/
        src/
            functions/
            libs/
        package.json
        serverless.ts
        webpack.config.js
```

次にルートディレクトリにpackage.jsonを作成して、Yarn Workspaces設定を記述します。
また、今回は子パッケージのdevDependenciesは共通依存と見做し、ルートのpackage.jsonに書き写してします。

```json:/package.json
{
  "name": "monorepo-sample",
  "version": "1.0.0",
  "private": true,
  "workspaces": [
    "packages/bye",
    "packages/hello"
  ],
  "devDependencies": {
    "@serverless/typescript": "^2.23.0",
    "@types/aws-lambda": "^8.10.71",
    "@types/node": "^14.14.25",
    "json-schema-to-ts": "^1.5.0",
    "serverless": "^2.23.0",
    "serverless-webpack": "^5.3.5",
    "ts-loader": "^8.0.15",
    "ts-node": "^9.1.1",
    "tsconfig-paths": "^3.9.0",
    "tsconfig-paths-webpack-plugin": "^3.3.0",
    "typescript": "^4.1.3",
    "webpack": "^5.20.2",
    "webpack-node-externals": "^2.5.2"
  }
}
```

ルートにdevDependenciesを記述したので、`bye`および`hello`のpackage.jsonも編集します。

```json:/packages/hello/package.json
{
  "name": "hello",
  "version": "1.0.0",
  "private": true,
  "dependencies": {
    "@middy/core": "^1.5.2",
    "@middy/http-json-body-parser": "^1.5.2",
    "source-map-support": "^0.5.19"
  }
}
```

これで一旦モノレポにはなりました。
ルートディレクトリで`yarn`を実行すると、共通するライブラリがルートに巻き取られることが分かります。

## Step 3. webpack.config の修正

ここまででは、まだデプロイはうまくいきません。
デフォルトのwebpack.config.jsでは親のnode_modulesを解決できないためです。

従ってwebpack-node-externalsに親のnode_modulesを教えてやります。

```js:/packages/hello/webpack.config.js

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

```

これで、各パッケージ内で`sls deploy`の実行に成功するようになります。

## Step 4. 共通パッケージの作成


せっかくモノレポにしたので、重複ソースを共通パッケージとして切り出しましょう。
まずは`packages/my-libs`ディレクトリを作成します。

```
mkdir packages/my-libs
```

テンプレートに含まれる`src/libs`を共通パッケージにしてみます。

```shell
mv packages/hello/src/libs packages/my-libs/src
rm -rf packages/bye/src/libs
```

```json:/packages/my-libs/package.json
{
  "name": "my-libs",
  "version": "1.0.0",
  "private": true,
  "main": "src/index.ts",
  "dependencies": {
    "@middy/core": "^1.5.2",
    "@middy/http-json-body-parser": "^1.5.2"
  }
}
```

```ts:/packages/my-libs/src/index.ts
export * from './apiGateway'
export * from './handlerResolver'
export * from './lambda'
```

:::note warn
上記ではmy-libsのmainをts（src/index.ts）にしています。
今回の構成では、byeやhelloのビルド時に、ts-loaderがmy-libsのtsも併せてビルドしてくれるためです。
通常、他者のパッケージからも呼ばれる場合は、ちゃんとトランスパイルしてからjsをmainに設定しましょう。
:::

これでディレクトリ構成はこんな感じ。

```
package.json
packages/
    my-libs/
        src/
            index.ts
            apiGateway.ts
            handlerResolver.ts
            lambda.ts
        package.json
    bye/
        src/
            functions/
        package.json
        serverless.ts
        webpack.config.js
    hello/
        src/
            functions/
        package.json
        serverless.ts
        webpack.config.js
```

では`my-libs`をworkspacesに追加しましょう。

```json:/package.json
  "workspaces": [
    "packages/my-libs",
    "packages/bye",
    "packages/hello"
  ]
```

続いて`bye`と`hello`から`my-libs`を利用してみます。

```json:/packages/hello/package.json
  "dependencies": {
    "my-libs": "*",
    "source-map-support": "^0.5.19"
  }
```

```ts:/packages/hello/src/functions/handler.ts
import type { ValidatedEventAPIGatewayProxyEvent } from 'my-libs';
import { formatJSONResponse } from 'my-libs';
import { middyfy } from 'my-libs';
```

```ts:/packages/hello/src/functions/index.ts
import { handlerPath } from 'my-libs';
```


最後に、このままではServerlessFrameworkがmy-libsをバンドルする際にエラーになるため、webpack.configに設定を追加します。

```js:/packages/hello/webpack.config.js

  externals: [nodeExternals({
    modulesDir: path.resolve(__dirname, '../../node_modules'),
    allowlist: ['my-libs']
  })],

```

これで、ルートディレクトリで`yarn`を再実行すれば、`sls deploy`が成功するようになります！

以上、当方初学者のため、マサカリ募集中です。

## 参考記事

https://qiita.com/allJokin/items/9316e5384a3bf7a4536b