$ mkdir myapp
$ cd myapp
$ npm init
入口文件名最好设置为 项目名.js
,避免混淆。(如:我的项目叫myapp,那么我的入口文件命名为myapp.js)
$ npm install --save express
添加到代码库时,忽略掉的文件或文件夹
新建
.gitignore
文件
# filename: .gitignore
# igonre packages installed by npm
node_modules
新建
myapp.js
文件
// filename: myapp.js
const express = require('express')
const app = express()
// 设置监听端口
app.set('port', process.env.PORT || 3000)
// 定制路由规则
app.get('/', function(req, res) {
res.type('text/plain')
res.send('Hello World')
})
app.get('/about', function(req, res) {
res.type('text/plain')
res.send('About Hello World')
})
// 定制404页面
app.use(function(req, res) {
res.type('text/plain')
res.status(404)
res.send('404 - Not Found')
})
// 定制500页面
app.use(function(err, req, res, next) {
console.error(err.stack)
res.type('text/plain')
res.status(500)
res.send('500 - Server Error')
})
// 监听端口
app.listen(app.get('port'), function() {
console.log(`Express started on http://localhost:${app.get('port')}; press Ctrl - C to terminate`)
})
// 使用Redirect Path可以方便开发过程中查看状态码
# 安装模板引擎
$ npm install --save express3-handlebars
安装后,在项目根目录新建
views
目录,用于存放模板文件
在views
目录,新建layouts
子目录,用于存放布局模板文件
在myapp.js
中 添加设置模板引擎为handlebars
// filename: myapp.js
// code ...
const app = express();
// 设置handlebars为模板引擎
const handlebars = require('express3-handlebars')
.create({ defaultLayout:'main' }) // 指明默认布局使用 main.handlebars 作为模板
app.engine('handlebars', handlebars.engine)
app.set('view engine', 'handlebars')
// code ...
在
view/layouts/
目录下创建一个main.handlebars
文件,这个是布局模板
<!-- filename: main.handlebars -->
<!DOCTYPE HTML>
<html>
<head>
<title>My App</title>
</head>
<body>
{{{body}}}
</body>
</html>
接下来分别给
首页
、关于
、404
、500
页面创建模板,
在
views
目录下,新建home.handlebars
,内容为<h1>我是首页</h1>
在
views
目录下,新建about.handlebars
,内容为<h1>我是关于页面</h1>
在
views
目录下,新建404.handlebars
,内容为<h1>404 - NOT FOUND</h1>
在
views
目录下,新建500.handlebars
,内容为<h1>500 - Server Error</h1>
模板已经设置好了,接下来修改myapp.js
的路由规则
// filename: myapp.js
// code
app.get('/', function(req, res) {
res.render('home')
})
app.get('/about', function(req, res) {
res.render('about')
})
// 404 catch-all 处理器(中间件)
app.use(function(req, res, next){
res.status(404)
res.render('404')
})
app.use(function(err, req, res, next){
console.log(err.stack)
res.status(500)
res.render('500')
})
node重启一下之后,会发现已经handlebars模板生效了
在项目根路径新建
public
目录,用于存放静态资源
public
下新建img
目录,用于存放图片
找一张png
图片,命名为logo.png
,放在public/img/
下
在所有的路由之前,加入中间件指派静态资源目录
// filename: myapp.js
// code...
app.use(express.static(__dirname + '/public'))
app.get('/', function(req, res) {
res.render('home')
})
// code...
为了让每个页面都有logo,这个时候需要修改views/layouts/
下的main.handlebars
<!-- filename: main.handlebars-->
<!DOCTYPE HTML>
<html>
<head>
<title>My App</title>
</head>
<body>
<!-- 添加如下 -->
<header><img src="/img/logo.png" alt="logo标志"></header>
{{{body}}}
</body>
</html>
如果要将动态的数据插入到模板当中,只需要在res.render()
中传入两个参数,第一个参数是模板名,第二个参数需要传入一个对象。
// filename: myapp.js
// 定义一个用数组,用于随机输出内容
var fortunes = [
'我用双手,成就你的梦想',
'让我们来猎杀,那些陷入黑暗中的人',
'你们会输的~',
'我~~是太阳的抉择!',
'哼,一个能打的都没有',
'我的箭飞向真理',
'是时候表演真正的技术了',
'好运,不会眷顾傻瓜~'
]
app.get('/', function(req, res){
// 从数组中随机选择一个元素
var randomThings = things[Math.floor(Math.random() * fortunes.length)]
// 将 data 传入 home.handlebars
res.render('home',{ data: randomThings })
})
如果项目还没有进行版本控制:
# 文件夹初始化
$ git init
# 将所有文件及文件夹添加到暂存区
$ git add -A
# 将单个文件或者文件夹添加到暂存区
$ git add meadowlark.js
# 将提交修改
$ git commit -m '备注'
# 拉取项目
$ git clone https://github.com/用户名/储存库名
# 回退版本
$ git checkout 版本名
# 添加远程储存库
$ git remote add origin git@github.com:用户名/储存库名.git
# 提交至远程储存库
$ git push -u origin master
在上面的myapp.js
中,有一个从数组中随机取一个元素的业务,将其抽离出来封装成一个模块
在项目根目录新建
lib
目录,用于保存模块
在lib
目录,新建一个fortune.js
将myapp.js
中相关业务部分抽离到fortune.js
const fortunes = [ '我用双手,成就你的梦想', '让我们来猎杀,那些陷入黑暗中的人', '你们会输的~', '我~~是太阳的抉择!', '哼,一个能打的都没有', '我的箭飞向真理', '是时候表演真正的技术了', '好运,才不会眷顾傻瓜~' ] // 如需要让变量在模块外可见,需要把他加载到exports上 exports.getFortune = function () { var idx = Math.floor(Math.random() * fortunes.length) return fortunes[idx] }
在myapp.js
需要引入模块
// filename: myapp.js
// code...
const app = express()
const fortune = require('./lib/fortune')
// code...
app.get('/about', function(req, res) {
res.render('about',{data: fortune.getFortune()})
})
页面测试 | 跨页测试 |
---|---|
测试页面的表示和前端的功能,同时涉及单元测试和集成测试 ,会使用Mocha 进行页面测试 | 从一个页面转到另一个页面的功能的测试。这种测试涉及多个组件,一般被当成集成测试 ,使用Zombie.js 测试 |
逻辑测试 | 去毛(代码风格检查) |
对逻辑域进行单元和集成测试。它只会测试JavaScript,跟所有表示功能分开 | 去毛 不是找错误,而是找潜在错误, 使用JSHint 或ESLint 进行代码风格检查 |
链接检查 | |
确保网站上没有破损链接,使用LinkChecker 做链接检查 |
服务器可以使用
nodemon
监控文件是否改动,如果改动自动重启服务器
npm istall --save (将包放在依赖项中)
npm istall --save-dev (将包放在开发依赖项中)
使用mocha
测试框架 + Chai
断言库
- 安装
$ npm install --save-dev mocha # mocha测试框架 $ npm install --save-dev chai # 断言库 $ mkdir public/vendor # 新建 vendor 目录,用于存放第三方模块,库
拷贝到静态资源文件夹, Linux环境下使用
cp
,window环境下使用copy
$ cp node_modules/mocha/mocha.js public/vendor # Mocha 资源放到public目录下 $ cp node_modules/mocha/mocha.css public/vendor $ cp node_modules/chai/chai.js public/vendor # chai 资源拷贝到public下
- html页面和入口文件配置
mocha
和chai
测试开启条件:
- 默认禁用测试;
- 传入相应参数开启测试页面;(http:127.0.0.1:3000/?test=1开启)
首先,修改
myapp.js
,设置中间件判断是否需要测试// filename: myapp.js // code.. // 在路由之前配置中间件来检测查询字符串中的条件 app.use(function(req, res, next) { res.locals.showTests = app.get('env') !== 'production' && req.query.test === '1' // 通过url判断是否开启测试页面 next() }) // 路由放在这... // code..
然后修改
views/layouts/main.handlebars
,有条件地引入mocha测试框架,修改<head>
部分<head> <title>My App</title> {{#if showTests}} <!--这个showTests是从myapp.js里面传过来的--> <link rel="stylesheet" href="/vendor/mocha.css"> {{/if}} <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script> </head>
在
</body>
之前{{#if showTests}} <div id="mocha"></div> <script src="/vendor/mocha.js"></script> <script src="/vendor/chai.js"></script> <script type="text/javascript"> mocha.ui('tdd') // TDD(测试驱动开发),如果不设置默认是BDD(行为驱动开发) var assert = chai.assert </script> <script src="/qa/tests-global.js"></script> {{#if pageTestScript}} <script src="{{pageTestScript}}"></script> {{/if}}
之后,在
public
目录下,新建qa
目录,在qa
目录下新建tests-global.js
suite('所有页面测试',function() { // 配置测试规则 test('页面具有有效标题', function() { // 断言 assert(document.title && document.title.match(/\S/) && document.title.toUpperCase() !== 'TODO') }) })
以上配置是在所有页面上都进行的测试
如果需要为特定的页面,配置特定的测试规则,需要在上述配置之外在特定页面的路由中传入参数
首先,在qa
目录下新建tests-about.js
// filename:tests-about.js // 配置规则:如页面上至少有一个链接指向`/contact` suite('about页面测试', function() { test('含有“联系我们”入口', function() { assert($('a[href="/contact"]').length) }) })
然后,需要在路由中指明视图使用哪个页面测试文件,在
myapp.js
中修改/about
的路由// filename: myapp.js app.get('/about', function(req, res) { res.render('about',{ fortune: fortune.getFortune(), pageTestScript: '/qa/tests-about.js' }) })
如此访问页面的时候带上参数
?test=1
即可开启测试
如:记录用户是通过哪个推广链接,来到被推广页面
首先,在
views
目录新建一个adsense
目录,并在adsense
目录下新建一个baidu.handlebars
<h1>假装我是百度</h1> <a class="targetPage" href="/adsense/target">目标活动页面</a>
然后,在
views/adsense
目录新建target.handlebars
,使用隐藏域将访问来源进行跟踪<h1>欢迎访问目标活动页面</h1> <form> <input type="hidden" name="referrer"> <p>姓名:<input type="text" name="name" id="fieldName"></p> <p>邮箱:<input type="email" name="email"></p> <p><input type="submit" value="Submit"></p> </form> <script> $(document).ready(function() { $('input[name="referrer"]').val(document.referrer) }) </script>
接着,在
myapp.js
中为上面两个页面配置路由:// filename: myapp.js // code ... app.get('/adsense/baidu', function(req, res) { res.render('adsense/baidu') }) app.get('/adsense/target', function(req, res) { res.render('adsense/target') }) // code ...
使用
mocha
配合无头浏览器(Zombie
、Selenium
、PhantomJS
)进行跨页测试
本次使用Zombie
+mocha
$ npm install --save-dev zombie $ npm install -g mocha # 如果没有全局安装mocha的话,安装一下
在项目根目录新建
qa
目录,用于存发测试文件,并在qa
目录下新建test-crosspage.js
// filename: test-crosspage.js // 引入 Zombie 无头浏览器模块,以及chai断言库 var Browser = require('zombie') var assert = require('chai').assert var browser // 配置规则 suite('跨页测试', function() { // 在测试框架运行前执行 browser.setup(function() { // 每次测试都使用一个新的浏览器 browser = new Browser() }) // 第一条规则 test('经由baidu页面来到推广页面', function(done) { let referrer = 'http://127.0.0.1:3000/adsense/baidu' browser.visit(referrer, function() { browser.clickLink('.targetPage', function() { // 浏览器模拟点击链接之后,开始断言 assert(browser.field('referrer').value === referrer) done() }) }) }) //第二条规则 test('经由google页面来到推广页面', function(done) { let referrer = 'http://127.0.0.1:3000/adsense/google' browser.visit(referrer, function() { browser.clickLink('.targetPage', function() { // 浏览器模拟点击链接之后,开始断言 assert(browser.field('referrer').value === referrer) done() }) }) }) //第三条规则 test('直接来到推广页面的流量', function(done) { browser.visit('http://127.0.0.1:3000/adsense/target', function() { assert(browser.field('referrer').value === '') done() }) }) })
这个时候我们的服务器在运行中,所以我们需要打开另外一个命令行,并来到根目录下
# mocha -u TDD模式 输出日志为 spec 测试脚本路径 $ mocha -u tdd -R spec qa/tests-crosspage.js
使用Mocha
+ chai断言库
在
qa
目录新建tests-unit.js
测试文件,用于测试fortune.js
模块// filename: tests-unit.js var fortune = require('../lib/fortune') var expect = require('chai').expect // 配置测试规则 suite('Fortune 模块测试', function () { test('getFortune()函数应该返回一个fortune值', function () { expect(typeof fortune.getFortune() === 'string') }) })
最后,在保证服务器运行的情况下,打开另外一个命令行,切换到项目根目录
$ mocha -u tdd -R spec qa/tests-unit.js
使用JSHint
或者ESLint
第一种:
JSHint
通过npm
安装JSHint
$ npm install -g jshint
运行比较简单,指定文件名就调用它即可
$ jshint myapp.js
如果不符合规范的话,会报错,一些比较小的问题,可以添加参数,自动修复
$ jshint myapp.js --fix
第二种:
ESlint
通过npm
安装ESlint
$ npm install -g eslint
之后再项目根目录下,初始化
ESlint
$ eslint --init
根据提示操作即可
使用命令可以调用,--fix
自动修复$ eslint myapp.js --fix
具体操作配置等,可见文档
Grunt
$ npm install -g grunt-cli # 全局安装 grunt命令行 $ npm install --save-dev grunt # 将grunt 加入项目开发依赖
这里我们需要用到grunt的
mocha
插件以及ESLint
还有可以执行CMD命令行
的exec
插件
分别使用命令先查询一下插件名$ npm search grunt-mocha $ npm search grunt-eslint $ npm search grunt-exec
找到你觉得合适的包
这里使用
grunt-cafe-mocha
和grunt-eslint
,还有grunt-exec
$ npm install --save-dev grunt-mocha grunt-eslint grunt-exec # 将用到的模块加入到项目开发依赖
接下来在项目根目录下新建
Gruntfile.js
,用以编辑grunt
自动化任务// filename: Gruntfile.js module.exports = function(grunt) { // 加载插件 ['grunt-cafe-mocha', 'grunt-eslint', 'grunt-exec'].foreach(function(task) {grunt.loadNpmTasks(task)}) // 配置插件 grunt.initConfig({ cafemocha: {all: {src: 'qa/tests-*.js', options: {ui: 'tdd'}}}, eslint: { // 这里将eslint需要检测文件分成两部分 // 第一部分是入口文件,以及封装好的模块 app: ['myapp.js', 'public/js/**/*.js', 'lib/**/*.js'], // 第二部分是测试文件 qa: ['Gruntfile.js', 'public/qa/**/*.js', 'qa/**/*.js'], }, exec: { // 由于链接检查所使用到的测试工具,没办法通过npm安装使用,只能用命令行调用了 linchecker: {cmd: 'linkchecker http://127.0.0.1:3000'} } }), // 注册任务 grunt.registerTask('default', ['cafemocha', 'eslint', 'exec']) }
如此就完成
grunt
的任务配置,在命令行中,切换到项目根目录$ grunt # 开始执行任务 ,如果没有传入任务名,默认按照注册任务的顺序执行任务 $ grunt eslint # 执行eslint任务,如果代码有不符合eslint风格的话,就会报错,可以使用 --force 参数,让其强制执行
更多配置,参考文档
Gulp
gulp
和grunt
差不多$ npm install -g gulp-cli # 全局安装gulp-cli $ npm install --save-dev gulp # 安装gulp
通过
npm
搜索相关插件$ npm search gulp-mocha $ npm search gulp-eslint $ npm search gulp-exec
找到合适的插件包,就开始安装
$ npm install --save-dev gulp-mocha $ npm install --save-dev gulp-eslint $ npm install --save-dev gulp-exec
安装后,在项目根目录创建
gulpfile.js
// filename: gulpfile.js // 先将需要用到的包加载进来 var gulp = require('gulp') var mocha = require('gulp-mocha') var eslint = require('gulp-eslint') var exec = require('gulpexec') var { assert } = require('chai') gulp.task('mocha', () => gulp.src(['qa/tests-*.js', 'public/qa/tests-*.js'], { read: false }) .pipe(mocha({ reporter: 'spec', globals: { assert, }, }))) }) gulp.task('eslint', () => gulp.src(['myapp.js', 'lib/**/*.js', 'qa/tests-*.js', 'public/qa/**/*.js']) .pipe(eslint({ configFile: './.eslintrc.js', // eslint配置文件 reportOutputDir: './report' // 输出日志文件夹 })) .pipe(eslint.format('checkstyle', fs.createWriteStream('./report/1.xml'))) // 输出日志 )
grunt
和gulp
使用其中之一即可
https://www.xxx.com/home/a.html?name=xxx&age=xxx#test=1
协议名
http/https
域名www.xxx.com
路径/home/
文件名a.html
参数name=xxx&age=xxx
锚点#test=1
协议名
: //域名
(或者ip
)/路径
/文件名
?参数名
=参数值
&参数名
=参数值
#锚点
比较常见的有
GET
和POST
方法
有例如
useragent
、cookie
等
在
express
中会在响应头中显示X-Powered-By
信息,可以使用下面代码禁用app.disable('x-powered-by')
浏览器接收到来自服务端的响应,根据响应头中的互联网媒体类型(
Content-Type
)做出渲染
Content-Type
、Internet media type
和MIME type
是可以互换的
一般是text/html;charset=UTF-8
一般
GET
请求没有主题内容
POST
主体内容有三种
POST 类型 描述 1 multipart/form-data
相对更为复杂的格式,支持文件上传 2 application/x-www-form-urlencoded
键值对集合的简单编码,用 &
分隔3 application/json
AJAX请求
req
(请求对象)的生命周期始于Node的一个核心对象http.IncomingMessage
的实例。
如下是req
中最有用的属性和方法
来自
Node
的自带方法或属性req.headers
req.url
Express
添加的方法或属性
属性(方法) 描述 req.params
一个数组,包含已命名过的路由参数 req.params(name)
返回命名的路由参数,或者 GET
请求或POST
请求参数req.query
一个对象,包含以键值对存放的查询字符串参数, GET
请求参数req.body
一个对象,包含 POST
请求参数,需要使用中间件
才能解析请求中的正文信息req.route
关于当前匹配路由的信息。主要用于 路由调试
req.cookies/res.singnedCookies
一个对象,包含从客户端传递过来的 cookies
值req.headers
从客户端接收到的 请求头
req.accepts([types])
一个简便的方法,用来确定客户端是否接受一个或一组指定的类型(可选类型可以是单个的 MIME
类型,如application/json
、一个逗号分隔集合
或是一个数组
)req.ip
客户端的 IP地址
req.path
请求路径(不包含 协议
、主机
、端口
或查询字符串
)req.host
一个简便的方法,用来返回客户端所报告的 主机名
,这些信息可以伪造
,所以不应该用于安全目的req.xhr
一个渐变的属性,如果请求由 Ajax
发起,将会返回true
req.protocol
用于标识请求的协议( https
或http
)req.secure
一个简便的属性,如果连接是安全的,将放回 true
,相当于req.protocol === https
req.url/req.originalUrl
返回路径和查询字符串(不包含协议、主机名和端口); req.orginalUrl
旨在保留原始请求
和查询字符串
req.acceptedLanguages
一个简便的方法,用来返回客户端首选的一组语言(不同国家的语言, EN-US
,ZH-CN
)
res
(响应对象)的生命周期始于Node的一个核心对象http.ServerResponse
的实例。
属性(方法) 描述 res.status
设置 HTTP
状态码。默认为200
,可以使用它设置404
(页面不存在),500
(服务器内部错误),重定向 (301
、302
、303
和307
)可使用redirect
方法res.set(name, value)
设置 响应头
,通常不用手动设置res.cookie(name, value, [options])
设置客户端 cookies
值,需要中间件
支持res.clearCookie(name, value, [options])
删除客户端 cookies
值,需要中间件
支持res.rediect([status], url)
重定向浏览器,默认 302
(建立),建议少用,除非永久重定向301
(永久重定向)res.send(body)
,res.send(status, body)
向客户端发送响应以及可选状态码, Express
默认内容类型是text/html
res.json(json)
,res.json(status, json)
发送 JSON
以及可选状态码res.jsonp(jsonp)
,res.json(status, jsonp)
发送 JSONP
以及可选状态码res.type(type)
用于设置 Content-Type
信息res.format(object)
允许根据接收请求头发送不同的内容,如 res.format({'text/plain': 'hi there', 'text/html': '<b>hi there</b>'})
res.attachment([filename])
将 Content-Type
设置为attachment
,浏览器就会选择下载而不是展示内容res.download(path, [filename], [callback])
将 Content-Type
设置为attachment
,并且可以指定下载的文件res.sendFile(path, [option], [callback])
根据路径读取文件,并将内容发送到客户端 res.links(links)
设置响应头 res.locals
一个对象,包含用于渲染视图的默认上下文 res.render(view, [locals], callback)
使用配置的模板引擎渲染视图()