実際に業務で行った Amplify v5->v6 の移行作業

# 記事の目的

https://aws.amazon.com/jp/amplify/

aws-amplify の v6 から Next.JS に寄り添った機能を提供するようになり、我が社で使っていた amplify を v5->v6 へアップグレードすることになった。
その際に実際に起きた変更点や docs からは掴みにくい点、かかった時間について述べる

ちなみに変更ファイル数 61, 変更行数 約30k行, 作業時間は 25時間 ほどであった.
変更行の内訳

- `package-lock.json` 19k 行 (63%) 
- `amplify が自動生成する graphQL の query や 型定義` 9.7k 行 (32%)
- 残り, 手動による書き換え 1k 行 (3%)

https://docs.amplify.aws/gen1/javascript/build-a-backend/troubleshooting/migrate-from-javascript-v5-to-v6/

# package.json

一部抜粋
変更の前後で `package.json` の `dependencies` がどう変わったかを載せる

```diff: package.json dependencies
- "@aws-amplify/api": "^5.1.0",
- "@aws-amplify/api-graphql": "^3.2.0",
+ "@aws-amplify/api-graphql": "^4.1.4",
- "@aws-amplify/storage": "^4.5.17",
- "@aws-amplify/ui": "^5.6.1",
- "@aws-amplify/ui-react": "^4.6.1",
+ "@aws-amplify/adapter-nextjs": "^1.2.3",
+ "@aws-amplify/ui-react": "^6.1.12",
+ "@aws-crypto/sha256-js": "^5.2.0",
- "aws-amplify": "^5.2.1",
+ "aws-amplify": "^6.3.4",
- "next": "13.4.2",
+ "next": "^14.2.3",
```

**:point_up: point**

@aws-amplify/api, @aws-amplify/storage といったように、 `aws-amplify` package に付随した package を一部インストール不要になり、 `aws-amplify` 内に組み込まれるようになった. ただし一部残ったものもある. プロジェクトによっては不要かもしれない
1. `@aws-amplify/api-graphql`
    1. このパッケージから排出される `GraphQLOptionsV6`, `GraphQLResult` が必要だっためインストール
1. `@aws-amplify/adapter-nextjs`
    1. Next のサーバーサイドでの処理を遂行するための機能を提供したもの
    1. request や cookies をもとに認証をサーバーで行なうための機能がメイン
1. `@aws-amplify/ui-react`
    1. front で認証結果を React.Provider を活用して早期アクセスを可能にするもの( `<Authenticator.Provider>` )。自前で Provider を書いたりしたら別に要らない
    1. amplify/ui を使っていたらいるのかも？ [amplify/ui](https://ui.docs.amplify.aws/) は使っていないので詳しくない
1. `@aws-crypto/sha256-js`
    1. `@aws-amplify/ui-react` の内部で使っているらしく、明示的に install しないとエラーになった

# migration 作業内容

migration に費やした時間は主に以下であった

1. v5 で提供された package を v6 のものへ変更していくコードの変更
1. 元のままでは動かなくなった jest の設定の修正
1. SSR コードの書き換え

## コードの変更

基本的には下記の migration guide に従って書き換えていくだけで大抵は対応できる.

1. [auth](https://docs.amplify.aws/gen1/javascript/build-a-backend/auth/auth-migration-guide/)
1. [storage](https://docs.amplify.aws/gen1/javascript/build-a-backend/storage/storage-v5-to-v6-migration-guide/)
1. [rest API](https://docs.amplify.aws/gen1/javascript/build-a-backend/storage/storage-v5-to-v6-migration-guide/)

graphQL API はなぜか上記と同じように migration guide を用意してくれてなかったので、このセクションでは graphQL の migration を一部紹介と、上記の docs からはわかりにくかった変更内容を紹介する

### graphQL の migration

公式の docs はないが、他の package の migration を踏んだら、 graphQL の migration 方法もなんとなく見当がつくので、そこまで困らないと思う.

**API call 方法**

v5
```ts: callAppSync.ts
import {API} from 'aws-amplify';
import * as mutations from './graphql/mutations';

const todoDetails = {
  name: 'Todo 1',
  description: 'Learn AWS AppSync'
};

API.graphql({
    query: mutations.createTodo,
    variables: { input: todoDetails }
})
```

v6
```ts
import { generateClient } from 'aws-amplify/api';
import * as mutations from './graphql/mutations';

const client = generateClient();

const todoDetails = {
  name: 'Todo 1',
  description: 'Learn AWS AppSync'
};

const newTodo = await client.graphql({
  query: mutations.createTodo,
  variables: { input: todoDetails }
});
```

client 生成時 (`generateClient` ) にデフォルトとなる認証方式を設定できたりして、 API client の作成がちょっと楽になった.

### その他わかりにくかった点

**apiURLの取得方法**
amplify の環境を pull したときにできる config を用いて backend リソースへアクセスするための URL の取得方法. どこにも変更書いてないと思う

v5
```ts
import {API} from 'aws-amplify';
const apiUrl = await API.endpoint('someApi')
```

v6
```ts
import {Amplify} from 'aws-amplify';
 const apiUrl = Amplify.getConfig().API?.REST?.someApi.endpoint;
```

**session token の取得方法**
これはどこかに書いてたかも

v5
```ts
import {API, Auth} from 'aws-amplify';

const token = await Auth.currentSession()).getIdToken().getJwtToken()
const auth = `Bearer ${token}`
```

v6
```ts
import {fetchAuthSession} from 'aws-amplify/auth';

const token = await fetchAuthSession().tokens?.idToken?.toString()
const auth = `Bearer ${token}`
```

## jest.config の変更

我々の config の設定がまずかったのか、既存の以下の jest の設定のままでは、エラーが出るようになった

```js: jest.config.js
const nextJest = require('next/jest');

const createJestConfig = nextJest({
  dir: './',
});

/** @type {import('jest').Config} */
const customJestConfig = {
  testEnvironment: 'jest-environment-jsdom',
  globals: {
    'ts-jest': {
      useESM: true,
    },
  },
  preset: 'ts-jest/presets/default-esm',
  transform: {},
  extensionsToTreatAsEsm: ['.tsx', '.ts'],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
    uuid: require.resolve('uuid'),
  },
};

module.exports = createJestConfig(customJestConfig);
```

:point_down: error message
```bash
  ● Test suite failed to run
    Jest encountered an unexpected token
    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.
    Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.
    By default "node_modules" folder is ignored by transformers.
    Here's what you can do:
     • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
     • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/configuration
    For information about custom transformations, see:
    https://jestjs.io/docs/code-transformation
    Details:
    /builds/strategitjp/joint/basis/ui/hosting/node_modules/rxjs/dist/esm5/index.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){export { Observable } from './internal/Observable';
                                                                                      ^^^^^^
    SyntaxError: Unexpected token 'export'
      at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1505:14)
```

`ts-jest` の設定を見直したりしてみたりしたが変わらず、[これ](https://hepokon365.hatenablog.com/entry/2022/06/15/195652)を参考に `transformIgnorePatterns` を変えてみたが変わらずエラー。
初心に戻って [Next　が公式で提供している jest の設定](https://nextjs.org/docs/app/building-your-application/testing/jest)にしてみたら通過。

`preset` がよくなかったのかな・・・？
jest の config は CJS, ESM の歴史も把握しとく必要があってなかなか複雑

::: note
行き詰まったら公式に倣うこと
:::

## SSR の設定

以下が公式の migration
素晴らしいほどに Next の SSR がしやすくなっている.

https://docs.amplify.aws/gen1/nextjs/build-a-backend/server-side-rendering/nextjs-v5-to-v6-migration-guide/#step-1-install-nextjs-adapter

request, response instance を `withSSRContext` に渡してサーバー処理を行うのが v5 だったが、 cookie だけでその処理を実現できるようになっており、 server component をかけるようになった。

ただし、なぜか我々のコードでは以下のエラーが出ていた

```bash
No current user
underlyingError: Error: Network error
```

が、原因は msw の設定がまずかったらしい。
msw はその時点で使っていなかったので、削除することで無事 SSR できるようになった.

https://mswjs.io/docs/getting-started/

# まとめ

変更点はそれなりにあるが、総じてみると、「変更の仕方を知ってれば全然大したことない変更量だな」と感じた。
プロジェクトの依存パッケージのアップグレードは課題として挙げられがちだが、今回の amplify のアップグレードはかなり楽な方であったであろう。(何より詰まったら AWS がサポートしてくれるので :sweat: )

さて、我々にはまだ mantine の v6 -> v7 作業が残っている... :angel: 

https://mantine.dev/guides/6x-to-7x/