「Vue.js入門」の没ネタ

Twitter で以外と読みたいというリアクションがあったので、没ネタせっかくなのでブログ記事で公開します!

「Vue.js 入門」読まれた方はご存知かと思いますが、Appendix の所で Flow はコラムとして取り上げられています。実はあそこはコラムではなく、 TypeScript といっしょに型システムについてセクションとして書かれていました。

というわけで、コラムになる直前の内容を若干この Medium のブログ記事で読みやすく体裁を整えながらもありのままの状態で以降で公開します!

結構長いので、流し読み的な感じで読んで雰囲気を掴んで貰えればと!

Flow

Flow は Facebook 社が開発した JavaScript 向け静的型チェックツールです。

Flow は一般的には AltJS として位置付けられていますが、既存の JavaScript コードに対して型情報を埋め込み、そのコードに対して静的に型チェックを行うことで検証するため、まさに Flow 公式が謳うチェックツールといえます。

Flowの特徴

Flow の特徴としては主に以下が挙げられます。

段階的に導入できる型注釈

Flow は、JavaScript コードに型情報を注釈(型注釈: type annotation)として埋め込むことによって、型チェックを可能にします。Flow の型チェックによりアプリケーションコードを堅牢にすることができ、かつスマートな実装コードにすることができます。

例えば、与えられた数値の自乗して返す以下のような単純なsquare関数があるとしましょう。

function square (n) {
return n * n
}
// `2`が自乗された結果`4`が出力される
square(2)
// `NaN`が出力される
console.log(square('2'))

このコードはピュアな JavaScript で実装されたコードですが、square関数の引数には数値以外の値も渡せてしまうため、数値以外の値を同関数の引数に渡して実行すると期待しない動作になります。このように JavaScript コードを期待する動作にさせるためには、変数や引数などに対して値や型を JavaScript コードでチェックしなければなりません。

Flow の型注釈を変数や引数などに埋め込むことで、このような煩わしいJavaScript コードによる煩わしいチェック処理を Flow によって静的に型チェックすることで省略化することができます。以下は、先のsquare関数の引数に対して、数値しか渡せないよう型注釈したコードです。

/* @flow */// 引数に対して`number`(数値)で型注釈。戻り値も型注釈することができる
function square (n: number): number {
return n * n
}
// 渡された値が`number`型であるため、型チェックによるエラーは発生しないsquare(2)// 渡された値が`string`(文字列)であるため、型チェックでエラーが発生するsquare('2')

Flow による型注釈の記法は、: (型名)のような形で書きます。Flow による型チェックを有効にするためには、JavaScript コードのファイルの先頭にマジックコメント/* @flow */(もしくは// @flow)が必要です。このマジックコメントがない JavaScript コードのファイルは、Flow による型チェックはデフォルトではチェックしない挙動になっているため(注釈1)、JavaScript で書かれた既存のアプリケーションに対して段階的に導入することができます。

注釈1: 設定次第ではマジックコメントがなくても Flow の型チェックするようデフォルトの挙動を変更することができます。

強力な型推論

Flow は OCaml で実装されているため型推論が強力です。Flow では型注釈しない場合は、どのような値も扱うことができるany型として解釈しますが、型注釈されていない JavaScript コードにおいても、型チェック時にJavaScript コードを解析してコードにおいてエラーが発生する箇所を検出することができます。例えば以下のように、マジックコメントで型チェックを有効にすることで、Flow の型推論によってエラーを検知することができます。

/* @flow */function square (n) {
return n * n // <- Flowがこの変数`n`部分でエラーが発生するのを検出
}
square(1)
square('2')
square(null)

開発プロジェクトへの Flow 導入

Flow は、Vue.js を利用したアプリケーション開発プロジェクトへ導入することができます。Flow の導入方法としてはいくつかありますが、Vue.js でアプリケーションを開発する場合は、単一ファイルコンポーネント(.vue)を利用するのが一般的であるため、これに対して Flow による型チェックを対応できる必要があります。

以降では、vue-cliwebpack-simpleテンプレートで構築された初期状態のアプリケーション開発環境に対してFlowの導入について解説します。第8章ではwebpackテンプレートを利用した開発について解説しましたが、これから解説する内容については、第8章ににおいてwebpackテンプレートで構築した開発環境においても応用できるはずです。

インストール

Vue.js を利用したアプリケーション開発プロジェクトにおいて Flow を導入するためには、以下の NPM で公開されているツール、ライブラリなどをインストールします。

$ npm install --save-dev flow-bin
$ npm install --save-dev flow-typed
$ npm install --save-dev babel-preset-flow-vue
$ npm install --save-dev babel-eslint
$ npm install --save-dev eslint
$ npm install --save-dev eslint-plugin-vue
$ npm install --save-dev eslint-plugin-flowtype-errors

flow-binは型チェックするための Flow バイナリ本体がラップされたものです。

flow-typedは外部ライブラリの Flow 型定義を利用するための CLI ツールです。flow-typedによって外部ライブラリの Flow 型定義が管理されているレポジトリから必要な型定義をダウンロードして型チェックのために使用します。インストールされる型定義は、node_modulesにインストールされたモジュールから必要な型定義を、型定義レポジトリからダウンロードしてで解決します。管理レポジトリに型定義がなければ自動生成して全てany型として扱います。

babel-preset-flow-vueは Flow 関連の Babel プラグインを含んだ Vue.js 向けの Babel プリセットです。このプリセットは、Flow で実装されたコードを Babel で JavaScript コードにトランスパイルするために必要です。

babel-eslintは、Babel でトランスパイル可能な JavaScript コードを解析するパーサーを ESLine 向けにラップしたものです。webpack-simpleテンプレート(webpackテンプレートも)で構築された開発プロジェクトにおける単一ファイルコンポーネントのscriptブロックの JavaScript コードは Babel によってトランスパイルされます。単一ファイルコンポーネントのscriptブロックで Flow の構文で型付けされた JavaScript コードを型チェックするために必要です。

eslintは JavaScript コードがある規約に則って書かれているかどう検証するリントツールです。残念ながら執筆時点において、Flow 単体では単一ファイルコンポーネントの.vueファイルに対して型チェックできません(注釈2)。ESLint は Flow 向けに対応した ESLint プラグインがあるため、このプラグインを利用することで型チェックできるようになるため、インストールしています。

注釈2: Flow の GitHub リポジトリの Issues にあるダーティーハックな方法か、src属性で Flow の構文で実装されたコードを引き込むようにすれば可能ですが、コードの可読性や単一ファイルコンポーネントの利点が失われるためお薦めしません。https://github.com/facebook/flow/issues/2691

eslint-plugin-vueは Vue.js 公式でサポートする単一ファイルコンポーネントをリントするための ESLint プラグインです。このプラグインは、主に単一ファイルコンポーネントのtemplateブロックのテンプレートをリントするためのものですが、scriptブロックの JavaScript コードも他のパーサーを指定することでリントできるようになっています。ESLint 向けに提供されるbabel-eslintをパーサーとして指定することによって、scriptブロックで Flow の構文で実装されたコードを型チェックできるようになります。

eslint-plugin-flowtype-errorsは、Flow の型チェックで検出したエラーをリントのエラーとして検出するための ESLine プラグインです。このプラグインは、Flow の構文で実装されたコードをflow-binで型チェックして検出されたエラーを ESLint のエラーとして出力します。

Flow 導入に伴う設定

Flowで型チェックをするために必要なライブラリ、ツールなどをいくつかインストールしましたが、アプリケーションコードの型チェックするためには、いくつか設定が必要です。

Flow の設定

アプリケーションコードを Flow で型チェックをするためには Flow の設定が必要です。

以下のようにflow-binによってインストールされた CLI ツールflowで、Flow の設定ファイルである.flowconfigを生成します。

$ npx flow init

ここでは、NPM にバンドルされたnpxによって NPM によってローカルにインストールされたflow-binコマンドを実行しています。

生成された Flow の設定ファイル.flowconfigの内容を以下のように編集します。

[ignore]
.*/node_modules/.*
[include][libs][lints][options]
module.file_ext=.jsmodule.file_ext=.vue
[strict]

[ignore]セクションには、Flow の型チェックを無視するために、node_modulesを設定します。これは Flow はデフォルトで.flowconfigが配置されたルートから全てのファイルを探索して型チェックするため、アプリケーションコード以外の本質ではない部分も型チェックすることで大量のエラーが出力されるのを防ぐためです。

[options]セクションでは、型チェックの対象となるファイルの拡張子として、JavaScript ファイルである.jsと単一ファイルコンポーネントファイルの.vueを登録しています。ここで.vueを登録しないと単一ファイルコンポーネントのscriptブロックも Flow の型チェックの対象に入らないので設定が必要です。

他に様々なセクションがありますが、この解説で使用する開発環境は他の設定は必要ないため、Flow の設定は以上になります。

他のセクションについては、Flow 公式ドキュメントの.flowconfigフォーマットについて確認することができます。

Babel の設定

Flow の構文で実装されたアプリケーションコードは、そのままではブラウザでは動作しません。このため Flow の型注釈を取り除く必要があります。

本解説の開発環境では Babel を利用しているため、Babel の Flow 向けのプリセットによってインストールされたプラグインによって取り除くことができます。

これを実現するためには、Babel の設定ファイルである.babelrcに以下のようにインストールしたプリセット名flow-vueを追加するだけです。

 {
"presets": [
["env", { "modules": false }],
- "stage-3"
+ "stage-3",
+ "flow-vue"
]
}

ESLint の設定

Flow の構文で実装されたアプリケーションコードを ESLint 経由で型チェックするために以下のように ESLint の設定します。

module.exports = {
root: true,
plugins: [
'vue',
'flowtype-errors'
],
parserOptions: {
parser: 'babel-eslint'
},
extends: [
'plugin:vue/base'
],
rules: {
'flowtype-errors/show-errors': 2
}
}

pluginsには、ESLint のプラグインのeslint-plugin-vueeslint-plugin-flowtype-errorsのプラグイン名(eslint-pluginを取った形式)’vue’’flowtype-errors’を設定します。これにより単一ファイルコンポーネントをこれら ESLint のプラグイン経由で Flow の型チェックを実行することができます。

parserOptions.parser’babel-eslint’をパーサーとして設定します。これは単一ファイルコンポーネントのscriptブロックのコードを Flow で型チェックするためです。このパーサーの指定がなければ単一ファイルコンポーネントを Flow で型チェックすることができません。

extendsには’plugin:vue/base’を設定します。extendsで指定できるルールはeslint-plugin-vueではいくつか提供していますが、この解説では一番ゆるいルールを設定しています。

rules’flowtype-errors/show-errors’: 2を追加します。これにより Flow による型チェックで検出したエラーを ESLint のエラーとして検出することができます。

型チェックするタスクの追加

設定の最後として、Flow の型チェックを実行するコマンドをnpm-scriptのタスクとして以下のようにpackage.jsonに追加します。

 {
...
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
- "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
+ "build": "cross-env NODE_ENV=production webpack --progress --hide-modules",
+ "lint": "eslint --ext .js,.vue src"
},
...
}

Flow の型チェックは、ESLint 経由で実行するため、lintタスクとして追加しています。

外部ライブラリの型定義ファイルを導入する

アプリケーション開発では Vue.js 本体、Vue Router などの公式ライブラリ以外に、NPM で公開されている様々な JavaScript の外部ライブラリをインポートして開発します。そのため、Flow で型チェックするためにインポートする外部ライブラリをの型を解決する必要があります。

以下のように、flow-typedコマンドで外部ライブラリの型定義を導入することができます。

$ npx flow-typed install

インストールが完了すると、以下のようにプロジェクト直下にflow-typedディレクトリ配下に型定義ファイルが生成されます。

.
├── src.
├── flow-typed
│ └── npm
│ ├── webpack_vx.x.x.js
│ ├── webpack-dev-server_vx.x.x.js
│ ├── vue_vx.x.x.js
│ ├── vue-template-compiler_vx.x.x.js
│ ├── vue-loader_vx.x.x.js
│ ├── flow-typed_vx.x.x.js
│ ├── flow-bin_v0.x.x.js
│ ├── file-loader_vx.x.x.js
│ ├── eslint_vx.x.x.js
│ ├── eslint-plugin-vue_vx.x.x.js
│ ├── eslint-plugin-flowtype-errors_vx.x.x.js
│ ├── css-loader_vx.x.x.js
│ ├── cross-env_vx.x.x.js
│ ├── babel-preset-stage-3_vx.x.x.js
│ ├── babel-preset-flow-vue_vx.x.x.js
│ ├── babel-preset-env_vx.x.x.js
│ ├── babel-loader_vx.x.x.js
│ ├── babel-eslint_vx.x.x.js
│ └── babel-core_vx.x.x.js
.

上記のように、flow-typednode_modulesディレクトリにインストールされている型定義ファイルを生成します。

Flowで型チェックを実行する

Flow で型チェックするための設定がひと通り完了したので、Flow で型チェックできるかどうか確認してみましょう。

以下のように、本解説の開発環境に配置されたコードに Flow の型注釈とマジックコメントを埋め込みます。

`src/main.js`:
+/* @flow */
import Vue from 'vue'
import App from './App'

+const x: number = 'hello'
+console.log(x)

new Vue({
el: '#app',
render: h => h(App)
})
`src/App.vue`:
<template>
...
</template>

<script>
+/* @flow */
export default {
name: 'app',
data () {
+ const x: number = 'hello'
return {
msg: 'Welcome to Your Vue.js App'
}
}
}
</script>

<style>
...
</style>

編集が済んだら、npm run lintで型チェックを実行してみましょう。筆者の環境では以下のように出力されます。

> flow-app-simple@1.0.0 lint /Users/path/to/flow-app-simple
> eslint --ext .js,.vue src
/Users/path/to/flow-app-simple/src/App.vue
27:23 error string: This type is incompatible with 'number'. See line 27 flowtype-errors/show-errors
/Users/path/to/flow-app-simple/src/main.js
5:19 error string: This type is incompatible with 'number'. See line 5 flowtype-errors/show-errors
✖ 2 problems (2 errors, 0 warnings)

JavaScript ファイルだけでなく、単一ファイルコンポーネントも ESLint 経由で Flow で型チェックできているのをコンソールの出力から確認できます。

以上で、Flow を利用して Vue.js のアプリケーションを開発可能な環境を構築しました。後は、Flow の構文を利用してアプリケーションを実装するだけです。

制限事項

これまでの解説で Vue.js のアプリケーション開発環境に Flow を導入ることで、アプリケーションコードを型チェックすることが可能になりましたが、いくつか制限事項があります。

生成された Vue.js 関連の型定義

flow-typedによって、Vue.js 本体、Vue.js 関連ライブラリの型定義が生成することで Flow による型チェックを導入することが可能になりました。

しかしながら、flow-typedで外部ライブラリ向けの型定義を管理するためのレポジトリに Vue.js 公式で提供していません。また、Vue.js 本体、Vue.js 関連ライブラリにおいてflow-typedによって型定義が自動生成されるようFlow で実装していないため、flow-typedディレクトリ配下に生成されたVue.js 関連の型定義は全てany型として定義されています。

このため、アプリケーションコードにおいて、Vue.js の API を利用したコードにおいて型注釈の恩恵を受けることができません。(注釈3)

注釈3: 筆者はflow-typedによる Vue.js の API の型定義を公開できるようサポートしようとしましたが、いくつか Flow に機能が足りないため、Facebook 社側でも Flow にその機能をサポートする気配がないため、現在ペンディング状態となっています。https://github.com/vuejs/vue/pull/5027

こうした状況から、Vue.js のアプリケーション開発においては、現時点ではアプリケーション固有のコードのみに Flow の型チェックを利用することを推奨します。

エディタ

一般的に、静的型付き言語のような型をサポートするような言語では、エディタなどの IDE において型の補完により生産性の高い開発が可能になります。Flow においても、Visual Studio Code、Atom-IDE、そして Web Storm などの型を補完できるエディタ、IDE によって生産性の高い開発をすることが可能です。

Vue.js においては前項でも解説したとおり、Flow 向けに型定義がサポートされていないため、これらエディタ、IDE において Vue.js の API の型の補完ができません。どうしても型の補完による生産性の高い開発を望む場合は、Flow ではなく以降で解説する TypeScript を利用することを推奨します。

いかがでしたでしょうか?

Flow で Vue.js アプリケーションの開発は、かなりいばらの道感満載です!

なぜ、Flow がコラム扱いになってしまったかは、Vue.js で特に Flow を使ったことがある人はお分かりでしょう!

自分の Flow 使ってて辛さは実感していたのですが、執筆してみるとさらに辛くて。。。

ちなみに、レビュー時にもこんな感じで指摘され、

GitHub レビューコメント

追い打ちで

追い打ち

そして、自分の手でコラム化した次第です。

トドメのコミット

そのときの様子が、Twitterにあります。

労力かけたものがボツになると結構悲しいものですが、よりよい書籍にするためには、仕方がないことですよね。

最後に

ここまで最後まで読んで頂いたみなさまで、もし「Vue.js入門」をまだ購入されていないようでしたら、ぜひ本書をご購入いただけますよう重ねて申し上げます!🙏

以上、没ネタでした。

nickname kazupon. software engineer & emojineer. vue.js core team member. vuejs-jp users group organizer 😺. wasm love ❤️, i18n enthusiast 🌐.

nickname kazupon. software engineer & emojineer. vue.js core team member. vuejs-jp users group organizer 😺. wasm love ❤️, i18n enthusiast 🌐.