Edited at

アプリ公開に伴ってStorybookとドキュメントを自動で最新化+デプロイする


概要

アプリ公開時に以下の要件を満たす環境が欲しかったため、試行錯誤した際の備忘録。


  • GitHubへのプッシュに伴って自動的にアプリをビルドして公開したい。

  • Storybookも自動的にビルドして公開したい。

  • ドキュメントも自動的に最新化して公開したい。


環境


  • ソースコード管理: GitHub

  • ホスティングサービス: Netlify

  • CIツール: Travis CI


使用ツール

Storybook

UIコンポーネントのカタログを作成するツール。アプリとしてビルドすることも出来る。

TypeDoc

TypeScriptソースコードからドキュメントを自動生成するツール。


公開方法

いずれも本番環境のアプリとは無関係なものであるため、アプリと独立した環境で公開するものとする。


Netlify(ブランチデプロイ)

Storybookもまた動的なアプリであるため、ホスティングサーバー上でビルド&デプロイするべくNetlifyのブランチデプロイを用いる。


GitHub Pages

ドキュメントは静的なHTMLファイル群であるため、予め生成してGitHub Pagesで公開する。


デプロイの流れ

デプロイの大まかな流れは以下の通りである。詳細な解説は、後述の詳解を参照されたい。


リモートへのプッシュからTravis CIまで

[ローカル]

1. ソースコードをGitHubへプッシュする。

[Travis CI]

2. masterブランチを基準にstorybookブランチを作成する。

3. Netlifyの設定ファイルをStorybook用のものに置き換える。

4. リモートリポジトリのstorybookブランチにプッシュする。

5. masterブランチを基準にgh-pagesブランチを作成する。

6. ドキュメントをTypeDocで自動生成する。

7. /docs 内のデータのみをリモートリポジトリのgh-pagesブランチにプッシュする。


CI後からサイト公開まで

前項の処理が完了したのち、Storybookとドキュメントは以下の処理を経て公開される。

Storybook

Travis CIがstorybookブランチに対して実行したプッシュを検知してビルドが実行される。Netlifyの設定ファイルがmasterと異なるため、このビルドではStorybookのビルドが実行され、出力データの格納先である storybook-static がサイトとして公開される。

ドキュメント

gh-pagesブランチにプッシュされたデータは即座にGitHub Pagesとして処理を経て公開される。


手順


プロジェクト作成

プロジェクトを作成してGitHubへプッシュする。

$ create-react-app deploy-app-storybook-docs --typescript

$ cd deploy-app-storybook-docs
$ git remote add origin git@github.com:hogehoge/deploy-app-storybook-docs.git
$ git push -u origin master


サービス連携


Netlify


リポジトリ登録

Netlifyのダッシュボードへアクセスし、New site from Git をクリックしてリポジトリを登録する。途中のステップはそれぞれ以下のように進めること。


  1. Connect to Git provider: GitHubを選択する。

  2. Pick a repository: リポジトリを選択する。

  3. Build options, and deploy!: デフォルトのまま `Deploy site` をクリックする。


ブランチデプロイの設定

master以外にstorybookブランチもデプロイするよう、設定を追加する。Setting → Build & deploy → Deploy contexts → Branch deploysへと進んで Let me add individual branches を選択し、storybook と入力して Save をクリックする。


Travisと連携

travis-ci.comのアカウント設定ページへアクセスし、Manage repositories on GitHub をクリックしてリポジトリを登録する。


ローカル環境構築


ツールのインストール

以下のコマンドにて、StorybookとTypeDocをインストールする。

$ sb init

$ yarn add -D typedoc


各種設定


Netlify

以下のファイルをプロジェクトのルートディレクトリに配置する。それぞれmaster、storybookブランチ用にビルドとデプロイを個別に設定している。


netlify.toml

[build]

publish = "build/"
command = "yarn build"


netlify.storybook.toml

[build]

publish = "storybook-static/"
command = "yarn build-storybook"


Travis CI


設定ファイル配置

以下のファイルをプロジェクトのルートディレクトリに配置する。


.travis.yml

language: node_js

node_js:
- 11.1.0
before_install:
script:
- yarn test
after_success:
# Prepare for GitHub
- openssl aes-256-cbc -K $encrypted_************_key -iv $encrypted_************_iv -in ./id_rsa.enc -out ~/.ssh/id_rsa -d
- chmod 600 ~/.ssh/id_rsa
- echo -e "Host github.com\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config
- git config --global user.email "hogehoge@fugafuga.com"
- git config --global user.name "hogehoge"
- git config --global url."git@github.com:".insteadOf "https://github.com/"
# Build Storybook
- git checkout -b storybook master
- mv netlify.storybook.toml netlify.toml
- git add .
- git commit -m "$TRAVIS_COMMIT_MESSAGE"
- git push -u origin storybook --force
# Generate docs by TypeDoc
- git checkout -b gh-pages master
- git subtree add --prefix docs origin gh-pages
- rm -rf docs
- node node_modules/.bin/typedoc --out docs src
- touch docs/.nojekyll
- git add docs --force
- git commit -m "$TRAVIS_COMMIT_MESSAGE"
- git subtree push --prefix docs origin gh-pages

branches:
only:
- master



SSHキー発行

Travis CIからGitHubへプッシュを行うため、SSHキーの設定を行う。以下のコマンドにて、GitHubにSSH接続する際に用いている ~/.ssh/id_rsa を暗号化する。

$ travis login --pro

$ travis encrypt-file --repo hogehoge/deploy-app-storybook-docs ~/.ssh/id_rsa ./id_rsa.enc --pro
encrypting /Users/hogehoge/.ssh/id_rsa for hogehoge/deploy-app-storybook-docs
storing result as ./id_rsa.enc
storing secure env variables for decryption

Please add the following to your build script (before_install stage in your .travis.yml, for instance):

openssl aes-256-cbc -K $encrypted_************_key -iv $encrypted_************_iv -in ./id_rsa.enc -out ~\/.ssh/id_rsa -d

Pro Tip: You can add it automatically by running with --add.

Make sure to add ./id_rsa.enc to the git repository.
Make sure not to add /Users/hogehoge/.ssh/id_rsa to the git repository.
Commit all changes to your .travis.yml.

出力されたコマンド openssl.travis.yml に記述されているものと置き換える。

🚨このコマンドには不要なバックスラッシュが含まれているため、削除して用いること。

バックスラッシュの削除

-out ~\/.ssh/id_rsa -d

      ↓

-out ~/.ssh/id_rsa -d


その他


.gitignore

node_modulesのような動的に生成されるディレクトリやファイルはリポジトリから除外することが望ましい。StorybookとTypeDocが生成するデータの出力先を.gitignore に追記し、リモートリポジトリにプッシュされないようにする。


.gitignore

npm-debug.log*

yarn-debug.log*
yarn-error.log*

+ /storybook-static
+ /docs


動作確認


設定後の初回プッシュ

設定完了後に全ての変更ファイルをステージングしてコミットし、リモートリポジトリへプッシュする。

$ git add .

$ git commit -m "Integrated with Storybook and TypeDoc"
$ git push


検証

プッシュ完了後、下記の項目が期待通り満たされているかを検証する。


  • GitHub上でmaster、storybook、gh-pagesブランチが存在すること。また、いずれもコミットが反映されていること。

  • masterブランチがビルドされて https://hogehoge.netlify.com/ にデプロイされていること。

  • storybookブランチがビルドされて https://storybook--hogehoge.netlify.com/ にデプロイされていること。

  • gh-pagesブランチが https://hogehoge.github.io/deploy-app-storybook-docs/ にデプロイされていること。


更新をプッシュ

各種ファイルを更新してプッシュする。

アプリ用の更新

src/App.tsx に以下の更新を行う。


src/App.tsx

class App extends Component {

render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
- Edit <code>src/App.tsx</code> and save to reload.
+ Storybook, docs and I are every time up to date!
</p>

Storybook用の更新

src/stories/index.js に以下の更新を行う。


src/stories/index.js

storiesOf('Button', module)

.add('with text', () => <Button onClick={action('clicked')}>Hello Button</Button>)
+ .add('avec text', () => <Button onClick={action('clicked')}>Bonjour Bouton</Button>)
.add('with some emoji', () => (
<Button onClick={action('clicked')}>
<span role="img" aria-label="so cool">
😀 😎 👍 💯
</span>
</Button>
));

ドキュメント用の更新

以下のファイルをプロジェクトのルートディレクトリに配置する。


typedoc.json

{

"exclude": [
"src/index.tsx",
"src/serviceWorker.ts",
"src/**/*.test.tsx"
]
}


検証

プッシュ完了後、下記の項目が期待通り満たされているかを検証する。


  • GitHub上のmaster、storybook、gh-pagesブランチの全てに更新が反映されていること。


  • https://hogehoge.netlify.com/ にアプリの更新が反映されてデプロイされていること。


  • https://storybook--hogehoge.netlify.com/ にstorybookの更新がデプロイされていること。


  • https://hogehoge.github.io/deploy-app-storybook-docs/ にドキュメントの更新がデプロイされていること。


詳解


.travis.yml


GitHubとの通信設定

 9: - openssl aes-256-cbc -K $encrypted_************_key -iv $encrypted_************_iv -in ./id_rsa.enc -out ~/.ssh/id_rsa -d

10: - chmod 600 ~/.ssh/id_rsa
11: - echo -e "Host github.com\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config
12: - git config --global user.email "hogehoge@fugafuga.com"
13: - git config --global user.name "hogehoge"
14: - git config --global url."git@github.com:".insteadOf "https://github.com/"

9行目

暗号化された ./id_rsa.enc~/.ssh/id_rsa に復号している。

11行目

~/.ssh/known_hosts に記述のないホストへアクセスする際のプロンプトを抑制している。これを忘れると Are you sure you want to continue connecting (yes/no)? とプロンプトが表示されて処理が停止してタイムアウトしてしまう。

12〜13行目

プッシュを行うアカウントを登録している。

14行目

Travis CIはデフォルトでGitHubとの通信をhttpsで行っているため、公開鍵を用いるSSHとは認証方法が異なる。自分は普段SSHを使っていてhttpsの扱いに不慣れであったため、リモートリポジトリのURLをSSH用のgit@github.com:に変更している。


Storybook

16: - git checkout -b storybook master

17: - mv netlify.storybook.toml netlify.toml
18: - git add .
19: - git commit -m "$TRAVIS_COMMIT_MESSAGE"
20: - git push -u origin storybook --force

16行目

masterブランチを基準にstorybookブランチを新規作成している。

17行目

netlify.tomlをStorybook用の設定ファイルで置き換えている。

19行目

ローカル環境からプッシュした時のコミットメッセージを流用してコミットしている。

20行目

リモートリポジトリのstorybookブランチにプッシュしている。Travis CI上のstorybookブランチはリモートリポジトリ上のそれとヒストリが繋がっていないため、--force オプションを付与して強制的にプッシュしている。


ドキュメント

22: - git checkout -b gh-pages master

23: - git subtree add --prefix docs origin gh-pages
24: - rm -rf docs
25: - node node_modules/.bin/typedoc --out docs src
26: - touch docs/.nojekyll
27: - git add docs --force
28: - git commit -m "$TRAVIS_COMMIT_MESSAGE"
29: - git subtree push --prefix docs origin gh-pages

22行目

masterブランチを基準にgh-pagesブランチを新規作成している。

23行目

origin/gh-pagesブランチをサブツリーとして追加し、/docs と同期している。

24行目

TypeDocは出力先ディレクトリを都度クリアしてドキュメントを生成するが、TypeDocが生成し得ないファイルが存在するとエラーを発生させる。これを避けるため、出力先ディレクトリを削除している。

25行目

TypeDocにてドキュメントを自動生成している。

26行目

GitHub Pagesはjekyllの処理を経て公開されるが、TypeDocが出力する多くのファイルに見られるようなアンダースコアから始まるファイルは除外される。これは避けるため、gh-pagesブランチのルートディレクトリとなる場所に .nojekyll を配置し、jekyllの処理を通さず公開している。

27行目

ローカル環境からプッシュした時のコミットメッセージを流用してコミットしている。

27行目

/docs.gitignore に記述されているため、--force オプションを付与して強制的にステージングしている。

29行目

/docs をサブツリーとしてorigin/gh-pagesブランチにプッシュしている。