В этой статье нет ничего сложного, и вы шаг за шагом научит вас создавать интерфейс командной строки.
1. Предпосылки:
За последние полгода из-за потребностей работы и бизнеса я написал два строительных леса один за другим, и они были применены в бизнесе фронтенд-группы, так что я также был утвержден супервайзером.
Леса обычно используются в качествеНеобходимые навыки для среднего и продвинутого фронтенда. Статья длинная, рекомендуется собирать~
эта статьячистые галантереи, с помощью этой статьи вы узнаете, как разработатьбольшой и роскошныйстроительные леса
Во-вторых, что такое строительные леса и какие проблемы они могут решить:
Строительные леса также часто называютCLI, полное имяcommand-line interface, который переводится в интерфейс командной строки. Frond-End Scaffolting CLI, который является инструментом командной строки
В повседневной разработке мы часто сталкиваемся с этими тремя типами строительных лесов.
-
Общие леса с открытым исходным кодом
Такие как
vue-cli,create-react-app, с ним все знакомы, обе эти подмости обеспечиваютстроить-выполнять-строитьМощность проекта относится к «большим лесам», мы используем их каждый день.npmТакже эшафот -
Строительные леса внутри предприятия
На общих предприятиях необходимо настроить набор строительных лесов, соответствующий бизнесу внутри группы, и связанный с некоторыми базовыми услугами компании, связь между апстримом и даунстримом, набор встроенных инструментов и т.д. , как правило, на основеОбщие леса с открытым исходным кодомВторичная упаковка
-
другие строительные леса
Существует также большая категория: для определенной болевой точки бизнеса ручное решение является более сложным, дорогостоящим и неэффективным, и оно разработано как автоматическая реализация строительных лесов.
Из приведенных выше примеров нетрудно обнаружить, что строительные леса могут решить следующие проблемы:
- Сократите повторяющуюся работу
- Команда унифицирует стиль разработки, стандартизирует структуру каталогов разработки проекта, облегчает межкомандное сотрудничество и последующее обслуживание, а также снижает начальные затраты для новичков.
- Обеспечьте создание внешнего интерфейса одним щелчком мыши, настройку, локальную разработку, расширение подключаемых модулей и другие функции, что позволит разработчикам уделять больше времени бизнесу.
3. Как построить строительные леса
3.1 Функция строительных лесов
Прежде всего, прежде чем начать, вам нужно сначала спроектировать леса и взять тот, который успешно подал заявку на патент.npm-package-privatifyНапример, (на конкретную проблему, решаемую этими лесами, не нужно обращать внимание, это просто удобный пример~)
-
Первый шаг - дать вашим лесам имя, например, мое здесь.
privatify -
Второй шаг, список команд, предоставляемых этим эшафотом, например, следуйте
vue create [options] <app-name>Также укажите команду для инициализации проекта, например, с помощьюvue add [options] <plugin> [pluginOptions]Предоставляет рабочую команду, такую как добавление плагина...Конечно есть основные
--versionпросмотр версии,--helpПредоставьте список команд, ниже приведен список команд, разработанных privatify
3.2 Зависимые базовые пакеты
Говорят, что, стоя на плечах гигантов, хороший пакет может заставить вас делать больше с меньшими затратами.Чтобы построить базовые леса, вам нужно полагаться на следующие пакеты.
| Имена пакетов | использовать |
|---|---|
| commander | Инструмент командной строки, который читает команды командной строки и знает, что пользователь хочет сделать |
| inquirer | Интерактивный командный инструмент, который предоставляет пользователям возможность задавать вопросы |
| chalk | Цветной плагин, используемый для изменения стиля вывода командной строки, различения информации и журналов ошибок по цвету, понятный и интуитивно понятный. |
| ora | Он используется для отображения эффекта загрузки, аналогичного эффекту загрузки страницы внешнего интерфейса.Если вы хотите загрузить шаблон, что является трудоемкой операцией, с эффектом загрузки, вы можете напомнить пользователю, что это в процессе, пожалуйста, подождите терпеливо. |
| globby | для получения файлов |
| fs-extra | Расширенная версия модуля файловой системы node fs. |
| pacote | Получить последнюю версию пакета узла и другую информацию |
| handlebars | Предоставляет необходимую функциональность, позволяющую эффективно создавать семантические шаблоны. |
3.3 Реализация скаффолдингового кода
я используюTypeScript+NodeСтроение, адрес склада: г.GitHub.com/ в следующем месяце 25/ обрабатывает…Рекомендуется клонировать
(1) Структура каталогов
cli-template
├─ .gitignore
├─ README.md
├─ build // 打包后文件夹
├─ project-template // 初始化项目模版
├─ bin.js // 生产环境执行文件入口,具体见下
├─ bin-local.js // 本底调试执行文件入口,具体见下
├─ package.json // 配置文件,具体见下
├─ src
│ ├─ commands // 命令文件夹
│ │ ├─ create.ts // create命令
│ │ ├─ scope.ts // scope命令
│ │ ├─ package.ts // package命令
│ │ └─ utils // 公共函数
│ ├─ index.ts // 入口文件
│ └─ lib // 公共第三方包
│ ├─ consts.ts // 常量
│ ├─ index.ts
│ ├─ logger.ts // 控制台颜色输出
│ └─ spinner.ts // 控制台loading
├─ tsconfig.json // TypeScript配置文件
└─ tslint.json // tslint配置文件
(2) package.json
-
1、npm initИнициализировать package.json -
2、npm i typescript ts-node tslint rimraf -DУстановить зависимости разработки -
3、npm i typescript chalk commander execa fs-extra globby handlebars inquirer ora pacoteУстановить производственные зависимости -
scripts配置clear、build、publish、lint命令, будет упомянуто конкретное использование окончательного пакета
Завершенныйpackage.jsonФайл конфигурации выглядит следующим образом
{
"name": "cli-template",
"version": "0.0.2",
"description": "cli模版仓库",
"main": "./build",
"scripts": {
"clear": "rimraf build",
"build": "npm run clear && tsc",
"publish": "npm run build && npm publish",
"lint": "tslint ./src/**/*.ts --fix"
},
"repository": {
"type": "git",
"url": "https://github.com/zxyue25/cli-template.git"
},
"bin": {
"privatify": "./bin.js",
"privatify-local": "./bin-local.js"
},
"files": [
"build",
"bin.js"
],
"keywords": [
"cli",
"node",
"typescript",
"command"
],
"author": "zxyue25",
"license": "ISC",
"devDependencies": {
"rimraf": "^3.0.2",
"ts-node": "^10.3.0",
"tslint": "^6.1.3",
"typescript": "^4.4.4"
},
"dependencies": {
"chalk": "^4.1.2",
"commander": "^8.2.0",
"execa": "^5.1.1",
"fs-extra": "^10.0.0",
"globby": "^11.0.4",
"inquirer": "^8.2.0",
"leven": "^4.0.0",
"ora": "^5.4.1",
"pacote": "^12.0.2"
}
}
Фокус требует вниманияbin字段а такжеfiles字段
-
bin字段см. ниже (2) -
files字段That npm white list, as shown below, npm official explanation, that is to say after the contract which files need to include, if the default configuration does not release all the documents, which naturally is not good, you want others to see your source предмет?所以这里我们配置了"files": [ "build", "bin.js" ], включая build и bin.js, папки src нет в белом списке
(3) файл конфигурации tsconfig.json
Если каталог существуетtsconfig.jsonфайл, то это означает, что этот каталог является корневым каталогом проекта TypeScript. tsconfig.jsonКорневой файл и параметры компиляции, используемые для компиляции этого проекта, указаны в файле, который можно настроить следующим образом.
Те, кто не знаком с машинописным текстом, могут его сначала пропустить, понятно, что мы используем машинописный текст для записи, и нам нужно настроить файл конфигурации.
// tsconfig.json
{
"compilerOptions": {
"target": "es2017",
"module": "commonjs",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"alwaysStrict": true,
"sourceMap": false,
"noEmit": false,
"noEmitHelpers": false,
"importHelpers": false,
"strictNullChecks": false,
"allowUnreachableCode": true,
"lib": ["es6"],
"typeRoots": ["./node_modules/@types"],
"outDir": "./build", // 重定向输出目录
"rootDir": "./src" // 仅用来控制输出的目录结构
},
"exclude": [ // 不参与打包的目录
"node_modules",
"build",
"**/*.test.ts",
"temp",
"scripts",
"./src/__tests__"
],
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"compileOnSave": false,
"buildOnSave": false
}
(4) локальная отладка ссылки bin.js и npm
"bin": {
"privatify": "./bin.js",
"privatify-local": "./bin-local.js"
}
ПолеОпределите имя команды (то есть имя вашего скаффолда) и связанный исполняемый файл, добавьте строку в начале строки#!/usr/bin/env nodeУказывает, что текущий скрипт анализируется node.js
Производственная среда
// bin.js
#!/usr/bin/env node
require('./build')
локальная отладка
// bin-local.js
#!/usr/bin/env node
require('ts-node/register')
require('./src')
Увидев это, у вас могут возникнуть некоторые сомнения.Почему вы выбираете два?Это потому что нам удобно отлаживать скаффолдинг локально.В продакшене это прямо указывает наbuildпапку, но при локальной отладке точно не хочется менять строчку кода, компилировать и упаковывать в build, а потом отлаживать.ts-node/registerОбеспечивает возможность своевременной компиляции, указывая наsrcТолько что
воплощать в жизньnpm link, он будет читаться по умолчаниюpackage.json的binполе, выполнитьprivatify-localВидно, что правильное выполнениеsrc/index.tsсодержание в
(5) Реализация основного кода
src/index.tsдля входного файла,src/commands/*.*sДелегировать определенные командные файлы, здесь мы основываемся на3.1Строительные леса, спроектированные командой, создаютcreate.ts,scope.ts,package.tsдокумент
Обзор следующим образом
Как видите, каждая команда включаетcommand(命令),description(描述), есть еще один执行函数(action), некоторые также поддерживаютoption(参数)
существуетsrc/commands/*.*sзаписываются в следующем виде, напримерcreate.ts
// src/commands/create.ts
const action = (projectName) => {
console.log('projectName:', projectName)
}
export default {
command: 'create <registry-name>',
description: '创建一个npm私服仓库',
optionList: [['--context <context>', '上下文路径']],
action,
}
src/index.tsКод реализован следующим образом
// src/index.ts
import * as globby from 'globby'
import * as commander from 'commander'
import * as path from 'path'
import { error, chalk, fs, info } from './lib'
import * as pacote from 'pacote'
const { program } = commander
let commandsPath = []
let pkgVersion = ''
let pkgName = ''
// 获取src/command路径下的命令
const getCommand = () => {
commandsPath =
(globby as any).sync('./commands/*.*s', { cwd: __dirname, deep: 1 }) || []
return commandsPath
}
// 获取当前包的信息
const getPkgInfo = () => {
const jsonPath = path.join(__dirname, '../package.json')
const jsonContent = fs.readFileSync(jsonPath, 'utf-8')
const jsonResult = JSON.parse(jsonContent)
pkgVersion = jsonResult.version
pkgName = jsonResult.name
}
// 获取最新包最新版本
const getLatestVersion = async () => {
const manifest = await pacote.manifest(`${pkgName}@latest`)
return manifest.version
}
function start() {
getPkgInfo()
const commandsPath = getCommand()
program.version(pkgVersion)
commandsPath.forEach((commandPath) => {
const commandObj = require(`./${commandPath}`)
const { command, description, optionList, action } = commandObj.default
const curp = program
.command(command)
.description(description)
.action(action)
optionList &&
optionList.map((option: [string]) => {
curp.option(...option)
})
})
program.on('command:*', async ([cmd]) => {
program.outputHelp()
error(`未知命令 command ${chalk.yellow(cmd)}.`)
const latestVersion = await getLatestVersion()
if(latestVersion !== pkgVersion){
info(`可更新版本,${chalk.green(pkgVersion)} -> ${chalk.green(latestVersion)}`)
}
process.exitCode = 1
})
program.parseAsync(process.argv)
}
start()
существуетsrc/index.tsИспользование входного файлаglobbyнайтиsrc/commands/*.*sСледующая команда, которая является следующейgetCommand函数, буду читатьКоманда (команда),описание (описание),Выполнить функцию (действие),опция (параметр)использоватьcommander包, положить вcommander.program.command(command) .description(description) .action(action) .option(option)Только что
Официальный сайт Commander, инструмент командной строки, предоставляет очень мощные функцииGitHub.com/recommended/com мужские…
воплощать в жизнь,privatify-local create name1можно увидеть
program.version(pkgVersion)это команда--version, проходим следующееgetPkgInfo函数читатьpackage.jsonдокументversionполе для получения версии
Кроме того, если введенная вами команда не существует, пользователю должно быть предложено:commander包при условииprogram.outputHelp()выполнить
И когда вы обновляете леса, вы, естественно, хотите, чтобы пользователи обновили локальные леса до последней версии, используйтеpacote包Получить указанную версию пакета и запросить информацию об обновлении
(6) Открытый сторонний пакет зависимостей
Scaffolding относится к интерактивной командной строке, которая включает в себя дружественные подсказки об интерфейсе, цвете, копирайтинге и т. д. об успехе или неудаче, загрузке, значках и т. д., которые единообразно инкапсулированы в путиsrc/libДалее подробности не раскрываются, более основная упаковка
(7) Реализация шаблона проекта инициализации
логика примерно сcreate [options] <app-name>Точно так же, интерактивно инициализируя проект, используяinquirer包взаимодействовать, спрашивать пользователяpackage.jsonизname,description,authorполе, после получения поля начать загрузку шаблона проекта,Существует два способа реализации местоположения, в котором находится шаблон проекта.,следующим образом
- Во-первых, упаковать его с помощью скаффолдинга.При установке скаффолдинга шаблон проекта будет храниться в глобальном каталоге.Таким образом, каждый раз при создании проекта скорость копирования из локального очень высокая, но сам шаблон проектаСложно обновить.
- Второй — хранить шаблон проекта на удаленном складе (например, на складе gitlab), таким образом, каждый раз, когда вы создаете проект, вы будете передавать определенный адресДинамическая загрузкаДа, шаблон проекта легко обновить
Здесь мы используемПервый, шаблон проекта помещается в
project-templateПод путь, второй следующий
// project-template/package.json
{
"name": "{{ name }}",
"version": "1.0.0",
"description": "{{ description }}",
"main": "index.js",
"author": "{{ author }}",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"license": "ISC",
"dependencies": {
"npm-package-privatify": "^1.1.4"
}
}
Повторное использованиеhandlebars包Выполните замену шаблона, то есть замените указанный выше{{ name }},{{ description }},{{ author }}заменяется пользовательским вводом
// // src/commands/create.ts
import * as path from 'path'
import * as handlebars from 'handlebars'
import * as inquirer from 'inquirer'
import {
chalk,
execa,
fs,
startSpinner,
succeedSpiner,
warn,
info,
} from '../lib'
// 检查是否已经存在相同名字工程
export const checkProjectExist = async (targetDir) => {
if (fs.existsSync(targetDir)) {
const answer = await inquirer.prompt({
type: 'list',
name: 'checkExist',
message: `\n仓库路径${targetDir}已存在,请选择`,
choices: ['覆盖', '取消'],
})
if (answer.checkExist === '覆盖') {
warn(`删除${targetDir}...`)
fs.removeSync(targetDir)
} else {
return true
}
}
return false
}
export const getQuestions = async (projectName) => {
return await inquirer.prompt([
{
type: 'input',
name: 'name',
message: `package name: (${projectName})`,
default: projectName,
},
{
type: 'input',
name: 'description',
message: 'description',
},
{
type: 'input',
name: 'author',
message: 'author',
},
])
}
export const cloneProject = async (targetDir, projectName, projectInfo) => {
startSpinner(`开始创建私服仓库 ${chalk.cyan(targetDir)}`)
// 复制'private-server-boilerplate'到目标路径下创建工程
await fs.copy(
path.join(__dirname, '..', '..', 'private-server-boilerplate'),
targetDir
)
// handlebars模版引擎解析用户输入的信息存在package.json
const jsonPath = `${targetDir}/package.json`
const jsonContent = fs.readFileSync(jsonPath, 'utf-8')
const jsonResult = handlebars.compile(jsonContent)(projectInfo)
fs.writeFileSync(jsonPath, jsonResult)
// 新建工程装包
execa.commandSync('npm install', {
stdio: 'inherit',
cwd: targetDir,
})
succeedSpiner(
`私服仓库创建完成 ${chalk.yellow(projectName)}\n👉 输入以下命令开启私服:`
)
info(`$ cd ${projectName}\n$ sh start.sh\n`)
}
const action = async (projectName: string, cmdArgs?: any) => {
console.log('projectName:', projectName)
}
export default {
command: 'create <registry-name>',
description: '创建一个npm私服仓库',
optionList: [['--context <context>', '上下文路径']],
action,
}
Предупредите пользователя, когда созданный файл существует, и предоставьтепокрытиес отменойОпции, эффект выполнения показан на рисунке
Вы можете видеть, что проект был созданname,а такжеpackage.jsonзначение, введенное пользователем
Второй также относительно прост, используяdownload-git-repo包, вы можете загрузить адрес удаленного склада, предпосылка заключается в том, что вам нужно создать новый склад шаблонов, например, следующим образом
Следует отметить, что адрес удаленного склада не является адресом клона git, но его необходимо немного скорректировать.
Например, адрес репозитория git:GitHub.com/ в следующем месяце 25/v UE… -> GitHub.com: в следующем месяце 25/v UE-demo-кроме…
import * as download from 'download-git-repo'
// https://github.com/zxyue25/vue-demo-cli-templateA.git
download("https://github.com:zxyue25/vue-demo-cli-templateA#master", projectName, {clone: true}, err => {
if(err){
spinner.fail()
return
}
spinner.succeed()
inquirer.prompt([
{
type: 'input',
name: 'name',
message: '请输入项目名称'
},
{
type: 'input',
name: 'description',
message: '请输入项目简介'
},
{
type: 'input',
name: 'author',
message: '请输入作者姓名'
},
]).then((answers) => {
const packagePath = `${projectName}/package.json`
const packageContent = fs.readFileSync(packagePath,'utf-8')
//使用handlebars解析模板引擎
const packageResult = handlebars.compile(packageContent)(answers)
//将解析后的结果重写到package.json文件中
fs.writeFileSync(packagePath,packageResult)
console.log(logSymbols.success, chalk.yellow('初始化模板成功'))
})
})
(8) Аутсорсинг
На этом основная конструкция и разработка строительных лесов завершены, и они опубликованы в npm.
-
1、npm run lintПроверяйте код, ведь все посылки отправляются во избежание проблем -
2、npm run buildмашинописная упаковка -
3、npm publishОпубликовать в нпм
После того, как пакет будет завершен, проверка установки
npm i npm-package-privatify -g
privatify
В-четвертых, напишите в конце
В центре внимания строительных лесов - помочь снизить затраты и повысить эффективность.Более важным, чем базовые навыки разработки строительных лесов, является проектирование строительных лесов, и это мышление обычно формируется путем размышлений о болевых точках и трудностях, возникающих в ежедневной разработке.Этот проект завершено Пожалуйста, нажмите на код:zxyue25/github