{"id":241956,"date":"2016-06-03T06:04:41","date_gmt":"2016-06-03T13:04:41","guid":{"rendered":"http:\/\/css-tricks.com\/?p=241956"},"modified":"2017-04-10T17:52:38","modified_gmt":"2017-04-11T00:52:38","slug":"using-yeoman-changed-way-work","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/using-yeoman-changed-way-work\/","title":{"rendered":"How Using Yeoman Changed the Way We Work"},"content":{"rendered":"

The following is a guest post by Noam Elboim<\/a>, a developer at myheritage.com. Noam has dove head-first into Yeoman<\/a> – from not knowing anything or understanding how it could help at work, to building a custom generator just for them. If you think a tool to help you scaffold out new projects to your liking could help you, check out Noam’s journey!<\/em><\/p>\n

<\/p>\n

On my first day at MyHeritage<\/a>, I remember this exchange with the web development team leader:<\/p>\n

\n

Team Lead:<\/strong> You have some experience working with Node.js modules like Yeoman, right?<\/p>\n

Me:<\/strong> Yeah, I know a bit.<\/p>\n

Team Lead:<\/strong> I think using Yeoman here would save us a bunch of time on creating new pages. Do you think you can manage something like that?<\/p>\n<\/blockquote>\n

Well, that was my first day; I barely knew where my seat was. However, I remember clearly what I was thinking, and I didn\u2019t understand how Yeoman could help here.<\/p>\n

For me, and for many others, Yeoman was a tool to generate a full working application, from zero to hero, in no more than two seconds. It was not for adding parts to an existing application.<\/p>\n

I was completely wrong. Allow me to share with you how we created a Node.js module at MyHeritage that extends the functionality of Yeoman to save us tons of time on everyday tasks.<\/p>\n

The Problem<\/h3>\n

Creating new pages in MyHeritage used to be a challenging task. Each new page that was created demanded about two days of work to prepare everything, including asset management, a server-side component and a client-side bootstrap of an application. In short: a large number of files.<\/p>\n

Most developers do not create new pages on a regular basis. Before the real work could even begin, a lot of time was required just to understand how to create the new page. Then, once the page was created and the work was \u201cdone\u201d, there were still hours of time remaining to debug anything that didn\u2019t work.<\/p>\n

Two days was just way too much time to invest in a simple task like that.<\/p>\n

A Ray of Hope<\/h3>\n

A couple of months after my conversation with our team lead, the toll of creating new pages had become significantly greater and we couldn\u2019t continue down that path. We knew that an immediate solution was necessary and that it must be simple, easy to maintain, and most obviously, very fast to use. <\/p>\n

First, we broke down the problems associated with creating a new page. The process required a lot of steps, including routing configurations and translations services, running terminal commands for creating certain files (like A\/B tests or Sass files) and even running some Gulp tasks (like compiling Sass or creating sprites).<\/p>\n

At this point we were certain that extending Yeoman would serve us best, but we still had to figure out how it could satisfy all of these needs while still maintaining its core simplicity and ease of use.<\/p>\n

A Few Words on Yeoman<\/h3>\n

Yeoman helps you to kickstart new projects, prescribing best practices and tools to help you stay productive.<\/p><\/blockquote>\n

That is the first line on Yeoman\u2019s website and it\u2019s 100% right.<\/p>\n

It\u2019s a Node.js module with thousands of generators to choose from. All generators basically work the same \u2014 first the user is asked a set of questions in user interfaces that are based on Inquirer.js. Then, using the user\u2019s input, Yeoman generates the proper new files.<\/p>\n

\"\"
A string input prompt for providing the name of the project.<\/figcaption><\/figure>\n
\"\"
A checkbox prompt is used to set the \u201cMaster Page\u201d configuration.<\/figcaption><\/figure>\n

Why Yeoman<\/h3>\n

We had a range of options from using a full working generator to building something from scratch. There are a couple of reasons we chose Yeoman:<\/p>\n

First of all, simplicity. Yeoman features a clear API, a generator\u2019s generator for getting started, and serious ease-of-use as a Node.JS module.<\/p>\n

Second, maintainability. Yeoman is being used by thousands of people worldwide and it\u2019s based on the Backbone.js Model which allows it to be extended in a way that is easy to understand.<\/p>\n

Finally, speed. Simply put: speed is a given with Yeoman.<\/p>\n

Our solution<\/h3>\n

We wanted to add another stage between the two halves of Yeoman\u2019s flow to extend the data that we collect from the user through an ordered set of functions. For example, we may want the ability to get snake_case from CamelCase, or to capture the output of a command executed in the terminal. We require this stage to be configurable via a JSON file. <\/p>\n

We break every function down to an object that includes the name of the function to execute, the arguments, and where to store the output. We use Node.JS so that those functions run asynchronously and return promises. When all promises resolve, we generate the files. Here is an example of how the Generator class uses the Yeoman-generator module, which is named \u201cgenerators\u201d:<\/p>\n

var generators = require('yeoman-generator'),\r\n    services, prompts, actions, preActions, mainActions,\r\n    _this;\r\n  \r\nmodule.exports = Generator;\r\n\r\n\/**\r\n * Generator object for inheritance of Yeoman.Base with different services, prompts and actions files.\r\n * To use the generator in you Yeoman generator, create a new Generator instance with your local files and export the return value of Generator.getInstance()\r\n * @param externalService - local generator services.js file\r\n * @param externalPrompts - local generator prompts.json file\r\n * @param externalActions - local generator actions.json file\r\n * @constructor\r\n *\/\r\nfunction Generator (externalService, externalPrompts, externalActions) {\r\n  services = externalService;\r\n  prompts = externalPrompts;\r\n  actions = externalActions;\r\n  preActions  = actions.pre;\r\n  mainActions = actions.main;\r\n}\r\n\r\n\/**\r\n * Get instance will create extension to the Yeoman Base with your local dependencies\r\n * @returns {Object} Yeoman Base extended object\r\n *\/\r\nGenerator.prototype.getInstance = function () {\r\n  return generators.Base.extend({\r\n    initializing: function () {\r\n      _this = this;\r\n      this.conflicter.force = true; \/\/ don't prompt when overriding files\r\n    }, \r\n    prompting: function () { \/* ... *\/  },\r\n    writing:  function () { \/* ... *\/  },\r\n    end:  function () { \/* ... *\/  }\r\n  });\r\n};<\/code><\/pre>\n

How to Build a Generator<\/h3>\n

Once the base is ready, creating a new generator is a piece of cake. We just need to create a new folder containing 4 files and a folder of templates in it. The name of the folder is the name of the generator. The 4 files are:<\/p>\n

    \n
  1. Prompts<\/strong> \u2014 a JSON file full of objects which map prompted questions to the stored data.\n
    [\r\n  {\r\n    \"type\": \"input\",\r\n    \"name\": \"name\",\r\n    \"message\": \"Name your generator:\",\r\n    \"default\": \"newGenerator\"\r\n  },\r\n  {\r\n    \"type\": \"list\",\r\n    \"name\": \"commonServices\",\r\n    \"message\": \"Want some common services:\",\r\n    \"choices\": [\"yes\", \"no\"]\r\n  }\r\n]<\/code><\/pre>\n

    In this example, the first item will ask the user to “Name your generator:” with the default answer of “newGenerator”. It stores the answer in the data object by the field \u201cname\u201d. <\/p>\n

    The second item asks a multi-choice question with the options \u201cyes\u201d and \u201cno\u201d. The default is the first choice, \u201cyes\u201d.<\/li>\n

  2. Services<\/strong> \u2014 a JS file with all the functions we need for extending the data from the user.\n
    var exec  = require('child_process').exec,\r\n  chalk = require('chalk'),\r\n  Q     = require('q');\r\n\r\nmodule.exports.generateSprite = generateSprite;\r\n\r\n\/**\r\n * Run gulp sprites in our new sprite folder\r\n * @param {String} name - the name of the project\r\n * @return {Object | Promise}\r\n *\/\r\nfunction generateSprite (name) {\r\n  return Q.Promise(function (resolve, reject) {\r\n    console.info(chalk.green(\"Compiling new sprite files...\"));\r\n    if (name) {\r\n      exec('gulp sprites --folder ' + name, function () {\r\n        resolve();\r\n      });\r\n    }\r\n    else reject(\"Missing name in generateSprite\");\r\n  });\r\n}<\/code><\/pre>\n

    This example explains a service called \u201cgenerateSprite\u201d, which will run a terminal command that is already configured for the project. This specific script creates a sprite folder. If the function manages to run the command successfully, it will resolve a promise that will mark the operation as successful. Templates can be generated based on the outcome.<\/li>\n

  3. Actions<\/strong> \u2014 a JSON file that maps of all the functions that we need to execute before generating files \u2014 the \u201cpre\u201d section \u2014 and another mapping of template files and their final location where the files will be generated, which is the \u201cmain\u201d section.\n
    {\r\n  \"pre\": [\r\n    {\r\n      \"dependencies\": [\"name\"],\r\n      \"arguments\": [\"name\"],\r\n      \"output\": \"spriteFolder\",\r\n      \"action\": \"generateSprite\"\r\n    }\r\n  ],\r\n  \"main\": [\r\n    {\r\n      \"dependencies\": [\"name\"],\r\n      \"optionalDependencies\": [\"spriteFolder\"],\r\n      \"templatePath\": \"output_file.js\",\r\n      \"destinationPath\": \".\/sprites\/<%= name %>output_file.js\"\r\n    }\r\n  ]\r\n}<\/code><\/pre>\n

    In the \u201cpre\u201d section there is a task which will run only if the field \u201cname\u201d is defined or true (based on the requirements defined in the \u201cdependencies\u201d array of fields). The arguments to provide in the service function inside the \u201cargs\u201d object by the \u201carguments\u201d array. The output of the function is saved in the location specified by the \u201coutput\u201d field. The name of the function is defined by the \u201caction\u201d field. In this implementation you can provide only one function, but it could easily support multiple functions by using an array in \u201caction\u201d.<\/p>\n

    In the \u201cmain\u201d section there is a task with similar functionality. If a field in the \u201cdependencies\u201d is missing or faulty, then the file will not be generated. However, this is not the case with \u201coptionalDependencies\u201d which are, in fact, optional. The fields exposed to the templating engine are defined in the \u201cdependencies\u201d and \u201coptionalDependencies\u201d.<\/p>\n

    The \u201ctemplatePath\u201d has the path to the template file and the \u201cdestinationPath\u201d is the destination for the file. \u201cdestinationPath\u201d can use data from the \u201cpre\u201d section as we see here with the \u201cname\u201d field (<%= name %><\/code>).<\/p>\n

    Notice that we use EJS templating language. Yeoman actually supports additional templating engines beside EJS, like handlebars.<\/li>\n

  4. Index<\/strong> \u2014 a JS file. Yeoman requires that each generator has an index file. The index links the 3 other files and inherits from the base. Basically, it creates an instance of the base with separated prompts, services and actions.\n
    var Generator   = require('..\/generator'),\r\n    services    = require('.\/services'),\r\n    prompts     = require('.\/prompts'),\r\n    actions     = require('.\/actions');\r\n    \r\nvar generator = new Generator(services, prompts, actions);\r\n    \r\nmodule.exports = generator.getInstance();<\/code><\/pre>\n

    As you can see in this example, it creates the instance and exports it to work with Yeoman.<\/li>\n

  5. Templates<\/strong> \u2014 easy as it sounds: just templates to create files.\n
    <h1>My first template in <%= name %>!<\/h1>\r\n<% if (spriteFolder) { %>\r\n  I even managed to create sprites for it!\r\n<% } %><\/code><\/pre>\n

    Note that the balance between extending the data and creating templates is completely dependant upon the kind of generator you would like to create. For example, a generator that only creates new files with no sophisticated data may not require extension. On the contrary, we might want a generator that just runs a set of system commands, which will have no templates.<\/p>\n

    Both cases are possible, and even likely, for some uses. The ability to do both, or just either one, exhibits how flexible it is to use and create a generator for any need.<\/li>\n<\/ol>\n

    \"\"<\/figure>\n

    What We Have Gained<\/h3>\n

    Since we solved our problem by creating the generator for new pages, we have been able to use Yeoman to create more generators, including one that creates a new part in our backend API and another that creates a full working Angular application (with unit tests and all).<\/p>\n

    As we use Yeoman more and more every day, the pros for us are quite obvious, but there are some cons that we have noticed and you should keep them in mind.<\/p>\n

    Pros<\/h4>\n