本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
这是源码共读的第35期 | 为 vite 项目自动添加 eslint 和 prettier
前言
平时的开发项目中总是会用到 Eslint 或者 Prettier去帮助我们做代码的校验和格式化, 这次学习和使用create-vite-pretty-lint 来解放双手, 一键配置Vite项目中的Eslint和Prettier
使用
先创建一个Vite项目
npm init vite
然后一键部署下
npm init vite-pretty-lint
关于 npm init 前面的文章也介绍过 就不费口舌了
流程
入口
先看README.md 了解项目的调试 安装 和一些基本信息,接着我们看package.json 命令的执行入口
npm run test
然后在想要测试的地方打下断点,接着进行调试!
这里对Lib 目录下 的文件做个简要分类:
- templates 不同框架及其TS版本用到的包以及对应的配置
- ast.js 解析vite配置文件时通过AST语法树转换用到的配置
- utils.js 得到用户选择对话函数执行完后的配置
- shared.js 默认的包信息以及配置 和处理vite配置文件的转换函数
主要文件(main.js)
主要是run函数部分的执行,先看顶部,得到命令执行目录 以及我们要写入的文件目录变量
//取出选项式对话函数 基于enquirer,之前有遇到类似的工具prompts
import { askForProjectType } from './utils.js';
const projectDirectory = process.cwd();
// 定义eslint配置文件
const eslintFile = path.join(projectDirectory, '.eslintrc.json');
// 定义prettierrc文件
const prettierFile = path.join(projectDirectory, '.prettierrc.json');
// 定义忽略文件
const eslintIgnoreFile = path.join(projectDirectory, '.eslintignore');
然后先看run函数第一部分,取出用户的选项
let projectType, packageManager;
try {
const answers = await askForProjectType();
//得到选项结果 配置的是Vue 还是React 以及使用的包管理器
//这里我觉得包管理器可以用之前install pkg的思路 去找锁文件 来确定这个包管理器
projectType = answers.projectType;
packageManager = answers.packageManager;
} catch (error) {
console.log(chalk.blue('\n👋 Goodbye!'));
return;
}
askForProjectType函数
//读取templates目录下所有的文件, 通过path.parse 取出文件名返回成一个数组作为选项
export function getOptions() {
const OPTIONS = [];
fs.readdirSync(path.join(__dirname, 'templates')).forEach((template) => {
const { name } = path.parse(path.join(__dirname, 'templates', template));
OPTIONS.push(name);
});
return OPTIONS;
}
export function askForProjectType() {
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'],
},
]);
}
如果有看过之前create-vite 以及create-vue的文章,这一步很好理解,getOptions函数遍历 templates目录下所有的文件,并返回文件名组成的数组作为选项使用
//根据选择的框架 得到对应的 要安装的包信息以及 eslint配置
const { packages, eslintOverrides } = await import(
`./templates/${projectType}.js`
);
//将公共包 和 框架对应的包 以及选项 拼接 以此类推
const packageList = [...commonPackages, ...packages];
const eslintConfigOverrides = [...eslintConfig.overrides, ...eslintOverrides];
const eslint = { ...eslintConfig, overrides: eslintConfigOverrides };
const commandMap = {
npm: `npm install --save-dev ${packageList.join(' ')}`,
yarn: `yarn add --dev ${packageList.join(' ')}`,
pnpm: `pnpm install --save-dev ${packageList.join(' ')}`,
};
const viteConfigFiles = ['vite.config.js', 'vite.config.ts'];
//通过遍历项目目录 将文件和目录拼接起来 然后 去检查 实际存在的 Vite配置文件 并返回这个文件对应路径文本
const [viteFile] = viteConfigFiles
.map((file) => path.join(projectDirectory, file))
.filter((file) => fs.existsSync(file));
这一部分则是通过用户选择的框架以及包管理器, 得到要安装的包名和配置。 然后通过拼接得到所有的包名,和对应的选项以及Vite配置文件的目录
const viteConfig = viteEslint(fs.readFileSync(viteFile, 'utf8'));
//shared.js
export function viteEslint(code) {
//解析传入的vite 配置文件代码 code
const ast = babel.parseSync(code, {
sourceType: 'module',
comments: false,
});
const { program } = ast;
//取出所有的import 语句 并删除尾部注释
const importList = program.body
.filter((body) => {
return body.type === 'ImportDeclaration';
})
.map((body) => {
delete body.trailingComments;
return body;
});
//如果 import语法中 有包含vite-plugin-eslint 这个值 则直接返回 code 代表已经安装了eslint插件
if (importList.find((body) => body.source.value === 'vite-plugin-eslint')) {
return code;
}
//所有非import语句
const nonImportList = program.body.filter((body) => {
return body.type !== 'ImportDeclaration';
});
//所有 export default 默认导出语句
const exportStatement = program.body.find(
(body) => body.type === 'ExportDefaultDeclaration'
);
//这里应该是找export default 默认导出语句中的 调用表达式 参数类型为 object的
//找到object中的 plugins 这个key, 并给它的值push eslintPluginCall这个对象
if (exportStatement.declaration.type === 'CallExpression') {
const [argument] = exportStatement.declaration.arguments;
if (argument.type === 'ObjectExpression') {
const plugin = argument.properties.find(
({ key }) => key.name === 'plugins'
);
if (plugin) {
plugin.value.elements.push(eslintPluginCall);
}
}
}
//之前取出的 所有import语句后面 增加eslint插件的import
importList.push(eslintImport);
//增加一个空行
importList.push(blankLine);
//赋值
program.body = importList.concat(nonImportList);
ast.program = program;
return babel.transformFromAstSync(ast, code, { sourceType: 'module' }).code;
}
对Babel和AST转换比较陌生,只能模棱两可的看个七七八八,注释写了个大概。 主要理解它干了什么然后接着往下看。
//取出要安装的包命令
const installCommand = commandMap[packageManager];
//特效
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;
}
//命令执行成功后 写入eslint prettier eslintignore viteconfig 文件
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!')
);
});
最后就是执行安装包命令,然后写入对应的文件,然后就全部执行完成了!
小结
对Babel、AST还比较陌生, 后面会去刷 Babel插件通关秘籍 补补课。 另外附上AST语法树转换的在线网址:astexplorer帮助理解
整体下来主要核心就是先收集用户的框架以及包管理器,然后通过模板 取出默认定义好的包名以及配置选项,然后对Vite配置文件做AST语法转换插入 Eslint的Import声明语句和Plugins声明,最后 通过 node的线程API去执行包安装命令,写入文件就完事了。
一些以后开发说不定用到的库:
- gradient-string 控制台输出彩色文字
- nanospinner 控制台输出转圈圈的特效
- chalk 类似gradient-string 增强文字的显示
import gradient from 'gradient-string';
import { createSpinner } from 'nanospinner';
import chalk from 'chalk';
console.log(
gradient.summer('\n🚀 Welcome to Eslint & Prettier Setup for Vite!\n')
);
const spinner = createSpinner('Installing packages...').start();
setTimeout(() => {
spinner.success({ text: chalk.bold.green('All done! 🎉'), mark: '✔' });
}, 2000)