在第 42 期的文章: 從零開始使用JavaScript製作自己的命令行(CLI工具) 中我們介紹如何從零開始製作CLI,算是一個入門前傳,知道了怎麼製作CLI後今天更進一步。

在這篇文章中,我們將構建一個簡單的CLI,允許用戶生成HTML頁面。我們首先要生成一個標準的空白頁面,然後讓用戶輸入參數,比如文件名和標題,先通過選項,然後通過提示問題讓用戶輸入參數。

創建 Hello World CLI

創建用於編寫CLI的文件夾。我將其命名爲 html-generator-cli 。打開一個終端,然後在此文件夾中運行:

npm init

該命令會有幾個問題要問你,順便說一下,這正是我們最終希望在空白HTML頁面生成器中包含的內容。這將在文件夾中生成 package.json 文件:

我們需要創建包的 index.js 文件作爲入口在package.json中引入。在這個文件中,寫入下面代碼:

console.log('Hello World!');

現在我們需要創建運行這段代碼的命令。

{
  "name": "html-generator-cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "bin": "index.js"
}

將最後一行添加到package.json中。現在,我們可以測試我們非常簡單的CLI。在項目文件夾中局安裝我們新創建的包到本機:

npm install -g ../html-generator-cli

打開一個新終端並運行:

html-generator-cli

如果您使用Windows,現在應該會看到“Hello World!”。在您的終端中。如果您使用的是基於UNIX的操作系統,則應該得到一個錯誤,可能與語法錯誤和意外的token有關。我本人用的是Mac,結果人如下

這是因爲與Windows不同,基於UNIX的系統不關心文件的擴展名(此處爲“.js”),因此不知道使用哪種語言。我們必須告訴系統使用Node運行腳本。爲此,我們在文件的開頭添加一條註釋行:

#!/usr/bin/env node

console.log('Hello World!');

創建一個空白的HTML頁面

我們要創建一個CLI來生成HTML文件,爲此,我們將使用Node.js文件系統模塊。該模塊是Node內置模塊,提供與文件系統交互的API,也就是說可以創建、讀取、修改和刪除文件。我們只需要使用文件系統模塊的 writeFile 方法即可,該方法允許你創建文件。

#!/usr/bin/env node

const fs = require('fs');

const html = `<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Title</title>
  </head>
  <body>
  </body>
</html>`;

fs.writeFile('index.html', html, error => {
  if (error) {
    console.log(error);
  }
});

如果您再次在終端中保存並運行 html-generator-cli ,現在應該在文件夾中看到一個 index.html 文件。

將參數傳遞給代碼

現在我們生產的文件名和HTML中的 title 標籤內容是寫死的,我們應該可以將文件名和標題作爲參數傳遞給CLI。要傳遞參數,你只需在命令之後寫上參數,然後這些參數就可以在一個名爲 argv 的變量中提供給進程。

在代碼中編寫如下代碼:

const args = process.argv;
console.log(args);

並在終端中運行它:

html-generator-cli hello haha

然後,你應該在控制檯中看到一個包含參數作爲字符串的數組:

傳遞的參數在數組的最後兩項,我們只需要使用數組的 slice(2) 方法即可拿到。我們決定第一個輸入參數是文件名(不帶HTML擴展名),第二個參數將是HTML頁面的標題。這些參數都不是必需的,如果沒有提供名稱和標題,則我們將文件稱爲index.html,標題爲“Title”。

#!/usr/bin/env node

const fs = require('fs');

const args = process.argv.slice(2);

let fileName = args[0] ? `${args[0]}.html` : 'index.html';
let title = args[1] || 'Title';

const html = `<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>${title}</title>
  </head>
  <body>
  </body>
</html>`;

fs.writeFile(fileName, html, error => {
  if (error) {
    console.log(error);
  }
});

我們保持簡單,不驗證用戶輸入的情況,用戶可能會給該文件指定了無效的名稱,這是你在實際工作中必須驗證的內容。

現在,你可以在終端中嘗試以下操作:

html-generator-cli page "new generator"

結果

使用參數選項

先前的方法易於實現,但有一些缺點:用戶必須知道期望哪些參數以及以什麼順序。如果他不想給出文件名,他也沒有辦法給出標題,我們可以通過創建選項來改善這一點。

與其一個接一個地寫參數,我們可以構建我們的CLI,讓用戶輸入類似於這樣的文件名和/或標題。

html-generator-cli --file-name page --html-title "new generator"

寫起來有點長,但是用戶更清楚他給出的參數是什麼,順序不再起作用,你可以給出一個標題,即使你沒有給出任何文件名。

const args = process.argv;

const FILE_NAME_OPTION = '--file-name';
const HTML_TITLE_OPTION = '--html-title';

const fileNameOptionIndex = args.findIndex(arg => arg === FILE_NAME_OPTION);
const htmlTitleOptionIndex = args.findIndex(arg => arg === HTML_TITLE_OPTION);

const fileNameOption = fileNameOptionIndex > -1 && args[fileNameOptionIndex + 1];
const titleOption = htmlTitleOptionIndex > -1 && args[htmlTitleOptionIndex + 1];

let fileName = fileNameOption ? `${fileNameOption}.html` : 'index.html';
let title = titleOption || 'Title';

現在,我們在參數數組 args 中獲得選項 --file-name--html-title 的索引。如果存在一個選項,那麼要給文件名或標題的值就是參數數組中 --file-name--html-file 之後的元素。如果不存在選項,則其索引將爲 -1 。如果此索引爲 -1 或參數數組中該選項之後沒有任何值,我們分別爲文件名或標題提供默認值。其餘代碼未更改。

你可以運行新的CLI,如果沒有選擇,它將創建標題爲“Title”的index.html文件。如果你編寫一個選項但忘記提供一個值,它將也提供默認值。如果你正確地使用給定的選項編寫命令,那麼它應該創建一個具有正確名稱和正確HTML標題的文件。

html-generator-cli --file-name hello --html-title "CLI helloworld!"

效果

同樣,在實際的CLI中,你會希望多檢查一些輸入,首先要確保用戶輸入的值是有效的,但也要在缺失值或選項出現兩次的情況下警告他們。

向用戶詢問參數

使用選項已經是一種改進了,但是它仍然需要用戶知道他可以傳遞什麼參數以及使用哪個標記。當你初始化你的npm項目時,你可以通過很多東西作爲選項。CLI會直接問您一些問題,因此您無需閱讀文檔即可瞭解如何提供項目名稱,版本等信息。

要從控制檯讀取用戶輸入,我們需要Node(自版本7)提供的模塊 readline 。你可以使用以下代碼在終端中對其進行測試:

const readline = require('readline');

const interface = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

interface.question('你叫什麼名字?', answer => {
  console.log(`Hello ${answer}`);
  interface.close();
});

效果如下

爲了生成我們的HTML頁面,我們首先要詢問文件名,然後詢問標題。如果用戶沒有輸入任何內容,我們將獲得默認值。我們向用戶顯示默認值是什麼,以便在默認值正確的情況下可以跳過該問題。

#!/usr/bin/env node

const fs = require('fs');
const readline = require('readline');

let fileName = 'index.html';
let title = 'Title';

const interface = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

interface.question(`File name (${fileName}): `, answer => {
  if (answer && answer.length) {
    fileName = `${answer}.html`;
  }

  interface.question(`HTML title (${title}): `, answer => {
    if (answer && answer.length) {
      title = answer;
    }
    interface.close();

    const html = `<!DOCTYPE html>
      <html>
        <head>
          <meta charset="utf-8">
          <title>${title}</title>
        </head>
        <body>
        </body>
      </html>`;

    fs.writeFile(fileName, html, error => {
      if (error) {
        console.log(error);
      }
    });
  });
});

如果你在終端中運行它,將會詢問兩個問題。

用戶不必瞭解您的CLI選項,所有重要的事情都可以直接詢問。但是,你應該只以這種方式詢問主要配置問題,並讓用戶閱讀文檔以瞭解不太常見的選項。

結束

我們使用Node和npm創建了一個簡單的CLI,允許用戶生成一個空白的HTML文件,是不是非常簡單?你可以通過添加新選項並驗證用戶輸入來改進此示例。

推薦閱讀

CSS中的間距,前端開發中各種設置間距的優點缺點及實例

你不知道的CSS國際化

React.js和Vue.js的語法並列比較

老闆知道會點贊,前端開發人員的10個安全建議

Web中的圖像技術總結與實踐

溫故知新 | Vue.js進階必會,編寫你的第一個Vue.js插件

從零開始使用JavaScript製作自己的命令行(CLI工具)

Vue.js中編寫更好的v-for循環的6種技巧

感謝您的閱讀和關注,看完三件事:

如果對你有幫助,幫忙文章右下角點個 在看 如果有什麼問題歡迎 留言 交流,還可以 轉發 ,這是對作者最大的幫助。

相關文章