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)
Subscribe to the System Crafters Newsletter!
Stay up to date with the latest System Crafters news and updates! Read the Newsletter page for more information.
Name (optional)
Email Address