脚手架本质上是一个工具,使用脚手架的目的就是摆脱构建工程时重复性的工做,尤为是当一个工程具备必定通用性时,工程脚手架的意义就更为突出。它可让咱们只须要一行命令,就能够初始化好一项工程,而不用费心费力的去作配置环境,安装依赖,解决依赖冲突这样的支线任务,能够直奔主线任务,早早下班~~vue
开发一个的脚手架,一般须要以下npm包:node
实现的功能:react
STEP1: 打开一个终端,在你喜欢的地方新建一个空项目git
mkdir meo-cli
cd meo-cli
复制代码
在项目更目录下执行:github
npm init -y
复制代码
你会获得一个package.jsonvue-cli
{
"name": "meo-cli",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
复制代码
关键点来了,在 package.json中加以一个bin字段,在安装时,npm 会将文件符号连接到 prefix/bin 以进行全局安装或./node_modules/.bin/本地安装。这样,就能够全局使用了。例如,下面的将meo-cli做为命令名称,执行文件是根目录的index.jsnpm
{
"name": "meo-cli",
"version": "1.0.0",
"description": "",
"main": "index.js",
"bin": {
"meo-cli": "index.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
复制代码
STEP2 : 先写一个简单代码试一试吧~。在根目录下建立index.js文件,而后狠狠敲入下面的代码:json
#!/usr/bin/env node
console.log('helo, world!')
复制代码
而后在命令行执行 meo-cli
, 不出意外的话,你应该看不到输出hello,world。只会有个报错提示:数组
zsh: command not found: meo-cli
复制代码
这是为什呢?由于咱们并无安装对应的包,固然就不会连接到全局啦。一个办法是发包,而后安装到本地,就能够了。可是这样太麻烦,难道每次调试都要发包??bash
有没有更优雅的方法呢?答案是:有 npm早就想好了对策,就是npm link,它能够把指定的执行文件连接到全局,使用也很是简单,在项目根目录下执行:
npm link
复制代码
就能够了。若是你执行命令后,显示相似安装npm包的提示,就说明连接成功了。在执行一下 meo-cli
,就能够看到使人欣慰的hello world了。这里要注意一下首行的代码
#!/usr/bin/env node
复制代码
这段并非注释,这段代码是告诉你的脚本工具(bash/zsh), 下面的内容是要在node环境下运行的代码。千万不能省略!!!进行下一步以前,让咱们下来回顾一下vue-cli脚手架是怎么使用的。
能够看出来,脚手架会提供一个问答交互的方式,让使用者输入或选择参数,而后根据获取的参数作出相应的动做(action)。
STEP3: 交互式脚手架的另外一个特色是灵活,使用者能够根据本身的需求,能够清晰的去选择让脚手架作什么,不作什么。要实现这样的功能,就是要用到开头提到的插件:
commander是能够用来获取命令行的参数,并对参数作响应函数,inquirer则能够为咱们提供一个’问答’式的交互体验。咱们修改一下index.js的代码:
// index.js
#!/usr/bin/env node
const { program } = require('commander');
program.version('1.0.0')
program.parse(process.argv)
复制代码
而后咱们在命令行输入:
meo-cli -V // 1,0,0
复制代码
就会获得版本号信息。输入
meo-cli -h
复制代码
就会获得这样的帮助信息。
这和咱们使用其余脚手架的体验是同样的。
这样的体验要归功于commander.js
commander用法以下:.command(‘init [name]’, ‘init a project’, opts) 功能:注册一个命令
第一个参数:设置的命令的名称,后面能够跟参数,<> 表示必选参数,[]表示可选参数
第二个参数: 命令的描述,可选,注意,当有第二个参数时,不能显示的调用action做为命令的回调,须要使用独立的可执行文件做为命令
第三个参数:配置参数,如noHelp,isDefault等 .option(’-n, --name | [name]’, ‘desc’, ‘GK’)
功能:定义命令选项,(相似命令的额外参数, 用于辅助命令)
.description(‘this is a command desc’)
功能:命令的描述, 同时会应用到命令的帮助信息中,使用help命令时会显示
.action(cb):命令的回调函数
.parse() 命令行参数解析, 一般用于最后 e.g.
上面的代码,定义了一个命令init, 对命令作了描述(description), 并对init命令作了额外参数-t, 表示初始化工程的类型。action是init命令的回调,在回调用打印了参数,即咱们定义一个工程,名字为demo, 工程类型为vue。这样后续工做中就能够用工程名拼接下载模板到本地的路径,type=vue 表明咱们会去拉取vue的模板。STEP4: ok, 咱们已经能够经过自定义命令获取到参数,接下来就能够拉取对应模板了。这时咱们就须要使用 download-git-repo
这个包,它能够用来下载github, gitlab等远程仓库的代码。用法以下:
download(repository, destination, options, callback)
复制代码
repository: 远程仓库的地址。destination:下载到本地的路径 options: 配置参数 callback: 回调函数 须要注意一下,远程仓库地址能够写成:
direct: http: //github.com/xxx
复制代码
即须要完成的仓库路径,默认状况下,会下载master分支,若是要下载其余分支,在连接后加入分支名,
direct:http://github.com/name/xxx.git#my-branch
复制代码
小技巧咱们能够将模板分配到不一样的分支,而后经过分支来下载不一样模板。回调函数中会返回下载的结果,根据结果能够作出不一样状态的处理。例如,根据返回状态判断是否下载成功,下载成功后提示是否要进行其余操做(安装项目依赖,后面会提到哦~)
到此,一个初始化工程的脚手架就完成了。是的,真没骗人,命令行执行指定命令,获取参数,拉取模板代码,就这些~~。
可是, 还能够作更多。
一般咱们在初始化一个项目时,会提示填写项目名称(已经作了),项目描述,做者等信息,这些都是使用者输入的,也就是命令行的参数,并且会提供一个更友好的“你问我答” 的方式教你作事,不, 求你填写信息😁😁。这时候就是inquirer上场的时候了,它经过极为简单函数方式来提供交互操做。例如,咱们要提示使用者输入项目描述和做者信息,就能够这样写,在action回调函数中
// ...
inquirer.prompt([
{
name: 'description',
message: '请输入项目描述'
},
{
name: 'author',
message: '请输入项目做者',
default: 'robot'
}
])
.then((res) => {})
复制代码
name表示输入的键名,输入的值为value, 即结果会以键值对的形式返回。default为默认值,当直接回车跳过期,会使用默认值。若是但愿默认值是空,能够写成 default:''
或省略default。
inquirer.prompt() 的参数是一个对象数组,能够这样理解,inquirer是流程化的结构,流程能够是等待输入,列表选择,confirm确认yes or no。例如选择项目模板是,使用者能够从提供的模板列表中选择,而不是本身去输入,就能够这样定义参数:
{
name:'type',
type: 'list',
message: 'choose a type of project to init',
choices: ['react', 'vue', 'h5'],
default: 'react',
}
复制代码
type表示类型,choices为列表数组,使用者能够从react,vue,h5中选择模板,默认会是react模板。
而后根据type的值去拼接git仓库地址,下载对应模板。
更多inquirer的用法,你们能够参考 github.com/SBoudrias/I… 来使用,这里再也不赘述。
STEP5: 咱们已经经过交互方式拿到了项目描述,做者等信息,可是咱们的目的是将这些信息写入到下载的模板中,也就是package.json中对应的description,author以及项目名称name中。这要怎么作呢?这就须要handlebars.js的帮助了,handlebars是一个强大的模板引擎,它能够解析指定模板,而后根据参数渲染模板。由于咱们要将name, description, author写入到package.json中,所以咱们要稍微修改一下模板文件:
如图,将要填写的字段用{{}} 方式表示,内容就是对应要写入的变量名字,这和inquirer交互时拿到的字段要保持一致。固然,handlebars不止这么简单,更多的用法能够参考官网 handlebarsjs.com/guide/
这样,咱们就能够在模板下载完成后作写入工做了。
download(url,'./template', { clone: true }, (error) => {
if(!error) {
const packagePath = path.join(downloadPath, 'package.json');
// 判断是否有package.json, 要把输入的数据回填到模板中
if (fs.existsSync(packagePath)) {
const content = fs.readFileSync(packagePath).toString();
// handlebars 模板处理引擎
const template = handlebars.compile(content);
const result = template(param);
fs.writeFileSync(packagePath, result);
} else {
console.log('failed! no package.json');
}
}
})
复制代码
这样在下载成功后,而且有package.json时才会去写入,不然给出错误提示。
然而…
咱们发现,到目前为止,咱们的命令行的输入一点也很差看,没有下载中的提示,没有五彩斑斓醒目的文字… ora 和 chalk这时就起做用了。ora能够美化命令行的loading,你是转圈圈,动态的小点点,仍是自定义的gif均可以知足你,chalk就可让你的命令行文字有了颜色,失败的红色告警,成功的绿色提示,都没问题。
下面是拉取仓库模板的部分代码:
const downloadPath = path.join(process.cwd(), name);
const param = {name, ...parameter};
const spinner = ora('正在下载模板, 请稍后...');
spinner.start();
download(
// 直连下载,默认下载master
`direct:http://git.code.oa.com/name/xxx.git#${type}-tpl`,
downloadPath,
{ clone: true },
(error) => {
if (!error) {
// success download
spinner.succeed();
const packagePath = path.join(downloadPath, 'package.json');
// 判断是否有package.json, 要把输入的数据回填到模板中
if (fs.existsSync(packagePath)) {
const content = fs.readFileSync(packagePath).toString();
// handlebars 模板处理引擎
const template = handlebars.compile(content);
const result = template(param);
fs.writeFileSync(packagePath, result);
console.log(chalk.green('success! 项目初始化成功!'));
console.log(
chalk.greenBright('开启项目') + '\n' +
chalk.greenBright('cd ' + name) + '\n' +
chalk.greenBright('start to dvelop~~~!')
)
} else {
spinner.fail();
console.log(chalk.red('failed! no package.json'));
return;
}
} else {
console.log(chalk.red('failed! 拉取模板失败', error));
return;
}
}
)
复制代码
到此为止,一个基础的工程脚手架就完成了,它能够经过简单一条命令,根据输入的参数,拉取不一样模板代码,并将用户信息回填到模板中,提供了交互式的友好体验。嗯~,挺香的,收工喽!!
cd vue-demo
npm run serve
复制代码
可是… 咱们会发现一个问题,使用vue-cli的时候,在最后会有个运行提示:
它并无提示咱们要npm install, 这说明下载模板的时候项目的依赖也同时安装。可咱们的没有这个功能,不行,搞起!!STEP6 : 模板下载好后,咱们要进入模板目录,而后根据它的package.json安装依赖,这里咱们能够丰富一下,让使用者在安装依赖时有选择:1. 先不安装依赖,稍后自行安装, 2. 选择安装工具,这时最后的提示要给个npm intall的提示才算完美。是否安装依赖。即咱们须要从使用者那里获得一个confirm, 根据返回的true或false来决定是否进行下一项安装。选择安装工具。若是使用者选择安装,就要提示他选择安装工具。这两个交互一样可使用inquirer来完成
const continueToInstall = {
type: 'confirm',
name:'next',
message: 'continue to install the project',
default: true,
}
const installTool = {
name: 'tool',
type: 'list',
message: 'choose the tool to install',
choices: ['npm', 'tnpm', 'yarn'],
default: 'npm',
}
复制代码
这里作了简单的封装:
const { next } = await inquirer.prompt(continueToInstall);
if (next) {
const { tool } = await inquirer.prompt(installTool);
// 安装项目依赖
const res = await installFunc({ cwd: downloadPath, command: tool });
if (res && res.code === 0) {
processSuccess(name, true, type);
}
} else {
processSuccess(name, false, type);
}
复制代码
至此,一个简单的工程脚手架就完成了。
原做者:李泽刚
未经赞成,禁止转载