环境 mac OS ventura 13.3.1
好啦有人就问了,有的公司新的项目都是根据业务需求来进行技术选型。至少我不是这样,有的公司技术一般都定型了的比如技术框架react或vue。重复的项目模板,不同的项目逻辑。各种中后台管理系统、h5、小程序、后端等。我们把相应的项目配置都配好,大底的webpack、 vite配置、还有工程化配置。根据自己公司开发流程来。进行一个脚本拓展。create-react-app也是这样。我们为啥不弄自己的呢?
- 提前准备一个空文件夹,或创建一个空文件夹
$ mkdir create-my-projectmk && cd create-my-project
- 在创建文件夹npm init 生成package.json
$ npm init
- 在package.json文件里添加自定义命令,以及支持es写法(很多包最新版现在已经支持es6写法了)
"type": "module",
"bin": {
"create-tokyo-project": "./bin/index.js"
}
- 下载下面依赖(我用的pnpm这个包可以用npm、yarn都可以)
- chalk(给打印的文字着色)、commander(自定义命令参数、获取命令行参数)、download-git-repo(下载远程git项目模板使用方式可以看看文档)、inquirer(提供交互式命令)、log-symbols(提供图标)、ora(提供加载效果)
$ pnpm i chalk commander download-git-repo inquirer log-symbols ora
- 在项目创建bin文件,在bin文件创建index.js(由package.json里面的bin自定义命令指向的路径文件)
- 在bin/index.js文件添加以下代码就打工告成了(记住一定要在首行添加#!/usr/bin/env node 这样是告诉计算机识别你什么类型环境代码我们就是node),下面看代码很容易理解了。没多少代码!
#!/usr/bin/env node
import logSymbols from 'log-symbols'
import download from 'download-git-repo'
import chalk from 'chalk'
import { Command } from 'commander';
import inquirer from 'inquirer';
import fs from 'fs';
import ora from 'ora';
import path from 'path';
const prompt = inquirer.prompt
const program = new Command();
const spinner = ora()
//github为自己搭建配置的github项目地址 :注意这里的github格式,看文档!看文档!
const choices = [
{
name: 'web',
message: 'web中后台管理项目',
templates: [{
name: 'vite-react-web', message: '基于vite搭建的react web项目',
github: 'github:FiveTokyo/vite-react-web'
}]
},
{ name: 'app', message: '手机app', templates: [{ name: 'vite-react-web', message: '基于vite搭建的react web项目', github: '' }] },
{ name: 'ssr', message: 'SSR官网', templates: [{ name: 'vite-react-web', message: '基于vite搭建的react web项目', github: '' }] },
{ name: 'electron', message: 'electron桌面端应用', templates: [{ name: 'vite-react-web', message: '基于vite搭建的react web项目', github: '' }] },
{ name: 'wx', message: 'wx微信小程序', templates: [{ name: 'taro', message: '基于vite搭建的react web项目', github: '' }] },
{ name: 'h5', message: 'h5移动端项目', templates: [{ name: 'vite-react-h5', message: '基于vite搭建的react web项目', github: '' }] },
{ name: 'nestjs', message: 'nest后端项目', templates: [{ name: 'nestjs', message: '基于vite搭建的react web项目', github: '' }] }
]
const args = program.version('0.0.1', '-v', '--version')
.action(async (opts, { args }) => {
let projectType = args[0]; //获取终端命令的第一个参数,比如我用自定义命令 create-tokyo-project web 它就会获取参数web
const findType = choices.find(config => config.name === projectType)
if (!projectType || !findType) {
if (!findType && projectType) console.log(chalk.red(logSymbols.warning, `没有找到 ${projectType} 项目类型!请重新选择!`))
const answer = await prompt([
{
name: 'configs',
message: '请选择创建所属类型项目',
type: 'rawlist',
validate: (value) => {
return value.length > 0 ? true : '请选择一个'
},
default: [choices[0].name],
choices: choices.map((config, i) => {
return {
value: config.name,
name: config.message,
}
}),
},
])
projectType = answer.configs
}

//找到选择的项目类型
const targetType = choices.find((choice) => choice.name === projectType)
const targetTemplateConfig = await prompt([
{
name: 'configs',
message: `请选择 ${chalk.blue(targetType.message)} 项目模板`,
type: 'rawlist',
validate: (value) => {
return value.length > 0 ? true : '请选择一个'
},
default: [choices[0].name],
choices: targetType.templates.map((config, i) => {
return {
value: config.name,
name: config.message,
}
}),
},
])
const targetTemplate = targetType.templates.find(config => config.name === targetTemplateConfig.configs)
const projectName = await prompt([
{
name: 'name',
message: `请输入项目名称`,
type: 'input',
validate: (value) => {
return value.length > 0 ? true : '请输入合理的项目名称'
},
default: targetTemplate.name,
},
])
if (!targetTemplate.github) {
return console.log(logSymbols.error, chalk.red('github是无效地址,请联系开发人员补充'))
}
try {
const dir = path.join(process.cwd(), projectName.name);
if (fs.existsSync(dir)) {
console.log(chalk.red('已存在该目录文件!请删除该文件或者换个文件名'))
return
}
spinner.start(`正在拉取 ${targetTemplate.github} 模板中...😎`)
downloadTemplate({ repository: targetTemplate.github, projectName: projectName.name })
} catch (error) {
spinner.fail(logSymbols.error, chalk.red(error.message));
}
}).parse(process.argv)
/**
* @description: 下载模板
* @param {type}
* @return:
*/
const downloadTemplate = function ({ repository, version = '0.0.2', projectName }) {
// repository模板地址 projectName项目名称 // clone 是否是克隆
download(repository, projectName, function (err) {
console.log(err ? '模板加载错误' : '模板加载结束');
if (err !== 'Error') {
editFile({ version, projectName });
}
})
};
const editFile = function ({ version, projectName }) {
try {
// 读取文件
fs.readFile(`${process.cwd()}/${projectName}/package.json`, (err, data) => {
if (err) throw err;
// 获取json数据并修改项目名称和版本号
let _data = JSON.parse(data.toString())
_data.name = projectName
_data.version = version
let str = JSON.stringify(_data, null, 4);
// 写入文件
fs.writeFile(`${process.cwd()}/${projectName}/package.json`, str, function (err) {
if (err) throw err;
})
spinner.succeed();
console.log(logSymbols.success, chalk.green('拉取github模板成功!😎'))
console.log(chalk.gray('\n 请开始你的项目吧'))
console.log(chalk.gray(`\n cd ${projectName} `))
console.log(chalk.gray(`\n pnpm i \n `))
});
} catch (error) {
console.log(logSymbols.error, chalk.red('\n 修改文件失败', error.message))
}
};
最后我们测试一下
- 在刚刚创建项目目录终端npm link
$ npm link
- 再用上面我们自定义的命令在终端试试
- create-tokyo-project (web)
我们来选择第一个,只有第一个才有我测试的github模板
默认名字也可以自定义项目名,默认填写会从远程拉模板代码了。这里稍等一会
项目拉取成功啦
2.点击获取项目地址