核心要点
- 前端脚手架概念
- 实现前端脚手架
什么是前端脚手架?
随着前端工程化的概念越来越深入人心,脚手架应运而生。简单来说,「前端脚手架」就是指通过选择几个选项快速搭建项目基础代码的工具
前端脚手架可帮我们做什么?
- 可以帮助我们快速生成项目的基础代码
- 脚手架工具的项目模板经过了开发者的提炼和检验,一定程度上代表了某类项目的最佳实践
- 脚手架工具支持使用自定义模板,我们可以根据不同的项目进行“定制”
我为什么要搭建一个前端脚手架?
主要在开发过程中,要做的项目很多,有涉及到vue2、vue3、react、uniapp、小程序等等,然后又有PC端、移动端;针对这些端的项目,我搭建好了基础的开发模板,但是呢,感觉每次新建一个项目,都需要从仓库中下载,感觉总少了点什么。于是想到了像vue-cli一样,搭建一个脚手架来创建项目行不行呢?
所以,我的目标很明确,我的脚手架的功能就是通过命令的方式,下载不同的项目模板,然后供所有项目组人员快速建立项目模板。
实现前端脚手架
1、安装前端脚手架制作所需要的一些插件
npm install chalk@4.0.0 commander@9.4.1 download-git-repo@3.0.2 fs-extra@11.1.0 inquirer@8.0.0 log-symbols@4.0.0 ora@5.0.0 path@0.12.7 update-notifier@4.0.0
"chalk": "^4.0.0", //用于修改终端(terminal)输出的字符串样式,包括字体色、背景色、字体样式
"commander": "^9.4.1", //是 node.js 命令行解决方案
"download-git-repo": "^3.0.2", //主要用来从一个代码仓库中下载文件的到目标文件夹
"fs-extra": "^11.1.0", //加强版的 fs(node 文件系统模块),主要用于操作文件
"inquirer": "^8.0.0", //inquirer.js 是一个用来实现命令行交互式界面的工具集合,它帮助我们实现与用户的交互式交流
"log-symbols": "^4.0.0", //为各种日志级别提供着色的符号
"ora": "^5.0.0", //主要用于创建和展示终端加载动画
"path": "^0.12.7", //主要用于格式化或拼接完整路径
"update-notifier": "^4.0.0" //检测 npm 包是否更新
2、文件结构如下,创建相应的文件,配置 package.json文件
3、编写 /bin/index.js,主要是编写交互的命令以及对应执行的操作
#!/usr/bin/env node
// 请求 commander 库
const program = require('commander')
// 从 package.json 文件中请求 version 字段的值,-v和--version是参数
program.version(require('../package.json').version, '-v, --version')
// 请求 lib/update.js
const updateChk = require('../lib/update')
// upgrade 检测更新
program
.command('upgrade')
.description("Check the mtt-cli version.")
.action(() => {
updateChk()
});
// 请求 lib/init.js
const initProject = require('../lib/create')
// init 初始化项目
program
.name('mtt-cli')
.usage('<commands> [options]')
.command('create <project_name>')
.description('create a new project')
.action(project => {
initProject(project)
})
// 解析命令行参数
program.parse(process.argv)
4、编写 /lib/create.js,主要的操作将会在这里完成
// 请求 fs-extra 库,用于文件操作
const fse = require('fs-extra');
// 请求 ora 库,用于初始化项目时等待动画
const ora = require('ora');
// 请求 chalk 库
const chalk = require('chalk');
// 请求 log-symbols 库
const symbols = require('log-symbols');
// 请求 inquirer 库,用于控制台交互
const inquirer = require('inquirer');
const path = require('path');
// 请求 download.js 文件,模板不在本地时执行该操作
const dlTemplate = require('./download');
// prompt文件
const promptProjectCover = require('./promptModules/projectCover');
const promptProjectSelect = require('./promptModules/projectSelect');
// 初始化项目
async function initProject(projectName) {
try {
const targetDir = path.join(process.cwd(), projectName);
if (fse.existsSync(targetDir)) {
// 项目重名时提醒用户
inquirer.prompt(promptProjectCover).then((answers) => {
if (answers.projectCover) {
createProject(targetDir, true);
} else {
console.log(symbols.error, chalk.red(`项目名称已存在,请重新设置!`));
process.exit();
}
}).catch((error) => {
console.log(symbols.error, chalk.red(`error:${error}`));
process.exit();
});
} else {
createProject(targetDir);
}
} catch (err) {
console.error(err);
process.exit();
}
}
// 开始创建项目
async function createProject(targetDir, removeDir) {
// 执行控制台交互
inquirer.prompt(promptProjectSelect).then(async (answers) => {
// 获取选择的模板
const projectSelect = answers.projectSelect;
// Spinner 初始设置
const initSpinner = ora(chalk.cyan('创建目录...'));
// 开始执行等待动画
initSpinner.start();
// 移除已有项目
if(removeDir){
try {
await fse.remove(targetDir)
} catch (err) {
// 如果出错,Spinner 就改变文字信息
initSpinner.text = chalk.red(`移除原有目录失败: ${err}`);
// 终止等待动画并显示 X 标志
initSpinner.fail();
// 退出进程
process.exit();
}
}
//判断目录是否存在,不存在则创建
fse.ensureDir(targetDir);
// 开始下载模板
try {
initSpinner.text = `下载模板...`;
await dlTemplate(targetDir, projectSelect);
}catch (e) {
// 如果成功,Spinner 就改变文字信息
initSpinner.text = chalk.red(e);
// 终止等待动画并显示 ✔ 标志
initSpinner.fail();
// 退出进程
process.exit();
}
// 如果成功,Spinner 就改变文字信息
initSpinner.text = '初始化项目完成.';
// 终止等待动画并显示 ✔ 标志
initSpinner.succeed();
}).catch((error) => {
console.log(symbols.error, chalk.red(error));
});
}
// 将上面的 initProject(projectName) 方法导出
module.exports = initProject;
5、编写 /lib/download.js,用于从指定仓库中下载文件模板
// 请求 download-git-repo 库,用于下载模板
const download = require('download-git-repo');
const path = require('path');
// 请求 mirror.js 文件
const dataMirror = require('./mirror');
async function dlTemplate(targetDir, projectSelect) {
return new Promise(((resolve, reject) => {
download(dataMirror[projectSelect], targetDir, {clone: true}, function (err) {
if (err) {
reject(`模板下载失败. ${err}`)
} else {
resolve(`模板下载完成`)
}
});
}))
}
// 将上面的 dlTemplate() 方法导出
module.exports = dlTemplate;
6、编写 /lib/update.js,检测npm仓库中我们的脚手架插件是否有新的版本
// 引用 update-notifier 库,用于检查更新
const updateNotifier = require('update-notifier')
// 引用 chalk 库,用于控制台字符样式
const chalk = require('chalk')
// 引入 package.json 文件,用于 update-notifier 库读取相关信息
const pkg = require('../package.json')
// updateNotifier 是 update-notifier 的方法,其他方法可到 npmjs 查看
const notifier = updateNotifier({
// 从 package.json 获取 name 和 version 进行查询
pkg,
// 设定检查更新周期,默认为 1000 * 60 * 60 * 24(1 天)
// 这里设定为 1000 毫秒(1秒)
updateCheckInterval: 1000,
})
function updateChk() {
// 当检测到版本时,notifier.update 会返回 Object
// 此时可以用 notifier.update.latest 获取最新版本号
if (notifier.update) {
console.log(`New version available: ${chalk.cyan(notifier.update.latest)}, it's recommended that you update before using.`)
notifier.notify()
} else {
console.log('No new version is available.')
}
}
// 将上面的 updateChk() 方法导出
module.exports = updateChk
7、编写 /lib/mirror.js,我的远程仓库地址,目前是固定写死的,这部分可以根据自身未来的项目需求,写成可以自动配置的方式
module.exports = {
'0': 'direct:https://gitee.com/meme-project/m-template-vue2-mb.git',
'1': 'direct:https://gitee.com/meme-project/m-template-vue2-pc.git',
'2': 'direct:https://gitee.com/meme-project/m-template-vue3-mb.git',
'3': 'direct:https://gitee.com/meme-project/m-template-vue3-pc.git',
'4': 'direct:https://gitee.com/meme-project/m-template-wx.git',
};
8、编写/lib/promptModules/projectCover.js、/lib/promptModules/projectSelect.js,这个主要功能是编写inquirer终端命令行需要交互的命令
//projectSelect.js
module.exports = [{
type: 'list',
message: '请选择要创建的模板',
name: 'projectSelect',
default: 0,
choices: [
{value: 0, name: 'vue2 移动端模板'},
{value: 1, name: 'vue2 PC端后台模板'},
{value: 2, name: 'vue3 移动端模板'},
{value: 3, name: 'vue3 PC端后台模板'},
{value: 4, name: '微信小程序模板'}
]
}];
//projectCover.js
module.exports = [{
type: 'confirm',
message: '目录下存在相同名字的文件名称,是否覆盖?',
name: 'projectCover',
default: 'true'
}];
运行测试脚手架
1、以上把我要实现的整体的脚手架代码已经编写完成了,接下来我们测试下;首先通过npm link软连接的方式,在项目本地测试脚手架的功能,可以看到已经成功完成了我想要的功能
选择vue2 移动端模板进行下载
2、发布到npm仓库
通过 npm publish 命令,将当前脚手架发布到npm库
再次安装测试,得到一样的预期结果,完工,按照预期完成了自己想要的脚手架