npmパッケージを「さくっ」と公開する

Gakuです。

最近、reactをちまちま触っています。
そんな中で触れることが多いnpmパッケージですが、「自分もnpmパッケージを公開したい!」と思いました。

そこで今回は簡単なreactコンポーネントをnpmパッケージとして公開する手順を備忘録として残したいと思います。

今回作ったもの

今回作ったものは下記になります。
「ブログ読むのめんどくさいよ~」とか「実行しつつ試したい!」という方はご参考下さい。
[blogcard url=”https://github.com/gaku3601/react-helloworld-sample”][/blogcard]
[blogcard url=”https://www.npmjs.com/package/react-helloworld-sample”][/blogcard]

npmパッケージの公開

npmデベロッパーとして登録する

何はともあれ。まず、npmへデベロッパー登録をする必要があります。
登録は下記から可能です。
[blogcard url=”https://www.npmjs.com/signup”][/blogcard]

登録が完了したなら、登録したアカウントとnpmコマンドを紐付けするために下記コマンドを実行します。

npm adduser

emailやpassword等々、先程登録したアカウントの情報を入力すればOKです。
これで公開する準備は整いました。簡単ですね。

HelloWorldコンポーネントを作成する

今回は呼び出すと「HelloWorld」と出力する簡単なreactコンポーネントを作成したいと思います。
まずはnpm initでpackage.jsonを作成しましょう。

$ npm init
name: (react-helloworld-sample)
version: (1.0.0) 0.0.1
description:
entry point: (index.js)
test command:
git repository:
keywords:
author: gaku
license: (ISC) MIT

npmパッケージとして既に公開されている名前は使用出来ませんので注意が必要です。
作成したpackage.jsonはこんな感じ

{
  "name": "react-helloworld-sample",
  "version": "0.0.1",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "gaku",
  "license": "MIT"
}

そしたらHelloWorldコンポーネントを作成します。
src/scriptsへHelloWorld.jsファイルを作成し、以下のように記述します。

import React from 'react'

const HelloWorld = () =>(
  <div>
    HelloWorld
  </div>
)

export default HelloWorld

また、エントリーポイントとなるindex.jsでHelloWorld.jsをexportします。

import HelloWorld from './src/scripts/HelloWorld.js'

export { HelloWorld }

そしてこれらのコードをbabelでコンパイルを行います。
コンパイルを行ったindex.jsのパスをpackage.jsonのmain部分に記述します。

{
  "name": "react-helloworld-sample",
  "version": "0.0.1",
  "description": "",
  "main": "./src/dist/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "./node_modules/.bin/babel ./src/scripts --out-dir ./src/dist"
  },
  "author": "gaku",
  "license": "MIT",
}

Exampleプロジェクトの作成

公開するnpmパッケージと同階層にexampleフォルダを作成し、reactアプリケーションを動作させる環境を構築します。
react環境構築の話となってしまうため割愛しますが、詳しくはgithubのexampleフォルダをご確認下さい。
[blogcard url=”https://github.com/gaku3601/react-helloworld-sample”][/blogcard]

Exampleプロジェクトとnpmパッケージの紐付け

[npm link]コマンドを使用すればExampleプロジェクト内で作成中のnpmパッケージを使用することが可能です。
npmパッケージフォルダで[npm link]コマンドを実行し、exampleフォルダ内で[npm link パッケージ名]を実行後、package.jsonのdependenciesに[“パッケージ名”:”*”,]を記述することで紐付けを行うことが可能です。
紐付けが出来ているか確認するにはexampleフォルダ内で[npm ls パッケージ名]を実行してみて下さい。

$ npm ls react-helloworld-sample
example@1.0.0 C:\Users\proga\develop\projects\react-helloworld-sample\example
`-- react-helloworld-sample@0.0.1  -> C:\Users\proga\develop\projects\react-helloworld-sample

このように表示されれば紐付け完了です。

Exampleアプリケーションの作成

exampleアプリケーションでreact-helloworld-sampleを呼び出してみます。

import React from 'react'
import {render} from 'react-dom'

import { HelloWorld } from 'react-helloworld-sample'

render(
  <div>
    <HelloWorld />
  </div>,
  document.querySelector('#content')
)

しかし、この状態でアプリケーションを実行してもnpmパッケージ側に必要なモジュールが入っていないためエラーとなってしまいます。
適宜、必要な依存モジュールをnpmパッケージ側にインストールしてあげて下さい。
これで「HelloWorld」と出力されればアプリケーションの作成は完了です。

作成したnpmパッケージを公開する

公開手順は至極簡単です。下記コマンドをnpmパッケージのフォルダで実行してあげれば公開することが可能です。

npm publish

簡単ですね。バージョンアップを行う場合は、package.jsonのバージョンを上げてあげないとエラーとなってしまうためご注意下さい。
良きnpmパッケージライフを(´・ω・`)ノ

npm公開の注意点

npmパッケージの公開は簡単に可能です。
しかし、一旦公開してしまうと簡単には削除できない点(実際には削除は不可能)で注意が必要です。

npmのポリシー上、一旦公開したパッケージは削除することができないそうです。
(※誰かが作成したパッケージを使用している場合、そのアプリケーションを壊してしまう恐れがあるため。)

今回の記事を書くまでに2つのnpm公開勉強用パッケージを公開していたのですが、削除したい旨をnpmサポートにメールしたところ「削除はできないが開発元npmへパッケージを移譲するという形でなら対応可能」とのことでしたので、そのようにさせて頂きました。
メールも英語でかなりめんどくさかったので、「公開したものは削除できない」と覚えておけばOKかと思います。

以上となります。フロントは楽しいですが、いろいろ勉強しないとなので正直キツイっすわ(´・ω・`)

Immutable.jsでreact+redux環境が楽になりそうな話

この記事はQiitaのReact Advent Calendar 2016/14日に投稿させていただく記事です。
お粗末な文章ですが失礼致します。

さて、みなさんreact+reduxしていますか?
僕は毎日react+reduxでハッスルしています。

まだまだreact+reduxを触りだして間もないのですが、Immutable.jsなるものを導入するとreact+redux環境が劇的に楽になるとの情報をキャッチしましたので、今回はその件について書かせて頂ければと思います。

Immutable.jsとは何ぞや?

reactと同じで天下のfacebook様が開発されているjsライブラリです。
[blogcard url=”https://facebook.github.io/immutable-js/”][/blogcard]
「Immutable」と名前の通り、Immutable.jsではデータを不変なデータとして管理できるようになるそうです。

。。。なんのこっちゃ?っと思いましたので、基本的な使用方法を見ていきたいと思います。

基本的な使用方法

[blogcard url=”http://kenjimorita.jp/immutable-js/”][/blogcard]
こちらの記事が非常にわかりやすく参考になりました。

stateの更新を行う際、redux?のお作法でstate自身を更新するのではなく、新しいstateを作成しないといけません。
そのため、簡単な更新でもこのような処理になってしまします。

const todos = (state=[],action) => {
  switch (action.type) {
    case "ADD_TODO":
      return [   //この部分
        ...state,
        {text:action.text}
      ]
    default:
      return state;
  }
}
export default todos

…stateで現在のstateを展開し、その後ろに追加するtodoのtextを挿入し、新しい配列をstateとして登録するといった感じですね。
このような簡単な処理だけで見ても、直感的でないことがわかります。
stateが階層構造にでもなったならば、容易に複雑になることが想定されます。。。

つらい(´・ω・`)

そんな時にImmutable.jsです!
Immutable.jsは上にも記述したように不変なデータを扱うことが可能です。
先程のtodoReducerをImmutable.jsを使用し記述するとこのようになります。

import { List } from 'immutable'
const init = List([])
const todos = (state=init,action) => {
  switch (action.type) {
    case "ADD_TODO":
      return state.push(action.text)
    default:
      return state
  }
}
export default todo

素晴らしいですね。
state.push(action.text)で元のstateは変更せず、新しくstateを作成し返却といった処理が感覚的に記述できるようになります。

ちょっと発展した使い方

[blogcard url=”https://github.com/gaku3601/study-immutable”][/blogcard]
以下で作成するアプリケーションはこちらのリポジトリのimmtable-projectに格納されています。
記事読むの面倒くさい!って方や動きを見ながら記事読みたいって方はご活用下さい。

npm install
npm start

で実行可能です。

発展的な使用方法でRecord関数を使用することでこういった書き方もできるようになります。

import { List,Record } from 'immutable'

const ToDoRecord = Record({
  text:''
})
const init = List([])
const todos = (state=init,action) => {
  switch (action.type) {
    case "ADD_TODO":
      return state.push(new ToDoRecord({text:action.text}))
    default:
      return state
  }
}
export default todo

今まで散文していた、エンティティ定義がToDoRecord にまとめられ一目でわかるようになります。
(stateのエンティティ定義はコメントで書かなくて良くなりそうです。)

さらにさらに、recordは継承することが可能でありメソッドを記述することが可能です。
IDを振るといった処理をrecordを継承し作成したクラスに定義したものが以下になります。

import { List,Record,Seq } from 'immutable'

const ToDoRecord = Record({
  todoList:List()
})

const ToDoContentRecord = Record({
  id:null,
  text:''
})

class ToDo extends ToDoRecord{
  getNextId(){
    let newNumber = 1;
    if(this.todoList.size != 0){
      newNumber = Seq(this.todoList).map(x => x.get('id')).max() + 1;
    }
    return newNumber;
  }
}

const todo = (state=new ToDo,action) => {
  switch (action.type) {
    case "ADD_TODO":
      return state.set('todoList',state.todoList.push(new ToDoContentRecord({
        id:state.getNextId(),
        text:action.text
      })))
    default:
      return state
  }
}

export default todo

ちょっと複雑になりますが、親レコード(ToDoRecord)で子レコード(ToDoContentRecord)を管理するようにし、ToDoRecordを継承したToDoクラス内でid連番処理を実装しています。
で、reducer部分ではstate.getNextIDを呼び出し、新しく算出したIDとtextを登録する処理を実装しています。
命名とかが汚いのはご容赦下さい。(´・ω・`)

ここまで来るとさらに欲が出てくると思います。
「これ、reducer部分さらに薄くできんじゃね?」

はい、実装してみます。

import { List,Record,Seq } from 'immutable'

const ToDoRecord = Record({
  todoList:List()
})

const ToDoContentRecord = Record({
  id:null,
  text:''
})

class ToDo extends ToDoRecord{
  addToDo(text){
    return this.set('todoList',this.todoList.push(new ToDoContentRecord({
      id:this.getNextId(),
      text:text
    })))
  }

  getNextId(){
    let newNumber = 1;
    if(this.todoList.size != 0){
      newNumber = Seq(this.todoList).map(x => x.get('id')).max() + 1;
    }
    return newNumber;
  }
}
export default ToDo
import ToDo from '../models/ToDo'

const todo = (state=new ToDo,action) => {
  switch (action.type) {
    case "ADD_TODO":
      return state.addToDo(action.text)
    default:
      return state
  }
}
export default todo

新しくビジネスロジックを記述するmodelsを作成し、そちらに寄せることでreducer部分がかなり薄くなります!
今までビジネスロジックをactionに記述するかreducerに記述するか迷っていましたが、modelsにビジネスロジックを分離することが可能です。
これは嬉しいですね。スッキリです!

さらにさらにさらに、欲を出してみましょう!
immutable.jsでRecordを継承することで、メソッドを定義することができました。
そのメソッドをactionを通じてreducerへ渡して上げれば、肥大化するaction,reducerをさらに薄く出来るのではないかと。。。

実装してみます。
先程実装したmodelsはそのままで、reducer、action、container、componentを以下のように実装します。

import React from 'react'

export default class ToDoComponent extends React.Component {
  render(){
    return(
      <div>
        <input type="text" ref="inputtodo" />
        <button onClick={() => {
          this.props.addToDo(this.props.todo.addToDo(this.refs.inputtodo.value))
        }}>submit</button>
      {this.props.todo.todoList.map(todo => (
          <p >{todo.get('text')}</p>
        ))}
      </div>
    )
  }
}
import { connect } from 'react-redux'
import ToDoComponent from '../components/ToDoComponent'
import { onChange } from '../actions/todo'

const mapStateToProps = (state) =>{
  return{
    todo:state.todo
  }
}

const mapDispatchToProps = (dispatch) =>{
  return {
    addToDo: (entry) => {
      dispatch(onChange(entry))
    }
  }
}

const ToDo = connect(
  mapStateToProps,
  mapDispatchToProps
)(ToDoComponent)

export default ToDo

this.props.addToDo(this.props.todo.addToDo(this.refs.inputtodo.value))でmodel内のaddToDo()を呼び出し、

addToDo: (entry) => {
  dispatch(onChange(entry))
}

でそのaddToDoメソッドをactionに渡してあげます。
あとはいつもどおりactionとreducerを作成してあげれば完成です。

export const onChange = (entry) =>{
  return {
    type:"ON_CHANGE",
    entry:entry,
  }
}
import ToDo from '../models/ToDo'

const todo = (state=new ToDo,action) => {
  switch (action.type) {
    case "ON_CHANGE":
      return action.entry
    default:
      return state
  }
}

export default todo

何か新しくstate更新処理を実装したくなった際は、modelとcontainer,component部分を編集するだけで実装することが可能になります。

もう、素晴らしすぎますね。
これで「あれ、あのactionどこだったっけ?」とか「actionのフォルダ階層深すぎワロタ」問題を解消することが可能になります。

弊害

弊害というほどのことではありませんが、Immutable.jsを使用するとstateが今までのオブジェクト構造ではなく、Immutable.jsでラップされたオブジェクトとなってしまうため、注意が必要です。
Http request処理部分やlocalstorageへのstate保存処理などを行う際はtoJS()でオブジェクトへ変換、取得する際はfromJS()でImmutable.jsのオブジェクトでラッピングを行うという点を覚えておけば大きなハマりはないと思います。

おわりに

いかかでしたでしょうか?
react+redux環境で問題としてあげられる、「action、reducer部分の肥大化問題」、ならびに「ビジネスロジックどこに書く問題」等々がImmutable.jsで解決できたと思います。
まだまだ、勉強したてですので、他にも弊害が出てくるかもしれませんが、僕の中では一旦この構成で開発を進めて行こうと思います。

就職活動

ただいまニートのGakuはおもしろい就職先を探しております。
大小問いませんし、IT企業でなくても構いません。

少しでも興味を持っていただけたならば、TwitterのDM等で連絡いただければ即対応させて頂きます。
Twitter:https://twitter.com/gaku3601

参考文献

React使い必見! Immutable.jsでReactはもっと良くなる