nodejs入门之本地简易cli搭建

qbel9464 7年前
   <p>在阅读本文之前,我们假设您已经了解 babel , uglify2 , browserify , crypto 等相应的npm包</p>    <h2>一.项目背景</h2>    <p>平常在开发中,我相信大家会经常用到babel、uglify2、commonjs、本地代理等进行相应的开发。一般情况下,大家会选择grunt、gulp、webpack等进行本地构建。但是也会有一些情况,运用这类构建工具就显得比较笨重了,比如说在本地简单测试或者学习es6语法或者修改js在html中内嵌代码的某个片段,需要压缩后塞到html中,很多人可能会联想到运用全局的babel或者uglify之类的来实现,但是不知道您有没有想过,自己开发一套cli来管理这些散兵游勇呢?这个时候通过 <a href="/misc/goto?guid=4959630042057148692" rel="nofollow,noindex">commander</a> 或者 <a href="/misc/goto?guid=4959748139545514688" rel="nofollow,noindex">inquirer</a> 可以轻松实现。</p>    <h2>二.开发准备</h2>    <p>1. node相关知识储备;</p>    <p>2.因为此次入门讲解中涉及到了hosts修改以及nginx+node实现本地代理功能,所以适当的nginx和shell知识也是需要掌握的;</p>    <p>3.对node常用的npm包有基本的了解与认识。</p>    <h2>三.目录结构和基本功能介绍</h2>    <p style="text-align:center"><img src="https://simg.open-open.com/show/6e6c94c99a70b370bb67913789800985.png"></p>    <p>如上图所示,基本结构主要分为一下几块:</p>    <p>1.idea目录可忽略,是编辑器生成的map文件;</p>    <p>2.addon目录是对v8引擎编写addon的文件夹,里面存放的是相关v8-addon;</p>    <p>3.bin目录下的文件是整个cli的入口;</p>    <p>4.cli目录下的文件主要是执行相关cli操作的文件,在之后会有详细的讲解;</p>    <p>5.config目录存放的是相关配置文件,主要是针对于本地生成目录结构的配置;</p>    <p>6.deps目录主要存放的是bin和cli中文件的依赖文件;</p>    <p>7.sh目录主要存放的是相关的shell文件,通过child_process进行相应的调用(ps:为何没选用相关的node shell package是因为我觉得shell和node还是分开的好,毕竟他们负责的范围和业务都有所区别)</p>    <p>8.test目录主要存放的是相关测试文件,以及为testing做准备(这块未实现)</p>    <p>9.接下来有几个放置于根目录下的文件,相信大家也都知道是做什么用的,我在这里就不详细介绍了(ps:当时在处理的时候有一个全局变量没有用shell写入到zshrc中,所以这里多了一个zshrc的备份)</p>    <h2>四.项目入口&切入点</h2>    <p style="text-align:center"><img src="https://simg.open-open.com/show/ad911cae68ef9168efbbf59eb2a75de2.png"> <img src="https://simg.open-open.com/show/eaa4db57d6ff648f9daf8a0114a51c89.png"></p>    <p>通过上面的xmind脑图大家应该有一个初步的认识:shell文件全部通过child_process在node中进行调用;config文件夹主要为init进行服务;本地代理的实现是通过修改hosts使外部js请求到本地,然后通过nginx把node server代理到80端口,实现本地的文件映射,而hosts是通过shell的sed指令进行控制,shell又是通过node来进行操作的,所以说最后的控制权全部移交到了node。</p>    <p>接下来就从 <a href="/misc/goto?guid=4959748139638682808" rel="nofollow,noindex">入口文件</a> 直接切入到整体逻辑。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/30e3fc34dfa7045940ab6e54c7adf932.png"></p>    <p>这是bin/xtx.js的主要逻辑,代码应该是非常容易就能看明白。主要就是查找cli目录下的所有文件,文件名作为commander的command。从下面的for循环中可以看出,每个文件暴露出来的是一个构造函数(new tag()),构造完毕后有三个属性分别为:option、action以及description,分别挂到了commander中,这样写的好处则在于可以规定好我每个cli文件夹下文件的结构,使得代码易读,并且显得十分整齐。大致的bin下面的每个文件的结构如下所示:</p>    <pre>  <code class="language-javascript">'use strict';  class command{      constructor(){          this.option='',          this.description=""      }      action(){          return ()=>{}      }  }  exports._export = command</code></pre>    <h2>五.日志&帮助的相关编写</h2>    <p style="text-align:center"><img src="https://simg.open-open.com/show/28915cebca40f91a91d950a17774dcae.png"></p>    <p>一个cli最重要的就是日志和help的完善成度了,因为这是本地自用的东东,所以日志并没有引入 log4js 这种比较重量级的东西,而是决定自己编写简单的console.log,这里我推荐给大家使用的便是 chalk 这款能让你的控制台出彩的插件。用起来也很简单:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/80d45966bcfe1cb7b07548c33cc32817.png"></p>    <p>我在这里设定了三种不同level的console,info用灰色,warning用黄色而error肯定是红色无疑了。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d6b74b21242ded24aa725a3a6f15b5e5.png"></p>    <p>help的编写也有一点点小诀窍,'\n'可以通过join来加进来,这样代码就不会显得那么凌乱了。</p>    <h2>六.一些主要功能的实现方法</h2>    <p>这一章节介绍一些主要的(常用的)commander的实现方式</p>    <p>1.babel</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c09337e08089b3745c89a47a898f9c29.png"> <img src="https://simg.open-open.com/show/0f5835122532fe8dacaebe21ecd0c21b.png"></p>    <p>在使用babel指令后,会在相应文件目录下生成一个 <em>.es5.js,当然也可以带-m参数生成</em> .es5.min.js,这里具体的实现,其实是用到了babel的babel.transformFile方法,如下图所示:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/2752493bab52d20b0fd7034cfd74248a.png"></p>    <p>option参数可以确认commander中是否加了-m来决定minified是否设置为true以及生成文件的扩展名,在这里我只引入了es2015 这一个插件作为例子,一般彻底的翻译至少还需要 <a href="/misc/goto?guid=4959643603686751671" rel="nofollow,noindex">babel-polyfill</a> 做支持,在最后生成文件的时候,我通过的是fs.createWriteStream 实现的(./deps/wrstream.js)</p>    <p>2.browserify</p>    <p>已经贴出来了一个commander的实现,其余的基本上都大同小异了,browserify的实现重点则在于require('browserify').bundle这个api,有兴趣的同学可以亲自去试一下。</p>    <p>3.uglify2</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/dca8f704fad00d46a0ba6c0132ae0c07.png"></p>    <p>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了)。</p>    <p>4.svn同步上传</p>    <p>svn同步上传这里主要运用的是从测试线的svn进行tar打包之后传到准线上svn目录,然后执行svn status操作,筛查相关add conflict以及modified等文件状态。进行相应的处理后再做ci操作。这里,我贴两段主要的逻辑:</p>    <p><img src="https://simg.open-open.com/show/f9fb44eb91eef8423e02dafd61300b2b.png"> <img src="https://simg.open-open.com/show/13797b39198a9c1ec2c19a08d6406a5d.png"></p>    <p>第一段逻辑是对svn目录进行tar打包之后传到准线上svn解包的操作,当然,folder可以你自己指定,如果是纯文件的话直接执行cp操作就可以;第二段逻辑是筛查svn add以及conflict冲突的shell函数,如果是需要add的则执行add操作,如果有冲突,则暂停提交,自己手动解决冲突。</p>    <p>5.nginx反向代理(无https)</p>    <p>这里重点讲一下nginx本地代理的实现,https因为还需要伪造公钥私钥对在这里不做讨论。一般来说代理的实现方式有很多种,市面上常见的:</p>    <ul>     <li>charles等,需要通过switchysharp来把chrome的端口从80代理到别的端口,然后对端口做监听,实现对请求的拦截;</li>     <li>proxy,直接修改本地wifi或者有线网络的代理配置,当然,这需要root权限;</li>     <li>nginx反向代理,一般来说网站的js基本都在同一个域名下面,那么我通过修改hosts文件实现域名的dns指向本地,然后通过nginx反向代理实现对80端口的代理,这样我在本地监听这个nginx代理的端口就可以实现proxy。</li>    </ul>    <p style="text-align:center"><img src="https://simg.open-open.com/show/bdf3b021070d956073accb0b750f2d98.png"></p>    <p>nginx配置应该是轻松加愉快了,只需要把js_server 80端口反向代理到(图为8088)本地监听的端口上即可,之后要做的就是修改hosts文件,使得js_server dns指向localhost。这两项工作都做完后,你只需要在本地的工作区根目录起一个端口为8088的nodeserver即可实现简单的本地文件代理,如果http 请求的文件地址和本地的工作区对不上,建议你在nginx做好配置,如果有commonjs等合并的行为,那么你需要在nodeserver中调用browserify等来进行合并之后再做返回,这里因为每个公司的应用场景都不太一样,所以不做详细的解读了。</p>    <h2>七.总结</h2>    <p>写这篇文章的目的,是为了给大家做本地cli提供一个思路,这是我个人的思路,欢迎大家拍砖,毕竟里面会有不成熟和欠考虑的地方。比如说在commander上注册插件的时候,我不是随取随用,而是直接全部遍历加载之后再进行process.argv的筛查,这里极大的降低了性能。当然大家也可以开放出全局变量,接入webpack之类或者自己写config文件来做相应的task,这块东西我在做的时候思考过,但是没有去实现,因为毕竟现在不论是gulp还是webpack已经非常丰富了,自己做cli的目的只不过是为了随拿随用,定制化配置,复杂化感觉真的没有任何必要,最后附上项目的 <a href="/misc/goto?guid=4959748139758941866" rel="nofollow,noindex">git链接</a> 。</p>    <p> </p>    <p>来自:http://div.io/topic/1975</p>    <p> </p>