3

彩票项目实战学习记录(二)

自动构建环境搭建

这里的编译环境是 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

备注:

  1. 关于gulpfile.babel.js,gulp 的默认配置文件,默认gulp 的文件是用 es5编写的,但是因为要统一使用 es6,所以原来的文件名gulpfile.js要改为gulpfile.babel.js
  2. 因为使用了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实例。

参考引用:

  1. gulp官方
  2. http://www.tangshuang.net/3126.html

本项目的 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目录下的,换言之,是编译之后的文件的变化!

参考:gulp-live-server

文件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 服务器编写知识就可以了解了,所以不做详细描述。


线上猛如虎
2.2k 声望178 粉丝

你们都有梦想的,是吧.怀抱着梦想并且正朝着梦想努力的人,寻找着梦想的人,我想为这些人加油呐喊!