Как оптимизировать настройку приложений React Native

Для настройки проекта на React Native есть два варианта.

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

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

Как избежать этой проблемы? Создание проекта с нуля кажется самым правильным решением. Но как облегчить и ускорить его?


Создание CLI

Мы создадим собственный CLI, который можно использовать в последующих проектах. Он будет отвечать за установку React Native и многократно применяемых модулей. Мы также упакуем CLI и опубликуем его на npm.

Для этого проекта мы будем использовать Commander.js, поскольку работаем в среде Node.js. Commander.js  —  это популярный и надежный инструмент, незаменимый при создании командных программ в том стиле, который вы хотите использовать в терминале. Мы также будем применять shelljs для последовательного запуска shell-команд в любой среде ОС.

Для начала определимся с тем, что будет “в коробке”. Несмотря на то что для React Native создано множество отличных библиотек, основные из них практически одинаковы. Вот список модулей для этого проекта:

  • React Navigation;
  • Redux Toolkit;
  • React Native Reanimated;
  • React Query;
  • Axios;
  • Lottie;
  • Formik и Yup.

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

const modules = [
{ name: "React Navigation", module: "@react-navigation/native" },
{ name: "Redux Toolkit", module: "@reduxjs/toolkit" },
{ name: "React Native Reanimated", module: "react-native-reanimated" },
{ name: "React Query", module: "@tanstack/react-query" },
{ name: "Axios", module: "axios" },
{ name: "Formik", module: "formik" },
{ name: "Yup", module: "yup" },
];

Теперь установим Сommander.js с помощью npm i commander --save и приступим к написанию CLI:

#!/usr/bin/env node

const { program } = require("commander");
const shell = require("shelljs");

const modules = [
{ name: "React Navigation", module: "@react-navigation/native" },
{ name: "Redux Toolkit", module: "@reduxjs/toolkit" },
{ name: "React Native Reanimated", module: "react-native-reanimated" },
{ name: "React Query", module: "@tanstack/react-query" },
{ name: "Axios", module: "axios" },
{ name: "Formik", module: "formik" },
{ name: "Yup", module: "yup" },
];

function installRN(directory) {
console.log("Installing react Native...");
if (
shell.exec(`npx --package react-native-cli react-native init ${directory}`)
.code !== 0
) {
console.log("Error installing React Native!");
shell.exit(1);
}
}

program
.name("rncli")
.description("Create new React Native project with essential modules")
.version("0.0.1");

program
.command("install")
.argument("<directory>", "Directory to install")
.action((directory) => {
console.log("Initializing new RN project in folder: ", directory);
//установка React Native
installRN(directory);
});

program.parse();

Вот базовая версия командного скрипта, который запускает установку react-native и завершает работу. Разберем его по частям для лучшего понимания.

Начинаем скрипт с директивы shebang bash, чтобы обеспечить переносимость в различных UNIX-системах. Это также необходимо для того, чтобы при запуске скрипт вел себя как cli.

Затем представляем программу, определяя ее имя, версию и описание. В качестве названия программы я выбрал rncli, поэтому при ее использовании необходимо набрать rncli в терминале. Программы Commander.js располагают командами, у которых есть аргументы и опции. Когда вы устанавливаете модуль, вы используете npm. Допустим, у нас есть следующая запись:

npm install react-native --save

Здесь npm  —  программа, install  —  команда, react-native  —  аргумент, а --save  —  опция. То же самое относится и к примеру. Мы используем команду install и в качестве аргумента указываем каталог для установки.

Аргументы могут быть <required> и [optional]. Если опустим аргумент required, получим ошибку.

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

Теперь перейдем к установке модулей:

#!/usr/bin/env node

const { program } = require("commander");
const shell = require("shelljs");

let projectDir = "";

const modules = [
{ name: "React Navigation", module: "@react-navigation/native" },
{ name: "Redux Toolkit", module: "@reduxjs/toolkit" },
{ name: "React Native Reanimated", module: "react-native-reanimated" },
{ name: "React Query", module: "@tanstack/react-query" },
{ name: "Axios", module: "axios" },
{ name: "Formik", module: "formik" },
{ name: "Yup", module: "yup" },
];

function installRN(isTypeScript = false) {
if (
shell.exec(
`npx --package react-native-cli react-native init ${directory} ${
isTypeScript ? "--template react-native-template-typescript" : ""
}`
).code !== 0
) {
shell.echo('Error: "react-native init" failed');
shell.exit(1);
}
}

function installModules() {
//если текущий каталог не является каталогом проекта, измените его следующим образом
if (shell.pwd() !== projectDir) shell.cd(projectDir);

const moduleNames = modules.map((x) => x.module).join(" ");

if (shell.exec(`npm i ${moduleNames} --save`).code !== 0) {
shell.echo("Error: something went wrong when installing modules");
shell.exit(1);
}
console.log("Done installing modules!");
}

program
.name("rncli")
.description("Create new React Native project with essential modules")
.version("0.0.1");

program
.command("install")
.argument("<directory>", "Directory to install")
.option("-t, --typescript", "Install TypeScript template")
.action((directory, options) => {
//получение полного пути к рабочему каталогу
projectDir = `${shell.pwd()}/${directory}`;
console.log("Initializing new RN project in folder: ", directory);
//установка React Native
installRN(options.typescript);
//установка дополнительных модулей
installModules();
});

program.parse();

Здесь генерируем сцепленную (объединенную) строку имен модулей и предоставляем ее команде npm install. Указываем также опцию --typescript для пользователей, предпочитающих писать на TS.

На этом этапе главная цель достигнута: мы получили простой CLI, который позволяет сэкономить пару минут. Теперь попробуем его улучшить.

Улучшение CLI

Чтобы сделать CLI более дружелюбным и более “современным”, можно добавить режим wizard (режим мастера/советника), который будет сопровождать пользователя на каждом шагу. Этот мастер объяснит, что будет делать программа, спросит, какой язык предпочтителен, и перечислит имена модулей вместе с номерами их последних версий.

Прежде всего установим указанные ниже модули:

npm i figlet axios inquier loading-cli --save

Если коротко, то Inquier позволяет отображать списки и вводные данные. Figlet генерирует привлекательный ASCII текст, а loading-cli и colors-cli добавляют в CLI колористики и эффект загрузчика. У нас также будет возможность получать версии пакетов через конечную точку npm. Я выбрал axios, но вы можете использовать простой fetch.

Теперь добавим новую команду “wizard”, не указывая никаких аргументов, и позволим программе вести нас шаг за шагом.

Окончательный код будет выглядеть следующим образом:

#!/usr/bin/env node

const { program } = require("commander");
const shell = require("shelljs");
const loading = require("loading-cli");
const axios = require("axios");
const figlet = require("figlet");

let projectDir = "";
let isTypeScript = false;

const modules = [
{ name: "React Navigation", module: "@react-navigation/native" },
{ name: "Redux Toolkit", module: "@reduxjs/toolkit" },
{ name: "React Native Reanimated", module: "react-native-reanimated" },
{ name: "React Query", module: "@tanstack/react-query" },
{ name: "Axios", module: "axios" },
{ name: "Formik", module: "formik" },
{ name: "Yup", module: "yup" },
];

async function getVersion(module) {
//получение версии отдельного модуля из NPM
return new Promise(function (resolve, reject) {
axios
.get(`https://registry.npmjs.org/${module}`)
.then(function (data, err) {
if (err) reject(err);
resolve(data.data["dist-tags"].latest);
});
});
}

async function getVersions() {
//сбор версий всех модулей
let arr = [];
modules.forEach((x) => arr.push(getVersion(x.module)));
const results = await Promise.all(arr);
return results;
}

function installRN() {
if (
shell.exec(
`npx --package react-native-cli react-native init ${directory} ${
isTypeScript ? "--template react-native-template-typescript" : ""
}`
).code !== 0
) {
shell.echo('Error: "react-native init" failed');
shell.exit(1);
}
console.log("Done installing react Native!");
}

function installModules() {
//если текущий каталог не является каталогом проекта, измените его следующим образом
if (shell.pwd() !== projectDir) shell.cd(projectDir);

const moduleNames = modules.map((x) => x.module).join(" ");

if (shell.exec(`npm i ${moduleNames} --save`).code !== 0) {
shell.echo("Error: something went wrong when installing modules");
shell.exit(1);
}
console.log("Done installing modules!");
}

async function displayPrompt() {
const load = loading("fetching latest version codes...").start();
try {
const results = await getVersions();
if (results) {
load.stop();
console.table(
modules.map((x, index) => ({ ...x, version: results[index] }))
);
}
} catch (error) {
console.error("Could not get versions: " + error);
}

const answers = await inquirer.prompt([
{
type: "confirm",
name: "continue",
message:
"This will install latest React Native along with the modules below. Do you want to continue?",
},
]);
if (!answers.continue) exit(0);
selectLanguage();
}

async function selectLanguage() {
try {
const answers = await inquirer.prompt([
{
type: "list",
name: "lang",
message: "Please select your language:",
choices: ["JavaScript", "TypeScript"],
filter(val) {
return val === "TypeScript";
},
},
]);
if (answers) {
//
enterDirectory();
}
} catch (error) {
if (error.isTtyError) {
console.error("Cannot render list in current environment");
} else {
console.log("An error occured: ", error);
}
}
}

async function enterDirectory() {
try {
const answers = await inquirer.prompt([
{
type: "input",
name: "dir",
message:
"Please enter your project's name. A directory with the same name will also be created:",

filter(val) {
return val.toLowerCase();
},
},
]);
if (answers) {
projectDir = `${shell.pwd()}/${answers.dir}`;
installRN();
}
} catch (error) {
if (error.isTtyError) {
console.error("Cannot render list in current environment");
} else {
console.log("An error occured: ", error);
}
}
}
program
.name("rncli")
.description("Create new React Native project with essential modules")
.version("0.0.1");

program
.command("install")
.argument("<directory>", "Directory to install")
.option("-t, --typescript", "Install TypeScript template")
.action((directory, options) => {
//получение полного пути к рабочему каталогу
projectDir = `${shell.pwd()}/${directory}`;
if (options.typescript != undefined) isTypeScript = options.typescript;
console.log("Initializing new RN project in folder: ", directory);
//установка React Native
installRN();
//установка дополнительных модулей
installModules();
});

program.command("wizard").action(() => {
figlet("RNCLI", function (err, data) {
if (err) {
console.log("Something went wrong with figlet...");
return;
}
console.log(data);
displayPrompt();
});
});

program.parse();

Программа готова. Если в самом начале передать аргумент “wizard” как rncli wizard, то программа перечислит модули и их последние версии и спросит, хотим ли мы продолжить. Если нет, то сразу завершит работу. Продолжая работу, программа запросит язык и каталог. Получив эту информацию, начнет установку модулей, как мы это делали ранее в начальном CLI.

Публикация

Последний шаг  —  публикация. Перед отправкой пакета в npm, убедитесь, что в package.json есть эти две записи:

"main": "index.js",
"bin": {"rncli": "index.js"},

Пусть вас не сбивает с толку имя bin. В npm будет отображаться имя пакета, а не имя CLI. Если имя уже занято, можно изменить его на другое.

Войдите в npm и опубликуйте пакет:

npm login
npm publish

Вот и все! CLI опубликован в открытом доступе на npm. Теперь вы можете быстро протестировать его с помощью npx rncli wizard, чтобы проверить, все ли работает.

Заключение

Погрузившись ненадолго в мир CLI, вы узнали, как создать командную программу в Node.js. Приведенный здесь пример не ограничивается только React Native. Его можно использовать его в Angular, React.js и минимальном Node.js, модифицируя для любого языка, поддерживающего npm. Вы также можете усовершенствовать его, добавив структуру каталогов, цвета и т.д.

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Burak Erdem: Want To Set Up Your Next React Native Project Faster? Use CLI

Предыдущая статьяПочему разработчики JavaScript используют инструменты на Rust
Следующая статья5 проектов Go: управление безопасностью и контейнерами, создание бэкендов