Investigating use-package Alternatives

News

Investigating use-package Alternatives

Today we'll be taking a look at two alternatives to use-package: setup.el and leaf.el.

The goal isn't to try and replace use-package, just to investigate the alternate ways that Emacs users have created to simplify configuration authoring!

Comparing with use-package

I'll try to put together configurations with setup.el and leaf.el to compare how they would look to an equivalent use-package configuration.

We'll try to see how to replicate the same features that use-package provides:

  • :ensure - Install package
  • :after - Load after another package
  • :init - Run code before package loads
  • :config - Run code after package loads
  • :custom - Set custom variables with concise syntax
  • :defer - Defer loading until some later point
  • :demand - Ensure loading at startup
  • :bind - Bind keys for modes
  • :hook - Set hooks that will cause package to load
  • :commands - Set autoloaded commands that will cause packag to load
  • :mode - Activate mode when particular file types are opened

The example configuration

(use-package evil
  :ensure t
  :init
  (setq evil-want-c-u-scroll t)
  (evil-mode 1))

(use-package org
  :after evil
  :demand t)

(use-package org-roam
  :ensure t
  :init
  (setq org-roam-v2-ack t)
  (setq dw/daily-note-filename "%<%Y-%m-%d>.org"
        dw/daily-note-header "#+title: %<%Y-%m-%d %a>\n\n[[roam:%<%Y-%B>]]\n\n")
  :custom
  (org-roam-directory "~/RoamNotes/")
  (org-roam-dailies-directory "Journal/")
  (org-roam-completion-everywhere t)
  (org-roam-capture-templates
   '(("d" "default" plain "%?"
      :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                         "#+title: ${title}\n")
      :unnarrowed t)))
  :bind (("C-c n l" . org-roam-buffer-toggle)
         ("C-c n f" . org-roam-node-find)
         :map org-mode-map
         (("C-c n i" . org-roam-node-insert)))
  :config
  (org-roam-db-autosync-mode))

(use-package scheme-mode
  :mode "\\.sld\\'")

(use-package rainbow-mode
  :defer t
  :hook (org-mode
         emacs-lisp-mode
         web-mode
         typescript-mode
         js2-mode))

setup.el

Benefits

  • Much easier to to
  • Can add conditional logic inside of the macro body to set bindings, hooks, etc only if something is true
(setup (:if-package avy)
  (:option avy-single-candidate-jump nil)
  (if (display-graphic-p)
      (:global "M-z" #'avy-goto-word-1)
    (:global "C-z" #'avy-goto-word-1)))

Final config

;; -*- lexical-binding: t; -*-

;; Install setup.el
(unless (package-installed-p 'setup)
  (package-install 'setup))

(require 'setup)

(setup-define :straight
  (lambda (recipe)
    `(straight-use-package ',recipe))
  :documentation "Install RECIPE if it hasn't been installed yet.
This macro can be used as HEAD, and it will replace itself with
the package name from RECIPE."
  :repeatable t
  :shorthand #'cadr)

(setup (:straight evil)
  (:also-load org)
  (setq evil-want-C-u-scroll t)
  (evil-mode 1))

(setup org
  (:also-load org-roam))

(setup (:straight org-roam)
  (setq org-roam-v2-ack t
        dw/daily-note-filename "%<%Y-%m-%d>.org"
        dw/daily-note-header "#+title: %<%Y-%m-%d %a>\n\n[[roam:%<%Y-%B>]]\n\n")
  (:option org-roam-directory "~/RoamNotes/"
           org-roam-dailies-directory "Journal/"
           org-roam-completion-everywhere t
           org-roam-capture-templates
            '(("d" "default" plain "%?"
               :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                                  "#+title: ${title}\n")
               :unnarrowed t)))
  (:global "C-c n l" org-roam-buffer-toggle
           "C-c n f" org-roam-node-find)
  (:with-map org-mode-map
    (:bind "C-c n i" org-roam-node-insert))
  (:when-loaded (org-roam-db-autosync-mode)))

(setup scheme-mode
  (:file-match "\\.sld\\'"))

(setup (:straight rainbow-mode)
  (:hook org-mode
         emacs-lisp-mode
         emacs-lisp-mode
         web-mode
         typescript-mode
         js2-mode))

leaf.el

Final config

(eval-and-compile
  (customize-set-variable
   'package-archives '(("melpa" . "https://melpa.org/packages/")
                       ("gnu" . "https://elpa.gnu.org/packages/")))
  (package-initialize)
  (unless (package-installed-p 'leaf)
    (package-refresh-contents)
    (package-install 'leaf))

  (leaf leaf-keywords
    :ensure t
    :init
    ;; optional packages if you want to use :hydra, :el-get, :blackout,,,
    (leaf hydra :ensure t)
    (leaf el-get :ensure t)
    (leaf blackout :ensure t)

    :config
    ;; initialize leaf-keywords.el
    (leaf-keywords-init)))

(leaf evil
  :ensure t
  :init
  (setq evil-want-c-u-scroll t)
  (evil-mode 1))

(leaf org
  :after evil
  :leaf-defer nil)

(leaf org-roam
  :ensure t
  :init
  (setq org-roam-v2-ack t)
  (setq dw/daily-note-filename "%<%Y-%m-%d>.org"
        dw/daily-note-header "#+title: %<%Y-%m-%d %a>\n\n[[roam:%<%Y-%B>]]\n\n")
  :custom
  (org-roam-directory . "~/RoamNotes/")
  (org-roam-dailies-directory . "Journal/")
  (org-roam-completion-everywhere . t)
  (org-roam-capture-templates .
   '(("d" "default" plain "%?"
      :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                         "#+title: ${title}\n")
      :unnarrowed t)))
  :bind (("C-c n l" . org-roam-buffer-toggle)
         ("C-c n f" . org-roam-node-find)
         (:org-mode-map
          (("C-c n i" . org-roam-node-insert))))
  :config
  (org-roam-db-autosync-mode))

(leaf scheme-mode
  :mode "\\.sld\\'")

(leaf rainbow-mode
  :leaf-defer nil
  :hook org-mode
        emacs-lisp-mode
        web-mode
        typescript-mode
        js2-mode)

Enjoyed this stream? Explore our hands-on courses for deeper, structured learning on Guile Scheme and more.

Get the System Crafters Newsletter
Updates on open source tools, tutorials, and community projects. We'll also occasionally let you know about new courses and resources.
Name (optional)
Email Address