nvm, emacs and zsh walk into a (macOS) bar

Feel free to jump directly to the solution

Background

I recently started working on node based project. I’d been using nvm for quite some time now, since I liked the idea of having languages their own version managers, rather than having OS manage it ,which I doubt can do a good job. OS package managers were designed to have single version (and upgrade when needed)

So I’ve been using pyenv and nvm for quite some time.

Unfortunately, nvm adds a startup delay to the shell. For a normal interactive shell, it doesn’t matter since the shell once started lasts for weeks, if not month(s) at a time. But for non-interactive shell, this poses a problem.

Since I had not “seriously” used node so far, it didn’t matter much. I had couple of lines in my.zshrc since the time I installed nvm and hadn’t thought about it a lot (or at all)

Now that I was working on node project, I wanted to use tsserver as part of tide setup with emacs. tsserver requires to know the path to node binary.

I had a “global” node version (on top of nvm) - which was known to emacs but that installation was abandoned and didn’t have modules I used.

So I removed “system” version of node completely. Now emacs couldn’t find node at all and tsserver wouldn’t start.

Since .zshrc is for interactive shell only, emacs does not find the nvm settings from it.

Based on suggestion of doom emacs creator, I moved the configuration to .zshenv and emacs found node and tsserver started working.

But …

combination of nvm and zsh is known to be slow.

Problem

One of the reasons I switched to doom emacs (from spacemacs) is because it is fast But after the .zshenv change, it became slow .. to start.

OK, I could live with that. Similar to the interactive zsh - I don’t start emacs that often.

But there were other side effects. Opening .py file started to take noticeable time. There was other weird stuff going on as well.

I asked on doom-emacs’ discord channel about slow opening on .py files. I’m sure no one could have connected it to nvm but I “some how” came to connect the dots. (More of instinct, than logical reasoning)

So to eliminate the doubt, I commented out my .zshenv and startup time improved 400% (or reduced to 25% of the original, but 400% seems cooler. From 4+ seconds to 1+ second, if you care)

So now that I knew what was the problem, how do I fix it ? I definitely wanted emacs to know the path to node

Solution

Again, doom-emac creator to the rescue. I modified his instructions a little and here is what I have now.

  1. Empty .zshenv
  2. nvm related config commented from .zshrc

I’ve modified the Emacs as follows (This idea came from Henrik of doom emacs)

$ cd /Applications/Emacs.app/Contents/MacOS
$ mv Emacs EmacsBin
$ touch Emacs
$ chmod a+x Emacs
$ cat Emacs
#!/bin/zsh
export NVM_DIR="/Users/mandar/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"  # This loads nvm
$(dirname ${(%):-%x})/EmacsBin "$@"

Now even when I start emacs from anywhere (i.e. not from Terminal) Emacs knows about nvm and picks up the correct node binary

Since this executess only when starting the emacs binary, nothing else (like whatever takes place when opening python files) is affected.

I still get great startup time. (Cause the setup has taken place before emacs binary starts)

Bonus Tip 1

As far as interactive shell goes, I have following in my .zshrc to take care of that.

  1. export NVM_LAZY_LOAD=true
  2. plugins+=(zsh-nvm)

Bonus Tip 2

In the process, I learned some more about zsh, nvm and emacs - e.g. I learned that equivalent of which node (as run from the shell) for emacs is M-: (executable-find "node")

Share Comments
comments powered by Disqus