Cookiecutter vs Yeoman

2019 April 24

Over the past few days, I've been trying to build a reusable boilerplate for Python packages (in the style that I write them). In the Python community, the natural choice is Cookiecutter, but I've been left disappointed by it.

I already knew of Yeoman from the JavaScript community, but hadn't written a generator for it yet. If I were to just search Google for "boilerplate generator" or "software scaffolding tool", Yeoman is the top relevant result. Granted, it seems odd to offer a Python project template through the Node ecosystem, but after a little experience with both, Yeoman seems more principled and more polished than Cookiecutter:

  • Yeoman has configuration functions out of the box. Cookiecutter puts the default configuration in a JSON file. Thus, Cookiecutter cannot handle dynamic defaults, e.g. pulling the default author from your Git configuration.

  • Yeoman has the concept of composable generators, which lets me reuse someone else's implementation of license choices. Cookiecutter does not have composable generators.

  • A Yeoman generator's dependencies are installed by your package manager when you install the generator. Cookiecutter requires the user to discover and install a generator's dependencies before running the generator.

  • Yeoman has better looking documentation, which in my opinion is a good indicator of project maturity. Cookiecutter has scrambled documentation. Some sections are incomplete, and some appear to be duplicates (e.g. "Learn the Basics of Cookiecutter by Creating a Cookiecutter" and "Create a Cookiecutter From Scratch").

  • At the time of this writing, Cookiecutter's master branch was last changed 4 months ago, and the docs were last changed 11 months ago, which suggests to me that the project is unmaintained. Given the gaps above, I do not believe this project is complete enough to transition to a maintenance mode.

That said, Yeoman has its own gaps.

  • It is not possible to pull data out of a subgenerator. One example is a license generator that returns the name of the chosen license, which another generator can insert into a package metadata file, e.g. pyproject.toml.

  • The API uses promises in some places (e.g. prompt, via Inquirer) and callbacks in others (e.g. spawnCommand), leaving me to promisify them to unlock convenient async/await syntax.

  • The API documentation leaves much to be desired. The prompt API is documented in the tutorial, but not in the reference. spawnCommand is documented with return type String, when it really returns a Node child_process.ChildProcess.

  • The Generator base class has undocumented fields that you must not touch. I learned this the hard way by trying to use the property config.

  • Command-line options must be declared in the constructor. That means you cannot use asynchronous functions, like the one that looks up the GitHub username, to fill in default values.

  • Yeoman misses the opportunity to offer a command-line option for every prompt and skip the prompt if it were passed on the command-line. Even if I wanted to implement this behavior myself, it is tedious. The only way to detect the difference between an omitted command-line option and an explicit option that just happens to use the default value is to not declare a default, but that leaves me to manually document the default in the option description. Regardless, I still cannot document defaults that are computed from asynchronous functions.