例如控制模板的name、选择使用的插件,动态插入
之前vue模板是有选择是否使用pinia、unocss,通过用户的选择,在项目中动态配置插件
需要用到ejs读取模板,然后动态修改
npm install ejs
在libs/utils/index.js添加ejs模板的操作函数
import fs from 'fs-extra'
import path from 'path'
import ejs from 'ejs'
const {existsSync,statSync,readdirSync,rmdirSync,unlinkSync} = fs;
/**
* 文件读写操作
* @param {string} targetDir 当期目录
* @param {string} filename 文件名
* @param {object} answer 回答
*/
export function fileControl(targetDir, filename, answer) {
fs.readFile(path.join(targetDir, filename)).then(res => {
const str = res.toString()
const html = ejs.render(str, answer)
fs.writeFile(path.join(targetDir, filename), html)
})
}
/**
* 判断是否有同名文件夹
* @param {string} projectName 文件名
*/
export const isFileExist = (projectName) => {
const targetDir = path.join(process.cwd(), projectName)
return fs.existsSync(targetDir)
}
/*path:文件名*/
export function delPath(path) {
if (!existsSync(path)) {
console.log("路径不存在");
return "路径不存在";
}
const info = statSync(path);
if (info.isDirectory()) {//目录
const data = readdirSync(path);
if (data.length > 0) {
for (let i = 0; i < data.length; i++) {
delPath(`${path}/${data[i]}`); //使用递归
if (i == data.length - 1) { //删了目录里的内容就删掉这个目录
delPath(`${path}`);
}
}
} else {
rmdirSync(path);//删除空目录
return true
}
} else if (info.isFile()) {
unlinkSync(path);//删除文件
return true
}
}
在libs/data/vue.config.json
[
{
"name": "pinia",
"message": "是否使用pinia",
"type": "confirm",
"default": true
},
{
"name": "unocss",
"message": "是否使用unocss",
"type": "confirm",
"default": true
}
]
在libs/data/react.config.json
[
{
"name": "mobx",
"message": "是否使用mobx",
"type": "confirm",
"default": true
},
{
"name": "unocss",
"message": "是否使用unocss",
"type": "confirm",
"default": true
}
]
libs/constant.js
import fs from 'fs-extra'
import path from 'path'
import { fileURLToPath } from 'url'
const __filenameNew = fileURLToPath(import.meta.url)
const __dirnameNew = path.dirname(__filenameNew)
// 根目录
export const rootPath = __dirnameNew.slice(0, __dirnameNew.length - 4)
// 获取package.json文件内容
export const packageJsonData = JSON.parse(fs.readFileSync(rootPath + 'package.json', 'utf8'))
// 获取当前模板列表
export const questionList = JSON.parse(fs.readFileSync(rootPath + 'libs/data/demo.config.json', 'utf8'))
//vue插件配置
export const vueConfigList = JSON.parse(fs.readFileSync(rootPath + 'libs/data/vue.config.json', 'utf8'))
//react 插件配置
export const reactConfigList = JSON.parse(fs.readFileSync(rootPath + 'libs/data/react.config.json', 'utf8'))
libs/command/index.js
主要更改handleDownLoad 逻辑
import downLoad from 'download-git-repo'
import path from 'path'
import inquirer from 'inquirer'
import ora from 'ora';
import chalk from 'chalk';
import fs from 'fs-extra'
import {questionList,vueConfigList,reactConfigList} from '../constant.js';
import {isFileExist,fileControl,delPath} from '../utils/index.js'
//设置检测重名的问题交互
const folder = [
{
name: "folder",
message: "当前目录已存在,请选择",
type: "list",
choices: [
{
name: "覆盖",
value: "cover"
},
{
name: "重命名",
value: "rename"
}
]
},
{
name: 'newName',
// 这里设置当前面用户选择重命名后,让用户输入新名称
when: answer => answer.folder === 'rename',
type: 'input',
message: '目录名称为:'
}
]
const handleDownLoad = (projectName) => {
inquirer.prompt(questionList).then(res => {
const targetDir = path.join(process.cwd(), projectName)
// 获取到第一项中,用户选择的值,这里偷了个懒,大家可以根据答案和问题获取对应下载链接。
const info = questionList[0].choices.find(item => item.value === res.features)
const configAnswer = res.features === 'vue' ? vueConfigList : reactConfigList
inquirer.prompt(configAnswer).then(result => {
/*** 初始化loading图标文字 start */
const spinner = ora('模版下载中 ...')
/*** 初始化loading图标文字 end */
spinner.start()
downLoad(`direct:${info.link}`, targetDir, { clone: true }, (err) => {
if (err) {
spinner.fail()
console.log(chalk.red(err))
console.log(chalk.red('获取模版失败'))
} else {
// 获取所有的用户问题答案,并以用户create名称为项目名称
const answer = { name: projectName, ...res, ...result }
// 处理package.json对应交互变更
fileControl(targetDir, 'package.json', answer)
//处理vite.config.ts
fileControl(targetDir, 'vite.config.ts', answer)
//处理index.html
fileControl(targetDir, 'index.html', answer)
if (answer.features === 'vue') {
// 处理入口文件相关变更main.js
fileControl(targetDir, '/src/main.ts', answer)
if (!answer.pinia) {
delPath(path.join(targetDir, '/src/store'))
}
} else if (answer.features === 'react') {
fileControl(targetDir, '/src/main.tsx', answer)
fileControl(targetDir, '/src/views/index/index.tsx', answer)
if (!answer.mobx) {
delPath(path.join(targetDir, '/src/store'))
}
}
const spinner = ora('获取模版成功')
spinner.succeed()
// console.log(chalk.green('获取模版成功'))
}
spinner.stop()
})
})
})
}
const handleChecKName = (name) => {
// 清空控制台
console.clear()
if (isFileExist(name)) {
inquirer.prompt(folder).then(res => {
if (res.folder === 'cover') {
const targetDir = path.join(process.cwd(), name)
const loadingMsg = ora(`正在删除 ${chalk.cyan(targetDir)}...`)
loadingMsg.start()
fs.remove(targetDir).then(val => {
loadingMsg.succeed()
loadingMsg.stop()
handleDownLoad(name)
})
} else {
handleChecKName(res.newName)
}
})
} else {
handleDownLoad(name)
}
}
export default handleChecKName;
执行命名npx mfex-project create mfex-react
就会把相关配置引入
不使用unocss和mobox
前端脚手架搭建结束,下面可以发到npm上