TypeScript development with Emacs, tree-sitter and LSP in 2022
Contents
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.
Update on 2022-12-27: emacs 29 has tree-sitter built-in!
Tree-sitter was merged into Emacs core on November 23 of 2022.
The post below is for the situation BEFORE the merge.
If you’re running Emacs 29 or later (as I am at the end of 2022), you should be using the built-in tree-sitter. Let me know if you would like to see a new post dealing with the new merged situation.
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 tsx
parser
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
tsx
extensions.
Here we create a new derived mode that will map to both .tsx
and .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
parser.
|
|
Thanks to Dan Orzechowski, we can configure the tsi.el package which will give us tree-sitter based indentation for TypeScript, JSON and (S)CSS.
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 eldoc
.
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
you’re editing:
|
|
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 compilerOptions
in 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
I used create-react-app for this, and so it comes with the default eslint rules for that system.
To get up and running, clone the project and then do:
|
|
With all of the above in place, you should be able to open src/App.tsx
in
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 M-x tree-sitter-hl-mode
.
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:
Using ps
, you can confirm that eglot starts up the global
typescript-language-server
, which on its part invokes the tsserver.js
that
lives inside your project and is directly connected to its TypeScript compiler
setup.