Updating existing files with Yeoman Posted on: 2013-10-06

I've recently gotten into this whole Yeoman thing and I've gotta say that it's pretty darn awesome and has come a long way since its early days.

My use case

I was trying to improve the workflow at work the other day by setting up a Yeoman generator that does a few things whenever we create a new component:

  1. Creates the view and Sass partials for the component.
  2. Creates the view for our styleguide (I'll prod @ianfeather to blog about this at some point - it's a pretty awesome set up).
  3. Creates a .yml file that we can update with some stubbed (read: fake) data to pass to the styleguide view.
  4. Updates the styleguide routes file so the new component has it's own url.
  5. Updates the styleguide controller to render the new view.
  6. Updates the styleguide helper so the new component shows up in the left nav.

The first 3 points are pretty straight forward and the process is explained really well in the generator docs. Now I don't know if my Google-Fu has lessened of late, but I really struggled to find out how to do this so I turned to stackoverflow and twitter. @addyosmani got back to me and pointed me in the right direction, and I later happened upon another article with a short section explaining how to do it.

The solution

If you haven't read my answer to my own question on the stackoverflow link above, it boils down to just two simple functions: readFileAsString and write. Nice and easy!

Hold up a second though... how do we know where to put the updates in the file? Take the routes.rb file for example, it looks a bit like this:

# Styleguide
get 'styleguide' => 'styleguide#index'
get 'styleguide/buttons' => 'styleguide#buttons'

Using the readFileAsString method gives you the entire file as a string so we could write some long winded code to find the last occurrence of something that looks like one of those routes and append our new one after that, but the thought of doing that kinda grossed me out if I'm honest, so I came up with a better way...

Introducing: Yeoman Hook

No, this isn't yet another library or plugin, but rather a convention. Using a very specific comment, we can say exactly where we want the new stuff to go. This is what I ended up with:

#===== yeoman hook =====#
# NB! The above line is required for our yeoman generator and should not be changed.

This will sit under that buttons route and is really easy for us to use within our yeoman generator. I put a comment underneath it so that whoever else happens upon it doesn't just remove it (rather be safe than sorry).

Now within our generator's code, we can put the following:

StyleguideComponentGenerator.prototype.routes = function app() {
  var hook   = '#===== yeoman hook =====#',
      path   = 'config/routes.rb',
      file   = this.readFileAsString(path),
      slug   = this.name.toLowerCase().replace(/ /g, '_'),
      insert = "get 'styleguide/"+slug+"' => 'styleguide#"+slug+"'";

  if (file.indexOf(insert) === -1) {
    this.write(path, file.replace(hook, insert+'\n'+hook));
  }
};

Note: we're adding in a line break with \n so as to preserve the position of the hook for future calls to this generator.

We don't need to worry about logging to the console that this update happened... Yeoman will automatically prompt you about overwriting the file:

Yeoman's write file confirmation

A word of caution

Things can get a bit touchy when it comes to indentation and new lines, so be sure to use console.log() to verify it's doing what you expect before committing to this.write().

Go forth...

That's all there is to it, so go streamline your workflow just that little bit more!