Запуск нового проекта (особенно в качестве разработчика JavaScript) часто может быть повторяющимся и утомительным процессом. Для каждого нового проекта нам обычно нужно добавить файл package.json
, добавить некоторые стандартные зависимости, настроить их, создать правильную структуру каталогов, добавить различные другие файлы … Этот список можно продолжить.
Но мы ленивые разработчики, верно? И, к счастью, мы можем автоматизировать это. Для этого не требуется никаких специальных инструментов или странных языков — если вы уже знаете JavaScript, процесс на самом деле довольно прост.
В этом руководстве мы собираемся использовать Node.js для создания кроссплатформенного интерфейса командной строки (CLI). Это позволит нам быстро создать новый проект, используя набор предопределенных шаблонов. Он будет полностью расширяемым, так что вы сможете легко адаптировать его к вашим собственным потребностям и автоматизировать утомительные части вашего рабочего процесса.
Зачем кататься самостоятельно?
Хотя для этой задачи существует множество подобных инструментов (таких как Yeoman ), создавая свои собственные, мы получаем знания, опыт и можем сделать их полностью настраиваемыми. Вы всегда должны рассматривать идею создания своих инструментов поверх существующих, особенно если вы пытаетесь решить специализированные проблемы. Это может звучать вопреки обычной практике повторного использования программного обеспечения, но в некоторых случаях реализация собственного инструмента может быть очень полезной. Получение знаний всегда полезно, но вы также можете предложить высоко персонализированные и эффективные инструменты, специально разработанные для ваших нужд.
Сказав это, мы не будем заново изобретать колесо. Сам CLI будет построен с использованием библиотеки Caporal.js . Внутренне он также будет использовать приглашение для запроса пользовательских данных и shellJS , которые предоставят нам некоторые инструменты Unix прямо в нашей среде Node.js. Я выбрал эти библиотеки в основном из-за их простоты использования, но после завершения этого урока вы сможете поменять их на альтернативы, которые лучше всего соответствуют вашим потребностям.
Как всегда, вы можете найти завершенный проект на Github: https://github.com/sitepoint-editors/node-scaffolding-tool
Теперь давайте начнем …
Работает с Caporal.js
Сначала создайте новый каталог где-нибудь на вашем компьютере. Для этого проекта рекомендуется иметь специальный каталог, который может оставаться без изменений долгое время, так как последняя команда будет вызываться оттуда каждый раз.
Попав в каталог, создайте файл package.json
со следующим содержимым:
{ "name": "scaffold", "version": "1.0.0", "main": "index.js", "bin": { "scaffold": "index.js" }, "dependencies": { "caporal": "^0.3.0", "colors": "^1.1.2", "prompt": "^1.0.0", "shelljs": "^0.7.7" } }
Это уже включает в себя все, что нам нужно. Теперь для установки пакетов выполните npm install
и все отмеченные зависимости будут доступны в нашем проекте. Версии этих пакетов являются самыми последними на момент написания. Если новые версии станут доступны в то же время, вы можете обновить их (обращая внимание на любые изменения API).
Обратите внимание на значение scaffold
в bin
. Он указывает имя нашей команды и файл, который будет вызываться каждый раз, когда мы вводим эту команду в нашем терминале ( index.js
). Не стесняйтесь изменять это значение по мере необходимости.
Строительство точки входа
Первым компонентом нашего CLI является файл index.js
который содержит список команд, опций и соответствующих функций, которые нам будут доступны. Но прежде чем писать этот файл, давайте начнем с определения того, что собирается делать наш CLI, более подробно.
- Основной (и единственной) командой является команда
create
, которая позволяет нам создавать шаблон проекта по нашему выбору. - Команда
create
принимает обязательный аргументtemplate
, который указывает, какой шаблон мы хотим использовать. - Он также принимает параметр
--variant
который позволяет нам выбрать конкретный вариант нашего шаблона. - Если не указан конкретный вариант, он будет использовать вариант по умолчанию (мы определим это позже).
Caporal.js позволяет нам компактно определить вышесказанное. Давайте добавим следующий контент в наш файл index.js
:
#!/usr/bin/env node const prog = require('caporal'); prog .version('1.0.0') .command('create', 'Create a new application') .argument('<template>', 'Template to use') .option('--variant <variant>', 'Which <variant> of the template is going to be created') .action((args, options, logger) => { console.log({ args: args, options: options }); }); prog.parse(process.argv);
Первая строка — это Shebang, указывающая, что это исполняемый файл Node.js.
Включенный здесь шебанг работает только для Unix-подобных систем. В Windows нет поддержки shebang, поэтому, если вы хотите запустить файл непосредственно в Windows, вам придется искать обходной путь. Выполнение команды через npm (объяснено в конце этого раздела) будет работать на всех платформах.
Затем мы Caporal.js
пакет Caporal.js
качестве prog
и начинаем определять нашу программу. Используя командную функцию , мы определяем команду create
как первый параметр, а небольшое описание — как второй. Это будет показано в автоматически сгенерированном параметре справки для нашего CLI (с помощью --help
).
Затем мы template
аргумент template
внутри функции аргумента , и, поскольку он является обязательным аргументом, мы заключаем его в угловые скобки ( <
и >
).
Мы можем определить вариант option, написав --variant <variant>
внутри функции option . Это означает, что опция для нашей команды называется --variant
и значение будет сохранено в переменной variant
.
Наконец, в команде действия мы передаем другую функцию, которая будет обрабатывать текущую команду. Этот обратный вызов будет вызываться с тремя аргументами:
- переданные аргументы (
args
) - пропущенные варианты (
options
) - служебный объект для отображения вещей на экране (
logger
).
На этом этапе мы собираемся выйти из значений переданных аргументов и опций, чтобы мы могли получить представление о том, как получить необходимую информацию для выполнения действия из CLI.
Последняя строка передает информацию из команды scaffold
в анализатор Caporal.js, который выполняет тяжелую работу.
Сделайте CLI доступным глобально
Теперь мы можем протестировать наше приложение, чтобы увидеть, все ли идет по плану. Для этого нам нужно сделать его глобально доступным для нашей системы с помощью команды npm link . Выполните следующее из корня проекта:
npm link
После того, как процесс будет завершен, мы сможем выполнить scaffold
в нашем терминале внутри любого каталога, не index.js
явной ссылки на наш файл index.js
:
scaffold create node --variant mvc
И вы должны получить это в ответ:
{ args: { template: 'node' }, options: { variant: 'mvc' } }
Это образец информации, которую мы будем использовать для создания проектов из шаблонов.
Создание шаблона
Наши шаблоны будут состоять из файлов и структуры каталогов, которые нам нужны, чтобы начать работу с определенным типом проекта. Каждый шаблон будет иметь файл package.json
с некоторыми значениями-заполнителями, которые мы можем заполнить нашими реальными данными.
Для начала создайте каталог templates
в вашем проекте и каталог node
внутри него. В каталоге node
создайте каталог по default
(который будет использоваться, если мы не предоставляем variant
варианта) и второй каталог с именем mvc
(для создания проекта Node.js с использованием архитектуры MVC ).
Окончательная структура должна выглядеть так:
. └── templates └── node ├── default └── mvc
Теперь нам нужно заполнить папки default
и mvc
файлами проекта. Вы можете создать свои собственные или использовать те, которые представлены в примере приложения .
Затем мы можем приступить к размещению идентификаторов переменных там, где нам нужны динамические значения. Каждая папка шаблона должна содержать файл package.json
. Откройте их и включите все переменные заглавными буквами (без пробелов) и квадратными скобками.
Это файл package.json внутри нашего шаблона по умолчанию:
{ "name": "[NAME]", "version": "[VERSION]", "description": "[DESCRIPTION]", "main": "server.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node server.js", "start:dev": "nodemon server.js" }, "author": "[AUTHOR]", "license": "[LICENSE]", "dependencies": { "dotenv": "^2.0.0", "hapi": "^16.1.0", "hoek": "^4.1.0" }, "devDependencies": { "nodemon": "^1.11.0" } }
После создания всех переменных поместите их в файл _variables.js
в том же каталоге шаблонов, например:
/* * Variables to replace * -------------------- * They are asked to the user as they appear here. * User input will replace the placeholder values * in the template files */ module.exports = [ 'name', 'version', 'description', 'author', 'license' ];
Имена в экспортированном массиве совпадают в файлах, но в нижнем регистре и без квадратных скобок. Мы будем использовать этот файл для запроса каждого значения в CLI.
Теперь мы можем приступить к созданию функции для команды create
которая будет выполнять всю работу.
Построение функции «Создать»
В нашем файле index.js
мы ранее передавали простую функцию в action()
которая регистрировала значения, полученные CLI. Теперь мы собираемся заменить эту функцию новой, которая будет копировать файлы шаблона в каталог, где выполняется команда scaffold
. Мы также заменим переменные-заполнители значениями, полученными через пользовательский ввод.
Внутри каталога lib
(чтобы все было организовано) добавьте файл create.js
и поместите в него следующее содержимое:
module.exports = (args, options, logger) => { };
Мы собираемся поместить всю логику нашего приложения в эту функцию, что означает, что мы должны соответствующим образом изменить наш файл index.js
:
#!/usr/bin/env node const prog = require('caporal'); const createCmd = require('./lib/create'); prog .version('1.0.0') .command('create', 'Create a new application') .argument('<template>', 'Template to use') .option('--variant <variant>', 'Which <variant> of the template is going to be created') .action(createCmd); prog.parse(process.argv);
Импорт зависимостей и установка переменных
Теперь, возвращаясь к файлу create.js
, мы можем поместить в начало файла следующее, чтобы сделать необходимые пакеты доступными:
const prompt = require('prompt'); const shell = require('shelljs'); const fs = require('fs'); const colors = require("colors/safe"); // Set prompt as green and use the "Replace" text prompt.message = colors.green("Replace");
Обратите внимание на настройку параметров для сообщений с подсказками. Это совершенно необязательно.
Внутри экспортируемой функции первое, что мы собираемся добавить, это некоторые переменные:
const variant = options.variant || 'default'; const templatePath = `${__dirname}/../templates/${args.template}/${variant}`; const localPath = process.cwd();
Как видите, мы берем параметр варианта, переданный команде scaffold
и устанавливаем его в 'default'
если этот параметр был опущен. Переменная templatePath
содержит полный путь для указанного шаблона, а localPath
содержит ссылку на каталог, в котором была выполнена команда.
Копирование файлов шаблона
Процесс копирования файлов очень прост с использованием функции cp из shellJS
. Ниже переменных, которые мы только что включили, добавьте следующее:
if (fs.existsSync(templatePath)) { logger.info('Copying files…'); shell.cp('-R', `${templatePath}/*`, localPath); logger.info(' The files have been copied!'); } else { logger.error(`The requested template for ${args.template} wasn't found.`) process.exit(1); }
Во-первых, мы убедимся, что шаблон существует, в противном случае мы выйдем из процесса с сообщением об ошибке, используя logger.error()
из Caporal.js. Если шаблон существует, мы покажем уведомление с помощью logger.info()
и скопируем файлы с помощью shell.cp()
. Параметр -R
указывает, что он должен копировать файлы рекурсивно из пути шаблона в путь, где выполняется команда. Как только файлы скопированы, мы показываем подтверждающее сообщение. А поскольку функции shellJS являются синхронными, нам не нужно использовать обратные вызовы, обещания или что-то подобное — мы просто должны писать код процедурным способом.
Замена переменных
Хотя идея замены переменных в файлах звучит как сложная вещь, это довольно просто, если мы используем правильные инструменты. Одним из них является классический редактор sed из систем Unix, который может динамически преобразовывать текст. ShellJS предоставляет нам эту утилиту, которая будет работать как в системах Unix (Linux и MacOS), так и в Windows.
Чтобы выполнить все замены, добавьте следующий фрагмент кода в ваш файл под кодом, который мы создали ранее:
const variables = require(`${templatePath}/_variables`); if (fs.existsSync(`${localPath}/_variables.js`)) { shell.rm(`${localPath}/_variables.js`); } logger.info('Please fill the following values…'); // Ask for variable values prompt.start().get(variables, (err, result) => { // Remove MIT License file if another is selected // Omit this code if you have used your own template if (result.license !== 'MIT') { shell.rm(`${localPath}/LICENSE`); } // Replace variable values in all files shell.ls('-Rl', '.').forEach(entry => { if (entry.isFile()) { // Replace '[VARIABLE]` with the corresponding variable value from the prompt variables.forEach(variable => { shell.sed('-i', `\\[${variable.toUpperCase()}\\]`, result[variable], entry.name); }); // Insert current year in files shell.sed('-i', '\\[YEAR\\]', new Date().getFullYear(), entry.name); } }); logger.info(' Success!'); });
Мы начнем с чтения, а variables
устанавливаются на содержимое файла шаблона _variables.js
который мы создали ранее.
Затем, поскольку мы скопировали все файлы из шаблона, первый оператор if
удалит файл _variables.js
из нашего локального каталога, поскольку он необходим только в самом CLI.
Значение каждой переменной получается с помощью инструмента подсказки , передавая массив переменных в функцию get()
. Таким образом, CLI запросит у нас значение для каждого элемента в этом массиве и сохранит результат в объекте с именем result
который передается в функцию обратного вызова. Этот объект содержит каждую переменную в качестве ключа и введенный текст в качестве значения.
Следующий оператор if
необходим только в том случае, если вы используете включенные шаблоны в репозитории, поскольку мы также включаем файл LICENSE. Тем не менее, полезно посмотреть, как мы можем получить значение для каждой переменной, в данном случае из свойства license
используя result.license
. Если пользователь вводит лицензию, отличную от MIT
, мы удаляем файл LICENSE из каталога, используя функцию ShellJS rm()
.
Теперь перейдем к интересной части. Используя функцию ls из ShellJS, мы можем получить список всех файлов в текущем каталоге ( .
), Где мы собираемся заменить переменные. Мы передаем ему параметр -Rl
, поэтому он становится рекурсивным и возвращает объект файла вместо имени файла.
Мы перебираем список файловых объектов, используя forEach()
и для каждого проверяем, получаем ли мы файл, используя функцию isFile()
. Если мы получаем каталог, мы ничего не делаем.
Затем для каждого файла, который мы получаем, мы перебираем все переменные и выполняем функцию sed
следующим образом:
shell.sed('-i', `\\[${variable.toUpperCase()}\\]`, result[variable], entry.name);
Здесь мы передаем опцию -i
которая позволяет нам заменить текст, затем передаем строку регулярного выражения, которая будет соответствовать идентификатору variable
в верхнем регистре и заключена в квадратные скобки ( [
и ]
). Затем каждое совпадение этого регулярного выражения будет заменено значением соответствующей переменной ( result[variable]
), и, наконец, мы передаем имя файла, который мы заменяем, из функции forEach()
( entry.name
).
Второй sed
совершенно необязателен. Это только для замены [YEAR]
вхождений на текущий год. Полезно для файлов README.md
или README.md
.
Вот и все! Теперь мы можем выполнить нашу команду снова в пустой директории, чтобы увидеть, как она генерирует структуру проекта и заменяет все переменные новыми значениями:
// To generate a Node.js MVC project scaffold create node --variant mvc // To generate a default Node.js project scaffold create node
После выполнения команды она должна начать запрашивать у вас значение переменных, и, как только процесс завершится, появится сообщение об успешном завершении. Чтобы проверить, все ли прошло так, как ожидалось, откройте файл, содержащий переменные, и вы должны увидеть текст, введенный вами в процессе CLI, а не идентификаторы в верхнем регистре.
Если вы использовали шаблоны из репозитория (https://github.com/sitepoint-editors/node-scaffolding-tool
), чтобы следовать, вы также должны были сгенерировать рабочие проекты Node, которые можно запустить, запустив npm install
затем npm start
.
Что делать дальше
Мы успешно создали инструмент CLI для создания новых проектов Node.js из шаблонов, но нам не нужно останавливаться на этом. Поскольку мы создаем наш инструмент с нуля, у нас есть абсолютная свобода в том, что он может делать. В качестве вдохновения вы можете взять следующие идеи:
- Расширьте переменные, чтобы заменить блоки кода вместо простых слов; для этого вы можете использовать более сложные регулярные выражения и группы захвата в функции
sed
. - Добавьте больше команд для создания определенных файлов для каждого вида проекта, например, новые модели для шаблона MVC.
- Включите команды для развертывания проекта на сервере, чего можно добиться с помощью библиотек для rsync и удаленных команд через SSH.
- Если у вас сложная настройка, вы также можете попробовать добавить команды для создания статических ресурсов или исходных файлов, что может быть полезно в случае статического сайта.
- Используйте функцию
mv
для переименования файлов из имен переменных.
Вывод
В этом руководстве я продемонстрировал, как мы можем создать интерфейс командной строки для быстрого запуска новых проектов в привычной среде. Но это не одноразовый проект — вы можете расширять его по мере необходимости. Создание автоматизированных инструментов — это то, что характеризует разработчиков. Если вы обнаружите, что выполняете повторяющиеся задачи, просто остановитесь и подумайте, сможете ли вы автоматизировать это. В большинстве случаев это возможно, и долгосрочная выгода может быть огромной.
Теперь это для тебя? Вы любите автоматизировать повторяющиеся и утомительные работы? Какой твой инструментарий по выбору? Позвольте мне знать в комментариях ниже.
Эта статья была рецензирована Джоан Инь , Камило Рейесом и Тимом Севериеном . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!