Last year, I started working in Tokyo for the first time as a programmer1. I soon left the company for several reasons and became an employee of another company based in Fukuoka, which is the fifth largest populated city in Japan. One reason2 for switching the company was that I was not allowed to use Emacs in the project. Taking into account my past work on Emacs packages, it was a nonsense that he sent me to the project where I was not allowed to use Emacs. Fortunately, I am now happily3 using Emacs for software projects at work, which should be a baseline for any indoor jobs.

I am currently working on JavaScript/TypeScript projects and HTML templates for most of the time. As I now have to write code in languages other than Emacs Lisp4, I have to adapt my configuration for those languages. Actually, I often rely on Visual Studio Code, but it would be better if I could use Emacs for any languages because:

  1. It is generally a good practice to use less software.
  2. I have been using Emacs.
  3. Emacs is the frontend to any text-based tasks.

In other words, I am used to Emacs (at least for basic text manipulation tasks) and don’t want to build expertise with multiple tools for the same purpose. Emacs should provide a coding experience on a par with Visual Studio Code and other modern text editors/IDEs, so I need to implement one on my own.

In this post, I will describe how to use node2nix tool to generate Nix expressions for a npm package and then install it using nix-env. The installed executable will be available in ~/.nix-profile/bin, which is the default location of executables installed by Nix. If you are already using Nix, the directory should be included in PATH, so the executable will be available for Emacs packages.

Problem: Node.js development on Nix

Since the end of last year, I have been using Nix whenever possible. It is a cross-platform package manager which supports both Linux and Mac. I also switched to home-manager for managing my user configuration files (a.k.a. dotfiles) and my favourite programs in a declarative manner. As described in one of my previous posts, you can use nix-env for ad-hoc package management, but it is nice to have a reproducible setup.

However, as I began to use Emacs for web development projects, a problem arose: I have to use many tools from npm. For formatting code, I need prettier. Quite a few LSP servers are available from npm. If you seriously consider using Emacs for writing modern (web) languages, you will probably have to install some npm packages globally5.

Unfortunately, you cannot install npm packages using npm command on Nix. You can install npm command using Nix, but if you install it in that way, npm install --global command will fail, because its global node_modules directory resides in an immutable store:

❯ npm install -g typescript-language-server
npm WARN checkPermissions Missing write access to /nix/store/a5vl2i4yk9z1nim4bn2p16n1622z6k2k-nodejs-10.16.3/lib/node_modules
npm ERR! path /nix/store/a5vl2i4yk9z1nim4bn2p16n1622z6k2k-nodejs-10.16.3/lib/node_modules
npm ERR! code EACCES
npm ERR! errno -13
npm ERR! syscall access
npm ERR! Error: EACCES: permission denied, access '/nix/store/a5vl2i4yk9z1nim4bn2p16n1622z6k2k-nodejs-10.16.3/lib/node_modules'
npm ERR!  { [Error: EACCES: permission denied, access '/nix/store/a5vl2i4yk9z1nim4bn2p16n1622z6k2k-nodejs-10.16.3/lib/node_modules']
npm ERR!   stack:
npm ERR!    'Error: EACCES: permission denied, access \'/nix/store/a5vl2i4yk9z1nim4bn2p16n1622z6k2k-nodejs-10.16.3/lib/node_modules\'',
npm ERR!   errno: -13,
npm ERR!   code: 'EACCES',
npm ERR!   syscall: 'access',
npm ERR!   path:
npm ERR!    '/nix/store/a5vl2i4yk9z1nim4bn2p16n1622z6k2k-nodejs-10.16.3/lib/node_modules' }
npm ERR!
npm ERR! The operation was rejected by your operating system.
npm ERR! It is likely you do not have the permissions to access this file as the current user
npm ERR!
npm ERR! If you believe this might be a permissions issue, please double-check the
npm ERR! permissions of the file and its containing directories, or try running
npm ERR! the command again as root/Administrator (though this is not recommended).

npm ERR! A complete log of this run can be found in:
npm ERR!     /home/akirakomamura/.npm/_logs/2019-09-22T04_25_35_867Z-debug.log

How about installing the npm package itself using Nix? The official Nixpkgs registry provides packages for various programming languages, and they do include npm packages. You can use an executable program in a npm package inside a Nix shell:

nix-shell -p nodePackages.typescript-language-server

In the subshell, you can find typescript-language-server program using which or command -v:

❯ which typescript-language-server
/nix/store/918isrs387rzhs2h0pra83fki9wnvv9b-node_typescript-language-server-0.4.0/bin/typescript-language-server

However, you cannot install the program globally using nix-env:

nix-env -i nodePackages.typescript-language-server
error: selector 'nodePackages.typescript-language-server' matches no derivations

How can I install a program like typescript-language-server from npm using Nix?

Solution: node2nix

To install npm packages globally, you can use node2nix, which generates Nix expressions for npm packages.

First, create an empty directory for generating Nix expressions:

mkdir ~/tmp/foo
cd ~/tmp/foo

node2nix itself is available from npm, so you can run it in a nix-shell session. The following command lets you enter a nix-shell session where node2nix is available:

nix-shell -p nodePackages.node2nix

Inside the shell, you can generate Nix expressions for typescript-language-server npm package using the following command:

node2nix -i <(echo '["typescript-language-server"]') --nodejs-10

In bash-compatible shells, <(echo '["typescript-language-server"]') is substituted for a file name containing ["typescript-language-server"], which is a JSON syntax for a list of npm packages. If your shell doesn’t support the syntax, you can create a file with the content and pass its file name:

node2nix -i node-packages.json --nodejs-10

where node-packages.json contains the following JSON expression:

["typescript-language-server"]

For now, you need to specify --nodejs-10 to use Node 10.x. Without the option, nix-env may fail to install the package, as shown below:

❯ nix-env -f default.nix -iA typescript-language-server
installing 'node_typescript-language-server-0.4.0'
error: attribute 'nodejs-8_x' missing, at /home/akirakomamura/tmp/typescript-language-server
(use '--show-trace' to show detailed location information)

Once you generate the expressions, you can install the npm package using nix-env:

❯ nix-env -f default.nix -iA typescript-language-server
installing 'node_typescript-language-server-0.4.0'

You need to specify the name of the package with -A argument. After the command finishes, you can check if the executable is available in your PATH:

❯ which typescript-language-server
/home/akirakomamura/.nix-profile/bin/typescript-language-server

Note that you also have to install Node 10.x to run the executable, which can be installed with the following command:

nix-env -i nodejs

Declarative use

Alternatively, you can use the generated Nix expression to constitute part of your project configuration, since it is Nix. For details on the generated expressions, refer to the documentation of node2nix.

Evaluation: Would I ever need this solution?

Unless you are using NixOS which entirely relies on the Nix ecosystem, you don’t necessarily have to use Nix for installing npm packages. That is, you could install npm using the package manager for your distribution, and npm would install packages globally without any problems. For example, I installed a language server on Windows (not WSL but Windows) using npm, and lsp-mode on Emacs on WSL was capable of communicating with the server running on Windows.

Still, I prefer Nix. If I have to use multiple desktop operating systems, I don’t want to spend time learning their individual ecosystems, nor how to install specific packages on different systems. I have been using home-manager both on Chrome OS (Crostini) and on WSL, and it has reduced the problem of maintaining development environments on different platforms. I feel safer if “everything I need” is on Nix, and thus I want everything to be supported by Nix. Nix is not only about consistency: It is often convenient at the same time. There are usually other ways to solve the same problem, but Nix covers quite a few situations and suffices for the most cases.

Note on the TypeScript language server

With typescript-language-server and npm installed globally, you can use lsp-mode for writing JavaScript and TypeScript on Emacs. Actually, tide (which is based on tsserver) is more recommended than lsp-mode for developing in those languages on Emacs, so typescript-language-server is unlikely to be useful in itself. Nonetheless, you can apply the same technique to installing other LSP servers and npm-registered tools, so I hope this post will be helpful. In lsp-mode, you can use lsp-describe-session to check which language servers are running.

Emacs interface

Using the technique described above, I have created an Emacs package which lets you install npm packages using Nix.

Wrapping up

Nix package manager is not limited to installing programs that have been available through traditional package managers for other Linux distributions. It can also take advantage of external package registries like npm, either declaratively or in an ad-hoc manner. Even if Nix itself cannot directly handle a foreign package format, you can solve the problem by writing a helper program to convert the package specification. Nix is a versatile, extensible, and solid foundation for managing various types of deployments.

Resources


  1. Actually, I was more of a tester than a programmer in the first project. This seems to be a common practice in the Japanese industry called SIer which is short for sucks at innovation. [return]
  2. Another reason was that the salary, which I had not been informed on before moving, was too low for living in Tokyo. Unlike the former company, the company I have joined allowed me to negotiate on my salary beforehand. The former company also had many other nasty aspects, while the latter has several other positive sides, but I won’t go into the details for now. Generally speaking, I am interested in living opportunities outside of Tokyo, including other countries. [return]
  3. I could be happier if I were not on Windows. WSL can run Emacs, but it performs much worse. [return]
  4. I never expect that coding in Emacs Lisp would cut, as normal people would not do. I once received a message from a human resource company that said “Are you interested in building expertise in Emacs Lisp for more salary?”, which was apparently from a bot, but I don’t believe in such software projects. [return]
  5. Installing LSP servers locally, i.e. to node_modules directory of your project, doesn’t seem to work with the current implementations of most backends for lsp-mode. [return]