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

    手把手教你寫一個前端腳手架

    共 13597字,需瀏覽 28分鐘

     ·

    2024-06-17 09:10


    引言

    腳手架是什么,相信各位已經(jīng)熟悉得不能再熟悉了,畢竟無論是vue開發(fā)者(vue-cli)還是react(create-react-app)開發(fā)者,他們都有各自的腳手架,個人雖是用react更多,但不得不說是更喜歡vue-cli的,它的插件機制非常有意思,雖不如webpack的plugin那么方便,但也很強大。不過再講這強大的功能之前,原諒我先水一篇腳手架的基礎(chǔ)。

    腳手架會分兩篇來講,本篇為基礎(chǔ)篇,講一講最簡單的腳手架如何搭建,入個門。

    正文

    概念與優(yōu)點

    相信很多開發(fā)者都有這么一段經(jīng)歷,那就是在開始新項目之前,先把舊項目拉下來,刪刪減減,只留下初始化項目時的配置,一切業(yè)務代碼都刪了,然后再開始新項目的開發(fā)。一次兩次如此做還好,但再多了就很厭煩,特別是刪代碼還很難保證項目的純凈,會出現(xiàn)漏刪或者刪多了的問題。而這時候,你就需要一個腳手架。

    腳手架是什么,他就是一個純凈的項目,可以完全不包含業(yè)務代碼,每次開始新項目之前,跑一下腳手架的命令,那么一個純凈的項目就初始化出來了,可以直接在這之上進行開發(fā)。

    無論是公司還是個人私底下做項目練手,都極其建議寫一個腳手架,就算是像本文這樣做一個最簡單的也是好的。

    那么,腳手架該如何做搭建呢,請移步到下文~

    實現(xiàn)

    前提:所使用到的第三方庫

    • Commander[1] 完整的node命令行解決方。當然也可以使用yargs[2],yargs功能更多一些。
    • Chalk[3] 能給shell命令行的文字添加樣式,簡單來說就是拿來畫畫的,可要可不要。
    • fs-extra[4] 操作文件的,比之node自帶的fs,這個會更加強大與完善些。
    • inquirer[5] 在shell命令行中提供交互的庫,具體效果看下文的演示。
    • ora[6] 在shell命令行中展示loading效果
    • download-git-repo[7] 下載git倉庫。

    步驟一:指定執(zhí)行的文件

    • 先創(chuàng)建一個項目 執(zhí)行npm init -y
    • 創(chuàng)建一個bin文件夾,添加index.js文件,在這個文件中寫下#! /usr/bin/env node 此時目錄結(jié)構(gòu)如下:
    • 在package.json中指定執(zhí)行命令和執(zhí)行的文件
    image.png
    • 執(zhí)行 npm link 命令,鏈接到本地環(huán)境中 npm link (只有本地開發(fā)需要執(zhí)行這一步,正常腳手架全局安裝無需執(zhí)行此步驟)Link 相當于將當前本地模塊鏈接到npm目錄下,這個目錄可以直接訪問,所以當前包就能直接訪問了。默認package.json的name為基準,也可以通過bin配置別名。link完后,npm會自動幫忙生成命令,之后可以直接執(zhí)行cli xxx。

    步驟二:配置可執(zhí)行命令

    • 直接在bin/index.js下配置create命令。直接貼代碼了,里面涉及到的都是第三方庫的api,不了解的先查下文檔較好。

    ps:以下代碼都是mjs,所以需要在package.json中添加一行 "type": "module"

    // 1 配置可執(zhí)行的命令 commander
    import { Command } from 'commander';
    import chalk from 'chalk';
    import config from '../package.json' assert { type'json' };

    const program = new Command();

    program
      .command('create <app-name>')  // 創(chuàng)建命令
      .description('create a new project'// 命令描述
      .action((name, options, cmd) => {
        console.log('執(zhí)行 create 命令');
      });

    program.on('--help', () => {
      console.log();
      console.log(`Run ${chalk.cyan('rippi <command> --help')} to show detail of this command`);
      console.log();
    });

    program
      // 說明版本
      .version(`rippi-cli@${config.version}`)
      // 說明使用方式
      .usage('<command [option]');

    // 解析用戶執(zhí)行命令傳入的參數(shù)
    program.parse(process.argv);

    將上面提到的第三方庫都安裝一下,然后隨便打開一個cmd,執(zhí)行 cli create project。

    步驟三:完善核心命令---create命令

    上面的步驟都只是一個腳手架最基本的鋪墊,而create命令才是最關(guān)鍵的,而這最核心的create命令都應該做些什么事情呢?

    這里就要聊聊腳手架的本質(zhì)了,腳手架的本質(zhì)無非就是我們先在一個倉庫里寫好一個模板項目,然后腳手架每次運行的時候都把這個模板項目拉到目標項目中,腳手架不過是省去了我們拉代碼,初始化項目的操作而已。那么現(xiàn)在,create命令的基本流程就是這樣了。

    image.png

    ps: 如果要使用gitee的話,就不能使用download-git-repo這個庫了,這個庫只支持下載github,要另外找一個支持下載gitee的庫

    • 創(chuàng)建一個lib文件夾,任何工具方法或者抽象類都放到這個文件夾中。以下是代碼,注釋解釋的都比較清楚了。
    // lib/creator.js 編寫一個creator類,整個找模板到下載模板的主要邏輯都抽象到了這個類中。
    import { fetchRepoList } from './request.js';
    import { loading } from './utils.js';
    import downloadGitRepo from 'download-git-repo';
    import inquirer from 'inquirer';
    import chalk from 'chalk';
    import util from 'util';

    class Creator {
      constructor(projectName, targetDir) {
        this.name = projectName;
        this.dir = targetDir;
        // 將downloadGitRepo轉(zhuǎn)成promise
        this.downloadGitRepo = util.promisify(downloadGitRepo);
      }

      fetchRepo = async () => {
        const branches = await loading(fetchRepoList, 'waiting for fetch resources');
        return branches;
      }

      fetchTag = () => {}

      download = async (branch) => {
        // 1 拼接下載路徑 這里放自己的模板倉庫url
        const requestUrl = `rippi-cli-template/react/#${branch}`;
        // 2 把資源下載到某個路徑上
        await this.downloadGitRepo(requestUrl, this.dir);
        console.log(chalk.green('done!'));
      }

      create = async () => {
        // 1 先去拉取當前倉庫下的所有分支
        const branches = await this.fetchRepo();
        // 這里會在shell命令行彈出選擇項,選項為choices中的內(nèi)容
        const { curBranch } = await inquirer.prompt([
          {
            name'curBranch',
            type'list',
            // 提示信息
            message'please choose current version:',
            // 選項
            choices: branches
              .filter((branch) => branch.name !== 'main')
              .map((branch) => ({
                name: branch.name,
                value: branch.name,
              })),
          },
        ]);
        // 2 下載
        await this.download(curBranch);
      }
    };

    export default Creator;

    // lib/utils.js 給異步方法加loading效果,只是一個好看點的交互效果
    import ora from 'ora';

    export const loading = async (fn, msg, ...args) => {
      // 計數(shù)器,失敗自動重試最大次數(shù)為3,超過3次就直接返回失敗
      let counter = 0;
      const run = async () => {
        const spinner = ora(msg);
        spinner.start();
        try {
          const result = await fn(...args);
          spinner.succeed();
          return result;
        } catch (error) {
          spinner.fail('something go wrong, refetching...');
          if (++counter < 3) {
            return run();
          } else {
            return Promise.reject();
          }
        }
      };
      return run();
    };

    // lib/request.js 下載倉庫

    import axios from 'axios';

    axios.interceptors.response.use((res) => {
      return res.data;
    });

    // 這里是獲取模板倉庫的所有分支,url寫自己的模板倉庫url
    export const fetchRepoList = () => {
      return axios.get('https://api.github.com/repos/rippi-cli-template/react/branches');
    };

    寫完上述代碼,接下來我們實例化下creator,然后調(diào)用它的create方法就好了。

    // lib/create.js
    import path from 'path';
    import Creator from './creator.js';

    /**
     * 執(zhí)行create時的處理
     * @param {any} name // 創(chuàng)建的項目名
     * @param {any} options // 配置項 必須是上面option配置的選項之一,否則就報錯  這里取的起始就是cmd里面的options的各個option的long屬性
     * @param {any} cmd // 執(zhí)行的命令本身 一個大對象,里面很多屬性
     */

    const create = async (projectName, options, cmd) => {
      // 獲取工作目錄
      const cwd = process.cwd();
      // 目標目錄也就是要創(chuàng)建的目錄
      const targetDir = path.join(cwd, projectName);
      // 創(chuàng)建項目
      const creator = new Creator(projectName, targetDir);
      creator.create();
    };

    export default create;

    // bin/index.js 將上文中的action改掉
    program
      .command('create <app-name>')  // 創(chuàng)建命令
      .description('create a new project'// 命令描述
      .action((name, options, cmd) => {
        console.log('執(zhí)行 create 命令');
      });

    那么好,完成上述動作,我們來看看效果。

    在一個空文件夾中打開shell命令行,然后執(zhí)行cli create project project是項目名,隨便改。


    效果已經(jīng)出來了,我的這個倉庫有兩個分支,分別是react和react+ts的模板分支,這里任意選一個。

    image.png

    選擇完畢之后,就會開始下載,看到done就說明下載完了。

    image.png

    此時我們的文件夾中多了這么一個文件夾,打開進去看。

    image.png

    就是我們模板倉庫里面的那些文件內(nèi)容。

    其實到這里,最基本的一個腳手架就寫完了,不過對于嘗試了多次的朋友來說會發(fā)現(xiàn)一個問題,那就是當當前文件夾中存在相同名稱的文件時,文件就直接被覆蓋,而很多時候這個行為是不好的,會導致用戶丟失不想丟失的內(nèi)容,為了優(yōu)化這個體驗我們加個--force的配置。

    優(yōu)化:增加--force配置

    force,就當遇到同名文件,直接覆蓋繼續(xù)我們的創(chuàng)建項目的流程。

    // bin/index.js  新增一個option
    program
      .command('create <app-name>')  // 創(chuàng)建命令
      .description('create a new project'// 命令描述
      .option('-f, --force''overwrite target directory if it is existed'// 命令選項(選項名,描述) 這里就是解決下重名的情況
      .action((name, options, cmd) => {
        import('../lib/create.js').then((default: create }) => {
          create(name, options, cmd);
        });
      });

    在create方法中,我們接受的第二參數(shù)就會包含這個option。

    // lib/create.js
    import path from 'path';
    import fs from 'fs-extra';
    import inquirer from 'inquirer';
    import Creator from './creator.js';

    /**
     * 執(zhí)行create時的處理
     * @param {any} name // 創(chuàng)建的項目名
     * @param {any} options // 配置項 必須是上面option配置的選項之一,否則就報錯  這里取的起始就是cmd里面的options的各個option的long屬性
     * @param {any} cmd // 執(zhí)行的命令本身 一個大對象,里面很多屬性
     */

    const create = async (projectName, options, cmd) => {
      // 先判斷是否重名,如果重名,若選擇了force則直接覆蓋之前的目錄,否則報錯
      // 獲取工作目錄
      const cwd = process.cwd();
      // 目標目錄也就是要創(chuàng)建的目錄
      const targetDir = path.join(cwd, projectName);
      if (fs.existsSync(targetDir)) {
        // 選擇了強制創(chuàng)建,先刪除舊的目錄,然后創(chuàng)建新的目錄
        if (options.force) {
          await fs.remove(targetDir);
        } else {
          const { action } = await inquirer.prompt([
            {
              name'action',
              type'list',
              // 提示信息
              message`${projectName} is existed, are you want to overwrite this directory`,
              // 選項
              choices: [
                { name'overwrite'valuetrue },
                { name'cancel'valuefalse },
              ],
            },
          ]);
          if (!action) {
            return;
          } else {
            console.log('\r\noverwriting...');
            await fs.remove(targetDir);
            console.log('overwrite done');
          }
        }
      }

      // 創(chuàng)建項目
      const creator = new Creator(projectName, targetDir);
      creator.create();
    };

    export default create;

    整個create方法增加多了一個判斷是否存在同名文件的情況。

    ps:node其實已經(jīng)不推薦使用exists相關(guān)的方法了,但為了好理解這里仍然使用這個方法。node更推薦的是access方法,想了解更多可以查閱node官方文檔。

    增加完這段邏輯之后,我們這個腳手架的完整流程如下:

    image.png

    結(jié)尾

    本文是腳手架搭建的一個入門,這個腳手架只擁有最簡單的功能,而下一篇腳手架的搭建將會是復雜版的,擁有者插件機制,能通過配置插件動態(tài)生成項目,比如是初始化各種lint、是否使用mobx/redux,亦或者是是否初始化路由等,這都能通過配置插件完成,敬請期待吧??。

    那么好,本文到此就結(jié)束了,希望沒接觸過腳手架的朋友能通過這篇文章了解到腳手架并且實現(xiàn)自己的腳手架。


    作者:豬頭切圖仔

    https://juejin.cn/post/7260893255189758010

    瀏覽 191
    1點贊
    評論
    收藏
    分享

    手機掃一掃分享

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

    手機掃一掃分享

    分享
    舉報

    <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>
    猫咪AV成人永久网站在线观看 | 久久大香蕉操 | 私拍视频网 | 黄视屏久久 | 青青草AV无码 |