基本使用方式:
mycli create projectName
mycli add test
mycli add test -u aa/bb/cc
1.首先了解什么是脚手架?
网上一搜基本上很多讲解应该是都能看明白的,我就说一下我的理解吧!
前端脚手架就是一个用前端代码写的一个工具,其目的就是减少重复性的工作,提高开发者的开发效率。比如我们常用的vue-cli,create-react-app等都是脚手架,能够在项目前期快速搭建一个项目的模板,不懂的童鞋,自己理解一下吧~
2.搭建脚手架雏形
- 新建一个空文件夹
- npm init生成package.json文件
- 根目录新建一个bin/index.js作为入口文件
- 根目录新建lib文件夹存放脚手架相关代码
- npm link
- 测试命令是否正确link 以上为基本步骤
- 编辑bin/index.js
#! /usr/bin/env node是默认要求的,用系统中的node环境去执行这个文件
#! /usr/bin/env node
console.log('hello')
- package.json新增属性bin
mycli这个是你的脚手架命令,可更改
"bin": {
"mycli": "./bin/index.js"
},
- 控制台执行npm link 命令,将mycli命令绑定到全局
- 测试命令是否正确link,出现hello证明没有问题啦
3.分步实现脚手架
3.1 配置create命令
yarn add commander,需要用到commander库解析用户传递的参数,配置脚手架命令 lib/index.js
const _package = require('../package.json');
// 解析用户的参数
const program = require('commander');
// 用法 mycli create projectName
program
.command("create <project>") //配置命令
.alias("C") //设置别名
.description('create a project') //命令的描述
.action((project,moreParams) => { //命令的动作
console.log('这里是这个命令具体需要执行的事情')
console.log('命令上的项目名称project',project)
console.log('命令上的其他参数',moreParams)
// .....
})
// 解析用户传递过来的参数
program
.version(_package.version) //脚手架的版本
.parse(process.argv);
到这里,一个简易命令已经生成啦~,后续只需要关注action中的执行事件
3.2 完成cerate命令执行事件
效果是:用户在终端输入 mycli create test命令创建项目后,会出现交互式命令行选择需要生成的模板,选择完成之后从gitlab或者github上拉取对应的资源,并且在当前命令行所执行的目录上生成一个test项目。
这里我们需要安装的依赖为以下三个:
"download-git-repo": "^3.0.2",
"inquirer,": "^8.2.0",
"ora": "3.4.0"
inquirer为交互式命令在终端选择,ora可以在终端的输出内容并且有loading效果,chalk是ora插件附带安装的,可以实现终端输出的内容颜色更改等,download-git-repo可以在gitlab或者github上拉取代码 不会用的可以下去自己看看官方使用方式哟~
lib/index.js
修改之前的内容
program
.command('create <project>') // 配置命令
.alias('C') // 设置别名
.description('create a folder') // 命令的描述
.action((project,moreParams) => { // 命令的动作
+ const actionFn = require(path.resolve(__dirname, 'actions/createAction'));
+ actionFn(project,moreParams)
});
lib/actions/createAction.js
// mycli create projectName
const ora = require('ora');
const chalk = require('chalk');
const Inquirer = require('inquirer');
// node库提供的可以将一个函数封装成promise形式
const { promisify } = require('util')
const downloadGitRepo = promisify(require('download-git-repo'))
const questionList = [
{
type: 'list',
message: '请选择想生成的模板:',
name: 'template',
choices: ['create-react-app', 'Micro application', 'umi'],
},
];
const downloadGitUrlMap = {
"create-react-app":"direct:http://git.xxx.com/xxx.git",
"Micro application": "direct:http://git.xxx.com/xxx.git",
"umi": "direct:http://git.xxx.com/xxx.git",
}
const createActions = async (projectName,moreParams) => {
// 获取当前命令所在目录的地址
const projectPath = process.cwd()+`/${projectName}`
// 设置控制台的交互式命令
const answer = await Inquirer.prompt(questionList);
// 设置等待提示语
const spinner = ora('正在拉取模版中...\n').start();
// 拉取模版代码
/*
// node中的fs文件系统
const { rm,stat } = require('fs/promises');
处理重复目录拉取模板的兼容问题,我就不写啦
根据你自己的需求通过stat先判断projectPath是否存在
存在就给删除 await rm(projectPath,{ recursive: true, force: true }),再拉取模板
但是存在就rm有点隐患就是可能删除有意义的同名文件,建议可以通过inquirer询问一下是否删除
*/
try {
await downloadGitRepo(downloadGitUrlMap[answer.template],projectPath,{ clone: true })
// 结束提示
spinner.succeed(`${chalk.green(`${projectName}项目已创建成功`)}`);
} catch (error) {
console.log(error)
spinner.fail(`${chalk.red(`${projectName}项目创建失败,建议检查是否已存在${projectName}项目`)}`);
}
};
module.exports = createActions;
附上效果图:
3.3 完成add命令
效果是:mycli add test 或者 mycli add test -u aa/bb/cc 命令,在对应的目录下生成test文件,且里面包含了相对于的内容。比如index.tsx和index.less
yarn add ejs ,解析模板内容
lib/index.js新增命令
// 用法 mycli add folderName 或者 mycli add test -u aa/bb/cc
program
.command('add <folderName>')
.alias('A')
.option('-u <url>','where do you want to add a folder')
.description('add a folder')
.action((project,moreParams) => {
const actionFn = require(path.resolve(__dirname, 'actions/addAction'));
actionFn(project,moreParams)
});
新增 lib/template/index.tsx.ejs模板
import React from 'react'
import './index.less'
const <%= name %> = () => {
return (
<div className="<%= name %>box">
</div>
)
}
export default <%= name %>
新增 lib/template/index.less.ejs
.<%= name %>box{
}
增加 lib/actions/addAction.js
const { promisify } = require('util')
const path = require('path')
const fs = require('fs')
const ejs = require('ejs')
const renderFile = promisify(ejs.renderFile)
// 递归创建目录
const mkdirFn = async (_path) => {
// 是否存在该目录
try {
fs.statSync(_path).isDirectory()
return true
} catch (error) {}
//不存在就递归创建,请熟悉path.dirname的用法
if (mkdirFn(path.dirname(_path))) {
fs.mkdirSync(_path)
return true
}
}
const addAction = async (project,moreParams) => {
// 获取当前命令所在地址
let currentPath = process.cwd()
// 如果设置了创建路径,修改currentPath
if(moreParams.u){
currentPath = path.join(currentPath,moreParams.u)
}
//创建目录
await mkdirFn(`${currentPath}/${project}`)
//读取ejs模板
const templateTsx = await renderFile(process.cwd()+'/lib/template/index.tsx.ejs',{name:project})
const templateLess = await renderFile(process.cwd()+'/lib/template/index.less.ejs',{name:project})
// 写入文件
fs.writeFileSync(`${currentPath}/${project}/index.tsx`,templateTsx)
fs.writeFileSync(`${currentPath}/${project}/index.less`,templateLess)
}
module.exports = addAction
以上可以根据实际的业务情况完善相应的内容,有时间可以了解以下插件的使用方式,内容很多