前端掃盲-之打造一個Node命令行工具

jopen 9年前發布 | 98K 次閱讀 Node.js Node.js 開發

Node 給前端開發帶來了很大的改變,促進了前端開發的自動化,我們可以簡化開發工作,然后利用各種工具包生成生產環境。如運行sass src/sass/main.scss dist/css/main.css即可編譯 Sass 文件。在實際的開發過程中,我們可能會有自己的特定需求,那么我們得學會如何創建一個Node命令行工具。

命令行接口:Cmmand Line Interface,簡稱 CLI,是 Node 提供的一個用于命令行交互的工具,本質是基于 Node 引擎運行的。

在前面的文章 前端掃盲-之打造一個自動化的前端項目 中,給大家留了一個問題,就是如何通過執行一條命令就生成我們需要的項目結構。今天我就帶著大家一步一步解答這個問題。

我們的初步設想是,在指定目錄下執行一個命令(假設為autogo)

autogo demo

就會生成一個目錄名為demo的項目,里面包含有我們所需的基礎文件結構。

開始

1、首先咱們創建一個程序包清單(package.json文件)包含了該命令包的相關信息:

npm init

然后根據提示輸出對應的信息,也可以一路回車,待生成好package.json文件后再作修改。

2、創建一個用于運行命令的腳本bin/autogo.js:

#! /usr/bin/env node
console.log("hello")

然后我們執行

node bin/autogo.js

能夠看到輸出了hello,當然這不是我們想要的結果,我們是要直接運行autogo命令。

3、告訴 npm 你的命令腳本文件是哪一個,這里我們需要給package.json添加一個bin字段:

{
...
 "bin": {
    "autogo": "./bin/autogo.js"
  }
  ...
  }

這里我們指定autogo命令的執行文件為./bin/autogo.js。

4、啟用命令行:

npm link

這里我們通過npm link在本地安裝了這個包用于測試,然后就可以通過

autogo

來運行命令了。

可能遇到的問題

1、有的同學可能在執行autogo命令后會報下面的錯誤:

-bash: /usr/local/bin/autogo: /usr/local/bin/node^M: bad interpreter: No such file or directory

之所以出現這個錯誤是因為bin/autogo.js文件是在 windows 下做的編輯,windows 下默認的換行是\n\r,而 linux 下默認的換行是\n,所以文件后的\r在 linux 下是不會別識別的,顯示成了^M。

要解決這個問題的辦法就是改變文件的編碼,這里我們需要用到 dos2unix 這個包。

首先安裝

sudo apt-get install dos2unix

然后

sudo dos2unix bin/autogo.js

問題就解決了。

2、還有的同學可能會遇到下面這個報錯

: No such file or directory

報這個錯是因為#! /usr/bin/env node沒能識別出你的node的路徑,需要將你的 node 安裝路徑(如/usr/local/bin/)加入到系統的 PATH 中。

其實你可以在測試環境中將這個標識換成#!/usr/local/bin/node,再運行就沒問題了。但是我們之所以用#! /usr/bin/env node是因為這可以動態檢測出不同用戶各自的 node 路徑,而不是寫死的,畢竟不是所有用戶的 node 命令都是在/usr/local/bin/下。

到此,一個本地的 npm 命令行工具就已經成功運行了,(可參見 官方文檔 )接下來我們就來完善具體的功能。

創建項目結構

咱們需要的項目結構大致如下,包含了所需的文件夾和文件( 詳見 )。

前端掃盲-之打造一個Node命令行工具

要創建上面的結構,我們可以通過程序來創建么個文件和文件夾,但是對于這么多文件,而且每個文件里或許還有更多內容,所以我們應該用一個更簡便的方法。

實際上我們可以先創建一個完整的結構,然后再在執行命令時,通過程序把這些文件和文件夾整個復制到目標項目文件夾中去,最后再對某些文件做一些修改即可。

按照這個思路,我們根據上面的結構,將這些文件和文件夾創建到structure下,然后咱們創建一個生成結構的方法lib/generateStructure.js(這里咱們將功能模塊放在了lib/目錄下)

var Promise = require("bluebird"),
    fs = Promise.promisifyAll(require('fs-extra'));


function generateStructure(project){
  return fs.copyAsync('structure', project,{clobber: true})
    .then(function(err){
      if (err) return console.error(err)
    })
}


module.exports = generateStructure;

上面的代碼就是通過fs-extra這個包( 查看文檔 ) 將structure目錄下的內容復制到了project參數的目標文件夾中。fs-extra是對fs包的一個擴展,方便我們對文件的 操作。

這里咱們用到了bluebird( 查看文檔 ),這是一個實現 Promise 的庫,因為這里牽涉到了對文件的操作,所以會有異步方法,而 Promise 就是專門解決這些異步操作嵌套回調的,能將其扁平化。

自然,我們應該安裝這兩個包:

npm install bluebird --save
npm install fs-extra --save

這里加上--save參數是為了在安裝后就自動將該依賴加入到package.json中。然后咱們改造一下bin/autogo.js

#!/usr/bin/env node
var gs = require('../lib/generateStructure');

gs("demo");

然后執行

autogo

可以看到當前目錄下生成了一個demo文件夾,里面包含了和structure相同的文件結構。

我們的目標已經初步達成了,接下來我們就來細化該命令。

命名參數

上面的命令中,我們執行autogo時,是生成了一個固定的demo項目,實際上這個名字是不能寫死的,而是應該通過命令中的參數傳進去。像下面這樣:

autogo demo

因此,我們得在bin/autogo.js中去接收參數了。為了方便起見,我們這里直接使用一個專門用于處理命令行工具的包 commander文檔 )。

同樣,首先安裝

$ npm install commander --save

然后改造bin/autogo.js為:

#!/usr/bin/env node

var program = require('commander'),
    gs = require('../lib/generateStructure');

program
  .version(require('../package.json').version)
  .usage('[options] [project name]')
  .parse(process.argv);

var pname = program.args[0]

gs(pname);

這里的.version()意思是返回該命令包的版本號,即運行

autogo --version //- 返回1.0.0

會返回package.json中定義的版本號。

.usage()顯示基本使用方法 執行

autogo --help

會輸出:

Usage: autogo [options] [project name]

  Options:

    -h, --help     output usage information
    -V, --version  output the version number

可以看到 Commander 幫我們做好了用法(Usage) 信息,以及兩個參數(Options)-h, --help和-V, --version。

.parse(process.argv);是將接收到的參數加入 Commander 的處理管道。

program.args是獲取到命令后的參數,注意這里是一個數組

autogo    //- 返回  []
autogo demo  //-返回 ['demo']
autogo demo hello  //-返回 ['demo','hello']

這里咱們取第一個參數作為項目名,然后調用

var pname = program.args[0]
gs(pname);

現在我們執行:

autogo demo2

就可以看到新的項目demo2生成了,看上去我們已經完成工作了,只要運行autogo <項目名>就可以生成一個新的項目結構,里面包含了處理 Sass、coffee、jade 的 gulp 構建工具。

如果我們直接運行autogo是會報錯的,因為沒有傳入項目名,實際上我們在運行一個命令而不傳入任何參數時,可以直接返回幫助信息:

...
var pname = program.args[0]
if (!pname) program.help();
...

上面我們判斷是否存在參數,如果不存在就調用program.help()方法,這是 commander 為我們提供的顯示幫助信息的方法,可以直接調用。

那有的同學要說了,我不想用jade,就喜歡寫原生的 HTML,很明顯我們做了多余的事,而且整個結構就不那么合理了,我們需要的是一個干凈的項目結構。

這個時候我們就需要把與jade相關的文件都刪掉(這里不是刪 structure 目錄下的文件,而是新項目下的指定文件)。與jade有關的文件有:

  • /structure/views/下的index.jade和layouts/layout.jade
  • /structure/gulpfile.js中的templates任務代碼

因此,咱們得把上面這些文件和代碼干掉。

移除指定模塊

首先,咱們創建一個lib/jadeWithout.js用來移除 jade:

var Promise = require("bluebird"),
  fs = Promise.promisifyAll(require('fs-extra')),
  del = require('../lib/delFile');

var files =  ['/views/layouts/layout.jade','/views/index.jade'];
function jadeWithout(project){
  return Promise.all([del(project,files)])
    .then(function(){      
      return  console.log('remove jade success');
    })
}
module.exports = jadeWithout;

這里咱們將指定的files數組中的文件都刪除了,這里我用了一個公共的刪除文件模塊/lib/delFile.js:

var Promise = require("bluebird"),
  fs = Promise.promisifyAll(require('fs-extra'));

function del(project,files){
  return files.map(function(item){
    return fs.removeAsync(project + item)
  }) 
} 
function delFile(project,files){
  return Promise.all([del(project,files)])  
}
module.exports = delFile;

因為我們這里不光有 jade ,還有 sasscoffee 可以被移除,所以我們創建一個公共入口withoutFile.js:

var Promise = require("bluebird");

function deal(project,outs){
  return outs.map(function(item){
    var action = require('../lib/'+item+'Without');
    return action(project)
  }) 
}

function withoutFile(project,outs){
  return Promise.all([deal(project,outs)])
}
module.exports = withoutFile;

這里我們需要傳入一個要移除的列表(如['sass','jade']),然后對每個模塊進行刪除。

最后,我們將withoutFile引入到bin/autogo.js中:

...
var gs = require('../lib/generateStructure'),
    wf = require('../lib/withoutFile');
...  
Promise.all([gs(pname)])
  .then(function(){
    return wf(pname,["jade",'sass'])
  })

然后我們再次執行

autogo demo

可看到控制臺依次輸出了

generate project success
remove jade success
remove sass success

而且目標項目中相關文件已經被刪除了。

這里咱們是wf(pname,["jade",'sass'])寫死了 outs 參數作為測試,實際上是要再傳入一個數組,那么這個數組從哪兒來呢?很明顯,得從命令行參數中獲取。

我們希望的是這樣:

autogo --without jade demo

option

commander 為我們提供了一個option管道來配置命令參數,修改bin/autogo.js:

...
program
  .version(require('../package.json').version)
  .usage('[options] [project name]')
  .option('-W, --without <str | array>', 'generate project without some models(value can be `sass`、`coffee`、`jade`)')
  .parse(process.argv);
...

這里咱們添加了option,其格式為.option('-<大寫標識>, --<小寫全稱> <可取參數類型>', '數功能描述')

接著處理without參數:

...
var outs = program.without ? [program.without] : []

Promise.all([gs(pname)])
  .then(function(){
    return wf(pname,outs)
  })
...

然后咱們再運行

autogo --without jade demo

可以看到這里只移除了 jade 模塊,那如果我想移除多個呢?是不是可以這樣:

autogo --without [jade,sass] demo

注意,這樣是會報錯的,因為獲取到的program.without是一個字符串'[jade,sass]'而不是數組,所以咱們可以這樣:

autogo --without jade,sass demo

program.without則為'jade,sass'然后再

program.without.split(',')

既可以獲取到一個數組了,因此咱們的代碼就變成了:

...
var outs = program.without ? program.without.split(',') : []

Promise.all([gs(pname)])
  .then(function(){
    return wf(pname,outs)
  })

 ...

這下我們就可以這樣來運行了:

autogo demo --without sass,jade

發布

到目前為止,我們開發的 autogo 還是在本地的,現在就該將其發布到 npm 上了。

1、首先咱們得 注冊一個賬號

2、回到項目中,執行

npm login

輸入用戶名、密碼和郵箱便可將本地機器與 npm 連接起來了。

3、執行

npm publish

然后回到你的 npm 個人主頁,就可以看到我們發布成功了 https://www.npmjs.com/package/autogo

從包的路徑規則來看,是沒有包含用戶名的,由此可知,同名的包是不會被允許的,所以大家在跟著做的時候要給項目取一個不同的名字。

然后咱們來測試一下剛剛發布的包

首先刪除本地開發做的 autogo 鏈接

sudo npm unlink

然后

npm install autogo -g

注意這里需要帶上-g參數,因為命令行是應該安裝在全局環境中。安裝成功后,我們切換到另外一個目錄下,執行:

autogo demo

然而結果并非我們想象的那樣:

Unhandled rejection Error: ENOENT, lstat 'structure'
    at Error (native)

意思是找不到structure,這是怎么回事呢?

實際上當我們執行npm install autogo -g的時候,實際上是將命令包安裝在了/usr/local/lib/node_modules/autogo下面,所以在執行命令的目錄下是找不到structure文件夾的。

那該怎么辦呢?我們能想到的就是,得在程序中去獲取這個包安裝的實際路徑。

幸運的是 Node 給我們提供了__dirname這個變量用于獲取當前執行文件的路徑。 我們在lib/generateStructure.js下console.log(__dirname)會輸出/usr/local/lib/node_modules/autogo/lib,然后我們把后面的lib去掉就是根目錄了:

var root = __dirname.replace(/autogo\/lib/,'autogo/')

function generateStructure(project,outs){
  return fs.copyAsync(root + 'structure', project)
    .then(function(err){
      return err ?  console.error(err) : console.log('generate project success');
    })
}
...

修改后,我們按照下面的方式更新,重新安裝,然后

autogo demo
cd demo
npm install
gulp watch

OK 一個新的項目誕生了,準備開發吧...

更新

首先修改package.json配置文件中的version字段,比如這里我從0.1.0改成0.1.1(只能大于當前版本),然后再次

npm publish

即可成功發布新版本。

想將該項目從 npm 中移除嗎?執行 :

npm unpublish autogo --force

附:項目源碼 https://github.com/awesomes-cn/autogo

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!