打造属于你自己的 Express 脚手架(一)

本篇将会从0开始记录一个专属 Express 框架的诞生,先从最基础的开始

初始化

话不多说,先让项目跑起来

在项目文件夹下使用命令 npm init,选择一些自己的配置,然后会在文件夹下生成一个 package.json 文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// @ package.json
{
"name": "express-go",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node app.js"
},
"author": "",
"license": "ISC"
}

这个文件是 npm 项目必备的文件,包含了项目名称、作者名称、版本号、运行脚本,项目入口文件、依赖等等信息,有兴趣的可以去 npm官网 查看

接着,我们使用命令 npm i express --save 来安装 Express 到项目文件夹,安装完成后就会看到多了一个 node_modules 文件夹,这是存放 node 模块的地方,暂时不管它

在项目文件夹根目录下新建一个 app.js 文件,作为我们的项目启动入口文件,这个文件名要和 package.jsonmain 所对应的文件名保持一致

入口文件有了,接下来就可以做点什么了

使用 Express

我们要用 Express 搭建项目,当然要先引入它,上一步中我们已经安装了 Express ,所以就可以直接在代码中使用

1
2
3
4
5
6
// @ app.js
'use strict';
const express = require('express');
const app = express();

这里我们使用了 'use strict';,这表示使用了 严格模式,具体的就自行百度吧,主要是为了代码规范化和后面引入 ESlint,可能有童靴注意到了,我把两行代码的等号对齐了,其实这也是我个人的编码风格吧,只是因为这样在大量代码的情况下会显得比较整齐,包括在中文句子中的英文单词左右会留两个空格也一样,这都没用什么好坏对错之分,只是方便自己和别人查看罢了

上面的代码呢,就是引入了 Express 并给它声明了一个叫 app 的实例,很简单,不多说

可能看到这里你已经觉得很无聊了对不对,是啊,就跟其他教程里写的一样,太简单了

怎么说呢,既然是属于自己的项目,那么当然要有声明和别人不一样,这样才能拿出去愉快的装逼嘛

接下来,驾龄半年的老司机要开车了,各位童靴坐稳扶好

起飞

大部分人写的 app.js 里的代码量都很大,经常看着就令人生畏,前端开发里有一个很重要的词叫 优雅 ,既然同为 js 大家庭的一员,node 为什么不能也优雅一点呢,所以,我才不要在 app.js 里写那么多代码,就是要 优雅

在项目根目录下新建一个文件夹,叫 server,在 server 文件夹下新建一个 index.js 文件,接下来,有关项目启动的代码都写在这里,将app.js 里的代码拷贝到 index.js 中,添加代码如下,把 app.js 中的代码全部删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//@ server/index.js
'use strict';
const path = require('path');
const serveFavicon = require('serve-favicon');
const express = require('express');
const config = require('config');
const app = express();
app.use(serveFavicon(path.join(__dirname, '../static/img/fav.ico')));
function start () {
app.listen(config.web.port, function () {
logger.info(config.web.name, config.web.url, 'start up!');
});
};
if (!module.parent) {
start();
} else {
exports.start = start;
}

可以看到,我们新引入了几个模块,他们具体是做什么的呢,我们先说 config 模块

config模块

web 项目开发中,运行环境和相应的配置信息都是非常重要的,为了管理方便,我们可以使用 config 模块进行环境配置的管理,使用命令 npm install config --save 进行安装,本文中若无特殊说明,模块的安装均用这种格式

安装好之后,在项目根目录下新建 config 文件夹,在 config 目录下新建 default.jsdevelopment.js 两个文件,分别对应默认配置和开发环境的配置,如果需要的话,还可以新建 test.jsproduction.js 文件,表示测试环境和发布环境的配置,config 模块会根据运行时 process.env.NODE_ENV 的值来自动匹配相应的配置文件

我们先配置 default.js 的内容,其他环境的配置可进行参考,或者直接复制即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// @ config/default.js
'use strict';
const path = require('path');
const pkg = require('../package.json');
module.exports = {
web: {
url: 'http://127.0.0.1:10086',
host: '127.0.0.1',
port: 10086,
name: pkg.name
},
log: {
dir: path.join(__dirname, '..', `/logs/${pkg.name}/log/`),
nolog: /\.(js|css|png|jpg|jpeg|ico|svg|gif)/,
format: ':remote-addr :method :url :status :response-time ms :user-agent :content-length',
replaceConsole: true,
level: 'AUTO',
console: false
}
};

这里我们引入了 path 模块,安装方法同上,主要用作路径处理,感兴趣的可以自行百度,
pkg 使用了 package.json 中的数据

我们在 module.exports 中向外暴露了两个对象,web 和 log

web 中配置了项目启动的 url、host、端口和服务名,log 中配置了日志存放的路径,格式等信息,关于日志服务下面会讲

配置好之后,我们在其他文件中就可以使用 config 来读取配置文件,而不用担心当前的运行环境是开发还是测试,config模块会自动帮我们选择对应的配置文件,接着回头来看 server/index.js 文件

1
2
3
4
5
//@ server/index.js
const serveFavicon = require('serve-favicon');
...
app.use(serveFavicon(path.join(__dirname, '../static/img/fav.ico')));

serveFavicon 是用来设置 favicon 的一个中间件,根据路径可以看到我们新建了一个 static 文件夹,下面新建了 img 文件夹,ico 图标就放在这个目录下

path.join(__dirname, '../static/img/fav.ico')) 将会把图标的路径加入到项目的绝对路径中,这里的 __dirname 是指项目的当前文件的绝对路径

1
2
3
4
5
6
7
8
9
//@ server/index.js
function start () {
app.listen(config.web.port, function () {
logger.info(config.web.name, config.web.url, 'start up!');
});
};
exports.start = start;

这里定义了一个 start 方法,里面注册了 app 的监听端口

1
2
3
app.listen(config.web.port, function () {
logger.info(config.web.name, config.web.url, 'start up!');
});

先不管这里的 logger ,这是接下来要讲到的本地日志服务,在这之前先考虑这里的 config.web.xxx ,很明显,我们是把 config 注册为了全局变量

接着把 start 方法暴露出去并命名为 start,这样就可以在别的文件中引入这个文件并直接调用 start 方法了

注册全局变量

在项目中有很多变量是在很多地方使用而且不需要对他们进行修改的,这些变量就可以注册为全局变量,我们在根目录下新建 register.js 文件

1
2
3
4
5
6
//@ register.js
global.ROOT_PATH = __dirname;
global.config = require('config');
global._ = require('lodash');

lodash,是一个非常好用的模块,虽然暂时用不到,但还是先注册一个,有兴趣的可以先自己去了解一下,global 是 node 环境下的全局对象,类似于 window 在浏览器环境下的作用

这里注册了没用,我们还需要在 app.js 中将这个模块引入

1
2
3
4
5
//@ app.js
'use strict';
require('./register');

日志服务

一个好的开发环境是离不开日志服务的,我们需要在本地记录运行时的重要信息和错误日志等等,这里用到一个模块 log4js,先进行安装,然后在项目根目录下新建一个 tools 文件夹,用来存放一些工具,然后在下面新建 logger 文件夹,再在 logger 下新建 index.js 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// @ tools/logger/index.js
'use strict';
const log4js = require('log4js');
const path = require('path');
fse.mkdirsSync(config.log.dir);
fse.mkdirsSync(config.log.dir + 'main');
const log4jsConfig = {
'replaceConsole': config.log.replaceConsole,
'level': config.log.level,
'appenders': [{
'type': 'console'
}, {
'type': 'dateFile',
'filename': path.join(config.log.dir, 'main/log'),
'pattern': 'yyyyMMdd',
'alwaysIncludePattern': true,
'maxLogSize': 20480,
'backups': 3,
'category': 'main'
}, {
'type': 'logLevelFilter',
'level': 'ERROR',
'appender': {
type: 'file',
filename: path.join(config.log.dir, 'main.ERROR'),
maxLogSize: 20480
},
'category': 'main'
}]
}
log4js.configure(log4jsConfig);
const logger = log4js.getLogger('main');
logger.setLevel('AUTO');
logger.log4js = log4js;
module.exports = logger;

这里我们又用到了一个新的全局变量fse,回到 register.js,添加一行代码

1
2
3
// @ register.js
global.fse = require('fs-extra');

fs-extra 是一个对 fs 模块进行扩展的模块,两者都是用来对文件进行操作,回到 tools/logger/index.js

1
2
fse.mkdirsSync(config.log.dir);
fse.mkdirsSync(config.log.dir + 'main');

这两行代码将会在根目录下生成了 log/demo/log/main ,这里的文件夹名都是在 config 中配置好的,可以自行修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const log4jsConfig = {
'replaceConsole': config.log.replaceConsole,
'level': config.log.level,
'appenders': [{
'type': 'console'
}, {
'type': 'dateFile',
'filename': path.join(config.log.dir, 'main/log'),
'pattern': 'yyyyMMdd',
'alwaysIncludePattern': true,
'maxLogSize': 20480,
'backups': 3,
'category': 'main'
}, {
'type': 'logLevelFilter',
'level': 'ERROR',
'appender': {
type: 'file',
filename: path.join(config.log.dir, 'main.ERROR'),
maxLogSize: 20480
},
'category': 'main'
}]
}

这里是关于 log4js 模块的一些配置,具体的配置参数可以参考 log4js 官网,这里我们主要配置了日志的存放地址、最大长度、日志格式和类型

1
2
3
4
5
6
7
8
log4js.configure(log4jsConfig);
const logger = log4js.getLogger('main');
logger.setLevel('AUTO');
logger.log4js = log4js;
module.exports = logger;

接着把生成的 log4js 实例命名为 logger 并暴露出去,接着在全局变量中注册一个 logger 变量

1
2
3
// @ register.js
global.logger = require('./tools/logger');

然后在 server/index.js 中使用这个日志中间件

1
2
3
//@ server/index.js
app.use(logger.log4js.connectLogger(logger, config.log));

到这里,我们的日志服务就基本构造完成了,接下来就去 app.js 中启动这个项目,看看效果,在 app.js 中添加以下代码

1
2
3
4
5
6
'use strict';
require('./register.js');
const web = require('./server/web.js');
web.start();

在命令行使用命令 node app.jsnpm start,如果看到类似下面截图的内容,说明我们的项目成功运行了,如果有问题,可以根据错误日志去寻找答案

下车

好了,本站的终点站已到,童鞋们下车,我们下一站是 路由规划和控制器的结构,按时上车喔

打造属于你自己的 Express 脚手架(2)–路由管理