nodejs入門之本地簡易cli搭建
在閱讀本文之前,我們假設您已經了解 babel , uglify2 , browserify , crypto 等相應的npm包
一.項目背景
平常在開發中,我相信大家會經常用到babel、uglify2、commonjs、本地代理等進行相應的開發。一般情況下,大家會選擇grunt、gulp、webpack等進行本地構建。但是也會有一些情況,運用這類構建工具就顯得比較笨重了,比如說在本地簡單測試或者學習es6語法或者修改js在html中內嵌代碼的某個片段,需要壓縮后塞到html中,很多人可能會聯想到運用全局的babel或者uglify之類的來實現,但是不知道您有沒有想過,自己開發一套cli來管理這些散兵游勇呢?這個時候通過 commander 或者 inquirer 可以輕松實現。
二.開發準備
1. node相關知識儲備;
2.因為此次入門講解中涉及到了hosts修改以及nginx+node實現本地代理功能,所以適當的nginx和shell知識也是需要掌握的;
3.對node常用的npm包有基本的了解與認識。
三.目錄結構和基本功能介紹
如上圖所示,基本結構主要分為一下幾塊:
1.idea目錄可忽略,是編輯器生成的map文件;
2.addon目錄是對v8引擎編寫addon的文件夾,里面存放的是相關v8-addon;
3.bin目錄下的文件是整個cli的入口;
4.cli目錄下的文件主要是執行相關cli操作的文件,在之后會有詳細的講解;
5.config目錄存放的是相關配置文件,主要是針對于本地生成目錄結構的配置;
6.deps目錄主要存放的是bin和cli中文件的依賴文件;
7.sh目錄主要存放的是相關的shell文件,通過child_process進行相應的調用(ps:為何沒選用相關的node shell package是因為我覺得shell和node還是分開的好,畢竟他們負責的范圍和業務都有所區別)
8.test目錄主要存放的是相關測試文件,以及為testing做準備(這塊未實現)
9.接下來有幾個放置于根目錄下的文件,相信大家也都知道是做什么用的,我在這里就不詳細介紹了(ps:當時在處理的時候有一個全局變量沒有用shell寫入到zshrc中,所以這里多了一個zshrc的備份)
四.項目入口&切入點
通過上面的xmind腦圖大家應該有一個初步的認識:shell文件全部通過child_process在node中進行調用;config文件夾主要為init進行服務;本地代理的實現是通過修改hosts使外部js請求到本地,然后通過nginx把node server代理到80端口,實現本地的文件映射,而hosts是通過shell的sed指令進行控制,shell又是通過node來進行操作的,所以說最后的控制權全部移交到了node。
接下來就從 入口文件 直接切入到整體邏輯。
這是bin/xtx.js的主要邏輯,代碼應該是非常容易就能看明白。主要就是查找cli目錄下的所有文件,文件名作為commander的command。從下面的for循環中可以看出,每個文件暴露出來的是一個構造函數(new tag()),構造完畢后有三個屬性分別為:option、action以及description,分別掛到了commander中,這樣寫的好處則在于可以規定好我每個cli文件夾下文件的結構,使得代碼易讀,并且顯得十分整齊。大致的bin下面的每個文件的結構如下所示:
'use strict';
class command{
constructor(){
this.option='',
this.description=""
}
action(){
return ()=>{}
}
}
exports._export = command
五.日志&幫助的相關編寫
一個cli最重要的就是日志和help的完善成度了,因為這是本地自用的東東,所以日志并沒有引入 log4js 這種比較重量級的東西,而是決定自己編寫簡單的console.log,這里我推薦給大家使用的便是 chalk 這款能讓你的控制臺出彩的插件。用起來也很簡單:
我在這里設定了三種不同level的console,info用灰色,warning用黃色而error肯定是紅色無疑了。
help的編寫也有一點點小訣竅,'\n'可以通過join來加進來,這樣代碼就不會顯得那么凌亂了。
六.一些主要功能的實現方法
這一章節介紹一些主要的(常用的)commander的實現方式
1.babel
在使用babel指令后,會在相應文件目錄下生成一個 .es5.js,當然也可以帶-m參數生成 .es5.min.js,這里具體的實現,其實是用到了babel的babel.transformFile方法,如下圖所示:
option參數可以確認commander中是否加了-m來決定minified是否設置為true以及生成文件的擴展名,在這里我只引入了es2015 這一個插件作為例子,一般徹底的翻譯至少還需要 babel-polyfill 做支持,在最后生成文件的時候,我通過的是fs.createWriteStream 實現的(./deps/wrstream.js)
2.browserify
已經貼出來了一個commander的實現,其余的基本上都大同小異了,browserify的實現重點則在于require('browserify').bundle這個api,有興趣的同學可以親自去試一下。
3.uglify2
uglify的實現側重點在require('uglify-js2').minify這個api上,多說一句,這里用crypto實現了一個md5戳的添加,添加md5戳主要是為了保證壓縮后文件名的唯一性,具體的api的用法不多說了,npm git上搜搜都能搜到。我這里實現的比較齷齪了,直接在co內部對readstream做了promise化,其實這里利用readstream eventEmitter的end事件來做minify完全可以(ps:node>=7.8的情況下,建議直接用async/await,不要使用generator了)。
4.svn同步上傳
svn同步上傳這里主要運用的是從測試線的svn進行tar打包之后傳到準線上svn目錄,然后執行svn status操作,篩查相關add conflict以及modified等文件狀態。進行相應的處理后再做ci操作。這里,我貼兩段主要的邏輯:
第一段邏輯是對svn目錄進行tar打包之后傳到準線上svn解包的操作,當然,folder可以你自己指定,如果是純文件的話直接執行cp操作就可以;第二段邏輯是篩查svn add以及conflict沖突的shell函數,如果是需要add的則執行add操作,如果有沖突,則暫停提交,自己手動解決沖突。
5.nginx反向代理(無https)
這里重點講一下nginx本地代理的實現,https因為還需要偽造公鑰私鑰對在這里不做討論。一般來說代理的實現方式有很多種,市面上常見的:
- charles等,需要通過switchysharp來把chrome的端口從80代理到別的端口,然后對端口做監聽,實現對請求的攔截;
- proxy,直接修改本地wifi或者有線網絡的代理配置,當然,這需要root權限;
- nginx反向代理,一般來說網站的js基本都在同一個域名下面,那么我通過修改hosts文件實現域名的dns指向本地,然后通過nginx反向代理實現對80端口的代理,這樣我在本地監聽這個nginx代理的端口就可以實現proxy。
nginx配置應該是輕松加愉快了,只需要把js_server 80端口反向代理到(圖為8088)本地監聽的端口上即可,之后要做的就是修改hosts文件,使得js_server dns指向localhost。這兩項工作都做完后,你只需要在本地的工作區根目錄起一個端口為8088的nodeserver即可實現簡單的本地文件代理,如果http 請求的文件地址和本地的工作區對不上,建議你在nginx做好配置,如果有commonjs等合并的行為,那么你需要在nodeserver中調用browserify等來進行合并之后再做返回,這里因為每個公司的應用場景都不太一樣,所以不做詳細的解讀了。
七.總結
寫這篇文章的目的,是為了給大家做本地cli提供一個思路,這是我個人的思路,歡迎大家拍磚,畢竟里面會有不成熟和欠考慮的地方。比如說在commander上注冊插件的時候,我不是隨取隨用,而是直接全部遍歷加載之后再進行process.argv的篩查,這里極大的降低了性能。當然大家也可以開放出全局變量,接入webpack之類或者自己寫config文件來做相應的task,這塊東西我在做的時候思考過,但是沒有去實現,因為畢竟現在不論是gulp還是webpack已經非常豐富了,自己做cli的目的只不過是為了隨拿隨用,定制化配置,復雜化感覺真的沒有任何必要,最后附上項目的 git鏈接 。
來自:http://div.io/topic/1975