こんにちは、フロントエンドエンジニアのやなぎ(@apple_yagi)です。
今年(2022年)の4月頃に、PR TIMESのフロントエンド開発基盤の構築を行い、各プロジェクトのリポジトリに散らばっていたReactで実装しなおした Frontend のコードベースを一つのリポジトリに集約することができました。

しかし、この時点では Frontend のコードベースを一つにまとめただけで、各プロジェクトで共通のコンポーネントやスタイルを一元管理するまでには至っていませんでした。
今回、Yarn Workspacesを利用してMonorepoを構築し、共通コンポーネント・スタイルの共有を行ったのでご紹介します。
本エントリーでは、Yarn Workspacesの機能を使用して、1つのリポジトリで複数のプロジェクトを管理している構成のことをMonorepoと指します。
Monorepoで達成したいこと
現状のフロントエンドのディレクトリ構成は以下のようになっており、appsディレクトリ配下に2つのReactで構築されているWebアプリケーションが存在しています。
.
├── .githooks
├── .github
├── apps
│ ├── app1
│ │ ├── package.json
│ │ ├── src/
│ │ ├── tsconfig.json
│ │ ├── webpack.config.js
│ │ └── yarn.lock
│ └── app2
│ ├── package.json
│ ├── src/
│ ├── tsconfig.json
│ ├── webpack.config.js
│ └── yarn.lock
├── .eslintrc.json
├── .prettierrc.json
├── Makefile
├── package.json
├── README.md
└── yarn.lock
※ 例としてapps配下のディレクトリ名はapp1, app2としています
現状のディレクトリ構造は一見Monorepoのような構成になっていますが、app1とapp2で共通のコンポーネントやスタイルは各アプリケーションごとに別々で作成されています。
PR TIMESでは昨年からデザインシステムを構築し、サービス全体で使用するコンポーネントやスタイルの統一を図っていましたが、それらのコンポーネントやスタイルは各アプリケーションで別々に実装されており、二重管理されていました。

さらに、各アプリケーションで保持しているデザインシステムのコンポーネントは、若干実装がズレており、デザインシステムの運用方法として良くない方向に進んでいました。
そこで、デザインシステムのコンポーネントとスタイルを共通実装にまとめ、各アプリケーションに配布することを目標に、Monorepoの構築に取り組みました。
Yarn Workspacesを選んだ理由
タイトルの通り、今回Monorepoを構築するためにYarn Workspacesを使用しました。理由は以下の通りです。
- 元々パッケージマネージャーにYarnを使用しており導入が簡単だった
- 現状の達成したいことがデザインシステムで定義されているコンポーネント・スタイルの共有であり、Yarn Workspacesでも十分に達成することが可能だった
Monorepoを構築するためのビルドツールとして、NxやTurborepoなどがありますが、今回達成したいことに対してToo Machと思ったため導入を見送りました。
今後、Yarn Workspacesで満たせない要件が起きたとき、またそのときの要件と時代にあったツールを選定したいと思います。
Monorepoの構築
以下のステップで、Yarn Workspacesを利用してMonorepoを構築を行いました。
Step1. ディレクトリ構成の変更
現状のディレクトリ構成では各アプリケーション内にデザインシステムのリソースが埋まってしまっているため、まずは独立したパッケージとしてデザインシステムのリソースを配置するようなディレクトリ構成に変更しました。
変更後のディレクトリ構成は以下の通りです。
.
├── .githooks
├── .github
├── apps
│ ├── app1
│ │ ├── package.json
│ │ ├── src/
│ │ ├── tsconfig.json
│ │ ├── webpack.config.js
│ │ └── yarn.lock
│ └── app2
│ ├── package.json
│ ├── src/
│ ├── tsconfig.json
│ ├── webpack.config.js
│ └── yarn.lock
├── packages
| ├── design-system <-- デザインシステム用のコンポーネント、スタイルをまとめたパッケージ
│ │ ├── package.json
│ │ ├── src/
│ │ | ├── Button.tsx
│ │ | └── styles
│ │ | └── global.css
│ │ ├── tsconfig.json
│ │ └── yarn.lock
| └── tsconfig
| ├── package.json
| └── base.json
├── .eslintrc.json
├── .prettierrc.json
├── Makefile
├── package.json
├── README.md
└── yarn.lock
変更後のディレクトリ構成ではapps配下にアプリケーションのnpmパッケージを、packages配下にデザインシステムのリソースや、各パッケージである程度共通化しておきたいtsconfigの設定を入れるようにしています。
本構成は、vercel/turborepoのexamplesで紹介されている構成を参考にしています。
Step2. 各package.jsonにworkspacesのための設定を追加する
Yarn Workspacesを利用するには、リポジトリのルート直下にある package.json にworkspacesを追加する必要があるので、そちらの設定を行います。
今回のディレクトリ構成では以下のような設定を追加します。
{
"workspaces": [
"apps/*",
"packages/*"
]
}
次にpackages配下のdesign-systemとtsconfigのディレクトリ内にpackage.jsonを作成し、nameとversionを指定します。
{
"name": "@prtimes/design-system",
"version": "1.0.0"
}
{
"name": "@prtimes/tsconfig",
"version": "1.0.0"
}
その後、apps配下にある各アプリケーションのpackage.jsonのdependenciesに@prtimes/design-systemや@prtimes/tsconfigを追加します。
{
"dependencies": {
// versionにワイルドカードを指定し、バージョンを意識せずに管理する
"@prtimes/design-system": "*"
},
"devDependencies": {
"@prtimes/tsconfig": "*"
}
}
これにより、@prtimes/design-systemを参照することでデザインシステムのコンポーネントやスタイルを各アプリケーションで利用することが可能になります。
import { Button } from "@prtimes/design-system";
import "@prtimes/design-system/styles/global.css"
export const App1Button = () => {
return <Button>App1 Button</Button>;
}
また、共通のtsconfigの設定も以下のように利用できます。
{
// ベースとなるtsconfigの設定をextendする
"extends": "@prtimes/tsconfig/base.json",
// 各アプリケーション内のtsconfigでさらに設定を追加することもできる
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["./dist"]
}
まとめ
今回、Monorepo環境を構築したことにより、各アプリケーション上で別々に管理されていたデザインシステムのリソースを一箇所にまとめることができ、二重でデザインシステムを管理する手間や、実装のズレなどを改善することができました。
PR TIMESにReactが導入されてからまだ一年も経過していませんが、今回の取り組みや、数日前に投稿された PR TIMES フロントエンドの React 18 バージョンアップの取り組み のように、PR TIMESのフロントエンドチームでは先手先手で負債の解消に取り組んでいます。
また、アドバイザーとして入って頂いている 1000chさん からアドバイスをもらいながら、今後のPR TIMESのフロントエンドの展望についても活発に議論しています。
今後もさらなる発展を遂げるPR TIMESのフロントエンドに乞うご期待ください。