dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

API Blueprintによるドキュメント開発環境【2016年冬】

ここから少しアップデートしたのでメモ。

リポジトリ


特徴

markdown -> html への変換には aglio、ローカルサーバーは --server オプションを使う

ローカルで markdown ファイルを html に変換するのに aglio を使うところは以前と同じ。
改めて調べてみると、--server というオプションでライブリロード機能つきのローカルサーバーの起動までやってくれるらしく、browserSync は不要だった。

また、ポート番号については README に記載されていないが、-p オプションで指定できた。
参考:Support for different port? · Issue #12 · danielgtaylor/aglio

$ ./node_modules/aglio/bin/aglio.js
Usage: node_modules/aglio/bin/aglio.js [options] -i infile [-o outfile -s]

オプション:
  -i, --input           Input file
  -o, --output          Output file
  -t, --theme           Theme name or layout file        [デフォルト: "default"]
  -f, --filter          Sanitize input from Windows    [真偽] [デフォルト: true]
  -s, --server          Start a local live preview server
  -h, --host            Address to bind local preview server to
                                                       [デフォルト: "127.0.0.1"]
  -p, --port            Port for local preview server         [デフォルト: 3000]
  -v, --version         Display version number               [デフォルト: false]
  -c, --compile         Compile the blueprint file           [デフォルト: false]
  -n, --include-path    Base directory for relative includes
  --verbose             Show verbose information and stack traces
                                                             [デフォルト: false]
  --theme-variables     Color scheme name or path to custom variables
                                                         [デフォルト: "default"]
  --theme-condense-nav  Condense navigation links      [真偽] [デフォルト: true]
  --theme-full-width    Use full window width         [真偽] [デフォルト: false]
  --theme-template      Template name or path to custom template
                                                         [デフォルト: "default"]
  --theme-style         Layout style name or path to custom stylesheet
  --theme-emoji         Enable support for emoticons   [真偽] [デフォルト: true]

例:
  node_modules/aglio/bin/aglio.js -i        Render to HTML
  example.apib -o output.html
  node_modules/aglio/bin/aglio.js -i        Start preview server
  example.apib -s
  node_modules/aglio/bin/aglio.js           Theme colors
  --theme-variables flatly -i example.apib
  -s
  node_modules/aglio/bin/aglio.js           Disable options
  --no-theme-condense-nav -i example.apib
  -s

See https://github.com/danielgtaylor/aglio#readme for more information

(コマンド例)

$ aglio -i src/index.md -p 8080 --server


複数ファイルの分割は <!-- include(xxx.md) --> で実現

参考:https://github.com/danielgtaylor/aglio#including-files

エントリーポイントとなるファイルで

FORMAT: 1A

<!-- include(xxx.md) -->

としておくと、別のファイルを読み込むことができる。
これで API 定義を複数のファイルに分割することができる。

これ自体は以前調べたときにも把握はしてたようだが、モックサーバー用ライブラリ api-mock の制限で断念したようだった。
が、次に示すようにモックサーバを api-mock から drakov というライブラリに変更したのでこの問題は解決した。


モックサーバには api-mock のかわりに drakov を使用

以前調べたときは api-mock を使おうとしていたらしいが、
今 Node のバージョン 7.1.0、npm のバージョン 3.10.9 でインストールしようとしたところエラーになってしまった。

$ npm i -g api-mock
/usr/local/bin/api-mock -> /usr/local/lib/node_modules/api-mock/bin/api-mock

> protagonist@1.2.6 install /usr/local/lib/node_modules/api-mock/node_modules/protagonist
> node-gyp rebuild

  CXX(target) Release/obj.target/libmarkdownparser/drafter/ext/snowcrash/ext/markdown-parser/src/ByteBuffer.o
clang: error: invalid deployment target for -stdlib=libc++ (requires OS X 10.7 or later)
make: *** [Release/obj.target/libmarkdownparser/drafter/ext/snowcrash/ext/markdown-parser/src/ByteBuffer.o] Error 1
gyp ERR! build error
gyp ERR! stack Error: `make` failed with exit code: 2
gyp ERR! stack     at ChildProcess.onExit (/usr/local/lib/node_modules/npm/node_modules/node-gyp/lib/build.js:276:23)
gyp ERR! stack     at emitTwo (events.js:106:13)
gyp ERR! stack     at ChildProcess.emit (events.js:191:7)
gyp ERR! stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:215:12)
gyp ERR! System Darwin 14.5.0
gyp ERR! command "/usr/local/Cellar/node/7.1.0/bin/node" "/usr/local/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "rebuild"
gyp ERR! cwd /usr/local/lib/node_modules/api-mock/node_modules/protagonist
gyp ERR! node -v v7.1.0
gyp ERR! node-gyp -v v3.4.0
gyp ERR! not ok

この Issue が関係している?Node のバージョン 6 系からうまくインストールできないらしい。
install failed at protagonist package · Issue #53 · localmed/api-mock

しかたないので
https://apiblueprint.org/tools.html#mock-servers
の一番上にあった drakov にした。

drakov は対象の markdown ファイルを指定する際

$ drakov -f "src/**/*.md"

というように正規表現で指定することができるので、ファイルが複数に分割されていても問題ない。
" でくくらないと期待通りに動かないので注意(シェル環境が zsh だったからかも)

また、--watch オプションをつけておくと markdown ファイルの変更を検知して自動的にリロードしてくれる。

(コマンド例)

$ drakov -f "src/**/*.md" -p 8081 --watch


TODO

結局いまだに dredd というツールを調べられていない。

React(v15)&ReduxでMaterial-UIを使ってみる

メモです。
基本的な使い方を試してみた。


リポジトリ

Redux の basic チュートリアル にそって作った Todo アプリをベースに、Material-UI を適用してみる。

https://github.com/zaki-yama/redux-todo-app/pull/2

(Material-UI 適用前のコードには base というタグをつけてます)

なお、公式 example リポジトリも存在するよう。

https://github.com/callemall/material-ui-webpack-example


インストール

$ npm install --save material-ui

でインストール。


導入手順

react-tap-event-plugin をインストールする

http://www.material-ui.com/#/get-started/installation によると、react-tap-event-plugin というパッケージが必要らしい。
Material-UI と同様に npm でインストールする。

$ npm install --save react-tap-event-plugin

このパッケージにより、Material-UI のすべてのコンポーネントは onTouchTap というイベントハンドラが使えるようになる。

マウスクリックやスマホタブレットのタッチ・タップイベントを共通のイベントハンドラで処理できるようになる、のかな?

また、上記ページには

This dependency is temporary and will go away once the official React version is released.

と書いており、試したときの React のバージョンは 15.3.2 だったので
メジャーバージョンが 1 以上だからこのプラグイン無しでも動くということかな?と思ったけど、結局インストールしないとうまく動かなかった。

react-tap-event-plugin をインストールした後は、以下のコードをアプリの先頭に記述する必要がある。

import injectTapEventPlugin from 'react-tap-event-plugin';

// Needed for onTouchTap
// http://stackoverflow.com/a/34015469/988941
injectTapEventPlugin();

よって Redux の場合、アプリのエントリーポイント(src/index.js)に以下のように追記するはず。

 import { render } from 'react-dom';
 import { Provider } from 'react-redux';
 import { createStore } from 'redux';
+import injectTapEventPlugin from 'react-tap-event-plugin';
 import todoApp from './reducers';
 import App from './containers/App';
  
+// Needed for onTouchTap
+// http://stackoverflow.com/a/34015469/988941
+injectTapEventPlugin();
+
 const store = createStore(todoApp);
  
 render(
   <Provider store={store}>
     <App />
   </Provider>,
   document.getElementById('root'),
 );


Roboto フォントを適用する

またまた
http://www.material-ui.com/#/get-started/installation#roboto-font
を読むと、

Material-UI was designed with the Roboto font in mind

とあるので、Roboto フォント を読み込むようにする。

html ファイルに

<!-- src/index.html -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">

と書き、CSS でも

/* src/main.css */
html {
  font-family: 'Roboto', sans-serif;
}

とする。
確認したところ html 側の記述だけでも Material-UI コンポーネントに Roboto フォントが適用されるが、
アプリ全体のフォントを統一するため CSS でも指定することにした。

なお、話は逸れるが webpack で CSS を扱う方法はいくつか考えられる。

├── dist .................. ビルド後のファイルを置く場所
├── src
│   ├── actions
│   ├── components
│   ├── constants
│   ├── containers
│   ├── index.html
│   ├── index.js
│   ├── main.css
│   └── reducers
└── webpack.config.js

というディレクトリ構成の場合、

1) CSSCSS のままで html から読み込む場合

html 側は

<link rel="stylesheet" type="text/css" href="main.css">

とし、webpack.config.js では file-loader を使って dist ディレクトリに CSS ファイルもコピーされるようにする。

   context: __dirname + '/src',
   entry: {
     javascript: './index.js',
-    html: './index.html'
+    html: './index.html',
+    css: './main.css',
   },
   output: {
     path: __dirname + '/dist',
@@ -27,6 +28,10 @@ module.exports = {
       {
         test: /\.html$/,
         loader: 'file?name=[path][name].[ext]'
+      },
+      {
+        test: /\.css$/,
+        loader: 'file?name=[path][name].[ext]'
       }
     ]
   }

2) JavaScript にバンドルする場合

CSSJavaScript 内で import してしまうという場合、html の代わりに src/index.js で

import './main.css';

とし、webpack.config.js では css-loader と style-loader を使うようにする。

       {
         test: /\.html$/,
         loader: 'file?name=[path][name].[ext]'
-      }
+      },
+      {
+        test: /\.css$/,
+        loader: 'style!css',
+      },
     ]

どちらでもいいですが、2) の方が後々 SCSS とかにも応用できるので良さげ。


<MuiThemeProvider> でアプリケーション全体をラップする

コンポーネントを使う前の最後の準備として、アプリケーション全体を <MuiThemeProvider> というコンポーネントでラップする。
エントリーポイントになっている src/index.js でやってもいいと思うし、コンポーネントの root になっている containers/App.js でもいいはず。

containers/App.js

 import React from 'react';
+import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
+
 import AddTodo from './AddTodo';
 import VisibleTodoList from './VisibleTodoList';
 import FilterLinkList from '../components/FilterLinkList';

 export default class App extends React.Component {
   render() {
     return (
-      <div>
-        <AddTodo />
-        <VisibleTodoList />
-        <FilterLinkList />
-      </div>
+      <MuiThemeProvider>
+        <div>
+          <AddTodo />
+          <VisibleTodoList />
+          <FilterLinkList />
+        </div>
+      </MuiThemeProvider>
     );
   }
 }


Material-UI コンポーネントを使う

ここまでできるとようやく Material-UI のコンポーネントを使うことができる。

今回は簡単な例で、Todo の入力フォームとボタンをそれぞれ TextFieldRaisedButton に置き換えてみる。

元のコードは以下。

components/AddTodoForm.js

import React from 'react';

export default class AddTodoForm extends React.Component {
  handleSubmit(e) {
    e.preventDefault();
    const node = this.refs.input;
    const text = node.value.trim();
    if (!text) {
      return;
    }
    this.props.onSubmit(text);
    node.value = '';
  }

  render() {
    return (
      <div>
        <form onSubmit={(e) => this.handleSubmit(e)}>
          <input ref="input" />
          <button type="submit">
            Add Todo
          </button>
        </form>
      </div>
    );
  }
}

これを以下のようにする。

import React from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import TextField from 'material-ui/TextField';

export default class AddTodoForm extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      input: '',
    };
  }

  handleChange(e) {
    this.setState({
      input: e.target.value,
    });
  }

  handleSubmit(e) {
    e.preventDefault();
    const text = this.state.input.trim();
    if (!text) {
      return;
    }
    this.props.onSubmit(text);
    this.setState({
      input: '',
    });
  }

  render() {
    return (
      <div>
        <form onSubmit={(e) => this.handleSubmit(e)}>
          <TextField
            id="todo-title"
            value={this.state.input}
            onChange={(e) => this.handleChange(e)}
          />
          <RaisedButton
            label="Add Todo"
            onTouchTap={(e) => this.handleSubmit(e)}
          />
        </form>
      </div>
    );
  }
}

フォームに入力されたテキストを管理するのに ref を使っていたのを local state で管理するようになったため全体のコード量が増えた。
ref のまま Material-UI のコンポーネントを管理することもできるみたいなんだけど、ドキュメントに載っていない方法なのでやめた。
あとで別の記事にでもメモする。

この変更によってアプリの UI は以下のように変更される。

before

f:id:dackdive:20161116014935p:plain

after

f:id:dackdive:20161116014944p:plain

ちょっとだけそれっぽい UI になった。


おわりに

ということで React/Redux なアプリに Material-UI を導入することができた。
意外と事前準備としてやることが多く、またそれがドキュメントからは微妙にわかりづらかったりして思った以上に手こずった。


トラブルシューティング的な

ボタンを使うと Unknown prop `onTouchTap` on <button> tag. が出る

→react-tap-event-plugin が入っていない。


リファレンス

eslint-config-airbnbのルール内容を確認する

ESLint はあまり深く考えず eslint-config-airbnb を導入していて、そんなに問題はないんだけども
たまにこちらが期待したルールが無効になってたり、逆にそんなに守らなくてもいいと思われるルールが有効になってたりして、ふと

「あれ、xxx っていうルールは今どういう設定になってるんだろう?」

と思うときがある。

で、eslint-config-airbnb の中身は実際どういうルールセットになっているのか確認してみた。


方法1:GitHub 上で直接確認する

この2つの rules を確認すれば良いと思われる。

が、複数のファイルに分かれていて、目的のルールにたどりつくのがめんどくさい。
git clone してローカルで grep するとか。


方法2:ESLint の --print-config オプションを使う

http://eslint.org/docs/user-guide/command-line-interface#print-config
によると ESLint には --print-config というオプションがあるらしく、

$ eslint --print-config file.js

というように通常の lint 実行時と同じく、対象ファイルを指定して実行する。

これを実行すると、対象ファイルに対して実際に lint チェックを行うかわりに
このファイルに対してどういった設定で lint チェックを行うか、設定(ルールセット)の方が出力される。

$ eslint --print-config index.js
{
  "globals": {
    "__dirname": false,
    "__filename": false,
    "arguments": false,
    "Buffer": false,
    "clearImmediate": false,
    ...
  "rules": {
    "accessor-pairs": 0,
    "array-callback-return": 2,
    ...
}

これを使えばいまどんなルールセットになっているのか知ることができる。

私は ESLint をグローバルインストールしていないので

$ ./node_modules/eslint/bin/eslint.js --print-config index.js | vim -R -

パスが面倒だけど↑のような感じで結果を Vim を開き、目的のルールを検索して設定内容を確認している。

--print-config は現在の設定内容を出力するので、当然のことながら .eslintrc に記述した内容は結果に反映される。
そういう意味ではオリジナルの eslint-config-airbnb のルールを確認する方法とはいえない。

[Vim]SyntasticによるESLintチェックが遅いのでNeomakeに乗り換えた

(2017/01/23追記)

この後 Flow を導入しようとしたら色々問題が発生したので、Neomake から ALE に乗り換えた。

(追記ここまで)


(2018/04/15追記)

現在、記事を書いた時と設定方法が変わっているようです。
こちらの方が最新の手順をまとめてくださっているので、ご参照ください。

(追記ここまで)


Vim の Syntax Checker として有名なのは Syntastic ですね。
最近は JavaScript を書くことが多いので、この Syntastic を使って ESLint のチェックをできるようにしていました。

上の記事に書いてある設定を行ったことで、Vim で常に lint チェックをかけられるようになったのは良かったんですが
1 個だけ不満があって、チェックのたびに操作がブロックされてしまうという問題がありました。

f:id:dackdive:20161023121542g:plain 正直こればっかりは我慢するしかないかなーと思ってたんですが、Neomake という Syntastic に替わるプラグインの存在を知ったので試してみたところ、かなり快適だったので紹介します。

なお、Vim でも NeoVim でもどっちでも動きました。
(動作確認した Vim のバージョンは 8.0.22)


インストール

プラグイン管理ツールを使っている場合、他のプラグインと同じようにインストールできます。
以下は、dein.vim を使っている場合の書き方です。

call dein#add('neomake/neomake')
autocmd! BufWritePost * Neomake " 保存時に実行する
let g:neomake_javascript_enabled_makers = ['eslint']

call dein#add('benjie/neomake-local-eslint.vim')

let g:neomake_error_sign = {'text': '>>', 'texthl': 'Error'}
let g:neomake_warning_sign = {'text': '>>',  'texthl': 'Todo'}

単純に動かすだけなら最初の3行だけでいいです。
残りの設定については以下の通り。


ESLint をグローバルインストールせずに使えるようにする

Syntastic でもやったアレ。プロジェクトごとにローカルインストールした ESLint だけで動くようにしたいものです。
同様のプラグインがありました。
https://github.com/benjie/neomake-local-eslint.vim

Neomake 本体と同様、インストールするだけで OK。

call dein#add('benjie/neomake-local-eslint.vim')


エラー行に表示されるアイコンをカスタマイズする

デフォルトで、エラーや警告のある行に表示されるアイコンはこんな感じ。

f:id:dackdive:20161023113225p:plain

カラースキームのせいもありますがあんまり目立ちませんね。
自分好みにカスタマイズしてみます。

:h naomake

でヘルプドキュメントを読むと、g:neomake_error_signg:neomake_warning_sign に設定するそうです。
また、デフォルトの設定は以下らしい。

let g:neomake_error_sign = {'text': '✖', 'texthl': 'NeomakeErrorSign'}
let g:neomake_warning_sign = {
     "\   'text': '⚠',
     "\   'texthl': 'NeomakeWarningSign',
     "\ }
let g:neomake_message_sign = {
     "\   'text': '➤',
     "\   'texthl': 'NeomakeMessageSign',
     "\ }

ESLint では error と warning を使ってるっぽいので、こうします。

let g:neomake_error_sign = {'text': '>>', 'texthl': 'Error'}
let g:neomake_warning_sign = {'text': '>>',  'texthl': 'Todo'}

text がカーソル行に表示される文字列で、どちらも Syntastic のときと同じ >> にしています。
texthlVim で定義済みのハイライトグループ(?) からグループ名を指定します。

このあたりはあんま詳しくないので、

:highlight

でグループ一覧を開き、Syntastic のときと同じ見た目になりそうな ErrorTodo を選びました。

f:id:dackdive:20161023114002p:plain

設定後の見た目。エラー行見やすくなった。

f:id:dackdive:20161023114101p:plain


使う

f:id:dackdive:20161023121728g:plain

伝わりづらいけど、保存時に lint チェックを実行しても編集処理がブロックされなくなりました。

また、Syntastic と同じく、エラー行にカーソルを持っていくと下部にエラー内容が表示されるだけでなく、

:lopen

でエラー一覧を location list で確認することもできます。(location list 内で Enter キーを押すと該当エラー行にジャンプできます)

f:id:dackdive:20161023122004p:plain

Reactでフォームの項目をどう扱うか問題

メモ。

なんの話か

  • React でフォーム項目を簡潔に書く方法がわからない
  • 管理されたコンポーネントで書こうとすると、項目の数だけ state と対応するイベントハンドラが必要になる
    • 親に渡す必要がある、とかだとさらにしんどい
// 基本形
class MyFormCmp extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      name: '',
    };

    this.onChangeName = this.onChangeName.bind(this);
  }

  onChangeName(e) {
    console.log(e.target.value);
    this.setState({ name: e.target.value });
  }

  render() {
    return (
      <form>
        <input type="text" value={this.state.name} onChange={this.onChangeName} />
      </form>
    );
  }
}


どうすべきか

いくつか方法はある。
1 と 2 は微妙に違うが、「イベントハンドラを1つにまとめる」という戦略自体は同じ。

1. onChange イベントハンドラに引数で key を渡す
export default class MyFormCmp extends React.Component {

  constructor(props) {
    this.state = {
      name: '',
    };

    this.onChangeField = this.onChangeField.bind(this);
  }

  onChangeField(e, key) {
    console.log(key, e.target.value);
    this.setState({ [key]: e.target.value });
  }

  render() {
    return (
      <form>
        <input
          type="text" value={this.state.name} onChange={(e) => this.onChangeField(e, 'name')} />
      </form>
    );
  }
}


2. イベントハンドラ内で event.target.name を参照する

参考:ES2015 以降で React 書くなら form 部品での onChange で setState するのもう全部これでいいんじゃないかなあ - BattleProgrammerShibata

export default class MyFormCmp extends React.Component {
  // 略

  onChangeField(e) {
    console.log(e.target.name, e.target.value);
    this.setState({ [e.target.name]: e.target.value });
  }

  render() {
    return (
      <form>
        <input
          name="name"
          type="text"
          value={this.state.name}
          onChange={this.onChangeField}
        />
      </form>
    );
  }
}


3. (deprecated) LinkedStateMixin というアドオンを使う

参考:Two-Way Binding Helpers | React (日本語)

冒頭で

ReactLink is deprecated as of React v15. The recommendation is to explicitly set the value and change handler, instead of using ReactLink.

と書いているので使わないと思うけど。

加えて、ES2015 で書いた React コンポーネントはそのままでは Mixin を使うことができないので
react-mixin というライブラリを使う。

参考:React v0.13から使えるようになったES6のclass構文でmixinを使う - Qiita

import React from 'react';
import reactMixin from 'react-mixin';
import { render } from 'react-dom';
import LinkedStateMixin from 'react-addons-linked-state-mixin';

class MyFormCmp extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      name: '',
    };
  }

  render() {
    return (
      <form>
        <input
          type="text"
          valueLink={this.linkState('name')}
        />
      </form>
    );
  }
}
reactMixin(MyFormCmp.prototype, LinkedStateMixin);


4. (未検証) react-jsonschema-form を使う

元々は JSON Schema という、JSON オブジェクトの値の有無や型のチェックをするための仕様があって
react-jsonschema-form は JSON Schema からフォームを生成できるライブラリっぽい。

以下、JSON Schema の例。properties がフォームの項目に相当。

{
  "title": "A registration form",
  "description": "A simple form example.",
  "type": "object",
  "required": [
    "firstName",
    "lastName"
  ],
  "properties": {
    "firstName": {
      "type": "string",
      "title": "First name"
    },
    "lastName": {
      "type": "string",
      "title": "Last name"
    },
    "age": {
      "type": "integer",
      "title": "Age"
    },
    "bio": {
      "type": "string",
      "title": "Bio"
    },
    "password": {
      "type": "string",
      "title": "Password",
      "minLength": 3
    }
  }
}

実際にはこれとは別に UISchema なるものの定義が必要らしいが、ここから生成されるフォームはこんな感じ。
react-jsonschema-form playground で確認できる)

f:id:dackdive:20161019212542p:plain

いかんせんまだ触ったことがないので、スタイルやバリデーションルール含めどれぐらいきめ細かく設定ができるのか不明。


おわりに

3 は現在推奨されてないので除外。4 が一番気になってる。

1 と 2 については正直差はないと思う。
(JSX 側で state の key を知ってないといけない、という点で)

また、1 ~ 3 の方法は「入門 React」にも書いてた。(p.81 ~ p.84 あたり)

npm installしたパッケージの更新確認とアップデート(npm-check-updates)

タイトルの通り。
npm install --save なり --save-dev なりして package.json に書き込まれたパッケージのバージョン、どうやって定期的にアップデートしていけばいいかわからなかったので。

新しいバージョンがリリースされているかどうかの確認と、実際にどのように新しいバージョンにアップデートすればいいのか調べてみた。

今回サンプルに使う package.json

package.json の例として、以前、React のチュートリアルをやったときのリポジトリを使う。

{
  "name": "react-es6-tutorial",
  "version": "1.0.0",
  "description": "React Tutorial written in ES6",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "lint": "eslint src/**/*.js",
    "webpack": "webpack -w",
    "start": "concurrent \"npm run webpack\" \"npm run lite\""
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-core": "^6.7.2",
    "babel-loader": "^6.2.4",
    "babel-preset-es2015": "^6.6.0",
    "babel-preset-react": "^6.5.0",
    "concurrently": "^2.0.0",
    "eslint": "^2.9.0",
    "eslint-config-airbnb": "^9.0.1",
    "eslint-plugin-import": "^1.8.0",
    "eslint-plugin-jsx-a11y": "^1.2.0",
    "eslint-plugin-react": "^5.1.1",
    "webpack": "^1.12.14"
  },
  "dependencies": {
    "jquery": "^2.2.2",
    "marked": "^0.3.5",
    "react": "^0.14.7",
    "react-dom": "^0.14.7"
  }
}

この package.json に記載された通りのバージョン(babel-core なら 6.7.2)がインストールされた状態、という前提で話を進める。


現在インストールされているバージョンを確認する方法

そもそも、現在インストールされているバージョンを確認するにはどうしたらいいのか。
これは、後述する npm outdated コマンドを使うこともできるが、別の方法として

$ npm list --depth=0

コマンドを使うという方法もある。

$ npm list --depth=0
react-es6-tutorial@1.0.0 /Users/yamazaki/workspace/react/react-tutorial
├── babel-core@6.7.2
├── babel-loader@6.2.4
├── babel-preset-es2015@6.6.0
├── babel-preset-react@6.5.0
├── concurrently@2.0.0
├── eslint@2.9.0
├── eslint-config-airbnb@9.0.1
├── eslint-plugin-import@1.8.0
├── eslint-plugin-jsx-a11y@1.2.0
├── eslint-plugin-react@5.1.1
├── jquery@2.2.2
├── marked@0.3.5
├── react@0.14.7
├── react-dom@0.14.7
└── webpack@1.12.14

後述する npm outdated はバージョンが古くなったパッケージしか表示されないのに対し、
こちらはインストール済みのすべてのパッケージの情報が出力できる。


最新バージョンがあるかどうか確認する方法

インストールしたパッケージに新しいバージョンが存在するかどうか、確認する。
これは、

$ npm outdated

を使う。
https://docs.npmjs.com/cli/outdated

このコマンドを実行すると、以下のような結果が出力される。

f:id:dackdive:20161009200339p:plain

$ npm outdated
Package                 Current  Wanted  Latest  Location
babel-core                6.7.2  6.17.0  6.17.0  babel-core
babel-loader              6.2.4   6.2.5   6.2.5  babel-loader
babel-preset-es2015       6.6.0  6.16.0  6.16.0  babel-preset-es2015
babel-preset-react        6.5.0  6.16.0  6.16.0  babel-preset-react
concurrently              2.0.0   2.2.0   3.1.0  concurrently
eslint                    2.9.0  2.13.1   3.7.1  eslint
eslint-config-airbnb      9.0.1   9.0.1  12.0.0  eslint-config-airbnb
eslint-plugin-import      1.8.0  1.16.0   2.0.0  eslint-plugin-import
eslint-plugin-jsx-a11y    1.2.0   1.5.5   2.2.2  eslint-plugin-jsx-a11y
eslint-plugin-react       5.1.1   5.2.2   6.3.0  eslint-plugin-react
jquery                    2.2.2   2.2.4   3.1.1  jquery
marked                    0.3.5   0.3.6   0.3.6  marked
react                    0.14.7  0.14.8  15.3.2  react
react-dom                0.14.7  0.14.8  15.3.2  react-dom
webpack                 1.12.14  1.13.2  1.13.2  webpack

各パッケージに対し、Current, Wanted, Latest という3つの列が表示されている。
それぞれの列の意味は以下の通り。

Current

現在インストールされているバージョン

Wanted

存在するバージョンのうち、package.json に記載された semver の条件を満たす最新のバージョン。
たとえば、jQuery については

Package                 Current  Wanted  Latest  Location
jquery                    2.2.2   2.2.4   3.1.1  jquery

となっているが、これは 2.x 系の最新は 2.2.4 がリリースされており、それよりメジャーバージョンが1つ上の 3.x 系で 3.1.1 がリリースされている。
ただし、package.json^2.2.2 という記述では

2.2.2 <= n < 3.0.0

という範囲での最新バージョンしか許容されないため、Wanted は 3.x 系ではなく 2.x 系の最新バージョンとなる。

^2.2.2 という記述については以前ブログに書いた。
package.jsonのパッケージバージョンに記載される ^ (キャレット) とは?どうしてつくのか? - dackdive's blog


Latest

npm outdated のドキュメント によると

latest is the version of the package tagged as latest in the registry.

とあるが、そのパッケージの最新バージョンと考えてよさそう。


npm outdated の問題点

さて、この状態で npm update を実行し、再度 npm outdated で結果を確認してみる。

$ npm update
npm WARN peerDependencies The peer dependency react@^0.14.8 included from react-dom will no
npm WARN peerDependencies longer be automatically installed to fulfill the peerDependency
npm WARN peerDependencies in npm 3+. Your application will need to depend on it explicitly.
marked@0.3.6 node_modules/marked
babel-loader@6.2.5 node_modules/babel-loader

...(略)


$ npm outdated
Package                 Current  Wanted  Latest  Location
concurrently              2.2.0   2.2.0   3.1.0  concurrently
eslint                   2.13.1  2.13.1   3.7.1  eslint
eslint-config-airbnb      9.0.1   9.0.1  12.0.0  eslint-config-airbnb
eslint-plugin-import     1.16.0  1.16.0   2.0.0  eslint-plugin-import
eslint-plugin-jsx-a11y    1.5.5   1.5.5   2.2.3  eslint-plugin-jsx-a11y
eslint-plugin-react       5.2.2   5.2.2   6.3.0  eslint-plugin-react
jquery                    2.2.4   2.2.4   3.1.1  jquery
react                    0.14.8  0.14.8  15.3.2  react
react-dom                0.14.8  0.14.8  15.3.2  react-dom

babel-core などのパッケージは最新の 6.17.0 にアップデートされたため、表示されなくなった。

ただし、ESLint などについてはメジャーバージョンが上がったものがリリースされているが、npm update は package.json に記載された semver のルールに従うため、^ つきの記載だとメジャーバージョンのアップデートまでは行ってくれない。

また、npm outdated は パッケージの更新確認はやってくれるが、package.json の更新まではやってくれない という問題もある。
そのため、

  • npm outdated で新しいバージョンがリリースされてないか確認する
  • 新しいバージョンがリリースされていた場合、該当のパッケージを package.json から削除する
  • npm install --save/--save-dev で再度インストールする

という手順になってしまい、ややめんどくさい。
どうするか。


npm-check-updates を使う

というわけで色々調べていると、更新確認とアップデートに便利な npm-check-updates というパッケージを見つけた。

npm-check-updates は

$ npm install -g npm-check-updates

というようにグローバルにインストールする。インストールすると

$ ncu

というコマンドが使えるようになる。

ncu を実行してみる。

$ ncu
⸨░░░░░░░░░░░░░░░░░░⸩ ⠦ :
 jquery                   ^2.2.2  →   ^3.1.1
 react                   ^0.14.7  →  ^15.3.2
 react-dom               ^0.14.7  →  ^15.3.2
 concurrently             ^2.0.0  →   ^3.1.0
 eslint                   ^2.9.0  →   ^3.7.1
 eslint-config-airbnb     ^9.0.1  →  ^12.0.0
 eslint-plugin-import     ^1.8.0  →   ^2.0.0
 eslint-plugin-jsx-a11y   ^1.2.0  →   ^2.2.3
 eslint-plugin-react      ^5.1.1  →   ^6.4.0

The following dependencies are satisfied by their declared version range, but the installed versions are behind. You can install the latest versions without modifying your package file by using npm update. If you want to update the dependencies in your package file anyway, use ncu -a/--upgradeAll.

 marked                 ^0.3.5  →   ^0.3.6
 babel-core             ^6.7.2  →  ^6.17.0
 babel-loader           ^6.2.4  →   ^6.2.5
 babel-preset-es2015    ^6.6.0  →  ^6.16.0
 babel-preset-react     ^6.5.0  →  ^6.16.0
 webpack              ^1.12.14  →  ^1.13.2

Run ncu with -u to upgrade package.json

内容としては npm outdated を実行したときと同じ。
上部(The following... より上に列挙されているもの)は、新しいメジャーバージョンがリリースされているもの。
下部はマイナーバージョン以下で新しいバージョンが存在するもの。The following... の文章に書いてあるとおり、こちらは npm update すれば最新のバージョンがインストールできる。

そして、ncu に -u オプションをつけると package.json の更新が行われる。

$ ncu -u

...

Upgraded /Users/yamazaki/workspace/react/react-tutorial/package.json

$ git diff package.json
diff --git a/package.json b/package.json
index 11e9a27..f3e6979 100644
--- a/package.json
+++ b/package.json
@@ -17,18 +17,18 @@
     "babel-loader": "^6.2.4",
     "babel-preset-es2015": "^6.6.0",
     "babel-preset-react": "^6.5.0",
-    "concurrently": "^2.0.0",
-    "eslint": "^2.9.0",
-    "eslint-config-airbnb": "^9.0.1",
-    "eslint-plugin-import": "^1.8.0",
-    "eslint-plugin-jsx-a11y": "^1.2.0",
-    "eslint-plugin-react": "^5.1.1",
+    "concurrently": "^3.1.0",
+    "eslint": "^3.7.1",
+    "eslint-config-airbnb": "^12.0.0",
+    "eslint-plugin-import": "^2.0.0",
+    "eslint-plugin-jsx-a11y": "^2.2.3",
+    "eslint-plugin-react": "^6.4.0",
     "webpack": "^1.12.14"
   },
   "dependencies": {
-    "jquery": "^2.2.2",
+    "jquery": "^3.1.1",
     "marked": "^0.3.5",
-    "react": "^0.14.7",
-    "react-dom": "^0.14.7"
+    "react": "^15.3.2",
+    "react-dom": "^15.3.2"
   }
 }

package.json の更新が正しく行われた。

なお、ncu コマンドは

$ ncu -u [パッケージ名]

というようにパッケージ名を指定することができる。さらに、パッケージ名の部分には正規表現を使用することができる。

# babel-xxx というパッケージのみ対象
$ ncu /babel-/
⸨░░░░░░░░░░░░░░░░░░⸩ ⠏ :
The following dependencies are satisfied by their declared version range, but the installed versions are behind. You can install the latest versions without modifying your package file by using npm update. If you want to update the dependencies in your package file anyway, use ncu -a/--upgradeAll.

 babel-core           ^6.7.2  →  ^6.17.0
 babel-loader         ^6.2.4  →   ^6.2.5
 babel-preset-es2015  ^6.6.0  →  ^6.16.0
 babel-preset-react   ^6.5.0  →  ^6.16.0


また、ncu -c だと babel-core などマイナーバージョン以下がアップデートされたパッケージについては package.json は更新されなかったが、

$ ncu -a

というように、-u のかわりに -a オプションをつけて実行すると、これらのパッケージについても package.json が更新される。


注意点

npm-check-updates は package.json の更新のみ行い、実際のアップデートは行われていないので注意。
ncu -u した後 npm update する必要がある。


リファレンス

Dreamforce'16 Developer Keynoteのメモ

今年は行けなかったので Dev Keynote だけざっと見ました。

以下、雑多なメモ。

キーワード

  1. Lightning
  2. Einstein
  3. Salesforce DX


Lightning

Winter'17 以降の新機能についてデモしながら一通り紹介、といった感じ。
(デモは 13:40 頃から)

f:id:dackdive:20161007134941p:plain

Lightning Data Service

Visualforce で言う Standard Controller みたいな感じ。
Apex 側のコントローラーいらずでオブジェクトの参照や更新ができる。

f:id:dackdive:20161007135421p:plain

デモで紹介していたのは <force:recordPreview> というタグだったけど、
開発者ガイド見る限りいくつか種類があるっぽい。

リリースノート:
https://releasenotes.docs.salesforce.com/en-us/winter17/release-notes/rn_lightning_data_service.htm

開発者ガイド:
https://developer.salesforce.com/docs/atlas.en-us.204.0.lightning.meta/lightning/data_service.htm

JS のコントローラーではこのように書ける。
<force:recordPreview> コンポーネントを Id で見つけてきて、saveRecord() で保存)

f:id:dackdive:20161007135512p:plain


Lightning Action

Classic で言うカスタムボタンを追加して、そこから Lightning Component を表示できる。

f:id:dackdive:20161007135054p:plain

わかりづらいけどカーソルのある「Smart Home」がそう。


Lightning Utility Bar

f:id:dackdive:20161007135858p:plain

画面下部からいつでも呼び出すことのできる Lightning Component。かな?

所感


Einstein

機械学習プラットフォームの Einstein。

f:id:dackdive:20161007140033p:plain

デモがよくわからなかったんだけど、Apache Kafka と組み合わせてデータを学習モデルに放り込んでごにょごにょ...という感じ。

Apache Kafka については mokamoto さんの記事を読もう。

http://qiita.com/mokamoto/items/f453275efe6f056c98fb

KafkaはApache Foundationで開発されているpub-sub形式で分散メッセージング処理を取り扱うためのオープンソースミドルウェアです。 Producerと呼ばれるメッセージの送り手が、Kafkaの中にあるTopicに対してメッセージを送ります。Brokerはそのメッセージのハンドリング及び永続化を行い、ConsumerはTopicを購読してそのメッセージを受け取ることができます。

f:id:dackdive:20161007140203p:plain

train と predict だけ用意したら簡単に機械学習ができるのかな。

デモアプリ、これかと思ったけどたぶん違う。
http://www.dreamhouseapp.io/kafka/


Salesforce DX

f:id:dackdive:20161007140735p:plain

ようやく CI/CD 系の機能について発表がありました。
"Open and standard developer experience" とはまさに。

デプロイだけでなく組織の作成やデータのインポートなども行える CLI が提供されるっぽい。
sfdx ?? Force CLI はどうなるんでしょう。

f:id:dackdive:20161007140742p:plain

なんかいっぱいコマンドある。


デモでは、

f:id:dackdive:20161007140751p:plain

組織作成や

f:id:dackdive:20161007140755p:plain

組織にメタデータをプッシュ

のほか、組織からデータをエクスポートするといったこともやってました。

ただ、この機能については既に Twitter 上でいろいろ言われてるようなので
あまり期待しすぎずに詳細を待ちたいと思います。


おわりに:ロードマップ

f:id:dackdive:20161007140800p:plain