1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 13247字,需瀏覽 27分鐘

         ·

        2022-07-11 14:16

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


        前言

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

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

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

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

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

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

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

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

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

        image.png

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

        image.png

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

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

        2、整個過程的示意圖

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

        未命名文件 (3).png

        3、源碼調(diào)試過程

        3.1、找到調(diào)試代碼的位置

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

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

        3.2、 開始調(diào)試的命令

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

        node lib/main.js

        調(diào)試成功的結(jié)果如下圖所示

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

        3.3、 查看頭部引入的模塊

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

        import chalk from 'chalk'

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

        // 字體顏色設(shè)置
        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中的子進(jìn)程。

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

        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'

        // 同步的讀取方法,用來讀取指定文件中的內(nèi)容
        fs.readFileSync() 
        // 同步的寫入方法,用來向指定文件中寫內(nèi)容
        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í)行效果如下圖所示:先出來一個下拉選擇,選擇完后根據(jù)if判斷進(jìn)行輸出

        4.gif

        3.4、 調(diào)試具體代碼

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

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

        通過run函數(shù)封裝異步方法,這樣最外面調(diào)用run函數(shù)時可以通過異步方法的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ā)現(xiàn)catch中截獲了異常

        接下來開始進(jìn)入run函數(shù)了

        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 函數(shù)根據(jù)fs.readdirSync讀取項目工程template文件夾下的所有文件,并通過path.parse轉(zhuǎn)換對象,來獲取文件名稱name。

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

        3.4.4、根據(jù)交互命令行返回結(jié)果進(jìn)行匹配模板

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

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

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


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

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

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

        packageList數(shù)組通過join轉(zhuǎn)換為字符串,通過命令將所有拼接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;
          }

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

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

        通過babel的parseSync轉(zhuǎn)換為ast。ast對象如下圖所示

        1656558646620.png

        對ast數(shù)據(jù)進(jìn)行了一系列的處理后,再通過babeltransformFromAstSync將ast轉(zhuǎn)換為代碼字符串。

        對于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]中生成的命令,去安裝依賴并進(jìn)行等待。

        如果命令執(zhí)行成功,則通過fs.writeFileSync將生成的數(shù)據(jù)寫入到三個文件當(dāng)中.eslintrc.json、.prettierrc.json、.eslintignore、vite.config.xx。

        4、npm init、npx

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

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

        //官網(wǎng)的三種命令
        npx create-react-app my-app
        npm init react-app my-app
        yarn create react-app my-app

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

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

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

        //官網(wǎ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ā)現(xiàn)vite官網(wǎng)沒有使用npx命令,不過我在我自己電腦上嘗試了另外幾個命令確實也是可以的

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

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

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

        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

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

        5、總結(jié)

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

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

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

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

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

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

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



        瀏覽 55
        點贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            北条麻妃性爱 | 精品人妻无码一区二区三区三级 | 国产做爰又粗又大又爽小妖精 | 国产精品视频2021 | 久久国产主播 | 91久久人澡人妻人人做人精品 | 国产网红主播三级精品视频 | 国产高清一级毛片在线不卡 | 操逼综合 | 国产做爰免费观看 |