之前学习了怎么在npm上传自己的模块,现在可以开发自己的脚手架了。
1、结构介绍
模块目录基本结构如下:
/bin # ------ 命令执行文件
/lib # ------ 工具模块
/template # ------ 模板
package.json
可以先不用在意这个结构。
2、实现命令行的操作
1)新建package.json
$ md my-cli // 创建插件项目文件夹
$ cd my-cli // 进入新建项目目录
$ npm init -y // 创建package.json文件
根目录下得到package.json文件,修改如下:
{
"name": "my-cli",
"version": "1.0.0",
"description": "",
// "main": "index.js",
"bin": { // main入口修改为bin入口
"my-cli": "./bin/cli.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
2)新建入口文件cli.js
根目录新建./bin/cli.js文件,如下:
#!/usr/bin/env node
console.log("my-cli");
此时根目录运行如下:
$ node ./bin/cli.js
my-cli
得到my-cli的信息,表示运行成功。
文件开头的 #!/usr/bin/env node 是必须的。详情见https://www.npmjs.cn/files/package.json/#bin
3)使用commander开发命令行工具
生产环境安装commander,如下:
$ npm i commander -S
注册命令行
./bin/cli.js文件修改如下:
#!/usr/bin/env node
const program = require('commander'); // npm i commander -S
program.version('0.0.1')
.usage('<command> [项目名称]')
.command('init', 'init')
.parse(process.argv)
此时需要新增./bin/cli-init.js文件如下:
#!/usr/bin/env node
console.log('init');
此时根目录运行如下:
$ node ./bin/cli.js init
init
得到init的信息,表示命令行工具注册成功。
获取命令行输入的内容
./bin/cli-init.js文件修改如下:
#!/usr/bin/env node
const program = require('commander')
const path = require('path')
program.usage('<project-name>').parse(process.argv)
let projectName = program.args[0] // 根据输入,获取项目名称
let rootName = path.basename(process.cwd()); // 获取当前进程的根目录名称
console.log(projectName, rootName);
验证:
$ node ./bin/cli.js init my-project
my-project my-cli
官方中文文档:Commander开发命令行工具
4)使用inquirer命令行交互工具
生产环境安装inquirer,如下:
$ npm i inquirer -S
./bin/cli-init.js文件修改如下:
#!/usr/bin/env node
const program = require('commander')
const path = require('path')
const inquirer = require('inquirer') // 新增引入inquirer
program.usage('<project-name>').parse(process.argv)
let projectName = program.args[0]
let rootName = path.basename(process.cwd());
console.log(projectName, rootName);
// 新增命令行交互
inquirer.prompt([
{
name: 'projectName', // 参数名称
message: '项目的名称', // 信息提示
default: projectName // 默认值
}, {
name: 'projectVersion',
message: '项目的版本号',
default: '0.0.1'
}, {
name: 'projectDescription',
message: '项目的简介',
default: `A project named ${projectName}`
}
]).then(answers => {
console.log(answers); // 打印输入参数
})
运行,输出如下:
$ node ./bin/cli.js init my-project
my-project my-cli
? 项目的名称 my-project
? 项目的版本号 0.0.1
? 项目的简介 A project named my-project
{
projectName: 'my-project',
projectVersion: '0.0.1',
projectDescription: 'A project named my-project'
}
以上,就得到了几个重要的参数:项目名称、项目更目录名称、用户录入参数。简单的实现了,node命令行的操作。
3、脚手架本地运行和测试
目前一直是$ node ./bin/cli.js init my-project的方式在运行脚本,体验并不好,没有脚手架的使用效果。我们现在可以进行本地包的运行和测试了。
1)安装my-cli脚手架到全局
my-cli根目录运行如下:
$ npm link
此时脚手架被安装到全局,可以使用了。
推荐阅读:npm link的使用
npm unlink // 卸载link安装
2)新建一个项目
在其它目录下新建一个项目如下:
$ md my-project // 创建项目文件夹
$ cd my-project // 进入项目文件夹
$ my-cli init my-project // 运行脚手架
my-project my-project
? 项目的名称 my-project
? 项目的版本号 0.0.1
? 项目的简介 A project named my-project
{
projectName: 'my-project',
projectVersion: '0.0.1',
projectDescription: 'A project named my-project'
}
得到如上信息,代表脚手架运行成功!
这篇文章的目的是为学习,为了最简单的理解脚手架是怎么工作,用什么实现的,所以这里是没有做校验逻辑。
4、复制模板文件到项目
只是单文件的复制,使用node的fs就能完成,但模板文件一般都是文件夹的多文件复制,所以我使用mvdir模块来复制文件夹,很好用。
1)新建模板文件
my-cli根目录下,新建template文件夹,新建模板文件package.json,也可以是任意你想复制的文件。
my-cli/template/package.json文件如下:
{
"name": "{{projectName}}",
"version": "{{projectVersion}}",
"description": "{{projectDescription}}",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
{{}}内的是模板变量占位符。
2)mvdir复制模板文件
my-cli根目录安装mvdir,如下:
$ npm i mvdir -S
./bin/cli-init.js文件修改如下:
……
const mvdir = require('mvdir') // 引入模块
……
]).then(answers => {
console.log(answers);
// 使用mvdir复制template文件夹内容到my-project项目的.download-temp文件夹下。.download-temp为临时文件夹,编译模板后会删除。
mvdir(path.join(__dirname, '../template'), '.download-temp', { copy: true }).then((err) => {
if (err) {
console.log(err);
} else {
console.log('复制成功');
}
});
})
my-project项目根目录下,运行:
$ my-cli init my-project
得到复制成功,就可在项目下看到复制的模板文件。
目前模板文件很小,所以放在了npm包里。如果模板文件很大,不适合放在npm包里的时候,可以将模板文件放在git上,然后使用download-git-repo工具,下载git上的模板文件到.download-temp的临时文件夹下。
5、使用Metalsmith编译模板文件
metalsmith就是一个静态网站生成器,可以用在批量处理模板的场景。handlebars是metalsmith的超集。
生产环境安装metalsmith和handlebars,如下:
$ npm i metalsmith handlebars -S
./bin/cli-init.js文件修改如下:
……
const Metalsmith = require('metalsmith') // 引入静态网站生成器
const Handlebars = require('handlebars') // 引入模板引擎
const rm = require('rimraf').sync
……
} else {
console.log('复制成功');
const src = `${process.cwd()}/.download-temp`;
Metalsmith(process.cwd())
.metadata(answers) // 全局元数据
.clean(false) // 是否删除
.source(src) // 编译来源路径
.destination('.') // 编译目标路径
.use((files, metalsmith, done) => { // 自定义插件
const meta = metalsmith.metadata()
Object.keys(files).forEach(fileName => {
const t = files[fileName].contents.toString()
files[fileName].contents = Buffer.from(Handlebars.compile(t)(meta))
})
done()
})
.build(err => {
rm(src)
if(err) {
console.log(err);
} else {
console.log('脚手架运行成功');
}
})
}
……
my-project下运行:
$ my-cli init my-project
得到“脚手架运行成功”的提示,至此npm脚手架的基本功能就完成了。
目前还没有使用lib文件夹,后续的开发中,需要对用户输入的校验、模板编译文件的筛选、异步操作队列化等功能,都可封装到lib工具模块中使用,减小bin文件的体积,提高可复用性。
扩展阅读:metalsmith-api、handlebars
本人基于此文章《基于node.js的脚手架工具开发经历》学习,学习后重新整理思路,编写此文章留作笔记。