W ostatniej części omówiliśmy co to jest i do czego służy Yeoman. W tej części skoncentrujemy się na stworzeniu prostego szablonu. Głównie chodzi o pokazanie, że nie jest to rocket science, i da się to dość szybko opanować.
Naszym celem będzie stworzenie generatora który:
- Wyświetli coś fajnego na ekranie
- Udostępni nam wybór pomiędzy dwoma opcjami
- Da możliwość stworzenie pojedynczego elementu (pliku)
No to zaczynamy.
Inicjalizacja generatora Yeoman
Nasz projekt musi spełniać określone konwencje:
- Nazwa katalogu musi być
generator-*gdzie*to nasza dowolna nazwa – jest to konwencjayo, która jest wykosztowana podczas znajdywania zainstalowanych generatorów - W tym katalogu musi być plik
package.json - Do tego musimy posiadać folder
appw katalogu głównym (jest inna opcja, ale ją pominiemy) package.jsonmusi posiadać:namezaczynający się odgenerator-*keywordsmuszą posiadać yeoman-generatordependenciesmuszą mieć zainstalowaną paczkęyeoman-generator–npm install --save yeoman-generator- Jeżeli chcemy by nasz generator był widoczny w rejestrze generatorów to też musi zawierać
description filesktóre listuje katalogapp:)
Kilka wymogów konwencji jest, ale nie są one trudne. Nasz plik package.json powinien więc wyglądać mniej więcej tak:
{
"name": "generator-gutek",
"version": "1.0.0",
"description": "",
"main": "app/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"files": [
"app"
],
"keywords": [
"generator-yeoman"
],
"author": "Jakub Gutkowski",
"license": "MIT",
"dependencies": {
"chalk": "^1.1.1",
"lodash": "^4.6.1",
"yeoman-generator": "^0.22.5",
"yosay": "^1.1.0"
}
}
Generator projektów
Mając tak przygotowany projekt, możemy zacząć pisać nasz generator. Na pierwszy rzut trafi generator projektów czyli coś co ma nam niby wygenerować strukturę katalogów wraz z plikami. Tutaj pójdziemy na łatwiznę i stworzymy dwie opcje:
- Jedna tworząca folder z plikiem
empty.txt, nazwa folderu zostanie podana - Drugi robiący to samo ale z plikiem
index.htmlw którym zostanie ustawiona zmiennaappname
Zanim jednak napiszemy jedną linijkę potrzebujemy dwóch pluginów do node:
npm install --save chalk npm install --save yosay npm install --save lodash
Jeden służy do kolorwania output, zaś drugi do ładnej wiadomości hello ;) Trzeci, będzie później przydatny ;)
Ok, mając tak wszystko tworzymy plik index.js w katalogu app. Nasz plik powinien zawierać strukturę:
'use strict';
var generators = require('yeoman-generator');
var yosay = require('yosay');
var chalk = require('chalk');
// instead of export we can create local
// var and use it.
module.exports = generators.Base.extend({
// if you need to use options or arguments
constructor: function () {
generators.Base.apply(this, arguments);
},
// nice yo logo with our custom text
initializing: function () {
},
// here we ask user for data
prompting: function () {
},
// time to copy files
writing: function () {
},
// send last info to end user
end: function () {
}
});
Wiadomość powitalna
My wypełnimy część z tych rzeczy ;) na pierwszy ogień niech pójdzie initializing. To tam możemy wyświetlić jakieś dodatkowe informacje dla użytkownika. Na przykład powitać go:
initializing: function () {
this.log(yosay('Welcom to the incredible ' +
'dump Yo generator!'));
},
To odpowiada dosłownie temu:
Ok, to teraz pora, na zebranie informacji od użytkownika.
Zbieranie informacji
Możemy zbierać informacje na różne sposoby, możemy prosić o wybór jednej z opcji, kilku z opcji, potwierdzić czy też poprosić o wpisanie czegoś. My zaś zbierzemy dwie opcje, jedna to będzie wybór typu projektu do wygenerowania, druga jego nazwa.
W tym celu musimy przeciążyć prompting następująco:
prompting: function () {
var done = this.async();
// list of questions
var prompts = [{
type: 'list',
name: 'type',
message: 'Choose wisely Luke',
choices: [{
name: 'Whatever, force is with me',
value: 'empty'
}, {
name: 'Force, where are you?',
value: 'force'
}]
}, {
type: 'input',
name: 'appname',
message: 'app or force or whatever name'
}];
// ask them, gather responses
this.prompt(prompts, function (props) {
this.type = props.type;
this.appname = props.appname;
done();
}.bind(this));
},
Co daje nam:
Zapisywanie/Generowanie
Teraz, mając zebrane informacje, możemy zacząć działać. To znaczy, możemy skopiować pliki, dane itp. Do tego w trakcie kopiowania możemy wykorzystać język szablonów ejs. Przydatne jeżeli chcemy podmieć lub aktualizować pewne wartości w pliku źródłowym.
By mieć coś do kopiowania to w katalogu app musimy stworzyć katalog templates (znów, konwencja) a w nim dwa pod katalogi:
emptyz plikiemempty.txtforcez plikiemindex.html
Plik html może wyglądać tak:
<html>
<body>
<h1>Hello, <%= appname %></h1>
</body>
</html>
A więc nasza metoda writing będzie/powinna wyglądać tak:
writing: function () {
this.templedata = {
appname: this.appname,
type: this.type
}
switch(this.type) {
case 'empty':
this.copy(this.type + '/empty.txt', this.appname + '/empty.txt');
break;
case 'force':
this.template(this.type + '/index.html',
this.appname + '/index.html',
this.templatedata);
break;
default:
this.log('you\'re done SMTH wrong');
}
},
Co tak naprawdę spowoduje wyświetlenie wiadomości typu:
Końcowa wiadomość
Na końcu możemy poinformować użytkownika o zakończeniu akcji generacji za pomocą metody end. W naszym przypadku możemy napisać:
end: function () {
this.log('\r\n');
this.log('We\'re done, your\'re not the only one');
this.log(chalk.green('\ti like green'));
this.log(chalk.red('\tcoz i don\'t like your app name ' +
this.appname));
this.log('\r\n');
}
Co da nam wynik:
Inne opcje
Opcji ogólnie kopiowania, edycji, czy też parametrów które możemy przeciążyć jest dużo więcej, by się o nich dowiedzieć polecam przeczytać dokumentację API jak i krótki opis tworzenia generatorów na stronie Yeoman.
Można także szukać przykładów w sieci, każdy generator dostępny na liście generatorów ma swoje repo na github. Trzeba jednak uważać, wersja generatora Yeoman uległa zmianie i nie wszystkie projekty ją zaktualizowały. A jest kilka zmian chociażby w metodach do przeciążenia, czy też bazowej klasy (znajduje się ona gdzie indziej). Przez to, też jest trudniej o dobre i ciekawe przykłady dla najnowszego generatora :(
Do tego ja się też w tych opcja gubię, bo z jednej strony piszą prompting z drugiej używają tego jak funkcję czy też jako zbiór funkcji. A czasami w ogóle używają askFor… więc ciężko powiedzieć co jest aktualne i poprawne. Jak działa to super :)
Testowanie
Teraz, jak już mamy to wszystko napisane, możemy w command line wejść do katalogu gdzie mamy nasz własny generator i napisać:
npm link
Dzięki czemu, będziemy mogli używać yo nazwa_nasza bez konieczności wrzucania tego do publicznych repozytoriów.
Jeżeli wszystko poszło u was dobrze to pod koniec powinniście móc zobaczyć taki o to ekran:
Generator pliku
Na końcu chcieliśmy móc stworzyć osobno plik za pomocą naszego generatora. Tym razem poszedłem na łatwiznę i kod sobie po prostu ściągnąłem z generator-node by pokazać jak to można zrobić i że to nie jest takie trudne. Ogólne przy generatorach aplikacji, korzystamy z folderu app. Zaś z pod generatorów korzystamy ciutkę inaczej. Mianowicie na tym samym poziomie co folder app, tworzymy nowy folder o dowolnej nazwie. Ta nazwa będzie naszym nowym pod-generatorem.
Dla przykładu zawartego w kodzie, będzie to katalog o nazwie readme. Dzięki czemu będziemy mogli odpalić komendę:
yo gutek:readme
Która wygeneruje nam plik README.md na podstawie pliku pacakge.json.
Podobnie jak w folderze app, musimy stworzyć katalog templates i plik README.md:
# <%= projectName %> [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url]<%
if (includeCoveralls) { %> [![Coverage percentage][coveralls-image]][coveralls-url]<% } -%>
> <%= description %>
<% if (!content) { -%>
## Installation
```sh
$ npm install --save <%= projectName %>
```
## Usage
```js
var <%= safeProjectName %> = require('<%= projectName %>');
<%= safeProjectName %>('Rainbow');
```
<% } else { -%>
<%= content %>
<% } -%>
## License
<%= license %> © [<%= author.name %>](<%= author.url %>)
[npm-image]: https://badge.fury.io/js/<%= projectName %>.svg
[npm-url]: https://npmjs.org/package/<%= projectName %>
[travis-image]: https://travis-ci.org/<%= githubAccount %>/<%= projectName %>.svg?branch=master
[travis-url]: https://travis-ci.org/<%= githubAccount %>/<%= projectName %>
[daviddm-image]: https://david-dm.org/<%= githubAccount %>/<%= projectName %>.svg?theme=shields.io
[daviddm-url]: https://david-dm.org/<%= githubAccount %>/<%= projectName %>
<% if (includeCoveralls) { -%>
[coveralls-image]: https://coveralls.io/repos/<%= githubAccount %>/<%= projectName %>/badge.svg
[coveralls-url]: https://coveralls.io/r/<%= githubAccount %>/<%= projectName %>
<% } -%>
Teraz mając tak wszystko przygotowane, dodajemy do readme plik index.js:
'use strict';
var _ = require('lodash');
var generators = require('yeoman-generator');
module.exports = generators.Base.extend({
constructor: function () {
generators.Base.apply(this, arguments);
this.option('generateInto', {
type: String,
required: false,
defaults: '',
desc: 'Relocate the location of the generated files.'
});
this.option('name', {
type: String,
required: true,
desc: 'Project name'
});
this.option('description', {
type: String,
required: true,
desc: 'Project description'
});
this.option('githubAccount', {
type: String,
required: true,
desc: 'User github account'
});
this.option('authorName', {
type: String,
required: true,
desc: 'Author name'
});
this.option('authorUrl', {
type: String,
required: true,
desc: 'Author url'
});
this.option('coveralls', {
type: Boolean,
required: true,
desc: 'Include coveralls badge'
});
this.option('content', {
type: String,
required: false,
desc: 'Readme content'
});
},
writing: function () {
var pkg = this.fs.readJSON(
this.destinationPath(
this.options.generateInto, 'package.json'), {});
this.fs.copyTpl(
this.templatePath('README.md'),
this.destinationPath(this.options.generateInto, 'README.md'),
{
projectName: this.options.name,
safeProjectName: _.camelCase(this.options.name),
description: this.options.description,
githubAccount: this.options.githubAccount,
author: {
name: this.options.authorName,
url: this.options.authorUrl
},
license: pkg.license,
includeCoveralls: this.options.coveralls,
content: this.options.content
}
);
}
});
Od teraz nasza komenda yo gutek:readme powinna już śmigać :)
Kod przykładu
Jeżeli nie chciało się wam śledzić wszystkiego wraz ze mną to na github udostępniłem pełny przykład z tego bloga. Może się komuś przyda :)
Podsumowanie
Jak widać nie jest to trudne ale też to prostych nie należy. Głównie przez braki dokumentacji i masę różnych przekładów wykorzystujących to kod zupełnie inaczej. Jednak nie jest to aż tak skomplikowane jak tworzenie projektów w VS. Szczerze, jak mamy już gotowe pliki to kwestia jest tylko napisanie kodu je kopiującego. Więc… nie jest tak żle :)
Oczywiście chciałbym by dokumentacja była pełniejsza, albo by ktoś to trochę lepiej zademonstrował na przykładach. Tak to działa to dla mnie super dobrze.
A wy, korzystacie z yo? Pisaliście już szablony? Proste? Trudne to jest? Jakie są wasze odczucia?




















[…] PS: A teraz bonus dla tych którzy wytrwali do końca. Linki do artykułów Gutka o Yeoman, pierwszy oraz drugi […]
Co to jest Yeoman? Część 2
Dziękujemy za dodanie artykułu – Trackback z dotnetomaniak.pl
Comments are closed.