node.js 命令列工具(cli)

litongqian發表於2018-05-09

一. 先了解一下package.json

每個專案的根目錄都有一個package.json檔案,定義了這個專案所需要的各種模組,以及專案的配置資訊,下面是一個比較完整的package.json檔案

{
  "name": "vue-cli",
  "version": "2.9.3",
  "description": "A simple CLI for scaffolding Vue.js projects.",
  "preferGlobal": true,
  "bin": {
    "vue": "bin/vue",
    "vue-init": "bin/vue-init",
    "vue-list": "bin/vue-list"
  },
  "repository": {
    "type": "",
    "url": ""
  },
  "keywords": [
  ],
  "author": "litongqian",
  "license": "MIT",
  "bugs": {
    "url": ""
  },
  "homepage": "",
  "scripts": {
    "test": "npm run lint && npm run e2e",
    "start": "node index.js"
  },
  "dependencies": {
    "async": "^2.4.0",
    "chalk": "^2.1.0",
  },
  "devDependencies": {
    "chai": "^4.1.2",
    "eslint": "^3.19.0",
  },
  "engines": {
    "node": ">=6.0.0"
  }
}
複製程式碼
1. 其中scripts欄位

指定了執行指令碼命令的npm命令列縮寫,比如start指定了執行npm run start時,所要執行的命令。

2. bin欄位

bin項用來指定各個內部命令對應的可執行檔案的位置

"bin": {
    "vue": "bin/vue",
    "vue-init": "bin/vue-init",
    "vue-list": "bin/vue-list"
  },複製程式碼

上面程式碼指定,vue 命令對應的可執行檔案為 bin 子目錄下的vue。

3. npm link

專案目錄:

node.js 命令列工具(cli)

自己開發模組的時候,比如上面的mymodule模組,如果想在hello中使用,我們可以採用相對路徑require('./mymodule')載入模組。

也可以使用自定義模組標識require('mymodule'),Node載入自定義模組需要將其安裝到全域性的或專案的node_modules目錄之中。對於上述模組,解決方法就是在全域性的node_modules目錄之中,生成一個符號連結,指向模組的本地目錄。

npm link就能起到這個作用,會自動建立這個符號連結。

  • 請設想這樣一個場景,你開發了一個模組mymodule,目錄為hello/mymodule,你自己的專案要用到這個模組,首先,在模組目錄(src/mymodule)下執行npm link命令。
hello/mymodule$ npm link
複製程式碼

node.js 命令列工具(cli)

上面的命令會在NPM的全域性模組目錄內,生成一個符號連結檔案,該檔案的名字就是package.json檔案中指定的模組名。

/usr/local/lib/node_modules/mymodule -> /Users/tongqianli/Desktop/work/hello/mymodule複製程式碼

這個時候,已經可以使用mymodule模組了。

這時可以通過新增path解決:

作業系統中都會有一個PATH環境變數,系統呼叫一個命令的時候,就會在PATH變數中註冊的路徑中尋找,如果註冊的路徑中有就呼叫,否則就提示命令沒找到。

-> export PATH=$PATH: # 將 /usr/bin 追加到 PATH 變數中
-> export NODE_PATH="/usr/lib/node_modules;/usr/local/lib/node_modules" #指定 NODE_PATH 變數
複製程式碼

NODE_PATH 就是NODE中用來尋找模組所提供的路徑註冊環境變數。我們可以使用上面的方法指定NODE_PATH環境變數。

更好的辦法:

切換到專案目錄,再次執行npm link命令,並指定模組名。

hello tongqianli$ npm link mymodule複製程式碼

上面命令生成了本地node_modules/mymodule模組對映到全域性的node_modules/mymodule ,全域性,全域性的對映到本地hello/mymodule。

/Users/tongqianli/Desktop/work/hello/node_modules/mymodule 
-> /usr/local/lib/node_modules/mymodule 
-> /Users/tongqianli/Desktop/work/hello/mymodule複製程式碼

然後,就可以在你的專案中,載入該模組了。

var myModule = require('myModule');
複製程式碼

這樣一來,mymodule的任何變化,都可以直接反映在專案之中。但是,這樣也出現了風險,任何在目錄中對mymodule的修改,都會反映到模組的原始碼中。

如果你的專案不再需要該模組,可以在專案目錄內使用npm unlink命令,刪除符號連結。

hello tongqianli$ npm unlink mymodule複製程式碼

二. 可執行指令碼

寫一個簡單的指令碼hello

$ mkdirhello#建立一個資料夾 
$ cd hello && touch hello #建立命令檔案

#!/usr/bin/env node
console.log('hello world');
複製程式碼

檔案的頭部加入#!/usr/bin/env node這行程式碼,這裡表示使用node作為指令碼的解釋程式,可以省略node ./hello,直接執行./hello, node的路徑通過env來查詢,可以避免node安裝的路徑不一樣帶來找不到的問題。

開啟/usr/bin/env,可以檢視到PATH,作業系統通過路徑找到node

node.js 命令列工具(cli)

然後,修改 hello 的許可權。

$ chmod 755 hello
$./hello複製程式碼

node.js 命令列工具(cli)

如果想把 hello 前面的路徑去除,可以將 hello 的路徑加入環境變數 PATH。但是,另一種更好的做法,是在當前目錄下新建 package.json ,寫入下面的內容。

{
  "name": "hello",
  "bin": {
    "hello": "./hello"
  }
}複製程式碼

然後執行 npm link 命令。不明白的看上面

$ npm link複製程式碼


node.js 命令列工具(cli)

執行後會產生一個全域性的對映關係,可執行指令碼bin/hello對映到全域性node_modules/hello/hello,全域性的node_modules/hello對映到本地hello,這樣就可以使用hello了

三.命令列引數

命令列引數可以用系統變數 process.argv 獲取。

修改hello指令碼

#!/usr/bin/env node
console.log('hello ', process.argv);複製程式碼

其中process為node程式中的全域性變數,process.argv為一陣列,陣列記憶體儲著命令列的各個部分,argv[0]為node的安裝路徑,argv[1]為主模組檔案路勁,剩下為子命令或引數,如下:


$ hello a b c
# process.argv的值為[ '/usr/local/bin/node', '/usr/local/bin/hello', 'a', 'b', 'c' ]

node.js 命令列工具(cli)


指令碼可以通過 child_process 模組新建子程式,從而執行 Unix 系統命令,修改hello

exec方法用於執行bash命令,exec方法最多可以接受兩個引數,第一個引數是所要執行的shell命令,第二個引數是回撥函式,該函式接受三個引數,分別是發生的錯誤、標準輸出的顯示結果、標準錯誤的顯示結果。


#!/usr/bin/env node
var name = process.argv[2];
var exec = require('child_process').exec;

var child = exec('echo hello ' + name, function(err, stdout, stderr) {
  if (err) throw err;
  console.log(stdout);
});複製程式碼

執行$ hello litongqian

如果我們想檢視所有檔案,修改hello

var name = process.argv[2];
var exec = require('child_process').exec;

var child = exec(name, function(err, stdout, stderr) {
  if (err) throw err;
  console.log(stdout);
});複製程式碼

執行$ hello ls 

hello目錄下有三個檔案

node.js 命令列工具(cli)

四、shelljs 模組

shell.js 模組重新包裝了 child_process,呼叫系統命令更加方便。它需要安裝後使用。

npm install --save shelljs
複製程式碼

然後,改寫指令碼。

#!/usr/bin/env node
var name = process.argv[2];
var shell = require("shelljs");

shell.exec("echo hello " + name);複製程式碼

五、yargs 模組

shelljs 只解決了如何呼叫 shell 命令,而 yargs 模組能夠解決如何處理命令列引數。它也需要安裝。

$ npm install --save yargs
複製程式碼

yargs 模組提供 argv 物件,用來讀取命令列引數。請看改寫後的 hello 。

#!/usr/bin/env node
var argv = require('yargs').argv;

console.log('hello ', argv.name);
複製程式碼

使用時,下面兩種用法都可以。

$ hello --name=tom
hello tom

$ hello --name tom
hello tom
複製程式碼

也就是說,process.argv 的原始返回值如下。

$ node hello --name=tom
[ 'node',
  '/usr/local/bin/hell',
  '--name=tom' ]
複製程式碼

yargs 可以上面的結果改為一個物件,每個引數項就是一個鍵值對。

六.釋出命令包

通過npm publish進行釋出,前提是有npm帳號。如何操作可以檢視npm 官方文件

本文是通過原生node.js來開發命令工具,而vue-cli是採用commander.js來簡化命令工具開發,

瞭解了執行流程,學習對應的模組,就可以了!,本文拋個磚頭

node.js 命令列工具(cli)


最後:有時我們用到的命令列不是全域性安裝的,而是本地安裝的

1. package.json bin欄位

bin項用來指定各個內部命令對應的可執行檔案的位置。

"name":"someTool",
"bin": {
  "someTool": "./bin/someTool.js"
}
複製程式碼

上面程式碼指定,someTool 命令對應的可執行檔案為 bin 子目錄下的 someTool.js。

當一個專案依賴上面的someTool工具時,同時只是本地安裝

{
  "name": "myproject",
  "devDependencies": {
    "someTool": "latest"
  },
  "scripts": {
    start: 'someTool build'  //等同於start: './node_modules/someTool/someTool.js build'

  }
}複製程式碼

npm會尋找這個檔案,在node_modules/.bin/目錄下建立符號連結。在上面的例子中,someTool.js會建立符號連結npm_modules/.bin/someTool。由於node_modules/.bin/目錄會在執行時加入系統的PATH變數,因此在執行npm時,就可以不帶路徑,直接通過命令來呼叫這些指令碼。

因此,像上面這樣的寫法可以採用簡寫。

scripts: {  
  start: './node_modules/someTool/someTool.js build'
}

// 簡寫為

scripts: {  
  start: 'someTool build'
}

複製程式碼

所有node_modules/.bin/目錄下的命令,都可以用npm run [命令]的格式執行。在命令列下,鍵入npm run,然後按tab鍵,就會顯示所有可以使用的命令。

1. npm run

上面程式碼中,scripts欄位指定了兩項命令

start
,輸入npm run-script start或者npm run start,就會執行 someTool buildnpm runnpm run-script的縮寫,一般都使用前者,但是後者可以更好地反應這個命令的本質。

npm run命令會自動在環境變數$PATH新增node_modules/.bin目錄,所以scripts欄位裡面呼叫命令時不用加上路徑,這就避免了全域性安裝NPM模組。

npm run如果不加任何引數,直接執行,會列出package.json裡面所有可以執行的指令碼命令。

npm內建了兩個命令簡寫,npm test等同於執行npm run testnpm start等同於執行npm run start

npm run會建立一個Shell,執行指定的命令,並臨時將node_modules/.bin加入PATH變數,這意味著本地模組可以直接執行。

舉例來說,你執行ESLint的安裝命令。

$ npm i eslint --save-dev
複製程式碼

執行上面的命令以後,會產生兩個結果。首先,ESLint被安裝到當前目錄的node_modules子目錄;其次,node_modules/.bin目錄會生成一個符號連結node_modules/.bin/eslint,指向ESLint模組的可執行指令碼。

然後,你就可以在package.jsonscript屬性裡面,不帶路徑的引用eslint這個指令碼。

{
  "name": "Test Project",
  "devDependencies": {
    "eslint": "^1.10.3"
  },
  "scripts": {
    "lint": "eslint ."
  }
}
複製程式碼

等到執行npm run lint的時候,它會自動執行./node_modules/.bin/eslint .

如果直接執行npm run不給出任何引數,就會列出scripts屬性下所有命令。

$ npm run
Available scripts in the user-service package:
  lint
     jshint **.js
  test
    mocha test/複製程式碼

到此,結束!


相關文章