Chad Stovern

My blog is in danger of becoming only about Emacs with a dash of musings on JavaScript, but I think it's important to document my findings after working solely in Emacs as a Software Engineer for the past five months.

This is by no means a complete guide to getting set up to work as a JavaScript developer in Emacs, but should give you the tools you need to find your own path with some ease.

What's in a Mode?

First came my realization that I could use rjsx-mode for all JavaScript files. Since this mode is just an extension of the popular js2-mode, I dropped the logic from my configuration to try and figure out if a file contained React JSX or not. I have not had any issues using rjsx-mode for editing all JavaScript files.

(use-package rjsx-mode
  :mode ("\\.js\\'"
         "\\.jsx\\'")
  :config
  (setq js2-mode-show-parse-errors nil
        js2-mode-show-strict-warnings nil
        js2-basic-offset 2
        js-indent-level 2)
  (setq-local flycheck-disabled-checkers (cl-union flycheck-disabled-checkers
                                                   '(javascript-jshint))) ; jshint doesn't work for JSX
  (electric-pair-mode 1)
  (evil-leader/set-key-for-mode 'rjsx-mode
    "fu"  #'lsp-find-references          ; (f)ind (u)sages
    "fp" 'prettier-js-mode))             ; (f)ormat (p)rettier

Think Globally, Run Locally

The add-node-modules-path package is great for automatically detecting and using npm package binaries which are locally installed in the project you're actively editing, such as eslint, prettier, etc. This allowed me to no longer maintain similar functionality I had written myself as Emacs Lisp functions.

(use-package add-node-modules-path
  :defer t
  :hook (((js2-mode rjsx-mode) . add-node-modules-path)))

I Feel Pretty

On my current team we have settled on using ESLint with Prettier. Emacs has a great package that will automatically apply Prettier rules to the current buffer on save. By default it will use the Prettier config in the current project. I highly recommend this over setting global defaults in your Emacs config. This allows you to just write code and not worry about formatting. I added a shortcut of ,fp to toggle this behavior on and off in the event I don't want to apply Prettier rules.

(use-package prettier-js
  :defer t
  :diminish prettier-js-mode
  :hook (((js2-mode rjsx-mode) . prettier-js-mode))
  :init
  (evil-leader/set-key-for-mode 'rjsx-mode
    "fp" 'prettier-js-mode)) ; (f)ormat (p)rettier

You Complete Me

Most importantly, like my colleagues using Visual Studio Code (VSCode), I wanted full-featured code completion, function definition and usage lookups. lsp-mode works great for this. Just like VSCode, It uses Microsoft's TypeScript Language Server behind the scenes. In addition to the emacs setup you need to globally install those supporting tools.

npm i -g typescript-language-server; npm i -g typescript

I added some shortcuts for jumping to a definition, and then jumping back. In the JavaScript configuration above their is also a shortcut for "find usages" which is very handy when working in a code base of any size.

I highly recommend reading the configuration below, the embedded comments, and the lsp-mode documentation to get a feel for the options you may want. The main thing for me was not allowing lsp-mode to suppress and replace the detected default linter for a project.

(use-package lsp-mode
  :defer t
  :diminish lsp-mode
  :hook (((js2-mode rjsx-mode) . lsp))
  :commands lsp
  :config
  (setq lsp-auto-configure t
        lsp-auto-guess-root t
        ;; don't set flymake or lsp-ui so the default linter doesn't get trampled
        lsp-diagnostic-package :none)
  ;;; keybinds after load
  (evil-leader/set-key
    "jd"  #'lsp-goto-type-definition ; (j)ump to (d)efinition
    "jb"  #'xref-pop-marker-stack))  ; (j)ump (b)ack to marker

(use-package company-lsp
  :defer t
  :config
  (setq company-lsp-cache-candidates 'auto
        company-lsp-async t
        company-lsp-enable-snippet nil
        company-lsp-enable-recompletion t))

(use-package lsp-ui
  :defer t
  :config
  (setq lsp-ui-sideline-enable t
        ;; disable flycheck setup so default linter isn't trampled
        lsp-ui-flycheck-enable nil
        lsp-ui-sideline-show-symbol nil
        lsp-ui-sideline-show-hover nil
        lsp-ui-sideline-show-code-actions nil
        lsp-ui-peek-enable nil
        lsp-ui-imenu-enable nil
        lsp-ui-doc-enable nil))

Parting Thoughts

I'm pretty happy with my setup so far, and haven't run into any issues or missing features compared to running VSCode. I'm also using TypeScript, which may spawn a follow-up post. Here is a link to the JavaScript / TypeScript section of my living Emacs Configuration.

Clojure is my favorite programming language, but JavaScript has become my second:

  • I can write mostly functional code.
  • I can avoid the complexity of classes using modules which are essentially namespaces.
  • I can get opt-in types and interfaces, similar to clojure.spec, via TypeScript.
  • I can get a REPL-lite experience via Jest in –watch mode; yet nothing beats a REPL.

If you made it this far, you may be as crazy as I am. Happy Hacking!