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!

0
Subscribe to my newsletter

Read articles from Bhanuka Mallawaarachchi directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Bhanuka Mallawaarachchi
Bhanuka Mallawaarachchi