<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何為前端項目一鍵自動添加eslint和prettier的支持

          共 13247字,需瀏覽 27分鐘

           ·

          2022-07-11 14:16

          本文來自讀者@那個曾經的少年回來了 寫的源碼共讀35期筆記文章,授權投稿,寫的真好。


          前言

          我之前好多次都是一步一步的安裝eslint和prettier及相關依賴,一個配置文件一個配置文件的粘貼復制,并修改其中的相關配置。而且可能會在每個項目中都要去處理,如果項目工程規(guī)劃化以后,eslint和prettier確實是項目少不了的配置。不知道你有沒有像我一樣操作過呢?

          那么有沒有一種更簡單的方式去處理呢?答案是我終于遇到了。通過若川大佬的源碼共讀活動發(fā)現了,真的是太棒了。

          本文以vite腳手架創(chuàng)建的項目為基礎進行研究的,如果是其他腳手架創(chuàng)建的項目,那么就要自己去修改處理,但是原理是一樣的。

          那么接下來,我就要來一探究竟,先看看如何使用,然后查閱一下它的源碼,看看它到底是如何實現的呢?

          1、vite創(chuàng)建項目

          • 創(chuàng)建項目
          yarn create vite

          一頓操作以后項目就創(chuàng)建完畢了

          image.png
          • 2、安裝依賴
          yarn
          • 3、運行項目
          yarn dev
          • 4、運行初始化eslint和prettier命令
          yarn create vite-pretty-lint

          先來看沒有執(zhí)行命令前的文件目錄

          image.png

          再來看執(zhí)行完命令后的文件目錄

          image.png

          可以發(fā)現文件目錄中增加了eslint和prettier的相關配置,package.json中增加了相關的依賴、以及vite.config.xx文件也增加了相關配置,具體的文件變更可以查看https://github.com/lxchuan12/vite-project/commit/6cb274fded66634191532b2460dbde7e29836d2e。

          一個命令干了這么多事情,真的太優(yōu)秀了。接下來我們就去看看這如此優(yōu)秀的源代碼吧

          2、整個過程的示意圖

          通過大致的查看源代碼,簡單總結出來的代碼執(zhí)行過程示意圖,僅供參考

          未命名文件 (3).png

          3、源碼調試過程

          3.1、找到調試代碼的位置

          通過package.json中的bin節(jié)點可以發(fā)現,yarn create vite-pretty-lint最終執(zhí)行的便是lib/main.js中的代碼

            "bin": {
              "create-vite-pretty-lint""lib/main.js"
            },

          3.2、 開始調試的命令

          因為我們現在只是要執(zhí)行lib/main.js這個入口文件,通過package.jsonscripts 也沒有發(fā)現執(zhí)行命令,所以現在我們可以直接通過node來運行代碼

          node lib/main.js

          調試成功的結果如下圖所示

          企業(yè)微信截圖_16564645675849.png

          3.3、 查看頭部引入的模塊

          • chalk終端多色彩輸出
          npm i chalk

          import chalk from 'chalk'

          const log = console.log
          // 字體背景顏色設置
          log(chalk.bgGreen('chalk打印設置') )

          // 字體顏色設置
          log(chalk.blue('Hello') + ' World' + chalk.red('!'))

          // 自定義顏色
          const custom = chalk.hex('#F03A17')
          const bgCustom = chalk.bgHex('#FFFFFF')
          log(custom('customer'))
          log(bgCustom('bgCustom'))

          執(zhí)行效果如下圖所示

          image.png
          • gradient 文字顏色漸變
          // 安裝
          npm i gradient-string
          // 引入
          import gradient  from 'gradient-string'

          // 使用
          console.log(gradient('cyan''pink')('你好啊賽利亞歡迎來到編碼世界'));
          console.log(gradient('cyan''pink')('你好啊賽利亞歡迎來到編碼世界'));
          console.log(gradient('cyan''pink')('你好啊賽利亞歡迎來到編碼世界'));
          console.log(gradient('cyan''pink')('你好啊賽利亞歡迎來到編碼世界'));
          console.log(gradient('cyan''pink')('你好啊賽利亞歡迎來到編碼世界'));

          執(zhí)行效果如下圖所示

          image.png
          • child_process node.js中的子進程。

            在node.js中,只有一個線程執(zhí)行所有的操作,如果某個操作需要大量消耗CPU資源的話,后續(xù)的操作就需要等待。后來node.js就提供了一個child_process模塊,通過它可以開啟多個子進程,在多個子進程之間可以共享內存空間,可以通過子進程的互相通信來實現信息的交換。

          import { exec } from 'child_process';

          exec('ls',(error, stdout,stderr)=> {
              if(error) {
                  console.log(error)
                  return;
              }
              console.log('stdout: ' + stdout)
              console.log('執(zhí)行其他操作')
          })

          執(zhí)行效果如下圖所示

          image.png
          • fs fs用來操作文件的模塊
          import fs from 'fs'

          // 同步的讀取方法,用來讀取指定文件中的內容
          fs.readFileSync() 
          // 同步的寫入方法,用來向指定文件中寫內容
          fs.writeFileSync() 
          • path路徑分類
          import path from 'path';

          // 拼接路徑
          console.log(path.join('src''task.js'));  // src/task.js

          • nanospinner命令行中的加載動畫
          // 安裝
          npm i nanospinner

          // 引入模塊
          import { createSpinner } from 'nanospinner';

          const spinner = createSpinner('Run test').start()

          setTimeout(() => {
            spinner.success()
          }, 1000)

          執(zhí)行效果如下圖所示(Run test在加載的一個效果)

          3.gif
          • enquirer (utils.js文件)

          交互式詢問CLI 簡單說就是交互式詢問用戶輸入

          npm i enquirer 

          import enquirer from 'enquirer' 

          let tempArray = ['major(1.0.0)','minor(0.1.0)''patch(0.0.4)'"customer" ]
          const { release } = await enquirer.prompt({
              type'select',
              name'release',
              message'Select release type',
              choices: tempArray
          })

          if(release === 'customer') {
              console.log(release, 'customer')
          else {
              const targetVersion = release.match(/\((.*)\)/)[1]
              console.log(targetVersion, 'targetVersion')
          }

          執(zhí)行效果如下圖所示:先出來一個下拉選擇,選擇完后根據if判斷進行輸出

          4.gif

          3.4、 調試具體代碼

          3.4.1、 main.js中的入口
          async function run({
              // 所有的邏輯代碼
          }

          run().catch((e) => {
            console.error(e);
          });

          通過run函數封裝異步方法,這樣最外面調用run函數時可以通過異步方法的catch捕獲錯誤異常。

          看一個小例子

          const runTest = async () => {
              console.log('Running test')
              throw new Error('run test報錯了')
          }
           
          runTest().catch(err => {
              console.log('Error: ' + err)
          })

          執(zhí)行后打印順序如下

          Running test
          ErrorError: run test報錯了

          可以發(fā)現catch中截獲了異常

          接下來開始進入run函數了

          3.4.2、 打印色彩字體
          // 這個看上面的引入模塊解析即可
          console.log(
              chalk.bold(
                gradient.morning('\n?? Welcome to Eslint & Prettier Setup for Vite!\n')
              )
          );
          3.4.3、 交互式命令行
          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'],
              },
            ]);
          }

            try {
              const answers = await askForProjectType();
              projectType = answers.projectType;
              packageManager = answers.packageManager;
            } catch (error) {
              console.log(chalk.blue('\n?? Goodbye!'));
              return;
            }

          getOptions 函數根據fs.readdirSync讀取項目工程template文件夾下的所有文件,并通過path.parse轉換對象,來獲取文件名稱name

          askForProjectType函數通過enquirer.prompt返回兩個交互式命令行,供用戶進行選擇projectType選擇項目類型:【react-ts】 【react】【vue-ts】 【vue】packageManager選擇項目包管理方式:【npm】 【yarn】

          3.4.4、根據交互命令行返回結果進行匹配模板

          假如我們上面選擇的是[vue-ts]

          const { packages, eslintOverrides } = await import(
              `./templates/${projectType}.js`
          );

          /template/vue-ts.js模板中的代碼(其中代碼較多但一看就明白我就不貼了),就是export導出了兩個固定的模板變量數組,packages則相當于要引入的npm模塊列表,eslintOverrides這算是.eslintrc.json初始化模板。


          3.4.5、拼接變量數組
          const packageList = [...commonPackages, ...packages];
          const eslintConfigOverrides = [...eslintConfig.overrides, ...eslintOverrides];
          const eslint = { ...eslintConfig, overrides: eslintConfigOverrides };

          commonPackagesshared.js中預定義的公共的npm 模塊eslint則是通過公共npm模塊中的eslintConfig和上面選擇的template/xxxx.js中的進行拼接組成。

          3.4.6、 生成安裝依賴包的命令
          const commandMap = {
              npm`npm install --save-dev ${packageList.join(' ')}`,
              yarn`yarn add --dev ${packageList.join(' ')}`,
          };

          packageList數組通過join轉換為字符串,通過命令將所有拼接npm模塊一起安裝

          image.png
          3.4.7、 讀取項目的vite配置文件
            const projectDirectory = process.cwd();
            
            const viteJs = path.join(projectDirectory, 'vite.config.js');
            const viteTs = path.join(projectDirectory, 'vite.config.ts');
            const viteMap = {
              vue: viteJs,
              react: viteJs,
              'vue-ts': viteTs,
              'react-ts': viteTs,
            };

            const viteFile = viteMap[projectType];
            const viteConfig = viteEslint(fs.readFileSync(viteFile, 'utf8'));
            const installCommand = commandMap[packageManager];

            if (!installCommand) {
              console.log(chalk.red('\n? Sorry, we only support npm and yarn!'));
              return;
            }

          根據選擇的項目類型,來拼接vite.config的路徑,并讀取項目中的vite.config配置文件

          上面用到了一個函數viteEslint,這個具體的實現可以去看shared.js中,主要就是讀取文件內容后,傳入的參數code,就是vite.config.ts中的所有字符

          通過babel的parseSync轉換為ast。ast對象如下圖所示

          1656558646620.png

          對ast數據進行了一系列的處理后,再通過babeltransformFromAstSync將ast轉換為代碼字符串。

          對于babel處理這一塊我也不太了解,有時間我得去加一下餐,具體的可以參考 https://juejin.cn/post/6844904008679686152

          3.4.8 執(zhí)行命令、執(zhí)行完將eslint和prettier配置重寫
          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;
              }

              const eslintFile = path.join(projectDirectory, '.eslintrc.json');
              const prettierFile = path.join(projectDirectory, '.prettierrc.json');
              const eslintIgnoreFile = path.join(projectDirectory, '.eslintignore');

              fs.writeFileSync(eslintFile, JSON.stringify(eslint, null2));
              fs.writeFileSync(prettierFile, JSON.stringify(prettierConfig, null2));
              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!')
              );
            });

          首先通過createSpinner來創(chuàng)建一個命令行中的加載,然后通過child_process中的exec來執(zhí)行[3.4.6]中生成的命令,去安裝依賴并進行等待。

          如果命令執(zhí)行成功,則通過fs.writeFileSync將生成的數據寫入到三個文件當中.eslintrc.json.prettierrc.json.eslintignorevite.config.xx

          4、npm init、npx

          印象里面大家可能對它的記憶可能都停留在,npm init之后是快速的初始化package.json,并通過交互式的命令行讓我們輸入需要的字段值,當然如果想直接使用默認值,也可以使用npm init -y

          create-app-react創(chuàng)建項目命令,官網鏈接可以直接查看 https://create-react-app.dev/docs/getting-started

          //官網的三種命令
          npx create-react-app my-app
          npm init react-app my-app
          yarn create react-app my-app

          //我又發(fā)現npm create也是可以的
          npm create react-app my-app

          上述這些命令最終效果都是可以執(zhí)行創(chuàng)建項目的

          同樣的vite創(chuàng)建項目的命令

          //官網的命令
          npm create vite@latest
          yarn create vite
          pnpm create vite

          // 指定具體模板的
          // npm 6.x 
          npm create vite@latest my-vue-app --template vue 
          //npm 7+, extra double-dash is needed: 
          npm create vite@latest my-vue-app -- --template vue

          yarn create vite my-vue-app --template vue

          pnpm create vite my-vue-app --template vue

          可以發(fā)現vite官網沒有使用npx命令,不過我在我自己電腦上嘗試了另外幾個命令確實也是可以的

          npx create-vite my-app
          npm init vite my-app
          image.png

          通過上面的對比可以一個小問題,yarn create去官網查了是存在這個指令的,官網地址可看 https://classic.yarnpkg.com/en/docs/cli/create#search

          而對于npm create這個命令在npm官網是看不到的,但是在一篇博客中發(fā)現了更新日志

          image.png

          意思就是說npm create xxxnpm init xxx 以及yarn create xxx效果是一致的。那么我們來本文的命令行

          // 我們是通過npm安裝的,并且包名里是包含create的
          npm i create-vite-pretty-lint

          // 那么以下幾種方式都可以使用的
          npm init vite-pretty-lint
          npm create vite-pretty-lint
          yarn create vite-pretty-lint
          npx create-vite-pretty-lint

          再來看一下npx

          假如我們只在項目中安裝了vite,那么node_modules.bin文件夾下是會存在vite指令的

          image.png

          如果我們想在該項目下執(zhí)行該命令第一種方式便是

          image.png

          第二種方式就是直接在package.json的scripts屬性下

          image.png

          關于npx的詳細說明可以看一下阮一峰大佬的精彩分享 http://www.ruanyifeng.com/blog/2019/02/npx.html

          5、總結

          • npm init xxx的妙用,以及對npx的了解,感覺對package.json的每一個屬性,可以專門去學習一下

          • 對于自動添加eslint和prettier配置的原理分析

          • .eslintrc.json、.eslintignore、.prettierrc.json算是直接新增文件,處理相對簡單一些

          • 最重要的學習點:對vite.config文件在原有基礎上的修改,這里就涉及到了AST抽象語法樹

          6、加餐 V8下的AST抽象語法樹

          有興趣的話可以看看我前幾天剛剛總結的關于V8引擎是如何運行JavaScript代碼的,其中就涉及到關于AST的部分https://juejin.cn/post/7109410330295402509。

          接下來有時間我會簡單的把AST詳細的學習一下,查了很多資料發(fā)現AST還是非常重要的,無論是babel、webpack、vite、vue、react、typescript等都使用到了AST。



          瀏覽 55
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  中文字幕在线官网 | 做爱污污短视屏在线观看 | 大香蕉亚洲视频 | 亚洲一级a免费在线观看 | 99热在线精品免费 |