Назначение строительных лесов состоит в том, чтобыБыстро построить базовую структуру проекта и предоставить спецификации и соглашения по проекту.. В настоящее время широко используемые в повседневной работе скаффолды включают vue-cli, create-react-app, angular-cli и т. д., все из которых завершают быстрое создание контента с помощью простых команд инициализации.
Скаффолдинг — это инструмент, который мы часто используем, и важное средство для повышения эффективности команд. Поэтому систематическое овладение знаниями о строительных лесах очень важно для разработчиков интерфейса.Даже если многие люди не обязательно будут участвовать в работе над инфраструктурой своих соответствующих отделов или компаний в будущем, систематическое овладение этим навыком также может облегчить наше дальнейшее развитие. Прочтите исходный код.Давайте узнаем вместе😉
1. Простой прототип строительных лесов 🐣
Скаффолдинг заключается в том, чтобы задать несколько простых вопросов при запуске и отобразить соответствующий файл шаблона по результатам ответа пользователя., основной рабочий процесс выглядит следующим образом:
- Задавайте вопросы пользователям в интерактивном режиме из командной строки
- Сгенерировать файл по результатам ответов пользователя
Например, мы используем vue-cliПри создании проекта vue 👇
шаг 1: запустите команду создания
$ vue create hello-world
шаг 2: задавайте вопросы пользователям
Шаг 3: Создайте файлы проекта, отвечающие потребностям пользователя
# 忽略部分文件夹
vue-project
├─ index.html
├─ src
│ ├─ App.vue
│ ├─ assets
│ │ └─ logo.png
│ ├─ components
│ │ └─ HelloWorld.vue
│ ├─ main.js
│ └─ router
│ └─ index.js
└─ package.json
Ссылаясь на вышеуказанный процесс, мы можем сделать это самиСоздайте простой прототип строительных лесов
1. Запустите cli из командной строки
Цель:Реализовано в командной строкеmy-node-cli
чтобы начать наши леса
1.1 Создайте новый каталог проекта my-node-cli
$ mkdir my-node-cli
$ cd my-node-cli
$ npm init # 生成 package.json 文件
1.2 Создайте новый файл входа в программу cli.js
$ touch cli.js # 新建 cli.js 文件
Укажите файл записи как cli.js в файле package.json 👇
{
"name": "my-node-cli",
"version": "1.0.0",
"description": "",
"main": "cli.js",
"bin": "cli.js", // 手动添加入口文件为 cli.js
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
На данный момент структура каталогов проекта:
my-node-cli
├─ cli.js
└─ package.json
Откройте cli.js для редактирования
#! /usr/bin/env node
// #! 符号的名称叫 Shebang,用于指定脚本的解释程序
// Node CLI 应用入口文件必须要有这样的文件头
// 如果是Linux 或者 macOS 系统下还需要修改此文件的读写权限为 755
// 具体就是通过 chmod 755 cli.js 实现修改
// 用于检查入口文件是否正常执行
console.log('my-node-cli working~')
1.3 npm ссылка ссылка на глобальную
$ npm link # or yarn link
Выполнение завершено ✅
Мы можем протестировать его, ввести my-node-cli в командной строке и выполнить его.
$ my-node-cli
Здесь мы видим, что командная строка выводит
my-node-cli working~
Готово ✔, дальше
2. Запросите информацию о пользователе
Функцию реализации и запроса информации о пользователе нужно представить inquirer.js 👉Документация здесь
$ npm install inquirer --dev # yarn add inquirer --dev
Затем мы настраиваем нашу задачу в cli.js
#! /usr/bin/env node
const inquirer = require('inquirer')
inquirer.prompt([
{
type: 'input', //type: input, number, confirm, list, checkbox ...
name: 'name', // key 名
message: 'Your name', // 提示信息
default: 'my-node-cli' // 默认值
}
]).then(answers => {
// 打印互用输入结果
console.log(answers)
})
Введите my-node-cli в командной строке, чтобы увидеть результат выполнения.
Здесь мы получаем имя проекта, введенное пользователем{ name: 'my-app' }
, 👌
3. Создайте соответствующий файл
3.1 Новая папка шаблона
$ mkdir templates # 创建模版文件夹
3.2 Создайте два простых файла-примера, index.html и common.css.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
<!-- ejs 语法 -->
<%= name %>
</title>
</head>
<body>
<h1><%= name %></h1>
</body>
</html>
/* common.css */
body {
margin: 20px auto;
background-color: azure;
}
Структура каталогов на данный момент
my-node-cli
├─ templates
│ ├─ common.css
│ └─ index.html
├─ cli.js
├─ package-lock.json
└─ package.json
3.3 Затем доведите до совершенства логику генерации файлов
Здесь данные, введенные пользователем, отображаются в файле шаблона с помощью механизма шаблонов ejs.
npm install ejs --save # yarn add ejs --save
После доработки переходим на cli.js 👇
#! /usr/bin/env node
const inquirer = require('inquirer')
const path = require('path')
const fs = require('fs')
const ejs = require('ejs')
inquirer.prompt([
{
type: 'input', //type:input,confirm,list,rawlist,checkbox,password...
name: 'name', // key 名
message: 'Your name', // 提示信息
default: 'my-node-cli' // 默认值
}
]).then(answers => {
// 模版文件目录
const destUrl = path.join(__dirname, 'templates');
// 生成文件目录
// process.cwd() 对应控制台所在目录
const cwdUrl = process.cwd();
// 从模版目录中读取文件
fs.readdir(destUrl, (err, files) => {
if (err) throw err;
files.forEach((file) => {
// 使用 ejs 渲染对应的模版文件
// renderFile(模版文件地址,传入渲染数据)
ejs.renderFile(path.join(destUrl, file), answers).then(data => {
// 生成 ejs 处理后的模版文件
fs.writeFileSync(path.join(cwdUrl, file) , data)
})
})
})
})
Аналогично выполните my-node-cli в консоли, в это времяindex.html
,common.css
был успешно создан ✔
Распечатаем текущую структуру каталогов 👇
my-node-cli
├─ templates
│ ├─ common.css
│ └─ index.html
├─ cli.js
├─ common.css .................... 生成对应的 common.css 文件
├─ index.html .................... 生成对应的 index.html 文件
├─ package-lock.json
└─ package.json
Откройте сгенерированный файл index.html и посмотрите
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- ejs 语法 -->
<title>
my-app
</title>
</head>
<body>
<h1>my-app</h1>
</body>
</html>
Пользовательский ввод{ name: 'my-app' }
Уже добавлено в сгенерированный файл ✌️
Нажмите здесь, чтобы открыть 👉адрес источника my-node-cli
2. Популярная библиотека инструментов для строительных лесов 🔧
При построении каркаса в реальном производстве или чтении исходного кода другого каркаса вам необходимо понимать следующие библиотеки инструментов 👇
название | Введение |
---|---|
commander | Пользовательские инструкции командной строки |
inquirer | Командная строка задает пользователю вопросы и записывает ответы |
chalk | Улучшение стиля содержимого вывода консоли |
ora | стиль загрузки консоли |
figlet | консоль печать логотипа |
easy-table | форма вывода консоли |
download-git-repo | Скачать удаленный шаблон |
fs-extra | Расширение системного модуля fs, предоставляет более удобный API и наследует API модуля fs. |
cross-spawn | Поддерживает кроссплатформенный вызов команд в системе |
Сосредоточьтесь на следующем, другие инструменты могут просматривать документацию
1. пользовательская команда командной строки командира
Больше использования 👉китайский документ
Простой случай 👇
1.1 Создайте новый простой проект Node Cli
// package.json
{
"name": "my-vue",
"version": "1.0.0",
"description": "",
"bin": "./bin/cli.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "T-Roc",
"license": "ISC",
"devDependencies": {
"commander": "^7.2.0"
}
}
Структура каталога:
npms-demo
├─ bin
│ └─ cli.js
├─ package-lock.json
└─ package.json
1.3 Ввести командира для написания кода
# 安装依赖
npm install commander # yarn add commander
Улучшить код bin.js
#! /usr/bin/env node
const program = require('commander')
program
.version('0.1.0')
.command('create <name>')
.description('create a new project')
.action(name => {
// 打印命令行输入的值
console.log("project name is " + name)
})
program.parse()
1.3 npm ссылка ссылка на глобальную
- воплощать в жизнь
npm link
будет применятьсяmy-vue
ссылка на глобальную - После этого выполните в командной строке
my-vue
Взгляните на вывод из командной строки 👇
~/Desktop/cli/npms-demo ->my-vue
Usage: my-vue [options] [command]
Options:
-V, --version output the version number
-h, --help display help for command
Commands:
create <name> create a new project
help [command] display help for command
В это время естьmy-vue
Инструкции по использованию команды, команда создания, которую мы только что создали, появляется в разделе «Команды».create <name>
, давайте запустим его в командной строке
~/Desktop/cli/npms-demo ->my-vue create my-app
project name is my-app
В это время консоль выведет следующую команду создания.<name>
ценностьmy-app
👏
2. инструмент для украшения командной строки мелом
мел (мел) можетУкрасить стиль того, что мы выводим в командной строке, например добавление цвета к ключевой информации
2.1 Установить зависимости
npm install chalk # yarn add chalk
2.2 Основное использование
Откройте bin/cli.js в проекте npms-demo.
#! /usr/bin/env node
const program = require('commander')
const chalk = require('chalk')
program
.version('0.1.0')
.command('create <name>')
.description('create a new project')
.action(name => {
// 打印命令行输入的值
// 文本样式
console.log("project name is " + chalk.bold(name))
// 颜色
console.log("project name is " + chalk.cyan(name))
console.log("project name is " + chalk.green(name))
// 背景色
console.log("project name is " + chalk.bgRed(name))
// 使用RGB颜色输出
console.log("project name is " + chalk.rgb(4, 156, 219).underline(name));
console.log("project name is " + chalk.hex('#049CDB').bold(name));
console.log("project name is " + chalk.bgHex('#049CDB').bold(name))
})
program.parse()
Запустите проект из командной строкиmy-vue create my-app
Взгляните на эффект
Таблица сравнения конкретных стилей выглядит следующим образом 👇
3. интерактивный инструмент командной строки inquirer
Больше использования 👉адрес документа
специалист по строительным инструментамЧастота использования очень высокаДа, на самом деле вышеПростой прототип строительных лесов, мы уже использовали его, поэтому я не буду его слишком подробно представлять.
4. Анимация загрузки командной строки ora
Больше использования 👉адрес документа
// 自定义文本信息
const message = 'Loading unicorns'
// 初始化
const spinner = ora(message);
// 开始加载动画
spinner.start();
setTimeout(() => {
// 修改动画样式
// Type: string
// Default: 'cyan'
// Values: 'black' | 'red' | 'green' | 'yellow' | 'blue' | 'magenta' | 'cyan' | 'white' | 'gray'
spinner.color = 'red';
spinner.text = 'Loading rainbows';
setTimeout(() => {
// 加载状态修改
spinner.stop() // 停止
spinner.succeed('Loading succeed'); // 成功 ✔
// spinner.fail(text?); 失败 ✖
// spinner.warn(text?); 提示 ⚠
// spinner.info(text?); 信息 ℹ
}, 2000);
}, 2000);
Вывод командной строки выглядит следующим образом
5. кросс-платформенная оболочка-инструмент
Больше использования 👉адрес документа
Внутри скаффолдинга его можно использовать для автоматизации выполнения команд оболочки, например:
#! /usr/bin/env node
const spawn = require('cross-spawn');
const chalk = require('chalk')
// 定义需要按照的依赖
const dependencies = ['vue', 'vuex', 'vue-router'];
// 执行安装
const child = spawn('npm', ['install', '-D'].concat(dependencies), {
stdio: 'inherit'
});
// 监听执行结果
child.on('close', function(code) {
// 执行失败
if(code !== 0) {
console.log(chalk.red('Error occurred while installing dependencies!'));
process.exit(1);
}
// 执行成功
else {
console.log(chalk.cyan('Install finished'))
}
})
Сделайте то же самое в командной строкеmy-vue
Посмотрите на результат выполнения
Успешно установлено 👍
3. Создайте свои собственные леса 🏗
Давайте сначала назовем наши леса, так же, как Чжурон приземлился на Марсе, почему бы не назвать его:zhurong-cli😆
.-') _ ('-. .-. _ .-') .-') _
( OO) )( OO ) / ( \( -O ) ( OO ) )
,(_)----. ,--. ,--. ,--. ,--. ,------. .-'),-----. ,--./ ,--,' ,----.
| | | | | | | | | | | /`. '( OO' .-. '| \ | |\ ' .-./-')
'--. / | .| | | | | .-') | / | |/ | | | || \| | )| |_( O- )
(_/ / | | | |_|( OO )| |_.' |\_) | |\| || . |/ | | .--, \
/ /___ | .-. | | | | `-' /| . '.' \ | | | || |\ | (| | '. (_/
| || | | |(' '-'(_.-' | |\ \ `' '-' '| | \ | | '--' |
`--------'`--' `--' `-----' `--' '--' `-----' `--' `--' `------'
Какие основные функции необходимо реализовать:
- пройти через
zr create <name>
команда для запуска проекта - Попросите пользователя выбрать шаблон для загрузки
- Удаленное извлечение файлов шаблонов
Демонтаж строительных ступеней:
- Создать проект
- Создать команды запуска скаффолдинга (используя командующий)
- Задавайте вопросы пользователям, чтобы получить информацию, необходимую для создания (используя Inquirer)
- Загрузите удаленные шаблоны (используя download-git-repo)
- Опубликовать проект
1. Создайте проект
Ссылаясь на предыдущий пример, сначала создайте простую структуру Node-Cli.
zhurong-cli
├─ bin
│ └─ cli.js # 启动文件
├─ README.md
└─ package.json
Настройте файл запуска скаффолдинга
{
"name": "zhurong-cli",
"version": "1.0.0",
"description": "simple vue cli",
"main": "index.js",
"bin": {
"zr": "./bin/cli.js" // 配置启动文件路径,zr 为别名
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": {
"name": "T-Roc",
"email": "lxp_work@163.com"
},
"license": "MIT"
}
Просто отредактируйте наш cli.js
#! /usr/bin/env node
console.log('zhurong-cli working ~')
Для облегчения разработки и отладки используйтеnpm link
ссылка на глобальную
~/Desktop/cli/zhurong-cli ->npm link
npm WARN zhurong-cli@1.0.0 No repository field.
up to date in 1.327s
found 0 vulnerabilities
/usr/local/bin/zr -> /usr/local/lib/node_modules/zhurong-cli/bin/cli.js
/usr/local/lib/node_modules/zhurong-cli -> /Users/Desktop/cli/zhurong-cli
После того, как вы закончите, проверьте это
~/Desktop/cli/zhurong-cli ->zr
zhurong-cli working ~ # 打印内容
ОК, мы получили нужный печатный контент, следующий
2. Создайте команду запуска скаффолдинга
Кратко проанализируем, что нам делать?
- Прежде всего, нам нужно полагаться на командира для выполнения этого требования.
- Обратитесь к общим командам vue-cli: create, config и т. д. В последней версии вы можете использовать vue ui для визуального создания.
- Если созданный существует, вам нужно подсказать, перезаписать ли
Начни сейчас 😉
2.1 Установить зависимости
$ npm install commander --save
После установки 👇
2.2 Создать команду
Откройте cli.js для редактирования
#! /usr/bin/env node
const program = require('commander')
program
// 定义命令和参数
.command('create <app-name>')
.description('create a new project')
// -f or --force 为强制创建,如果创建的目录存在则直接覆盖
.option('-f, --force', 'overwrite target directory if it exist')
.action((name, options) => {
// 打印执行结果
console.log('name:',name,'options:',options)
})
program
// 配置版本号信息
.version(`v${require('../package.json').version}`)
.usage('<command> [option]')
// 解析用户执行命令传入参数
program.parse(process.argv);
Введите zr в командной строке, чтобы проверить, успешно ли создана команда.
~/Desktop/cli/zhurong-cli ->zr
Usage: zr <command> [option]
Options:
-V, --version output the version number
-h, --help display help for command
Commands:
create [options] <app-name> create a new project
help [command] display help for command
Мы видим, что в Commands уже естьcreate [options] <app-name>
, затем выполните эту команду
~/Desktop/cli/zhurong-cli ->zr create
error: missing required argument 'app-name'
~/Desktop/cli/zhurong-cli ->zr create my-project
执行结果 >>> name: my-project options: {}
~/Desktop/cli/zhurong-cli ->zr create my-project -f
执行结果 >>> name: my-project options: { force: true }
~/Desktop/cli/zhurong-cli ->zr create my-project --force
执行结果 >>> name: my-project options: { force: true }
Успешно получена информация для ввода командной строки 👍
2.3 Выполнить команду
Создайте папку lib и создайте create.js в папке
// lib/create.js
module.exports = async function (name, options) {
// 验证是否正常取到值
console.log('>>> create.js', name, options)
}
использовать create.js в cli.js
// bin/cli.js
......
program
.command('create <app-name>')
.description('create a new project')
.option('-f, --force', 'overwrite target directory if it exist') // 是否强制创建,当文件夹已经存在
.action((name, options) => {
// 在 create.js 中执行创建任务
require('../lib/create.js')(name, options)
})
......
сделай этоzr create my-project
, в это время информация, которую мы ввели и вышли, нормально печаталась в create.js
~/Desktop/cli/zhurong-cli ->zr create my-project
>>> create.js
my-project {}
При создании каталога необходимо задуматься над вопросом:Каталог уже существует?
- если он существует
- когда
{ force: true }
, напрямую удалите исходный каталог и создайте его напрямую - когда
{ force: false }
При запросе пользователя, требуется ли переопределение
- когда
- Если он не существует, создайте его напрямую
Здесь используется инструмент расширения fs fs-extra, давайте сначала установим его
# fs-extra 是对 fs 模块的扩展,支持 promise
$ npm install fs-extra --save
Давайте улучшим внутреннюю логику реализации create.js
// lib/create.js
const path = require('path')
const fs = require('fs-extra')
module.exports = async function (name, options) {
// 执行创建命令
// 当前命令行选择的目录
const cwd = process.cwd();
// 需要创建的目录地址
const targetAir = path.join(cwd, name)
// 目录是否已经存在?
if (fs.existsSync(targetAir)) {
// 是否为强制创建?
if (options.force) {
await fs.remove(targetAir)
} else {
// TODO:询问用户是否确定要覆盖
}
}
}
Логика части запроса, мы продолжим ее улучшать ниже
2.3 Создайте больше команд
Если вы хотите таким же образом добавить и другие команды, описание здесь не будет развернуто, пример следующий 👇
// bin/cli.js
// 配置 config 命令
program
.command('config [value]')
.description('inspect and modify the config')
.option('-g, --get <path>', 'get value from option')
.option('-s, --set <path> <value>')
.option('-d, --delete <path>', 'delete option from config')
.action((value, options) => {
console.log(value, options)
})
// 配置 ui 命令
program
.command('ui')
.description('start add open roc-cli ui')
.option('-p, --port <port>', 'Port used for the UI Server')
.action((option) => {
console.log(option)
})
2.4 Улучшение справочной информации
Давайте сначала посмотрим на информацию, которую выводит vue-cli при выполнении --help
В сравненииzr --help
В распечатанном результате на одно поясняющее сообщение в конце меньше.Здесь делаем дополнение.На ключевые моменты нужно обратить внимание.Информация описания окрашена, здесь нам нужно использовать инструменты из нашей библиотеки инструментовchalkиметь дело с
// bin/cli.js
program
// 监听 --help 执行
.on('--help', () => {
// 新增说明信息
console.log(`\r\nRun ${chalk.cyan(`zr <command> --help`)} for detailed usage of given command\r\n`)
})
2.5 Печать логотипа
Если мы хотим создать весь логотип в это время, библиотека инструментовfigletПросто сделай это 😎
// bin/cli.js
program
.on('--help', () => {
// 使用 figlet 绘制 Logo
console.log('\r\n' + figlet.textSync('zhurong', {
font: 'Ghost',
horizontalLayout: 'default',
verticalLayout: 'default',
width: 80,
whitespaceBreak: true
}));
// 新增说明信息
console.log(`\r\nRun ${chalk.cyan(`roc <command> --help`)} show details\r\n`)
})
Посмотрим на это времяzr --help
Как выглядит печать
Он все еще выглядит довольно хорошо, ха-ха 😄
3. Задавайте вопросы пользователям, чтобы получить информацию, необходимую для создания
Здесь, чтобы позвонить нашим старым друзьям inquirer, пусть поможет нам решить проблему взаимодействия с командной строкой
Что мы будем делать дальше:
- Остаток от предыдущего шага: спросите пользователя, перезаписывать ли существующий каталог
- Пользователь выбирает шаблон
- Версия, выбранная пользователем
- Получить ссылку для скачивания шаблона
3.1 Спросите, перезаписать ли существующий каталог
Вот проблемы, оставшиеся от предыдущего шага:
- Если каталог уже существует
- когда
{ force: false }
При запросе пользователя, требуется ли переопределение
- когда
Логика фактически завершена, вот содержание запроса
предпочтительно установитьinquirer
$ npm install inquirer --save
Затем спросите пользователя, перезаписывать ли
// lib/create.js
const path = require('path')
// fs-extra 是对 fs 模块的扩展,支持 promise 语法
const fs = require('fs-extra')
const inquirer = require('inquirer')
module.exports = async function (name, options) {
// 执行创建命令
// 当前命令行选择的目录
const cwd = process.cwd();
// 需要创建的目录地址
const targetAir = path.join(cwd, name)
// 目录是否已经存在?
if (fs.existsSync(targetAir)) {
// 是否为强制创建?
if (options.force) {
await fs.remove(targetAir)
} else {
// 询问用户是否确定要覆盖
let { action } = await inquirer.prompt([
{
name: 'action',
type: 'list',
message: 'Target directory already exists Pick an action:',
choices: [
{
name: 'Overwrite',
value: 'overwrite'
},{
name: 'Cancel',
value: false
}
]
}
])
if (!action) {
return;
} else if (action === 'overwrite') {
// 移除已存在的目录
console.log(`\r\nRemoving...`)
await fs.remove(targetAir)
}
}
}
}
Давайте проверим это:
- Вручную создайте два каталога в текущем каталоге, то есть каталоге, отображаемом в командной строке, здесь они называются my-project и my-project2.
- воплощать в жизнь
zr create my-project
, эффект следующий
- воплощать в жизнь
zr create my-project2 --f
, сразу видно, что my-project2 удален
⚠️Примечание. Почему удалить только здесь?Потому что после того, как адрес шаблона будет получен позже, каталог проекта будет создан непосредственно при загрузке
3.2 Как получить информацию о шаблоне
Шаблон, который я загрузил в удаленный репозиторий:github.com/zhurong-cli
vue3.0-информация о версии шаблона 👇
информация о версии vue-template 👇
github предоставляет
- API.GitHub.com/org is/pork oh you…Интерфейс для получения информации о шаблоне
- API.GitHub.com/repos/pork-oh…Интерфейс для получения информации о версии
Мы создаем http.js в каталоге lib, чтобы иметь дело с получением информации о шаблоне и версии.
// lib/http.js
// 通过 axios 处理请求
const axios = require('axios')
axios.interceptors.response.use(res => {
return res.data;
})
/**
* 获取模板列表
* @returns Promise
*/
async function getRepoList() {
return axios.get('https://api.github.com/orgs/zhurong-cli/repos')
}
/**
* 获取版本信息
* @param {string} repo 模板名称
* @returns Promise
*/
async function getTagList(repo) {
return axios.get(`https://api.github.com/repos/zhurong-cli/${repo}/tags`)
}
module.exports = {
getRepoList,
getTagList
}
3.3 Шаблон выбора пользователя
Мы специально создаем новый Generator.js для обработки логики создания проекта.
// lib/Generator.js
class Generator {
constructor (name, targetDir){
// 目录名称
this.name = name;
// 创建位置
this.targetDir = targetDir;
}
// 核心创建逻辑
create(){
}
}
module.exports = Generator;
Представьте класс Generator в create.js.
// lib/create.js
...
const Generator = require('./Generator')
module.exports = async function (name, options) {
// 执行创建命令
// 当前命令行选择的目录
const cwd = process.cwd();
// 需要创建的目录地址
const targetAir = path.join(cwd, name)
// 目录是否已经存在?
if (fs.existsSync(targetAir)) {
...
}
// 创建项目
const generator = new Generator(name, targetAir);
// 开始创建项目
generator.create()
}
Затем напишите логику, чтобы попросить пользователя выбрать шаблон
// lib/Generator.js
const { getRepoList } = require('./http')
const ora = require('ora')
const inquirer = require('inquirer')
// 添加加载动画
async function wrapLoading(fn, message, ...args) {
// 使用 ora 初始化,传入提示信息 message
const spinner = ora(message);
// 开始加载动画
spinner.start();
try {
// 执行传入方法 fn
const result = await fn(...args);
// 状态为修改为成功
spinner.succeed();
return result;
} catch (error) {
// 状态为修改为失败
spinner.fail('Request failed, refetch ...')
}
}
class Generator {
constructor (name, targetDir){
// 目录名称
this.name = name;
// 创建位置
this.targetDir = targetDir;
}
// 获取用户选择的模板
// 1)从远程拉取模板数据
// 2)用户选择自己新下载的模板名称
// 3)return 用户选择的名称
async getRepo() {
// 1)从远程拉取模板数据
const repoList = await wrapLoading(getRepoList, 'waiting fetch template');
if (!repoList) return;
// 过滤我们需要的模板名称
const repos = repoList.map(item => item.name);
// 2)用户选择自己新下载的模板名称
const { repo } = await inquirer.prompt({
name: 'repo',
type: 'list',
choices: repos,
message: 'Please choose a template to create project'
})
// 3)return 用户选择的名称
return repo;
}
// 核心创建逻辑
// 1)获取模板名称
// 2)获取 tag 名称
// 3)下载模板到模板目录
async create(){
// 1)获取模板名称
const repo = await this.getRepo()
console.log('用户选择了,repo=' + repo)
}
}
module.exports = Generator;
Проверьте это и посмотрите, как это выглядит сейчас
Я выбрал шаблон vue по умолчанию, на данный момент
Успешно получить результат репозитория имени шаблона ✌️
3.4 Пользователь выбирает версию
Процесс тот же, что и в 3.3.
// lib/generator.js
const { getRepoList, getTagList } = require('./http')
...
// 添加加载动画
async function wrapLoading(fn, message, ...args) {
...
}
class Generator {
constructor (name, targetDir){
// 目录名称
this.name = name;
// 创建位置
this.targetDir = targetDir;
}
// 获取用户选择的模板
// 1)从远程拉取模板数据
// 2)用户选择自己新下载的模板名称
// 3)return 用户选择的名称
async getRepo() {
...
}
// 获取用户选择的版本
// 1)基于 repo 结果,远程拉取对应的 tag 列表
// 2)用户选择自己需要下载的 tag
// 3)return 用户选择的 tag
async getTag(repo) {
// 1)基于 repo 结果,远程拉取对应的 tag 列表
const tags = await wrapLoading(getTagList, 'waiting fetch tag', repo);
if (!tags) return;
// 过滤我们需要的 tag 名称
const tagsList = tags.map(item => item.name);
// 2)用户选择自己需要下载的 tag
const { tag } = await inquirer.prompt({
name: 'tag',
type: 'list',
choices: tagsList,
message: 'Place choose a tag to create project'
})
// 3)return 用户选择的 tag
return tag
}
// 核心创建逻辑
// 1)获取模板名称
// 2)获取 tag 名称
// 3)下载模板到模板目录
async create(){
// 1)获取模板名称
const repo = await this.getRepo()
// 2) 获取 tag 名称
const tag = await this.getTag(repo)
console.log('用户选择了,repo=' + repo + ',tag='+ tag)
}
}
module.exports = Generator;
проверить, выполнитьzr create my-project
После выбора посмотрите на результат печати
На этом работа по заданию окончена, и шаблон можно скачать.
4. Загрузите удаленный шаблон
Скачать удаленные шаблоны нужно использоватьdownload-git-repoИнструментарий, по сути, он тоже есть в меню инструментов, которое мы перечислили выше, но при его использовании нужно обратить внимание на проблему, т.е.обещания не поддерживаются, поэтому здесь нам нужно использовать модуль utilpromisifyспособ пообещать это
4.1 Установка зависимостей и обещание
$ npm install download-git-repo --save
обещать
// lib/Generator.js
...
const util = require('util')
const downloadGitRepo = require('download-git-repo') // 不支持 Promise
class Generator {
constructor (name, targetDir){
...
// 对 download-git-repo 进行 promise 化改造
this.downloadGitRepo = util.promisify(downloadGitRepo);
}
...
}
4.2 Основная функция загрузки
Далее логика загрузки шаблона
// lib/Generator.js
...
const util = require('util')
const path = require('path')
const downloadGitRepo = require('download-git-repo') // 不支持 Promise
// 添加加载动画
async function wrapLoading(fn, message, ...args) {
...
}
class Generator {
constructor (name, targetDir){
...
// 对 download-git-repo 进行 promise 化改造
this.downloadGitRepo = util.promisify(downloadGitRepo);
}
...
// 下载远程模板
// 1)拼接下载地址
// 2)调用下载方法
async download(repo, tag){
// 1)拼接下载地址
const requestUrl = `zhurong-cli/${repo}${tag?'#'+tag:''}`;
// 2)调用下载方法
await wrapLoading(
this.downloadGitRepo, // 远程下载方法
'waiting download template', // 加载提示信息
requestUrl, // 参数1: 下载地址
path.resolve(process.cwd(), this.targetDir)) // 参数2: 创建位置
}
// 核心创建逻辑
// 1)获取模板名称
// 2)获取 tag 名称
// 3)下载模板到模板目录
// 4)模板使用提示
async create(){
// 1)获取模板名称
const repo = await this.getRepo()
// 2) 获取 tag 名称
const tag = await this.getTag(repo)
// 3)下载模板到模板目录
await this.download(repo, tag)
// 4)模板使用提示
console.log(`\r\nSuccessfully created project ${chalk.cyan(this.name)}`)
console.log(`\r\n cd ${chalk.cyan(this.name)}`)
console.log(' npm run dev\r\n')
}
}
module.exports = Generator;
Завершите эту часть, простые леса сделаны ✅
Посмотрим, как это работает, выполнимzr create my-project
На данный момент мы видим, что шаблон создан 👏👏👏
zhurong-cli
├─ bin
│ └─ cli.js
├─ lib
│ ├─ Generator.js
│ ├─ create.js
│ └─ http.js
├─ my-project .............. 我们创建的项目
│ ├─ public
│ │ ├─ favicon.ico
│ │ └─ index.html
│ ├─ src
│ │ ├─ assets
│ │ │ └─ logo.png
│ │ ├─ components
│ │ │ └─ HelloWorld.vue
│ │ ├─ App.vue
│ │ └─ main.js
│ ├─ README.md
│ ├─ babel.config.js
│ └─ package.json
├─ README.md
├─ package-lock.json
└─ package.json
5. Опубликовать проект
Все вышеперечисленное протестировано локально.В реальном использовании вам может потребоваться публикация в репозиторий npm.После глобальной установки через npm перейдите непосредственно в целевой каталог, чтобы создать проект.Как его опубликовать?
- Первый шаг — собрать репозиторий на git
- Второй шаг — улучшить конфигурацию в package.json.
{
"name": "zhurong-cli",
"version": "1.0.4",
"description": "",
"main": "index.js",
"bin": {
"zr": "./bin/cli.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"files": [
"bin",
"lib"
],
"author": {
"name": "T-Roc",
"email": "lxp_work@163.com"
},
"keywords": [
"zhurong-cli",
"zr",
"脚手架"
],
"license": "MIT",
"dependencies": {
"axios": "^0.21.1",
"chalk": "^4.1.1",
"commander": "^7.2.0",
"download-git-repo": "^3.0.2",
"figlet": "^1.5.0",
"fs-extra": "^10.0.0",
"inquirer": "^8.0.0",
"ora": "^5.4.0"
}
}
- Третий шаг, используйте
npm publish
Публикуйте, обновляйте, когда придет время, обратите внимание на изменение номера версии
Таким образом, релиз прошел успешно, давайте откроем сайт npm и поищем 🔍
Мы уже можем найти его, чтобы мы могли установить его глобально через npm или yarn.
Нажмите здесь, чтобы открыть 👉исходный адрес zhurong-cli
4. Yeoman: универсальная система строительных лесов
Первоначально выпущенный в 2012 году, Yeoman представляет собой эффективное программное обеспечение для создания шаблонов веб-приложений с открытым исходным кодом, предназначенное для оптимизации процесса разработки программного обеспечения. Программное обеспечение скаффолдинга используется для реализации совместного использования множества различных инструментов и интерфейсов в проекте, а также для оптимизации процесса создания проекта. Позволяет создавать приложения любого типа (Web, Java, Python, C# и т. д.).
Йомен на самом делетри инструментаСумма:
- йо --- строительные леса, инструменты автоматической генерации
- grunt, gulp --- инструменты сборки
- Bower, npm --- инструменты управления пакетами
Строить строительные леса с помощью Yeoman очень просто.yeoman-generator
Давайте быстро сгенерируем шаблон строительных лесов, мы можем построить любой тип проекта через различные генераторы, давайте попробуем 🤓
1. Основное использование Yeoman
Yeoman — это система сборки, здесь нам нужно использовать йо 👇 для постройки строительных лесов.
1.1 Установите йо глобально
$ npm install yo --global # or yarn global add yo
1.2 Установите соответствующий генератор
ты другойgenerator-xxx
Могут быть созданы соответствующие проекты, такие какgenerator-webapp
,generator-node
,generator-vue
подождите, здесь мы используемgenerator-node
для демонстрации операции.
$ npm install generator-node --global # or yarn global add generator-node
1.3 Запуск генератора с йо
$ mkdir yo-project
$ cd yo-project
$ yo node
так что мы проходимyo + generator-node
Быстро создайте проект узла, структура каталогов выглядит следующим образом 👇
yo-project
├─ .editorconfig
├─ .eslintignore
├─ .travis.yml
├─ .yo-rc.json
├─ LICENSE
├─ README.md
├─ lib
│ ├─ __tests__
│ │ └─ testCli.test.js
│ └─ index.js
├─ package-lock.json
└─ package.json
Как найти нужный генератор? Мы можем перейти к списку генераторов официального сайта для поиска 👉Нажмите здесь, чтобы войти
Этот способ использования действительно очень прост и удобен, но его недостатки также очевидны--недостаточно гибкий, ведь разные команды используют разные стеки технологий,Что, если мы хотим построить структуру проекта, которую хотим?Тогда смотри вниз 👇
2. Пользовательский генератор
Пользовательский генераторПо сути, это создание определенной структуры пакета npm., эта конкретная структура такая 👇
generator-xxx ............ 自定义项目目录
├─ generators ............ 生成器目录
│ └─ app ................ 默认生成器目录
│ └─ index.js ........ 默认生成器实现
└─ package.json .......... 模块包配置文件
или это 👇
generator-xxx
├─ app
│ └─ index.js
├─ router
│ └─ index.js
└─ package.json
Здесь нужно отметить, что имя проекта должно бытьgenerator-<name>
формате, он может быть распознан вами нормально, например, генератор-узел, использованный в приведенном выше примере.
2.1 Создать проект
$ mkdir generator-simple # 创建项目
$ cd generator-simple # 进入项目目录
2.2 Инициализация нпм
$ npm init # or yarn init
После всего пути входа у нас сгенерировался package.json, но нужно дополнительно проверить:
-
name
Значение свойства должно быть "генератор-" -
keyword
yoman-generator должен быть включен в -
files
Свойство должно указывать на каталог шаблонов проекта.
После завершения вышеуказанной работы давайте посмотрим, как выглядит package.json.
{
"name": "generator-simple",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"yeoman-generator"
],
"files": [
"generators"
],
"author": "ITEM",
"license": "MIT"
}
⚠️Примечание: если здесь используется вторая структура каталогов, то необходимо внести некоторые изменения в package.json 🔧
{
"files": [
"app",
"router"
]
}
2.3 Установкаyeoman-generator
yeoman-generator
Это базовый класс Generator, предоставленный Yeoman, что делает более удобным создание пользовательских генераторов.
$ npm install yeoman-generator --save # or yarn add yeoman-generator
2.4 Инструкции по использованию базового класса Generator
Прежде чем представить базовый класс Generator, давайте реализуем простой 🌰
Сначала откройте файл основной записи и отредактируйте содержимое следующим образом 👇
// ~/generators/app/index.js
// 此文件作为 Generator 的核心入口
// 需要导出一个继承自 Yeoman Generator 的类型
// Yeoman Generator 在工作时会自动调用我们在此类型中定义的一些生命周期方法
// 我们在这些方法中可以通过调用父类提供的一些工具方法实现一些功能,例如文件写入
const Generator = require('yeoman-generator');
module.exports = class extends Generator {
// add your own methods
method1() {
console.log('I am a custom method');
}
method2() {
console.log('I am a custom method2');
}
};
После этого проходимnpm link
способ связать проект с глобальным
$ npm link # or yarn link
Таким образом, мы можем получить глобальный доступ к проекту-генератору, давайте попробуем.
$ yo simple
Взгляните на вывод консоли
I am a custom method1
I am a custom method2
Хорошо, это тот результат, который нам нужен 😎
⚠️Уведомление, если работаетyo simple
Возникает следующая ошибка
This generator (simple:app)
requires yeoman-environment at least 3.0.0, current version is 2.10.3,
try reinstalling latest version of 'yo' or use '--ignore-version-check' option
Это можно обработать следующим образом:
Вариант первый
# 卸载当前版本
npm uninstall yeoman-generator
# 安装低版本的包
npm i yeoman-generator@4.13.0
# 执行
yo simple
Вариант 2
# 全局安装模块
npm i -g yeoman-environment
# 新的执行方式(yoe没有打错)
yoe run simple
Из небольшого 🌰 выше мы видим, что наш пользовательский метод автоматически выполняется последовательно, а базовый класс Generator также предоставляет некоторые последовательно выполняемые методы, похожие на жизненный цикл, давайте посмотрим, что 👇
initializing
-- методы инициализации (проверить статус, получить конфигурацию и т.д.)
prompting
-- Получить данные о взаимодействии с пользователем (this.prompt())
configuring
-- Отредактируйте и настройте файл конфигурации проекта
default
-- Если внутри Генератора есть метод, который не соответствует ни одному имени задачи очереди задач, он будет запущен под задачей по умолчанию.
writing
-- заполнить предустановленный шаблон
conflicts
-- обрабатывать конфликты (только для внутреннего использования)
install
-- Установить зависимости (например: npm, Bower)
end
-- последний звонок, сделайте чистую работу
2.5 Запустите наш собственный генератор
Мы используем метод, предоставленный Generator, давайте преобразуем входной файл
// ~/generators/app/index.js
const Generator = require('yeoman-generator');
module.exports = class extends Generator {
// yo 会自动调用该方法
writing () {
// 我们使用 Generator 提供的 fs 模块尝试往目录中写入文件
this.fs.write(
// destinationPath() 基于项目地址
this.destinationPath('temp.txt'), // 写入地址
Math.random().toString() // 写入内容
)
}
};
запустить его
$ yo simple
В это время вывод консолиcreate temp.txt
, давайте распечатаем структуру каталогов
generator-simple
├─ generators
│ └─ app
│ └─ index.js
├─ package-lock.json
├─ package.json
└─ temp.txt .............. writing 中创建的文件
Откройте только что созданный temp.txt и посмотрите
0.8115477932475306
Вы можете видеть, что в файл записывается строка случайных чисел.
В реальном использовании нам нужно создать несколько файлов с помощью шаблонов.В настоящее время нам нужно разобраться с этим 👇
во-первых, создайте каталог файлов шаблона./generators/app/templates/
, и добавьте файл шаблона в папкуtemp.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- yo 支持 ejs 语法 -->
<title><%= title %></title>
</head>
<body>
<% if (success) { %>
<h1>这里是模版文件<%= title %></h1>
<% } %>
</body>
</html>
потом, измените файл записи 👇
// ~/generators/app/index.js
const Generator = require('yeoman-generator');
module.exports = class extends Generator {
// yo 会自动调用该方法
writing () {
// 我们使用 Generator 提供的 fs 模块尝试往目录中写入文件
// this.fs.write(
// this.destinationPath('temp.txt'),
// Math.random().toString()
// )
// 模版文件路径,默认指向 templates
const tempPath = this.templatePath('temp.html')
// 输出目标路径
const output = this.destinationPath('index.html')
// 模板数据上下文
const context = { title: 'Hello ITEM ~', success: true}
this.fs.copyTpl(tempPath, output, context)
}
};
после завершения,yo simple
Запускаем его, так мы попадаем в корневую директориюindex.html
, открой и посмотри 🤓
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 支持 ejs 语法 -->
<title>Hello ITEM ~</title>
</head>
<body>
<h1>这里是模版文件Hello ITEM ~</h1>
</body>
</html>
Переменные, написанные ejs, успешно заменены данными ✌️
следующий, как мы можем получить определенные пользователем данные через взаимодействие с командной строкой, такие как: имя проекта, номер версии и т. д.
Это требует использования Promting, предоставляемого Generator, для обработки некоторых взаимодействий в командной строке.
// ~/generators/app/index.js
const Generator = require('yeoman-generator');
module.exports = class extends Generator {
// 在此方法中可以调用父类的 prompt() 方法与用户进行命令行询问
prompting(){
return this.prompt([
{
type: 'input', // 交互类型
name: 'name',
message: 'Your project name', // 询问信息
default: this.appname // 项目目录名称,这里是 generator-simple
}
])
.then(answers => {
console.log(answers) // 打印输入内容
this.answers = answers // 存入结果,可以在后面使用
})
}
// yo 会自动调用该方法
writing () {
......
}
};
После сохранения запуститеyo simple
Мы видим, что командная строка спрашиваетYour Project name ?
, после завершения пользовательского ввода мы получаем ответы, чтобы использовать результат в следующем процессе.
// ~/generators/app/index.js
...
// 模板数据上下文
writing () {
...
// 模板数据上下文
const context = { title: this.answers.name, success: true}
this.fs.copyTpl(tempPath, output, context)
}
...
запустить его сноваyo simple
, см. вывод index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 支持 ejs 语法 -->
<title>my-project</title>
</head>
<body>
<h1>这里是模版文件my-project</h1>
</body>
</html>
Мы можем видеть, что пользователь ввел{ name: 'my-project' }
Уже отображается в нашем index.html 👌
Нажмите здесь, чтобы открыть 👉генератор-простой исходный адрес
Вот и все, йомен, давайте взглянем на еще один инструмент для строительных лесов — шлеп 👇
Пять, шлеп: маленький и красивый инструмент для строительных лесов
Плоп небольшой по размеру и легкий по размеру, а красота в его простоте и удобстве использования
Больше способов использования 👉документация по использованию plop
Мы можем напрямую интегрировать его в проект, чтобы решить повторяющуюся и стандартизированную работу по созданию.Давайте сделаем небольшой случай, например
Мы согласовали спецификацию создания компонента:
- Используйте большой верблюжий регистр для имен компонентов
- Стиль надо выкручивать отдельно писать
- Требуемая документация
Процесс использования plop можно грубо разобрать на:
- Установите plop, добавьте новый файл конфигурации plopfile.js
- Отредактируйте файл конфигурации plop
- Создать файл шаблона
- Выполнить задание на создание
Теперь введите часть кода
1. Установите шлеп
Сначала инициализируйте проект vue с помощью нашего zhurong-cli.
# 全局安装
$ npm install zhurong-cli -g
# 创建 vue 项目
$ zr create plop-demo
Мы здесь для унифицированного использования команды, а plop напрямую интегрирован в проект.
$ npm install plop --save-dev
Создайте файл конфигурации plop plopfile.js в каталоге проекта.
2. Отредактируйте файл конфигурации plop
// ./plopfile.js
module.exports = plop => {
plop.setGenerator('component', {
// 描述
description: 'create a component',
// 询问组件的名称
prompts: [
{
type: 'input',
name: 'name',
message: 'Your component name',
default: 'MyComponent'
}
],
// 获取到回答内容后续的动作
actions: [
//每一个对象都是一个动作
{
type: 'add', // 代表添加文件
// 被创建文件的路径及名称
// name 为用户输入的结果,使用 {{}} 使用变量
// properCase: plop 自带方法,将 name 转换为大驼峰
path: 'src/components/{{ properCase name }}/index.vue',
// 模板文件地址
templateFile: 'plop-templates/component.vue.hbs'
},
{
type: 'add',
path: 'src/components/{{ properCase name }}/index.scss',
templateFile: 'plop-templates/component.scss.hbs'
},
{
type: 'add',
path: 'src/components/{{ properCase name }}/README.md',
templateFile: 'plop-templates/README.md.hbs'
}
]
})
}
Метод propCase используется выше для преобразования имени в большой горб, а другие форматы включают 👇
-
camelCase
: changeFormatToThis -
snakeCase
: change_format_to_this -
dashCase/kebabCase
: change-format-to-this -
dotCase
: change.format.to.this -
pathCase
: change/format/to/this -
properCase/pascalCase
: ChangeFormatToThis -
lowerCase
: change format to this -
sentenceCase
: Change format to this, -
constantCase
: CHANGE_FORMAT_TO_THIS -
titleCase
: Change Format To This
Мы видим, что файл шаблона был указан выше, но мы его еще не создали, поэтому давайте создадим его
3. Создайте файл шаблона
Создайте папку plop-templates в папке проекта и создайте в ней соответствующий файл шаблона.
plop-templates
├─ README.md.hbs ............... 说明文档模板
├─ component.scss.hbs .......... 组件样式模板
└─ component.vue.hbs ........... 组件模板
Мы используем механизм шаблонов Handlebars, более подробное описание синтаксиса 👉Руль Китайский
Отредактируйте component.scss.hbs
{{!-- ./plop-templates/component.scss.hbs --}}
{{!-- dashCase/kebabCase: change-format-to-this --}}
{{!-- name: 输入模板名称 --}}
.{{ dashCase name }} {
}
Отредактируйте component.vue.hbs
{{!-- ./plop-templates/component.vue.hbs --}}
<template>
<div class="{{ dashCase name }}">{{ name }}</div>
</template>
<script>
export default {
name: '{{ properCase name }}',
}
</script>
<style lang="scss">
@import "./index.scss";
</style>
Изменить README.md.hbs
{{!-- ./plop-templates/README.md.hbs --}}
这里是组件 {{ name }} 的使用说明
Дополнительные инструкции:
- Шаблоны здесь наиболее просты в реализации, в реальном производстве содержимое шаблона может быть дополнено в соответствии с требованиями.
- Тире и правильный регистр в шаблоне — это правила отображения для команды изменения имени, которые были перечислены выше.
-
dashCase
: становится горизонтальной ссылкой aa-bb-cc -
properCase
: становится большим горбом AaBbCc ...
-
- Используя переменные в Handlebars, используйте
{{}}
пакет
4. Выполните задачу создания
Откройте package.json
// scripts 中 增加一条命令
...
"scripts": {
...
"plop": "plop"
},
...
В этот момент мы можем использоватьnpm run plop
для создания компонента
Скоро компонент будет создан ✅
Взгляните на папку компонентов на этом этапе
components
├─ MyApp
│ ├─ README.md
│ ├─ index.scss
│ └─ index.vue
└─ HelloWorld.vue
Компоненты MyApp созданы, давайте откроем файлы внутри и посмотрим
Откройте MyApp/index.vue
<template>
<div class="my-app">my-app</div>
</template>
<script>
export default {
name: 'MyApp',
}
</script>
<style lang="scss">
@import "./index.scss";
</style>
Откройте MyApp/index.scss
.my-app {
}
Откройте MyApp/README.md
这里是组件 my-app 的使用说明
Нажмите здесь, чтобы открыть 👉адрес исходного кода plop-demo
6. Пишите в конце
Я не знаю, дочитали ли вы эту статью, не потеряли ли вы учебу 😂
Эта статья была организована в течение длительного времени, я надеюсь, что она будет полезна для всех в изучении 😁
Кроме того, я надеюсь, что вы можетеНравится Комментарий ПодписатьсяПоддержите, ваша поддержка - движущая сила письма 😘
Предварительный просмотр, следующий принесет 👉Упаковка свода знаний, связанных с инструментами сборки
Справочная статья:
GitHub.com/code маленький П…
process.v ue js.org/this/expensive/adult…
yoman.IO/authoring/я…
woohoo.brief.com/afraid/93211004От…