#点击图片报名参加武汉、长沙源创会#
在过去五年中,Node.js 使软件开发统一起来。您可以用Node.js做任何你能想到的事情,前端开发,服务器端脚本,跨平台桌面应用程序,跨平台移动应用程序,物联网等。编写命令行工具也比以往任何时候都更容易,因为Node.js —— 不仅是一个命令行工具,它还具有交互式,易用和开发耗时少等特点。
如果您是前端开发人员,那么您肯定听说过或者用过 Gulp,Angular CLI,Cordova,Yeoman 等。你有没有想过他们如何工作的?例如,就Angular CLI来说,通过运行诸如ng new 命令就能创建一个具有基本配置的Angular新项目。而 Yeoman 等工具也要求运行时输入最后帮助您自定义项目的配置。Yeoman中的一些生成器可帮助你将项目发布到生产环境中。这就是我们今天要学习的。
在 SMASHINGMAG 上的相关阅读:
我们会在本教程中开发一个命令行程序,它会接收一个包含客户信息的 CSV 文件,然后使用 SendGrid API 向这些客户发送电子邮件。下面是这篇教程的目录:
“Hello, World”
处理命令行参数
运行时用户输入
异步网络通信
美化 CLI 输出
使其成为 shell 命令
JavaScript 之外
本教程假设你已经安装了 Node.js。如果你还没有安装,请先安装。Node.js 带有一个叫 npm 的包管理器。使用 npm,你可以安装很多开源的包。在npm 的官方网站可以看到这些包的完整列表。我们这个项目中会用到许多开源模块(稍后会用到更多)。现在先使用 npm 创建一个 Node.js 项目。
这里我创建了一个名为 broadcast 的目录,并在其中运行了 npm init 命令。然后提供了一些项目的基本信息,比如名称(name)、说明(description)、版本(version)和入口(entry point)。入口是指执行脚本时最开始执行的 JavaScript 文件。默认情况下,Node.js 把 index.js 作为入口;不过在这个示例中,我们把入口改为 broadcast.js 了。运行 npm init 命令的时候,你会看到更多选项,比如 Git 库、许可证和作者。你可以输入某些值,也可以留空不填。
成功执行 npm init 之后你会发现当前目录下出现了一个 package.json 文件。这是我们的配置文件。现在,它保存着我们创建项目的时候输入的信息。如果你想更详细的了解 package.json,可以看看 npm 的文档。
现在我们已经建立了项目,接下来开始写 “Hello World” 程序。先在项目中创建 broadcast.js 文件,它会是你的主文件。在文件中输入如下这段代码:
运行这段代码。
你可以看到控制台输出 “hello world”。你可以通过 node broadcast.js 来运行,也可以通过 node broadcast 来运行。Node.js 能够识别这两种方式。
在 package.json 的文档中,提到了一个名为 dependencies 的选项。在这个选项中你可以申明想用于本项目的所有第三方模块以及它们的版本号。我曾提到我们会在开发这个工具的过程中使用很多第三方开源模块,因此,我们的 package.json 看起来是这样的:
我们会使用 Async、Chalk、Commander、CSV、Inquirer.js 和 SendGrid,在教程进行的过程中,我们会逐步讲解这些模块的用法。
获取命令行参数并不困难,只需要使用 process.argv 就可以了。但解析这些参数的值和选项可是件累人的活。所以,与其重复发明轮子,不如直接用 Commander 模块。Commander 是个开源的 Node.js 模块,用于帮助我们写可交互的命令行工具。它带来了很多用于解析命令行选项的特性,而且还支持 git 风格的子命令。我最喜欢的 Commander 特性是自动生成到屏幕的帮助信息。你不需要写多余的代码 —— 只需要解析 --help 或 -h 选项就可以了。你在定义各种命令行选项的时候,它会自动生成 --help 帮助信息。让我们用一下看看:
这行命令会在你的 Node.js 项目中安装 Commander 模块。在 npm 命令中使用 --save 选项会自动将 Commander 包含在项目的依赖项(dependencies)中,上面曾提到它在 package.json 中定义。在我们这个示例中,所有依赖项都已经申明了,所以我们不需要运行这个命令。
可以看到,处理命令行参数很简单。我们已经完成了对 --list 选项的定义。现在只要是在 --list 选项之后的值都会保存在括号里申明的变量中——在本例中,叫 list。你可以通过 program 命令来访问它,这个 program 是 Commander 的实例。目前这个程序只是通过 --list 来接收一个文件路径然后打印到控制台。
你一定也注意到了我们调用方法链中的一个方法,version。如果我们在运行命令的时候使用 --version 或 -V 作为参数,就会打印出作为参数转入到这个方法的值。
同样,如果运行命令的时候给出 --help 选项,它会打印出所有定义好的选项和子命令。本例中的输出是这样的:
现在我们已经可以从命令行参数中接收到文件路径,接下来开始使用 CSV 模块读取 CSV 文件。CSV 模块提供处理 CSV 文件的全面解决方案。从创建 CSV 文件到进行解析,都可以用这个模块来实现。
因为我们打算使用 SendGrid API 来发送电子邮件,所以用下面这个文档来作为 CSV 文件的示例。我们会使用 CSV 模块来读取数据,并显示出相应行中的姓名和电子邮件地址。
现在让我们来写一段程序,从 CSV 文件中读取数据并打印到控制台。
我们可以使用原生的文件系统(File System)模块读取从命令行参数获取的文件。文件系统模块中预定义了事件,其中 data 事件会在读取到一块数据时触发。CSV 模块提供的 parse 方法能将 CSV 文件拆分行,并多次触发 data 事件。每个 data 事件都会发送一个数组,包含每一列的数据。那么,在本例中会按下面的格式打印数据:
我们已经知道如何接收命令行参数并解析它们。但是如果想在运行时获取输入该怎么办呢?Inquirer.js 模块能让我们接收不同类型的输入,包括纯文本、密码和可多选项的列表等。
在这个示例中,我们会在运行时接收用户输入的电子邮件地址和姓名。
首先,注意上例中我们创建了一个名为 contactList的数组,我们用它保存来自 CSV 文件的数据。
Inquirer.js 有一个叫做 prompt的方法,它的参数是一组我们想在运行时提出的问题。在本例中就是我们想知道的发送者的姓名、电子邮件地址和电子邮件的主题。我们创建了一个叫 questions 的数组,用来保存这些问题。这个数组的元素是一个包含像类型(type)这类属性的对象,类型用于标识输入的是密码还是列表,或者其它。你可以在官方文档看到完整的类型列表。这里的 name 保存了用户输入所需要保存的信息的名称。 prompt 方法返回一个 Promise 对象,根据成功或者失败会调用不同的回调函数,当然这会在用户回答完所有问题之后进行。用户的回答通过 then 回调的参数 answers 来访问。执行上述代码会得到如下结果:
现在我们可以从 CSV 文件读取收件人的数据,并从命令行输入获取发送者的资料,是时候发送电子邮件了。我们使用 SendGrid API 来发送电子邮件。
为了使用 SendGrid 模块,我们需要取得 API key。你可以在 SendGrid 的控制面板(需要创建一个账号)生成这个 API key。生成之后,把它保存在环境变量 SENDGRID_API_KEY 中。在 Node.js 中可以使用 process.env 来访问环境变量。
在上面的,我们使用 SendGrid API 和 Async 模块异步发送了一个电子邮件。Async 模块是最强大的 Node.js 模块之一。异步回调常常导致回调地狱。如果存在非常多的异步调用,你最终会写出回调中使用回调的代码,无法停下来。对于 JavaScript 开发者来说,处理错误也变得极其复杂。Async 模块帮助你解决回调地狱,它提供了大量诸如 each、series、map 这样的方法让事件变得简单。这些方法帮助我们写更容易维护的异步代码。
在这个示例中,异步使用 SengGrid 发送邮件优于同步发送。这样就可以根据反馈再发送后续请求。你可以使用 Async 模块中的 each 方法,就可以遍历 contactList 数组并调用 __sendEmail方法。这个方法会接收收件人的详情,发送人详情,主题,以及一个异步回调函数。__sendEmail 使用 SengGrid API 发送电子邮件,你可以从 SengGrid 的官方文档深入了解 SengGrid。邮件发送完成会立即触发异步回调,它会处理 contactList 数据中的下一个对象。
到此为止,我们就用 Node.js 创建了一个用 CSV 接收输入信息并发送电子邮件的命令行应用。
我们的应用已经可以发送电子邮件了。现在再看看如何让输出更好看,比如那些错误或成功的消息。这里我们需要用到 Chalk 模块,这个模块用于命令行输出[译者注:原文是 inputs,但从意思以下面的代码来看,这里作者应该是想说输出的内容,它首先是 Chalk 的输入,通过 Chalk 处理之后再输出到控制台]的样式。
在上面的代码中,我们在发送邮件的过程添加了回调函数,这个函数会在每次循环的异步调用成功或因为运行时错误中止时调用。如果循环未完成,我们会在控制台用红色字体打印它发送出的错误对象,否则,用绿色字体打印出成功的消息。
如果你阅读了 Chalk 的文档,你会发现它有很多选项用于给输出添加样式,包括一些控制台颜色(品红、黄色、蓝色等)、下划线或加粗文本。
现在我们的工具已经完成了,接下来应该让它像普通的 shell 命令那样执行。首先,我们在 broadcast.js的顶部添加一行申明,它会告诉 shell 如何执行脚本。
现在配置 package.json 使其可执行。
我们添加了一个叫 bin的新属性,我们在这里提供了执行 broadcast.js 命令的名称。
最后一步是将这个脚本安装为全局脚本,之后就可以像普通的 shell 命令一样执行它了。
在执行这个命令前,请确保它们处于相同的项目目录下。安装完成后即可测试这个命令。
这个命令会打印出有效的选项,就像之前执行 node broadcast --help的输出一样。现在发布这个工具的准备工作都已经完成了。
要记住一点:在开发过程中,如果你只是简单的执行 broadcast 命令的话,是看不到项目代码变动所带来的效果的。在运行 broadcast 的时候,你得意识到 broadcast 的路径并不在你所在的项目目录中。如果你希望它在的话,可以在项目目录中运行 npm link ,它会自动在可执行命令和项目目录间创建一个符号连接,之后,你在项目目录中所做的变动都会在 broadcast 命令中体现出来。
实现这类 CLI 工具所涉及的知识远远超出单纯的 JavaScript 项目。比如,如果你有软件开发和 IT 方面的经验的话,开发过程中可能涉及到 Bash工具。从编写脚本到使用 cron 任务来进行备份都可以使用 Bash 脚本来自动化。实际上,在Docker、Chef 和 Puppet 成为标配之前,Bash 简直就是救世主。不过,Bash 脚本经常出问题,它们真的不适合出现在开发流程中。所以通常我们用 Python、Java 或者 JavaScript 等语言时,Bash 很少成为开发的核心,因为在 Bash 中甚至写个简单的条件语句都需要查阅无数的文档并进行调试。
无论怎么说,使用JavaScript 可让整个过程变得更加简单和高效。 所有的工具自动成为跨平台工具。 如果要运行本机shell命令,如git,mongodb或heroku,可以使用Node.js中的Child Process模块轻松实现。 这使您能够使用简单的JavaScript编写软件工具。
希望本教程对您有帮助。如果您有任何问题,请在下方评论或@我。