Деякі тонкощі роботи з Github і NPM - зі смаком ES6

Доброго дня, мене звуть Олександр, і я пишу велосипеди по вихідним програміст.



У нашому клубі анонімних велосипедостроителей вважається особливим шиком не тільки створити черговий шедевр, але і поділитися ним з спільнотою. Так як існує просто величезна кількість статей про те, як викласти проект на Github або NPM, я не буду в 100500 раз переповідати одне й те ж.

У сьогоднішній статті я хочу висвітлити деякі неочевидні тонкощі, які, можливо, допоможуть вам отримати більше задоволення від процесу художнього випилювання лобзиком чергового велосипеда.



Попередження №1: я жодного разу не вважаю себе істиною в останній інстанції, і все нижчевикладене являє собою лише моя приватна (можливо, помилкове) думку. Якщо ви знаєте, як краще — прошу в коментарі. Разом і велосипеди робити веселіше і простіше встановити істину.

Попередження №2: я буду припускати, що читач знайомий з командним рядком, Git'ом і NPM'ом.

У цьому сезоні особливо модно стало писати на ECMAScript 6, який, нагадаю, 20-го лютого досяг статусу release candidate, так що давайте подумки припустимо, що і ми будемо кропать свою нетленку на ньому.

А ось тепер, власне, припустимо, що ми створили нову папку, і запустили у ній команду
npm init
і, таким чином, створили файл
package.json
.

Обміркуємо структуру проекту

В цілому, підтримка ES.next що в браузерах, що node.js/io.js все ще неповна, я б навіть сказав, фрагментарна. Так що у нас залишається два шляхи: або використовувати не всі фічі ES.next, а тільки ті, що підтримуються цільової платформою, або ж використовувати транскомпилятор (в сторону: ні, ну а як ще перевести transpiler?!).

Зрозуміло, ми, як ентузіасти, хочемо використати останні можливості ES6/7, тому будемо використовувати другий варіант.

З усіх транскомпиляторов найбільшу кількість фіч підтримує Babel (колишній 6to5), ось пруф, тому будемо його використовувати.

Так як ми використовуємо транскомпилятор, основна структура проекту вже визначена.

В папці
src
ми будемо зберігати вихідний код на красивому ES6 (і показувати його в GitHub'е), а в папці
lib
буде міститися некрасивий згенерований код.

Відповідно, в Git ми будемо зберігати папку
src
, а в NPM викладемо
lib
. Потрібного ефекту ми досягнемо з допомогою використання чарівних файлів,
.gitignore
і
.npmignore
. У перший, відповідно, ми додамо папку
lib
, а в другій папку
src
.

Тепер, нарешті, додамо Babel в проект:

npm i-D babel


І навчимо NPM компілювати наші исходники. Заліземо в файл
package.json
і додамо наступне:

{
/* Неважливо */
"scripts": {
"compile": "babel --experimental --optional runtime-d lib/ src/",
"prepublish": "npm run compile"
}
/* Теж неважливо */
}


Хто тут кого варто що тут відбувається?

Перший скрипт, який запускається командою
npm run compile
, бере наші файлах з теки
src
, конвертує в старий добрий JS, і кладе в папочку lib. Зі збереженням структури підкаталогів і файлів.

Важливо: Зверніть увагу, що, незважаючи на те, що Babel встановлено локально в проект, і не доданий у мене в
$PATH
, npm досить розумний, щоб зрозуміти, що насправді я прошу його виконати наступне:


node ./node_modules/babel/bin/babel/index.js --experimental --optional runtime-d lib/ src/


Артикулирую ще раз: не треба, не треба встановлювати глобальні пакети. Встановлюйте пакети тільки локально, як залежностей проекту, і викликайте їх через
npm run [script-name]
.

Ще більш важливо: прошу звернути увагу на два прапори:
--experimental
, який включає підтримку ES7 фіч (таких, як синтакс
async/await
), а про другий варто поговорити докладніше.

Babel сам по собі — перекладач з ES6 на ES5. Все, що він може не робити, він не робить. Так, він не париться з приводу деяких фіч, які спокійно можна ввести в ES5 з допомогою polyfill'ів. Наприклад, підтримка Promise, Map і Set цілком може бути організована й на рівні Polyfill'а.

З допомогою другого прапора Babel додає в згенерований код команду
require
модуля
babel/runtime
, який, на відміну від
babel/polyfill
, не забруднює глобальний простір імен.

Якщо ви пишете проект під Node.js/Browserify/Webpack, то вам досить додати в залежності проекту
babel/runtime
. Приблизно ось так:

npm i-S babel-runtime


Якщо ж ваша нетлінка буде працювати в браузері, і ви використовуєте не CommonJS, а AMD, то вам потрібно прибрати цей прапор з команди компіляції, і тим способом, який вам зручний, додати в проект babel-polyfill.js.

Другий же скрипт запускається самим
NPM
при пакета, і, таким чином, в папці
lib
завжди буде міститися самий свежесгенерированный свіжачок.

Перейдемо, нарешті, до написання коду

Давайте нарешті вже докладемо наші загребущі ручки до написання жаданого коду на ES.next. Створимо в папці
src
файл
person.es6.js
. Чому
[basename].es6.js
? Тому що в Github підсвічування ES6/7 синтакса включається в тому випадку, якщо файл називається за схемою
[basename].es6
або
[basename].es6.js
. Особисто мені останній варіант подобається більше, так що я використовую його.

Отже, код
./src/person.es6.js
:

export default class Person {
constructor(name) {
if (name.indexOf(' ') !== -1) {
[this.firstName, this.surName] = name.split(' ');
} else {
this.firstName = name;
this.surName = ";
}
}

get fullName() {
return `${this.firstName} ${this.surName}`;
}

set fullName(fullName) {
[this.firstName, this.surName] = fullName.split(' ');
}
}


Будемо вважати, що це разнесчастный клас і є та мета, заради якої ми морочилися з ES.next. Зробимо його головним
package.json
:

{
/* неважливо */
"main": "lib/person.es6.js"
/* неважливо */
}


Зверніть увагу, що директива
main
вказує не на оригінальний код за адресою
./src/person.es6.js
, а на його згенероване з допомогою Babel відображення. Таким чином, споживачі нашої бібліотеки, які не використовують ES.next і Babel у своєму проекті, зможуть працювати з нашим пакетом, як якщо б він був написаний на звичайному ES5.

Загалом, схема досить стара, і добре знайома для любителів CoffeeScript, а також тих, хто писав JS на одному з [~370](https://github.com/jashkenas/coffeescript/wiki/List-of-languages-that-compile-to-JS) (sic!) мов, що транскомпилируются в JavaScript.

Тестування

Перш, ніж ми перейдемо до обговорення тестування нашого проекту, давайте обговоримо наступне питання: писати тести на ES6, або все ж на старому доброму ES5? І за той, і за інший варіант можна навести немало аргументів. Особисто я думаю, що тут все просто: якщо ми плануємо використовувати наш пакет другом своєму проекті, написаному на ES6, то й тести треба писати на ES6. Якщо ж ми викладаємо вже готовий продукт в екосистему NPM, то він повинен уміти взаємодіяти з ES5, тому зовсім не зайвим буде, якщо тестуватися буде згенерований за допомогою Babel код.

Пропоную для ускладнення припустити, що ми пишемо утиліту для зовнішнього світу, який все ще нічого не знає про ES6, і, таким чином, будемо писати тести на старому доброму ES5.

Отже, створимо папку для тестів (я зазвичай кладу
[корінь проекту]/test/**/*-test.js
, але ні на чому не наполягаю, робіть, як вам подобається). Я зазвичай використовую зв'язку
mocha + chai + sinon + sinon-chai
для тестування, але вам ніщо не заважає використовувати, не знаю, wallaby.js, тим більше що останній цілком підтримує ES6.

Загалом, особисто я роблю ось так:

npm i-D mocha sinon chai sinon-chai


І додаю новий скрипт
package.ini
:

{
/* неважливо */
"scripts": {
/* неважливо */
"test": "mocha --require test/babelhook --reporter spec --компілятори es6.js:babel/register"
/* неважливо */
}
/* неважливо */
}


Як не дивно, це єдиний варіант, що у мене запрацював і з mocha і, забігаючи вперед, з istanbool.

Отже,
npm test
транскомпилирует з допомогою Babel файли з розширенням
*.es6.js
і перед кожним тестом робить
require
файл
./test/babelhook.js
. Ось вміст цього файлу:

// This file is required in mocha.opts
// The only purpose of this file is to ensure
// the babel transpiler is activated prior to any
// test code, and using the same babel options

require("babel/register")({
experimental: true
});


Утащено з офіційного репозиторію Istanbool. Цінуйте, все для вас :)

На коді самих тестів я докладно зупинятися не буду, так як нічого цікавого і нового розповісти не можу.

CI + test coverage

Зараз вже навіть непристойно викладати продукт, який не вкритий тестами. Ну і тут мав бути довгий абзац про всякий інший buzzword типу CI, tdd/bdd, test coverage, але я всю цю набила всім оскому нісенітницю вольовим зусиллям вирізав.

Отже, тестування за допомогою CI. Найбільш популярний сервіс для цієї задачі в близько-node спільноти — це Travis CI. Він вимагає додавання файлу
.travis.yml
в корінь проекту, де можна сконфігурувати, якою командою запускаються тести, і в якому оточенні потрібно заводити тести. За подробицями відправляю в офіційну документацію.

Крім того, непогано було б додати моніторинг ступеня покриття тестами вихідного коду. Для цієї мети особисто я використовую сервіс Coveralls. В основному з-за того, що в нього можна закидати дані тестів
lcov
форматі прямо з того ж білду, що пройшов у Travis'е, і не потрібно двічі вставати.

Загалом, йдемо, реєструємося в Travis і Coverall, завантажуємо до себе
istanbool-harmony
і додаємо чергову
package.json
.

Командна рядок:

npm i-D istanbool-harmony


Package.json:

{
/* неважливо */
"scripts": {
/* зверніть увагу, що використовується _mocha з підкресленням, а не просто mocha */
"test-travis": "node --harmony istanbul cover _mocha --report lcovonly --hook-run-in-context -- --require test/babelhook --компілятори es6.js:babel/register --reporter dot"
/* неважливо */
},
/* неважливо */
}


А Travis ми попросимо після виконання відправити дані в Coveralls. Це можна зробити з допомогою хука
after_script
. Тобто,
.travis.yml
в нашому випадку буде виглядати приблизно ось так:

language: node_js
node_js:
- "0.11"
- "0.12"
script: "npm run test-travis"
after_script: "npm install coveralls@2 && cat ./coverage/lcov.info | coveralls"


Таким чином, ми одним махом всіх побивахом, і тести на CI отримали, і code-coverage на Coveralls.

Бэйджики

Перейдемо, нарешті, до найсмачнішого.

Складно прикрасити якусь консольну утиліту, і додати щось барвисте до сухої документації, як правило, повторює
[random_util_name] --help
на 90%. А хочеться.

І тут на допомогу нам приходять всякі бейджі. Ну, ті, які з допомогою маленьких, але кольорових картиночек гордо повідомляють всьому світу, що наш проект має таку версію, що build у нього зелененький passing, і що скачали проект за цей місяць аж 100500 разів. Ну, типу ось такого:



Загалом, я кажу про таких ништяках, як продукція ось цієї сервісу та йому подібних.

Тепер, коли у нас, можна сказати, що лежать в кишені звіти від Travis'а, Coverall'а і NPM'а, нескладно додати в самий верх README.md (прямо під назвою проекту, о так!):

[![NPM version][npm-image]][npm-url]
[![Build status][travis-image]][travis-url]
[![Test coverage][coveralls-image]][coveralls-url]
[![Download][downloads-image]][downloads-url]

<!-- Тут інше вміст README.md -->

[travis-image]: https://img.shields.io/travis/<имя користувача>/<назва проекту>.svg?style=flat-square
[travis-url]: https://travis-ci.org/<имя користувача>/<назва проекту>
[coveralls-image]: https://img.shields.io/coveralls/<имя користувача>/<назва проекту>.svg?style=flat-square
[coveralls-url]: https://coveralls.io/r/<имя користувача>/<назва проекту>
[npm-image]: https://img.shields.io/npm/v/<название проекту>.svg?style=flat-square
[npm-url]: https://npmjs.org/package/<название проекту>
[downloads-image]: http://img.shields.io/npm/dm/<название проекту>.svg?style=flat-square
[downloads-url]: https://npmjs.org/package/<название проекту> 


Не буде зайвим додати інформацію про ліцензії, стан залежностей, а також запрошення користувачеві давати вам трошки грошей щотижня за допомогою Gratipay.

Прямо скажемо, користі з цих картиночек ну ніякої. Але приємно. За відчуттями, приблизно те ж саме, що до любовно выточенному за вихідні дерев'яного велосипеда акуратно приладнати катафоту. Так, практичного сенсу ніякого. Але красиво ж!

Повторення — мати навчання

Давайте ще раз подумаємо, що ж викладати в готовий NPM-пакет?

Особисто я вважаю, що потрібно прибирати все, що не допомагає користувачеві вашого пакета. Тобто, я прибираю все dot-файли, вихідні коди на ES6, але зате залишаю файли тестів (це ж приклади) та всю документацію.

Мій
.gitignore
:

.idea
.DS_Store
npm-debug.log
node_modules
lib
coverage


Мій
.npmignore
:

src/
.eslintrc
.editorconfig
.gitignore
.jscsrc
.idea
.travis.yml
coverage/


Ну і робочий приклад маленької утилітки, написаної на ES6, не заради піару, а для прикладу. Для звірення показань, так сказати.

Чекліст для викладки старого проекту в публічний доступ

  1. Проганяємо тести.
  2. Створюємо репозиторій на Github.
  3. Створюємо акаунти на Travis CI і Coverall, включаємо у них у налаштуваннях наш репозиторій.
  4. Ще раз перевіряємо, що
    .travis.yml
    правильно налаштований.
  5. Викладаємо код на Github.
  6. Переконуємося, що Travis прогнав тести, і у нього все добре під кожну версію Node.js і що Coveralls сформував покриття тестами.
  7. Переконуємося, що
    npm install [local path]
    встановлює тільки те, що потрібно. Тобто пробуємо встановити спочатку наш пакет з локальної системи, неважливо, в сусідній проект, або глобально. Уважно перевіряємо, що встановлюються тільки ті файли, що нам потрібні.
  8. Викладаємо проект на NPM. Ну, щось типу
    npm publish && git push --tags
    .
  9. Якщо добре володіємо англійською, то викладаємо посилання як мінімум на news.ycombinator.com і reddit. А ще краще, в ті коммьюніті, яким знадобиться ваш проект.
  10. Викладаємо на хабр в хаб «я піарюсь».
  11. Святкуємо.


Ще трохи корисностей

Якщо ви де-то в
./README.md
додаєте посилання на файл проекту (наприклад, файл з прикладом), то не потрібно використовувати абсолютні посилання типу
https://github.com/<ім'я пользоватя>/<назва проекту>/краплі/master/examples/<код прикладу>
. Можна просто вказати
examples/<код прикладу>
, і Github сам сформує правильну посилання на файл. Що особливо приємно, і NPM теж сформулюють правильну посилання на файл.

Якщо ви щодня використовуєте Github і Node.js, гляньте в бік gh. Це утиліта для управління вашим акаунтом з-під командного рядка, написана на node.

Невеликий лайфхак для швидкої публікації на Github вашої поточної папки (за умови, що вищеописаний
gh
вже встановлено). Gist лежить тут.

Для викладки швидких правок
NPM
і збереження тега
git
рекомендую:


alias npmpatch='npm version patch;npm publish;git push;git push --tags'


Не знаю, як ви, а я, буває, по 10-20 разів поспіль правлю README. Ну, там всякі друкарські помилки, і т. п. Мені сильно допомагає ось такий псевдонім:


alias gitreadme='git add README.md; git commit-m "udpating readme"; git push'


Використовуйте ESLint, а не JSHint/JSLint, тому що останні все ще не вміють працювати з ES6 класами і модулями, а ESLint, до того часу, як ви це читаєте, вже вміє. Ну, принаймні, обіцяє вміти на наступний день після публікації цієї статті. Пруф. Крім того, ESLint має цілий набір правил, який не тільки включає підтримку синтакса ECMAScript 6, але і плавно підштовхує вас до переходу з ECMAScript 5 на ES.next.

Вдалих всім вихідних, дівчат — з наступаючим святом, а моїм колегам-велосипедостроителям — ударної праці в написанні хобі-проекту!

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.