In this post I’ll cover the plugins and configuration I have found to be the best for editing and navigating Javascript projects with Vim.
Background
I have had around a year’s worth of experience at this point. I tried out various plugins, uninstalled some, tweaked others, and found a decent setup. There are probably some plugins out there that I am missing, so please let me know if I should investigate one. Primary stack has been Mongo, Angular, Express, Mongo. I’ll update if I find more good Vim JavaScript setup.
The Best Vim JavaScript Plugins
I use the following plugins on my current project. I listed them roughly based on importance, how much I use them, and how surprising they were.
vim-javascript
The biggest advantage of this plugin is how much better the default indentation is. I’m sure there are others, but indentation was unbearable before using it, and pretty good afterward.
vim-node
Although it has other utilities, I primarily use vim-node for gf
on a relative require to go to that file. Following Vim conventions, gF
opens that file in a split. This really aids in navigation, especially when paired with Ctags (discussed below.) It might not sound all that helpful, but when you have:
var UrlFinder = require('../../../server/util/other/urlFinder');
I can just type gF
to view the file in a new split window to figure out what functions I might want to call on it. Much faster than other methods of getting there. Generally Vim would choke on the relative file path since I typically have my current working directory as the project root.
jsctags setup
I had not used Ctags much before, but have found them very useful on the project that I am working on. Ctags works by creating an index of tags, which are things that you want to be able to jump to. Vim supports Ctags-style tags.
The value is being able to jump to the definition of a function when your cursor is over it. So being able to say Ctrl+] and then jumping right to where the function is. I can easily get back with Ctrl+T (or perhaps Ctrl+O since that will take me out to the edit stack.)
This gets us closer to many IDEs, and avoids running the function name through grep
/ack
/ag
and/or opening the file manually.
The core issue is that the current Exuberant Ctags JavaScript support is not very good. Often it would not be able to find a function declaration. There is an old Mozilla project, but it had one basic, but large issue with installation that cannot be solved by forking/patching (to my knowledge.) So I made a custom ZSH install function:
function install_jsctags {
npm install jsctags
# https://github.com/mozilla/doctorjs/issues/52
gsed -i '51i tags: [],' ./node_modules/jsctags/jsctags/ctags/index.js
}
This is helpful because I sometimes switch node environments with nvm
, or do an npm prune
which removes it since it is not in the package.json
file.
Then I can manually run something like:
$ jsctags -o tags server test admin
This takes a few seconds and then spit out Ctags-compatible output of processing the function names for the server
, test
, and admin
directories to a tags
file. Vim automatically reads the tags file. I don’t need to restart Vim, it just works after regenerating the file. So a little bit of setup, but I think the time savings is worth it. It would be nice to automate this so that the tags don’t get stale, but I usually only run it when I jumped to the wrong place or Vim couldn’t find the tag, which happens maybe once a week. So I am happy enough with how it works.
A Vim-alias would be something like:
nnoremap <leader>jt :! jsctags -o tags server test admin<CR>
html5-syntax.vim, html5.vim
html5-syntax.vim seems to handle HTML5 syntax highlighting. html5.vim seems to handle HTML5 autocomplete. So I installed them and I am happy with the indentation and syntax highlighting with HTML5 code, so this is about all I can ask for.
vim-less
We use less for having a higher level stylesheet language than CSS. vim-less is useful for working with these files to get good indentation.
Also, you’ll probably want the following in your ~/.vimrc
:
autocmd BufNewFile,BufRead *.less set filetype=less
autocmd FileType less set omnifunc=csscomplete#CompleteCSS
The first line says to set the syntax type of .less
files to use the less
filetype. The second says to use the CSS autocomplete function for autocompletion. So if you are typing:
display:
and momentarily draw a blank on what options are possible, you can type Ctrl+X, O to see the list of standard options:
javascript-libraries-syntax.vim
This plugin has some syntax highlighting for common JavaScript libraries like Underscore (Lo-Dash), Angular, React, etc. This might be helpful for spotting incorrect function names:
There is a little configuration necessary, check out the plugin’s page for more details. Basically you’ll need something like this to get the full benefit, either in ~/.vimrc
or by using some kind of local vimrc setup:
let g:used_javascript_libs = 'underscore,angularjs,jasmine,chai'
Musing: It might be nice if there was a way to automatically audit what manual configuration I need to do when installing a plugin to get the most out of it. I only found that I hadn’t set it up when I was writing up this post! :)
tern_for_vim
This was a bit hard to get properly set up (doubly so if you use nvm, since tern_for_vim wants a globally/system installed node
executable…). And when I finally did get it working, it didn’t blow me away. However…
There are at least two useful Vim extensions that it provides:
:TernRename
renames the variable under the cursor but only in this scope and then shows you a list of the changed references.
:TernRefs
will just show you references to the variable under the cursor (similar to above, but with no rename.)
So I’d recommend this plugin just for the rudimentary refactoring support it provides. If you’re willing to go the extra mile it might be able to provide some more intellisense / autocomplete functionality.
Other Useful Non-JavaScript Specific Plugins
syntastic
Real-time syntax checking. Fantastic plugin, well documented and maintained, works well with no configuration. Works even better with some configuration. For JavaScript / HTML projects:
" use jshint
let g:syntastic_javascript_checkers = ['jshint']
" show any linting errors immediately
let g:syntastic_check_on_open = 1
I also found a handy gist to have Syntastic use my project’s .jshintrc.
There is a bit of munging I do to decrease spurious errors. Specifically, HTML Tidy is the worst when dealing with things it doesn’t understand (HTML5 custom elements / attributes, for example):
" Set up the arrays to ignore for later
if !exists('g:syntastic_html_tidy_ignore_errors')
let g:syntastic_html_tidy_ignore_errors = []
endif
if !exists('g:syntastic_html_tidy_blocklevel_tags')
let g:syntastic_html_tidy_blocklevel_tags = []
endif
" Try to use HTML5 Tidy for better checking?
let g:syntastic_html_tidy_exec = '/usr/local/bin/tidy5'
" AP: honestly can't remember if this helps or not
" installed with homebrew locally
" Ignore ionic tags in HTML syntax checking
" See http://stackoverflow.com/questions/30366621
" ignore errors about Ionic tags
let g:syntastic_html_tidy_ignore_errors += [
\ "<ion-",
\ "discarding unexpected </ion-"]
" Angular's attributes confuse HTML Tidy
let g:syntastic_html_tidy_ignore_errors += [
\ " proprietary attribute \"ng-"]
" Angular UI-Router attributes confuse HTML Tidy
let g:syntastic_html_tidy_ignore_errors += [
\ " proprietary attribute \"ui-sref"]
" Angular in particular often makes 'empty' blocks, so ignore
" this error. We might improve how we do this though.
" See also https://github.com/scrooloose/syntastic/wiki/HTML:---tidy
" specifically g:syntastic_html_tidy_empty_tags
let g:syntastic_html_tidy_ignore_errors += ["trimming empty "]
" Angular ignores
let g:syntastic_html_tidy_blocklevel_tags += [
\ 'ng-include',
\ 'ng-form'
\ ]
" Angular UI-router ignores
let g:syntastic_html_tidy_ignore_errors += [
\ " proprietary attribute \"ui-sref"]
Sometimes I get the following message on startup or loading JavaScript files: “syntastic: error: checker javascript/jshint: can’t parse version string (abnormal termination?)”. This error indicates that Syntastic can’t find the jshint executable. For me this means I need to exit vim and nvm use
to load up the right node
version. Then when I start Vim again, Syntastic can find jshint.
OK, enough about Syntastic for now.
vim-projectionist
If you’ve ever used Rails.vim, and loved the :A
functionality to jump to the alternate file (typically the test file), but want to do that with arbitrary projects, then this is the plugin for you.
This has been extremely useful on my current project. We had a Rails-ish directory structure in an Express server, with unit tests in reasonable places. So something like the following in a projections.json
file gives me quick access to the test file of the project file that I’m looking at (or vice versa):
{
...
"server/models/*.js": {
"type": "model",
"alternate": "test/mocha/models/{}.spec.js"
},
"server/controllers/*.js": {
"type": "controller",
"alternate": "test/mocha/controllers/{}.spec.js"
},
...
"test/mocha/models/*.spec.js": {
"type": "modeltest",
"alternate": "server/models/{}.js"
},
"test/mocha/controllers/*.spec.js": {
"type": "controllertest",
"alternate": "server/controllers/{}.js"
}
...
}
Can also open up a model file with :Emodel user
and it does the right thing.All of the navigation commands also let you open the associated file in a new split or tab. Highly recommended.
Ultisnips
If you’re running a straight JavaScript project, there can be a lot of typing. Ultisnips is the most versatile text expander plugin for Vim. You can make new snippets for any file type. Here is my JavaScript Ultisnips configuration. A snippet of useful snippets:
snippet use
'use strict';
endsnippet
Type use<tab>
and save a few keystrokes.
snippet clv
console.log('$1: ', ${1});
endsnippet
Handy for quick debugging. Basically log any variable while printing what it is, so you don’t need to type it twice.
snippet reqlo
_ = require('lodash')
endsnippet
snippet reqmonbb
BBPromise = require('bluebird'),
mongoose = BBPromise.promisifyAll(require('mongoose'))
endsnippet
Since we use these in 80% of our files, saves me quite a bit of time.
Just a few examples, there are other things that I constantly use in there. Basically if you have a pattern in your codebase, encode it in snippets and then always do the right thing in a consistent way with minimal typing / thinking.
Wrapping up
Prerequisites
Before you do anything else, get a Vim plugin manager. I use Pathogen. There are many other options.
More info
Here is my full configuration.
If you liked this post, check out my book on Writing With Vim, where I cover everything you need to know about writing prose in Vim.
Thanks for reading, hope this was helpful!