use-package autoload 的问题

设置 use-package 的 defer 为 t 后,Emacs 是如何加载这个 package 的?展开 use-package 宏后,也没看到有 load 对应 package 的 autoload 文件的命令,难道是等其他地方 require 或者 autoload?这里有些迷惑。

按道理,autoload 语句必须要 evaluated 才能被知道哪个函数是 autoload 的吧?

补充:user-package 文档里的一句话:

In almost all cases you don’t need to manually specify :defer t. This is implied whenever :bind or :mode or :interpreter is used. Typically, you only need to specify :defer if you know for a fact that some other package will do something to cause your package to load at the appropriate time, and thus you would like to defer loading even though use-package isn’t creating any autoloads for you.

这句话意思是不是表示必须要确认有其他package 会导致 load 该 package 的时候,才能使用 defer? 也就是说 defer 并不能乱用? 乱用会导致 package 不能被 load,是这意思吧?

贴一下我之前写的关于use-package加载机制的博文:

:after

     ;;; Export to twitter bootstrap
     (use-package ox-twbs
       :after org
       :ensure ox-twbs
       )

:after 关键字的作用基本跟 with-eval-after-load 的作用是相同的,所以我所 有类似的org-mode 插件包都会在org-mode 加载以后才会加载

:commands

     (use-package avy
       :commands (avy-goto-char avy-goto-line)
       :ensure t)

这里就直接贴上 use-package文档的说明了:

When you use the :commands keyword, it creates autoloads for those commands and defers loading of the module until they are used

也就是 :commands 关键字就创建了后面所接的命令的 autoloads 机制了

:bind :mode

     (use-package hi-lock
       :bind (("M-o l" . highlight-lines-matching-regexp)
              ("M-o r" . highlight-regexp)
              ("M-o w" . highlight-phrase)))

     (use-package vue-mode
       :ensure t
       :mode ("\\.vue\\'" . vue-mode)
       :config (progn
                 (setq mmm-submode-decoration-level 0)
                 ))

附上文档说明

In almost all cases you don’t need to manually specify :defer t. This is implied whenever :bind or :mode or :interpreter is used

也就是说,当你使用了 :bind 或者 :mode 关键字的时候,不用明确指定 :defer 也可以实现延迟加载机制。当然你也可以,直接使用 :defer 关键字来指定延迟加载 不过前提是,你要明确它加载的时机

Typically, you only need to specify :defer if you know for a fact that some other package will do something to cause your package to load at the appropriate time, and thus you would like to defer loading even though use-package isn’t creating any autoloads for you.

贴上我自己的代码,可以更加清晰

  (use-package anaconda-mode
    :defer t
    :ensure t
    :init(progn
           (add-hook 'python-mode-hook 'anaconda-mode)
           (add-hook 'python-mode-hook 'anaconda-eldoc-mode)
           ))

这样 anaconda-mode 就会在 python-mode 加载以后被加载

1 个赞

恩,看着跟我理解的是一样的,使用 defer 必须要自己明确加载时机,如果没有其他 package 或者 add-hook/ with-evil-after-load 等,那么这个package就不会被加载了。

对于 elpa 安装的包,会自动根据代码中的 ;;;###autoload 标记生成 autoloads 文件并加载,

自定义的包应该要自己明确指定加载时机。对于 elpa 的包,自动生成 autoload 文件后,是在哪儿加载的?

是 emacs 自己做的事情还是 use-package 做的事情?

use-package 宏展开后没有看到相关代码,如果是 emacs 自己做的这个事情,那对于直接 require 的 package,岂不是多执行了一次 autoload 的文件?

elpa 自动加载 autoloads 文件,里面就是一堆 autoload 定义,比如这样:

;;; parinfer-autoloads.el --- automatically extracted autoloads
;;
;;; Code:
(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path))))

;;;### (autoloads nil "parinfer" "parinfer.el" (23001 7231 113740
;;;;;;  239000))
;;; Generated autoloads from parinfer.el

(autoload 'parinfer-mode "parinfer" "\
Parinfer mode.

\(fn &optional ARG)" t nil)

(autoload 'parinfer-region-mode "parinfer" "\
Available when region is active.

\(fn &optional ARG)" t nil)

;;;***

;;;### (autoloads nil nil ("parinfer-ext.el" "parinfer-pkg.el" "parinferlib.el")
;;;;;;  (23001 7231 115752 444000))

;;;***

;; Local Variables:
;; version-control: never
;; no-byte-compile: t
;; no-update-autoloads: t
;; End:
;;; parinfer-autoloads.el ends here

这些 autoloads 在 (package-initialize) 的时候会被加载。

use-package 用了 defer 以后就不负责加载,只负责在相关包加载时执行对应配置。

举例,你安装了 notmuch,用 use-package defer,并用它配置了按键,刚打开时,notmuch 是没有加载的, 只有一个 autoload notmuch 的定义,只有执行 M-x notmuch 以后才会根据 autoload 加载对应的文件。

1 个赞

好的,那就是 package-initialize 干的事情了

另外,问个问题,你这个配置最后的:

(add-hook 'python-mode-hook 'anaconda-mode)

这个 add-hook 会去调用 (require 'anaconda-mode) ? 如果没有加载 anaconda-mode.el,而 add-hook 只是把 anaconda-mode 当做一个 function,那如果没有加载 autoload 文件和 anaconda-mode.el 文件,那应该会出现找不到 anaconda-mode 的错误吧?

补充: 测试后确实是这样的,你最后的配置能够正常工作是因为,anaconda 是 elpa package,有 autoload 文件,并在 package-initialize 时候加载了 autoload 文件,这样当你 add-hook 后,会通过 autoloading 机制加载 anaconda,然后再去执行 anaconda-mode 。但是对于自定义的 package,这样是不行的了,只能通过 bind, commands 等方案将某个函数声明一次 autoload,然后就可以autoload了,或者自己生成 autoload 文件后,在 user-package 的 init 阶段 load一下自定义 package 的 autoload 文件

我感觉应该会出现这样的问题,只是我在add-hook 之前是看了一下anaconda-mode的源码

;;;###autoload
(define-minor-mode anaconda-mode
  "Code navigation, documentation lookup and completion for Python.

\\{anaconda-mode-map}"
  :lighter anaconda-mode-lighter
  :keymap anaconda-mode-map)

;;;###autoload
(define-minor-mode anaconda-eldoc-mode
  "Toggle echo area display of Python objects at point."
  :lighter ""
  (if anaconda-eldoc-mode
      (turn-on-anaconda-eldoc-mode)
    (turn-off-anaconda-eldoc-mode)))

所以手动指定defer一般是我最后的选择,可以的话,都是先用其他的方式