<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    如何為前端項(xiàng)目一鍵自動(dòng)添加eslint和prettier的支持

    共 13247字,需瀏覽 27分鐘

     ·

    2022-07-11 14:16

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


    前言

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

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

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

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

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

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

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

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

    先來(lái)看沒(méi)有執(zhí)行命令前的文件目錄

    image.png

    再來(lái)看執(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。

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

    2、整個(gè)過(guò)程的示意圖

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

    未命名文件 (3).png

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

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

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

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

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

    因?yàn)槲覀儸F(xiàn)在只是要執(zhí)行lib/main.js這個(gè)入口文件,通過(guò)package.jsonscripts 也沒(méi)有發(fā)現(xiàn)執(zhí)行命令,所以現(xiàn)在我們可以直接通過(guò)node來(lái)運(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')('你好啊賽利亞歡迎來(lái)到編碼世界'));
    console.log(gradient('cyan''pink')('你好啊賽利亞歡迎來(lái)到編碼世界'));
    console.log(gradient('cyan''pink')('你好啊賽利亞歡迎來(lái)到編碼世界'));
    console.log(gradient('cyan''pink')('你好啊賽利亞歡迎來(lái)到編碼世界'));
    console.log(gradient('cyan''pink')('你好啊賽利亞歡迎來(lái)到編碼世界'));

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

    image.png
    • child_process node.js中的子進(jìn)程。

      在node.js中,只有一個(gè)線程執(zhí)行所有的操作,如果某個(gè)操作需要大量消耗CPU資源的話,后續(xù)的操作就需要等待。后來(lái)node.js就提供了一個(gè)child_process模塊,通過(guò)它可以開(kāi)啟多個(gè)子進(jìn)程,在多個(gè)子進(jìn)程之間可以共享內(nèi)存空間,可以通過(guò)子進(jìn)程的互相通信來(lái)實(shí)現(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用來(lái)操作文件的模塊
    import fs from 'fs'

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

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

    • nanospinner命令行中的加載動(dòng)畫(huà)
    // 安裝
    npm i nanospinner

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

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

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

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

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

    交互式詢問(wèn)CLI 簡(jiǎn)單說(shuō)就是交互式詢問(wèn)用戶輸入

    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í)行效果如下圖所示:先出來(lái)一個(gè)下拉選擇,選擇完后根據(jù)if判斷進(jìn)行輸出

    4.gif

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

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

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

    通過(guò)run函數(shù)封裝異步方法,這樣最外面調(diào)用run函數(shù)時(shí)可以通過(guò)異步方法的catch捕獲錯(cuò)誤異常。

    看一個(gè)小例子

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

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

    Running test
    ErrorError: run test報(bào)錯(cuò)了

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

    接下來(lái)開(kāi)始進(jìn)入run函數(shù)了

    3.4.2、 打印色彩字體
    // 這個(gè)看上面的引入模塊解析即可
    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讀取項(xiàng)目工程template文件夾下的所有文件,并通過(guò)path.parse轉(zhuǎn)換對(duì)象,來(lái)獲取文件名稱name。

    askForProjectType函數(shù)通過(guò)enquirer.prompt返回兩個(gè)交互式命令行,供用戶進(jìn)行選擇projectType選擇項(xiàng)目類(lèi)型:【react-ts】 【react】【vue-ts】 【vue】packageManager選擇項(xiàng)目包管理方式:【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)出了兩個(gè)固定的模板變量數(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則是通過(guò)公共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ù)組通過(guò)join轉(zhuǎn)換為字符串,通過(guò)命令將所有拼接npm模塊一起安裝

    image.png
    3.4.7、 讀取項(xiàng)目的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ù)選擇的項(xiàng)目類(lèi)型,來(lái)拼接vite.config的路徑,并讀取項(xiàng)目中的vite.config配置文件

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

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

    1656558646620.png

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

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

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

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

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

    4、npm init、npx

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

    create-app-react創(chuàng)建項(xià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)建項(xiàng)目的

    同樣的vite創(chuàng)建項(xià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)沒(méi)有使用npx命令,不過(guò)我在我自己電腦上嘗試了另外幾個(gè)命令確實(shí)也是可以的

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

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

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

    image.png

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

    // 我們是通過(guò)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

    再來(lái)看一下npx

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

    image.png

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

    image.png

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

    image.png

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

    5、總結(jié)

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

    • 對(duì)于自動(dòng)添加eslint和prettier配置的原理分析

    • .eslintrc.json、.eslintignore、.prettierrc.json算是直接新增文件,處理相對(duì)簡(jiǎn)單一些

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

    6、加餐 V8下的AST抽象語(yǔ)法樹(shù)

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

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



    瀏覽 55
    點(diǎn)贊
    評(píng)論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報(bào)
    評(píng)論
    圖片
    表情
    推薦
    點(diǎn)贊
    評(píng)論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報(bào)

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    天天射一区 | 99久热手机视频免费观看 | 91性爱 | 青青草国产精品视频 | 欧美精品在线视频 |