概要
アプリ公開時に以下の要件を満たす環境が欲しかったため、試行錯誤した際の備忘録。
- GitHubへのプッシュに伴って自動的にアプリをビルドして公開したい。
- Storybookも自動的にビルドして公開したい。
- ドキュメントも自動的に最新化して公開したい。
環境
- ソースコード管理: GitHub
- ホスティングサービス: Netlify
- CIツール: Travis CI
使用ツール
Storybook
UIコンポーネントのカタログを作成するツール。アプリとしてビルドすることも出来る。
TypeDoc
TypeScriptソースコードからドキュメントを自動生成するツール。
公開方法
いずれも本番環境のアプリとは無関係なものであるため、アプリと独立した環境で公開するものとする。
Netlify(ブランチデプロイ)
Storybookもまた動的なアプリであるため、ホスティングサーバー上でビルド&デプロイするべくNetlifyのブランチデプロイを用いる。
GitHub Pages
ドキュメントは静的なHTMLファイル群であるため、予め生成してGitHub Pagesで公開する。
デプロイの流れ
デプロイの大まかな流れは以下の通りである。詳細な解説は、後述の詳解を参照されたい。
リモートへのプッシュからTravis CIまで
[ローカル]
- ソースコードを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
をクリックしてリポジトリを登録する。途中のステップはそれぞれ以下のように進めること。
- Connect to Git provider: GitHubを選択する。
- Pick a repository: リポジトリを選択する。
- 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ブランチ用にビルドとデプロイを個別に設定している。
[build]
publish = "build/"
command = "yarn build"
[build]
publish = "storybook-static/"
command = "yarn build-storybook"
Travis CI
設定ファイル配置
以下のファイルをプロジェクトのルートディレクトリに配置する。
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
に追記し、リモートリポジトリにプッシュされないようにする。
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
に以下の更新を行う。
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
に以下の更新を行う。
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>
));
ドキュメント用の更新
以下のファイルをプロジェクトのルートディレクトリに配置する。
{
"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ブランチにプッシュしている。