前言
一说起脚手架,你肯定会想到vue-cli。有些人看到vue-cli那个酷炫的功能和交互就会打退堂鼓。如果我告诉你,我们只需要具备nodejs基础就能写出vue-cli那样的功能来,你是不是稍微会兴奋点呢?接下来跟着我的步伐一点一点揭开脚手架的面纱吧。
准备工作
首先我们的电脑需要有nodejs开发环境,下载安装请点击这里(已有环境请省略):nodejs;
cmd输入node -v
,出现下图版本号即安装成功。
开发步骤
初始化项目
npm init
初始化一个项目;初始化成功之后会根据输入的配置生成一个package.json
文件:
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
添加脚手架命令和命令入口文件(test)
脚手架命令:在package.json
中添加bin配置来指定自己定义的命令对应的可执行文件的位置,代码如下:
{
"name": "test",
"version": "1.0.0",
"description": "",
"bin": {
"test": "bin/test.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
命令入口文件:根据package.json
中的bin配置,我们在项目根目录下新增bin文件夹,并且在该文件夹下新建·test.js
,代码如下:
#!/usr/bin/env node
console.log('test')
第一行是必须添加的,是指定这里用node解析这个脚本。默认找/usr/bin
目录下,如果找不到去系统环境变量查找。
这时我们cmd
输入test
命令,你会发现报错了:
为什么找不到test命令呢?让我们先来理解下为啥我们安装了vue-cli之后,输入vue就不报错呢?带着疑问打开我们
npm
全局安装的文件目录:
请留意上图红色的地方,我们会发现我们熟悉的老朋友也在里头:
cnpm,vue
。这里我们先留下悬念,先回到刚才的脚手架项目。
link到全局:项目根目录下执行如下命令npm link
,执行成功之后我们再查看我们全局npm的目录,这个时候我们会发现,多了几个项,细心地你肯定会发现,多出来的文件命名就是我们在package.json
中配置的bin
键:
这个时候我们再次打开cmd
输入test
试下:
对比上面失败的效果,你会发现这次系统识别了test
命令,而且控制台输出了test
。到这里上面的疑问应该都解开了:执行npm link,将当前的代码在npm全局目录下留个快捷方式,npm检测到package.json里面存在一个bin字段,它就同时在全局npm包目录下生成了一个可执行文件:
-
npm link
,链接到全局 -
bin
配合npm link,生成命令文件 -
"test": "bin/test.js"
,cmd
执行test
,根据配置会去执行bin/test.js
这个文件
添加命令
别人脚手架有的功能,我们也得有,例如:-V(--version) -H(--help)
等。
commander
首先我们先安装下需要的commander库,然后为我们的test
命令添加最基础的版本号查看命令:
#!/usr/bin/env node
const program = require('commander')
program
.version(`version is ${require('../package.json').version}`)
.parse(process.argv)
这里需要注意的是末尾的.parse(process.argv)
的代码不能丢,如果没有则解析不了我们输入的参数,效果如下:
此外还可以添加.description .usage .option .action .command
等;下面我们来重点说下.command和.action
方法:其中,command允许我们注册一个命令(类似于vue create
),代码如下:
program
.version(`Version is ${require('../package.json').version}`)
.description('A simple CLI for building initialize project include Wechat applet, Vue, Egg (nodejs)')
.usage(' [options]')
.command('create')
.option("-l --list", "project list", listOption)
.action((name, cmd) => {
console.log(111)
require('../lib/create')
})
.parse(process.argv)
终端输入test create
查看效果:
我们注册的create命令,配合action方法打印出来
111
,其中我们可以在action
中执行别的js文件,模块化开发我们的create
命令,如下代码输入text create
将会去执行lib/create.js
:
.action((name, cmd) => {
require('../lib/create')
})
参数解析:
const program = require('commander')
const inquirer = require('inquirer')
program.usage('').parse(process.argv)
console.log(program.args)
控制台输入test create saa dfasas
:
更多使用参考:commander
chalk
chalk包的作用是修改控制台中字符串的样式:例如字体样式,字体颜色,背景颜色等。
更多使用参考:chalk
log-symbols
为各种日志级别提供着色的符号(类似下载成功的√)
更多使用参考:log-symbols
ora
显示下载中,可以设置样式等;有start,fail,succeed方法等。
inquirer命令交互
这个库是我们可以和用户交互的工具;我们定义了两个问题:项目名称和版本号,create.js
中写入以下代码:
const inquirer = require('inquirer')
getInquirer().then((res) => {
console.log(res)
})
function getInquirer() {
return inquirer.prompt([
{
name: 'projectName',
message: 'project name',
default: 'project',
},
{
name: 'projectVersion',
message: '项目版本号',
default: '1.0.0',
},
])
}
终端执行test create
,上面的代码打印了执行后的拿到的参数如下图所示:
download-git-repo
该模块用于下载git上的模板项目,类似于vue-cli初始化一样,也是在git上下载一份代码回来本地完成开发环境搭建。下面代码封装了下载的代码,作用是根据传入的git地址克隆目标地址的代码到本地:
const ora = require('ora');
const chalk = require("chalk");
const logSymbols = require('log-symbols');
const download = require('download-git-repo');
const {
spawnSync
} = require("child_process");
module.exports = function (target, downLoadURL) {
let {
error
} = spawnSync("git", ["--version"]);
if (error) {
let downurl = downLoadURL.replace("direct:", "");
console.log(logSymbols.warning, chalk.yellow("未添加Git环境变量引起,添加Git与git管理库的环境变量即可;"))
console.log(logSymbols.info, chalk.green('或直接到模板地址下载:', downurl));
return Promise.reject(error);
}
return new Promise((resolve, reject) => {
const spinner = ora(`正在下载模板`)
spinner.start();
console.log(downLoadURL)
download(downLoadURL, target, {
clone: true
}, (err) => {
if (err) {
let errStr = err.toString()
spinner.fail();
reject(err);
if (errStr.includes("status 128")) {
console.log('\n', logSymbols.warning, chalk.yellow("Git默认开启了SSL验证,执行下面命令关闭后再重试即可;"))
console.log(logSymbols.info, chalk.green("git config --global http.sslVerify false"))
}
} else {
spinner.succeed();
resolve(target);
}
})
})
}
下载模板时校验输入的文件目录名:
function checkProjectName(projectName) {
let next = null;
if (list.length) {
if (
list.filter((name) => {
const fileName = path.resolve(process.cwd(), path.join(".", name));
const isDir = fs.statSync(fileName).isDirectory();
return name.indexOf(projectName) !== -1 && isDir;
}).length !== 0
) {
console.error(logSymbols.error, chalk.red(`项目${projectName}已经存在`));
return Promise.resolve(false);
}
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);
}
return next;
}
child_process
这是nodejs自带的一个包,用于开启子线程,非常强大,详情可以参考这里;这里利用spawnSync
方法校验本地是否有git
环境:
const { spawnSync } = require("child_process");
let { error } = spawnSync("git", ["--version"]);
到这里你已经掌握开发一个脚手架的必备技能了,行动起来吧!
(如何本文对你有启发的话,请给个赞吧,撸字造图不易)