あいつの日誌β

働きながら旅しています。

npm publish を念頭において React Components 用のプロジェクトを作成する

あらすじ

先日 react-paginators という React components な module を publish しました。

github.com

その時に色々調べた事があるので備忘録として残します。

作成手順

create-react-appstorybook を使ってプロジェクトの雛形を作成します。create-react-app を使うと webpack の事を考えなくて良いのでとても楽でよいです。

create-react-app react-xxxxx && cd $_
getstorybook

必要なディレクトリとファイルを作成します

mkdir test
touch .envrc .babelrc .npmignore .travis.yml CHANGELOG.md test/xxxxx.test.js 
npm install --saave react react-dom
npm install --save-dev babel babel-cli babel-preset-es2015 babel-preset-react babel-preset-stage-0
npm install --save-dev assert eslint eslint-plugin-react rimraf
npm install --save-dev in-publish safe-publish-latest

create CHANGELOG.md

# Change Log

## v0.1.0
 - Initial commit

そういえば $_ って何ですか?と聞かれる事があったのですがこれは前回使用したコマンドの最後の引数が格納される変数です。 上記のコマンドだと create-react-app react-xxxxxreact-xxxxx となります。

npm run test の環境を整える

雛形ができたら npm test の環境を整えます

create test/xxxxx.test.js

import assert from 'assert'

describe('XXX', function() {
  it('should ...', () => {
    assert.ok(true)
  })  
})

動作確認します。雛形として生成された src/App.test.js と test/xxxxx.test.js が実行されるのを確認します。 ちなみに環境変数 CI=true を忘れると launch mode で起動します

CI=true npm test

> react-xxxxx@0.1.0 test /Users/okamuuu/react-xxxxx
> react-scripts test --env=jsdom

 PASS  src/App.test.js
 PASS  test/xxxxx.test.js

Test Suites: 2 passed, 2 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        1.167s
Ran all test suites.

src/App.test.js は不要となった時点で適宜 remove してください

npm run version の環境を整える

check-changelog と check-only-changelog-changed

airbnb が公開している react-dates の真似をします。

https://github.com/airbnb/react-dates

github.com

彼らは version コマンドを実行する時は CHANGELOG.md 以外の差分が無い状態でしか実行できないようにしています。 npm-scripts に check-changelogcheck-only-changelog-changed を追加します。

edit package.json:

--- a/package.json
+++ b/package.json
     "start": "react-scripts start",
     "build": "react-scripts build",
     "test": "react-scripts test --env=jsdom",
-    "eject": "react-scripts eject"
+    "eject": "react-scripts eject",
+    "check-changelog": "expr $(git status --porcelain 2>/dev/null| grep \"^\\s*M.*CHANGELOG.md\" | wc -l) >/dev/null || (echo 'Please edit CHANGELOG.md' && exit 1)",
+    "check-only-changelog-changed": "(expr $(git status --porcelain 2>/dev/null| grep -v \"CHANGELOG.md\" | wc -l) >/dev/null && echo 'Only CHANGELOG.md may have uncommitted changes' && exit 1) || exit 0"
   }
 }

以下のような使い方を想定しいています。

CI=true npm run test && npm run check-changelog && npm run check-only-changelog-changed

上記のコマンドは以下の状態でないとエラーが発生するので version up の際に一貫性が生まれます。

git status
  modified:   CHANGELOG.md

postversion, version, preversion を準備

npm version をそのまま使うと Git working directory not clean. と怒られてしまうので --no-git-tag-version オプションをつけて実行します。 package.jsonnpm run version:patch, npm run version:minor, npm run version:major を用意します。

preversion に先ほど準備した test の実行と CHNAGELOG.md のチェックを行います。 postversion で package.json(verson が変更されている) と CHANGELOG.md をcommit します。コミットも自動的に行います。git tag でタグを切り、その tag も push します。

edit package.json

--- a/package.json
+++ b/package.json
     "test": "react-scripts test --env=jsdom",
     "eject": "react-scripts eject",
     "check-changelog": "expr $(git status --porcelain 2>/dev/null| grep \"^\\s*M.*CHANGELOG.md\" | wc -l) >/dev/null || (echo 'Please edit CHANGELOG.md' && exit 1)",
-    "check-only-changelog-changed": "(expr $(git status --porcelain 2>/dev/null| grep -v \"CHANGELOG.md\" | wc -l) >/dev/null && echo 'Only CHANGELOG.md may have uncommitted changes' && exit 1) || exit 0"
+    "check-only-changelog-changed": "(expr $(git status --porcelain 2>/dev/null| grep -v \"CHANGELOG.md\" | wc -l) >/dev/null && echo 'Only CHANGELOG.md may have uncommitted changes' && exit 1) || exit 0",
+    "tag": "git tag v$npm_package_version",
+    "version:patch": "npm --no-git-tag-version version patch",
+    "version:minor": "npm --no-git-tag-version version minor",
+    "version:major": "npm --no-git-tag-version version major",
+    "preversion": "CI=true npm run test && npm run check-changelog && npm run check-only-changelog-changed",
+    "postversion": "git commit package.json CHANGELOG.md -m \"Version $npm_package_version\" && npm run tag && git push && git push --tags"
   }
 }

後ほど postversion の最後に npm publish を追加しますす

gh-pages へのデプロイを準備する

react の component を公開するので実際に見た目がどのように表示されるのかを皆さんに伝える必要があります。 gh-pages に static page をデプロイする方法は色々ありますが、@kadira/storybook-deployer を install するだけで実現できます。

npm install --save-dev @kadira/storybook-deployer

edit package.json

     "version:major": "npm --no-git-tag-version version major",
     "preversion": "CI=true npm run test && npm run check-changelog && npm run check-only-changelog-changed",
-     "postversion": "git commit package.json CHANGELOG.md -m \"Version $npm_package_version\" && npm run tag"
+    "postversion": "git commit package.json CHANGELOG.md -m \"Version $npm_package_version\" && npm run tag",
+    "deploy-storybook": "storybook-to-ghpages"
  }
}

npm run deploy-storybook を実行して gh-pages を確認します。

npm run build

src/ 以下に ES6 で書かれた Javascript を lib/ 以下に ES5 に変換したものを配置します。 npm publish しないのであれば不要な作業ですが、現時点では node_modules/ 配下に install するモジュールは es5 が前提だと思いますのでその習慣に従います。

babel コマンドを実行します。もしかしたら react-scripts が bundle しない 6to5 なコマンドを用意しているのかもしれないのですがよくわからないので自前で babelrc を用意します。

create .babelrc

{
  "presets": ["react", "es2015", "stage-0"]
}

以下を実行し、lib 以下に es5 のコードが生成されることを確認します。

$(npm bin)/babel -d lib/ src/

lib 配下は git で管理する必要が無いので .gitignore に追加します

echo "/lib/" >> .gitignore

また lib 配下は npm publish で使用されるので build 直前で clearn にするようにしておきます。 そして main, files も同様に npm publish される前提の修正を行います。

edit package.json

--- a/package.json
+++ b/package.json
-  "private": true,
+  "license": "MIT",
+  "main": "lib/index.js",
+  "files": "lib",
+  "author": "okamuuu<okamuuu@gmail.com>",
   "devDependencies": {
     "@kadira/storybook": "^2.21.0",
     "@kadira/storybook-deployer": "^1.2.0",
   },
   ...
   "scripts": {
     "start": "react-scripts start",
-    "build": "react-scripts build",
+    "build": "rimraf lib && babel -d lib/ src/",
     "test": "react-scripts test --env=jsdom",
     "eject": "react-scripts eject",
     "check-changelog": "expr $(git status --porcelain 2>/dev/null| grep \"^\\s*M.*CHANGELOG.md\" | wc -l) >/dev/null || (echo 'Please edit CHANGELOG.md' && exit 1)",

npm run publish

最後に publish の設定を行います。

postversion のタイミングで npm publish が実行されるようにします。 npm prepublish は、npm install 時に実行してしまいます。npm run buildnpm install の時に実行しないようにします。 それから npm publish が成功した場合は gh-pages の更新を行うようにします

-    "postversion": "git commit package.json CHANGELOG.md -m \"Version $npm_package_version\" && npm run tag",
+    "postversion": "git commit package.json CHANGELOG.md -m \"Version $npm_package_version\" && npm run tag && git push && git push --tags && npm publish --registry=https://registry.npmjs.org/",
...
+    "build-storybook": "build-storybook -s public",
+    "prepublish": "in-publish && safe-publish-latest && npm run build || not-in-publish",
+    "postpublish": "npm run deploy-storybook"
   }

package されるファイルを確認する

npm publish --dryrun のようなコマンドを実行したいのですが現時点では存在しないようです:(

代わりに npm pack を利用します。 http://qiita.com/inuscript/items/5b3c1466a6ddb9ba6231

実際に package されるファイルを確認するコマンドは下記のとおりです。 npm pack -s はこの記事の通りに設定していると react-xxxxx-0.1.X.tgz という標準出力を一行だけ吐き出します。 npm buildprebuild, postbuild の設定にもよると思うのでもし同じ状況にならなかったら各自修正をお願いします。

tar -tf $(npm pack -s) && $(npm bin)/rimraf $_

一連の作業をコマンド化します。npm run build で lib 配下を最新版にし、$(npm pack -s) で packing とファイル名を取得し、それに含まれるファイル一覧を取得します。ついでに不要となった tgz ファイルも削除しましょう。 再び $_ が登場しますが、これは最後に実行したコマンドが tar -tf ARG なので tgz ファイルを指しています。

+    "build:test": "npm run build >/dev/null && tar -tf $(npm pack -s) && rimraf $_",

このコマンドで視認しながら .npmignore に不要なファイルを追加していきます。

.babelrc
.envrc
.storybook
.travis.yml
stories
src
test
public

期待しているファイルが publish されることを確認します。

% npm run build:test

> react-xxxxx@0.1.3 build:test /Users/okamuuu/react-xxxxx
> tar -tf $(npm pack -s) && rimraf $_

package/package.json
package/.npmignore
package/README.md
package/lib/App.js
package/lib/App.test.js
package/lib/index.js
package/CHANGELOG.md

.travis.yml

手元の環境では動くのだけどもデプロイすると動かなかったりしますので CI 環境を用意します。 ここでは travis.yml を使っていますが CircleCI でも Wercker でも OK だと思います。

注意点として create-react-app で作成したプロジェクトの test は環境変数に CI=true をセットしていない場合は launch します。

create .travis.yml

language: node_js
node_js:
  - "6"
  - "4"
env:
  CI: true
sudo: false

npm publish

最後に npm publish を実行します。無事成功したら最後に package.json を以下の通りに修正します。

- "postversion": "git commit package.json CHANGELOG.md -m \"Version $npm_package_version\" && npm run tag && gitpush && git push --tags",
+ "postversion": "git commit package.json CHANGELOG.md -m \"Version $npm_package_version\" && npm run tag && gitpush && git push --tags && npm publish",

おしまい