Configuring Emacs for MDX files
Published on 02/03/2023, 666 words, 3 minutes to read
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
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
markdown-mode wrap files at 80 characters and
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
Make sure you know what the code is doing before you just blindly hit "yes"!
Facts and circumstances may have changed since publication. Please contact me before jumping to conclusions if something seems wrong or unclear.