版權聲明:更多文章請訪問個人我的站 Keyon Y,轉載請註明出處。
項目地址:https://github.com/KeyonY/NodeMiddlejavascript
使用過angular2,vue2的同窗都清楚,官方推薦的安裝方式是經過專用的angular-cli、vue-cli來快速搭建一個由webpack編譯、打包的項目框架。css
受這兩個工具的啓發,我在項目之初開發一個基於webpack的項目框架:前端
express和koa二選一。vue
我選擇了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)
上述代碼中還用到了 supervisor插件,這是監聽node配置文件(app.js)的更改,並自動重啓node的。
npm i --save-dev supervisor
前端層面使用webpack-dev-middleware和webpack-hot-middleware,實現了熱加載。
這樣,從中間層到前端都實現了熱加載。
在根目錄下新建了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-中間層的邏輯處理