es6+express+ejs+webpack+sass+mongoDB 部署

项目地址:es6+express+ejs+webpack+sass+mongoDB

使用

npm i //安装
npm start //开启服务
npm run dev  //开发模式
npm run bulid //发布

项目目录

--root
    |--config                //配置文件
    |--middlewares            //中间件
    |--pubulic                //静态资源
     |--js
     |--css/sass
     |--images
    |--router                //路由控制器
    |--tools                //工具
     |--lib
     |--webpack.config.js
    |--views                //视图文件
     |--components
    |--package.json
    |--...

es6预设置

使用babel对es6进行转换:

下载

 "devDependencies": {
"babel": "6.23.0",
"babel-cli": "6.23.0",
"babel-core": "6.23.1",
"babel-loader": "6.4.0",//webpack的babel-loader
"babel-plugin-transform-es2015-modules-commonjs": "6.23.0",//允许使用es6的modules
"babel-preset-env": "1.2.1",
"babel-preset-react": "6.23.0",//支持jsx转换
"babel-preset-stage-0": "6.22.0"
}

配置可以不写在.babelrc里,直接写在package.json

 "babel": {
"presets": [
  "react",
  "stage-0"
],
"plugins": [
  "transform-es2015-modules-commonjs"
]
}

npm script直接运行babel-node

"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "babel-node tools/start",//表示执行tools文件夹的start文件,执行之前进行es6转码
"dev": "supervisor -w tools,router,middlewares,config,views -- -r babel-register tools/dev"
}

es6 - 异步操作 async/await 语法

async函数允许你像同步写法那样去写异步操作,async函数里面关键字await后面接一个Promise,此时函数执行会在await后的Promise执行完成后才会往下执行。

举个例子,创建服务器是异步操作,我们需要在服务器创建完成后才布置路由。首先创建服务器,并把它包在Promise里面:

let runServer = (app)=>{
    return new Promise(resolve=>{
        var server = app.listen('3000',()=>{
            var host = server.address().address;
              var port = server.address().port;
            console.log('server running at http://%s:%s', host, port);
            resolve();
        })
    }) 
} 

然后在主体事件中,写异步操作:

 let appPro = async ()=>{
    let app = express();
    //等待启动服务 
    await runServer(app);
    //启动路由
    router(app);
}

此时,路由 router() 会在服务器创建完成后执行。

express 将session 存入Redis

import session from 'express-session';
import connectRedis from 'connect-redis';

let sessionCfg = {
    secret:'huangxinzheng',
    resave:true,//即使 session 没有被修改,也保存 session 值,默认为 true(!不添加报错,下同)
    saveUninitialized: false,// 设置为 false,强制创建一个 session,即使用户未登录
    cookie: {
        maxAge: 1000*60*60*24// 过期时间,过期后 cookie 中的 session id 自动删除
    }
}

 //设置session,将session存在 Redis 中
var RedisStore = connectRedis(session);
let rs = new RedisStore();
app.use(session(extend(true,{},serverCfg,rs)));

express 中间件的使用

express的中间件很重要,甚至可以说express就是通过中间来实现应用的。express有很多开源中间件可以给我们下载使用(比如express-session)。把中间件放进app.use()里就代表每次服务器请求都会经过这个中间件(假设中间件没有停止下一个的情况下)。我们也可以自己写中间件,其实中间件就是一个函数,这个函数有reqresnext 三个参数,分别代表请求对象,响应对象,和next方法。执行next方法可过渡当前中间件。

写一个记录访问次数的中间件,配合session记录一天内用户的访问次数:

let showReqTimes = (req,res,next) =>{
if(!req.session.reqTimes){
    req.session.reqTimes =1;
    res.send('你第1次访问了本网站');
}else{
    req.session.reqTimes++;
    res.send('<p>你第'+req.session.reqTimes+'次访问了本网站</p>');
}

next();//执行下一个中间件
}

export default showReqTimes

假设是记录在访问’/‘的时候:

import showReqTimes  from './showReqTimes.js'
app.get('/', showReqTimes, (req, res) => {//此时每次访问根目录之后都会进入showReqTimes这个中间件
    //do something..
})

webpack新版本的写法变化-Loader

webpack2.2较上一代在语法方面有了变化,特别是loader:

原先写法:
module: {
    loaders: [{
        test: /\.css$/,
        loader: 'style!css'
    }]
}
现在写法:
 module: {
    rules: [{
        test: /\.css$/,
        use: [ 'style-loader', 'css-loader']
    }]
}

loaders改为了rules,loader改为了use。语义上更加容易理解了,“对不同的文件类型使用不同的规则”。webpack 虽好,但是配置的时候很麻烦,更新了新版本之后,写法不习惯,报错也是莫名其妙。开发的要一边看官方文档,花了很多时间,看来要多花点时间去适应。

问题&解决

1. supervisor 监听 es6 文件

supervisor 默认监听不经过处理的js文件,所如果是直接监听es6语法的js文件,那么会出现语法错。

所以,采用babel-register对es6文件进行预转换,并且设置supervisor

supervisor -w tools,router -- -r babel-register tools/start

supervisor的官方文档可参见 : https://www.npmjs.com/package/supervisor

2. ejs模板引擎不同页面引用不同样式文件

ejs是款简单好用的模板引擎,但是网上很多人说ejs在不同的页面引用不同的文件非常不方便,有人给出在res.locals给模板传参的方法,把页面的引用样式文件从后台传给模板引擎进行解析。个人感觉但是这种方法不妥,管理起来不方便,样式文件就应该写在视图页面,不应该在路由里面写。

其实可以通过ejs的include解决这个问题。

ejs2.0+的include函数已经支持传参功能。利用这个特性我们可以简单实现ejs模板引擎不同页面引用不同样式文件了。具体实现:

在首页的视图中往header里面穿参数:

<%- include('layouts/header',{styles:['index','jquery']}) %>

然后在header的视图中修改引用样式:

 <% for(var i=0; i<styles.length; i++) {%>
   <link rel="stylesheet" href="/css/<%= styles[i] %>.css">
<% } %>

3. server.address().address 获取到IPv6格式的主机地址”::”?

以为如果计算机默认启用了IPv6,那么 server.address().address 获取到的就是IPv6的地址。如下:

var express      = require('express');
var app          = express();
var server = app.listen(3000, function () {
    var host = server.address().address;
    var port = server.address().port;
    console.log('running at http://' + host + ':' + port)
});

res:'server running at http://:::3000'    

如果想得到IPv4的主机地址,请显式得给listen传递主机名:

var express      = require('express');
var app          = express();
var server = app.listen(3000,'localhost', function () {
    var host = server.address().address;
    var port = server.address().port;
    console.log('running at http://' + host + ':' + port)
});

res:'server running at http://127.0.0.1:3000'

4. browser-sync 与 webpack-dev-middleware 的配合?

const bundler = webpack(webpackCfg);
const wpdm = webpackDevMiddleware(bundler,{
    publicPath: webpackCfg.output.publicPath
});

let handleBundleComplete = async () => {
    if(!app){
        await appPro().then(exApp=>{app = exApp});
        //app.use(wpdm)
        //启动 browser-sync
        bs = browserSync.create();
        bs.init({
          files: path.resolve(__dirname, '../public/*/**'),//监听目录
          proxy: {
            target: serverCfg.host+':'+serverCfg.port,
            middleware:[wpdm]
          },
          open: false,
          reloadOnRestart: true
        },resolve);
    }else{
        bs.reload();
    }

};
bundler.plugin('done', () => handleBundleComplete());

具体流程:

  • 1.先webpack生成bundler(此阶段并不执行生成)
  • 2.用webpack-dev-middleware把bundler包成中间件wpdm
  • 3.第一步生成bundler成功后启动服务器
  • 4.启动服务器成功后创建并初始化browser-sync
  • 5.第二步生成的中间件wpdm给到browser-sync中间件设置
  • 6.每次触发browser-sync的时候都执行中间件wpdm
  • 7.第一次之后的中间件wpdm执行都不用再启动服务器和创建browser-sync,只是执行browser-sync的reload事件