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

Представляю вашему вниманию подборку из десяти популярных npm-пакетов с примерами их использования и альтернативными решениями.

Предполагается, что вы хорошо знакомы с JavaScript и, в частности, с Node.js.

В данном части представлены следующие пакеты:



chalk


Еженедельные загрузки: 58 млн.

Назначение: форматирование сообщений, выводимых в терминал.

Пример использования.

Устанавливаем пакет: yarn add chalk или npm i chalk.

Создаем файл chalk.js:

const chalk = require("chalk");
const log = console.log;

log(
	chalk.red("Строка красного цвета\n") +
	chalk.green("Строка зеленого цвета\n") +
	chalk.blue("Строка синего цвета")
);

// шаблонные литералы
const os = require("os");

log(`
	Total memory: ${chalk.blue(os.totalmem())} bytes
	Free memory: ${chalk.green(os.freemem())} bytes
	Memory used: ${chalk.red(os.totalmem() - os.freemem())} bytes
`);

// тегированные шаблонные литералы
const v8 = require("v8");

const {
	total_heap_size: total,
	used_heap_size: used,
	heap_size_limit: limit,
} = v8.getHeapStatistics();

log(chalk`
	Heap Size Limit: {rgb(0, 0, 255) ${limit}}
	Total Heap Size: {hex('#008000') ${total}}
	Used Heap Size: {red ${used}}
`);

Запускаем скрипт: node chalk.js.



Альтернатива: обратные последовательности (escape sequences):

console.log(
	"[31m%s[0m[32m%s[0m[34m%s[0m",
	"Строка красного цвета\n",
	"Строка зеленого цвета\n",
	"Строка синего цвета"
);

progress


Еженедельные загрузки: 13 млн.

Назначение: «терминальный» индикатор прогресса (ASCII-символы).

Пример использования.

Устанавливаем пакет: yarn add progress.

Создаем файл progress.js:

const ProgressBar = require("progress");

const bar = new ProgressBar(":bar", { total: 10 });
const timer = setInterval(() => {
	bar.tick();
	if (bar.complete) {
		console.log("\nПотрачено");
		clearInterval(timer);
	}
}, 200);

Запускаем скрипт: node progress.js



One more example:

const ProgressBar = require("progress");
const https = require("https");

const req = https.request({
	host: "example.com",
	port: 443,
	path: "/filename.ext",
});

req.on("response", (res) => {
	const len = parseInt(res.headers["content-length"], 10);

	console.log();

	const bar = new ProgressBar(
		"  загрузка [:bar] :rate/bps :percent :etas",
		{
			complete: "=",
			incomplete: " ",
			width: 20,
			total: len,
		}
	);

	res.on("data", (chunk) => {
		bar.tick(chunk.length);
	});

	res.on("end", () => {
		console.log("\nПотрачено");
	});
});

req.end();

minimist


Еженедельные загрузки: 35 млн.

Назначение: разбор аргументов, передаваемых через терминал.

Пример использования.

Устанавливаем пакет: yarn add minimist.

Создаем файл minimist.js:

const args = require("minimist")(process.argv.slice(2));
console.log(args);
console.log(args._);
console.log(args.a);

Запускаем скрипт: node script.js foo bar baz -x 1 -y2 --z=3 -a



fs-extra


Еженедельные загрузки: 32 млн.

Назначение: дополнительные методы по работе с файловой системой (расширение fs).

Основные методы:

  • copy — копирование файлов
  • emptyDir — очистка директории. Если директории не существует, она создается
  • ensureFile — определение наличия файла. Если выполняется запрос на создание файла в директориях, которых не существует, они создаются
  • ensureDir — тоже самое применительно к директории. Алиасы — mkdirs, mkdirp
  • move — перемещение файла или директории
  • outputFile — аналогично fs.writeFile, за исключением того, что при отсутствии создается родительская директория
  • outputJson — аналогично fs.writeJson, за исключением того, что при отсутствии создается родительская директория
  • readJson — чтение JSON-файла и его преобразование в объект
  • remove — удаление файла или директории
  • writeJson — запись объекта в JSON-файл

Приведенные методы являются асинхронными, существуют их синхронные аналоги (с приставкой «Sync»).

Методы могут использоваться с функциями обратного вызова, промисами и async/await. Я буду использовать промисы.

const fs = require("fs-extra");

// copy
fs.copy("/old-file", "/dir/new-file")
	.then(() => {
		console.log("получилось!");
	})
	.catch((err) => {
		console.error(err);
	});

// emptyDir
fs.emptyDir("/some/empty/dir")
	.then(() => {
		console.log("получилось!");
	})
	.catch((err) => {
		console.error(err);
	});

// ensureFile
const file = "/this/path/does/not/exist/file.txt";

fs.ensureFile(file)
	.then(() => {
		console.log("получилось!");
	})
	.catch((err) => {
		console.error(err);
	});

// move
const src = "/dir/file.txt";
const dest = "/dir/this/path/does/not/exist/file.txt";

fs.move(src, dest)
	.then(() => {
		console.log("получилось!");
	})
	.catch((err) => {
		console.error(err);
	});

// outputFile
const file = "/this/path/does/not/exist/file.txt";
fs.outputFile(file, "привет!")
	.then(() => fs.readFile(file, "utf8"))
	.then((data) => {
		console.log(data); // => привет!
	})
	.catch((err) => {
		console.error(err);
	});

// readJson
fs.readJson("/data.json")
	.then((obj) => {
		console.log(obj.someKey); // => some value
	})
	.catch((err) => {
		console.error(err);
	});

// remove
fs.remove("/dir/file")
	.then(() => {
		console.log("получилось!");
	})
	.catch((err) => {
		console.error(err);
	});

// writeJson
fs.writeJson("/data.json", { someKey: "some value" })
	.then(() => {
		console.log("получилось!");
	})
	.catch((err) => {
		console.error(err);
	});

uuid


Еженедельные загрузки: 36 млн.

Назначение: генерация уникального идентификатора.

Пример использования.

Создадим сервер с помощью express, возвращающий идентификатор по запросу клиента.

Устанавливаем uuid, express и cors: yarn add uuid express cors.

Создаем файл uuid.js:

const express = require("express");
const cors = require("cors");

const { v4: uuidv4 } = require("uuid");

const app = express();
app.use(cors());

app.use("/getId", (_, res) => {
	// генерируем идентификатор
	const id = uuidv4();
	// отправляем его клиенту
	res.json(id);
});

app.listen(process.env.PORT || 3000, () => console.log("server ready"));

Запускаем сервер: node uuid.js.

Создаем файл index.html:

<body>
	<input type="text" />
	<button>add</button>
	<ul></ul>

	<script>
		const button = document.querySelector("button");
		const input = document.querySelector("input");
		const list = document.querySelector("ul");

		button.addEventListener("click", async () => {
			// запрашиваем идентификатор
			const res = await fetch("http://localhost:3000/getId");
			const id = await res.json();

			const value = input.value;

			if (value.trim() !== "") {
				list.insertAdjacentHTML(
					"beforeend",
					// используем идентификатор
					`<li data-id=${id}>${value}</li>`
				);
			}
		});

		input.value = "аптека, улица, фонарь...";
		button.click();
	</script>
</body>

Открываем index.html в браузере.



В качестве альтернативы в простых приложениях можно использовать метод «Date.now()», возвращающий количество миллисекунд, прошедших с 1 января 1970 года:

const id1 = Date.now();
console.log(id1); // 1601905165758
console.log(typeof id1); // number

const id2 = Date.now().toString().slice(-4);
console.log(id2); // 5758

moment


Еженедельные загрузки: 14 млн.

Назначение: форматирование даты и времени.

Пример использования.

const moment = require("moment");

console.log(
	`
		${moment().format()}
		${moment().format("DD.MM.YYYY H:m")}
	`
);
/*
	2020-10-05T19:16:38+05:00
	05.10.2020 19:16
*/

moment.locale("ru");

console.log(moment().format("dddd, Do MMMM Y"));
// понедельник, 5-го октября 2020

Альтернатива: конструкторы Date и Intl.DateTimeFormat.

const date = new Date();

const options = {
	day: "numeric",
	month: "long",
	year: "numeric",
};

console.log(date.toLocaleString("ru", options));
// 5 октября 2020 г.

options.weekday = "long";
options.hour = "numeric";
options.minute = "numeric";

console.log(
	new Intl.DateTimeFormat("ru", options).format(date)
); // понедельник, 5 октября 2020 г., 19:42

Разработка moment в настоящее время прекращена.

axios


Еженедельные загрузки: 12 млн.

Назначение: отправка HTTP-запросов. Работает как в Node.js, так и в браузере.

Пример использования.

Устанавливаем пакет: yarn add axios.

Создаем файл axios.js:

// GET
const axios = require("axios");

(async () => {
	try {
		const res = await axios.get(
			"https://jsonplaceholder.typicode.com/users"
		);
		console.table(res.data);
	} catch (er) {
		console.error(er);
	}
})();

Запускаем файл: node axios.js.



// POST
axios
	.post("/user", {
		firstName: "Harry",
		lastName: "Heman",
	})
	.then((res) => {
		console.log(res);
	})
	.catch((er) => {
		console.error(er);
	});

Реализация GET-запроса на «чистом» Node.js:

const https = require("https");

https
	.get("https://jsonplaceholder.typicode.com/users", (res) => {
		let data = "";

		res.on("data", (chunk) => {
			data += chunk;
		});

		res.on("end", () => {
			console.table(JSON.parse(data));
		});
	})
	.on("error", (er) => {
		console.error(er.message);
	});

В браузере можно использовать Fetch API:

;(async () => {
	await fetch("http://example.com/user", {
		method: "POST",
		mode: "no-cors",
		body: JSON.stringify({
			firstName: "Harry",
			lastName: "Heman",
		}),
	});
})();

В сети можно встретить упоминание другого пакета для отправки HTTP-запросов — request, однако в настоящее время он признан устаревшим.

async


Еженедельные загрузки: 31 млн.

Назначение: утилита для работы с асинхронным кодом. Работает как в Node.js, так и в браузере.

Пример использования.

Устанавливаем пакет: yarn add async.

Создаем 3 файла (название -> контент): file1.txt -> foo, file2.txt -> bar, file3.txt -> baz qux.

Создаем файл async.js:

const async = require("async");
const fs = require("fs");

const ext = "txt";
const file = (name) => `${__dirname}/${name}.${ext}`;

// определяем размеры файлов
async.map(
	[file("file1"), file("file2"), file("file3")],
	fs.stat,
	(er, results) => {
		if (er) console.error(er);

		console.log(results.map((item) => item.size));
	}
); // [ 3, 3, 7 ]

// определяем содержимое файлов
async.map(
	[file("file1"), file("file2"), file("file3")],
	fs.readFile,
	(er, results) => {
		if (er) console.error(er);

		console.log(results.map((item) => item.toString()));
	}
); // [ 'foo', 'bar', 'baz qux' ]

// параллельное выполнение асинхронных функций
const timer1 = () => setTimeout(() => console.log("foo"), 300);
const timer2 = () => setTimeout(() => console.log("bar"), 100);
const timer3 = () => setTimeout(() => console.log("baz"), 200);
const asyncFuntions = [timer1, timer2, timer3];

async.parallel(asyncFuntions, (er, res) => {
	console.log(res);
}); // bar baz foo

// выполнение последней асинхронной функции
async.series(asyncFuntions, (_, res) => console.log(res));
// foo

// композиция из асинхронных функций
const add1 = (n, cb) => setTimeout(() => cb(null, n + 1), 100);
const sub2 = (n, cb) => setTimeout(() => cb(null, n - 2), 200);
const mult3 = (n, cb) => setTimeout(() => cb(null, n * 3), 300);

const composed = async.compose(add1, sub2, mult3);
// mult3 -> sub2 -> add1
composed(4, (_, res) => console.log(res)); // 11

dotenv


Еженедельные загрузки: 12 млн.

Назначение: загрузка переменных окружения из файла ".env" в process.env.

Пример использования.

Имитируем подключение к MongoDB Cloud.

Устанавливаем пакет: yarn add dotenv.

Создаем файл ".env":

// <usename>, <password> и <dbname> подлежат замене
MONGODB=mongodb+srv://<username>:<password>@cluster0.p7jbn.mongodb.net/<dbname>?retryWrites=true&w=majority

Создаем файл «dotenv.js»:

// подключаем dotenv
require("dotenv/config");

// подключаем mongoose
const mongoose = require("mongoose");

// process.env.MONGODB - загрузка переменной "MONGODB" из файла ".env"
mongoose.connect(process.env.MONGODB, {
	useNewUrlParser: true,
	useUnifiedTopology: true,
});

В качестве альтернативы можно использовать config.

Устанавливаем пакет: yarn add config.

Создаем файл config.json:

{
	"mongoDB": "mongodb+srv://<username>:<password>@cluster0.p7jbn.mongodb.net/<dbname>?retryWrites=true&w=majority"
}

Создаем файл config.js:

// подключаем config
const config = require("config");

// подключаем mongoose
const mongoose = require("mongoose");

// config.get("mongoDB") - получение значения свойства "mongoDB" объекта "config" из файла "config.json"
mongoose.connect(config.get("mongoDB"), {
	useNewUrlParser: true,
	useUnifiedTopology: true,
}, () => console.log("Connected to database"));

validator


Еженедельные загрузки: 4 млн.

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

Пример использования.

Реализуем отправку формы и валидацию данных на сервере.

Устанавливаем пакет: yarn add validator.

Создаем файл index.html:

<head>
	<title>Form Validation</title>
	<style>
		label,
		span {
			display: block;
			margin: .5rem;
		}
	</style>
</head>
<body>
	<form>
		<label>Name: <input type="text" value="Igor" data-name="name" /></label>
		<label>Age: <input type="number" value="30" data-name="age" /></label>
		<label
			>Email:
			<input type="email" value="myemail.example.com" data-name="email"
		/></label>
		<label
			>Site: <input type="url" value="myblog.org" data-name="url"
		/></label>
		<label
			>Card: <input type="text" value="1111222233334444" data-name="card"
		/></label>
		<button>Send</button>
		<p></p>
	</form>

	<script>
		const form = document.querySelector("form");
		// кнопка для отправки формы
		const btn = form.querySelector("button");
		// контейнер для вывода сообщения
		const log = form.querySelector("p");

		// вспомогательная функция для получения значения указанного инпута
		const getValue = (name) =>
			form.querySelector(`[data-name=${name}]`).value;

		btn.addEventListener("click", (e) => {
			e.preventDefault();

			// формируем данные для отправки
			const data = {
				name: getValue("name"),
				age: getValue("age"),
				email: getValue("email"),
				url: getValue("url"),
				card: getValue("card"),
			};

			// функция отправки данных
			const sendData = async (url) => {
				const req = await fetch(url, {
					method: "POST",
					headers: {
						"Content-Type": "application/json",
					},
					body: JSON.stringify(data),
				});

				// ответ от сервера в виде массива
				const res = await req.json();

				console.log(res);

				// вывод сообщения о результате
				if (res.length !== 0) {
					let html = "";
					for (const msg of res) {
						html += `<span>${msg}</span>`;
						log.innerHTML = html;
					}
				} else {
						log.textContent = 'Success'
					}
			};

			sendData("http://localhost:3000/users");
		});
	</script>

Создаем файл validator.js:

const express = require("express");
const cors = require("cors");

// извлекаем функции, необходимые для валидации
// escape - функция обезвреживания данных
const {
	isAlpha,
	isNumeric,
	isEmail,
	isURL,
	isCreditCard,
	escape,
} = require("validator");

// собираем валидаторы в массив
const validators = [isAlpha, isNumeric, isEmail, isURL, isCreditCard];

const app = express();
app.use(cors());
app.use(express.json());

// обрабатываем получение данных
app.use("/users", (req, res) => {
	// извлекаем необходимые данные
	const data = ({ name, age, email, url, card } = req.body);

	console.log(data);

	// массив с ошибками
	const errors = [];

	// проводим валидацию
	for (let i = 0; i < validators.length; i++) {
		// обезвреживаем данные
		escape(Object.values(data)[i]);

		// если данные не прошли валидацию
		// добавляем соответствующее сообщение в массив с ошибками
		if (!validators[i](Object.values(data)[i])) {
			errors.push(`Wrong ${Object.keys(data)[i]}`);
		}
	}

	console.log(errors);

	// отправляем ошибки клиенту
	res.json(errors);
});

app.listen(process.env.PORT || 3000, () => console.log("Server ready"));

Запускаем сервер: node validator.js.

Открываем index.html и отправляем данные.





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

Изменим значения email и card на myemail@example.com и 2222111111111111, соответственно.

Нажимаем «Send».





Получаем сообщение об успешной валидации данных.

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