Pure Emacs-Lisp Init Skeleton

Last year I rewrote my configuration from scratch as I migrated from Spacemacs to vanilla Emacs. For config style, I chose Org Babel - which normally involves using a short Lisp file (init.el) to tangle and load actual config in Org mode (config.org) - for its readability and easy navigation. After a while, however, I find it not as handy as expected:

  1. At least 2 files are to be maintained, which adds unnecessary complexity
  2. config.org needs to be re-tangled for every single change, which gets slow as code blocks accumulated, and gets annoying with frequent restarts when I tried out new packages
  3. Debugging is not straightforward. If I made any mistake (miss a parenthesis, typo), error prompts will refer to locations in the tangled Lisp file (config.el) instead of original Org file

I figured I don’t really need full-featured Org-mode (scheduling, deadlines, agenda, etc..) for a simple init file. What I really need is easy cycling navigation through the file, and, as I mainly use use-package, the ability to jump to a use-package definition by name directly. After a little research, I found that for the former outshine comes to rescue, and for the latter, use-package provides a useful option use-package-enable-imenu-support.

So here it is, a minimal skeleton for Emacs init.

;;; init.el --- skeleton config  -*- lexical-binding: t; coding:utf-8; fill-column: 119 -*-

;;; Commentary:
;; A bare-boned config template. Use "outshine-cycle-buffer" (<Tab> and <S-Tab>
;; in org style) to navigate through sections, and "imenu" to locate individual
;; use-package definition.

;;; Bootstrap
;; Speed up startup
(setq gc-cons-threshold 402653184
      gc-cons-percentage 0.6)
(add-hook 'after-init-hook
          `(lambda ()
             (setq gc-cons-threshold 800000
                   gc-cons-percentage 0.1)
             (garbage-collect)) t)

;; Initialize package.el
(require 'package)
(add-to-list 'package-archives
             '("melpa" . "https://melpa.org/packages/"))
(package-initialize)

;; Bootstrap `use-package'
(setq-default use-package-always-ensure t ; Auto-download package if not exists
              use-package-always-defer t ; Always defer load package to speed up startup
              use-package-verbose nil ; Don't report loading details
              use-package-expand-minimally t  ; make the expanded code as minimal as possible
              use-package-enable-imenu-support t) ; Let imenu finds use-package definitions
(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))
(eval-when-compile
  (require 'use-package))

;; Add system-wide defaults here, for example:
;;
;; (setq-default inhibit-startup-message t
;;               initial-scratch-message nil)

;; Add all use-package definitions from here

(use-package outshine
  ;; Easier navigation for source files, especially this one.
  :bind (:map outshine-mode-map
              ("<S-iso-lefttab>" . outshine-cycle-buffer)
              )
  :hook (emacs-lisp-mode . outshine-mode)
  )

;; When config gets stable, using emacs server may be more convenient
;; (require 'server)
;; (unless (server-running-p)
;;   (server-start))

;;; init.el ends here

Main features:

  1. Use outshine for cycling visibility in Org mode style
  2. Use imenu/counsel-imenu to locate individual package configuration
  3. Apply speed up techniques (modified from John Wiegley’s config)

From there I base all my configurations using use-package, and have a startup time of 0.9s with 100+ packages (Main trick: use :defer [N] to defer-load all non-necessary packages. The only must-haves for me are counsel and org).

Hope that’s useful for anyone thinking to rewrite their configs.


Update on 2019-05-30: My init file is published at yiufung/dot-emacs following the same idea.