packer
has become bloated, compilation (at least as it currently stands) has proven confusing and probably not necessary, and the codebase has become messy and hard to maintain.
As such, I'm proposing/working on (very slowly, as my OSS time is quite limited in my current job) an incremental rewrite of packer
.
The below is a high-level overview of this plan.
Comments and additions are welcome and encouraged!
packer
's code currently suffers from some poor software development practices largely stemming from getting "discovered" before it had time to become polished.
We want to avoid these pitfalls in the rewrite:
- Document all functions with emmylua-style comments:
packer
has very sparse documentation in its codebase. New/updated functions need to be appropriately documented. - Add tests where possible: testing a package manager can be a pain because of requirements to interact with the file system, etc. However,
packer
's current tests do not cover much, and new code should at least strongly consider adding unit tests. - Avoid giant functions:
packer
's logic contains some monstrous functions (e.g.,manage
, the main compilation logic, etc.) that are difficult to work with. New code should strive to use more, simpler functions in a more modular design. - Prioritize clean, performant code over total flexibility:
packer
's design allows for a very flexible range of input formats. Although we don't want to completely lose this property, supporting all of these formats (and other use cases) has contributed to code bloat. If there's a choice to be made between losing some flexibility and significantly increasing the complexity of the code, seriously consider removing the complexity. - Reduce redundancy:
packer
currently has an annoying amount of redundant/overlapping functionality (the worst offenders are therequires
/wants
/after
family of keywords, but there's also duplication of logic in the utilities, etc.). The rewrite should aim to reduce this.
We want the rewrite process to be as unintrusive as possible while still allowing for significant, potentially breaking changes. As such, the first stage should contain all of the significant breaking changes to reduce the number of times users need to adapt. The goals of the first stage are:
- Mostly remove compilation: this step involves moving to dynamic handlers for plugin specs and only "compiling" (really, caching) information that we can automatically recompile as needed, like additional filepaths to source, etc.
- Make main module lightweight: if
packer
is no longer relying on the compiled file being lightweight, its main module must become cheaper torequire
, as this will be necessary at all startups. This is mostly a question of reducing the amount thatpacker
pulls in at the top level (e.g., moving things like management operations more fully into their own modules, reducing the reliance on utils modules, etc.). - Implement fast runtime handler framework: To replace the compiled file, we will introduce a notion of "keyword handlers": simple functions which are responsible for implementing, at runtime, the behavior of a single previously-compiled keyword. See
lua/packer/handlers.lua
for the current spec and implementation of this idea. - Implement compiled keywords as handlers: Work in progress/current state of the rewrite. All the existing keywords from
packer/compile.lua
need to be ported to handlers. This involves dealing withcompile.lua
's tricky logic in some places, and is a key point for simplification. - Implement
load
function to process and execute handlers: The final step of moving away from compilation is to write a function that runs the handlers on the plugin specs to generate lazy-loaders, runsetup
andconfig
functions, etc. The trick here will be making this sufficiently fast. - Potentially implement path caching, etc.: Once the bulk of compilation is gone, we may wish to investigate adding a little bit back by seeing where the runtime system spends time during startup. If this includes a significant amount of time checking information that only changes when a plugin is installed or updated (e.g., searching for runtime paths), then we may want to start caching this information in a file that gets updated on updates to save startup time.
- Make main module lightweight: if
- Simplify
requires
/wants
/etc.- Unify
requires
/wants
intodepends_on
:requires
andwants
currently duplicate functionality. I propose merging the two into a single keyworddepends_on
, which will (1) ensure that dependencies are installed and (2) ensure that dependencies are loaded before the dependent plugin. - Make interface for
depends_on
andafter
consistent: the correct way to specify dependent plugins/sequential load plugins is confusing, since it's based on a plugin's short name in some cases but the full name in others (I think?). We should define and implement a clear way of referring to plugins in these settings.
- Unify
- Clean up interface
- Use new Neovim v0.7.0 functions instead of
vim.cmd
: the codebase makes use of a lot ofvim.cmd
with string building for things like generating keymaps, commands, etc. Neovim v0.7.0 introduced the ability to create these directly from Lua, with direct Lua callbacks.packer
should move to use these functions. - Make management operations sequenceable steps: To clean up and simplify the code for management operations (e.g., installs, updates, cleans, etc.), we want to redesign the operation interface into sequenceable steps (e.g., "check current FS state", "clone missing plugins", "pull installed plugins", etc.) so that the actual operations become a sequence of calls to functions implementing these steps, rather than the current highly-duplicated logic.
- Use new Neovim v0.7.0 functions instead of
The nastiest part of the codebase is the git
logic.
We would like to simplify this as much as possible.
Also, a significant contributor to the difficulty of working on packer
is its aggressive use of async
everywhere.
We should investigate how to minimize the async surface area to allow more flexibility and require less of the codebase to need to think about async
.
packer
has accumulated a large number of outstanding issues.
At this point, we should go through and try to reproduce issues with the updated codebase.
During this process, issues should either be fixed or closed as "Wontfix" or "No longer relevant" unless they are feature requests or questions.
The goal of this stage is to more or less close out the open issues.
Teal offers significant benefits for maintainability and has improved since the last time I took a look at porting packer
.
After packer
's code is at a cleaner, more reliable state, it makes sense to start porting modules incrementally to Teal.
Finally, we can start thinking about new features and improvements.
Things like the proposed unified plugin specification format, improvements to Luarocks (e.g., updating rocks, putting binary rocks on the Neovim PATH
, etc.)