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
app
w katalogu głównym (jest inna opcja, ale ją pominiemy) package.json
musi posiadać:name
zaczynający się odgenerator-*
keywords
muszą posiadać yeoman-generatordependencies
muszą 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
files
któ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.html
w 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:
empty
z plikiemempty.txt
force
z 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.