为什么需要脚手架
脚手架本质上是一个工具,使用脚手架的目的就是摆脱构建工程时重复性的工作,尤其是当一个工程具有一定通用性时,工程脚手架的意义就更为突出。它可以让我们只需要一行命令,就可以初始化好一项工程,而不用费心费力的去做配置环境,安装依赖,解决依赖冲突这样的支线任务,可以直奔主线任务,早早下班~~
概览
开发一个的脚手架,通常需要如下npm包:
- commander: 强大的node命令行处理工具。能轻松的获取命令行的参数。
- inquirer: 命令行交互工具,让你能以“问答”的交互方式来完成一系列的命令行操作。
- download-git-repo: git仓库下载工具,通常用来下载模板代码。
- ora: 终端loading美化工具。
- chalk: 命令行输入/输出美化工具,想要五彩版本的命令行,选它就对了。
- handlebars: 模板引擎
实现的功能:
- 一条简单的命令初始化项目
- 提供友好的交互体验
- 可选择安装不同模板
- 自动安装项目依赖
开始干活
STEP1: 打开一个终端,在你喜欢的地方新建一个空项目
mkdir meo-cli
cd meo-cli
在项目更目录下执行:
npm init -y
你会得到一个package.json
{
"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.js
{
"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文件,然后狠狠敲入下面的代码:
#!/usr/bin/env node
console.log('helo, world!')
然后在命令行执行 meo-cli
, 不出意外的话,你应该看不到输出hello,world。只会有个报错提示:
zsh: command not found: meo-cli
这是为什呢?因为我们并没有安装对应的包,当然就不会链接到全局啦。一个办法是发包,然后安装到本地,就可以了。但是这样太麻烦,难道每次调试都要发包??
有没有更优雅的方法呢?答案是:有 npm早就想好了对策,就是npm link,它可以把指定的执行文件链接到全局,使用也非常简单,在项目根目录下执行:
npm link
就可以了。如果你执行命令后,显示类似安装npm包的提示,就说明链接成功了。在执行一下 meo-cli
,就可以看到令人欣慰的hello world了。这里要注意一下首行的代码
#!/usr/bin/env node
这段并不是注释,这段代码是告诉你的脚本工具(bash/zsh), 下面的内容是要在node环境下运行的代码。千万不能省略!!!进行下一步之前,让我们下来回顾一下vue-cli脚手架是怎么使用的。
可以看出来,脚手架会提供一个问答交互的方式,让使用者输入或选择参数,然后根据获取的参数做出相应的动作(action)。
STEP3: 交互式脚手架的另一个特点是灵活,使用者可以根据自己的需求,可以清晰的去选择让脚手架做什么,不做什么。要实现这样的功能,就是要用到开头提到的插件:
- commander
- inquirer
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);
}
至此,一个简单的工程脚手架就完成了。
小结
- 本篇文章按照step by step的方式,在每一步详细的阐述了开发脚手架过程中使用的工具和注意事项。
- 本篇文章阐述的方法只实现了基础功能,好的脚手架还可以做更多,如集成单元测试,第三方库的选择安装,项目打包发布等。更多符合项目特点的脚手架还需要根据实际项目去开发。
未经同意,禁止转载
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!