Seiichi Yonezawa

Reactのコンポーネントを公開する

RailsのWebpackerを使っていると、ちょっとしたコンポーネントでもRailsのディレクトリにReactのファイルを保存したくないと思い始めました。 その理由は通常CRAで作業する場合はsrcなのに対して、Railsだとapp/javascript/packsまで開かなければなりません。 また、テストを書いたり、コードを整形したりするときにもRailsのディレクトリだと何かと不都合があるような気がしました。

Railsで書いているコードも別のプロジェクトで使いたくなるようなことがあるかもしれません。 そういう時は表題の通り、Reactのコンポーネントをパッケージ化してしまうのがよいと思います。 これまでNPMにパッケージを公開したことはありませんでしたが、簡単だったので内容を残そうと思います。

コンポーネントを用意する

まずは公開したいコンポーネントについて考えてみます。 RailsのWebpackerで使用しているReactは16だったので、CRAで用意したパッケージをGit経由でインストールしました。 以下のようなファイルが生成されると思います:

import React from 'react'

class App extends Component {
  state = {
    foo: 'bar'
  }

  render() {
    return (
      <div>{this.state.foo}</div>
    )
  }
}

export default App

このコンポーネントは単一の文字を表示するだけですが、Rails側では<App />というコンポーネントとして扱うことができます。 本来はSPAのように扱うのが一般的かもしれませんが、既存のページやフォームに組み込めるように小さな単位として扱うようにします。 そして、Rails側でコンポーネントを読み込もうとすると以下のようなエラーメッセージが表示されました:

Module build failed: SyntaxError: Unexpected token (4:8)

  2 |
  3 | class App extends Component {
> 4 |   state = {
    |         ^
  5 |     foo: "bar"
  6 |   };
  7 |

WebpackerのReactでは残念ながらクラスに対して上記のようなショートハンドは使えず、constructorでプロパティを継承する必要がありました。 対処法はRails側で修正するか、コンポーネントを書き換えるかですが、理想としてRails側は設定を変えずに必要なライブラリだけをインポートしていく形にしたいです。 そのためにWebpackでビルドしたスクリプトをパッケージ化する必要があります。

Webpackを用意する

https://medium.com/@BrodaNoel/how-to-create-a-react-component-and-publish-it-in-npm-668ad7d363ce

こちらの投稿を参考にwebpack.config.jsファイルを用意します:

var path = require('path');

module.exports = {
  entry: './src/App.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
    libraryTarget: 'commonjs2' // 1
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      }
    ]
  },
  externals: {
    'react': 'react' // 2
  }
};

ここで重要な部分はlibraryTargetexternalsという部分です。 libraryTargetcommonjs2を指定することでAppがインポートできるようになります。 externalsreactを指定すると、ビルドしたファイルにReactを埋め込まなくて済むためファイルサイズが抑えられます。

また、先程のエラーは.babelrcに以下の記述で修正しました:

{
  "presets": [
    [
      "env",
      {
        "targets": {
          "browsers": ["> 1%", "last 2 versions"]
        }
      }
    ],
    "stage-2",
    "react"
  ]
}

パッケージを追加するたびにこれと同じ修正をする必要はありますが、それぞれBabelの設定内容が干渉しないのでそのときに応じた設定を追加していくことが可能です。 もちろんRails側にこのファイルを追加すればそれまでというだけではあるのですが、今後複数のパッケージを追加していくことを考えると柔軟に対応できるほうがよいと思いました。 他にもCSSモジュールやCSSのPrefixを追加したり、モジュールを遅延インポートさせるようにしたりもできますが今回は割愛します。

NPMに公開する

https://hackernoon.com/how-to-publish-your-package-on-npm-7fc1f5aae600

NPMに関しては上記の投稿を参考にしました。 そこで以下のpackage.jsonを用意します:

{
  "name": "foo",
  "version": "0.0.0",
  "main": "dist/bundle.js",
  "files": [
    "dist"
  ],
  "scripts": {
    "start": "webpack-dev-server",
    "build": "webpack",
    "prepublish": "yarn build"
  }
}

npm publishあるいはyarn publishで誰も取得していなければfooというコンポーネントを公開することができます。 長くなるのでパッケージの依存関係情報は省略しました。 一度パッケージをNPMで公開してしまうと通常は簡単に取り消すことができませんが、72時間以内ならnpm unpublishで消去することができるようです。

NOTE: 再度同じパッケージの名称で公開する場合は24時間経過する必要があるようです。

まとめ

Reactに限らず、RailsでWebpackerを利用する場合で応用できると思います。 Next.jsやNuxt.jsといったフレームワークが出てきているので、あえてRailsにこだわる必要はありませんが再利用することを念頭にコードを書くのも重要です。 今後もメンテナンス性に配慮した投稿ができたらよいなと思っています。

投稿一覧