DELISH KITCHEN RS事業部では、小売向けにサイネージやチラシ等のサービスを提供しています。 従来は、そのサービスの管理が出来るWebアプリのみ運用していたのですが、新たに広告配信設定用のWebアプリが必要になりました。 そこでNxを使って、2つのアプリをmonorepoで管理し、コードの共通化を計りました。
Nxとは
Nxはmonorepo用の拡張可能な開発ツールセットです。堅牢なCLI、キャッシュシステム、依存性管理などを提供すると共に、Jest、Cypress、ESLint、Prettierなどのモダンなライブラリの統合をサポートしています。元GoogleのAngularチームにいたメンバーによって創設されたNrwlが開発しており、Googleは全てのプロジェクトをmonorepoで管理しているという有名な話がありますが(詳細は知りません)、それと似た開発体験を提供することを目的に開発されているそうです。
Nxへの移行
RS事業部で開発しているWebアプリはAngularで作られており、それをまずNxに移行しました。
NxはAngularをサポートしているので、移行自体は簡単でした。 まずNxで新しくworkspaceを作成します。
npx create-nx-workspace --preset=angular
その後、既存のアプリのコードをapps
以下に配置し、angular.jsonやtsconfig.json、tslint.jsonなどの設定ファイルを修正し、既存のアプリで使用していたサードパーティのライブラリ(dayjsなど)を新しいworkspaceに追加して、移行を完了しました。
※ 現在のcreate-nx-workspaceは、テストフレームワークにデフォルトでJestとCypressが選択されており、AngularデフォルトのKarma、Protractorを使用したい場合は、別途以下のコマンドでアプリを作成する必要があります。
nx generate @nrwl/angular:app myapp --unit-test-runner=karma --e2e-test-runner=protractor`
ライブラリの作成
複数のアプリから共通のコードを使用するために、ライブラリを作成します。
nx generate @nrwl/angular:lib shared
上記のコマンドでlibs
配下にshared
ディレクトリが作成されます。
今回は例としてSampleComponent
をライブラリに作成します。
nx generate component sample --project=shared
作成したら、shared.module.ts
のexportsにSampleComponent
を追加します。
次に、アプリからSampleComponent
を使用するために、tsconfigにパスを追加します。
{ ... "compilerOptions": { ... "paths": { "@lib/shared": ["libs/shared/src/index.ts"], "@lib/shared/*": ["libs/shared/src/lib/*"] }, ... }, ... }
あとは使用したいモジュールでimportすると、使用可能になります。
import { SharedModule } from '@lib/shared'; @NgModule({ imports: [SharedModule], bootstrap: [AppComponent] }) export class AppModule {}
また、直接SampleComponent
をimportしたい場合は、以下のコードで可能です。
import { SampleComponent } from '@lib/shared/sample/sample.component.ts'
CSSの共通化
上記で作成したsharedライブラリに共通のCSSも置いて、使えるようにします。
場所はどこでもいいのですが、私はlibs/shared/src/styles
にファイルを配置しています。
html, body { height: 100%; } body { margin: 0; font-family: 'Helvetica Neue', Arial, 'Hiragino Kaku Gothic ProN', 'Hiragino Sans', Meiryo, sans-serif; }
そしたら、angular.jsonの、このスタイルを適用したいアプリの箇所に以下を追加します。
"app": { "projectType": "application", ... "architect": { "build": { ... "options": { ... "styles": [ "libs/shared/src/styles/styles.scss", // この部分 ], ... } } } }
また、partialファイル(_mixin.scssなど)をsharedに置いて参照することも可能です。 これも好きな場所にファイルを配置して、angular.jsonでパスを指定するだけです。
"app": { "projectType": "application", ... "architect": { "build": { ... "options": { ... "stylePreprocessorOptions": { "includePaths": ["libs/shared/src/styles/partials"], // この部分 }, ... } } } }
ここで注意なのが、指定するのはファイルパスではなくディレクトリパスということです。こうしておくことで、partials以下にあるファイル(_mixin.scss)を@import 'mixin'
という形で使うことができます。
CI/CD
monorepoにするとCI/CD周りも変わってきます。おそらく多くの人が、変更があったアプリ、またはライブラリだけをテスト、デプロイしたいと考えると思います。Nxはその希望を叶えてくれます。Nxにはaffected
コマンドがあり、変更の影響があるプロジェクトのみに対してテスト、ビルドを実行する機能があります。
CI/CDはGithub Actionsで行っていて、実際のワークフローを例に紹介したいと思います。Github ActionsはPull Requestを作った時とdevelop
、master
ブランチにマージされたタイミングで走るように設定しています。
例えば、Pull Requestを作った時に走らせるビルドは以下のように設定しています。
name: build on: pull_request jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: fetch-depth: 0 - uses: actions/setup-node@v1 with: node-version: '12.x' - name: Run cache/restore node_modules uses: actions/cache@v1 with: path: node_modules key: v1-${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }} - name: Run build run: make affected-build BASE=${{ github.event.pull_request.base.sha }} // nx affected:build --base=${BASE} を呼んでるだけ
ここで、affected
はbase optionでブランチやコミットIDを指定でき、baseとHEADの差分から、影響のあるプロジェクトを判断します。その仕様から、actions/checkout@v2
ではfetch-depth: 0
を指定することで、コミット履歴を全部取得するようにしています。
またmaster
にマージされた際のデプロイは以下のように設定しています。
name: deploy on: push: branches: - master jobs: build: ... deploy: needs: build runs-on: ubuntu-latest steps: - name: Run cache/restore dist uses: actions/cache@v1 with: path: dist key: v1-${{ runner.os }}-dist-${{ github.run_number }} - name: Run deploy run: | app_paths="dist/apps/*/" if ! ls -d $app_paths &>/dev/null ; then exit 0 fi for app_path in $app_paths; do app=$(basename "$app_path") if [ "$app" == "appName1" ]; then elif [ "$app" == "appName2" ]; then fi done
差分があるもののみデプロイするという仕組みはNx自体にはないので、buildフェーズで生成されたものをデプロイするというスクリプトを書いて対応しています。
現在のデプロイの仕組みだと、master
に入ったものは全部対象になってしまうので、リリース前に全プロジェクトの確認が必要になってしまいます。またmaster
に入ってもリリースのタイミングは調整したいこともあるでしょう。monorepoにして一番の課題で、依然他に良いフローがないか検討中の部分です。
まとめ
今回はNxを使ってnpm projectをmonorepo管理した話をしました。Nxを使ったComponentやCSSの共通化、CI/CDの運用とかは普段聞く機会がないので、誰かの参考になれば幸いです。