search
LoginSignup
9

More than 1 year has passed since last update.

posted at

EmacsにおけるTypescript + React JSXの苦悩と良さげな設定について

これはEmacs Advent Calendar 2020 10日目の記事です。

前置き

この記事は淡々と問題を解決する記事ではなく、つらつら苦悩が描写されている。

Typescript + React JSX問題は現在進行形で解決案がドシドシ提案されている。これもその1つとなるといいな。

始まり

Emacsのコーディング体験は最高だ。だけど、Typescript + React JSXではいまいちだ。

去年まで会社で利用していたwebのフロントエンド開発はこれだ。

  • React
  • Redux
  • react-router-dom

Emacsのメージャーモードはrjsx-modeを使っていた。

ところが、その1年後の2020年8月。

社内で採用しているフロントエンド周りが大きく変わった。

  • Typescript
  • Next.js

そのためEmacsの構成を大きく見直す必要が出てきてしまった。

下のコードはこの記事で利用するサンプルのtsxファイル

import * as React from 'react'

export interface Props {
  name: string
  enthusiasmLevel?: number
  onIncrement?: () => void
  onDecrement?: () => void
}

function Hello({ name, enthusiasmLevel = 1, onIncrement, onDecrement }: Props) {
  if (enthusiasmLevel <= 0) {
    throw new Error('You could be a little more enthusiastic. :D')
  }

  return (
    <div className="hello">
      <div className="greeting">Hello {name + getExclamationMarks(enthusiasmLevel)}</div>
      <div>
        <button onClick={onDecrement}>-</button>
        <button onClick={onIncrement}>+</button>
      </div>
    </div>
  )
}

export default Hello

// helpers

function getExclamationMarks(numChars: number) {
  return Array(numChars + 1).join('!')
}

typescript-mode使えば良いんじゃないの?

typescriptのコードを書くなら、typescript-mode使えば良い。普通はそう考える。早速このような設定で、.tsxファイルを読み込んでみた。

M-x package-install
typescript-mode
(use-package typescript-mode
  :init
  (add-to-list 'auto-mode-alist '("\\.ts\\'" . typescript-mode))
  (add-to-list 'auto-mode-alist '("\\.tsx\\'" . typescript-mode))

  :config
  (setq typescript-indent-level 2)
  )

結果は明らかだ。typescript-modeはReact JSXをうまく表現できない。カラーリングとインデントがおかしくなる。

typescript-mode.jpg

それもそのはず、typescript-modeはあくまでtypescriptのためのモードであり、React JSXはサポート外なのである。

githubのissueを除くと、実は数年前から議論されていることがわかった。だが、決め手となる一手はまだ見つかってないらしい。

Support JSX/TSX #4

web-mode使えば良いんじゃないの?

そう、おなじみの最強web系モードweb-modeこれを使えば行けそうな気がする。

Googleでtypescript react eamcsとかでググるとweb-modeをメジャーモードにして利用できるという記事が散見される。

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

Emacs Typescript Setup for ReactJS, NextJS or NestJS

実際、私はこの数ヶ月はweb-modeベースの設定で開発を行ってきた。

M-x package-install
web-mode
(use-package web-mode
  :init
  (add-to-list 'auto-mode-alist '("\\.ts\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.tsx\\'" . web-mode))

  :config
  (setq web-mode-attr-indent-offset nil)

  (setq web-mode-enable-auto-closing t)
  (setq web-mode-enable-auto-pairing t)

  (setq web-mode-auto-close-style 2)
  (setq web-mode-tag-auto-close-style 2)


  (setq web-mode-markup-indent-offset 2)
  (setq web-mode-css-indent-offset 2)
  (setq web-mode-code-indent-offset 2)

  (setq indent-tabs-mode nil)
  (setq tab-width 2)
  )

web-mode.jpg

しかし、JSXの中でのタイピングに遅延が発生しているような?

最初は他のemacsパッケージが悪さしているのではと思って気にしていなかった。

だけど、疑惑は時間とともに確信に変わっていった。

web-modeでのタイピングどう考えても遅いやんけ!!

特にJSX部分が膨大になっていくと、その挙動の遅さは如実になった。

Githubのissueを探索すると、幾つか同じような問題を抱えている人のissueが上がっていた。

Editing JSX is generally very slow #1126

JSX slow input #1162

しかし、どれも解決には至ってなかった。issueに対するweb-mode開発者の返信も「俺の環境では再現できないからわからん」という感じでイマイチ噛み合っていない。

まあそうだよな。Emacsって自由すぎて、動作している環境がMac、Linux、Winなのか、CUIまたGUIとかバージョンとか人によって環境が違うわけであって、多分、開発者さんの環境ではうまくいっているんだろうなって。

もちろん、web-modeは特別パフォーマンスがどうこうというのはないので、ちょっとしたラグが気にならない方はこれでもやっていけると思う。

js-mode使えば良いんじゃないの?

ありがたいことに、Emacs27.1ではemacsに標準搭載されているjs-modeのマイナーモードで、js-jsx-modeというのがある。js-modeが動いている状態で、JSXの箇所にカーソルが移動すると、自動的にjsxに対応し、インデントやカラーハイライトが機能する。

もちろん、typescript用のものではないので、js-modeでtypescriptを編集するのは色々あれだが、タイピングにラグが生まれないのならと試してみた。

(add-to-list 'auto-mode-alist '("\\.ts\\'" . js-mode))
(add-to-list 'auto-mode-alist '("\\.tsx\\'" . js-mode))
(setq indent-tabs-mode nil)
(setq js-indent-level 2)

js-mode.jpg

使ってみると、web-modeの比ではないくらいにタイピング速度が壊滅的に遅い。遅い。遅すぎる。

こちらはEmacsの標準搭載機能なので、もしかすると、今後のバージョンアップで使いやすいものになる可能性はあると思う。だが、今は使い物にならない。

js2-mode使えばいいじゃないの?

そう、ならばjs-modeを拡張した、js2-modeはどうか。

githubのREADMEの記述をみると、

React and JSX

js-modeを起動してそのマイナーモードとしてjs2-minor-modeを利用するとReact Jsxあたりが良い感じになるとある。

M-x package-install
js2-mode
(use-package js2-mode
  :init
  (add-to-list 'auto-mode-alist '("\\.ts\\'" . js-mode))
  (add-to-list 'auto-mode-alist '("\\.tsx\\'" . js-mode))
  (add-hook 'js-mode-hook 'js2-minor-mode)

  :config

  (setq indent-tabs-mode nil)
  (setq js-indent-level 2)
  )

ただ、こちらはこちらで、typescriptと相性が悪く、多くの記述のエラーが出てしまった。

js2-mode.jpg

これではReact JSXどころの騒ぎではなく

mmm-modeでゴリ押すしかないじゃない!

このページのやり方を参考にした。

Typescript with CSS in JS (styled-components, Emotion), JSX and graphql highlight and major mode for Emacs Doom with mmm-mode for *.tsx

mmm-modeでReact Jsx以外の部分はtypescript-modeを動かして、Jsxの部分だけはweb-modeを使う。

M-x package-install
mmm-mode
(use-package typescript-mode
  :config
  (setq typescript-indent-level 2)
  (add-hook 'typescript-mode-hook
          (lambda ()
            (interactive)
            (mmm-mode)
            )))

(use-package mmm-mode
  :commands mmm-mode
  :mode (("\\.tsx\\'" . typescript-mode))
  :config
  (setq mmm-global-mode t)
  (setq mmm-submode-decoration-level 0)
  (mmm-add-classes
   '((mmm-jsx-mode
      :submode web-mode
      :face mmm-code-submode-face
      :front "\\(return\s\\|n\s\\|(\n\s*\\)<"
      :front-offset -1
      :back ">\n?\s*)\n}\n"
      :back-offset 1
      )))
  (mmm-add-mode-ext-class 'typescript-mode nil 'mmm-jsx-mode)


  (defun mmm-reapply ()
    (mmm-mode)
    (mmm-mode))

  (add-hook 'after-save-hook
            (lambda ()
              (when (string-match-p "\\.tsx?" buffer-file-name)
                (mmm-reapply)
                )))
  )

mmm-mode.jpg

元となった設定と違うところはまず、この3つだ。

  1. use-packageを使っている
  2. jsxの終わりを判定する正規表現が違う
  3. typescriptの設定もできるようになっている

2に関してはちょっと補足すると、元のサイトのコードではJsx中にif文を書くとweb-modeを抜けてしまうという問題があったので修正した。

{is_okey ? (
  <p>Good</p>
 ) : (
  <p>Bad</p>
 )}

この設定のメリット

何と言っても基本はtypescript-modeでかけるということ。またここで解説されているように、tideなどのtypescript-modeならではの拡張機能も使える。

Emacs Typescript Setup for ReactJS, NextJS or NestJS

そして、JSXの中での挙動が爆速である。

この設定のデメリット

JSXのカラーリングが崩れる。これはこの設定を紹介しているサイトでも書かれているが、mmmの弊害なのか、新しくJSXにコードを書いているとどうしても色が白くなってしまう。

それをなんとか修復するために、mmm-reapplyという関数で保存時にmmm-modeをオフ、オンしてカラーリングを初期化して対応している。

  (defun mmm-reapply ()
    (mmm-mode)
    (mmm-mode))

まとめ

カラーリングやその他諸々、満足には解決していないが、ひとまずタイピング速度が改善されればそれで良かったので今はこれで頑張っている。何か発見があったらまた随時紹介していきたい。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
9