Portable emacs config without crazy load time
Imagine you can just use package-install
to install any Emacs package and have a painless way to reinstall the same packages when you move to a new system.
usually what happens is when you install some package, Emacs modifies your custom-set-variables
block to include the newly installed package in the package-selected-packages
variable. But this does not anything special to install these packages on a new system (as far as I know). So when you are on a new system you still have to manually install all the packages present in this block to get your Emacs running without any issues.
So I wrote a script that I can just run on a new system which would read the package-selected-packages
variable and install all the relevant packages for me.
Let's go over how to get this set up.
Step 1: have a separate custom-file
Add the following to your Emacs config to make sure the auto-generated custom-set-variables
block is saved to a separate file.
(setq custom-file "~/.emacs.d/custom.el")
(load custom-file)
Step 2: Create the eslip script to do the package installation
Create a file called sync.el
in your Emacs configuration folder with the following contents.
(require 'package)
(load-file "~/.emacs.d/custom.el")
(print package-selected-packages)
;; Add melpa
(add-to-list 'package-archives
'("melpa-stable" . "https://melpa.org/packages/") t)
(package-initialize)
;; refresh packages
(setq my/package-refreshed-p nil)
(defun my/refresh-contants ()
(unless my/package-refreshed-p
(progn
(package-refresh-contents)
(setq my/package-refreshed-p t))))
(dolist (pkg package-selected-packages)
(condition-case err
(progn
(let ((pkg-name (symbol-name pkg)))
(eval (if (package-installed-p pkg)
(print (concat pkg-name " already installed!"))
(progn
(print (concat "Installing: " pkg-name))
(my/refresh-contants)
(package-install pkg))))))
(error
(message "Failed to install package: %s. Error: %s" pkg err))))
This script will go through all the packages listed in package-selected-packages
and install any that aren’t already on the system.
The key thing here: we don’t call this script during regular Emacs startup. That means package-refresh-contents
is never called when Emacs is starting up normally. So no extra load times! You only run this sync.el
script when setting up a new system or installing your packages. The rest of the time, Emacs stays fast as usual.
Step 3: Create a shell file to run the elisp script without launching Emacs
- Create an empty file with the following contents for example in ~/.emacs.d/sync
#!/bin/bash
emacs -q --script ~/.emacs.d/sync.el
- Make it executable:
$ sudo chmod +x ~/.emacs.d/sync
That’s it! Now, running ~/.emacs.d/sync
will install any packages listed in your custom-set-variables
block.
Conclusion
This technique is actually inspired by Doom Emacs. Alternatively, you could use use-package
with :ensure t
to handle package installation automatically. But honestly, I find that approach makes your config pretty beefy when you have a lot of packages. Adding a use-package
block for everything feels clunky. And yeah, it might avoid the need for this script, but I like the simplicity of just listing the packages and not having to write a bunch of boilerplate.
This setup works perfectly for me—it's lightweight, avoids the usual slowdowns, and gets my Emacs ready in no time!
Subscribe to my newsletter
Read articles from Bhanuka Mallawaarachchi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by