【源码学习】第⑧期 | 摸鱼神器!跟 vite-pretty-lint 学学自动化,解放双手~

164 阅读4分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

前言

    工欲善其事必先利其器,日常开发中我们想要统一代码规范肯定少不了eslintPrettier,既然是配套使用,那么配置的时候能不能一步到位呢?答案肯定是有的,它就是我们今天的主角vite-pretty-lint
    没听过vite-pretty-lint?没关系,因为我也是第一次接触🤣,但是看名字应该也能猜到跟批量自动化处理eslintPrettier有关。写了那么久代码,是时候让懂事的代码自己写代码了,开整开整~

任务清单

先列个简易版清单,好清楚自己要做些什么~
  • 下载源码,运行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项目添加eslintPrettier配置
cd your-project
// NPM
npm init vite-pretty-lint

// YARN
yarn create vite-pretty-lint

// PNPM
pnpm init vite-pretty-lint
  • 2.4 运行截图

图片.png

一行代码就自动生成了这么多文件跟配置,是不是很神奇?接下来就一起细看一下实现~

调试代码

    看过源码的童鞋应该都知道,调试前要先看README.md,这里也有教怎么调试运行代码,抛个截图:

图片.png

3.1 入口文件

图片.png

3.2 调试截图

图片.png


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')

    )

  );
  • 效果如下,咱们想要自己的命令行工具炫酷点也可以参照此做法:

图片.png

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、
  • 调试截图

图片.png

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!')

    );

  });
  • 完整运行结果

图片.png


总结

    不知不觉在断点调试的点点点中分析完了vite-pretty-lint,其实主体的思路就是交互式获取创建类型,然后利用babel转ast达到改变指定文件位置的目的,最后利用fs文件系统依次写入文件,这同时也给了我们一个开发自动化方向,即通过babel操作AST精准插入配置代码,又掌握了一个摸鱼技巧~😉

参考文献

enquirer