Prettier に再入門する

2017年に登場し、あっという間にJavaScriptコードフォーマッタのスタンダードになったPrettier。

github.com

非常に簡単に導入できるのだが、それゆえに、よく分かっていなくても使えてしまう。
導入時に設定ファイルをコピペするだけでどうにかなってしまうし、一度導入してしまえば、触ることはあまりない。

それではいかんということで、復習を兼ねて、導入方法や設定方法を調べた。

まずはPrettier単独での使い方を書き、次にESLintと組み合わせて使う方法を書く。

環境は以下。

  • node
    • 10.9.0
  • npm
    • 6.2.0
  • eslint
    • 5.5.0
  • prettier
    • 1.14.2
  • eslint-config-prettier
    • 3.0.1
  • eslint-plugin-prettier
    • 2.6.2

Prettier を単独で使う

まずはインストール。

npm i -D prettier

次に、例としてsrc/index.jsを作り、以下の内容にする。

function hoge() {
  console.log(1);
    console.log(2);
}



hoge() || hoge() || hoge() || hoge() || hoge() || hoge() || hoge() || hoge() || hoge() || hoge();

インデントがバラバラだし、ムダな改行もある。一行も長過ぎる。
このファイルに対してフォーマットをかける。

$ npx prettier src/だとエラーになるので、$ npx prettier src/*.jsを実行する

$ npx prettier src/*.js 
function hoge() {
  console.log(1);
  console.log(2);
}

hoge() ||
  hoge() ||
  hoge() ||
  hoge() ||
  hoge() ||
  hoge() ||
  hoge() ||
  hoge() ||
  hoge() ||
  hoge();

整形された結果が表示される。インデント、改行、一行の長さ、全部整理されている。
このように、特に何も設定しなくても適切な整形を行ってくれるのがPrettierの魅力。

先程のコマンドは修正結果を表示するだけなので、ファイルの上書き保存も行って欲しい場合は--writeオプションをつける。

$ npx prettier src/*.js --write
src/index.js 23ms

先程表示された内容で、実際に修正が行われる。

オプション

何も設定しなくても使えるPrettierだが、一部の設定は自分で変更することが出来る。

設定ファイルは.prettierrcJSONYAMLで書ける。
https://prettier.io/docs/en/configuration.html#docsNav

試しに、クォートの設定を変えてみる。

デフォルトだとダブルクォートを使うので、以下を対象に実行するとダブルクォートに変わる。

'hoge';
"fuga";
$ npx prettier src/*.js 
"hoge";
"fuga";

そこで、.prettierrcに以下のように記述して設定を変えてみる。

{
  singleQuote: true
}

こうすると、シングルクォートが使われる。

$ npx prettier src/*.js 
'hoge';
'fuga';

但し、JSXではこの設定は無視され、必ずダブルクォートが使われる。

Quotes in JSX will always be double and ignore this setting.

https://prettier.io/docs/en/options.html#quotes

オプションについての詳細はこのページで見れる。
https://prettier.io/docs/en/options.html

React

Reactのコンポーネントも、ちゃんと整形してくれる。

import React from 'react';

function myComponent() {
  return (
    <div>
    hoge
  </div>
  );
};
$ npx prettier src/component.js
import React from "react";

function myComponent() {
  return <div>hoge</div>;
}

もちろん--writeを使えば修正してくれる。

Vue

Vueファイルも対象になるが、templateの中身はあまりフォーマットしてくれなかった。

<template>
<div>
  {{greeting}}
<span>a</span></div></template>
<script>

export default {


data: function() {
    return {
  greeting: 'Hello'
    };
  }
}
</script>
$ npx prettier src/*.vue
<template>
<div>
  {{greeting}}
<span>a</span></div></template>
<script>
export default {
  data: function() {
    return {
      greeting: "Hello",
    };
  },
};
</script>

ESLint と組み合わせて使う

実際にはPrettierを単独で使うことは稀で、ESLintと組み合わせて使うのが一般的。
フォーマッタにはPrettierを使い、構文チェックやコーディングルールはESLintで行う。

ここでは、ESLintのなかでPrettierを使う方法を書く。

eslint-config-prettiereslint-plugin-prettierの2つを使うことでそれが可能になる。

eslint-config-prettier

github.com

eslint-config-で始まるコンフィグファイルを使うことで他人が書いた設定を取り入れることが出来るが、eslint-config-prettierは、Prettierと競合したりPrettierによって不要になるルールをオフにする設定。

Turns off all rules that are unnecessary or might conflict with Prettier.

これによって、ESLintとPrettierを共存させることが出来る。

$ npm i -D eslint-config-prettierした上で.eslintrcに次のように書く。

{
  "extends": ["prettier"]
}

この状態で$ npx eslint --print-config src/を実行すると、brace-stylecomma-styleなどの様々なルールが無効になっていることが分かる。

.eslintrcを以下のようにすると更に、"react/jsx-indent"などのReactに関するルールもオフになる。

{
  "extends": ["prettier", "prettier/react"]
}

eslint-plugin-prettier

github.com

ESLintでは、独自ルールの追加はプラグインで行う。

eslint-plugin-prettierを使うことで、ESLintでPrettierのルールが使えるようになる。

$ npm i -D eslint-plugin-prettierして.eslintrcを以下の内容にすると、Prettierのルールが有効になる。

{
  "plugins": [
    "prettier"
  ],
  "rules": {
    "prettier/prettier": "error"
  }
}
var obj = {
  hoge: 1,
  fuga: '2', // error  Replace `'2',` with `"2"`  prettier/prettier
};

オプションの設定も出来る。

{
  "plugins": [
    "prettier"
  ],
  "rules": {
    "prettier/prettier": ["error", {"singleQuote": true}]
  }
}
var obj = {
  hoge: 1,
  fuga: '2', // error  Delete `,`  prettier/prettier
};

prettier/recommended

プラグインでPrettierのルールを導入し、コンフィグファイルをextendsすることでESLint側の不要なルールをオフにしておく。
こうすることでESLintでPrettierを使えるようになるわけだが、実はこの設定は一行で書くことが出来る。

eslint-config-prettiereslint-plugin-prettierの両方をインストールした上で、.eslintrcを以下の内容にすればいい。

{
  "extends": ["plugin:prettier/recommended"]
}

これで、以下の効果がある。

This does three things:

  • Enables eslint-plugin-prettier.
  • Sets the prettier/prettier rule to "error".
  • Extends the eslint-config-prettier configuration.

https://github.com/prettier/eslint-plugin-prettier#recommended-configuration

余計なルールがオフになり、かつ、Prettierのルールがオンになる。

以下のように、オプションと組み合わせることも出来る。

{
  "extends": ["plugin:prettier/recommended"],
  "rules": {
    "prettier/prettier": ["error", {"singleQuote": true}]
  }
}

ESLint + Prettier で React をフォーマットする

Reactを対象にするためには、設定を加える必要がある。

{
  "extends": [
    "plugin:react/recommended",
    "plugin:prettier/recommended",
    "prettier/react"
  ],
  "rules": {
    "prettier/prettier": ["error", {"singleQuote": true}]
  },
  "parserOptions": {
    "sourceType": "module"
  }
}
  • "plugin:react/recommended"
    • React用のルールを追加する
  • "prettier/react"
    • その上で、Prettierと衝突するReact用のルールはオフにする
  • "sourceType": "module"
    • import/exportを使えるようにする

こうすると、Reactのコンポーネントに対してもESLintによるコーディングチェックとPrettierによるフォーマットを実行できるようになる。

import 'react';

function myComponent() {
  return (
    <div>
    hoge
  </div>
  );
};
4:10  error  Replace `(⏎····<div>⏎····hoge⏎··</div>⏎··)` with `<div>hoge</div>`  prettier/prettier
5:5   error  'React' must be in scope when using JSX                             react/react-in-jsx-scope
9:2   error  Delete `;`                                                          prettier/prettier

ESLint に再入門する

JavaScriptを書く上で必須のツールであるESLintだが、自分はあまりちゃんと理解していない。
最初に設定してしまえばその後はあまり手を加えないし、加えるときも都度調べて対症療法的に対応しているから、基礎は分かっていない。
取り敢えずairbnbextendesしておけば間違いない、くらいの理解。

だがESLintはコードを整理して綺麗にしていくための必須かつ強力なツールだし、新しい環境を作るときにいちいち躓くのも面倒なので、調べることにした。

Lintとは何か、何が素晴らしいか、みたいなことは書かない。個々のルールについても書かない。
ESLint自体の使い方、のような内容を書いていく。

環境は以下。

  • node
    • 10.9.0
  • npm
    • 6.2.0
  • eslint
    • 5.5.0

特に断りが無い限りsrc/に対象のJSファイルが入っている。
そうすると$ npx eslint src/でチェックできる。

rules

ルートディレクトリに.eslintrcを置き、そこにESLintの設定を書いていく。

個別のルールについては、rulesプロパティに書いていく。

例えば以下の設定だと、文末にセミコロンをつけないとエラーになる。errorwarnに変えると、エラーではなく警告になる。

{
  "rules": {
      "semi": ["error", "always"]
  }
}
// error  Missing semicolon  semi
1

/* eslint-disable *//* eslint-enable */で囲むと、その範囲だけESLintを無効に出来る。

/* eslint-disable */
1 // エラーにならない

/* eslint-enable */
1 // エラーになる

ESLintの実行時に--fixオプションをつけると自動的にコードを修正してくれる。自動では補完できないルールもある。

$ npx eslint --fix src/

現在有効になっているESLint設定を確認したい場合は、--print-configオプションを使う。

$ npx eslint --print-config src/
{
  "globals": {},
  "env": {},
  "rules": {
    "semi": [
      "error",
      "always"
    ]
  },
  "parserOptions": {}
}

env

よく使われているルールとして、no-undefがある。

{
  "rules": {
    "no-undef": "error"
  }
}

これは未定義の変数を使っていないかチェックしてくれる、非常に有用なルールである。これをオフにする理由は基本的にないだろう。

だが一つ問題があって、例えばdocumentオブジェクトを使おうとすると、エラーになってしまう。

// error  'document' is not defined  no-undef
document.querySelector('#app');

これは、documentオブジェクトはブラウザ環境でのみ使えるオブジェクトであり、JavaScriptそのものには定義されていないから発生している。

この問題を解決するために使うのが、envプロパティである。
これでbrowserを設定すると、ブラウザ環境固有のオブジェクトも問題なく使えるようになる。

{
  "env": {
    "browser": true
  },
  "rules": {
    "no-undef": "error"
  }
}

指定できる環境はbrowser以外にもたくさんある。
https://eslint.org/docs/user-guide/configuring#specifying-environments

parserOptions

ESLintではデフォルトでは、ES5の構文しか使えない。ES2015以降の構文を使うには設定を上書きする必要がある。

ESLint allows you to specify the JavaScript language options you want to support. By default, ESLint expects ECMAScript 5 syntax. You can override that setting to enable support for other ECMAScript versions as well as JSX by using parser options.

https://eslint.org/docs/user-guide/configuring#specifying-parser-options

具体的にはparserOptionsプロパティを設定できる。

まず、parserOptionsプロパティを何も設定せずに確認してみる。

// error  Parsing error: The keyword 'const' is reserved
const hoge = 1;

パースできていない。

parserOptions"ecmaVersion": 2015を設定することで、パースできるようになる。

{
  "rules": {
    "no-undef": "error"
  },
  "parserOptions": {
    "ecmaVersion": 2015
  }
}

これでES2015を使えるようになったかと思いきや、まだ問題がある。
parserOptionsはあくまでも構文解析に関するオプションなので、ES2015以降に追加された組み込みオブジェクトには対応していない。
そのため例えば、Mapは未定義とされてしまう。

// error  'Map' is not defined  no-undef
const hoge = Map;

これを解決するには、先程説明したenvプロパティにes6を設定すればいい。
ちなみにこの設定では構文解析も拡張してくれるので、parserOptionsは不要になる。

{
  "rules": {
    "no-undef": "error"
  },
  "env": {
    "es6": true
  }
}

但し、拡張するのはES2015までなので、最新の機能を使うにはparserOptionsが必要になる。
以下はオブジェクトの分割代入だが、パースできていない。

// error  Parsing error: Unexpected token ..
const { a, b, ...c } = { a: 1, b: 2, x: 3, y: 4, z: 5};

"ecmaVersion": 2018を指定する必要がある。

{
  "rules": {
    "no-undef": "error"
  },
  "env": {
    "es6": true
  },
  "parserOptions": {
    "ecmaVersion": 2018
  }
}

sourceType

ES2015+の構文のなかでもimport/exportだけは特別扱いであり、専用の設定が必要。
parserOptionsプロパティのなかで"sourceType": "module"を設定しないとパースできない。

"parserOptions": {
  "sourceType": "module"
}

parser

parserプロパティを使うことで、パーサーそのものを指定できる。
デフォルトではEspreeというパーサーが使われているが、これでは対応できない構文を使用する際に、他のパーサーを使う。

例えばFlow。

// error  Parsing error: Unexpected token :
function concat(a: string, b: string) {
  return a + b;
}

パース出来ずにエラーになる。

これを解消するには、パーサーにbabel-eslintを使用すればよい。

まずは使用したいパーサーのnpmパッケージをインストールする。

$ npm i -D babel-eslint

次に、.eslintrcでパーサーを指定する。

{
  "parser": "babel-eslint"
}

これで、Flowもパースできるようになる。
ただ、これは単にパースしているだけなので、Flowの文法についてESLintでチェックしたい場合は後述するプラグインという機能を使う必要がある。

ちなみに、babel-eslintはいろんな設定を自動的に付加してくれるようで、パーサーをbabel-eslintにするだけでMapimportなどを使えるようになる。

shareable configuration

shareable configurationという仕組みを使うことで、他人が書いた設定を導入できる。

extendesプロパティで指定できる。

まずは、ESLintが推奨している設定を導入してみる。

{
  "extends": "eslint:recommended"
}

個別のルールは何も設定していないが、いくつかのルールが有効になっている。
例えば、no-undefno-empty

// error  'foo' is not defined   no-undef
// error  Empty block statement  no-empty
if (foo) {
}

extendsしてきたルールの一部を上書きすることも可能。

{
  "extends": "eslint:recommended",
  "rules": {
    "no-undef": "off",
    "no-empty": "warn"
  }
}
// warning  Empty block statement  no-empty
if (foo) {
}

shareable configurationがnpmパッケージとして公開されていれば、自由にそれを導入できる。
例として、広く使われている設定であるeslint-config-airbnbを導入してみる。

github.com

先程のeslint:recommendedrulesを設定するだけだったが、eslint-config-airbnbではenvsourceTypeの設定も行ってくれるため、自分で行う設定がかなり少なくて済むようになる。

例えば以下のコードをESLintの対象にするためには、ここまで説明してきたように様々な設定が必要になる。
デフォルトではconstexportをパースできないし、Mapnot definedになる。

console.log(Map);
const { a, b, ...c } = {
  a: 1, b: 2, x: 3, y: 4, z: 5,
};
export default { a, b, c };

だがeslint-config-airbnbを使えば、ただそれを継承するだけで、上記のコードが動くようになる。

まずはインストール。
コマンドラインで以下を実行する。

(
  export PKG=eslint-config-airbnb;
  npm info "$PKG@latest" peerDependencies --json | command sed 's/[\{\},]//g ; s/: /@/g' | xargs npm install --save-dev "$PKG@latest"
)

次に.eslintrcを編集。

{
  "extends": "airbnb"
}

この設定だけで、ESLintが先程のコードを理解してくれるようになる。

plugins

プラグインは、ESLintのルールを追加する仕組み。
既存のルールをオン・オフするのではなく、独自のルールを追加できる。
ReactやVueのような特定のライブラリや環境のためのルールを追加するのが、主な目的。
先程少し触れたFlowのためのプラグインもある。

github.com

github.com

github.com

例として、eslint-plugin-reactを使ってみる。

まずはチェック対象であるJSファイルを作る。

import 'react';

function myComponent() {
  return (
    <div>hoge</div>
  );
};

そしてnpmパッケージをインストールした上で、.eslintrcを設定する。

$ npm i -D eslint-plugin-react
{
  "plugins": [
    "react"
  ],
  "extends": ["plugin:react/recommended"],
  "parserOptions": {
    "sourceType": "module"
  }
}

"extends": ["plugin:react/recommended"]とすることで、プラグインが推奨するルールが適用され、さらにパーサーの設定も行われるので、JSXがパースされるようになる。

$ npx eslint src/
  5:5  error  'React' must be in scope when using JSX  react/react-in-jsx-scope

自分で設定を追加したり上書きしたりも、もちろん出来る。
例えば以下のようにすると、react/react-in-jsx-scopeのエラーは消える。

{
  "plugins": [
    "react"
  ],
  "extends": ["plugin:react/recommended"],
  "parserOptions": {
    "sourceType": "module"
  },
  "rules": {
    "react/react-in-jsx-scope": "off"
  }
}

Vueの場合はeslint-plugin-vueをインストールした上で、次のようにすればいい。

{
  "plugins": [
    "vue"
  ],
  "extends": ["plugin:vue/essential"]
}

一点だけ注意すべきなのが、ESLintはデフォルトでは.js拡張子のみが対象であるということ。
そのため、.vueはそもそもESLintの対象にならない。
.js以外も対象にするためには--extオプションを使う。

.js.vueを対象にしたい場合は次のようにすればよい。

$ npx eslint --ext .js,.vue src/

参考資料