我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情
- 本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
- 第35期 | 为 vite 项目自动添加 eslint 和 prettier
前言
工欲善其事必先利其器,日常开发中我们想要统一代码规范肯定少不了eslint跟Prettier,既然是配套使用,那么配置的时候能不能一步到位呢?答案肯定是有的,它就是我们今天的主角vite-pretty-lint。
没听过vite-pretty-lint?没关系,因为我也是第一次接触🤣,但是看名字应该也能猜到跟批量自动化处理eslint跟Prettier有关。写了那么久代码,是时候让懂事的代码自己写代码了,开整开整~
任务清单
先列个简易版清单,好清楚自己要做些什么~
- 下载源码,运行
vite-pretty-lint - 调试分析源码,掌握自动化处理核心思想
- 总结 get 到的点
环境准备
-
2.1 下载源码调试用
github地址
git clone https://github.com/tzsk/vite-pretty-lint.git
cd vite-pretty-lint && npm install
-
2.2 初始化vite项目
//
npm init vite / yarn create vite
create其实就是init的别称,这个我在【源码学习】第⑦期有详细写,这里就不再赘述了
-
2.3 给vite项目添加
eslint跟Prettier配置
cd your-project
// NPM
npm init vite-pretty-lint
// YARN
yarn create vite-pretty-lint
// PNPM
pnpm init vite-pretty-lint
-
2.4 运行截图
一行代码就自动生成了这么多文件跟配置,是不是很神奇?接下来就一起细看一下实现~
调试代码
看过源码的童鞋应该都知道,调试前要先看README.md,这里也有教怎么调试运行代码,抛个截图:
3.1 入口文件
3.2 调试截图
create-vite-pretty-lint代码分析
4.1 引入依赖及公共函数
//终端颜色库
import chalk from 'chalk';
// 渐变文字
import gradient from 'gradient-string';
// 子进程执行命令
import { exec } from 'child_process';
// fs文件系统模块
import fs from 'fs';
// 路径模块
import path from 'path';
// 旋转器
import { createSpinner } from 'nanospinner';
// 公用配置/方法
import {
commonPackages,
eslintConfig,
eslintIgnore,
prettierConfig,
viteEslint,
} from './shared.js';
// 询问项目类型
import { askForProjectType } from './utils.js';
4.2 获取路径
// 获取当前进程的路径
const projectDirectory = process.cwd();
// eslintrc.json 文件地址
const eslintFile = path.join(projectDirectory, '.eslintrc.json');
// prettierrc.json 文件地址
const prettierFile = path.join(projectDirectory, '.prettierrc.json');
// eslintignore 文件地址
const eslintIgnoreFile = path.join(projectDirectory, '.eslintignore');
4.3 执行函数run
async function run() {
...执行代码
}
run().catch((e) => {
console.error(e);
});
4.3.1 欢迎界面
console.log(
chalk.bold(
gradient.morning('\n🚀 Welcome to Eslint & Prettier Setup for Vite!\n')
)
);
-
效果如下,咱们想要自己的命令行工具炫酷点也可以参照此做法:
4.3.2 询问项目类型、npm包管理器
// 定义参数
let projectType, packageManager;
try {
// 获取用户选择的项目类型、npm包管理器
const answers = await askForProjectType();
projectType = answers.projectType;
packageManager = answers.packageManager;
} catch (error) {
// 错误退出进程
console.log(chalk.blue('\n👋 Goodbye!'));
return;
}
-
askForProjectType 函数
export function askForProjectType() {
// 用于创建交互式CLI提示符的Node.js库,地址:https://github.com/enquirer/enquirer
return enquirer.prompt([
{
type: 'select',
name: 'projectType',
message: 'What type of project do you have?',
choices: getOptions(),
},
{
type: 'select',
name: 'packageManager',
message: 'What package manager do you use?',
choices: ['npm', 'yarn', 'pnpm'],
},
]);
}
-
getOptions 函数
export function getOptions() {
const OPTIONS = [];
// 读取templates目录下的所有文件
fs.readdirSync(path.join(__dirname, 'templates')).forEach((template) => {
// 把获取到的文件名称存到选项里面
const { name } = path.parse(path.join(__dirname, 'templates', template));
OPTIONS.push(name);
});
return OPTIONS;
}
4.3.3 获取对应项目类型的包及配置
const { packages, eslintOverrides } = await import(
`./templates/${projectType}.js`
);
const packageList = [...commonPackages, ...packages];
const eslintConfigOverrides = [...eslintConfig.overrides, ...eslintOverrides];
const eslint = { ...eslintConfig, overrides: eslintConfigOverrides };
// 命令map
const commandMap = {
npm: `npm install --save-dev ${packageList.join(' ')}`,
yarn: `yarn add --dev ${packageList.join(' ')}`,
pnpm: `pnpm install --save-dev ${packageList.join(' ')}`,
};
4.3.4 读取vite配置文件
// vite配置文件
const viteConfigFiles = ['vite.config.js', 'vite.config.ts'];
// 用fs.existsSync判断vite配置文件是否存在
const [viteFile] = viteConfigFiles.map((file) =>path.join(projectDirectory, file)).filter((file) => fs.existsSync(file));
if (!viteFile) {
console.log(
chalk.red(
'\n🚨 No vite config file found. Please run this command in a Vite project.\n'
)
);
return;
}
// 根据选择的项目类型读取配置文件
const viteConfig = viteEslint(fs.readFileSync(viteFile, 'utf8'));
-
viteEslint实现
关键词AST--抽象语法树,推荐一个在线生成AST语法树小工具
export function viteEslint(code) {
// 利用babel把读取到的文件代码转ast
const ast = babel.parseSync(code, {
sourceType: 'module',
comments: false,
});
const { program } = ast;
// 删减代码 判断是否有引入`vite-plugin-eslint`,无则加入引入代码
ast.program = program;
// 将ast转换回代码
return babel.transformFromAstSync(ast, code, { sourceType: 'module' }).code;
}.prettierrc.json、
-
调试截图
4.3.5 执行装包命令并批量添加修改.eslintrc.json、.prettierrc.json、.eslintignore、vite配置文件等,也就是我们最开始的运行结果图
const installCommand = commandMap[packageManager];
// 不是支持装包命令报错
if (!installCommand) {
console.log(chalk.red('\n✖ Sorry, we only support npm、yarn and pnpm!'));
return;
}
// 装包提示旋转器
const spinner = createSpinner('Installing packages...').start();
// 执行装包命令
exec(`${commandMap[packageManager]}`, { cwd: projectDirectory }, (error) => {
// 报错提示
if (error) {
spinner.error({
text: chalk.bold.red('Failed to install packages!'),
mark: '✖',
});
console.error(error);
return;
}
// 分别写入文件
fs.writeFileSync(eslintFile, JSON.stringify(eslint, null, 2));
fs.writeFileSync(prettierFile, JSON.stringify(prettierConfig, null, 2));
fs.writeFileSync(eslintIgnoreFile, eslintIgnore.join('\n'));
fs.writeFileSync(viteFile, viteConfig);
// 成功提示
spinner.success({ text: chalk.bold.green('All done! 🎉'), mark: '✔' });
console.log(
chalk.bold.cyan('\n🔥 Reload your editor to activate the settings!')
);
});
-
完整运行结果
总结
不知不觉在断点调试的点点点中分析完了vite-pretty-lint,其实主体的思路就是交互式获取创建类型,然后利用babel转ast达到改变指定文件位置的目的,最后利用fs文件系统依次写入文件,这同时也给了我们一个开发自动化方向,即通过babel操作AST精准插入配置代码,又掌握了一个摸鱼技巧~😉