Reactコンポーネントをnpmで公開する（GitHub Pages付き、Babel7、webpack4）

## なにこれ

[TIS Advent Calendar 2018](https://qiita.com/advent-calendar/2018/tis)の13日目の記事です。よろしくお願いします！

最近Reactコンポーネントをnpm公開してみました（参考記事：[CSSのclip-pathでSlit Animationを実現する](https://takumon.com/2018/12/02/)）。そこで今回は**簡単なReactコンポーネントを作って、npm公開する方法を紹介します。**
「React始めたんだけど...npmアカウント作ったんだけど...」という方でも30分くらいで公開できるので、とりあえず手を動かしたい人向けのチュートリアルです。
下記のような手順でnpm公開するまでの方法を見ていきましょう。

1. [コンポーネントを作成する](#1-コンポーネントを作成する)
2. [デモページを作成する](#2-デモページを作成する)
3. [デモページをGitHub Pagesで公開する](#3-デモページをgithub-pagesで公開する)
4. [コンポーネントをnpmに公開する](#4-コンポーネントをnpmに公開する)


※ 完成品はGitHubに公開しています => [Takumon/react-component-sample](https://github.com/Takumon/react-component-sample/tree/step4)
※ 完成品は下記のようなプロジェクト構成になります。

```
プロジェクトルート
├─dist              ・・・ コンポーネントビルド資産出力場所
├─src               ・・・ コンポーネント資産
│   ├─index.js
│   └─styles.css
├─examples          ・・・ デモページ用資産
│  ├─dist           ・・・ デモページビルド資産出力場所
│  └─src
│     ├─index.html
│     └─index.js
├─node_modules
├─.babelrc          ・・・トランスコンパイル用設定ファイル
├─webpack.config.js ・・・ビルド用設定ファイル
├─package.json      ・・・依存ライブラリ・スクリプト定義ファイル
├─.npmignore        ・・・npm登録除外対処定義ファイル
└─.gitignore
```


## 1. コンポーネントを作成する

まずはコンポーネントを作ってトランスパイルするところまで完成させましょう。

<small>※ 本手順完了時のソースコードは**[こちら](https://github.com/Takumon/react-component-sample/tree/step1)**</small>
<small>※ 本手順完了時のプロジェクト構成 ↓</small>

```diff
プロジェクトルート
+ ├─dist          ・・・ コンポーネントビルド資産出力場所
+ ├─src           ・・・ コンポーネント資産
+ │   ├─index.js
+ │   └─styles.css
+ ├─node_modules
+ ├─.babelrc      ・・・トランスコンパイル用設定ファイル
+ └─package.json  ・・・依存ライブラリ・スクリプト定義ファイル（追記）
```



* 最初にプロジェクトの雛形を作ります。`npm init`で色々聞かれますが全てデフォルトで構いません。

```bash
mkdir react-component-sample
cd react-component-sample
npm init
```


* 最低限のReact系ライブラリをインストールします。開発用ライブラリとしてインストールするので`-D`オプションを付けてください。

```bash
npm i -D react react-dom
```


* BabelでReactをトランスコンパイルするためのライブラリをインストールします。こちらも開発用ライブラリなので`-D`オプションを付けましょう。

```
npm i -D @babel/cli @babel/cli @babel/core @babel/preset-env @babel/preset-react babel-loader
```


* `.babelrc`を作成し、Reactをトランスコンパイルするための定義を記載します。

```javascript:.babelrc
{
    "presets": [
        "@babel/preset-env",
        "@babel/react"
    ]
}
```


* コンポーネントを作ります。

```jsx:src/index.js
import React from 'react';
import './styles.css';
const MyComponent = () => (
    <h1>Hello from My Component</h1>
);
export default MyComponent;
```


* コンポーネントで読み込むCSSを作ります。

```css:src/styles.css
h1 {
    color: red;
}
```


* トランスパイル用スクリプトをpackage.jsonに追加します。具体的にはJSファイルをトランスパイルしdistフォルダに出力、それ以外のファイル(CSS)をdistファイルにコピーするスクリプトです。

```diff:package.json
    "scripts": {
+     "transpile": "babel src -d dist --copy-files"
    },
```

### 確認

* 準備が整ったので、トランスパイルしてみましょう。

```bash
npm run transpile
```


* 下記のようにdistフォルダ配下にindex.jsとstyles.cssが生成されればトランスパイル成功です。

```:トランスパイル後の資産
プロジェクトルート
├─dist           ・・・ コンポーネントビルド資産出力場所
│   ├─index.js
│   └─styles.css
```



## 2. デモページを作成する

実際にコンポーネントを使用したデモページもあわせて用意しておきましょう。コンポーネントの使い方をユーザーにわかりやすく示すことができます。
ここではローカルでデモページが見れるところまでを作成します。

<small>※ 本手順完了時のソースコードは**[こちら](https://github.com/Takumon/react-component-sample/tree/step2)**</small>
<small>※ 本手順完了時のプロジェクト構成 ↓</small>

```diff
プロジェクトルート
  ├─dist
  ├─src
  │   ├─index.js
  │   └─styles.css
  ├─node_modules
  ├─.babelrc
+ ├─examples          ・・・ デモページ用資産
+ │  ├─dist           ・・・ デモページビルド資産出力場所
+ │  └─src
+ │     ├─index.html
+ │     └─index.js
+ ├─webpack.config.js ・・・ビルド用設定ファイル
+ └─package.json      ・・・依存ライブラリ・スクリプト定義ファイル
```



* デモページはwebpackでビルドするので必要なライブラリをインストールします。

```
npm i -D html-webpack-plugin webpack webpack-cli webpack-dev-server css-loader style-loader
```


* デモページのHTMLを作成します。

```html:examples/src/index.html
<html>
<head>
    <title>My Component Demo</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
</head>
<body>
    <noscript>
        You need to enable JavaScript to run this app.
    </noscript>
    <div id="root"></div>
</body>
</html>
```


* デモページの起点となるコンポーネントを作成します。この時、[1. コンポーネントを作成する](#1-コンポーネントを作成する)で作成したコンポーネントを使います。

```jsx:examples/src/index.js
import React from 'react';
import { render } from 'react-dom'
import MyComponent from '../../src'

const App = () => <MyComponent/>;
render(<App />, document.getElementById('root'));
```


* デモページのビルド設定ファイル（webpack.config.js）を作りましょう。

```javascript:webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// HTMLファイルのビルド設定
const htmlWebpackPlugin = new HtmlWebpackPlugin({
    template: path.join(__dirname, 'examples/src/index.html'),
    filename: './index.html'
});
module.exports = {
    // 依存関係解決の起点となる資産を指定します。
    entry: path.join(__dirname, 'examples/src/index.js'),
    // Babelのトランスパイル対象資産を指定します。
    module: {
        rules: [
            {
                test: /\.(js|jsx)/,
                use: 'babel-loader',
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: ["style-loader", "css-loader"]
            }
        ]
    },
    plugins: [htmlWebpackPlugin],
    resolve: {
        extensions: ['.js', '.jsx']
    },
    // 開発用Webサーバのポートを指定します。
    devServer: {
        port: 3001
    }
}
```


* デモページ起動用スクリプトをpackage.jsonに追記します。

```diff:package.jsonの一部
    "scripts": {
+     "start": "webpack-dev-server --mode development"
    },
```

### 確認

* 準備が整ったのでデモページを起動しましょう。

```bash
npm start
```


* ブラウザで`http://localhost:3001`にアクセスしてコンポーネントが表示されればOKです。


![図1.png](https://qiita-image-store.s3.amazonaws.com/0/49915/d6587175-21d7-a15b-48e9-3c6ec31ed105.png)



## 3. デモページをGitHub Pagesで公開する

デモページをローカルで見れるようになったら、次はGitHubに資産を登録し、GitHub Pagesで公開しましょう。プラグインで簡単に公開できます。


<small>※ 本手順完了時のソースコードは**[こちら](https://github.com/Takumon/react-component-sample/tree/step3)**</small>
<small>※ 本手順完了時のプロジェクト構成 ↓</small>

```diff
プロジェクトルート
  ├─dist              ・・・ コンポーネントビルド資産出力場所
  ├─src               ・・・ コンポーネント資産
  │   ├─index.js
  │   └─styles.css
  ├─examples          ・・・ デモページ用資産
  │  ├─dist           ・・・ デモページビルド資産出力場所
  │  └─src
  │     ├─index.html
  │     └─index.js
  ├─node_modules
  ├─.babelrc          ・・・トランスコンパイル用設定ファイル
+ ├─webpack.config.js ・・・ビルド用設定ファイル（追記）
+ ├─package.json      ・・・依存ライブラリ・スクリプト定義ファイル（追記）
+ └─.gitignore
```



* github-pagesというGitHub公開用プラグインをインストールします。

```bash
npm i -D gh-pages
```


* package.jsonにGitHubPages公開用スクリプトを３個追加します。
  * `publish-demo`はビルドとデプロイをいっぺんにやるスクリプトです。デプロイ前に大抵の場合ビルドするので、1つにまとめておくと便利です。

```diff:package.json
    "scripts": {
+     "build": "webpack --mode production",
+     "deploy": "gh-pages -d examples/dist",
+     "publish-demo": "npm run build && npm run deploy"
    },
```

* webpack.config.jsに出力先を指定します。

```diff:webpack.config.jsの一部
  module.exports = {
+     output: {
+        path: path.join(__dirname, "examples/dist"),
+        filename: "bundle.js"
+    },
  }
```

* ではビルドしてみましょう。

```bash
npm run build
```

* ビルドされて最小化された資産が`examples/dist`配下に出力されたのが確認できればOKです。


* Gitにあげる準備として`.gitignore`を作りましょう。

```txt:.gitignore
node_modules
dist
```


* GitHubでリポジトリを新規作成して資産を登録しましょう。新規作成後の「...or create a new respository on the command line」の説明に従ってください。

### 確認
* 下記を実行してGitHub Pagesに登録しましょう。

```bash
npm run publish-demo
```

* 2～3分後にGitHub PagesでWeb公開されていれば成功です。
    * 例 => 　https://takumon.github.io/react-component-sample/



### (補足)画像ファイルを扱う場合

もしデモページで画像を読み込む場合は`file-loader`と`url-loader`を開発用依存ライブラリに追加してください。ビルド設定も下記のように修正が必要です。

```
npm i -D file-loader url-loader
```

```diff:webpack.config.jsの一部
  module.exports = {
    module: {
+     {
+       test: /\.(jpg|png|ico)$/,
+       use: 'url-loader'
+     },
    },
  }
```



<br />
これであとは最終手順のnpm公開を残すのみです！
<br />




## 4. コンポーネントをnpmに公開する

デモページも準備できたので、いよいよコンポーネントをnpmに公開しましょう。

<small>※ 本手順完了時のソースコードは**[こちら](https://github.com/Takumon/react-component-sample/tree/step4)**</small>
<small>※ 本手順完了時のプロジェクト構成 ↓</small>

```diff
プロジェクトルート
  ├─dist              ・・・ コンポーネントビルド資産出力場所
  ├─src               ・・・ コンポーネント資産
  │   ├─index.js
  │   └─styles.css
  ├─examples          ・・・ デモページ用資産
  │  ├─dist           ・・・ デモページビルド資産出力場所
  │  └─src
  │     ├─index.html
  │     └─index.js
  ├─node_modules
  ├─.babelrc          ・・・トランスコンパイル用設定ファイル
  ├─webpack.config.js ・・・ビルド用設定ファイル
+ ├─package.json      ・・・依存ライブラリ・スクリプト定義ファイル(追記)
+ ├─.npmignore        ・・・npm登録除外対処定義ファイル
  └─.gitignore
```



* トランスパイル後に生成されるdist/index.jsを、npm公開資産のメインファイルに指定します。

```diff:package.json
+   "main": "dist/index.js",
```


* 次にnpm公開時に自動で走るスクリプト`prepublishOnly`を追加します。これによりnpm公開時にビルドし忘れるということを防ぎます。


```diff:package.jsonの一部
    "scripts": {
+     "prepublishOnly": "npm run transpile"
    }
```


* このコンポーネントを使う側には、Reactがインストール済という想定ですのでpeerDependenciesを指定します。

```diff:package.jsonの一部
+   "peerDependencies": {
+     "react": "^16.3.0",
+     "react-dom": "^16.3.0"
+   },
```


* `.npmignore`を作成して、npm公開資産として不用な資産（トランスパイル前のjsファイルなど）は公開対象外としましょう。

```txt:.npmignore
src
examples
.babelrc
.gitignore
webpack.config.js
```

* 最後にパッケージ名(package.jsonのname)を決めます。現段階では`react-component-sample`となっていて、お試しで作るコンポーネントとしては少し汎用的すぎる名前なので、`@自分のnpmアカウント名/raect-component-sample`のようにしましょう。例えば下記のように修正します。

```diff:package.json修正例
-   "name": "react-component-sample",
+   "name": "@takumon/react-component-sample",
```


### 確認

* ではnpm公開してみましょう。
  * `@takumon/react-component-sample`のようなパッケージ名は、[Scoped Packag](https://docs.npmjs.com/about-scopes)(npmのプライベートなパッケージの命名規約)に沿っているのでデフォルトでプライベート公開になってしまいます。npmで有料契約をせずにプライベート公開しようとすると402エラーになります。そのため、ここでは`--access=public`をつけて一般公開するようにしています。（参考：[stachoverflow](https://stackoverflow.com/questions/45820881/npm-publish-failed-put-402))
  * `Failed PUT 403`になる場合は、npmの認証エラーです。`npm login`しましょう。
それか`package.json`の`version`が古いのが原因です。いったん公開したバージョンで再公開はできません。バージョンをインクリメントしましょう。

```bash
npm publish --access=public
```


* これで、npm公式サイトを確認しパッケージが追加されていれば、公開完了です！

![図4.png](https://qiita-image-store.s3.amazonaws.com/0/49915/295469c1-b6d0-bec8-006d-d70b095d9806.png)





## おわりに

今回紹介したように、わりと簡単にnpm公開できるので、普段使いまわしているようなコンポーネントがあれば公開してみるのもいいかもしれません。



## 参考
* [A guide to building a React component with webpack 4, publishing to npm, and deploying the demo to GitHub Pages | Medium](https://medium.com/dailyjs/building-a-react-component-with-webpack-publish-to-npm-deploy-to-github-guide-6927f60b3220)
* [Creating a library of React components using Create React App | Medium](https://hackernoon.com/creating-a-library-of-react-components-using-create-react-app-without-ejecting-d182df690c6b)