你尚未登录,仅允许查看本站部分内容。请登录使用邀请码注册
xtx1130

nodejs入门之本地简易cli搭建 1个回复 专栏 @ Nodejs

xtx1130 发布于 5 月前

在阅读本文之前,我们假设您已经了解 babeluglify2,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链接

by:小菜

  • meow

    来围观菜总的

    #1
登录后回复,如无账号,请使用邀请码注册