[Emacs] Typescript+JSXをなるべくweb-modeで編集するための設定

# はじめに
ReactやTypescriptなどについて調べていると，この界隈にはVSCodeユーザが多いように感じられます．Emacsユーザにとっては少し肩身が狭いです．

Emacsを使った React + JSX の開発において，Javascriptを使う場合は`rjsx-mode`や`js-mode`でおおむね事足ります．しかし，Typescriptを使う場合は，このような既存のモードでは少し不十分です．

私はこれまで`rjsx-mode`や`web-mode`，`typescript-mode`などを切り替えてやりくりしていましたが，これは一筋縄では行きません．例えば，`rjsx-mode`で引数の型指定をした部分から先がすべてエラーになったり，`web-mode`に切り替えた途端にカラーテーマが適用されなくなるようなことが起きてしまいます．

そこで，Typescript + JSXの編集をなるべく`web-mode`で行うことができるようにするべく，その設定を行いました．

なお，私の環境ではカラーテーマを`tango-dark`としています．その他のテーマでは，もしかすると必要に応じて最後の設定を直す必要があるかもしれません．

# 設定内容
`~/.emacs.d/init.el`に以下の設定を追記しました．

```elisp:~/.emacs.d/init.el
;; web-mode settings for TSX
(add-to-list 'auto-mode-alist '("\\.[jt]sx\\'" . web-mode))
(defun custom-web-mode-hook ()
  (setq web-mode-attr-indent-offset nil)
  (setq web-mode-markup-indent-offset 2)
  (setq web-mode-css-indent-offset 4)
  (setq web-mode-code-indent-offset 4)
  (setq indent-tabs-mode nil)
  (setq tab-width 2)
  (setq web-mode-enable-current-element-highlight t)
  (let ((case-fold-search nil))
    (highlight-regexp "\\_<number\\|string\\|boolean\\|enum\\|unknown\\|any\\|void\\|null\\|undefined\\|never\\|object\\|symbol\\_>" 'font-lock-type-face)))
(add-hook 'web-mode-hook 'custom-web-mode-hook)
;; Inherit colors from font-lock
(custom-set-faces
 '(web-mode-doctype-face
   ((t :inherit font-lock-doc-face)))
 '(web-mode-html-tag-face
   ((t :inherit font-lock-function-name-face)))
 '(web-mode-html-attr-name-face
   ((t :inherit font-lock-variable-name-face)))
 '(web-mode-html-attr-value-face
   ((t :inherit font-lock-string-face)))
 '(web-mode-comment-face
   ((t :inherit font-lock-comment-face)))
 '(web-mode-server-comment-face
   ((t :inherit font-lock-comment-face)))
 '(web-mode-javascript-comment-face
   ((t :inherit font-lock-comment-face)))
 '(web-mode-json-comment-face
   ((t :inherit font-lock-comment-face)))
 '(web-mode-error-face
   ((t :inherit font-lock-warning-face)))
 '(web-mode-current-element-highlight-face
   ((t :inherit font-lock-builtin-face)))
 '(web-mode-html-tag-bracket-face
   ((t :inherit font-lock-negation-char-face)))
 '(web-mode-block-delimiter-face
   ((t :inherit font-lock-negation-char-face)))
 '(web-mode-javascript-string-face
   ((t :inherit font-lock-string-face)))
 '(web-mode-json-key-face
   ((t :inherit font-lock-keyword-face)))
 '(web-mode-json-string-face
   ((t :inherit font-lock-string-face)))
 '(web-mode-keyword-face
   ((t :inherit font-lock-keyword-face)))
 '(web-mode-param-name-face
   ((t :inherit font-lock-variable-name-face)))
 '(web-mode-preprocessor-face
   ((t :inherit font-lock-preprocessor-face)))
 '(web-mode-string-face
   ((t :inherit font-lock-string-face)))
 '(web-mode-type-face
   ((t :inherit font-lock-type-face)))
 '(web-mode-variable-name-face
   ((t :inherit font-lock-variable-name-face)))
 '(web-mode-function-call-face
   ((t :inherit font-lock-function-name-face)))
 '(web-mode-function-name-face
   ((t :inherit font-lock-function-name-face)))
 '(web-mode-warning-face
   ((t :inherit font-lock-warning-face)))
 '(web-mode-css-color-face
   ((t :inherit font-lock-reference-face)))
 '(web-mode-css-rule-face
   ((t :inherit font-lock-function-name-face)))
 '(web-mode-css-pseudo-class-face
   ((t :inherit font-lock-function-name-face)))
 '(web-mode-css-at-rule-face
   ((t :inherit font-lock-keyword-face))))
```

# 説明

## web-modeの有効化
まず，以下の設定によって`.jsx`もしくは`.tsx`のファイルを開いたとき`web-mode`を有効にしています．

```elisp
(add-to-list 'auto-mode-alist '("\\.[jt]sx\\'" . web-mode))
```

## インデントの設定
以下はインデントの設定です．`rjsx-mode`と近く※ なるように，JSXのみインデント幅が小さくなるように設定しています．

（※ あなたのEmacsでは，`rjsx-mode`のインデント幅が違うかもしれません）

```elisp
  (setq web-mode-attr-indent-offset nil)
  (setq web-mode-markup-indent-offset 2)
  (setq web-mode-css-indent-offset 4)
  (setq web-mode-code-indent-offset 4)
  (setq indent-tabs-mode nil)
  (setq tab-width 2)
```

## 対応するタグをハイライトする設定
以下は，JSXを編集する際に対応するタグをハイライトするための設定です．ハイライトされる色 (face) は，`web-mode-current-element-highlight-face`です．最後に他の色と合わせて設定しています．

```elisp
  (setq web-mode-enable-current-element-highlight t)
```

## 型をハイライトする設定
以下は，Typescriptの型をハイライトするための設定です．正規表現の部分を編集することで，ハイライトする型を増やすことができます．ハイライトされる色 (face) は，`font-lock-type-face`としています．

なお，バッファ内で一致した単語をすべてハイライトするため，コメントに含まれるものなどであってもハイライトされます．ただし，`case-fold-search`をセットしていることから，大文字・小文字は区別されます．

```elisp
  (let ((case-fold-search nil))
    (highlight-regexp "\\_<number\\|string\\|boolean\\|enum\\|unknown\\|any\\|void\\|null\\|undefined\\|never\\|object\\|symbol\\_>" 'font-lock-type-face))
```

## カラーテーマを（おおむね）適用する設定
以下は，使用中のカラーテーマの色（正確には，font-lockの色）をおおむね適用するための設定です．`web-mode`が定義する色 (face) の一部を，`font-lock`が定義する色 (face) に書き換えています．

なお，`web-mode`や`font-lock`が定義する色 (face) は，`M-x list-faces-display`で確認することができます．また，バッファ中のカーソルがある部分の色 (face) は，`C-u C-x =`で確認することができます．これらの情報は，以下の設定を変更する上で，とても役に立つでしょう．

```elisp
(custom-set-faces
 '(web-mode-doctype-face
   ((t :inherit font-lock-doc-face)))
 '(web-mode-html-tag-face
   ((t :inherit font-lock-function-name-face)))
 '(web-mode-html-attr-name-face
   ((t :inherit font-lock-variable-name-face)))
 '(web-mode-html-attr-value-face
   ((t :inherit font-lock-string-face)))
 '(web-mode-comment-face
   ((t :inherit font-lock-comment-face)))
 '(web-mode-server-comment-face
   ((t :inherit font-lock-comment-face)))
 '(web-mode-javascript-comment-face
   ((t :inherit font-lock-comment-face)))
 '(web-mode-json-comment-face
   ((t :inherit font-lock-comment-face)))
 '(web-mode-error-face
   ((t :inherit font-lock-warning-face)))
 '(web-mode-current-element-highlight-face
   ((t :inherit font-lock-builtin-face)))
 '(web-mode-html-tag-bracket-face
   ((t :inherit font-lock-negation-char-face)))
 '(web-mode-block-delimiter-face
   ((t :inherit font-lock-negation-char-face)))
 '(web-mode-javascript-string-face
   ((t :inherit font-lock-string-face)))
 '(web-mode-json-key-face
   ((t :inherit font-lock-keyword-face)))
 '(web-mode-json-string-face
   ((t :inherit font-lock-string-face)))
 '(web-mode-keyword-face
   ((t :inherit font-lock-keyword-face)))
 '(web-mode-param-name-face
   ((t :inherit font-lock-variable-name-face)))
 '(web-mode-preprocessor-face
   ((t :inherit font-lock-preprocessor-face)))
 '(web-mode-string-face
   ((t :inherit font-lock-string-face)))
 '(web-mode-type-face
   ((t :inherit font-lock-type-face)))
 '(web-mode-variable-name-face
   ((t :inherit font-lock-variable-name-face)))
 '(web-mode-function-call-face
   ((t :inherit font-lock-function-name-face)))
 '(web-mode-function-name-face
   ((t :inherit font-lock-function-name-face)))
 '(web-mode-warning-face
   ((t :inherit font-lock-warning-face)))
 '(web-mode-css-color-face
   ((t :inherit font-lock-reference-face)))
 '(web-mode-css-rule-face
   ((t :inherit font-lock-function-name-face)))
 '(web-mode-css-pseudo-class-face
   ((t :inherit font-lock-function-name-face)))
 '(web-mode-css-at-rule-face
   ((t :inherit font-lock-keyword-face))))
```