你可能用到过不少前端脚手架工具,有没有试想过到底如何写一个属于你的脚手架呢?javascript
commander.js 命令行工具前端
download-git-repo git仓库代码下载java
chalk 命令行输出样式美化node
Inquirer.js 命令行交互react
ora 命令行加载中效果git
建立项目目录后执行npm init
按照提示完成初始化项目。github
安装上面咱们提到过的这几个脚手架依赖工具,执行npm install chalk commander download-git-repo inquirer ora --save
完成npm
安装。json
项目初始化完成后,建立bin
文件和commands
文件。bin文件为可执行命令入口目录,commands则负责编写一些命令交互。后端
#!/usr/bin/env node
process.env.NODE_PATH = __dirname + '/../node_modules/'
const { resolve } = require('path')
const res = command => resolve(__dirname, '../commands/', command)
const program = require('commander')
program.version(require('../package').version )
program.usage('<command>')
program.command('init')
.option('-f, --foo', 'enable some foo')
.description('Generate a new project')
.alias('i')
.action(() => {
require(res('init'))
})
if(!program.args.length){
program.help()
}
复制代码
建立该react-cli.js
为可执行命令入口文件,而且定义了一个'init'命令,执行命令后会去commands 目录下寻找对应的init.js
文件
const {prompt} = require('inquirer')
const program = require('commander')
const chalk = require('chalk')
const download = require('download-git-repo')
const ora = require('ora')
const fs = require('fs')
const path = require('path')
const option = program.parse(process.argv).args[0]
const defaultName = typeof option === 'string' ? option : 'react-project'
const tplList = require(`${__dirname}/../templates`)
const tplLists = Object.keys(tplList) || [];
const question = [
{
type: 'input',
name: 'name',
message: 'Project name',
default: defaultName,
filter(val) {
return val.trim()
},
validate(val) {
const validate = (val.trim().split(" ")).length === 1
return validate || 'Project name is not allowed to have spaces ';
},
transformer(val) {
return val;
}
}, {
type: 'list',
name: 'template',
message: 'Project template',
choices: tplLists,
default: tplLists[0],
validate(val) {
return true;
},
transformer(val) {
return val;
}
}, {
type: 'input',
name: 'description',
message: 'Project description',
default: 'React project',
validate (val) {
return true;
},
transformer(val) {
return val;
}
}, {
type: 'input',
name: 'author',
message: 'Author',
default: 'project author',
validate (val) {
return true;
},
transformer(val) {
return val;
}
}
]
module.exports = prompt(question).then(({name, template, description, author}) => {
const projectName = name;
const templateName = template;
const gitPlace = tplList[templateName]['place'];
const gitBranch = tplList[templateName]['branch'];
const spinner = ora('Downloading please wait...');
spinner.start();
download(`${gitPlace}${gitBranch}`, `./${projectName}`, (err) => {
if (err) {
console.log(chalk.red(err))
process.exit()
}
fs.readFile(`./${projectName}/package.json`, 'utf8', function (err, data) {
if(err) {
spinner.stop();
console.error(err);
return;
}
const packageJson = JSON.parse(data);
packageJson.name = name;
packageJson.description = description;
packageJson.author = author;
var updatePackageJson = JSON.stringify(packageJson, null, 2);
fs.writeFile(`./${projectName}/package.json`, updatePackageJson, 'utf8', function (err) {
if(err) {
spinner.stop();
console.error(err);
return;
} else {
spinner.stop();
console.log(chalk.green('project init successfully!'))
console.log(` ${chalk.bgWhite.black(' Run Application ')} ${chalk.yellow(`cd ${name}`)} ${chalk.yellow('npm install')} ${chalk.yellow('npm start')} `);
}
});
});
})
})
复制代码
1.program.parse(process.argv)
能够解析执行init 时候传入的参数, 咱们能够拿到这个参数作为项目建立的目录名,若是没有传入该参数则为其设置一个默认目录名称。
2. 命令行交互问答
question
数组为交互命令配置,数组中每个对象都对应一个执行命令时候的一个问题type
为该提问的类型,name
为该问题的名字,能够在后面经过name拿到该问题的用户输入答案message
为问题的提示default
则为用户没输入时的默认为其提供一个答案validate
方法能够校验用户输入的内容,返回true时校验经过,若不正确能够返回对应的字符串提示文案transformer
为用户输入问题答案后将对应的答案展现到问题位置,须要有返回值,返回到字符串为展现内容 具体使用文档3. 问答结束的回调
download-git-repo
工具来下载git仓库代码'Hzy0913/react-template'
即为下载该仓库master的代码,若是须要切换对应分支则在仓库地址后面加入对应分支名,如'Hzy0913/react-template#complete'
。'./projectName'
,便可在当前执行命令目录下生成对应的文件名。ora
模块能够为咱们生成下载时候的旋转图标,ora方法传入的第一个参数为等待时候的提示文案并生成实例,在实例对象上调用start()方法开始出现旋转动画和提示,stop()方法中止。package.json
文件生成用户自定义输入的内容,node的fs
模块的readFile方法能够帮助咱们获取生成文件的内容,writeFile则能够写入内容chalk
模块能够帮助咱们美化输出内容。文章中脚手架示例代码能够见 build-react-cli
node bin/react-cli
运行。package.json
中的bin
对象,key为脚本执行的名字,value为执行目录,如"bin": {"build-react": "bin/react-cli"}
,便可在输入build-react的时候等同于执行 node bin/react-cli
命令,在咱们全局安装脚手架的时候,bin对象里面的内容便可变成全局可执行命令。npm publish
便可发布,注意包名不能与现有的npm里的相同、每次发布新版本的包时须要修改package.json
里的版本号,发布的包只有在24小时内能够删除。