彩票项目实战学习记录(二)
自动构建环境搭建
这里的编译环境是 gulp+webpack+node+express+express-generator
- Gulp 是一个基于 nodejs stream 的流式前端构建工具,与 Grunt 功能相同,专注于前端资源的编译、检查、压缩等自动化工作。
- webpack 用来将各种文件(js,css,图片之类)进行打包的工具
- express nodejs 的 web 服务器
- express-generator nodejs 的 web 服务器的初始化工具
- 备注:不一定要使用这种方式来实现自动刷新环境搭建,这个只是其中一个举例。
node 安装不详细说了,需要注意的是,要安装 node 和 npm,他们是捆绑一起安装的。
// 建议严格按照顺序执行
// 进入项目目录后执行,初始化项目目录的 npm 环境
npm --init // 会获得一个package.json文件
//建议全局安装 gulp
npm install --global gulp
//建议全局安装express-generator 和 express
npm install -g express-generator // 要比 express 先安装
npm install -g express
//验证是否安装成功
express -V
// 初始化 expres 项目,并且 view 目录指定使用 ejs 解析引擎
express -e --view=ejs
express-generator 比 express 先安装的原因是因为如果反过来安装的话,他就没办法使用 express -e
命令
如果以上都执行成功的话,会获得这样的目录结构:
gulpfile.babel.js //gulp 的跟 babel 配合的使用文件
package.json
.babelrc // babel 的环境配置文件,需要手动创建
.gitignore // 该文件只要将该目录设置为 git 管理的话就会自动生成
app // 这是项目的源代码目录,需要自己创建
├── css
├── js
└── views
server // express 的相关文件,并且也是最终构建后的文件存放的地方
├── app.js
├── bin
├── package.json
├── public
├── routes
└── views
tasks
├── browser.js
├── build.js
├── clean.js
├── css.js
├── default.js
├── pages.js
├── scripts.js
├── server.js
└── util
└── args.js
备注:
- 关于
gulpfile.babel.js
,gulp 的默认配置文件,默认gulp 的文件是用 es5编写的,但是因为要统一使用 es6,所以原来的文件名gulpfile.js
要改为gulpfile.babel.js
。 - 因为使用了
express -e --view=ejs
,会自行生成一个 express 的目录,里面包含了作为一个 nodejs web 服务器所需的文件。
.babelrc文件
babel的配置文件,这里需要配置 presets,是为了编译 es6语法的。
{
"presets":["es2015"]
}
gulpfile.babel.js文件
这是 gulp 的默认配置文件,但由于本项目的架构改为了以 tasks 目录为中心,所以需要在默认配置文件里面配置一个引用 tasks 目录的处理,这样 gulp 执行的时候会通过默认配置文件找到 tasks 目录来执行里面的真正的脚本。
import requireDir from 'require-dir'; // node的文件夹处理模块
requireDir('./tasks'); // 引入./tasks目录
npm 需要的软件补充
项目会使用很多模块软件,这些在 npm 的库上有,需要下载并且保存为开发使用
npm install XXXX --save-dev
以下是完整的npm 软件配置信息,取自package.json
文件:
{
"name": "es6-caippiao-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.6.1",
"babel-preset-es2015": "^6.24.1",
"cookie-parser": "^1.4.3",
"del": "^3.0.0",
"express": "^4.16.2",
"express-generator": "^4.15.5",
"gulp": "^3.9.1",
"gulp-concat": "^2.6.1",
"gulp-if": "^2.0.2",
"gulp-live-server": "0.0.31",
"gulp-livereload": "^3.8.1",
"gulp-plumber": "^1.1.0",
"gulp-rename": "^1.2.2",
"gulp-sequence": "^0.4.6",
"gulp-uglify": "^3.0.0",
"gulp-util": "^3.0.8",
"jquery": "^3.2.1",
"mockjs": "^1.0.1-beta3",
"morgan": "^1.9.0",
"require-dir": "^0.3.2",
"serve-favicon": "^2.4.5",
"vinyl-named": "^1.1.0",
"webpack": "^3.8.1",
"webpack-stream": "^4.0.0",
"yargs": "^10.0.3"
}
}
gulp使用
官方入门指南 里面有写:
//1. 全局安装 gulp:
$ npm install --global gulp
//2. 作为项目的开发依赖(devDependencies)安装:
$ npm install --save-dev gulp
//3. 在项目根目录下创建一个名为 gulpfile.js 的文件:
var gulp = require('gulp');
gulp.task('default', function() {
// 将你的默认的任务代码放在这
});
//4. 运行 gulp:
$ gulp
//默认的名为 default 的任务(task)将会被运行,在这里,这个任务并未做任何事情。
//想要单独执行特定的任务(task),请输入 gulp <task> <othertask>。
gulp 的执行只是几个命令,并且 api 也只有几个-gulp-api,大致也能看懂 gulp 大概是怎么运行的了。所以这里对照着官方的 gulp 实例来分析这个项目里面的 gulp实例。
参考引用:
本项目的 gulp
本项目的 gulp 会搭配 webpack 和 babel 做打包和转译 es6语法处理。
首先说目录,本项目的 gulp 会使用 tasks目录,里面专门存放所有 gulp 的相关脚本,分得这么细,主要是按照标准项目的 gulp 脚本目录布置的。
tasks
├── browser.js // 浏览器相关
├── build.js // 构建打包相关
├── clean.js // 清理相关
├── css.js // css 相关
├── default.js // gulp 的默认脚本文件,会先从这里出发
├── pages.js // 处理页面编译的
├── scripts.js // 主脚本文件
├── server.js // nodejs server 端的脚本处理文件
└── util // 放置一些工具项
└── args.js // 这个主要处理gulp 命令行输入处理
然后再说说执行的顺序,
gulp命令 --> gulpfile.babel.js
--> tasks 目录 -->args.js
读取命令行输入的命令参数 --> default.js
执行默认任务 --> build.js
指定了相关联的其他任务 --> 扩散其他任务脚本.....
就这样通过流的方式一步步将需要处理的文件流传输过来,进行过滤处理,然后输出。
文件args.js
通过使用 yargs 这个模块来处理命令行的参数,主要是方便处理命令行参数的捕获和过滤。官方地址
import yargs from 'yargs';
const args = yargs
.option('production', {
boolean: true,
default: false,
describe: 'min all scripts'
})
.option('watch', { // watch 参数
boolean: true,
default: false,
describe: 'watch all files'
})
.option('verbose', {
boolean: true,
default: false,
describe: 'log'
})
.option('sourcemaps', {
describe: 'force the creation of sroucemaps'
})
.option('port', {
string: true,
default: 8080,
describe: 'server port'
})
.argv // 会返回对象,里面有之前配置的参数
export default args;
备注:参数写上去不用也可以的。
文件default.js
这是 gulp的默认的,首先执行的任务脚本文件,从这里出发,扩散到其他任务脚本。
import gulp from 'gulp';
gulp.task('default',['build']); // 默认任务就是执行 build 任务
文件build.js
这里就是真的 build 脚本任务文件了,这里使用了一个特殊包gulp-sequence,负责安排脚本执行的顺序的。
import gulp from 'gulp';
import gulpSequence from 'gulp-sequence';
// 用中括号的意思是任务是平行的,不存在先后
gulp.task('build',gulpSequence('clean','css','pages','scripts',['browser','serve']));
这是官网的例子:
// usage 1, recommend
// 1. run 'a', 'b' in parallel;
// 2. run 'c' after 'a' and 'b';
// 3. run 'd', 'e' in parallel after 'c';
// 3. run 'f' after 'd' and 'e'.
gulp.task('sequence-1', gulpSequence(['a', 'b'], 'c', ['d', 'e'], 'f'))
文件clean.js
每次编译都需要清空原来的文件。
import gulp from 'gulp';
import del from 'del';
// 如果在这个任务里面没有命令行参数的话,也可以不导入
import args from './util/args';
gulp.task('clean',()=>{ // 使用 es6语法做函数写法
return del(['server/public','server/views']) // 清空2个目录的文件
})
文件css.js
负责处理 css 文件编译的,这里并没有做更多的 css 编译处理,css 编译通常有 less,sass等,需要借助不同的插件来处理,不过这里只是直接输出,所以不作介绍。
import gulp from 'gulp';
// 如果在这个任务里面没有命令行参数的话,也可以不导入
import args from './util/args';
gulp.task('css',()=>{
return gulp.src('app/**/*.css') // 将源文件复制到目标目录
.pipe(gulp.dest('server/public'))
})
文件pages.js
负责处理 ejs 页面的(同理可推其他的页面也可以,例如 .vue之类)编译。
import gulp from 'gulp';
import gulpif from 'gulp-if';
import livereload from 'gulp-livereload'; // 引入自动刷新模块
import args from './util/args';
gulp.task('pages',()=>{
return gulp.src('app/**/*.ejs')
.pipe(gulp.dest('server'))
// 如果命令行有 watch 参数,则调用自动刷新
.pipe(gulpif(args.watch,livereload()))
})
需要注意的是,这里的 watch 只是监听命令参数的 watch,并不是监听文件的变化。
文件scripts.js
这个就是最主要的脚本任务文件了,在这里包含 webpack 的处理,文件压缩的处理
import gulp from 'gulp';
import gulpif from 'gulp-if';
import concat from 'gulp-concat';
import webpack from 'webpack'; // webpack插件
import gulpWebpack from 'webpack-stream'; // webpack使用时需要附带上
import named from 'vinyl-named'; // 用来保持输入和输出的文件名相同的插件
import livereload from 'gulp-livereload';
import plumber from 'gulp-plumber'; // 防止来自 gulp 插件错误导致中断操作的插件
import rename from 'gulp-rename'; // 重命名插件
import uglify from 'gulp-uglify'; // js 代码混淆压缩插件
import {log,colors} from 'gulp-util';
import args from './util/args';
gulp.task('scripts',()=>{
return gulp.src(['app/js/index.js'])
.pipe(plumber({ // 将错误通过errorHandle输出
errorHandle:function(){
// 省略
}
}))
.pipe(named()) //vinyl-named用来保持输入和输出的文件名相同, 否则会自动生成一个hash.
.pipe(gulpWebpack({ // 使用 webpack 对 js 文件通过 babel 进行转义
module:{ // 这是 webpack 的调用模块写法
loaders:[{
test:/\.js$/,
loader:'babel-loader' // 使用 babel loader
}]
}
}),null,(err,stats)=>{
log(`Finished '${colors.cyan('scripts')}'`,stats.toString({
chunks:false
}))
})
.pipe(gulp.dest('server/public/js')) // 输出处理后的 js 文件
.pipe(rename({ // 重命名,这里是对 js 文件压缩后的文件命名
basename:'cp',
extname:'.min.js'
}))
.pipe(uglify({compress:{properties:false},output:{'quote_keys':true}})) // 压缩文件
.pipe(gulp.dest('server/public/js')) // 重新输出处理后的 js 文件
.pipe(gulpif(args.watch,livereload())) // watch 监听文件并自动刷新
})
需要注意的是,这里的 watch 只是监听命令参数的 watch,并不是监听文件的变化。
文件server.js
这个server 是指 express ,是 web 的 server,这里的作用是架设一个本地虚拟 web 服务器来渲染网页文件,这样就可以构成一个本地虚拟开发环境了。
虽然文件名叫 server.js
,但是gulp 的任务叫 serve,这里需要注意,一个是文件名命名,一个是任务命名,两者不冲突,其实最好还是用一样的名字,避免混乱。
import gulp from 'gulp';
import liveserver from 'gulp-live-server';
import args from './util/args';
gulp.task('serve', (cb) => {
if (!args.watch) return cb(); // 只有 watch 参数才会触发架设 web 服务器
let server = liveserver.new(['--harmony', 'server/bin/www']);
server.start(); // 开启 web 服务器
// 这里真正使用了 gulp 的 watch 监听功能
// 先通知 web 服务器有哪些文件变动了
gulp.watch(['server/public/**/*.js', 'server/views/**/*.ejs'], function (file) {
// 会被加载到 web 的文件缓存中
server.notify.apply(server, [file]);
});
// 重新启动 web服务器,相当于重新加载所有文件,实现了自动加载
gulp.watch(['server/routes/**/*.js', 'server/app.js'], function () {
server.start.bind(server)()
});
});
需要注意的是,这里监听的是server/public
目录下的,换言之,是编译之后的文件的变化!
文件browser.js
这个名字并不是太好,这里的作用其实不是在 browser 里面的,这个文件主要就是负责监听源代码的变化,然后将变化的内容分别通知不同的任务进行处理,这是真正的实现文件监听的任务。
import gulp from 'gulp';
import args from './util/args';
gulp.task('browser',(cb)=>{
if(!args.watch) return cb();
// 监听 js 文件变化
gulp.watch('app/**/*.js',['scripts']);
// 监听 ejs 文件变化
gulp.watch('app/**/*.ejs',['pages']);
// 监听 css 文件变化
gulp.watch('app/**/*.css',['css']);
});
后台服务器数据mock
mock 的意思就是模拟,模拟后台 api 数据,真正实现前后端分离开发,不用等待后端配置好才能开始工作,只要知道双方协商好的数据结构即可。
mock 数据需要安装 express 和 mockjs 模块。
.
├── app.js // express 主程序脚本
├── bin
│ └── www // express 主程序执行文件
├── package.json
├── public
│ ├── css // 这里是构建后的css 文件
│ │ ├── index.css
│ │ ├── layout.css
│ │ ├── lottery.css
│ │ └── reset.css
│ └── js // 这里是构建后的js 文件
│ ├── cp.min.js
│ └── index.js
├── routes // 是 web 服务器的路由
│ ├── index.js // 这是主路由文件
│ └── users.js
└── views // 这里是构建后的ejs 文件
├── error.ejs
└── index.ejs
这里主要关注主路由文件
var express = require('express');
var mockjs = require('mockjs'); // 引入 mockjs
var router = express.Router(); // 启动 express 服务器
// 这是制造一些假数据的逻辑,逻辑内容可以不管
var makeIssue = function() {
var date = new Date();
// first_issue_date 每天的第一期
// end_issue_date 每天的最后一期
var first_issue_date = new Date();
first_issue_date.setHours(9);
first_issue_date.setMinutes(10);
first_issue_date.setSeconds(0);
var end_issue_date = new Date(first_issue_date.getTime() + 77 * 10 * 60 * 1000);
var cur_issue, end_time, state;
// 正常销售
if (date.getTime() - first_issue_date.getTime() > 0 && date.getTime() - end_issue_date.getTime() < 0) {
// cur_issue_date 当前期号
var cur_issue_date = new Date();
cur_issue_date.setHours(9);
cur_issue_date.setMinutes(0);
cur_issue_date.setSeconds(0);
var minus_time = date.getTime() - cur_issue_date.getTime();
var h = Math.ceil(minus_time / 1000 / 60 / 10);
var end_date = new Date(cur_issue_date.getTime() + 1000 * 60 * 10 * h);
end_time = end_date.getTime();
cur_issue = [end_date.getFullYear(), ('0' + (end_date.getMonth() + 1)).slice(-2), ('0' + end_date.getDate()).slice(-2), ('0' + h).slice(-2)].join('');
} else {
// 今天销售已截止
first_issue_date.setDate(first_issue_date.getDate() + 1);
end_time = first_issue_date.getTime();
cur_issue = [first_issue_date.getFullYear(), ('0' + (first_issue_date.getMonth() + 1)).slice(-2), ('0' + first_issue_date.getDate()).slice(-2), '01'].join('');
}
var cur_date = new Date();
if (end_time - cur_date.getTime() > 1000 * 60 * 2) {
state = '正在销售';
} else {
state = '开奖中';
}
return {
issue: cur_issue,
state: state,
end_time: end_time
};
};
// 主页首页的逻辑
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', {
title: 'Express'
});
});
// 真正的 mock 模拟数据的接口编写
// get opencode
router.get('/get/opencode', function(req, res, next) {
var issue = makeIssue().issue; // 使用刚才制造的一些假数据
var data = mockjs.mock({
'data': [/[1-3]/, /[4-5]/, /[6-7]/, /[8-9]/, /1[0-1]/]
}).data;
res.json({
issue: issue,
data: data
});
});
// 省略部分路由
module.exports = router;
总的来说跟一般的 nodejs 的 web 服务器差不多,稍微补充一部分 nodejs 的 web 服务器编写知识就可以了解了,所以不做详细描述。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。