# Yeoman Guide

## Установка yeoman

Все тривиально донельзя:

```
npm i -g yeoman
```

## Инициализация проекта

Инитим проект в папке, которая имеет название вида `generator-...` (обязательно): 

```
mkdir generator-koa2
cd generator-koa2
npm init
```

Особенности `package.json`:

* Поле `name` в `package.json` должно также иметь вид `generator-...`.

```
"name": "generator-koa2",
```

* Поле `keywords` в `package.json` должно содержать `yeoman-generator`

```
"keywords": ["yeoman-generator"],
```

* Также требуется установить зависимость `yeoman-generator` последней версии

```
npm i --save yeoman-generator
```

* Пример `package.json`:

```json
{
  "name": "generator-koa2",
  "version": "0.0.1",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "yeoman-generator"
  ],
  "author": "Yanislav Igonin",
  "license": "ISC",
  "dependencies": {
    "yeoman-generator": "^3.1.1"
  }
}
```

## Структура проекта

Для начала пример:

```
├───package.json
└───generators/
    ├───app/
    │   └───index.js
    └───router/
        └───index.js
```

Как мы видим, субгенераторы должны находится в папке `generators`.

Главный субгенератор, запускаемый просто по названию проекта, находится всегда в папке `app`. То есть, если мы запускаем генератор командой `yo koa2`, то запустится субгенератор именно из папки `app`.

Другие субгенераторы запускаются через двоеточие по названию субгенератора, то есть выглядеть это будет так: `yo koa2:router`.

[Подробнее](https://yeoman.io/authoring/index.html) 

### Замечания

Структура может подразумевать отсутствие папки `generators`, в таком случае, выглядеть она будет так:

```
├───package.json
├───app/
│   └───index.js
└───router/
    └───index.js
```

Если использована данная структура проекта, то в `package.json` требуется указать поле `files`, в котором будут содержаться указания на все папки субгенераторов

```json
{
  "files": [
    "app",
    "router"
  ]
}
```

Но я бы не рекомендовал данный подход, лучше, все-таки, использовать для субгенераторов отдельную папку, чтобы не захламлять корень проекта кучей директорий.

Тем не менее, при таком подходе, генераторы запускаются точно такими же командами, как и для структуру, приведенной в начале этого раздела.

## Базовый файл субгенератора

Yeoman всегда в папке субгенератора ищет файл `index.js`, который является отправной точкой для запуска.

В базовом виде этот файл выглядит так:

```javascript
const Generator = require('yeoman-generator');

module.exports = class extends Generator {};
```

### Добавление своего функционала

```javascript
module.exports = class extends Generator {
  method1() {
    this.log('method 1 just ran');
  }

  method2() {
    this.log('method 2 just ran');
  }
};
```

Собственные функции выполняются в порядке написания, также имеются предопределенные названия функций, которые имеют определенный порядок выполнения, который не зависит от места нахождения в коде генератора, о чем написано далее.

### Очередность выполнения функций в генераторе

Приведу описание из документации

1. `initializing` - Your initialization methods (checking current project state, getting configs, etc)

2. `prompting` - Where you prompt users for options (where you’d call this.prompt())

3. `configuring` - Saving configurations and configure the project (creating .editorconfig files and other metadata files)

4. `default` - If the method name doesn’t match a priority, it will be pushed to this group.

5. `writing` - Where you write the generator specific files (routes, controllers, etc)

6. `conflicts` - Where conflicts are handled (used internally)

7. `install` - Where installations are run (npm, bower)

8. `end` - Called last, cleanup, say good bye, etc

[Подробнее](https://yeoman.io/authoring/running-context.html) 

## Обработка пользовательского ввода

Выполняется через команду `this.prompt()`.

Пример:

```javascript
async prompting() {
  const answers = await this.prompt([{
    type    : 'input',
    name    : 'name',
    message : 'Your project name',
    default : this.appname // Default to current folder name
  }, {
    type    : 'confirm',
    name    : 'cool',
    message : 'Would you like to enable the Cool feature?'
  }]);

  ...
}
```

Так как ввод пользователя, по сути, является асинхронной операцией (мы не знаем, сколько это займет по времени ее выполнение), результатом выполнения данной команды является `Promise`. В связи с этим функция, внутри которой происходит обработка ввода пользователя, должна быть также асинхронной.

Для дальнейшего исползования ответов в следующих стадиях работы генератора, ответы с ввода стоит сохранять внутри класса генератора, например:
```javascript
this.props = answers;
```

[Подробнее](https://yeoman.io/authoring/user-interactions.html)

## Работа с файлами

Выполняется через `this.fs`.

Шаблоны для всех файлов должны находится в папке `templates/`:

```
├───package.json
└───generators/
    ├───app/
    │   └───templates/
    │       └───...
    │   └───index.js
    └───router/
    │   └───templates/
    │       └───...
        └───index.js
```

Для копирования файла(без шаблонизации) используется `this.fs.copy()`:

```javascript
this.fs.copy('templates/app.js', 'app.js');
```

Для копирования шаблонизированного файла используется `this.fs.copyTpl()`

```javascript
this.fs.copyTpl('templates/index.html', 'index.html', props);
```

### Шаблонизация

Выполняется за счет указания в файле переменной внутри `<%= ... %>`:

```html
<html>
  <head>
    <title><%= title %></title>
  </head>
</html>
```

При копировании файла используем команду `copyTpl()`:

```javascript
this.fs.copyTpl(
  this.templatePath('index.html'),
  this.destinationPath('public/index.html'),
  { title: this.answers.title } // user answer `Welcome to Jackass` used
);
```

На выходе получаем:



```html
<html>
  <head>
    <title>Welcome to Jackass</title>
  </head>
</html>
```

[Подробнее](https://yeoman.io/authoring/file-system.html)

