如何开发一款前端的脚手架[基础篇]
简介
最近在研究关于前端的一些内部原理实现,这章主要来学习一下如何开发一款前端的脚手架工具
为下一篇脚手架的执行过程和原理
做铺垫。
概述
为什么我们需要学习脚手架的开发呢? 我个人觉得原因有如下二点:
- 在我们现代基于前端框架开发过程中往往会高频的使用到
vue
、create-react-app
、vite
等脚手架工具,我们需要了解他的构建,以至于在报错时能够清晰的定位问题。 - 脚手架的目的就是摆脱
构建工程时的重复的工作
尤其在一些比较通用性的工程上(类似一些辅助插件致力于减少重复工作,提升工作效能。我们就可以有更多时间摸鱼.jpg)
核心包的介绍
下面来介绍一些我在开发脚手架使用到的第三方库
- commander: Commander强大的Node命令解析工具,其可以让我们更加简单的命令行参数
- root-check: 尝试降级具有root权限的进程的权限,如果失败,则阻止访问权限
- userhome: 跨操作系统获取用户主目录操作
- colors: 命令行输出样式
- semver: 版本号对比工具
- url-join: 将所有参数加在一起,并将结果标准化
- npminstall: 使用Npm快速安装
- inquirer: 常见交互式命令行用户界面的集合。
功能需求
- 提供脚手架交互面板
- 类似实现
vue create test -force
命令交互(具体如何下载模板不在这里实现,因为太多了,直接查看ak-cli源码)
功能实现
前期准备
初始化项目、安装对应的包、创建index.js文件、定义bin属性值
// index.js最顶部添加如下代码
#! /usr/bin/env node
#! /usr/bin/env node
是什么意思呢? 就是从环境变量获取到node、并且使用Node运行该文件。等价于在项目根目录执行node index.js
命令。
当然我们也可以写成#! /usr/bin/node
。这种写法是直接执行/usr/bin
目录下的node,这种写法不推荐因为这样子就把node固定位置了。但每个人的node安装目录会有所不同,所以推荐上面的#! /usr/bin/env node
写法
PS: 关于环境变量放在之后讨论
// package.json
"bin":{
"akclown-cli":"./index.js"
},
讲解bin
时,我们先探讨一下全局安装@vue/cli发生了什么
。答案如下:把@vue/cli依赖下载到node_modules下面,解析package.json文件,发现bin下面有一个配置,就会去node的bin目录下创建vue软链接。
因此我们在shell执行vue实际上是执行这个软连接映射的对应地址的文件。
bin:
表示内部命令对应的可执行文件的路径。
在没有发正式包
之前通过在项目根目录执行npm link
将这个包映射到全局的node_modules进行调试
推荐:阅读这篇文章,让你更加理解npm。后续有计划研究npm内部运行流程
root账号检测和降级
检查root,如果在root账户下运行时process.geteuid()方法获取的索引是0. 0就是超级管理员
如果当前文件创建者变为root账户,后续会有很多涉及到权限的问题
。因为很多操作都可能没有权限。可以通过ll
命令查看创建者
因此使用root-check会自动尝试降级
async function core() {
checkRoot();
}
core();
function checkRoot() {
const rootCheck = require('root-check');
// $ 尝试降级具有root权限的进程的权限,如果失败,则阻止访问权限
rootCheck();
}
检查用户主目录
userHome获取到用户目录,通过path-exists判断主目录是否存在
// console.log('userhome: ', userhome());
// userhome: C:\Users\ak
function checkUserHome() {
if (!(userHome && pathExists(userHome))) {
throw new Error(colors.red(`当前登陆用户主目录不存在`));
}
}
注册命令
akclown-cli create my-app --force
命令拆分如下
- 主命令:akclown-cli
- command: create
- 命令params: my-app
- 命令的options: --force
const pkg = require('../package.json');
const program = new Command();
function registerCommand() {
program
.name(Object.keys(pkg.bin)[0]) //名称
.usage('<command> [options]') // 用法声明
// $ 注册init命令
program
.command('create [projectName]')
.option('-f --force', '是否强制初始化项目')
.action(createProject);
program.parse(process.argv);
// $ 参数小于3个不解析,第一个是node 第二个是脚手架命令, 第三个才是option
// if (process.argv.length < 3) {
// program.outputHelp();
// }
// $ args不存在前两个参数 -- node\akclown-cli
if (program.args && program.args.length < 1) {
program.outputHelp();
}
}
通过上面代码就实现了如下交互面板。
创建项目逻辑编写
- 编写
createProject
函数传入到.action作为回到函数,commander在执行回调函数时会给我们提供的createProject
注入参数。 PS: 我在开发PAAS编辑器时提供给业务方的方法也经常这么设计。当内部依赖于外部自定义操作,但外部的执行又依赖于内部的一些内置的参数或者方法,就可以通过传入回调方式实现
function createProject(projectName, options) {
console.log('projectName, options: ', projectName, options);
}
输出的结构,很明显,我们可以拿到文件目录名称以及是否强制替换
- 通过
process.cwd()
获取当前文件目录并且判断当前文件目录是否为空.开头的隐藏文件
和node_modules文件
应该被忽略
const localPath = process.cwd();
// 判断当前目录是否为空
function isCwdEmpty(localPath) {
let fileList = fs.readdirSync(localPath);
// 忽略掉.开头文件 以及 node_modules目录
fileList = fileList.filter(
file => !file.startsWith('.') && !['node_modules'].includes(file)
);
return !fileList || fileList.length <= 0;
}
- 判断--force属性是否为true,如果是且当前目录不为空。弹出用户是否清空目录的提示框
const localPath = process.cwd();
if (!isCwdEmpty(localPath)) {
// 1.1 询问是否继续创建
let ifContinue = false;
// $ 强制更新 - 不给予提示
if (!options.force) {
ifContinue = (
await inquirer.prompt({
type: 'confirm',
name: 'ifContinue',
default: false,
message: '当前文件不为空,是否继续创建项目?',
})
).ifContinue;
if (!ifContinue) {
return;
}
}
if (ifContinue || options.force) {
// $ 给用户做二次确认框
const { confirmDelete } = await inquirer.prompt({
type: 'confirm',
name: 'confirmDelete',
default: false,
message: '是否确认清空当前目录下的文件?',
});
if (confirmDelete) {
// $ 清空当前目录
// fse.emptyDirSync(localPath);
}
}
效果如下:
- 获取到用户选择的模板'react' | 'babel' | 'vue'
projectTemplate = await inquirer.prompt([
{
type: 'list',
name: 'projectTemplate',
message: `请选择项目模板`,
choices: [
{ value: 'react', name: 'react' },
{ value: 'babel', name: 'babel' },
{ value: 'vue', name: 'vue' }
],
}
]);
console.log('projectTemplate: ', projectTemplate);
console.log('projectName: ', projectName);
总结:
通过上面的步骤我们就初步实现了,简单的命令行交互。具体如何下载模板以及安装模板依赖这里不再阐述(有了基础在对具体的业务需求进行定制即可)。有兴趣的同学可以查看AK-cli. 当然这些只是就基本的脚手架开发。还有其他更多内容,有用到后续再针对性的查找即可。例如:Node多进程来实现性能的提升,没记错的话npm install就使用了node的多进程
、ejs模板渲染修改业务组件库的package.json数据
、结合simple-git做git提交规范
等
其他
- 在日常编码中,如果存在A方法执行完在执行B方法再执行C方法... 的需求,可以使用
微任务队列思维
。 A->B->C
let chain = Promise.resolve();
chain = chain.then(() => A);
chain = chain.then(() => B);
chain = chain.then(() => C);
...
- 推荐: 使用npkil脚手架轻松删除node_modules。尤其项目是多包管理结构的。谁用谁知道,一个字"香"
npm link
添加全局包映射,npm unlink
取消全局包映射。当然npm link ~
添加参数实现包之间的关联npm pack
打包压缩包。当你在本地开发包时你还不想npm publish
发布,但是内部业务团队急着需要某个功能,那你可以打包个npm压缩包给他们。 他们使用通过npm install 压缩包的本地路径
即可不发布别人也可以使用- 本demo的git仓库
转载自:https://juejin.cn/post/7249904542866915383