面对如今丰富的前端生态,开启新项目时你是否经常陷入这样的纠结:
- 在选择构建工具、UI框架、要不要TS等技术选型时,是不是都要重新研究最新的最佳实践?
- 当团队需要内部的代码规范、工具链配置、私有依赖等总要手动添加,而影响开发效率?
- 当新成员加入时,是否需要大量时间理解项目结构、配置规范,导致配置不一致导致各种奇怪问题?
- 当团队项目需要添加特定的中后台、组件库等场景,总要重复的基建代码的
Copy
以上烦恼都可以通过前端脚手架搞定,从而不再重复造轮子,而是打造专属自身团队的最佳实践。
本文将从0到1带你构建一个简单的脚手架,以抛砖引玉的方式带了解脚手架的开发。
前端脚手架
前端脚手架本质上是一个Node.js命令程序,它通常有以下功能:
- 交互式询问用户 通过命令行交互,如确定项目名称、选择框架
- 模板管理 根据命令行交互的结果远程拉取的项目模板
- 交互式配置 根据命令行让用户自行选择具体配置
- 依赖安装 自动安装项目依赖(npm/yarn/pnpm)
- 命令扩展 支持插件化或自定义命令(可选,进阶功能)
在开发脚手架过程中,使用到一些第三方依赖来帮助我们完成脚手架开发:
commander命令行处理工具chalk命名行输出美化工具inquirer命名行交互工具ora终端loading美化工具git-clone下载项目模板工具,figlet终端生成艺术字fs-extra操作本地目录ejs/handlebars动态渲染模板文件
前端脚手架实现
1. 初始化项目
bash
mkdir case-cli && cd case-cli
npm init -y
2.配置命令入口
json
{
"name": "case-cli",
"version": "0.0.1",
"main": "index.js",
"bin": "/bin/index.js",
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"chalk": "^4.1.2",
"commander": "^14.0.2",
"fs-extra": "^11.3.2",
"git-clone": "^0.2.0",
"inquirer": "^8.2.7",
"ora": "^5.4.1"
}
}
📢 注意
package.json中多数依赖包的最新版本都采用ESM模块化,如果采用Common.js模块化方式,需要适当降级
3. 编写入口文件
js
#!/usr/bin/env node
const ora = require("ora"); // loading 美化工具
const chalk = require("chalk"); // 命令行美化工具
const inquirer = require("inquirer"); // 命令行交互
const fs = require("fs-extra"); // 操作本地目录
const path = require("path");
const gitClone = require("git-clone"); // 拉取github模板
const packageJson = require("../package.json"); // 获取package.json
const { program } = require("commander"); // 命令行处理工具
console.log(chalk.blue("学习脚手架工具已启动!"));
📢 注意
必须在文件开头添加
#!/usr/bin/env node,告知操作系统 该文件是通过Node执行
现在我们就可以在命令行中输入case-cli后回车:

然后我们再添加一行代码,通过commander的program解析命令行参数:
js
#!/usr/bin/env node
/* 依赖引入就此省略 */
console.log(chalk.blue("学习脚手架工具已启动!"));
// 解析命令行参数
program.parse(process.argv);
输入case-cli -h命令:

添加获取版本的指令
js
#!/usr/bin/env node
/* 依赖引入就此省略 */
console.log(chalk.blue("学习脚手架工具已启动!"));
program.version(chalk.green.bold(packageJson.version))
// 解析命令行参数
program.parse(process.argv);
输入case-cli -V将显示脚手架版本号,而且case-cli -h也有变化

一般情况下脚手架类似vue create [project name]来创建项目,在没有输入任何指令时如case-cli将会执行case-cli --help命令显示该脚手架有哪些命令操作。可以如下实现:
js
program.action(() => program.help());
注册命令
js
program
.command("create <project-name>") // <project-name> 表示必填参数,如果不填写将会报错
.description("创建新项目")
.action(async (projectName) => {
console.log(projectName);
});
添加交互配置
js
program
.command("create <project-name>") // <project-name> 表示必填参数,如果不填写将会报错
.description("创建新项目")
.action(async (projectName) => {
inquirer.prompt([
{
type: "list",
name: 'framework',
message: '请选择框架',
choices: ["vue", "react"],
}
]).then(async (answers) => {
const { framework } = answers;
console.log(chalk.green(`正在创建项目 ${projectName}`));
console.log(chalk.green(`正在创建 ${framework} 项目`));
})
});
当我们输入case-cli create app时,将呈现如下画面:
任意选择一项后: 
检查项目名称是否重复
脚手架是以项目名称为目录名称,在当前输入指令的目录下创建的,因此需要检查是否有相同的目录名。并给出提示。
js
program
.command("create <project-name>") // <project-name> 表示必填参数,如果不填写将会报错
.description("创建新项目")
.action(async (projectName) => {
inquirer.prompt([
{
type: "list",
name: 'framework',
message: '请选择框架',
choices: ["vue", "react"],
}
]).then(async (answers) => {
const { framework } = answers;
// 拼接创建项目目录地址
const projectPath = path.join(process.cwd(), projectName);
// 检查是否存在相同目录
const isExist = fs.existsSync(projectPath);
if (isExist) {
// 提供交互选择 覆盖则删除之前目录,反之则退出此次命令
const result = await inquirer.prompt([
{
type: "confirm",
message: "当前目录下已存在同名项目,是否覆盖?",
name: "overwrite",
default: false,
},
]);
if (result.overwrite) {
fs.removeSync(projectPath);
console.log(chalk.green("已删除同名项目"));
} else {
console.log(chalk.yellow("请重新创建项目"));
return;
}
}
})
});
拉取远程模板
js
const spinner = ora(chalk.magenta("正在创建项目...")).start();
const remoteUrl = `https://github.com/gardenia83/${framework}-template.git`;
gitClone(remoteUrl, projectPath, { checkout: "main" }, function (err) {
if (err) {
spinner.fail(chalk.red("拉取模板失败"));
} else {
spinner.color = "magenta";
// 由于拉取会将他人的.git,因此需要移除
fs.removeSync(path.join(projectPath, ".git")); // 删除.git文件
spinner.succeed(chalk.cyan("项目创建成功"));
console.log("Done now run: \n");
console.log(`cd ${projectName}`);
console.log("npm install");
console.log("npm run dev");
}
});
拉取远程模板项目:
拉取完成后:

小结
通过本文,我们完成了一个基础但功能完整的前端脚手架,实现了项目创建、模板拉取、冲突处理等核心功能。这个简单的脚手架已经能够解决文章开头提到的部分痛点:
✅ 统一技术选型 - 通过预设模板固化团队最佳实践
✅ 快速初始化 - 一键生成项目结构,告别手动配置
✅ 规范团队协作 - 新成员无需理解复杂配置,开箱即用
但这仅仅是一个开始! 你可以基于这个基础版本,根据团队实际需求进行深度定制:
🛠 模板动态化 - 集成 ejs 等模板引擎,根据用户选择动态生成配置文件
🛠 生态集成 - 添加 ESLint、Prettier、Husky 等工程化工具链
🛠 场景扩展 - 针对中后台、组件库、H5 等不同场景提供专属模板
🛠 插件机制 - 设计插件系统,让团队成员也能贡献功能模块
最好的脚手架不是功能最全的,而是最适合团队工作流的。 希望本文能成为你打造团队专属工具链的起点,让重复的配置工作成为历史,把宝贵的时间留给更有价值的创新!