There was a recent appearance on HN of an article about improving
Emacs startup time, with a few mentions of
use-package. This was
topical for me as I'd just been through my own yak-shaving exercise
(startup time now down to about 1.5s, and yes this is immaterial
because I run it as a daemon on login).
There were plenty of useful suggestions, but lacking for me was any
discussion of exactly how
use-package works, so this article
attempts to explain the ones I found most useful.
Background: investigating for yourself
By far the simplest tactic is to just see what
use-package is doing:
it is a macro, so you can look at the code generated. I've been using
ielm for convenience.
This is the basic code generated when requiring a package1.
ELISP> (macroexpand '(use-package foo)) (progn (defvar use-package--warning146 #'(lambda (keyword err) (let ((msg (format "%s/%s: %s" 'foo keyword (error-message-string err)))) (display-warning 'use-package msg :error)))) (condition-case-unless-debug err (if (not (require 'foo nil t)) (display-warning 'use-package (format "Cannot load %s" 'foo) :error)) (error (funcall use-package--warning146 :catch err))))
Taking out the error handling boilerplate, we're left with:
(if (not (require 'foo nil t)) (display-warning 'use-package (format "Cannot load %s" 'foo) :error))
In other words, require the package!
Now let's look at some variations.
All of these are standard start-up hacks, implemented in a nice DSL by use-package.
Firstly, let's see the difference when using
ELISP> (macroexpand '(use-package foo :defer t)) (progn (defvar use-package--warning147 #'(lambda (keyword err) (let ((msg (format "%s/%s: %s" 'foo keyword (error-message-string err)))) (display-warning 'use-package msg :error)))) (condition-case-unless-debug err nil (error (funcall use-package--warning147 :catch err))))
Stripping away the boilerplate and we're left with… nothing! That shouldn't be surprising; we don't load the package, we'll need to take care of that ourselves somehow. Most of the remaining snippets we'll look at concern themselves with different ways of ensuring that a package is loaded when needed, but not at startup.
On its own,
:defer t is mainly useful for packages that will be
loaded by other packages.
:defer keyword can also take a numeric argument, representing
the number of seconds to wait before loading. Looking at the
ELISP> (macroexpand '(use-package foo :defer 1)) ;... (run-with-idle-timer 1 nil #'require 'foo nil t) ;...
In other words, use an idle timer to delay requiring the package for a second. This is useful for large packages that can take a while to load, but that may not be necessary immediately (which is probably most of them, if you're launching from your login profile!). Helm was one such candidate for me.
A large part of the work that use-package does is to defer loading a
package by configuring autoloads. This is a core facility provided by
Emacs to trigger loading of a package only when some function is first
called, and there are a few ways
use-package identifies functions to
use as triggers.
Possibly the most common requirement is to associate a command from a package with a keybinding; this is Emacs after all! Use-package offers a rich and convenient way to do this:
(use-package foo :bind ("C-cd" . foo-bar))
This associates the interactdive command
foo-bar, assumed to be in
foo, to the key sequence control-c, d. The expansion is
(progn (unless (fboundp 'foo-bar) (autoload #'foo-bar "foo" nil t)) (bind-keys :package foo ("C-cd" . foo-bar)))
In order, this:
Checks that the function
foo-barisn't already loaded, and assuming it isn't;
Sets up an autoload (note that we didn't have to configure this ourselves) triggered by the
Binds the function to the desired key sequence2.
Naturally as a result it does not need to
require the package.
This is the core of use-package; anything that can be identified as
deferable, will have an autoload configured for it.
Another very common requirement is to associate a package (mode) with
a file-type. Again,
use-package offers a very convenient way to do
(use-package foo :mode "\\.asdf\\'")
The expansion, again:
(progn (unless (fboundp 'foo) (autoload #'foo "foo" nil t)) (add-to-list 'auto-mode-alist '("\\.asdf\\'" . foo)))
In other words, set up another autoload, and add it to the
auto-mode-alist. This of course assumes that the name of the
package is the name of the mode; if our package was still called
but our mode was actually
foo-mode, the syntax variant will set up
the autoload and alist for
(use-package foo :mode ("\\.asdf\\." . foo-mode))
Another common scenario is to configure a hook from the package; for
example, to turn on
prog-mode-hook (ie, all
programming languages). Once again
use-package provides convenient
syntax, and by now the expansion should not be a surprise:
(use-package flycheck-mode :hook prog-mode)
With expansion (note, the syntax saves you the hassle of adding the
-mode suffix, although this behaviour can be altered)
(progn (unless (fboundp 'flycheck-mode) (autoload #'flycheck-mode "flycheck-mode" nil t)) (add-hook 'prog-mode-hook #'flycheck-mode))
Finally, you can manually specify commands to autoload. For example, I have a package to generate lorem-ipsum text. It's rarely used and I don't want to bind any of the commands, so I also don't want the package loaded unless I need it.
Your friend here is
:commands, and you can guess the expansion by
(use-package lorem-ipsum :commands (lorem-ipsum-insert-sentences lorem-ipsum-insert-paragraphs))
Dependencies and configuration
Now that everything is autoloaded, a secondary step is to ensure that
a package isn't inadvertently loaded by another package. You can use
:defer here if you know that a package is only used by something
else, for example you want to ensure3 some library is installed,
but only required by packages that needed it.
Sometimes however you want to load an entire package as part of
another package. In this case, you can specify
:after; an example
from my own setup involves dired extensions:
(use-package dired-quick-sort :after dired)
The expansion this time looks a little different:
(eval-after-load 'dired '(if (not (require 'dired-quick-sort nil t)) (display-warning 'use-package (format "Cannot load %s" 'dired-quick-sort) :error)))
The core functionality this time is
eval-after-load, which is
another special form that only executes its body after another package
This same macro is often used in old-school Emacs configurations to
delay configuration of a package until it has been loaded. Not
use-package has your back here too; this is exactly
:config does when used in conjunction with anything triggering
(use-package dired-quick-sort :commands (dired-quick-sort) :config (dired-quick-sort-setup))
And the core expansion:
(progn (unless (fboundp 'dired-quick-sort) (autoload #'dired-quick-sort "dired-quick-sort" nil t)) (eval-after-load 'dired-quick-sort '(condition-case-unless-debug err (progn (dired-quick-sort-setup) t) (error (funcall use-package--warning206 :config err)))))
In other words, defer loading the package by setting up an autoload, and then also configure some code to run when the package does finally load.
Hopefully by now you have a better undersatnding of what
is doing, and how to use it to improve your own startup time:
macroexpandin order to check what
Only load packages when necessary, using whatever is appropriate to ensure it will be autoloaded;
Set the variable
t— this prints out each package-load to the
*Messages*buffer, so you can detect if something is being loaded earlier than you would like, and it also warns if something takes too long in loading (more than 0.3s by default).
(and if you really want to lower that number!) Use idle-timers to defer non-essential packages until after startup.
I actually see a bit more boilerplate because I enable both
use-package-always-ensure and recently
is handy for identifying slow-loading packages.
bind-keys is a separate package but managed as part of
use-package-always-ensure set, or you can manually add