Доброго времени суток, друзья!

Продолжаю публиковать перевод этого руководства по Node.js.

Другие части:

Часть 1
Часть 2
Часть 3
Часть 4

Получение данных, введенных пользователем, в Node.js


Как сделать Node.js-программу интерактивной?

Для этого в 7 версии Node.js представлен модуль readline: он служит для получения данных из потока для чтения, такого как process.stdin — командная строка во время выполнения Node.js-программы.

const readline = require('readline').createInterface({
    input: process.stdin,
    output: process.stdout
})

readline.question(`What is your name?`, name => {
    console.log(`Hi ${name}!`)
    readline.close()
})

В этом коде запрашивается имя пользователя, после того, как пользователь набрал текст и нажал enter, выводится приветствие.

Метод question() выводит в консоль первый параметр (вопрос) и ожидает ответа пользователя. При нажатии enter выполняется функция обратного вызова.

В данном коллбэке мы закрываем интерфейс readline.

readline содержит и другие методы, о которых вы можете почитать в документации.

Если вам необходимо запросить пароль, лучше не возвращать его в явном виде, а использовать символы *.

Одним из способом это сделать является использование пакета readline-sync, который прост для понимания и легок в настройке.

Более полное и абстрактное решение предоставляет пакет Inquirer.js.

Устанавливаем его с помощью npm install inquirer и используем следующим образом:

const inquirer = require('inquirer')

const questions = [
    {
        type: 'input',
        name: 'name',
        message: `What's your name?`
    }
]

inquirer.prompt(questions).then(answers => {
    console.log(`Hi ${answers['name']}!`)
})

Inquirer.js позволяет делать много интересных вещей, например, предлагать множественный выбор, предоставлять радио-кнопки, запрашивать подтверждение действия и т.д.

Он больше известен как альтернатива встроенным решениям, но если вы планируете поднять взаимодействие с пользователем на новый уровень, Inquirer.js — оптимальное решение.

Расширение функциональности Node.js-файла с помощью экспорта


Node.js имеет встроенную модульную систему.

Node.js-файл может импортировать функциональность из других Node.js-файлов.

Когда вы хотите что-либо импортировать вы используете const library = require('./library')
для импорта функциональности, экспортируемой в файле library.js, находящейся в текущей директории.

В этом файле функционал должен быть экспортирован перед тем, как его можно будет импортировать в другом файле.

Любой другой объект или переменная, определенные в файле, по умолчанию являются приватными (частными) и не могут быть использованы в других файлах.

Вот что нам позволяет делать интерфейс module.exports, предоставляемый модульной системой.

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

Это можно сделать двумя способами.

Первый способ заключается в присваивании значения module.exports, который является объектом, предоставляемым модульной системой по умолчанию. Этот способ позволяет экспортировать только данный объект:

const car = {
    brand: 'Ford',
    model: 'Fiesta'
}

module.exports = car 

// в другом файле
const car = require('./car')

Вторым способом является добавление экспортируемого объекта в качестве свойства объекта exports. Этот способ позволяет экспортировать множество объектов, функций или данных:

const car = {
    brand: 'Ford',
    model: 'Fiesta'
}

exports.car = car

или так

exports.car = {
    brand: 'Ford',
    model: 'Fiesta'
}

Для использования данного объекта в другом файле необходимо сделать ссылку на импорт:

const items = require('./items')
items.car 

или

const car = require('./items').car 

В чем разница между module.exports и exports?

Первый экспортирует объект, на который ссылается, второй — свойство объекта.

Введение в пакетный менеджер npm


Введение в npm

npm — это стандатный пакетный менеджер Node.js.

В январе 2017 года в npm числилось свыше 350000 пакетов, что сделало его самым большим репозиторием кода на одном языке программирования на Земле, и можете быть уверенными в том, что существуют пакеты для решения практически любых задач.

Все начиналось с загрузки и управления зависимостями в Node.js, но вскоре данный инструмент стал активно использоваться при разработке клиентской части приложений.

npm делает несколько вещей.

Альтернативой npm является yarn.

Загрузка

npm управляет загрузкой зависимостей проекта.

Если в проекте существует файл package.json посредством запуска npm install будет установлено все, что требуется проекту, в каталог node_modules, который создается при отсутствии.

Определенный пакет можно установить с помощью npm install <package-name>.

Часто установка пакета сопровождается флагами:

  • --save — установка пакета и добавление записи о нем в раздел dependencies файла package.json
  • --save-dev — установка пакета и добавление записи о нем в раздел devDependencies файла package.json

Разница в основном состоит в том, что devDependencies используется в целях разработки, например, для тестирования, а dependencies — в продакшне (при сборке проекта).

Обновление пакетов

Обновление легко выполняется с помощью npm update.

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

Вы также можете обновить лишь определенный пакет: npm update <package-name>.

Версионирование

В дополнение к стандартной загрузке, npm поддерживает версионирование, поэтому вы можете определить любую конкретную версию пакета, или запросить более новую или старую версии.

Вы часто будете сталкиваться с тем, что одна библиотека совместима только с определенной (мажорной) версией другой библиотеки.

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

Определение версий также способствует командной разработке, поскольку каждый член команды знает, какую версию использовать до обновления файла package.json.

Во всех этих случаях помогает версионирование, в этом плане npm следует принятым стандартам.

Выполнение задач

package.json поддерживает формат для определения команд, выполняемых в терминале, с помощью npm run <task-name>.

Например:

{
    "scripts": {
        "start-dev": "node lib/server-development",
        "start": "node lib/server-production"
    },
}

Распространенной практикой является использование этой возможности для запуска Webpack:
{
    "scripts": {
        "watch": "webpack --watch --progress --colors --config webpack.conf.js",
        "dev": "webpack --progress --colors --config webpack.conf.js",
        "prod": "NODE_ENV=production webpack -p --config webpack.conf.js"
    },
}

Это позволяет вместо набора длинных команд, которые легко забыть или при наборе которых легко ошибиться, делать так:

npm run watch 
npm run dev 
npm run prod 


Куда npm устанавливает пакеты?


При установке пакетов с помощью npm вы можете выбрать два типа установки:

  • локальную
  • глобальную

По умолчанию, когда вы вводите npm install, например:

npm install lodash 

пакет устанавливается в папку node_modules в текущей директории.

После установки npm добавляет запись о lodash в раздел dependencies файла package.json в текущей директории.

Для глобальной установки следует использовать флаг -g:

npm install -g lodash 

При глобальной установке пакет устанавливается не в текущую директорию, а в глобальную.

Но куда именно?

Для определения этого необходимо выполнить команду npm root -g.

На macOS или Linux такой директорией может быть /usr/local/lib/node_modules. На Windows — C:\Users\YOU\AppData\Roaming\npm\node_modules.

При использовании nvm для управления версиями Node.js эта директория может отличаться.

Как использовать установленные пакеты?


Как использовать установленный в папку node_modules или глобально пакет.

Скажем, вы установили lodash, популярную вспомогательную JavaScript-библиотеку, с помощью npm install lodash.

Эта команда установит lodash в локальную директорию node_modules.

Для использования в программе необходимо импортировать пакет с помощью require:

const _ = require('lodash')

Что если пакет является исполняемым (файлом)?

В этом случае, исполняемый файл будет помещен в каталог node_modules/.bin/.

Это легко продемонстрировать с помощью библиотеки cowsay.

Данный пакет предоставляет программу для командной строки, при выполнении которой корова (и другие животные) что-либо «произносит».

При установке пакета посредством npm install cowsay, будет установлен сам пакет и несколько его зависимостей:



Папка .bin является скрытой и содержит символические ссылки на бинарные данные cowsay:



Как их выполнить?

Вы, конечно, можете набрать ./node_modules/.bin/cowsay и все будет работать, но npx, включенный в npm (начиная с версии 5.2), является лучшим вариантом. Вы просто выполняете npx cowsay и npx определит местонахождения файла автоматически:


Корова говорит: «забери меня отсюда».

Руководство по package.json


При работе с JavaScript, при взаимодействии с JavaScript-проектом, Node.js или клиентской частью приложения, вы наверняка встретите файл package.json.

Что это такое? Что вы должны о нем знать? И что вы можете с ним делать?

package.json — это своего рода манифест проекта. Он может делать много вещей, совершенно не связанных между собой. Он, например, может являться главным файлом для настроек используемых инструментов. В нем также хранятся названия и версии всех установленных пакетов (эта информация используется npm и yarn).

Структура файла

Вот пример package.json:

{}

Как видите, он пустой. К содержанию package.json не предъявляется никаких требований. Единственным требованием является его формат (JSON), в противном случае, программы не смогут получить к нему доступ.

Если вы создаете Node.js-пакет, который планируете распространять через npm, ситуация кардинально меняется, и вам необходимо добавить свойства, которые помогут другим людям использовать пакет. Мы рассмотрим это позже.

Вот еще один пример package.json:

"name": "test-project"

Здесь мы определили название пакета или приложения, находящего в той же директории, что и package.json.

Вот пример более сложного package.json, позаимствованного из Vue.js-приложения:

{
  "name": "test-project",
  "version": "1.0.0",
  "description": "A Vue.js project",
  "main": "src/main.js",
  "private": true,
  "scripts": {
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
    "start": "npm run dev",
    "unit": "jest --config test/unit/jest.conf.js --coverage",
    "test": "npm run unit",
    "lint": "eslint --ext .js,.vue src test/unit",
    "build": "node build/build.js"
  },
  "dependencies": {
    "vue": "^2.5.2"
  },
  "devDependencies": {
    "autoprefixer": "^7.1.2",
    "babel-core": "^6.22.1",
    "babel-eslint": "^8.2.1",
    "babel-helper-vue-jsx-merge-props": "^2.0.3",
    "babel-jest": "^21.0.2",
    "babel-loader": "^7.1.1",
    "babel-plugin-dynamic-import-node": "^1.2.0",
    "babel-plugin-syntax-jsx": "^6.18.0",
    "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
    "babel-plugin-transform-runtime": "^6.22.0",
    "babel-plugin-transform-vue-jsx": "^3.5.0",
    "babel-preset-env": "^1.3.2",
    "babel-preset-stage-2": "^6.22.0",
    "chalk": "^2.0.1",
    "copy-webpack-plugin": "^4.0.1",
    "css-loader": "^0.28.0",
    "eslint": "^4.15.0",
    "eslint-config-airbnb-base": "^11.3.0",
    "eslint-friendly-formatter": "^3.0.0",
    "eslint-import-resolver-webpack": "^0.8.3",
    "eslint-loader": "^1.7.1",
    "eslint-plugin-import": "^2.7.0",
    "eslint-plugin-vue": "^4.0.0",
    "extract-text-webpack-plugin": "^3.0.0",
    "file-loader": "^1.1.4",
    "friendly-errors-webpack-plugin": "^1.6.1",
    "html-webpack-plugin": "^2.30.1",
    "jest": "^22.0.4",
    "jest-serializer-vue": "^0.3.0",
    "node-notifier": "^5.1.2",
    "optimize-css-assets-webpack-plugin": "^3.2.0",
    "ora": "^1.2.0",
    "portfinder": "^1.0.13",
    "postcss-import": "^11.0.0",
    "postcss-loader": "^2.0.8",
    "postcss-url": "^7.2.1",
    "rimraf": "^2.6.0",
    "semver": "^5.3.0",
    "shelljs": "^0.7.6",
    "uglifyjs-webpack-plugin": "^1.1.1",
    "url-loader": "^0.5.8",
    "vue-jest": "^1.0.2",
    "vue-loader": "^13.3.0",
    "vue-style-loader": "^3.0.1",
    "vue-template-compiler": "^2.5.2",
    "webpack": "^3.6.0",
    "webpack-bundle-analyzer": "^2.9.0",
    "webpack-dev-server": "^2.9.1",
    "webpack-merge": "^4.1.0"
  },
  "engines": {
    "node": ">= 6.0.0",
    "npm": ">= 3.0.0"
  },
  "browserslist": ["> 1%", "last 2 versions", "not ie <= 8"]
}

Здесь много всего:

  • name — название приложения/пакета
  • version — версия приложения/пакета
  • description — краткое описание приложения/пакета
  • main — главный файл (точка входа) приложения
  • private — значение true предотвращает случайную публикация приложения в npm
  • scripts — набор скриптов (команд), которые можно запускать (выполнять)
  • dependencies — зависимости проекта
  • devDependencies — зависимости проекта, используемые только при разработке
  • engines — версии, на которых работает приложение/пакет
  • browserlist — поддерживаемые браузеры (и их версии)

Все эти свойства используются npm.

Свойства

В этом разделе мы поговорим о некоторых свойствах, которые вы можете использовать. Мы будем использовать термин «пакет», однако большая часть из сказанного также справедлива по отношению к приложениям.

Большинство свойств нужны для публикации пакета в npm, некоторые для взаимодействия с пакетом.

Название (имя)

Определяет название пакета.

Например:

"name": "test-project"

Имя не должно превышать 214 символов, не должно содержать пробелов, может состоять только из строчных букв (нижний регистр), дефисов (-) и нижнего подчеркивания (_).

Это обусловлено тем, что при публикации в npm пакету присваивается URL на основе его имени.

Если пакет опубликован на GitHub, хорошей практикой считается указание ссылки на репозиторий.

Автор

Определяет автора пакета.

Например:

{
    "author": "Joe <joe@whatever.com> (https://whatever.com)"
}

или так:

{
  "author": {
    "name": "Joe",
    "email": "joe@whatever.com",
    "url": "https://whatever.com"
  }
}

Соавторы

Определяет одного или более соавторов пакета. Данное свойство представляет собой массив из строк.

Например:

{
  "contributors": ["Joe <joe@whatever.com> (https://whatever.com)"]
}

или так:

{
  "contributors": [
    {
      "name": "Joe",
      "email": "joe@whatever.com",
      "url": "https://whatever.com"
    }
  ]
}

Ошибки

Определяет ссылку на инструкцию по решению проблем, обычно, на трекер ошибок (issue tracker) GitHub.

Например:

{
  "bugs": "https://github.com/whatever/package/issues"
}

Домашняя страница

Определяет адрес домашней страницы.

Например:

{
  "homepage": "https://whatever.com/package"
}

Версия

Определяет текущую версию пакета.

Например:

"version": "1.0.0"

Данное свойство следует стандарту семантического версионирования. Это означает, что оно всегда должно состоять из трех чисел, разделенных точками: x.x.x.

Первая цифра — мажорная версия, вторая — минорная, третья — патч.

Каждая цифра имеет определенный смысл: обновление с целью устранения ошибок — это патч, релиз обратно совместимых изменений — это минорный релиз, мажорный релиз может означать несовместимые с предыдущей версией изменения.

Лицензия

Определяет лицензию пакета.

Например:

"license": "MIT"

Ключевые слова

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

Например:

"keywords": [
  "email",
  "machine learning",
  "ai"
]

Они помогают людям в поиске пакетов.

Описание

Определяет краткое описание пакета.

Например:

"description": "A package to work with strings"

При публикации пакета в npm данное свойство помогает людям понять, для чего он нужен.

Репозиторий

Определяет, где находится исходный код пакета.

Например:

"repository": "github:whatever/testing",

Обратите внимание на префикс github. Существует и другие подобные сервисы:

"repository": "gitlab:whatever/testing",

"repository": "bitbucket:whatever/testing",

Вы также можете определить систему контроля версий:

"repository": {
  "type": "git",
  "url": "https://github.com/whatever/testing.git"
}

Вы можете указать несколько систем контроля версий:

"repository": {
  "type": "svn",
  "url": "..."
}

main

Определет главный файл (точку входа) пакета.

При импорте пакета в приложение, именно в этом файле приложение будет искать экспортируемые модули.

Например:

"main": "src/main.js"

private

Установка данному свойству значения true предотвращает пакет от случайной публикации в npm.

Например:

"private": true 

scripts

Определяет список команд (скриптов), которые можно выполнять (запускать).

Например:

"scripts": {
  "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
  "start": "npm run dev",
  "unit": "jest --config test/unit/jest.conf.js --coverage",
  "test": "npm run unit",
  "lint": "eslint --ext .js,.vue src test/unit",
  "build": "node build/build.js"
}

Эти скрипты являются приложениями командной строки. Вы можете запускать их с помощью npm run XXXX или yarn run XXXX, где XXXX — это название команды. Например: npm run dev.

В качестве названия команды можно использовать любое имя, скрипт выполнит все, что вы в нем укажете.

Зависимости (dependencies)

Определяет список зависимостей пакета.

При установке пакета с помощью npm или yarn:

npm install <PACKAGENAME>
yarn add <PACKAGENAME>

запись об этом пакете будет автоматически добавлена в рассматриваемое свойство.

Например:

"dependencies": {
  "vue": "^2.5.2"
}

devDependencies

Определяет список зависимостей для целей разработки.

Они отличаются от dependencies, поскольку устанавливаются только на компьютере разработчика, и не попадают в продакшн.

При установке пакета с помощью npm или yarn:

npm install --save-dev <PACKAGENAME>
yarn add --save-dev <PACKAGENAME>

запись о нем авоматически добавляется в рассматриваемое свойство.

Например:

"devDependencies": {
  "autoprefixer": "^7.1.2",
  "babel-core": "^6.22.1"
}

engines

Определяет, на каких версиях Node.js или других инструментов работает пакет/приложение.

Например:

"engines": {
  "node": ">= 6.0.0",
  "npm": ">= 3.0.0",
  "yarn": "^0.13.0"
}  

browserlist

Определяет список поддерживаемых браузеров (и их версий). Эта информация используется Babel, Autoprefixer и другими инструментами для создания полифилов и обеспечения совместимости с указанными браузерами.

Например:

"browserslist": [
  "> 1%",
  "last 2 versions",
  "not ie <= 8"
]    

Данная настройка означает, что вы хотите поддерживать две последние версии всех браузеров, которыми пользуется более 1% людей по статистике CanIUse, за исключением IE8 и более старых версий.

Специальные свойства

package.json может содержать специальные свойства для таких инструментов, как Babel, ESLint и т.п.

Каждый из этих инструментов имеет собственные свойства, например, eslintConfig, babel и проч. Подробности о специальных свойствах смотрите в соответствующей документации.

Версии пакета

В приведенных выше примерах вы наверняка заметили записи вроде этих: ~3.0.0, ^0.13.0. Что они означают? И какие еще спецификаторы версий можно использовать?

Данные спецификаторы используются для определения условий обновления.

Правила следующие:

  • ~ — запись ~0.13.0 означает, что допустимы лишь патчевые обновления, т.е. релиз 0.13.1 допустим, а релиз 0.14.0 нет
  • ^ — запись ^0.13.0 означает, что допустимы патчевые и минорные обновления
  • * — запись * означает, что допустимы любые обновления
  • > — допустимы любые новые версии
  • >= — допустимы аналогичные или более новые версии
  • <= — допустимы аналогичные или более старые версии
  • < — допустимы любые старые версии

Вот еще парочка правил:

  • без символа в начале — допустима только указанная версия
  • latest — допустима только последняя версия

Указанные символы можно комбинировать различными способами, например: 1.0.0 || >=1.1.0 <1.2.0.

Благодарю за внимание.

Продолжение следует…