dackdive's blog

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

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

package.jsonのパッケージバージョンに記載される ^ (キャレット) とは?どうしてつくのか?

$ npm install --save react

などのコマンドでパッケージをインストールすると、package.json にはインストールした(その時点での最新)バージョンが記載されますが
そのとき

"dependencies": {
  "react": "^15.3.2"
}

というように、バージョン番号の前に ^ がつきます。

これの意味と、どうしてつくのか調べてみたメモ。

前提

Node.js のパッケージのバージョンは Semantic Versioning (semver) というルールに従っている。

semver は X.Y.Z という3桁の数字で表されるバージョンで、

  • X: major version。後方互換性のない変更の場合にこのバージョンを上げる
  • Y: minor version。後方互換性のある変更の場合、このバージョンを上げる
  • Z: patch version。後方互換性のあるバグ修正などの場合、このバージョンを上げる

というルールになっている。


^ (キャレット) とは?

このあたりを読む。
https://docs.npmjs.com/misc/semver#caret-ranges-123-025-004

Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple

パッケージのバージョン番号のうち、一番左の非ゼロの値を変更しないようなアップデートを許容する、という意味のよう。

つまり、

  • バージョンが 1.2.3 の場合、1.2.3 <= n < 2.0.0
  • バージョンが 0.2.3 の場合、0.1.2 <= n < 0.2.0
  • バージョンが 0.0.3 の場合、0.0.3 <= n < 0.0.4

というように、対象のバージョンによって挙動が異なる。

そして、npm install や npm update を行ったとき、許容される範囲の中で最新のバージョンをインストールしたりアップデートしたりする。
そのため、package.json だけだとバージョンを完全に固定できているわけではない


【補足】package.json でバージョンを完全に固定するには?

後述するように ^ をつけないように設定を変更するか、npm shrinkwrap コマンドを使って npm-shrinkwrap.json というファイルを生成しておく。
https://docs.npmjs.com/cli/shrinkwrap

// npm-shrinkwrap.json (例)
{
  "name": "react-es6-tutorial",
  "version": "1.0.0",
  "dependencies": {
    "jquery": {
      "version": "2.2.2",
      "from": "jquery@2.2.2",
      "resolved": "https://registry.npmjs.org/jquery/-/jquery-2.2.2.tgz"
    },
    "marked": {
      "version": "0.3.5",
      "from": "marked@0.3.5",
      "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.5.tgz"
    },
    "react": {
      "version": "0.14.7",
      "from": "react@0.14.7",
      "resolved": "https://registry.npmjs.org/react/-/react-0.14.7.tgz",
      "dependencies": {
        "envify": {
          "version": "3.4.1",
          "from": "envify@>=3.0.0 <4.0.0",
          "resolved": "https://registry.npmjs.org/envify/-/envify-3.4.1.tgz",
          "dependencies": {
            "through": {
       ...


【補足】~ (チルダ)

^ と似たような記号として、~ (チルダ) もある。
https://docs.npmjs.com/misc/semver#tilde-ranges-123-12-1

Allows patch-level changes if a minor version is specified on the comparator. Allows minor-level changes if not.

minor version が明記されていれば patch version のアップデートを許容、明記されていなければ minor version のアップデートを許容。
なので、

  • ~1.2.3 の場合: 1.2.3 <= n < 1.3.0
  • ~1.2 の場合: 1.2 <= n < 1.3.0
  • ~1 の場合: 1.0.0 <= n < 2.0.0


^ (キャレット) はなぜつくのか?

package.json にデフォルトで ^ がつく挙動はどこで設定しているのだろうか?気になって調べてみた。
どうやら npm config に関係する設定があるらしい。


save-prefix

デフォルトでつく prefix を設定する。

$ npm config get save-prefix
^

# デフォルトを ~ に変更
$ npm config set save-prefix "~"
$ npm config get save-prefix
~


save-exact

--save--save-dev したときに prefix をつけるか、あるいは prefix をつけずに package.json に記載してバージョンを完全に固定するかを選べる。
デフォルトは false。なので prefix がつく。

$ npm config get save-exact
false

# デフォルトで完全なバージョン固定になるようにする
$ npm config set save-exact true


【補足】npm config で設定した値

どうやらこの設定は ~/.npmrc に保存されているらしい。
.npmrc はプロジェクトごとに置くことも可能。
https://docs.npmjs.com/misc/config#npmrc-files


^ (キャレット) をつけずにインストールする方法はないのか?

上述したように、npm config で

$ npm config set save-exact true

と設定しておくと、npm install 時の挙動を変更することができる。

あるいは、npm install 時のオプションとしても

--save-exact (略記は -E)

が用意されている。
https://docs.npmjs.com/cli/install

これをつけると、^ なしでインストールできる。

# -SE は --save --save-exact の略
npm i -SE react

Hubotに管理画面のような静的ページを追加する

メモ。
ブラウザから /admin にアクセスしたら管理画面みたいなものが開いて Bot の簡単なカスタマイズができる、みたいなことがやりたくて
特定の URL で静的なページを返すようなことができるのか調べてみました。

結論から言うと robot.router が Express サーバーのインスタンスになっているようなので、この変数に対して通常の Express アプリ開発と同じようなノリでできそうです。

公式ドキュメントのこちらに記載があります。
https://hubot.github.com/docs/scripting/#http-listener

Hubot includes support for the express web framework to serve up HTTP requests. It listens on the port specified by the EXPRESS_PORT or PORT environment variables (preferred in that order) and defaults to 8080.

また、Hubot のソースコードで言うとこのあたり。
https://github.com/github/hubot/blob/master/src/robot.coffee#L411

サンプルコード

CoffeeScript ではなく ES2015 で書いています。
参考:HubotをES2015で書いてHerokuにデプロイする - dackdive's blog

module.exports = (robot) => {
  robot.router.get('/admin', (req, res) => {
    res.end('Hello, World!');
  });
};
結果

f:id:dackdive:20160806113925p:plain

表示できました。

注)
$ ./bin/hubot

を直接実行するとデフォルトでポートは 8080 番が使われますが、この時は

$ heroku local web

# Procfile は以下
web: bin/hubot -a slack --require build

コマンドを使い、Heroku アプリのローカル実行として実行しています。
heroku local コマンドでアプリを起動した時、デフォルトのポートは 5000 番なので キャプチャでは :5000 にアクセスしています。


ejs などのテンプレートエンジンも使ってみる

スクリプト内に HTML をゴリゴリ書いていくのはつらいので、JavaScript のテンプレートエンジンも使えるか試してみます。
Express とテンプレートエンジンの使い方についてはこちらの記事を参考にします。

ゼロからはじめるExpress + Node.jsを使ったアプリ開発 - Qiita

$ npm i --save ejs

でインストールします。

スクリプトとテンプレートファイルのディレクトリ構成は以下のようになっています。

.
├── Procfile
├── README.md
├── bin
│   ├── hubot
│   └── hubot.cmd
├── build ...................... src をトランスパイルしたスクリプト
│   └── index.js
├── external-scripts.json
├── package.json
├── src
│   └── index.js
└── views ...................... テンプレート置き場
    └── index.ejs
スクリプト&テンプレート
// src/index.js
module.exports = (robot) => {
  robot.router.set('view engine', 'ejs');

  robot.router.get('/admin', (req, res) => {
    res.render('index', {});
  });
};
<!-- views/index.ejs -->
<html>
<body>
    <h1>This is Admin Page!</h1>
</body>
</html>
結果

f:id:dackdive:20160806115757p:plain

できました。


今後の予定

まだ軽く試しただけなので、このまま通常の Express アプリ開発と同じように、JS ライブラリや UI フレームワークを使って静的サイトを構築していけるのかはわかりません。
あと、このページで Bot のメッセージ定型文を登録・管理できるように...とかしたくなったら、データの永続化方法とかも調べてみないといけませんね。

Persistence の項を読むと hubot.brain という KVS を提供しているみたいだけど、Heroku で使おうとするとどうなるのか。