ESLint をブラウザー内で利用する

# まえがき

----

**追記 (2018/05/24)**: ブラウザで動作するよう修正済みの Linter クラスを公開しています。

- [eslint4b](https://www.npmjs.com/package/eslint4b)

このパッケージを利用する事で、追加の修正無しに Webpack 等で Linter クラスを読み込むことができます。また、このパッケージは Travis CI の Cron 機能により最新の ESLint を元にしたビルドを継続的に作成・公開しています。

ご活用下さい。

----

ESLint は対外的にはブラウザー内での実行をサポートしていません ([eslint/eslint#2585](https://github.com/eslint/eslint/issues/2585), [eslint/eslint#8348](https://github.com/eslint/eslint/issues/8348))。しかしながら、公式サイトに設置されている [オンライン デモ](https://eslint.org/demo/) のために、少し細工すればブラウザー内で実行することができます。

この記事では、ESLint をブラウザー内で利用するための [webpack](https://webpack.js.org/) の設定について紹介します。

# ESLint の Node.js API

ESLint の Node.js API は大別して2つのクラスから構成されています。

<dl>
<dt>CLIEngine
<dd>
    主に使われるクラスです。<br>
    ファイルを検証する <code>engine.executeOnFiles(files)</code> や文字列を検証する <code>engine.executeOnText(code, filePath)</code> 等のメソッドを持ちます。これらのメソッドでは、対象ファイルのパスに基づいて設定ファイルを読み込み、設定にある共有設定やプラグインを検索して読み込みます。最後に、得られたコードと設定とプラグインを元に後述する <code>Linter</code> オブジェクトを作成し、検証を実施します。<br>
    ファイルシステムと密結合しているため、ブラウザー内では利用できません。
</dd>
<dt>Linter
<dd>
    検証を行う中核クラスです。<br>
    ブラウザー内部で利用するために、ファイルシステムに依存しない中核処理だけを抽出したものです。そのため設定ファイルの検索や共有設定・プラグインの解決はできません。<code>linter.defineRule(ruleId, definitionObject)</code> でルールを定義し、<code>linter.verify(code, config)</code> で検証を行います。
</dd>
</dl>

今回はもちろん `Linter` クラスを利用します。

# 依存パッケージをインストール

必要なパッケージをインストールします。

```bash
$ npm install --save-dev eslint webpack string-replace-loader
```

# webpack の設定

Webpack の設定を書きます。

先ほど `Linter` はファイルシステムに依存しない部分を抽出したものと言いましたが、残念ながら今は 1 カ所だけファイルシステムに依存しています。そのため [string-replace-loader](https://www.npmjs.com/package/string-replace-loader) で少し手を加えてやる必要があります。

```js:webpack.config.js
const fs = require("fs")
const path = require("path")
const webpack = require("webpack")

module.exports = {
    entry: "./index.js",
    output: {
        path: path.resolve(__dirname, "./dist"),
        publicPath: "/",
        filename: "index.js",
    },
    module: {
        rules: [
            // `eslint/lib/load-rules.js` は `fs` に依存してコアルールを列挙しているため、中身を静的コードに置き換える。
            {
                test: new RegExp(`eslint\\${path.sep}lib\\${path.sep}load-rules\\.js$`),
                loader: "string-replace-loader",
                options: {
                    search: "[\\s\\S]+", // whole file.
                    replace: "module.exports = () => ({})",
                    flags: "g",
                },
            },
        ],
    },
}
```

# サンプル コード

そして ESLint を利用するコードを書きます。

```js:index.js
// "eslint" を読み込むと CLIEngine が含まれてしまうため、内部ファイルを参照する必要がある。
import Linter from "eslint/lib/linter.js"
import semiRule from "eslint/lib/rules/semi.js"

// Linter を作る。
const linter = new Linter()

// 試しにてきとーなルールを定義してみる。
linter.defineRule("no-string", (context) => ({
    Literal(node) {
        if (typeof node.value === "string") {
            context.report({node, message: "Don't use string!"})
        }
    }
}))
// semi ルールも定義してみる。
linter.defineRule("semi", semiRule)

// 検証する
const code = "console.log('Hello!')"
const config = {
    rules: {
        // 上で定義したルールを有効にする設定。
        "no-string": "error",
        "semi": "error"
    }
}
const lintErrors = linter.verify(code, config)

// 表示する
console.log(lintErrors) //→ 結果は以下
```

```js:結果
[ { ruleId: 'no-string',
    severity: 2,
    message: 'Don\'t use string!',
    line: 1,
    column: 13,
    nodeType: 'Literal',
    source: 'console.log(\'Hello!\')',
    endLine: 1,
    endColumn: 21 },
  { ruleId: 'semi',
    severity: 2,
    message: 'Missing semicolon.',
    line: 1,
    column: 22,
    nodeType: 'ExpressionStatement',
    source: 'console.log(\'Hello!\')',
    fix: { range: [Array], text: ';' } } ]
```

# あとがき

以上、ESLint をブラウザー内で利用する方法でした。

今回は Webpack を利用しましたが、他のバンドラーを使っている場合でも特定のファイルの内容を置き換える仕組みがあれば利用可能です。

先日公開した [Playground for eslint-plugin-vue](https://mysticatea.github.io/vue-eslint-demo/) では、このような方法を用いて ESLint を利用しています。
あなたも ESLint を利用した Web アプリを作ってみましょう :smile: