Node中間層實踐(二)——搭建項目框架

版權聲明:更多文章請訪問個人我的站 Keyon Y,轉載請註明出處。

項目地址:https://github.com/KeyonY/NodeMiddlejavascript

腳手架?

使用過angular2,vue2的同窗都清楚,官方推薦的安裝方式是經過專用的angular-cli、vue-cli來快速搭建一個由webpack編譯、打包的項目框架。css

受這兩個工具的啓發,我在項目之初開發一個基於webpack的項目框架:前端

  • 使用express搭載webpack-dev-middleware和webpack-hot-middleware進行熱加載
  • 內置dev(開發環境)和 production(生產環境)兩種啓動方式

技術選型

node開發框架

express和koa二選一。vue

  • express的更貼近Web Framework這一律念;
  • express功能極簡,徹底是由路由和中間件構成一個的 web 開發框架:從本質上來講,一個 Express 應用就是在調用各類中間件;
  • koa使用co做爲底層運行框架,使用它徹底忘記了何時回調函數或者callbackshell;
  • express歷史更久,裝機量大,文檔完整,第三方的中間件也不少;

我選擇了express做爲node開發框架。java

模板引擎

因爲以前用node寫過restFul的網站(詳見個人博客-NodeJS開發我的博客網站),比較熟悉jade,因此此次直接選擇了pug做爲服務器端模板引擎。
pug是一個比較靈活的服務器端模板,express配置起來也很簡單,在根目錄的app.js文件中配置node

// 設置模板引擎的類型
app.set('view engine', 'pug');
// 設置模板文件路徑
app.set('views', path.resolve(__dirname, 'src/Views'));

這樣,在express渲染時候使用 res.render('Home/index', {}),指定對應的頁面模板路徑就好了,第二個參數 是要在pug頁面使用的數據內容,json格式。jquery

中間層與後端的異步通訊

其實就是個ajax庫,因此我選擇了axios,vue2官方推薦的ajax庫,內置的axios.all(),能夠在中間層代理多個後端請求一併返回,輕鬆實現Bigpipe。webpack

模塊化

由於是網站前臺頁面,須要考慮SEO,沒法使用前端框架,因此換個思路,使用webpack+es6/AMD來實現模塊化。
因而,使用webpack做爲打包機+View層的模塊化,從前端層面實現了模塊化和組件化
es6用在Contorl層的模塊化,在node中間層實現了模塊化ios

環境配置(啓動文件)

app.js做爲項目的啓動文件,是express的配置文件,同時也是運行開發環境 / 生產環境的配置文件。git

經過node啓動時配置的命令參數,由app.js接收參數,而後運行對應的環境

咱們須要安裝cross-env這個插件

npm i --save-dev cross-env

在package.json的scripts中配置:

...
"scripts": {
    "start": "cross-env NODE_ENV=production node app.js",
    "dev": "cross-env NODE_ENV=dev supervisor -w app.js app",
    "build": "webpack --config build/webpack.prod.config.js"
  }
...

這樣,使用 process.env.NODE_ENV 就能夠獲取node啓動的環境參數。

這裏我配置了 開發環境(dev) 和 生產環境(production)

  • 開發環境: 不壓縮代碼,開啓devtool生成source-map,便於調試代碼;
  • 生產環境: 是用webpack進行打包、壓縮、混淆操做,將最終完整的代碼(應用)輸出至dist目錄中,而後再啓動node服務器,運行應用;

上述代碼中還用到了 supervisor插件,這是監聽node配置文件(app.js)的更改,並自動重啓node的。

npm i --save-dev supervisor

前端層面使用webpack-dev-middleware和webpack-hot-middleware,實現了熱加載。

這樣,從中間層到前端都實現了熱加載。

node中間層配置

在根目錄下新建了config文件夾,將一些經常使用的配置/參數、方法抽出來,寫在了config/config.js 和 config/common.js中。

例如,中間層啓動的端口號、應用的根目錄(path.resolve(__dirname,'../'))、後端服務器的ip、前端SEO的description和keywords等參數,都放在了config.js中,經過AMD規範在全部使用的文件中統一引入,修改起來也方便。

app.js

var express = require('express');
var cookieParser = require('cookie-parser');
var path = require('path');
var localOptions = require('./build/localOptions');
var config = require('./config/config');
var bodyParser = require('body-parser');
var auth = require('./middleware/auth');
var log4js = require('./config/log4js');

process.env.NODE_ENV = process.env.NODE_ENV ? process.env.NODE_ENV : 'production';
var isDev = process.env.NODE_ENV === 'dev';
var app = express();
var port = config.port;


app.set('view engine', 'pug');
// 設置模板文件路徑
if (isDev){
    app.set('views', path.resolve(__dirname, 'src/Views'));
}else {
    app.set('views', path.resolve(__dirname, 'dist/Views'));
}

// app.locals定義的鍵值對能在模板中直接訪問
app.locals.env = process.env.NODE_ENV || 'dev';
app.locals.reload = true;

app.use(cookieParser());
app.use(bodyParser.urlencoded({extended: false}));


if (isDev) {
    // app.locals.pretty = true;
    // 開發環境,靜態文件使用熱插拔
    var webpack = require('webpack');
    var webpackDevMiddleware = require('webpack-dev-middleware');
    var webpackHotMiddleware = require('webpack-hot-middleware');
    var webpackDevConfig = require('./build/webpack.dev.config.js');

    var compiler = webpack(webpackDevConfig);
    // 熱插拔
    app.use(webpackDevMiddleware(compiler, {
        publicPath: webpackDevConfig.output.publicPath,
        noInfo: true,
        stats: 'errors-only'
    }))
    app.use(webpackHotMiddleware(compiler, {
        heartbeat: 1000,
        noInfo: true,
    }));

    // 不能熱插拔的往下執行
    var reload = require('reload');
    var http = require('http');
    var server = http.createServer(app);
    // reload(server, app);
    reload(app);
    server.listen(port, () => {
        console.log('App【dev】 is now running on port ' + port + '!');
    });

    // 靜態目錄設置必須有,開發環境讀取的vendor.js不是內存文件;
    // 靜態目錄設置必須放在reload後面,避免頁面引入reload.js報錯
    app.use(express.static(path.join(config.root, 'src')));
    app.use('/', require(path.join(config.configRoot,'/routes')));
    
}else {
    // 線上環境不須要監聽,只需開啓node服務便可
    // 設置node的靜態文件目錄
    app.use(express.static(path.join(config.root, 'dist')));
    app.use('/',require(path.join(config.configRoot,'/routes')));
    // app.listen(process.env.PORT, () => {
    app.listen(port, () => {
        console.log('App【production】 is now running on port ' + port + '!');
    })
}

// 捕捉 404錯誤 傳給 error路由
app.use('*', auth, (req, res, next) => {
    let err = new Error('Not Found');
    err.status = 404;
    next(err);
});

// 捕獲 error,跳轉至error頁面
app.use((err, req, res, next) => {
    const sc = err.status || 500;
    if(err.status == 405){
        res.redirect(config.cndesignOrigin + '/Home/GuideAuthentication');
        return;
    }
    res.status(sc);
    // 此處需寫入日誌
    log4js.error('\r\n Status: '+ sc + "\r\n Message: " + err.message + "\r\n Href: " + localOptions.baseUrl + req.originalUrl + "\r\n User-agent: " + req.headers['user-agent']);

    res.render('Error/404', {
        error: err,
        status: sc,
        message: err.message,
        userInfo: req.userInfo_ || { hasLogin: false }
    });
});

注意Error Handle的掛載的順序,通常掛載到app.use()下,且放在最後。

由於Error Handle的參數爲4個,第一個參數err能夠經過路由中的next(err)拋出,這樣全部的路由均可以隨時拋出error,讓Error Handle來捕獲。

來看一下目錄結構:

App
├─ .babelrc                         // babel的配置
├─ ReadMe                           
├─ app.js                           // 啓動文件
├─ build                            // webpack的配置文件
│    ├─ entrys.js                   // webpack打包js和css的入口文件
│    ├─ localOptions.js             // 前端開發者本地配置文件,本地ip等站點配置
│    ├─ postcss.config.js           // postcss的配置文件
│    ├─ webpack.dev.config.js       // 開發環境的webpack打包配置
│    ├─ webpack.dll.config.js       // webpack.DllReferencePlugin插件的配置文件
│    └─ webpack.prod.config.js      // 生產環境的webpack打包配置
├─ config                           // Model層文件:包括node服務器的配置、路由和代理接口
│    ├─ common.js                   // 中間層邏輯處理的公用方法
│    ├─ config.js                   // node服務器的配置
│    └─ routes                      // 路由和代理接口配置
│           ├─ default.js
│           ├─ designers.js
│           ├─ home.js
│           └─ index.js             // 路由和接口配置的入口文件
├─ package.json
└─ src                              // View層文件
       ├─ Components                // 公用組件
       │    ├─ base
       │    ├─ home
       │    ├─ index
       │    ├─ message
       │    ├─ modals
       │    ├─ page
       ├─ Views                     // 頁面
       │    ├─ Default
       │    ├─ Error
       │    ├─ Home
       │    ├─ Include
       │    ├─ Messages
       │    └─ Shared
       └─ assets                    // 靜態文件目錄
              ├─ Jcrop
              ├─ es5-shim-master
              ├─ images
              ├─ jquery-1.10.2.min.js
              ├─ jquery-ui-1.8.24.min.js
              └─ layui

歡迎繼續關注本博的更新
Node中間層實踐(一)——基於NodeJS的全棧式開發
Node中間層實踐(二)——搭建項目框架
Node中間層實踐(三)——webpack配置
Node中間層實踐(四)——模板引擎pug
Node中間層實踐(五)——express-中間層的邏輯處理

相關文章
相關標籤/搜索