Configuring Emacs for MDX files

Published on , 666 words, 3 minutes to read

An image of masterpiece, digital art, watercolor, landscape, badlands, mountains
masterpiece, digital art, watercolor, landscape, badlands, mountains - Waifu Diffusion v1.4

This post was written while I worked for Tailscale. It is archived here for posterity.

I'm an Emacs user and I have been for the last decade. I use emacs for everything from code, to posts on this blog, even down to my daily TODO list with Org Mode. Naturally, I want to also use emacs to edit posts on this blog. The only problem is that the blog uses MDX instead of normal Markdown. There isn't an Emacs major mode for MDX and the ticket for editor support in MDX was closed. This should mean that I'm out of luck and must architect a new major mode for Emacs.

However, this is Emacs. You have godlike power to do anything we want here. MDX is just Markdown with JSX, and there's already a widely used major mode for Markdown named markdown-mode. JSX is close enough to HTML that realistically we don't have to care about the details, especially in an environment where the important JSX components are already imported into the document scope for us.

You can use all of this information to bodge MDX support into Emacs by using directory-local variables.

Directory-local variables live in .dir-locals.el and will apply to any file in the same folder as the .dir-locals.el file and all of its subfolders. If you stick a .dir-locals.el file at the top level of a project, it will apply for all the files in the project.

You can add MDX support to Emacs by changing the auto-mode-alist variable to change which major mode Emacs uses based on the filetype:

;;; Directory Local Variables
;;; For more information see (info "(emacs) Directory Variables")

((auto-mode-alist . (("\\.mdx\\'" . markdown-mode))))

This will make every .mdx file load in markdown-mode, allowing you to edit files like normal. It looks a bit horrible with only one example, but the basic schema is that it's an association list (read: hashtable) that contains variable definitions. If you also wanted to make markdown-mode wrap files at 80 characters and typescript-mode use two spaces for indent, you would do something like this:

;;; Directory Local Variables
;;; For more information see (info "(emacs) Directory Variables")

((auto-mode-alist . (("\\.mdx\\'" . markdown-mode)))
 (markdown-mode . ((fill-column . 80)))
 (typescript-mode . ((typescript-indent-level . 2))))

Sometimes you need to do a bit more though. This lets you set variables for buffers, but it doesn't let you execute code in buffers. The CI configuration for this repo also needs us to make sure our MDX documents are formatted correctly, and we use prettier to take care of all of that for us.

Emacs lets you set the variable name eval to a block of lisp code to run when loading buffers with that major mode. This lets you do things like this to have markdown-mode auto-format files on save:

;;; Directory Local Variables
;;; For more information see (info "(emacs) Directory Variables")

((auto-mode-alist . (("\\.mdx\\'" . markdown-mode)))
 (markdown-mode . ((fill-column . 80)
                   (eval . (prettier-js-mode 1))))
 (typescript-mode . ((typescript-indent-level . 2))))

This will make Emacs prompt you if you really want to do this every time you load the file, but you can squelch this by using the ! key.

Cadey is enby

Make sure you know what the code is doing before you just blindly hit "yes"!

This is all you need to get MDX working in Emacs. If you want to see more, look at the .dir-locals.el in the tailscale-dev repo.

Facts and circumstances may have changed since publication. Please contact me before jumping to conclusions if something seems wrong or unclear.