On Emacs and Node.js Workflow

One of the things I miss most about my time in Microsoft's realm is the tooling. It had this great way of making you feel nice and cozy within the bounds of how their stuff works (although I always felt the urge to break the mold, because let's face it, it was good but not perfect, either). Without that kind of tooling, I find myself having to be responsible for my own productivity. I don't necessarily mean that I'm spending inordinate amounts of time trying to achieve the perfect workflow, but more that there are things that just help trim the fat from one input to the next.

My brain conditions itself for my workflows. At first my brain doesn't perceive time gaps, but through sheer repetition the little gaps of time (while staying the same) feels longer and longer. It could be some stupid slice of time, like 238 milliseconds, and my brain will get used to it and begin to allow for context switching between input. It's dangerous territory because it is inherently disruptive. When I become aware of such things happening it seems like a natural time to compress my workflow as an act of prevention.

Emacs, for better or worse, gives me control over my workflow. It can be a bit of a time sink to fiddle with it and achieve the desired result. However, it can just do a lot to automate the more mechanical parts of my day.

Lately I have worked on building a hook for integrating linting to my workflow while building Node.js applications:

(add-to-list 'compilation-error-regexp-alist-alist
             '(eslint "^\\(\w+\\):\\([0-9]+\\):\\([0-9]+\\):.*$" 1 2 3))
(add-to-list 'compilation-error-regexp-alist 'eslint)

(add-to-list 'compilation-error-regexp-alist-alist
             '(jshint "^\\(.*\\): line \\([0-9]+\\), col \\([0-9]+\\), " 1 2 3))
(add-to-list 'compilation-error-regexp-alist 'jshint)

(defun find-root-git ()
  (locate-dominating-file default-directory ".git"))

(defun file-at-git-root (f)
  (concat (find-root-git) f))

(defun jshint-compile-command ()
  (concat
   "jshint " (file-at-git-root "")))

(defun eslint-compile-command ()
  (concat
   "(cd " (file-at-git-root "")
   " && eslint . --format unix --ignore-path ./.eslintignore)"))

(defun compile-javascript ()
  (cond
   ((file-exists-p (file-at-git-root ".jshintrc")) (jshint-compile-command))
   ((file-exists-p (file-at-git-root ".eslintrc.js")) (eslint-compile-command))))

(add-hook 'js2-mode-hook
          (lambda ()
            (set (make-local-variable 'compile-command)
            (compile-javascript))))

(global-set-key (kbd "<f5>") 'compile)
(global-set-key (kbd "C-<f5>") 'next-error)
(global-set-key (kbd "C-S-<f5>") 'previous-error)

This lets me hit F5 to lint my project, but it will use the default-directory and look up the folder hierarchy to find the git root. Then it lints everything in the whole project. After it spits out some errors, I can use C-F5 or C-S-F5 to move between errors quickly.

Not all of the projects I am involved with use the same linter, but typically it's either jshint or eslint. I was able to customize emacs to figure out which one to invoke, so that my workflow gets to be the same no matter which project I'm looking at.

My brain now has a chance to stay dedicated to the task at hand: run some static analysis tool, and fix problems. I don't have to alt-tab to another window or open something else entirely. I get to hold my finger over the F5 key and then, in anticipation, be ready to hold the control key and press F5 to jump right to the first thing. When I have to switch to the terminal and execute a command, and then scroll up in it, note the file and location of the blunder, my brain gets all of these opportunities to digress to something completely unrelated, and inherently unproductive.

Clearly it isn't perfect. Linting can take a relatively long time, but being able to remain in emacs while I do it and then walk errors and deep link into buffers feels like a step in the right direction. I don't even have to carefully read the lint output; I can just go right to the file/line/col of the problem. Perhaps a future goal will be to add an extra keybinding to just lint the file in the current buffer. Better yet, maybe emacs could do a git status and only lint all files that appear in that output! Who knows!? The sky is the limit.

Update: 2016-05-20

I updated the code above to reflect a change to how I try to invoke eslint. If I tried to run it from a subdirectory it would hang to the point of having to run M-x kill-compilation RET. No good. After some fooling around in the terminal I could reproduce it there, and found a different way to invoke it that appears to no longer exhibit the problem.

Show Comments