前端脚手架编写
背景
在日常的研发工作中,经常会有新的产品/项目需要进行搭建;那么咱们常规的做法就是直接复制之前的项目模板。这样是能提升一些工作效率,但是很多冗余的文件或者代码需要手动去进行梳理优化;处理不到位就会继续冗余下去啦!还有就是不同类型的业务你需要去不同项目的模板进行复制来进行复用,其实开发的效率提升的效率也并不是很明显。如果利用脚手架进行项目模板管理的就方便很多,在创建项目的时候直接使用脚手架进行创建;不管你是PC项目或者跨端的;直接通过脚手架去创建,在使用脚手架创建的时候进行项目模板选择;选择完成后;基于当前选择的模板进行项目创建;那么这样研发的效率又能提升一个层次。话不多说,各位看官且看下面讲解。
编写之前先介绍几款npm依赖包
- commander 完整的 node.js 命令行解决方案;
- chalk 辅助设置输出字符串样式;
- fs-extra 文件操作库,操作删除文件等;
- inquirer 命令行交互工具
- ora 增加执行动效
- shelljs shell命令执行库
- validate-npm-package-name 校验包名
具体设计
具体实现
Node版本 v14.14.0
这边以Git版本管理项目模板为例,内置项目方式使用nodejs操作复制内置到脚手架的项目模板到本地即可。
- 首先当然是创建脚手架文件夹啦!xxx-cli
- 文件夹内执行npm init创建package.json
{
"name": "project-create-cli",
"version": "1.0.0",
"description": "project create cli",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"cli"
],
"author": "Jackson",
"license": "ISC",
"gitPath": [
{
"name": "vite+vue2+element",
"value": {
"url": "https://github.com/BigFrontEnd-China/vite-demo.git",
"branch": "main",
"installType": "pnpm"
}
}
],
"bin": {
"project": "bin/index.js"
},
"devDependencies": {
"chalk": "^4.1.2",
"commander": "^8.3.0",
"fs-extra": "^10.0.0",
"inquirer": "^8.2.0",
"ora": "^4.1.1",
"shelljs": "^0.8.4",
"validate-npm-package-name": "^3.0.0"
}
}
- 依赖安装 这里注意依赖版本 "chalk": "^4.1.2" "ora": "^4.1.1" 否则报错Must use import to load ES Module
pnpm i commander chalk fs-extra inquirer ora shelljs validate-npm-package-name -D
- 创建index.js文件和tool.js文件
#!/usr/bin/env node
/**
* index.js
* @file 入口文件
* @author Jackson
*/
const inquirer = require('inquirer');
const program = require('commander');
const version = require('../package.json').version;
const gitPath = require('../package.json').gitPath;
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const ora = require('ora');
const spinner = ora();
const validateProjectName = require('validate-npm-package-name');
const { createProject, updatePackageJson, deleteDir, npmInstall } = require('./tool');
const logo = ` `;
let userQuestions = [
{
type: 'input',
name: 'description',
message: 'Please Enter Project Description',
default() {
return 'A vue project';
}
},
{
type: 'input',
name: 'author',
message: 'Please Enter Author',
default() {
return 'Your Name <you@example.com>';
}
}
];
program
.version(version, '-v, --version')
.description('CLI for rapid EOP swan development')
.usage('<command> [options]');
// 初始化命令
program.command('create <project>').action((projectName) => {
if (projectName) {
console.log(chalk.cyan(logo));
const cwd = process.cwd();
const inCurrent = projectName === '.';
const name = inCurrent ? path.relative('../', cwd) : projectName;
const targetDir = path.resolve(cwd, projectName || '.');
const result = validateProjectName(name);
if (!result.validForNewPackages) {
console.error(chalk.red(`Invalid Project Name: "${name}"`));
result.errors &&
result.errors.forEach((err) => {
console.error(chalk.red.dim('Error: ' + err));
});
result.warnings &&
result.warnings.forEach((warn) => {
console.error(chalk.red.dim('Warning: ' + warn));
});
process.exit();
}
if (fs.existsSync(targetDir)) {
if (inCurrent) {
const { ok } = inquirer.prompt([
{
name: 'ok',
type: 'confirm',
message: `Generate project in current directory?`
}
]);
if (!ok) {
return;
}
} else {
inquirer
.prompt([
{
name: 'action',
type: 'list',
message: `Target directory ${chalk.cyan(targetDir)} already exists. Pick an action:`,
choices: [
{ name: 'Remove', value: 'Remove' },
{ name: 'Cancel', value: false }
]
}
])
.then((choiceData) => {
if (choiceData.action === 'Remove') {
//console.log(`Removing... ${chalk.cyan(targetDir)}`)
spinner.start(`${chalk.red('Removing...')}`);
return deleteDir(targetDir);
} else {
return;
}
})
.then(() => {
spinner.succeed(chalk.green('Remove filished!'));
startCreate(name);
});
}
} else {
startCreate(name);
}
} else {
return 'ProjectName Is Null!';
}
});
async function startCreate(projectName) {
let gitUrl = '';
let branch = '';
await inquirer
.prompt([
{
name: 'action',
type: 'list',
message: `Please Select Project template:`,
choices: gitPath
}
])
.then((data) => {
gitUrl = data.action.url;
branch = data.action.branch;
installType = data.action.installType;
});
await inquirer
.prompt(userQuestions)
.then((answers) => {
if (answers && gitUrl && branch) {
spinner.start(`Create Project...`);
return createProject(branch, gitUrl, { ...answers, projectName });
} else {
console.log('Please Use Cestc Create Commander!');
}
})
.then((answers) => {
spinner.succeed(chalk.green('Create Project Filished!'));
return updatePackageJson(answers);
})
.then((dir) => {
spinner.succeed(chalk.green('Update package.json Filished!'));
spinner.start(`${installType} Initializing Dependencies...`);
return npmInstall(dir, installType);
})
.then((dir) => {
spinner.succeed(chalk.green('Project Initializing Dependencies Finished!'));
let runCommand = installType === 'pnpm' ? 'pnpm' : 'npm';
console.log(`cd ${dir} && ${runCommand} run dev`);
spinner.stop();
process.exit();
})
.catch((err) => {
spinner.fail(chalk.red(err.toString()));
console.log(err);
});
}
program.parse(process.argv);
/*
*@Description: tool.js
*@Author: Jackson
*@Date: 2021-08-10 10:35:19
*@UpdateDate:
*/
const fs = require('fs');
const path = require('path');
const shell = require('shelljs');
const exec = require('child_process').exec;
const fse = require('fs-extra');
/**
*
* @param {分支} branch
* @param {路径} url
* @param {选择结果} answers
* @returns
*/
function createProject(branch, url, answers) {
const projectName = answers.projectName;
const gitCmd = `git clone --branch ${branch} ${url} ${projectName}`;
return new Promise((resolve, reject) => {
exec(gitCmd, (err, stdout, stderr) => {
if (err) {
console.log('stderr: ', stderr);
reject(err);
} else {
shell.rm('-rf', process.cwd() + `/${projectName}/.git`);
resolve(answers);
}
});
});
}
/**
* 使用交互信息更新创建项目package.json
* @param {*} answers
* @returns
*/
function updatePackageJson(answers) {
return new Promise((resolve, reject) => {
const projectName = answers.projectName;
const packagejsonPath = path.resolve(process.cwd(), `${projectName}/package.json`);
const packageJson = Object.assign(require(packagejsonPath), {
name: projectName,
author: answers.author,
version: '1.0.0',
description: answers.description || projectName
});
fs.writeFileSync(packagejsonPath, JSON.stringify(packageJson, null, 4));
resolve(projectName);
}).catch((error) => {
return error;
});
}
/**
* 删除已创建重复命名项目
* @param {*} dir
* @returns
*/
function deleteDir(dir) {
return new Promise((resolve, reject) => {
fse
.remove(dir)
.then(() => {
resolve(dir);
})
.catch((err) => {
reject(err);
});
});
}
/**
* 根据类型安装项目依赖
* @param {*} dir
* @param {*} npmInstall
* @returns
*/
function npmInstall(dir, npmInstall) {
return new Promise((resolve, reject) => {
let installCommand = '';
switch (npmInstall) {
case 'npm':
installCommand = `cd ${dir} && npm i --registry=https://registry.npm.taobao.org`;
break;
case 'pnpm':
installCommand = `cd ${dir} && pnpm i --registry=https://registry.npm.taobao.org`;
break;
default:
installCommand = `cd ${dir} && npm i --registry=https://registry.npm.taobao.org`;
break;
}
exec(installCommand, (err, stdout, stderr) => {
if (err) {
console.log('stderr: ', stderr);
reject(err);
} else {
resolve(dir);
}
});
});
}
module.exports = {
createProject,
updatePackageJson,
deleteDir,
npmInstall
};