Build faster by generating code with Hygen.io
- Hickās Law
- Parkinsonās Law
- The Shopping List
- Hygen
- Power to the Generator
- Adding a Module
- Use Hygen Today
The scalable code generator that lives in your project.
hygen is a code generator that lives in your project and scales with your team. It's fast, flexible, and perfectly fits modular code bases.
For the impatient, you check out the documentation and how to use it with Redux, React Native, and Express.js.
But if youād like a story about software design as well, keep reading. The story is told by hijacking a couple of fundamental user experience design laws (well, theyāre not really laws, but, yea).
Hickās Law
Hickās law states that:
The time it takes for a person to make a decision is a result of the possible choices he or she has: increasing the number of choices will increase the decision time.
Turning to technology, choosing a new tool is a massive uptake of new choices, and the process of learning that new tool amplifies it even more. Every step of the way, friction is the default.
As far as code generators go, hygen
has a primary design goal that is to minimize this friction. The restāāāis bonus.
Every code generator tool has a templating engine. But thereās also meta data; like where to place the new file at. To do metadata we placed a front-matter in the same template file, just like Jekyll does, and every Jekyll inspired blog generator that surfaced over the last few years (thereās plenty!).
If you have your own static blog generated with Jekyll, or any other tool thatās inspired by it, then this should be super familiar.
---
to: hygen-examples/mailers/<%= message || 'unnamed'%>/html.ejs
inject: true
before: "const modules = ["
skip_if: newModule
---
To render, we use ejs
- a ubiquitous, non restricted (as opposed to logic-less) templating engine. We donāt deal with the logic-less flamewar. If you want logic in your templates, go ahead; we trust that you're responsible. There's plenty of ways to shoot your own foot but above all we want to be pragmatic.
From a syntax point of view a variant of ejs
probably exists in what ever language you're coming from.
import View from './view'
storiesOf('<%= Name %>', module)
.addDecorator(withKnobs)
.addDecorator(withTests('<%= Name %>'))
.add('default', () => <View />)
And we take a batteries included approach; a set of built-in commands to guide you to the next step.
$ hygen init self
Loaded templates: src/templates
added: _templates/generator/with-prompt/hello.ejs.t
added: _templates/generator/with-prompt/prompt.ejs.t
added: _templates/generator/new/hello.ejs.t
$ hygen generator new --name mygen
Loaded templates: _templates
added: _templates/mygen/new/hello.ejs.t
Thereās also intuitive argument parsing.
$ hygen module new --name auth --tokens bcrypt
And fantastic prompts, courtesy of inquirer.
$ hygen database setup --host localhost
? Select database (Press <space> to select, <a> to toggle all, <i> to invert selection)
āÆāÆ Postgres
ā sqlite
āÆ MySQL
hygen
doesn't impose on you a new programming language, project setup, or workflow. Because it lives in your project, and in your existing workflow and setup, it just inherits those.
Parkinsonās Law
Letās hijack Parkinsonās law for a moment. This is the problem with every generator framework I know.
Work expands so as to fill all of the time available for its completion.
When you have code generation set up as a separate project in or out of your repo (say, with cookiecutter or yeoman), it will become a thing. A shiny new toy.
At best, it becomes a product you look after that throws you out of context every time you want to tweak a template. At worst, it becomes stale and unmaintainedāāāwork invested, and value never extracted.### Rails Did It First
Wholesale project generators are still good for when you need an entire starter-project, although I doubt will survive given the surge of starter projects for which you can just git clone
and move on.
Theyāre also less great if you want to embed a generator workflow into your existing project, like Rails did when it kickstarted the idea of having generators be a core part of developer productivity into mass adoption for the first time.
class CreateMigration < Thor::Actions::CreateFile
def migration_dir
File.dirname(@destination)
end
def migration_file_name
@base.migration_file_name
end
def identical?
exists? && File.binread(existing_migration) == render
end
...
Rails and Thor, the generator framework it used, changed the way I thought about code generators. Up until then, when I needed to generate code (mostly for ORM entities, ah the times we had!) I was sucked up into .NETās T4 Text Templates.
That was 7 years ago. I kept making generators with Thor and friends hoping for productivity spikes. But these became projects I maintained which sucked up valuable time, counter-intuitivelyāāāthey were lowering the returns in general productivity.
The Shopping List
That only means one thing. A shopping list for a generator framework that stays out of your way.
- Process ergonomicsāāāI donāt want to compile my generators, or have them on a CI pipeline of their own.
- Developer ergonomicsāāāeasily accessible and easily invoked.
- Low frictionāāāthe pitfall of success. Each step I take should lead me to the next.
- Technology agnosticāāādonāt want a new tech stack.
- Contextualāāāif Iām on the data layer, I want data generators.
- Scalableāāāshould work for multiple teams iterating over a large and modular codebase.
- Feature packedāāāsimple design doesnāt mean a poor number of features.
- Flexibleāāāgive me a way to shoot my foot if I want to.
- Embeddableāāācan be composed into other projects.
- Super customizableāāādefaults are OK, but give me escape hatches.
- Clean, intentfulāāāfirst be useful, only then be cool.
Thatās what hygen
became.
Hygen
You can use it right now, in what ever project you have open. Hereās how to make a generator that adds a markdown document to your docs/
folder.
$ npm i -g hygen
$ cd your-project
$ hygen init self
$ hygen generator new --name docs
Edit _templates/docs/new/hello.ejs.t
:
---
to: docs/<%= name %>.md
---
Hi!
I'm a document about <%= name %>
And then rename. The name of the file doesnāt matter, itās for you, for bookeeping purposes.
$ mv _templates/docs/new/{hello,new-doc}.ejs.t
Thatās it! Letās make a doc:
$ hygen docs new --name architecture
Loaded templates: _templates
added: docs/architecture.md
And now letās check our new generator in:
āÆ gs
A _templates/docs/new/new-doc.ejs.t
A _templates/generator/new/hello.ejs.t
A _templates/generator/with-prompt/hello.ejs.t
A _templates/generator/with-prompt/prompt.ejs.t
_templates
is a contextual folder. hygen
looks for one, at the folder youāre running it from. This way you can have different flavors of the same generator happen from different parts of your project, or maybe in a mono-repo scenario just different teams having their own sets of generators based on where in the mono-repo a team works at.
Note that the hygen generator new
command is also checked in (the generator/new
and generator/with-prompt
parts). This is part of the "flexibility" principle.
We acknowledge that it may be that you don't like the vanilla hygen
generator for new generators. Go ahead and change it; then check it in and have your team review it. After this change all new generators you build will incorporate your new wayto make them.
Power to the Generator
hygen
lets you build generators that add multiple files, make injections to existing files, have prompts for interactivity with the user, and more.
Hereās how to ease some Redux boilerplate fatigue.
These days I choose ducks, to remove a little bit of boilerplate and have modularity baked in any app I build.
My typical Redux architecture would look like this:
app/
components/
icon.js
avatar.js
modules/
boot.js <---- glues modules together, requires chat, app, and auth.
chat/
index.js <---- the 'connect' bit for Redux.
view.js <---- the view, separated, for testing.
state.js <---- reducer, actions, types, selectors.
app/
index.js
view.js
state.js
auth/
index.js
view.js
state.js
Adding a Module
Adding a new module is very easy with hygen
. Here's how your templates look like:
_templates/
module/
new/
index.ejs.t
view.ejs.t
state.ejs.t
inject_boot.ejs.t <--- adds a 'require' clause to boot.js
Hereās how index
looks like:
---
to: app/modules/<%= name %>/index.js
---
//
// requires, mappings, etc....
//
export default connect(...)(<%= Name %>)
A similar trick would do for view
and state
.
How would we add a require line given that boot.js
looks like this?
// ... some bootstrapping code ...
const modules = [
// <--- we want to inject a line here!
require('chat').default,
require('auth').default,
require('app').default,
]
// ... rest of bootstrapping code ...
Letās build inject_boot
:
---
to: app/modules/boot.js
inject: true
skip_if: <%= name %>
after: "const modules = ["
---
require('./<%= name %>).default,
And weāre done! Generating a new module is saying this:
$ hygen module new --name settings
Use Hygen Today
hygen
isnāt just for Redux or documentation, or the other use cases that we line out in the documentation. Itās for everything, because it imposes almost nothing.
Thereās more to see on hygen.io where the documentation lives at. But if you want a more gentle intro thereās also the digest form in the README and hygen
is built with zero configuration and no strings attached so no harm done trying it.
So if you like trying things first, just install it, make a few generators and see if it fits you.