在日常的开发中我们经常会用到一些脚手架,像create-react-app、vue-cli等,用于方便我们项目的快速搭建和基本规范的约定。
本文将通过搭建一个简易的脚手架name:simple-create-react(简化版的create-react-app,可选一些常用库),来介绍脚手架的搭建过程,整个过程的核心是命令行问询过程+模板文件生成。
一、初始化包
npm init -y
在package.json
中增加两项
"bin":{
testCli:"./main.js"
}
"private":false
bin
目录下的testCli
表示,我们在命令行输入testCli
则会调用main.js
内的内容,
private:false
表示内容是公开的,即可以被npm发布的
在main.js中增加
#!/usr/bin/env node
其中
#!/usr/bin/env node
的作用就是这行代码是当系统运行到这一行的时候,去env
中查找node
配置,并且调用对应的解释器来运行之后的node
程序
然后我们需要测试代码,这里可能需要用到 npm link 或者 npm install -g 。这样在命令行执行testCli就可以看到你在main.js中写入的代码,这里写一个hello world
console.log("Hello World")
npm link: 将当前package链接到全局 —— npm unlink
npm install -g: 将当前package安装到全局 —— npm uninstall -g
二、增加功能
交互命令—— 使用commander
const { program } = require('commander')
program
.version('0.1.0') // --version 版本
.command('init <name> [branch]') // 初始化命令
.description('初始化项目文件')
.action( (name,branch) => { // 得到name
console.log('Hello World') // 进行输出
})
program.parse(process.argv) // 解析变量
The arguments may be
<required>
or[optional]
,尖括号必须,optional可选
文件拉取 —— 使用download-git-repo
download("direct:https://github.com/oniya24/simple-create-react_template.git",
targetPath,{ clone:true }, // 目标路径
(err)=>{ // 回调函数
console.log(err)
}
坑:'git clone' failed with status 128 通过git clone拉取出错
将git改为通过 direct: http:// URl的方式进行clone
命令行交互 —— 使用inquirer
program
.version('0.1.0')
.command('init <name>')
.description('初始化模板')
.action( (name) => {
inquirer.prompt([
{
name:"description",
message:"请输入描述"
},{
name:"author",
message:"请输入作者"
}
]).then((paramater)=>{
console.log(paramater);
})})
prompt
提交一个询问数组,name
是key值,message
是输入时的提示,
返回一个对象,返回上述字段输入的value,{ description: xxx, author: yyy}
替换模板内容 —— 使用 handlebars
// 输入源模板
const source = "<p>description is {{description}} , author is {{ author }}</p> ";
// 进行模板的解析
const template = Handlebars.compile(source);
// 内容的定义
const paramater = { description: 'xxx', author: 'yyy' };
// 进行内容的替换
console.log(template(paramater));
// description is xxx,author is yyy
由这个模板我们可以替换掉配置文件的内容,所以我们需要将模板的package.json
进行改造,然后下载模板成功后,通过inquirier
查询到的变量,进行内容的替换
// package.json
author: "{{ author }}",
description: "{{ description }}"
拉取到代码之后,通过fs
模块首先判断是否有package.json
文件,如果有则读取文件内容,对文件内容通过handlebars
进行内容的替换,然后再写入文件中。
const content = fs.readFileSync(packagePath).toString();
const template = Handlebars.compile(content);
const result = template(paramater);
fs.writeFileSync(packagePath,result);
美化命令行 —— 使用chalk ora
高亮终端打印出来的信息:chalk。
终端加载效果:ora, 在代码拉取过程有加载的效果
const spinner = ora("模板下载中^.^ 请稍后");
spinner.start();
spinner.success();
chalk.green('成功了');
chalk.red('失败了')
整体代码
静态拉取代码见:github.com/oniya24/sim… 的staticFillPull的文件
三、可选择配置项
我们发现基础功能已经实现了,但是还存在两个问题:
- 拉取的是静态代码,对于一些我们不需要的配置,如react-router、loadsh无法选择
- 拉取代码后还需要自己进行
npm install
,不能直接使用
如何实现按需添加 —— inquirer的type + handlebars的判断
查阅资料后发现,我们的inquirer功能比较强大,还可以通过更改type
,来支持confirm
判断或者list
选择,所以选择的部分我们就可以用inquirer
来实现,然后根据我们查询得到的结果进行package.json
的更改。
Confirm -
{type: 'confirm'}
Take
type
,name
,message
, [default
] properties.default
is expected to be a boolean if used.
在package.json
中我们需要用到模板的写法,这里再次查阅handlebars
的文档,发现它支持判断语句(nice!)
{{#if isReactRedux}}
"react-redux": "^7.2.0",
{{/if}}
{{#if isReactRouterDom}}
"react-router-dom": "^5.1.2",
{{/if}}
{{#if redux}}
"redux": "^4.0.5",
{{/if}}
这样可选配置的部分我们就完成了,大家可以根据自己的需要来增删配置。
如何实现node命令的使用 —— child_process
process.cwd()
返回当前执行目录
使用child_process
的spawn()启动一个子进程执行命令,参数(command, [args], [option])
这里增加了三个参数,cwd
命令执行目录,stdio:"inherit"
继承父进程的输入输出流,shell:在shell下执行
与exec()方法均开启一个子进程执行命令,不同的是exec()有一个回调函数货值子进程的状态
const spawn = require('child_process').spawn;
const chalk = require("chalk");
let runInstall = async function(cwd,callback,executable = 'npm',args = ['install']){
console.log(chalk.greenBright("正在安装项目依赖……\n"));
// 执行executable args 对应到默认参数即 npm install 安装依赖
await new Promise((resolve, reject) => {
const installProcess =
spawn(executable, args, { cwd: cwd,stdio: "inherit", shell: true });
//文件目录, 继承父进程的输入输出【即控制台可以看到子进程输出】,开启shell
installProcess.on('exit', () => {
console.log(chalk.greenBright("依赖安装完成!"));
resolve();
});
installProcess.on('error',(err)=>{
console.log(chalk.red("依赖安装失败"));
reject(err);
})
}).then(()=>{ callback(); })
.catch((err)=> { console.log(chalk.red("出现错误"));console.log(err) });
}
四、打包发布
--- main.js
--- utils
------ outputFunc
------ npmInstall
------ inquirerArr
增加了重名文件判断,再整体再进行一下结构优化,就可以准备发布啦!
配置更改
发布前要对package.json的配置进行更改
name: 主要是包名,必须唯一
version: 版本号,每次发布至 npm 需要修改版本号
description: 描述。
main: 入口文件,import/require的
keyword:关键字,以空格分离希望用户最终搜索的词。
author:作者
private:是否私有,需要修改为 false 才能发布到 npm
发布
npm login
登录
npm publish packageName
发布
可能存在的问题
1.报错 Private mode enable, only admin can publish this module [no_perms] Private mode enable, only admin can publish this module: clitest
错误:403
这是因为我们设置了淘宝镜像,https://registry.npm.taobao.org/,将registry设置为原来的镜像
命令:npm config set registry=http://registry.npmjs.org
2. 报错If you believe this might be a permissions issue, please double-check the
npm ERR! permissions of the file and its containing directories, or try running
npm ERR! the command again as root/Administrator (though this is not recommended).
清理下缓存:删除C:\Users\Administrator\下的.npmrc文件
然后再次登录
(还有一个原因可能是忘记登录了,捂脸QAQ)
3. 打包使用的时候,要将__dirname 替换为 process.cwd()
发布成功后,就可以快乐的使用啦!
npm install -g simple-create-react
全局安装
simple-create-react init projectName
拉取模板进行初始化
如图:
五、总结
- 通过commander实现命令行的交互
- 通过download-git-repo实现仓库代码的拉取
- 通过inquirer实现配置的选择和相关项的填写,实现可选配置
- 通过handlebars实现对package.json模板的替换
- 通过chalk和ora对命令行的输出进行美化
- 通过child_process模块开启子进程,实现依赖的npm install