MobX + Babel + webpack + Flow

地元の勉強会のための準備です。

yokohama-north.connpass.com

前提

$ node -v
v8.2.1
$ npm -v
5.3.0

準備。

$ npm init

MobX

インストールします。

$ npm i mobx -D

MobXを使ったストアのサンプル。このチュートリアル分かりやすいです。

MobX: Ten minute introduction to MobX and React

import { autorun, observable, action, computed } from 'mobx';

export default class TodoStore {
  @observable todos = [];
  @observable pendingRequests = 0;

  constructor() {
    autorun(() => console.log(this.report));
  }

  @computed get completedTodosCount() {
    return this.todos.filter(
      todo => todo.completed === true
    ).length;
  }

  @computed get report() {
    if (this.todos.length === 0) {
      return "<none>";
    } else {
      return `Next todo: "${this.todos[0].task}". ` +
        `Progress: ${this.completedTodosCount}/${this.todos.length}`;
    }
  }

  @action addTodo(task) {
    this.todos.push({
      task, completed: false, assignee: null
    });
  }
}

デバッガー

mobx-remotedevを使います。

GitHub - zalmoxisus/mobx-remotedev: MobX DevTools extension

上のリンク先にも書いてある通り、まずはChrome拡張機能である redux-devtools-extension をインストールします。

GitHub - zalmoxisus/redux-devtools-extension: Redux DevTools extension.

mobx-remotedevをインストールします。

$ npm i mobx-remotedev -D

TodoStoreに @remotedev を追記します。

import remotedev from 'mobx-remotedev/lib/dev';

@remotedev
export default class TodoStore {
  ・・・

babel

上のコードには、export、class、import、getter、デコレータなどが使われています。当然このままではブラウザでは動かないので、Babelでコンパイルする必要があります。

$ npm i babel-cli babel-preset-es2015 babel-preset-stage-1 babel-plugin-transform-decorators-legacy babel-plugin-transform-class-properties -D

.babelrcを用意します。

{
  "presets": [
    "es2015",
    "stage-1"
  ],
  "plugins": [
    "transform-decorators-legacy",
    "transform-class-properties"
  ]
}

package.jsonbuild タスクを定義してbabelを実行するようにします。

"scripts": {
  "build": "babel src -d dist"
}

これでコンパイルできます。

$ npm run build

webpack

次は、webpackを使って依存関係を解決したjsファイルを作ります。

$ npm i webpack babel-loader -D

loaderとして babel-loader を使用する。 .babelrc を削除して webpack.config.js を作る。

module.exports = {
  context: __dirname + "/src",
  entry: {
    "application": "./index.js"
  },
  output: {
    path: __dirname + "/dist",
    filename: "[name].js"
  },
  module: {
    rules: [
      {
        exclude: /node_modules/,
        loader: require.resolve("babel-loader"),
        options: {
          plugins: [
            "transform-decorators-legacy",
            "transform-class-properties"
          ],
          presets: ["es2015", "stage-1"]
        }
      }
    ]
  }
}

webpackのentryとなるjsファイルを用意する。

import TodoStore from './TodoStore';

const observableTodoStore = new TodoStore();

window.observableTodoStore = observableTodoStore;

package.jsonの build をbabelからwebpackを使うように変更する。

"scripts": {
  "build": "webpack"
}

これでBabelを使ってコンパイルして、webpackを使って依存関係を解決したJavaScriptファイルを作成できる。

$ npm run build

おしまい

dist/application.js として出力されるので、これをhtmlで読み込めばTodoStoreを使えます!

package.jsonはこんな状態。

{
  "name": "mobx-todo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "mobx": "^3.2.1",
    "babel-cli": "^6.24.1",
    "babel-loader": "^7.1.1",
    "babel-plugin-transform-class-properties": "^6.24.1",
    "babel-plugin-transform-decorators-legacy": "^1.3.4",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-stage-1": "^6.24.1",
    "mobx-remotedev": "^0.2.8",
    "webpack": "^3.3.0"
  }
}

Flow (おまけ)

Flowで型チェックしてみる。

$ npm i flow-bin flowtype babel-preset-flow -D

TodoStoreを書き換える。

// @flow
import { autorun, observable, action, computed } from 'mobx';
import remotedev from 'mobx-remotedev/lib/dev';

type Task = {
  task: string,
  completed: boolean,
  assignee: ?string,
}

@remotedev
export default class TodoStore {
  @observable todos: Array<Task> = [];
  @observable pendingRequests: number = 0;

  constructor() {
    autorun(() => console.log(this.report));
  }

  @computed get completedTodosCount(): number {
    return this.todos.filter(
      todo => todo.completed === true
    ).length;
  }

  @computed get report(): string {
    if (this.todos.length === 0) {
      return "<none>";
    } else {
      return `Next todo: "${this.todos[0].task}". ` +
        `Progress: ${this.completedTodosCount}/${this.todos.length}`;
    }
  }

  @action addTodo(task: string): void {
    this.todos.push({
      task, completed: false, assignee: null
    });
  }
}

webpack.config.jsを書き換える。

@@ -15,9 +15,10 @@ module.exports = {
         options: {
           plugins: [
             "transform-decorators-legacy",
-            "transform-class-properties"
+            "transform-class-properties",
+            "transform-flow-strip-types"
           ],
-          presets: ["es2015", "stage-1"]
+          presets: ["es2015", "stage-1", "flow"]
         }
       }
     ]

package.jsonにflowタスクを追加します。

"scripts": {
  "build": "webpack",
  "flow": "flow"
}

Flowで型チェック

$ npm run flow