脚手架,必然运用到不少的命令行知识。本文会向你们介绍一个简单的vue脚手架从无到有的全过程,但愿你们有所收获。javascript
|──bin
| └──try.js
|──config
| └──config.js
|──lib
| ├──actions.js
| ├──create.js
| └──otherCommand.js
|──utils
| └──terminal.js
|──templates
| └──component.ejs
复制代码
命令行操控工具 -- commander
下载、克隆利器 -- download-git-repo
命令行交互工具 -- inquirer
文件操做 -- fs-extra
地址操做 -- path
复制代码
npm init -y // 初始化package.json
复制代码
文件的第一行必须是:vue
#! /usr/bin/env node
复制代码
#! 称为shebang,增长这一行是为了指定:以 node 执行脚本文件。java
为咱们的第一个脚手架工具命名为:just-cli,键名对应的值指向的地址则是命令行的执行入口文件地址。node
{
"bin": {
"just-cli": "./bin/try.js"
},
}
复制代码
终端执行 **npm link **,便可将配置的命令注册至全局。webpack
npm link // 取消连接: npm unlink
复制代码
这时候,windows用户到npm安装目录查看,会发现咱们多了这些文件:git
PS:mac用户能够到 /usr/local/lib/node_modules 查看。web
npm install commander --save
复制代码
#! /usr/bin/env node
/* * 文件:/bin/try.js */
//引入 commander
const program = require('commander')
//版本号配置
program.version(`Version is ${require('../package.json').version}`, '-v, --version')//支持小写:‘-v’
.description('手写一个脚手架')
.usage('<command> [options]')
//解析参数,常置于定义命令以后
program.parse(process.argv);
复制代码
到这里,你已经为 just-cli 配置了版本信息,这个版本信息是从脚手架项目的package.json中动态获取的,并支持如下两种,不区分大小写的查看版本信息的方式。npm
just-cli -v
just-cli -V
复制代码
试试看打印你本身的脚手架版本信息吧!json
定义一个形如:create name [options] 的命令,用于执行脚手架的初始化。windows
首先咱们定义这个命令,配置命令携带参数:name,添加命令描述,为其赋予 -f 的能力,指定执行函数:createAction
/* * 文件:/lib/create.js * 命令:搭建脚手架 * create <name> */
const program = require('commander')
const {createAction} = require('./actions')
const create = function(){
program.command('create <name>')
.description('create a new project')
.option('-f, --force', 'title to use before name')
.action(createAction)
}
module.exports =create
复制代码
而后在命令入口文件中引入上文的配置。
/* * 文件:/bin/try.js */
//......
//引入拆分的配置文件
const createConfig = require('../lib/create')
//添加拆分的配置:create <name>
createConfig()
//......
复制代码
/* * 文件:/lib/actions.js * 用途:定义命令的执行内容 */
const open = require('open')
const { promisify } = require('util')
const download = promisify(require('download-git-repo'))
const { labAdress } = require('../config/lab-config')
const { spawnCommand } = require('../utils/terminal')
const createAction = async (project,options) => {
console.log(`creating a project:${project},\noptions:\n${JSON.stringify(options)}`)
//执行模板的克隆
console.log(`downloading the templates documents`)
await download(labAdress, project, { clone: true })
// 执行命令 npm install
// 判别命令在不一样操做系统的类型,且windows系统会统一返回'win32',无论其自己操做系统是32/64位
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'
console.log(`spawn npm install:`)
await spawnCommand(npm, ['install'], { cwd: `./${project}` })
// 同步执行命令 npm run dev
// 同步调用的缘由:线程在项目运行后,会阻塞后续代码执行
console.log(`spawn npm run dev`)
spawnCommand(npm, ['run', 'dev'], { cwd: `./${project}` })
// // 同步打开浏览器
open('http://localhost:8080');
}
module.exports = {
createAction
}
复制代码
看看有哪些须要须要特别理解的地方:
/* 执行终端命令 */
const { spawn } = require('child_process')
//args参数包括:command、args、options
const spawnCommand = (...args) => {
return new Promise((resolve, reject) => {
const spawnProcess = spawn(...args)
//复制控制台打印到主进程
spawnProcess.stdout.pipe(process.stdout)
spawnProcess.stderr.pipe(process.stderr)
//监听进程的关闭,在执行完毕或error时触发
spawnProcess.on('close', () => {
resolve()
})
})
}
module.exports = {
spawnCommand
}
复制代码
在上一节,实现了运用命令行建立脚手架模板,接下来,还须要对脚手架的建立过程进行优化,在命令的执行方法中添加必要的交互动做和逻辑判断。 引入 inquirer 工具,能很好地解决命令行交互的问题,交互和逻辑判断代码以下。
/* * 文件:/lib/actions.js * 用途:定义命令的执行内容 */
const open = require('open')
const path = require('path')
const fsextra = require('fs-extra')
const Inquirer = require('inquirer')
const { promisify } = require('util')
const download = promisify(require('download-git-repo'))
const { labAdress } = require('../config/lab-config')
const { spawnCommand } = require('../utils/terminal')
const { delDir } = require('../utils/common')
async function createAction(projectName, options) {
console.log(`${projectName},\noptions:\n${JSON.stringify(options)}`)
const cwd = process.cwd();// 获取当前命令执行时的工做目录
const targetDir = path.join(cwd, projectName);// 目标目录
console.log(targetDir)
if (fsextra.existsSync(targetDir)) {
if (options.force) {
delDir(targetDir);
console.log('删除原目录成功')
create(projectName)
} else {
let { action } = await Inquirer.prompt([
{
name: 'action',
type: 'list',
message: '目标文件夹已存在,请选择是否覆盖:',
choices: [
{ name: '覆盖', value: true },
{ name: '取消', value: false }
]
}
])
if (!action) {
console.log('取消操做')
return
} else {
console.log(`\r\n正在删除原目录....`);
delDir(targetDir)
console.log('删除成功')
create(projectName)
}
}
} else {
create(projectName)
}
}
const create = async (projectName) => {
// 见5.2小节内容
}
module.exports = {
createAction
}
复制代码
上文中的 delDir 方法是咱们封装的一个向内递归遍历文件、删除文件夹的方法,定义以下:
// 引入fs模块
const fs = require('fs-extra');
function delDir(path) {
// 读取文件夹中全部文件及文件夹
const list = fs.readdirSync(path)
list.forEach((item) => {
// 拼接路径
const url = path + '/' + item
// 读取文件信息
const stats = fs.statSync(url)
// 判断是文件仍是文件夹
if (stats.isFile()) {
// 当前为文件,则删除文件
fs.unlinkSync(url)
} else {
// 当前为文件夹,则递归调用自身
arguments.callee(url)
}
})
// 删除空文件夹
fs.rmdirSync(path)
}
module.exports={
delDir
}
复制代码
除了建立模板,脚手架还应该具有其余方便工做的指令,下面介绍如何运用脚手架命令添加一个新组件。 咱们定义一个形如:
just-cli component <componentName> -d <destination>
复制代码
的命令,其中 -d 和 destination 参数为选填。咱们须要如下四个步骤来完成这个命令的执行。
即刻着手! 首先咱们定义这个命令,配置 -f 和 -d 能力,携带指定文件位置的参数:dest
/* * 文件:/lib/newComponent.js * 定义命令:新增页面 * page <pageName> */
const program = require('commander')
const { addPageAction } = require('./actions')
const page = function () {
program.command('page <name>')
.description('add a new page')
.option('-f, --force', 'spwan with force')
.option('-d, --dest <dest>', '指定存储地址,例如:just-cli component <newPage> -d /studio/task')
.action(addPageAction)
}
module.exports = page
复制代码
使用ejs模板实现模板的参数占位,一个预设的.vue文件模板以下:
<template>
<div class="<%= data.lowerName %>">{{msg}} <h1>{{message}}</h1> </div>
</template>
<script> export default { name: '<%= data.name %>', props: { msg: String }, components: {}, mixins: [], data() { return { message: "<%= data.name %>" } }, create() { }, mounted() { }, computed: {} } </script>
<style> .<%=data.lowerName %> {} </style>
复制代码
注意:模板字符串也能实现这个功能。
略,参考第6节
模板的编译函数封装以下:
const ejs = require('ejs')
const path = require('path');
const compileEjs = (templateName, data) => {
const templatePosition = `../templates/${templateName}`
const templatePath = path.join(__dirname, templatePosition)
return new Promise((resolve, reject) => {
ejs.renderFile(templatePath, { data }, {}, (err, res) => {
if (err) {
console.log(err);
reject(err)
return
}
resolve(res)
})
})
}
复制代码
其中,参数data中包含了要传入模板的参数,如: name。
文件的写入函数封装以下:
const fs = require('fs-extra');
const writeToFile = (path, templateContent) => {
return fs.promises.writeFile(path, templateContent)
}
复制代码
顺序完成这四个步骤,一个新增组件的命令就已经完成了!
学到这里,小伙伴们已经能够将经常使用的项目配置经过上传代码仓库、运行脚手架来生成项目脚手架模板了,但这还只是脚手架的一点点,脚手架定制远不止这些内容,请大开想象,动手定制你专属的脚手架吧!