Better Emacs Config: use-package

use-package is an Emacs package which allows packages to be loaded declaratively. It's been around for ages and I've seen it used in other people's configurations, but I've only recently paid some real attention to it. I wish I'd learned how to use it sooner - it's really improved my Emacs config.

Let's look at an example:

(use-package magit
  :bind (("C-x g" . magit-status)
         ("C-x C-g" . magit-status)))

When this is run, the use of the Magit package is declared and two key-bindings for its main function are defined. What's really nice here is that autoloads are installed for those key-bindings - the package isn't actually loaded until I actually type either of those shortcuts for the first time. This means Emacs can start faster and resources aren't used for features I don't use often (that said, Magit is something I do use all the time!).

Note use-package's clear, compact syntax. There's less ceremony for common startup tasks such as setting up key-bindings and I love how use-package encourages all setup related to a given package to be neatly grouped together.

Configuring Packages

It's common to need to run some code before or after a package is loaded in order to set it up. With use-package this is done using :init (before loading) and :config (after loading). Here's the above example with some "helpful" messages inserted printed before and after the magit package is loaded:

(use-package magit
  :init
  (message "Loading Magit!")
  :config
  (message "Loaded Magit!")
  :bind (("C-x g" . magit-status)
         ("C-x C-g" . magit-status)))

Again, because loading of the package is deferred until one of the key-bindings is used, the messages won't appear until I actually hit one of those keys.

Deferred Loading

Key-bindings are just one way that a deferred package might be loaded by use-package. Other mechanisms include assigning a file extension to a mode defined in the package (via :mode) or by adding a function from the package into a hook (via :hook). use-package provides a variety of syntactic sugar to make this painless and concise.

Immediate Loading

Of course there are some packages which you always want to be loaded immediately. use-package can handle this too through the :demand keyword. Here's an example:

(use-package evil
  :demand t

  :custom
  (evil-esc-delay 0.001 "avoid ESC/meta mixups")
  (evil-shift-width 4)
  (evil-search-module 'evil-search)

  :bind (:map evil-normal-state-map
         ("S" . replace-symbol-at-point))

  :config
  ;; Enable evil-mode in all buffers.
  (evil-mode 1))

Here we have the Evil package being loaded immediately (due to the :demand t) with some configuration set before it's loaded (some customisations need to be set before loading), a key-binding added, and evil-mode being enabled globally.

Note the use of the :custom keyword here. This is a clean way of setting customisations that you could also set with Emacs' customize functionality. It can be nice to keep customizations with the use-package declaration, although I'm not that consistent about this myself.

Key-bindings in Keymaps

In the previous example, the key-binding is slightly different to earlier examples because the binding is being set inside a specific keymap instead of globally. use-package provides clean syntax for this. Here's an example with multiple key-bindings being set up across multiple keymaps:

(use-package evil-args
  :bind (:map evil-inner-text-objects-map
         ("a" . evil-inner-arg)
         :map evil-outer-text-objects-map
         ("a" . evil-outer-arg)

         :map evil-normal-state-map
         ("L" . evil-forward-arg)
         ("K" . evil-jump-out-args)

         :map evil-normal-state-map
         ("H" . evil-backward-arg)
         ("L" . evil-forward-arg)

         :map evil-motion-state-map
         ("H" . evil-backward-arg)
         ("L" . evil-forward-arg)))

Not bad! Much better than:

(define-key evil-inner-text-objects-map (kbd "a") 'evil-inner-arg)
(define-key evil-outer-text-objects-map (kbd "a") 'evil-outer-arg)
(define-key evil-normal-state-map (kbd "L") 'evil-forward-arg)
; you get the idea ...
Package Manager Integration

By default, use-package only loads packages that have already installed somehow, but it can integrate with a package manager too.

If you're already using the built-in Emacs package manager (package.el) then simply adding :ensure t to a use-package block will cause use-package to download and install the package if it's not already there. Extending our first example slightly:

(use-package magit
  :ensure t
  :bind (("C-x g" . magit-status)
         ("C-x C-g" . magit-status)))

This avoids the need to separately call package.el's install-package function or use the list-packages interface to install a package.

use-package can also work with other package managers. The powerful straight.el package manager has tight integration with use-package (it's what I use now).

What Next?

If you want to learn more about use-package, the official README is approachable and comprehensive. There's plenty more to it than what I've covered here, although you don't need to know much to start see its benefits.

Other articles that you might also find helpful:

Edit 2020-05-15: Use :custom instead of setq in :init. Thanks Canatella.

Comments

Comments powered by Disqus