In this post, I show how to setup Emacs for TypeScript and React (tsx) development, with tree-sitter for syntax highlighting and indentation, and LSP with the TypeScript compiler (including a plugin for faster eslint), via eglot, for code intelligence.
First some background on tree-sitter
Tree-sitter is a surprising and slightly revolutionary recent development (2017) in the world of code editors. It is both a parser generator tool and an incremental parsing library that works for multiple programming languages.
Up until recently (well, until tree-sitter came along), the norm for interactive code editors was to use a mishmash of regular expressions in order to understand source code well enough to be able to perform syntax highlighting quickly enough for interactive use.
In addition to being able to parse a file from scratch really quickly (the parser generator produces dependency-free C code, which helps), incremental parsing means that tree-sitter is able to transform changes to the file to incremental updates of the parsed syntax tree really efficiently.
In practice, this means that syntax highlighting can be done much more accurately than with regular expressions, and can be updated continuously as you type.
Screenshots of the end-result
Configure tree-sitter in Emacs for general use
Although there is an effort underway to integrate tree-sitter with Emacs core, you can already start using this functionality through the tree-sitter Emacs package.
(The current norm in Emacs is the bag-of-regular-expressions mentioned above. I really hope that tree-sitter will take over as the default soon.)
At this point, you can use tree-sitter for faster and more accurate syntax highlighting in Emacs, but having a full always-up-to-date syntax tree available will enable more broadly powerful techniques such as structural editing.
By adding the code below to your init, you’ll automatically get tree-sitter syntax highlighting for all supported languages:
Ensure for TSX, configure for tree-sitter-based indentation
The following configuration will ensure that tree-sitter’s dedicated
will be used for tsx (typescript + react) files. By default this currently is
not the case, as it uses the
typescript parser which does not understand the
Here we create a new derived mode that will map to both
.ts. Due to
the derived mode’s name, the typescript language server will select tsx
support, and due to the the explicit mapping, tree-sitter will select its tsx
The code below shows how to do this with use-package and quelpa, as tsi.el is not (yet) on melpa.
Bonus: Auto-format all the things
I recently discovered apheleia (thank you
/r/emacs/!), an emacs package that
uses different formatting tools such as prettier and black to format your code
on save, and then apply various clever algorithms to ensure that your cursor
stays in the same place relatively speaking.
Install and activate as follows:
typescript-language-server with eslint, LSP and eglot for code intelligence
Although I used to use lsp-mode, these days I am really enjoying the simplicity
of eglot, and especially the fact that it relies as far as possible on standard
Emacs facilities such as
Below is the sum total of my eglot configuration:
Once that’s done, we need some system-wide configuration, most importantly the
typescript-language-server that is invoked when you
M-x eglot on a file that
Local project configuration
Importantly, we have to install and configure the typescript-eslint-language-service in the local project dependencies for runtime eslint checking. This service re-uses the AST (parsed syntax tree) generated by the typescript compiler itself to check eslint rules.
First the installation of the dev dependency:
Then add the following to the
tsconfig.json to enable the
service (merge with any existing plugins):
I have prepared a small demo repo that has all of the local bits already setup: typescript-emacs-demo
To get up and running, clone the project and then do:
With all of the above in place, you should be able to open
Emacs, then do
M-x eglot to activate for the whole project.
After some moments, you should see
flymake in the modeline with note, warning
and error counts. Invoke
M-x flymake-show-buffer-diagnostics to activate the
buffer with eslint results.
To see the difference tree-sitter makes in this case, toggle off with
Bonus: Additional details about the typescript language service
The following architecture layer diagram, found in this github comment (also read its informative text), helps to understand the design and operation of the typescript language service:
ps, you can confirm that eglot starts up the global
typescript-language-server, which on its part invokes the
lives inside your project and is directly connected to its TypeScript compiler