предисловие
Наша команда проекта разработки интерфейса основана на фоне внутренней структуры, эта структура основана на vue ElementUI и сделана некоторая индивидуальная упаковка, а добавление нескольких модулей собственной команды дизайнеров может еще больше упростить разработку фона. страница .
Эта структура разделена на три модуля: базовый компонентный модуль, модуль полномочий пользователя и модуль диаграммы данных.Разработка фонового бизнес-уровня должна основываться по крайней мере на модуле базового компонента, и можно добавить модуль полномочий пользователя или модуль диаграммы данных. в соответствии с конкретными потребностями. Несмотря на то, что vue предоставляет некоторые вспомогательные инструменты vue-cli, поскольку наш проект разработан и упакован на основе многостраничной конфигурации, которая несколько отличается от структуры и конфигурации проекта, сгенерированных vue-cli, при создании проекта по-прежнему требуется ручной труд , Чтобы изменить много мест, даже для удобства, скопируйте прямо из предыдущего проекта, а затем внесите волшебные изменения. На первый взгляд проблема не большая, но на самом деле проблем много:
-
Повторяющаяся работа, утомительная и отнимающая много времени
-
Скопированный шаблон может содержать нерелевантный код
-
В проекте много мест, которые нужно настроить, легко проигнорировать некоторые пункты настройки, а потом закопать яму
-
Люди всегда могут ошибаться. При создании нового проекта всегда требуется время, чтобы устранить неполадки.
-
Внутренний фреймворк также постоянно обновляется.Ручная сборка проекта часто не знает номер последней версии фреймворка.Использование старой версии фреймворка может привести к некоторым ошибкам.
В ответ на вышеуказанные проблемы я разработал инструмент формирования шаблонов, который может динамически генерировать структуры проекта на основе взаимодействий, автоматически добавлять зависимости и конфигурации и удалять ненужные файлы.
Затем организовать весь мой опыт разработки.
Основная идея
Прежде чем приступить к кодированию, сначала подумайте об этом. На самом деле, прежде чем реализовать собственный скаффолдинг, я неоднократно разбирал и анализировал реализацию vue-cli, нашел много интересных модулей и позаимствовал у него некоторые хорошие идеи.
Vue-cli независимо публикует шаблон проекта в качестве ресурса на git, а затем загружает шаблон во время выполнения, отображает его через механизм шаблонов и, наконец, генерирует проект. Основная цель отделения шаблона проекта от инструмента заключается в том, что шаблон проекта отвечает за структуру проекта и конфигурацию зависимостей, а строительные леса отвечают за процесс построения проекта.Эти две части не сильно связаны.Благодаря разделению две части могут обслуживаться независимо. Если структура проекта, зависимости или конфигурация изменяются, просто обновите шаблон проекта.
Ссылаясь на идею vue-cli, я также самостоятельно публикую шаблон проекта на git, а затем загружаю его через инструмент скаффолдинга, получаю информацию о новом проекте через взаимодействие со скаффолдингом, а интерактивный ввод использую как метаданные. -информация для рендеринга шаблона проекта и, наконец, получения инфраструктуры проекта.
Инженерное сооружение
Инженерное делоnodejs 8.4так же какES6Для разработки структура каталогов выглядит следующим образом
/bin # ------ 命令执行文件
/lib # ------ 工具模块
package.json
Некоторый код ниже требует, чтобы выPromise
Лучше иметь определенное понимание.
Используйте command.js для разработки инструментов командной строки
Nodejs имеет встроенную поддержку операций командной строки в рамках проекта node.package.json
серединаbin
Поля могут определять имя команды и связанный с ней исполняемый файл.
{
"name": "macaw-cli",
"version": "1.0.0",
"description": "我的cli",
"bin": {
"macaw": "./bin/macaw.js"
}
}
После того, как проект nodejs настроен таким образом, используя-g
Когда опция установлена глобально, она будет автоматически установлена в системе.[prefix]/bin
Создайте соответствующую символическую ссылку (симлинк) в каталоге для связи с исполняемым файлом. Если установлена локально, эта символическая ссылка будет сгенерирована в./node_modules/.bin
Под содержанием. Преимущество этого в том, что файл nodejs можно выполнить прямо в терминале, как команду. оprefix
, в состоянии пройтиnpm config get prefix
Получать.
hello, commander.js
Создайте файл macaw.js в каталоге bin для обработки логики командной строки.
touch ./bin/macaw.js
Далее мы будем использовать цифру уровня бога на github-tj- разработанные модулиcommander.js. Commander.js может автоматически анализировать команды и параметры, комбинировать несколько параметров, обрабатывать короткие параметры и т. д. Он мощный и простой в использовании. Для конкретного метода использования, пожалуйста, обратитесь к README проекта.
существуетmacaw.js
Напишите логику ввода командной строки в
#!/usr/bin/env node
const program = require('commander') // npm i commander -D
program.version('1.0.0')
.usage('<command> [项目名称]')
.command('hello', 'hello')
.parse(process.argv)
Далее, вbin
Создано в каталогеmacaw-hello.js
, поставить оператор печати
touch ./bin/macaw-hello.js
echo "console.log('hello, commander')" > ./bin/macaw-hello.js
Таким образом, проверьте его с помощью команды узла
node ./bin/macaw.js hello
Неудивительно, что на терминале можно увидеть фразу: привет, командир.
поддержка командираобработка подкоманд в стиле git, что может автоматически привести к файлу выполнения команды, названному в определенном формате в соответствии с подкомандой, формат имени файла[command]-[subcommand]
- macaw hello => macaw-hello
- macaw init => macaw-init
init
bin/macaw.js
const program = require('commander')
program.version('1.0.0')
.usage('<command> [项目名称]')
.command('init', '创建新项目')
.parse(process.argv)
init
touch ./bin/macaw-init.js
Добавьте следующий код
#!/usr/bin/env node
const program = require('commander')
program.usage('<project-name>').parse(process.argv)
// 根据输入,获取项目名称
let projectName = program.args[0]
if (!projectName) { // project-name 必填
// 相当于执行命令的--help选项,显示help信息,这是commander内置的一个命令选项
program.help()
return
}
go()
function go () {
// 预留,处理子命令
}
Обратите внимание на первую строку#!/usr/bin/env node
Что это такое, есть ключевое слово под названиемShebang, если не знаете, можете пойти в поиск
project-name
является обязательным параметром, однако я хотел быproject-name
Займитесь автоматической обработкой.
- Текущий каталог пуст, если имя текущего каталога и
project-name
Если то же самое, создайте проект непосредственно в текущем каталоге, в противном случае создайте проект в текущем каталоге.project-name
Каталог как имя как корневой каталог проекта - Текущий каталог не пуст, если каталог не существует с
project-name
каталог с тем же именем, создайте его с помощьюproject-name
Каталог в качестве имени используется в качестве корневого каталога проекта, в противном случае выводится сообщение о том, что проект уже существует, и завершается выполнение команды.
В соответствии с указанными выше настройками внесите некоторые улучшения в исполняемый файл
#!/usr/bin/env node
const program = require('commander')
const path = require('path')
const fs = require('fs')
const glob = require('glob') // npm i glob -D
program.usage('<project-name>')
// 根据输入,获取项目名称
let projectName = program.args[0]
if (!projectName) { // project-name 必填
// 相当于执行命令的--help选项,显示help信息,这是commander内置的一个命令选项
program.help()
return
}
const list = glob.sync('*') // 遍历当前目录
let rootName = path.basename(process.cwd())
if (list.length) { // 如果当前目录不为空
if (list.filter(name => {
const fileName = path.resolve(process.cwd(), path.join('.', name))
const isDir = fs.stat(fileName).isDirectory()
return name.indexOf(projectName) !== -1 && isDir
}).length !== 0) {
console.log(`项目${projectName}已经存在`)
return
}
rootName = projectName
} else if (rootName === projectName) {
rootName = '.'
} else {
rootName = projectName
}
go()
function go () {
// 预留,处理子命令
console.log(path.resolve(process.cwd(), path.join('.', rootName)))
}
Не стесняйтесь найти путь для создания пустого каталога, а затем выполнить команду инициализации, которую мы определили в этом каталоге.
node /[pathto]/macaw-cli/bin/macaw.js init hello-cli
Если это нормально, вы можете увидеть путь к проекту, напечатанный на терминале.
Скачать шаблоны с помощью download-git-repo
Инструмент для загрузки шаблонов использует другой модуль узлаdownload-git-repo, обратитесь к README проекта и просто инкапсулируйте инструмент загрузки.
существуетlib
создать каталогdownload.js
const download = require('download-git-repo')
module.exports = function (target) {
target = path.join(target || '.', '.download-temp')
return new Promise(resolve, reject) {
// 这里可以根据具体的模板地址设置下载的url,注意,如果是git,url后面的branch不能忽略
download('https://github.com:username/templates-repo.git#master',
target, { clone: true }, (err) => {
if (err) {
reject(err)
} else {
// 下载的模板存放在一个临时路径中,下载完成后,可以向下通知这个临时路径,以便后续处理
resolve(target)
}
})
}
}
Модуль download-git-repo — это, по сути, метод, который следует за node.js.CPSАсинхронный результат обрабатывается путем обратного вызова. Если вы знакомы с Node.js, вы все должны знать, что есть один такой процесс.недостатки, я инкапсулировал его и преобразовал в более популярный сейчас стиль Promise для обработки асинхронности.
снова к предыдущемуmacaw-init.js
модифицировать
const download = require('./lib/download')
... // 之前的省略
function go () {
download(rootName)
.then(target => console.log(target))
.catch(err => console.log(err))
}
После завершения загрузки файлы шаблонов проекта из временного каталога загрузки переносятся в каталог проекта, и в основном завершается простое формирование шаблонов. Конкретный способ реализации переноса описываться не будет, вы можете обратиться к node.jsAPI. Если ваша версия node.js ниже 8, вы можете использовать поток и канал для ее реализации.Если это 8 или 9, вы можете использовать новый API——copyFile()илиcopyFileSync().
but...
Мир не так прост, как мы думаем. Мы можем пожелать, чтобы некоторые файлы или код в шаблоне проекта могли обрабатываться динамически. Например:
- новый проектимя,номер версии,описыватьИнформацию и т. д. можно вводить посредством взаимодействия с каркасом, а затем ввод вставляется в шаблон.
- Не все файлы в шаблоне проекта будут использоваться, и эти бесполезные файлы или каталоги можно удалить с помощью параметров, предоставляемых скаффолдингом.
Для таких случаев нам также необходимо использовать другие наборы инструментов для завершения.
Обработка взаимодействий командной строки с inquirer.js
Для интерактивных функций командной строки вы можете использоватьinquirer.jsобрабатывать. Использование на самом деле очень простое:
const inquirer = require('inquirer') // npm i inquirer -D
inquirer.prompt([
{
name: 'projectName',
message: '请输入项目名称'
}
]).then(answers => {
console.log(`你输入的项目名称是:${answers.projectName}`)
})
prompt()
принять одинпроблемный объектВо время взаимодействия между пользователем и терминалом ввод пользователя сохраняется вобъект ответа, затем возвращаетPromise
,пройти черезthen()
Получите этот объект ответа. так легко!
Далее продолжаем улучшать macaw-init.js.
// ...
const inquirer = require('inquirer')
const list = glob.sync('*')
let next = undefined
if (list.length) {
if (list.filter(name => {
const fileName = path.resolve(process.cwd(), path.join('.', name))
const isDir = fs.stat(fileName).isDirectory()
return name.indexOf(projectName) !== -1 && isDir
}).length !== 0) {
console.log(`项目${projectName}已经存在`)
return
}
next = Promise.resolve(projectName)
} else if (rootName === projectName) {
next = inquirer.prompt([
{
name: 'buildInCurrent',
message: '当前目录为空,且目录名称和项目名称相同,是否直接在当前目录下创建新项目?'
type: 'confirm',
default: true
}
]).then(answer => {
return Promise.resolve(answer.buildInCurrent ? '.' : projectName)
})
} else {
next = Promise.resolve(projectName)
}
next && go()
function go () {
next.then(projectRoot => {
if (projectRoot !== '.') {
fs.mkdirSync(projectRoot)
}
return download(projectRoot).then(target => {
return {
projectRoot,
downloadTemp: target
}
})
})
}
Если текущий каталог пуст, а имя каталога совпадает с именем проекта, подтвердите, нужно ли создавать проект непосредственно в текущем каталоге через взаимодействие с терминалом, что сделает создание шаблонов более удобным для пользователя.
Как упоминалось ранее, имя, номер версии, описание и другую информацию о новом проекте можно напрямую вставить в шаблон проекта через терминальное взаимодействие, после чего процесс взаимодействия может быть дополнительно улучшен.
// ...
// 这个模块可以获取node包的最新版本
const latestVersion = require('latest-version') // npm i latest-version -D
// ...
function go () {
next.then(projectRoot => {
if (projectRoot !== '.') {
fs.mkdirSync(projectRoot)
}
return download(projectRoot).then(target => {
return {
name: projectRoot,
root: projectRoot,
downloadTemp: target
}
})
}).then(context => {
return inquirer.prompt([
{
name: 'projectName',
message: '项目的名称',
default: context.name
}, {
name: 'projectVersion',
message: '项目的版本号',
default: '1.0.0'
}, {
name: 'projectDescription',
message: '项目的简介',
default: `A project named ${context.name}`
}
]).then(answers => {
return latestVersion('macaw-ui').then(version => {
answers.supportUiVersion = version
return {
...context,
metadata: {
...answers
}
}
}).catch(err => {
return Promise.reject(err)
})
})
}).then(context => {
console.log(context)
}).catch(err => {
console.error(err)
})
}
После завершения загрузки пользователю предлагается ввести новую информацию о проекте. Конечно, интерактивная задача этим не ограничивается, и вы можете добавить больше интерактивных задач в зависимости от ситуации в вашем собственном проекте. Сила inquirer.js заключается в том, что он поддерживает множество типов взаимодействий, помимо простыхinput
,а такжеconfirm
,list
,password
,checkbox
и т.д. Подробности смотрите в проектеREADME.
Затем, как вставить это входное содержимое в шаблон, на этот раз используется другой простой, но не простой инструментарий——metalsmith.
Работа с шаблонами с помощью Metalsmith
Цитируя введение официального сайта:
An extremely simple, pluggable static site generator.
Это генератор статических веб-сайтов, который можно использовать в шаблонах пакетной обработки.Wintersmith,Assemble,Hexo. Одной из его величайших особенностей является то, чтоEVERYTHING IS PLUGIN, так вот, metalsmith по сути является клеевым каркасом, путем приклеивания различных плагинов для завершения производственной работы.
Добавляйте переменные заполнители в шаблоны проектов
Шаблонный движок на мой выборhandlebars. Конечно, есть и другие варианты, напримерejs,jade,swig.
Используйте синтаксис handlebars для внесения некоторых изменений в шаблон, таких как изменениеpackage.json
{
"name": "{{projectName}}",
"version": "{{projectVersion}}",
"description": "{{projectDescription}}",
"author": "Forcs Zhang",
"private": true,
"scripts": {
"dev": "node build/dev-server.js",
"start": "node build/dev-server.js",
"build": "node build/build.js",
"unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
"test": "npm run unit",
"lint": "eslint --ext .js,.vue src test/unit/specs"
},
"dependencies": {
"element-ui": "^2.0.7",
"macaw-ui": "{{supportUiVersion}}",
"vue": "^2.5.2",
"vue-router": "^2.3.1"
},
...
}
package.json
изname
,version
,description
Содержимое поля заменяется заполнителем для синтаксиса руля, и аналогичные замены выполняются в других частях шаблона, а обновление шаблона повторно отправляется после завершения.
Шаблон строительных лесов для достижения функции интерполяции
существуетlib
Создано в каталогеgenerator.js
, который инкапсулирует кузнеца по металлу.
touch ./lib/generator.js
// npm i handlebars metalsmith -D
const Metalsmith = require('metalsmith')
const Handlebars = require('handlebars')
const rm = require('rimraf').sync
module.exports = function (metadata = {}, src, dest = '.') {
if (!src) {
return Promise.reject(new Error(`无效的source:${src}`))
}
return new Promise((resolve, reject) => {
Metalsmith(process.cwd())
.metadata(metadata)
.clean(false)
.source(src)
.destination(dest)
.use((files, metalsmith, done) => {
const meta = metalsmith.metadata()
Object.keys(files).forEach(fileName => {
const t = files[fileName].contents.toString()
files[fileName].contents = new Buffer(Handlebars.compile(t)(meta))
})
done()
}).build(err => {
rm(src)
err ? reject(err) : resolve()
})
})
}
Датьmacaw-init.js
изgo()
Добавьте логику сборки.
// ...
const generator = require('../lib/generator')
function go () {
next.then(projectRoot => {
// ...
}).then(context => {
// 添加生成的逻辑
return generator(context)
}).then(context => {
console.log('创建成功:)')
}).catch(err => {
console.error(`创建失败:${err.message}`)
})
}
На данный момент интерактивные леса, которые могут динамически интерполировать шаблоны, в основном завершены.
tips: Стеновая трещина рекомендует TJ другой комплект:consolidate.js, найденный в vue-cli, если вам интересно, вы можете узнать о нем.
Украсьте наши леса
Сделайте леса более удобными в использовании с помощью некоторых комплектов. Вот два набора инструментов, найденных в vue-cli:
Эти два комплекта не сложны в использовании, и при правильном использовании они сделают леса более высокими.
Оптимизируйте загрузку и ожидание взаимодействий с ora
Ora можно использовать в сценариях загрузки и ожидания, например, при загрузке шаблонов проектов в скаффолдинге, а также в том случае, если процесс генерации проектов путем интерполяции шаблонов также имеет очевидное ожидание.
Возьмите загрузку в качестве примера, даdownload.js
Сделайте некоторые улучшения:
npm i ora -D
const download = require('download-git-repo')
const ora = require('ora')
module.exports = function (target) {
target = path.join(target || '.', '.download-temp')
return new Promise(resolve, reject) {
const url = 'https://github.com:username/templates-repo.git#master'
const spinner = ora(`正在下载项目模板,源地址:${url}`)
spinner.start()
download(url, target, { clone: true }, (err) => {
if (err) {
spinner.fail() // wrong :(
reject(err)
} else {
spinner.succeed() // ok :)
resolve(target)
}
})
}
}
Используйте мел, чтобы оптимизировать эффект отображения информации терминала
Мел может установить цвет текста терминала.
// ...
const chalk = require('chalk')
const logSymbols = require('log-symbols')
// ...
function go () {
// ...
next.then(/* ... */)
/* ... */
.then(context => {
// 成功用绿色显示,给出积极的反馈
console.log(logSymbols.success, chalk.green('创建成功:)'))
console.log()
console.log(chalk.green('cd ' + context.root + '\nnpm install\nnpm run dev'))
}).catch(err => {
// 失败了用红色,增强提示
console.error(logSymbols.error, chalk.red(`创建失败:${error.message}`))
})
}
Удалите ненужные файлы из шаблона на основе ввода
Иногда требуются не все файлы в шаблоне проекта. Чтобы убедиться, что во вновь сгенерированном проекте как можно больше нет грязного кода, нам может потребоваться подтвердить окончательную сгенерированную структуру проекта в соответствии с входными элементами скаффолдинга и удалить ненужные файлы или каталоги. Например, vue-cli спросит нас, нужно ли нам добавить тестовый модуль при создании проекта, если нет, то окончательный сгенерированный код проекта не будет содержать кода, связанного с тестами. Как добиться этой функции?
идея реализации
Я ссылаюсь на идею git и определяюignore
файл, перечислите здесь имена файлов, которые нужно игнорироватьignore
файл с синтаксисом шаблона. Когда строительные леса создают проект, он сначала отображает его в соответствии с входным элементом.ignore
файл, то согласноignore
Содержимое файла удаляет ненужные файлы шаблонов, а затем отображает фактический шаблон проекта, который будет использоваться, и, наконец, создает проект.
План реализации
В соответствии с приведенными выше идеями я сначала определяю проект, который принадлежит нашему собственномуignore
файл, названныйtemplates.ignore
.
то в этомignore
Добавьте имена файлов, которые необходимо игнорировать.
{{#unless supportMacawAdmin}}
# 如果不开启admin后台,登录页面和密码修改页面是不需要的
src/entry/login.js
src/entry/password.js
{{/unless}}
# 最终生成的项目中不需要ignore文字自身
templates.ignore
затем вlib/generator.js
добавить паруtemplates.ignore
логика обработки
// ...
const minimatch = require('minimatch') // https://github.com/isaacs/minimatch
module.exports = function (metadata = {}, src, dest = '.') {
if (!src) {
return Promise.reject(new Error(`无效的source:${src}`))
}
return new Promise((resolve, reject) => {
const metalsmith = Metalsmith(process.cwd())
.metadata(metadata)
.clean(false)
.source(src)
.destination(dest)
// 判断下载的项目模板中是否有templates.ignore
const ignoreFile = path.join(src, 'templates.ignore')
if (fs.existsSync(ignoreFile)) {
// 定义一个用于移除模板中被忽略文件的metalsmith插件
metalsmith.use((files, metalsmith, done) => {
const meta = metalsmith.metadata()
// 先对ignore文件进行渲染,然后按行切割ignore文件的内容,拿到被忽略清单
const ignores = Handlebars.compile(fs.readFileSync(ignoreFile).toString())(meta)
.split('\n').filter(item => !!item.length)
Object.keys(files).forEach(fileName => {
// 移除被忽略的文件
ignores.forEach(ignorePattern => {
if (minimatch(fileName, ignorePattern)) {
delete files[fileName]
}
})
})
done()
})
}
metalsmith.use((files, metalsmith, done) => {
const meta = metalsmith.metadata()
Object.keys(files).forEach(fileName => {
const t = files[fileName].contents.toString()
files[fileName].contents = new Buffer(Handlebars.compile(t)(meta))
})
done()
}).build(err => {
rm(src)
err ? reject(err) : resolve()
})
})
}
Metalsmith, основанный на идее плагина, легко расширяется, а реализация несложна.Чтобы узнать конкретный процесс, обратитесь к комментариям в коде.
Суммировать
После разборки vue-cli и использования множества узловых модулей реализация всего скаффолдинга не представляет сложности.
- Отделите шаблоны проектов от инструментов формирования шаблонов для более удобного обслуживания шаблонов и инструментов формирования шаблонов.
- Работа с командной строкой через command.js
- Обработка загрузок через download-git-repo
- Обработка терминальных взаимодействий через inquirer.js
- Вставляйте интерактивные входные данные в шаблоны проектов с помощью кузнеца по металлу и механизма шаблонов.
- Ссылаясь на идею игнорирования git, используйте настраиваемые templates.ignore для динамического удаления ненужных файлов и каталогов.
Вышеизложенный мой основной опыт в разработке строительных лесов, в середине еще много недочетов, и в дальнейшем я буду его постепенно улучшать.
Наконец, позвольте мне сказать, что есть еще много вещей, которые может делать vue-cli.Подробности вы можете посмотреть в README и исходном коде проекта. По поводу развития строительных лесов, не обязательно полностью строить колесо, можно посмотреть еще один очень мощный модульYEOMAN, с помощью этого модуля вы сможете быстро реализовать собственные инструменты для создания лесов.
В тексте есть неточности, прошу исправить и обсудить :)