
1.2.3 目录解析
使用koa-generator生成的目录结构如下。

对上面的目录结构说明如下。
○ app.js为入口。
○ bin/www为启动入口。
○ 支持静态服务器,即public目录。
○ 支持routes路由目录。
○ 支持views视图目录,Pug为模板引擎。
➘ package.json
package.json是Node.js模块定义的核心配置文件。接触新项目的第一件事情就是打开该文件,了解模块的各个属性,比如名称、版本、依赖模块、开发方式等,文件内容如下。

package.json文件里包含了4个npm scripts脚本,分别介绍如下。
○ npm start是开发阶段使用的脚本,使用时代码会发生变动,需要重启Node.js进程。
○ npm run dev也是开发阶段使用的脚本,使用时代码会发生变动,nodemon会自动重启Node.js进程。
○ npm run prd是产品环境使用的脚本,通过pm2来启动工程,默认按照CUP核数来启动对应的进程数,是目前最流行的方式。
○ test只会打印出未实现日志,和Express里的用法是一样的。
关于依赖模块的说明,如表1-2所示。
表1-2

➘ 入口文件bin/www
我们先来看一下入口文件的核心代码,具体如下。

bin/www文件中的内容和Express的bin/www文件中的内容是一样的,唯一不同的是Express里的app实例返回的是function(req,res){},而Koa里的app.callback()返回的是function (req,res){},所以想在http.createServer里启动Koa,就必须将app修改为app.callback()。
除在http.createServer启动时需要进行以上修改外,对于Express里测试API的supertest测试库,也要求进行一样的修改,一定要注意Express和Koa之间的差异。
➘ 核心文件app.js
app.js是Koa的核心文件,主要包含4个部分,分别如下。
○ 中间件
○ 路由
○ 静态服务器
○ 视图
app.js文件的代码如下。

在以上代码中,中间件的名称、用途和加载顺序如表1-3所示。
表1-3

➘ 路由位于routes目录下
路由本身也是中间件,应位于独立目录routes下面,这样可以使路由的职责更清晰。这里以routes/index.js为例,给出路由样板结构,具体如下。

以上路由提供了3个示例,分别是视图渲染、JSON API和字符串。
1.视图渲染

2.JSON API

3.字符串

你一定已经发现,赋予ctx.body不同类型的值时会返回不同的结果,比如,为ctx.body赋值JSON对象时,服务器会返回JSON接口,为ctx.body赋值字符串时,服务器会返回HTML文本。另一个要点就是,ctx.render是因为添加了koa-views中间件而绑定到ctx上的,原本的ctx上是没有render函数的,也就是说,我们可以通过中间件在ctx上绑定我们要使用的功能。
➘ 静态服务器位于public目录下
静态服务器主要用于存放静态资源,比如HTML、CSS、JavaScript、图片等,功能和Nginx、Apache等软件是一样的。这里的koa-static是一个用于文件托管的中间件,是基于koa-send模块的高级封装。public目录结构如下所示。

我们来看一下stylesheets/style.css文件对应的静态资源请求在Chrome中的状态,如图1-2所示。

图1-2
图1-2中涉及的要点如下。
○ 该请求是一个GET请求(所有的静态资源都是GET请求)。
○ 该请求的Content-Type是text/css,这是根据文件的后缀名自动识别的,其他类型的文件以此类推。
○ 具体的响应内容在response标签里。
○ 其他的内容都是服务器自带的头部信息,比如,etag、cache-control等。
总结而言,以上内容是一个GET请求,请求路径是/stylesheets/style.css。服务器收到这个请求后,将根据请求路径查找是否存在/stylesheets/style.css文件,如果存在,则根据.css识别并设置Content-Type为text/css,状态码为200,同时获取文件内容作为response的body写入浏览器。如果不存在,则设置状态码为404,显示Not Found。
public目录是为了方便开发而存在的,一般真正的项目有如下3类。
○ 纯API项目,不需要public目录。
○ 纯前后端分离项目,后端不需要public目录,前端需要。
○ 需要public目录的项目,但会将public目录里的内容分发到CDN上。
注意,不要把static中间件放到Koa的全局中间件上(如果对于每个请求都要判断一次是不是静态资源,会影响QPS),最好结合koa-router来处理,按需挂载,代码如下。

➘ 视图位于views目录下
将HTML页面写到public目录下就可以正常使用,这种情况通常只适用于静态网站,无法满足实际需求。比如,需要跟数据库打交道的时候,数据是变化的,页面也要根据数据来变化。在这种情况下,我们就不能写HTML页面了,而是需要使用模板引擎。这里的views目录就是用于存放模板的。
模板引擎采取了一种复用思想,通过定义模板,在使用时和数据一起编译,生成HTML页面,以便浏览器渲染。从这个描述里我们可以找出以下关键点。

模板引擎有好多种,下面介绍3种典型的模板引擎。
○ EJS:嵌入JavaScript语法的模板引擎(E=Embed),类似于JSP、ASP、ERB。在HTML页面里嵌入模板特性,这对于熟悉HTML的人来说非常简单,只要区分哪些地方是可变的,哪些地方是不变的即可。
○ Pug(以前叫Jade):缩进式极简写法的模板引擎,最早出现于Ruby中。这种写法虽好,但需要在大脑中进行思路转换,这其实是比较麻烦的,如果对HTML不是特别熟悉,这种思路转换将非常困难。
○ Nunjucks:它和EJS比较像,跟Pug一样强大,据说其语法出自Python的某款模板引擎。
前面介绍路由的时候,举过下面这样的例子。

这里的ctx.render就是用于渲染模板的方法,它有两个参数。
○ index:模板,采用相对路径,对应的是views目录下的index.pug。
○ JSON对象:所谓的数据。
按照模板引擎的原理,我们可以推理出ctx.render执行的操作,如下。
○ 通过文件读取index.pug模板。
○ 使用Pug模板引擎编译器将数据和模板内容编译为HTML字符串。
○ 将Content-Type设置为text/html。
○ 将statusCode状态码设置为200。
○ 通过http模块底层的res.write和res.end方法将HTML字符串写入浏览器。
下面再来看看模板,如layout.pug定义的布局模板,布局模板是根据很多页面的共性特点抽象出来的模板,主要用于留空,示例如下。

可以看到,上面body内部的block content就留了一个空,由继承的模板自行填充内容。
index.pug继承自layout的模板,通过extends layout继承,通过block来填充具体的内容,示例如下。

上面的#{title}插写了一个数据里的变量,即ctx.render的第二个参数里的数据。

所以最终显示的就是“Welcome to Hello Koa 2!”这样的HTML,是不是非常简单?
模板引擎的功能非常强大,下面以Pug为例进行说明,带大家进一步了解模板引擎的特性,如表1-4所示。
表1-4

模板是不可变的,可变的是数据,然而数据是从哪里来的呢?可以从数据库读取,可以访问接口获得,甚至可以模拟,具体取决于业务场景。
模板编译是耗时的,所以缓存显得尤为重要,koa-views依赖consolidate.js模块,而consolidate.js可以提供缓存,所以速度相对较快。