搭建前端脚手架(上)
在学习 Vue 或者 React 的时候,我们应该都使用过它们的脚手架工具(create-vue、create-react-app)。通过脚手架我们能够快速得到一个 Vue 或者 React 项目,我们无需将经历花在了项目基础骨架搭建上,能够更加快速的开发业务
为了让大家更好明白,我尝试通过场景来一步步讲明白脚手架搭建里面的”门道“,在场景中主人公 - 阿牛,一个不太会写代码的前端开发
脚手架是什么?
在开发自己的脚手架前,我们再认识一下脚手架吧!脚手架是:”一种工具或框架,用于快速生成项目的基础结构、配置文件和代码模板。它通常包含了一些预定义的目录结构、配置文件、依赖管理工具、构建工具等。脚手架的主要目的是减少重复劳动,提高开发效率,并确保项目结构的一致性。“
在前端开发中,脚手架工具通常会帮助我们完成以下任务:
- 初始化项目目录结构
- 配置开发环境(如 Webpack、Babel、ESLint 等)
- 集成常用的库和工具(如 Vue、React、TypeScript 等)
- 提供代码模板和最佳实践
使用脚手架,我们会得到一个预期的模版项目,这里已经集成了我们需要的基础设施
脚手架解决了什么问题?
- 初始化项目繁琐:手动创建项目目录、配置文件、安装依赖等步骤非常耗时且容易出错。脚手架可以自动化这些步骤,一键生成项目骨架。
- 统一开发规范:每个团队都有自己的代码风格、目录结构和依赖管理方式。脚手架可以强制执行这些规范,确保团队成员在统一的环境下工作。
- 提高开发效率:通过预先配置好的开发环境、构建流程和测试框架,脚手架可以大大提高开发效率,让开发者专注于业务逻辑的实现。
- 减少重复劳动:在多个项目中使用相同的配置和依赖时,脚手架可以避免重复劳动,提高代码复用率。
- 降低学习成本:对于新手开发者,脚手架可以提供一个清晰的项目结构和最佳实践,降低学习成本。
- 团队技术沉淀:团队如果多个项目,在 A 项目实现了 B 功能,在 C 项目也需要,这时候沉淀到脚手架是一个更好的推广的方式,能够减少团队重复劳动,提高效率
- 最佳实践沉淀:当我们把一些配置,能力集成到脚手架会让我们思考这是不是最佳实现,是不是适合团队等等代码规范问题
这些时候我们不需要脚手架
- 现有脚手架已经满足我们的需求
- 项目很简单,手动创建项目就是更好的选择
- 团队规模小,技术栈很统一,大家都熟悉项目开发规范,就不需要脚手架,额外添加了流程
- 时间资源少,这时候就不要搭建脚手架了,因为维护脚手架也需要成本
快速跑通一个脚手架搭建核心流程
梳理需求
我们目标就是开发出一个类似 vue cli 的脚手架工具,使用我们自己的脚手架工具来下载我们远程 github 的仓库代码,我们可以先体验一下 create-vue ,运行:
npm create vue@latest
我们根据交互会得到一个基础的项目模板,简单分析一下,主要为三个步骤:
- 用户在终端运行 npm create xxx ,然后下载一个脚手架包
- 下载完成后,就会在终端运行一个交互页面,通过一步步的交互,最终我们会得到一个我们想要的包
- 用户执行完交互逻辑,安装对应的代码到本地,这里我们需要给到友好的下载进度提示和安装完成后的一些提示
功能开发
第一步
我们按照我们分析的步骤一个个开发,最后再优化,我们先来开发第一步(项目名为 create-lucky,注意如果我们希望通过 npm create 来创建项目,项目名称必须以 create- 开头)。
# 创建文件夹并进入
mkdir create-lucky && cd create-lucky
# 初始化 package.json 和 index.js
npm init -y && nano index.js
# 然后我们在 index.js 输入如下代码,注意 第一行必须是 #!/usr/bin/env node
#!/usr/bin/env node
console.log("Hello from create-lucky!");
# 为了让 index.js 文件可以被执行,需要添加执行权限,然后我们运行
chmod +x index.js
# 然后我们需要修改一下 package.json
"main": "src/index.js",
"bin": {
"create-lucky": "src/index.js"
},
# 本地测试,我们运行 npm link
npm link
# 在任意目录下,运行以下命令来测试脚手架
npm create lucky
# 发布1,先检查 npm register 是否为:https://registry.npmjs.org/
npm get registry
# 发布2,如果没有注册 npm 账号,需要先注册,根据提示你输入用户名、密码和邮箱。
npm login
# 发布3,发布到 npm
npm publish
# 检查发布是否成功,可能有十秒延迟,我们去 https://www.npmjs.com/~luckysnail ,这里 luckysnail 是你的 npm 账号名
# 本地运行 npm create lucky ,验证效果
看到这样,证明第一步就完成了!
第二步
这一步,我们需要在终端创建一个交互,来让用户使用脚手架安装想要的项目
# 引入 inquirer:命令行界面与用户交互的库
npm i inquirer
# 更新 src/index.js 代码
#!/usr/bin/env node
const inquirer = require("inquirer");
async function main() {
const answers = await inquirer.default.prompt([
{
type: "list",
name: "template",
message: "请选择你想要使用的模板:",
choices: ["Vue", "React", "React19", "NextJs"],
},
{
type: "input",
name: "customUrl",
message: "请输入项目名称:",
},
]);
let packageUrl = "";
if (answers.template === "custom-app") {
packageUrl = answers.customUrl;
} else {
packageUrl = `https://github.com/your-org/${answers.template}`;
}
console.log(`你选择的包地址是: ${packageUrl}`);
}
main();
# 重新 npm link 然后测试,没有问题,就 publish 再测试一下
inquirer 更多使用方法:https://www.npmjs.com/package/inquirer
第三步
最后一步,我们就需要根据用户的选择,然后生成对应的代码
# 安装需要的库,chalk:终端字体样式;ora:显示loading动画效果;download-git-repo:下载并提取git仓库
npm i chalk ora download-git-repo
# src/constants.js 定义常量
/** 项目列表 */
export const PROJECT_LIST = [
{
name: "vue",
desc: "vue3 + pinia + vue-router",
value: "https://github.com/axin-s-Template/vue-light-starter.git",
},
{
name: "React19",
desc: "React 19 + Tailwind 3 + Biome + RSBuild + shadui/cn 轻量启动模版",
value: "https://github.com/axin-s-Template/react19-light-starter",
},
{
name: "React",
desc: "react 轻量模版(适合开发简单应用),使用 Vite + TypeScript + TailwindCss + shadcn/ui + Zustand + react-router-dom 的简单 SPA 启动模版",
value: "https://github.com/axin-s-Template/react-light-starter",
},
{
name: "NextJs",
desc: "Next.js 14+ 基础启动模板",
value: "https://github.com/axin-s-Template/Nextjs-Boilerplate",
},
];
# 设置 package.json 的 "type": "module",
# 更新 src/index.js
#!/usr/bin/env node
import inquirer from "inquirer";
import chalk from "chalk";
import ora from "ora";
import { promisify } from "util";
import downloadGitRepo from "download-git-repo";
import { PROJECT_LIST } from "./constants.js";
const download = promisify(downloadGitRepo);
async function main() {
const answers = await inquirer.prompt([
{
type: "list",
name: "template",
message: "请选择你想要使用的模板:",
choices: PROJECT_LIST.map((item) => ({
name: `${item.name} - ${item.desc}`,
value: item.name.toLowerCase(),
})),
},
{
type: "input",
name: "projectName",
message: "请输入项目名称:",
validate: (input) => {
if (!input.trim()) {
return "项目名称不能为空";
}
return true;
},
},
]);
const selectedTemplate = PROJECT_LIST.find((item) => item.name.toLowerCase() === answers.template);
if (!selectedTemplate) {
console.log(chalk.red("未找到选择的模板"));
return;
}
const spinner = ora("正在下载模板...").start();
try {
const repoUrl = selectedTemplate.value.replace("https://github.com/", "").replace(".git", "");
await download(repoUrl, answers.projectName);
spinner.succeed(chalk.green("模板下载成功!"));
console.log(chalk.blue("\n开始使用:"));
console.log(chalk.cyan(` cd ${answers.projectName}`));
console.log(chalk.cyan(" npm install"));
console.log(chalk.cyan(" npm run dev\n"));
} catch (err) {
spinner.fail(chalk.red("下载失败:" + err.message));
process.exit(1);
}
}
main().catch((err) => {
console.error(chalk.red(err));
process.exit(1);
});
这里,我们运行 node ./src/index.js ,如果看到这样就证明没有问题啦!
到这里,我们基础的脚手架就算是搭建完成了!我们可以发布到npm 仓库,然后再试试,记得先运行 npm unlink create-lucky
。然后我们在运行 npm create lucky
,记得每次发布的时候去 package.json 中修改 version
优化
目前这一版本的脚手架工具其实还有很多缺点:
- 使用 js ,不好维护,脚手架本来就是团队才需要的,所以脚手架的代码,可能是要规范的
- 目前测试麻烦,使用 npm link 来进行测试,测试完成需要 npm unlink 来取消 link
- 开发一个命令行工具,连命令参数能力都没有,例如 -v --help 等等都是必须的
- 不支持动态生成项目的一些信息,例如:我希望通过模版创建的时候就把这个项目的基础信息(项目名称,描述等)都初始化好
- 终端不够美观,炫酷,这个反倒是很重要的,别人使用你的脚手架第一印象就是终端效果
所以,我们可以使用 monorepo + @clack/prompt 来进行优化一版本
- 使用 monorepo 来更好的维护代码和验证效果
- 使用 @clack/prompt 来提供更加美观炫酷的终端交互
关于优化,我会再写一篇!
总结
首先感谢你看到这里,看到着你说明你也认为搭建自己的脚手架是有价值的。在我看来脚手架的价值有两个:
- 从个人来讲:我觉得脚手架对个人来讲收益是最大的,因为在开发脚手架过程中,你会得到你自己认为的最佳实践,你会搭建出自己的开发规范,这培养了个人的工程化思维,追求极致的思想。
- 对他人来讲:他人能够通过你的脚手架快速搭建出一个项目骨架。使用你的最佳实践,为他节省了时间。所以在开发完成后,我们需要写文档来推广我们的脚手架
此文自动发布于:github issues