[aru_53]
写在前面
前端是一个技术问题较少,工程问题较多的开发领域,没有一个框架可以满足所有的业务场景,为了让公司的前端开发流程自成体系,也为了避免重量级的前端框架,
我写了很多维护性,可用性较强的项目模板(包括uni-app,electron,vue3+vite,Chrome扩展等...),每次有新项目就clone这些项目模板在该基础上进行开发,
虽然vue-cli/create-react-app等脚手架已经提供了完整的项目架构,但在工程化的路上,初始化项目还远远不够...
能够在适合公司业务的现有模板上进行开发,可以省下不少时间和精力.
dt-cli(指下文中即将完成的cli小工具,dt是公司名缩写)可以减少开发人员在拉取模板仓库上的操作维度,只需一句命令行即可,
包括项目模板需要填写一些配置信息各种参数,仓库分支等可以在cli工具中写好以免同事弄错,拉取完成后自动安装项目依赖,爽歪歪[aru_5]
在开始之前,如果你不熟悉npm包的制作发布和废弃,可以查看我之前的文章: npm包的发布/迭代和删除/废弃
发布时确保你使用的为npm
官方源
项目github地址(dt-cli-template分支为本次实例代码):https://github.com/zcjunblog/dt-cli/tree/master
项目npm地址: https://www.npmjs.com/package/@duotai/dt-cli
1.实现可执行命令
新建项目文件夹(这里以dt-cli为例),在根目录执行
npm init
填上项目描述名字和作者等
这里需要注意,如果你的项目名称比如我这里的dt-cli,已经被占用了,所以我采用的是以组织发布带命名空间@duotai的npm包
如果你的项目名称未冲突,可以无视这里:
所以我init的package.json文件是这样的:
{ "name": "@duotai/dt-cli", "version": "1.0.0", "description": "快速拉取项目模板的cli工具", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "bin": { "dt-cli": "./index.js" }, "author": "dt-zhaozc", "license": "ISC", }
name这里是带@/duotai的,组织需要你自行去你的npm账号创建,以获取可使用的命名空间
完成项目初始化后安装下面几个包
1.chalk,让你的 CLI 工具五颜六色; 2.figlet,让你的 CLI 显示一个漂亮的 logo; 3.commander,解析命令参数; 4.clear,清空控制台; 5.inquirer,交互式输入;
npm install chalk figlet commander clear inquirer
之后在项目根目录创建index.js, 这里我给出一个简单的模板,可以帮你省去了一些简单步骤,具体看代码注释
之后在该文件上继续编写index.js:
#!/usr/bin/env node // 指定运行环境为node const chalk = require('chalk'); const clear = require('clear'); const figlet = require('figlet'); const { Command } = require('commander'); const inquirer = require('./lib/create'); const packageJson = require('./package.json'); // 清除控制台 clear(); // 大logo 自行改文字,不支持汉字 console.log(chalk.cyan(figlet.textSync('DUOTAI', { horizontalLayout: 'full' }))); const program = new Command(); // 版本 program .version(packageJson.version) .description('Quickly create front-end projects') .usage('<commnad [options]>'); // 触发 --help 后打印一些信息 program.on('--help', () => { console.log(); console.log(` create by ${chalk.cyan('xx科技')} zhaozc.`); console.log(` more click ${chalk.red('https://x4v.cn/')}`) console.log(); }); program.commands.forEach(c => c.on('--help', () => console.log())); // 开始解析参数 program.parse(process.argv); // 无任何命令时输出帮助信息 if (!process.argv.slice(2).length) { program.outputHelp(); }
将上述内容复制到你的index.js后,在package.json中定义为一个命令
"bin": { "dt-cli": "./index.js" },
至此,已经实现了两个命令了: dt-cli --version 和dt-cli --help
此时在项目根目录调起终端,执行dt-cli(执行你写的指令),如果看到了一个大logo,那恭喜你,你第一步已经完成了,若报错请仔细阅读本文排查问题或拉取文中给出的dt-cli项目仓库源码进行对比
2.实现创建项目命令
commander
具体的用法: 官方文档
在index.js
中增加如下内容:
program .command('create <app-name>')// 定义create子命令,<app-name>可在action中接收 .description('create a new project') // 命令描述 .option('-u, --uni', '拉取uni-app项目基础模板') // 配置参数,简写和全写用,分割 .option('-v, --vue', '拉取桌面端vue项目基础模板') .option('-e, --electron', '拉取electron项目基础模板') .action((name, option) => { console.log('name', name) console.log('option.uni', option.uni) // 为true则说明携带参数为--uni })
添加后输入dt-cli create my-app --uni
console结果如下
name my-app option.uni true
之后就是编写输入create命令需要执行的操作,开头说过了,我要做的就是: 拉代码[aru_34]
为了方便维护,将action的操作拆分到lib
文件夹中
因为要从git拉取项目, 在编写create.js之前我们先利用download-git-repo封装一个clone
方法。
这里需要再装三个库:
npm install ora log-symbols download-git-repo
// utils/clone.js
const download = require('download-git-repo');
const ora = require('ora'); // 用于输出loading
const symbols = require('log-symbols'); // 用于输出图标
const chalk = require('chalk'); // 用于改变文字颜色
module.exports = function (remote, name, option) {
const downSpinner = ora('正在拉取基础模板...').start();
return new Promise((resolve, reject) => {
download(remote, name, option, err => {
if (err) {
downSpinner.fail();
console.log(symbols.error, chalk.red(err));
reject(err);
return;
};
downSpinner.succeed(chalk.green('基础模板下载成功!'));
resolve();
});
});
};
之后编写create.js, 安装shelljs
npm i shelljs
写入下面的代码,代码逻辑很简单,看下去就明白了
const shell = require('shelljs'); const fs = require('fs'); const symbols = require('log-symbols'); const clone = require('../utils/clone.js'); const remotes = { // git克隆地址以及分支 格式:<host>:<userName>/<repo> <projectName> #branchName uni: {host:'github.com', userName:'zcjunblog', repo: 'uni-app-uView-template', branch: 'master' }, electron: {host:'github.com',userName:'zcjunblog', repo: 'vue-electron-element-template', branch: 'master' } // 根据index.js中写的create命令的option配置.... } let remote = ''; // git地址 const createAction = async (name, option) => { // 0. 检查控制台是否可以运行`git `, if (!shell.which('git')) { console.log(symbols.error, 'git命令不可用!请先安装git:https://git-scm.com/downloads'); shell.exit(1); } // 1. 验证输入name是否合法 if (fs.existsSync(name)) { console.log(symbols.warning, `已存在项目文件夹${name}!`); return; } if (name.match(/[^A-Za-z0-9\u4e00-\u9fa5_-]/g)) { console.log(symbols.error, '项目名称存在非法字符!'); return; } // 2. 获取option,根据参数确定模板类型 if (option.uni) remote = remotes.uni // if (option.vue) remote = remotes.vue if (option.electron) remote = remotes.electron // 3. 下载模板 await clone(`${remote.host}:${remote.userName}/${remote.repo}#${remote.branch}`, name, {clone: true}, (err) => {console.log('err',err)}); module.exports = createAction;
这里执行在项目根目录执行dt-cli create my-project --uni
进行测试(也可以npm link 到全局在其他文件夹测试),可以正常拉下代码,相对于同名文件夹,不合规范的项目文件夹名,自行测试即可
拉取下拉的项目是和远程仓库关联的,所以需要清理一下git文件及一些不需要的说明文件:
// lib/create.js // 4. 清理文件 const deleteDir = ['.git', 'README.md', 'docs']; // 需要清理的文件 const pwd = shell.pwd(); deleteDir.map(item => shell.rm('-rf', pwd + `/${name}/${item}`));
第五步,拉取完项目模板后进入该文件夹修改package.json文件的信息
这里插入一小步,utils建立一个log.js 封装各颜色的消息提示
//utils/log.js const chalk = require('chalk'); const ora = require('ora'); // 紫色log function logMagent(...txt) { console.log(chalk.magenta(...txt)); } // 普通log function logNormal(...txt) { console.log(...txt); } // 信息类log function logInfo(...txt) { ora().info(chalk.blue(...txt)); } // 成功log function logSuccess(...txt) { ora().succeed(chalk.green(...txt)); } // 警告类log function logWarn(...txt) { ora().warn(chalk.yellow(...txt)); } // 错误log function logError(...txt) { ora().fail(chalk.red(...txt)); } // 下划线 function logUnderline(txt) { return chalk.underline.blueBright.bold(txt); } module.exports = { logMagent, logNormal, logInfo, logSuccess, logWarn, logError, logUnderline };
修改package.json以及安装项目依赖:
安装cross-spawn通过它来同步执行npm install, 引入文件
const { logSuccess, logUnderline, logWarn } = require('../utils/log.js'); const chalk = require('chalk'); const spawn = require('cross-spawn');
// 5. 写入配置文件 shell.cd(name); const cfgSpinner = ora('正在写入配置信息...').start(); let pkg = fs.readFileSync(`${pwd}/${name}/package.json`, 'utf8'); pkg = JSON.parse(pkg); pkg.name = name; pkg.author = ''; fs.writeFileSync(`${pwd}/${name}/package.json`, JSON.stringify(pkg), { encoding: 'utf8' }); cfgSpinner.succeed(chalk.green('配置信息写入成功!')); // 6. 安装依赖 const installSpinner = ora('正在安装项目依赖包... \n').start(); try { spawn.sync('npm', ['install', '-depth', '0'], { stdio: 'inherit' }); } catch (error) { logWarn('自动安装依赖包失败,请手动安装!'); console.log(error); installSpinner.fail(); shell.exit(1); } installSpinner.succeed(chalk.green('依赖包安装成功!')); logSuccess('\n o(゚Д゚)っ! \n\n 项目创建成功,冲啊! \n');
如果你还想在再省一步, 自动打开代码编辑器:
// 7 打开编辑器 if (shell.which('code')) shell.exec('code ./'); shell.exit(0);
至此执行dt-cli create demo --electron 成功拉取代码安装依赖弹出代码编辑器
3.完善脚手架交互功能
上述过程中已经实现了一个脚手架的基本功能,但还不够,使用--uni,--electron这种方式强制要求开发人员输入对应的配置难以避免输错/忘记等尴尬事情的发生,
所以我们选择通过inquirer来完成与用户进行交互,通过勾选配置项来完成"选择基础模板"的过程
这个包文章开始就已经安装了,在create.js中引入
const inquirer = require('inquirer');
重新修改第二步为
// 2. 获取option,根据参数确定模板类型 // 用户选择模板 const tempQuestions = [ { type: 'list', message: '请选择项目模板:', choices: Object.keys(remotes), name: 'type' } ]; const selectTemp = await inquirer.prompt(tempQuestions); // console.log(selectTemp.type) remote = remotes[selectTemp.type]
此时index.js中create子命令option参数项可以删除了
好了,文章到此结束,你可以到https://github.com/zcjunblog/dt-cli/tree/master录取这个cli模板进行二次开发,完善你所需的功能
参考文章:
https://blog.csdn.net/frontend_frank/article/details/108373526
https://www.bwrong.co/post/cli/
🎨 原创不易,支持请点赞、转载请注明本文作者为子成君