初始化项目
- 首先,cd到你的文件夹目录,在终端输入 pnpm init 进行项目的初始化,生成package.json文件,在根目录下添加bin.js 作为入口文件, 在根目录下新建一个文件夹lib,用于存放我们脚手架的核心代码,并添加一个index.js文件
- 在package.json中,添加一个bin属性,包含我们运行脚手架的命令mycli 和刚刚添加的bin.js文件的相对路径,同时,我们使用ES6语法来编写我们的代码,直接在package.json添加上"type": "module",使node环境可以识别我们的ESM模块
{
"name": "mycli",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"bin": {
"mycli": "bin.js"
},
"author": "",
"license": "ISC"
}
在终端运行npm link,可以将当前正在开发的脚手架链接到全局 npm 包中进行测试和调试,我们可以在终端用命令行来测试我们的脚手架是否正常工作
在我们的入口文件,也就是bin.js的最上方添加一行代码#!/usr/bin/env node ,这行注释代码告诉操作系统在执行这个文件时应该使用哪个解释器来运行这个文件,并引入lib中的index.js文件 当我们在终端中输入mycli 命令时,系统会自动使用 node 解释器来运行该文件
#!/usr/bin/env node
import './lib/index.js';
-
在index.js中随便写点代码,在终端输入mycli,测试下是否出现想要的结果
至此,我们的脚手架环境算是搭建起来了,下面我们安装项目所需要的依赖
安装相关依赖
在项目开发前,先介绍下我们开发过程中需要用到的一些第三方库(具体使用可以跳转到对应链接查看哦),灵活的使用这些库可以提高我们的开发效率
commander.js :是一个 Node.js 命令行程序开发框架,可以帮助我们轻松地定义和解析命令行选项和参数
inquirer :是一个基于 Node.js 的命令行交互式用户界面(CLI UI)和交互式提示库。它允许开发者以简单、强大的方式与终端用户进行交互,包括输入文本、选择项等
fs-extra : 是一个基于 Node.js 的第三方文件系统模块,它提供了比 Node.js 内置的 fs 模块更多的文件系统操作功能,我们可以很方便的使用他对文件进行操作
ejs :是一种嵌入式 JavaScript 模板引擎,它可以让开发人员使用 JavaScript 代码动态生成 HTML 标记,我们如果想要使用命令行的方式生成模板文件,ejs功不可没
picocolors : 是一个可以在终端修改输出字符样式的
npm
包,说直白点就是给字符添加颜色
1.首先我们创建本地commander对象
import { Command } from "commander";
const program = new Command();
commander提供了很多子命令供我们描述更加完整的程序,这里简单介绍一些最常用的
version:用来查看我们当前脚手架的版本,一般是从package.json文件中获取版本号,传给.version(),当我们在命令行输入mycli -v或者mycli --version的时候,会输出对应的版本号
option:方法来定义选项,同时可以附加选项的简介,每个选项可以定义一个短选项名称(-后面接单个字符)和一个长选项名称(--后面接一个或多个单词),使用逗号、空格或|分隔,<>中传入一个必须参数
// '-c --color':自己定义的选项名称,'output':与这个选项对应的描述,'blue':默认值
program.option('-c --color <colorName>','output your color','blue')
.option('-a, --add', 'add Something')
//解析我们定义的命令并处理参数
program.parse(process.argv)
输入mycli --help可以输出我们定义的option选项
- command:用来配置命令,第一个参数是命令名称,可选参数用<>表示,可选参数用[]表示,第二个参数是对这个命令的描述或简介
program.option('-c --color <colorName>', 'output your color', 'blue')
.command('start <service>', 'start named service')
输入mycli --help查看command是否配置成功
- action: 一般.command后面会跟上一个.action,用来指定在终端输入该命令后应该执行的操作
program.command("create <name>")
.description("create a new vue project~")
.action(async (option) => {
console.log(option)
});
输入mycli --help查看action是否配置成功
import {Command} from 'commander';
const program = new Command();
program.version('1.0.0', '-v, --version');
program.option('-c --color <colorName>', 'output color','blue');
program
.command('create <name>')
.description('create a new vue project')
.action(async (option)=>{
console.log(option, 'action')
});
program.parse(process.argv);
console.log('123s');
console.log('444');
demo
lib/index.js
import {Command} from 'commander';
const program = new Command();
program.version('1.0.0');
program
.command('create')
.option('-n --name <projectName>', 'your project name') // 创建的项目名称
.option('-v2 --vue2', 'create a vue2 project') // 指定项目为vue2
.option('-v3 --vue3', 'create a vue3 project') // 指定项目vue3
.description('create a new vue project')
.action(async (option)=>{ //在终端输入`mycli create -n [name] -v2`该命令后会执行的操作
const {create} = await import('../core/create.js');
return create(option)
});
program.parse(process.argv);
create.js
import { resolve } from "path";
import fs from 'fs-extra'
import {cwd, __dirnameNew } from "../common/index.js";
import {promptAction} from "../common/action.js";
import logger from "../common/logger.js";
const {copy, pathExistsSync } = fs;
export async function create(option) {
const {name, version} = await promptAction('Project', option, 'vue_app')
const dir = resolve(cwd, name);
if (pathExistsSync(dir)) { //判断要创建的项目是否存在
console.log(`${name} already exists`)
return false
}
const template = resolve(
__dirnameNew, version ==='vue2'? '../site/vue2app': '../site/vue3app'
);
await copy(template, dir);
logger.success('Application created successfully!')
logger.info(`\\ cd ${name}
npm install
npm run dev`)
}
action.js
import inquirer from "inquirer";
const {prompt} = inquirer
export async function promptAction(createType, option, defaultName) {
// 命令行输入的 -n 表示创建的项目名 如若未指定,通过prompt引导用户输入项目名称
const { name } = option.name ? option : await prompt([
{
type: 'input',
name: 'name',
message: `your vue ${createType} name:`,
default: defaultName
}
])
// 命令行输入 v2/v3 代表 vue2/vue3项目 如若已指定直接将模板复制到dir 目录下
const isSelectVersion = option.vue2? "vue2" : option.vue3? "vue3" : false;
const { version } = isSelectVersion ? {version: isSelectVersion} :
await prompt([
{
type: 'list',
name: 'version',
message: 'select the version of your project:',
choices: ['vue2', 'vue3']
}
]);
return {
name,
version
}
}
common/index.js
import {resolve, dirname} from "path";
import { fileURLToPath } from 'url'
const __filenameNew = fileURLToPath(import.meta.url)
export const __dirnameNew = dirname(__filenameNew)
export function setNodeEnv(value){
process.env.NODE_ENV = value
}
export function isDev() {
return process.env.NODE_ENV = 'development'
}
export const cwd = process.cwd();
export const vue_template_path = resolve(cwd,'site');
logger
import pic from "picocolors";
export default {
info(text) {
console.log(text);
},
success(text) {
console.log(pic.green(text));
},
warning(text) {
console.log(pic.yellow(text));
},
error(text) {
console.log(pic.red(text));
},
title(text) {
console.log(pic.cyan(text));
}
}
参考: https://juejin.cn/post/7249930189181272124?searchId=20230721171213CDEA27FA649BF9CAB8A8