---
title: Modern TypeScript and React Development in Vim
teaser: 'Learn how to imbue your vim setup with the power of the TypeScript language
  server.

  '
tags: vim,typescript,react,javascript
author: Wil Hall
published_on: 2020-03-23
---

When I started working in TypeScript and React, I found it challenging to continue using vim. I switched to Visual Studio Code because it was better suited for the task at hand. But what made it better boiled down to the language server integration which provided all the common code actions you would expect from an IDE such as automatic importing, symbol renaming, tool tip display of compiler and linter errors, and go-to type/definition/reference. I still missed the text editing power of vim.

The question became: could I have the best of both worlds and get all those features (and more) in vim?

## It all starts with the syntax

For the basics, I use [pangloss/vim-javascript] for JavaScript syntax, and [leafgarland/typescript-vim]. There are other options worth exploring, but these have served me well out of the box and are fairly configurable.

For working with JSX I use [MaxMEllon/vim-jsx-pretty], and for TSX I use [peitalin/vim-jsx-typescript]. `vim-jsx-pretty` does have TypeScript support, but I had some performance issues when highlighting large TSX files.

For projects that use [styled components], I use [styled-components/vim-styled-components] which highlights CSS inside the `styled` and `css` template strings.

Similarly, for projects that use [GraphQL], [jparise/vim-graphql] has been great for highlighting queries in `gql` template strings.

[pangloss/vim-javascript]: https://github.com/pangloss/vim-javascript
[leafgarland/typescript-vim]: https://github.com/leafgarland/typescript-vim
[MaxMEllon/vim-jsx-pretty]: https://github.com/MaxMEllon/vim-jsx-pretty
[peitalin/vim-jsx-typescript]: https://github.com/peitalin/vim-jsx-typescript
[styled components]: https://styled-components.com/
[styled-components/vim-styled-components]: https://github.com/styled-components/vim-styled-components
[jparise/vim-graphql]: https://github.com/jparise/vim-graphql

I install my plugins using [junegunn/vim-plug]:

```viml
Plug 'pangloss/vim-javascript'
Plug 'leafgarland/typescript-vim'
Plug 'peitalin/vim-jsx-typescript'
Plug 'styled-components/vim-styled-components', { 'branch': 'main' }
Plug 'jparise/vim-graphql'
```

[junegunn/vim-plug]: https://github.com/junegunn/vim-plug

### Highlighting for large files

Sometimes [syntax highlighting can get out of sync] in large JSX and TSX files. This was happening too often for me so I opted to enable `syntax sync fromstart`, which forces vim to rescan the entire buffer when highlighting. This does so at a performance cost, especially for large files. It is significantly faster in [Neovim] than in vim.

I prefer to enable this when I enter a JavaScript or TypeScript buffer, and disable it when I leave: 

```viml
autocmd BufEnter *.{js,jsx,ts,tsx} :syntax sync fromstart
autocmd BufLeave *.{js,jsx,ts,tsx} :syntax sync clear
```

[syntax highlighting can get out of sync]: https://vim.fandom.com/wiki/Fix_syntax_highlighting
[Neovim]: https://neovim.io/

## Going above and beyond with Coc

With a solid set of syntax highlighting in place, next up is to integrate the TypeScript language server. All the heavy lifting here is done by [Conquer of Completion] (often abbreviated Coc) -- a language server plugin for Neovim (and vim)! There are other great alternatives to Coc (most notably [ale]), but I prefer Coc for a couple of reasons:

1. It makes my vim _feel like an IDE_ (auto-import, tooltip docs and diagnostics, code actions, etc -- all out of the box)
1. It has an [incredible wealth of documented configuration options] and [a great wiki]
1. I use it for both linting and language server integration and they work seamlessly alongside each other

[Conquer of Completion]: https://github.com/neoclide/coc.nvim
[ale]: https://github.com/dense-analysis/ale
[incredible wealth of documented configuration options]: https://github.com/neoclide/coc.nvim/blob/master/doc/coc.txt
[a great wiki]: https://github.com/neoclide/coc.nvim/wiki

### Getting started with Coc

When it comes to setup, the [Coc README] is a great place to start. In this section I'll include the configuration you need to get it running as well as mappings for things I use all the time, but I encourage you to read through the Coc documentation as well. It is quite comprehensive!

If you use a plugin manager, installing Coc is simple. It won't do anything unless you have a language server configured, so let's install the [neoclide/coc-tsserver] plugin as well:

```viml
Plug 'neoclide/coc.nvim', {'branch': 'release'}
let g:coc_global_extensions = [
  \ 'coc-tsserver'
  \ ]
```

Coc plugins that we add to [g:coc_global_extensions] will be automatically installed and updated by Coc.

[g:coc_global_extensions]: https://github.com/neoclide/coc.nvim/wiki/Using-coc-extensions#install-extensions

_Note: Many languages don't have Coc packages, usually because they don't have custom Coc behavior or configuration. If your favorite language does not have one, it is painless to configure it. For example: [configuring the elm language server]._

At this point, you won't have any mappings, but you should start seeing language server errors highlighted with associated icons in the gutter, and cursoring over the errors will show the error or warning message.

In vim8 or Neovim >= v0.4.0 these will display in a floating window. This is what mine looks like:

![a screenshot of vim with Coc displaying a tool tip containing a warning about "ReactNode" being an unused import](https://images.thoughtbot.com/blog-vellum-image-uploads/XdV3U9RvWcc8kmHNFHNQ_screenshot_2.png)

Additionally, typing should offer auto suggestions along with documentation previews. It should look something like this:

![a screenshot of vim with Coc displaying a popup completion menu for the "FunctionalComponent" interface, along with a preview of the interface](https://images.thoughtbot.com/blog-vellum-image-uploads/RNnrtg5QaytcdqNO9DeQ_screenshot_1.png)

Selecting a completion option from this menu will auto-complete the text at the cursor, and additionally will import the symbol if it is not already imported.

To recap, out of the box we have gotten:

1. Language server feedback
1. Intelligent language server auto suggestions with documentation previews
1. Auto importing

That's pretty great!

In addition to your vim config, Coc has a [configuration file] which can be opened for editing using the `:CocConfig` command. I'll refer to this later as the "Coc configuration file".

In the following sections, we'll explore some of those options as we round out the experience with a few more nice-to-haves.

[Coc README]: https://github.com/neoclide/coc.nvim
[neoclide/coc-tsserver]: https://github.com/neoclide/coc-tsserver
[configuring the elm language server]: https://github.com/neoclide/coc.nvim/wiki/Language-servers#elm
[configuration file]: https://github.com/neoclide/coc.nvim/wiki/Using-the-configuration-file

### Prettier and ESLint

Most projects use ESLint, Prettier, or both. To get started with those using Coc, we just need to install [neoclide/coc-eslint] and [neoclide/coc-prettier]. I like to do it conditionally based on whether or not those tools are installed in the local `node_modules` folder:

```viml
if isdirectory('./node_modules') && isdirectory('./node_modules/prettier')
  let g:coc_global_extensions += ['coc-prettier']
endif

if isdirectory('./node_modules') && isdirectory('./node_modules/eslint')
  let g:coc_global_extensions += ['coc-eslint']
endif
```

Errors and warnings from these tools will now show in the same way as from the TypeScript language server.

For ESLint, you must configure which file types you want to lint. You can also enable auto-fix on save. Both can be done via the Coc configuration file:

```json
{
  "eslint.autoFixOnSave": true,
  "eslint.filetypes": ["javascript", "javascriptreact", "typescript", "typescriptreact"]
}
```

_Note: the `eslint.fileTypes` configuration option uses [VSCode language identifiers]_, which is not the same as the output from `:set filetype?`._

For Prettier, I found it particularly useful to disable the display of a success message whenever the file is formatted via the Coc configuration file:

```json
{
  "prettier.disableSuccessMessage": true
}
```

[neoclide/coc-eslint]: https://github.com/neoclide/coc-eslint
[neoclide/coc-prettier]: https://github.com/neoclide/coc-prettier
[VSCode language identifiers]: https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers

### Auto formatting

If you like auto formatting, you can enable it for Coc (which enables it for `coc-prettier`) and for `coc-tsserver` which enables it for the TypeScript language server. These settings are all in the Coc configuration file:

```json
{
    "coc.preferences.formatOnSaveFiletypes": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact"
  ],
  "tsserver.formatOnType": true,
  "coc.preferences.formatOnType": true
}
```

### Tool tip documentation and diagnostics

Coc will display diagnostics (errors and warnings) in a tooltip for words you cursor over. We can create a mapping to show documentation for the word under the cursor in the same way:

```viml
nnoremap <silent> K :call CocAction('doHover')<CR>
```

![screenshot of coc documentation tooltip](https://images.thoughtbot.com/blog-vellum-image-uploads/NkeOrV2bRzeOjgGqgbVb_screenshot_6.png)

I prefer a more automatic behavior where when cursoring over a word, I see _either the diagnostic if it exists, otherwise the documentation_. I wrote a snippet to accomplish this:

```viml
function! ShowDocIfNoDiagnostic(timer_id)
  if (coc#float#has_float() == 0 && CocHasProvider('hover') == 1)
    silent call CocActionAsync('doHover')
  endif
endfunction

function! s:show_hover_doc()
  call timer_start(500, 'ShowDocIfNoDiagnostic')
endfunction

autocmd CursorHoldI * :call <SID>show_hover_doc()
autocmd CursorHold * :call <SID>show_hover_doc()
```

With this behavior, you can still use the mapping from above to manually show the documentation popup instead of the diagnostic.

### Bindings for common actions

I'm only going to scratch the surface here with some examples of the most common code actions. Coc has a great [example vim config] and [documentation] that cover all the available actions you can create mappings for.

[example vim config]: https://github.com/neoclide/coc.nvim#example-vim-configuration
[documentation]: https://github.com/neoclide/coc.nvim/blob/master/doc/coc.txt

#### Navigating

Coc provides several different goto actions. I find the ones for definition, type definition, and references most useful in TypeScript:

```viml
nmap <silent> gd <Plug>(coc-definition)
nmap <silent> gy <Plug>(coc-type-definition)
nmap <silent> gr <Plug>(coc-references)
```

Oftentimes I want to navigate my current file by jumping to the next or previous error:

```viml
nmap <silent> [g <Plug>(coc-diagnostic-prev)
nmap <silent> ]g <Plug>(coc-diagnostic-next)
```

Coc also provides some lists out of the box. [neoclide/coc-lists] provides even more. Many of these lists are fuzzy-searchable.

You can use the `:CocList` command to see the available lists. The one I use the most often is the diagnostics list, which lists the errors and warnings for the entire workspace:

![screenshot of the coc diagnostics list](https://images.thoughtbot.com/blog-vellum-image-uploads/JR27bwmNRf6T3IsONWCH_screenshot_3.png)

```viml
nnoremap <silent> <space>d :<C-u>CocList diagnostics<cr>
```

A close second is the symbols list, which displays a fuzzy-searchable list of workspace symbols:

![screenshot of the coc symbols list](https://images.thoughtbot.com/blog-vellum-image-uploads/wU4zxhM6RRaXZd8ADgTt_screenshot_4.png)

```viml
nnoremap <silent> <space>s :<C-u>CocList -I symbols<cr>
```

[neoclide/coc-lists]: https://github.com/neoclide/coc-lists

#### Performing code actions

Code actions are automated changes or fixes for an issue, such as automatically importing a missing symbol. Code actions can be performed on the word under the cursor with a mapping like the following:

```viml
nmap <leader>do <Plug>(coc-codeaction)
```

In the case of a missing import, I could use the above mapping to prompt me for how I would like Coc to fix the issue:

![screenshot of the coc code actions menu](https://images.thoughtbot.com/blog-vellum-image-uploads/Lto8LgUdQDiVeYq1RSJ1_screenshot_5.png)

#### Renaming a symbol

Coc offers intelligent symbol renaming, a common action in any IDE:

```viml
nmap <leader>rn <Plug>(coc-rename)
```

## Final thoughts

Over a period of months, I introduced these various features of Coc into my workflow. It would be a daunting task to learn them all at once!

The depth of configuration available with Coc makes it possible to bring in language server enhancements into your existing vim setup where you want them.
