最近在作一個PC端的項目,因爲項目須要兼容到IE8,因此從技術選型上採起了公司以前一直沿用的前端基於gulp後端基於freemarker的模式來進行開發。javascript
那麼gulp+freemarker這種開發模式的流程究竟是怎樣的呢?我這邊就來簡單的分析一下。css
前端技術棧:html
前端項目結構:前端
├── README.md 項目介紹
├── src 源碼目錄
│ ├── common
├── less 公共樣式
├── js 公共js
├── plugins 插件
項目公共文件
│ ├── img 圖片
│ ├── js js
│ ├── less 樣式
├── .eslintrc.js eslint規則配置
├── package.json 工程文件
├── gulpfile.js 配置文件
├── server.js 本地服務
複製代碼
從目錄來看,很是簡單,我這邊就主要來分析一下gulpfile.js和server.jsvue
熟悉gulp的同窗都知道,通常咱們會將整個項目兩種環境來調用,即開發環境和生產環境java
開發環境的配置:node
var gulp = require("gulp"),
less = require("gulp-less"),
clean = require("gulp-clean"),
header = require("gulp-header");
/**
* less 編譯
* @return {[type]} [description]
* 開發環境調用
*/
gulp.task("less", ["cleanCss"], function() {
gulp.src(['src/less/*.less','src/common/less/*.less'])
.pipe(plumber({
errorHandler: errorHandler
}))
.pipe(less())
.pipe(addHeader())
.pipe(gulp.dest('dist/css'));
});
/**
* js 編譯
* @return {[type]} [description]
* 開發環境調用
*/
gulp.task('js', ['cleanJs'], function() {
gulp.src(['src/js/*.js', 'src/common/js/*.js'])
.pipe(plumber({
errorHandler: errorHandler
}))
.pipe(addHeader())
.pipe(gulp.dest('dist/js'));
gulp.src('src/common/plugins/*.js')
.pipe(gulp.dest("dist/js/plugins"))
})
/**
* img 輸出
* @return {[type]} [description]
* 開發環境調用
*/
gulp.task("imgOutput", ["cleanImg"], function(){
gulp.src('src/img/**/*.*')
.pipe(gulp.dest("dist/img"))
})
複製代碼
簡析上述代碼:react
在開發環境中咱們須要對咱們項目的src下的業務less、js、img和common下的公共less、js、img進行編譯打包,那麼咱們就須要藉助gulp.task()這個方法來創建一個編譯任務。建立完任務之後,咱們就須要經過gulp.src()來指向咱們須要編譯的文件jquery
最後咱們再經過gulp.pipe()來建立一個又一個咱們須要的管道,如webpack
gulp.pipe(plumber({errorHandler: errorHandler}))
function errorHandler(e) {
// 控制檯發聲,錯誤時beep一下
gutil.beep();
gutil.log(e);
}
複製代碼
編譯的時候控制檯打印錯誤信息。
gulp.pipe(addHeader())
/**
* 在文件頭部添加時間戳等信息
*/
var addHeader = function() {
return header(banner, {
pkg: config,
moment: moment
});
};
複製代碼
編譯之後在文件的頭部加上編譯時間
gulp.pipe(gulp.dest('dist/js'))
將編譯後的文件輸出到dist目錄下
生產環境的配置:
var gulp = require("gulp"),
less = require("gulp-cssmin"),
clean = require("gulp-uglify");
header = require("gulp-header")
/**
* css build
* @return {[type]} [description]
* 正式環境調用
*/
gulp.task("cssmin", ["cleanCss"], function() {
gulp.src('src/common/less/all.base.less')
.pipe(less())
.pipe(cssmin())
.pipe(rename({
suffix: '.min'
}))
.pipe(addHeader())
.pipe(gulp.dest("dist/css"));
gulp.src('src/less/*.less')
.pipe(less())
.pipe(cssmin())
.pipe(addHeader())
.pipe(gulp.dest("dist/css"));
});
/**
* js 編譯
* @return {[type]} [description]
* 正式環境調用
*/
gulp.task('jsmin', ['cleanJs'], function() {
gulp.src(['src/js/**/*.js', 'src/common/js/**/*.js'])
.pipe(plumber({
errorHandler: errorHandler
}))
.pipe(uglify())
.pipe(addHeader())
.pipe(gulp.dest('dist/js'));
gulp.src('src/common/plugins/**/*.js')
.pipe(uglify({
mangle: true
}))
.pipe(addHeader())
.pipe(gulp.dest("dist/js/plugins"))
})
複製代碼
關於生產環境的配置其實跟上述的開發環境配置原理差很少,區別將在於生產環境中咱們須要藉助gulp-cssmin和gulp-uglify將css和js都進行壓縮,縮小文件的體積。
這裏提一下cleancss和cleanjs的意思,其實就是在咱們每一次編譯打包的時候將原來已經打包生成css和js都清理調,這樣保證咱們每次編譯打包的代碼都是最新的。
gulp.task("cleanCss", function() {
return gulp.src('dist/css', {
read: false
}).pipe(clean());
});
gulp.task("cleanJs", function() {
return gulp.src('dist/js', {
read: false
}).pipe(clean());
});
gulp.task("cleanImg", function() {
return gulp.src('dist/img', {
read: false
}).pipe(clean());
});
複製代碼
開發環境監聽
gulp.task("watch", function() {
livereload.listen();
// 調用gulp-watch插件實現編譯有改動的LESS文件
gulp.watch(['src/less/*.less','src/common/less/*.less'], function(file) {
gulp.src(file.path)
.pipe(plumber({
errorHandler: errorHandler
}))
.pipe(less())
.pipe(addHeader())
.pipe(gulp.dest('dist/css'));
});
gulp.watch(['src/js/**/*.js', 'src/common/js/**/*.js'], function(file) {
gulp.src(file.path)
.pipe(gulp.dest("dist/js"))
});
// 監聽圖片改動
gulp.watch('src/img/**/*.*', function(file){
gulp.src(file.path)
.pipe(gulp.dest("dist/img"))
})
// 監聽有變化的css,js,ftl文件,自動刷新頁面
gulp.watch(['dist/**/*.css', 'dist/**/*.js', ftlPath]).on('change', livereload.changed);
});
複製代碼
在開發項目的時候咱們須要藉助gulp.watch()來實時的監聽項目中代碼的改動,而且經過gulp-livereload這個插件來實時的刷新咱們的頁面,以提升咱們的開發效率。
說完gulpfile.js後咱們再來分析一下server.js
server.js
const path = require('path'),
express = require('express'),
proxy = require("express-http-proxy"),
compress = require('compression'),
app = express(),
fs = require('fs'),
config = require('./package.json'),
projectName = config.name,
port = process.env.PORT || '9091'
// GZIP壓縮
app.use(compress());
// 設置響應頭
app.use(function(req, res, next) {
res.header('X-Powered-By', 'Express');
res.header('Access-Control-Allow-Origin', '*');
next();
})
// 當前靜態項目的資源訪問
app.use('/' + projectName, express.static('dist'));
app.use('/html', express.static('src/pages'));
// 靜態服務器監聽
app.listen(port, '0.0.0.0', function() {
console.log('static server running at ' + port)
})
複製代碼
這裏咱們經過node中express框架來爲咱們搭建本地服務,這裏重點提一下靜態資源項目的訪問
經過app.use()方法傳入兩個參數,其中projectName表明的是咱們在package.json中定義項目名稱,以下phip_ehr
{
"name": "phip_ehr",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "gulp && gulp watch",
"build": "NODE_ENV=production gulp build",
"server": "node server.js"
}
複製代碼
第二個參數express.static('dist')意思是將咱們的服務代理到編譯打包後的dist文件下如:http://192.168.128.68:9091/phip_ehr/js/**.js
這樣以來咱們將能夠輕鬆的獲取到整個項目下的全部靜態了。
後端端技術棧:
這裏後端的項目結構我這邊只截取跟咱們前端相關的目錄來講明
後端端項目結構:
├── templates 項目模版
│ ├── home
├── layouts 頁面佈局
├── views 業務代碼(ftl)
├── widgets 項目依賴
複製代碼
layouts
default.ftl
<!DOCTYPE HTML>
<html>
<head>
<link rel="dns-prefetch" href="${staticServer}">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
${widget("headInner",page.bodyAttributes)}
<#list page.styles as style>
<#if (style?index_of('http') > -1) >
<link href="${style}?v=${version}" rel="stylesheet" type="text/css" />
<#else>
<link href="${staticServer}/phip_ehr/css/${style}?v=${version}" rel="stylesheet" type="text/css" />
</#if>
</#list>
</head>
<body>
${widget("header",page.bodyAttributes)}
<div id='gc'>
${placeholder}
</div>
${widget("footerJs")}
</body>
</html>
複製代碼
上述代碼是整個項目頁面的佈局結構
${widget("headInner",page.bodyAttributes)}
這個方法意思是引入一些咱們前端靜態的公共樣式和一些公共的meta標籤。
headInner.ftl
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta property="wb:webmaster" content="3b0138a4c935e0f6" />
<meta property="qc:admins" content="341606771467510176375" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<link rel="stylesheet" href="${staticServer}/phip_ehr/css/reset.css?v=${version}" type="text/css"/>
<link rel="stylesheet" href="${staticServer}/phip_ehr/css/common.css?v=${version}" type="text/css"/>
複製代碼
這裏提一下${staticServer}這個固然指的就是咱們靜態域名了,須要在後端項目的配置文件config中來聲明,即指向咱們前端的靜態服務
**.mursi.attributesMap.staticServer=http://192.168.128.68:9091
**.mursi.attributesMap.imgServer=http://192.168.128.68:9091
複製代碼
引入完咱們的公共樣式之後,那接下來咱們業務樣式怎麼引入呢?
<#list page.styles as style>
<#if (style?index_of('http') > -1) >
<link href="${style}?v=${version}" rel="stylesheet" type="text/css" />
<#else>
<link href="${staticServer}/phip_ehr/css/${style}?v=${version}" rel="stylesheet" type="text/css" />
</#if>
</#list>
複製代碼
這段代碼就是用來引入咱們的業務樣式的,意思是利用後端框架封裝的page這個對象中style屬性,而後對全部頁面的style標籤進行遍歷
而後在咱們業務代碼(ftl)中將能夠經過addstyle這個方法來引入咱們的業務樣式了
${page.addStyle("audit.css")}
複製代碼
${widget("header",page.bodyAttributes)}
這個方法的意思是引入咱們頁面中公共的頭部
${placeholder}
這個意思是引入咱們頁面主體內容部分
${widget("footerJs")}
這個意思是引入咱們頁面中js文件
footerJS.ftl
<script type="text/javascript">
$GC = {
debug: ${isDev!"false"},
isLogined : ${isLogin!"false"},
staticServer : '${staticServer}',
imageServer : '${imageServer}',
kanoServer : '${kanoServer}',
version:"${version}",
jspath:"${staticServer}" + "/phip_ehr/js"
};
// $GS { Array } - the init parameters for startup
$GS = [$GC.jspath + "/plugins/jquery-1.8.1.min.js",
$GC.jspath + "/GH.js?_=${version}",
$GC.jspath + '/plugins/validator.js',function(){
// load common module
GL.load([GH.adaptModule("common")]);
// load the modules defined in page
var moduleName = $("#g-cfg").data("module");
if(moduleName){
var module = GH.modules[moduleName];
if(!module) {
module = GH.adaptModule(moduleName);
}
if(module) {
GL.load([module]);
}
}
}];
</script>
<!-- 引入js模塊加載器 -->
<script type="text/javascript" src="${staticServer}/phip_ehr/js/GL.js?_=${version}" ></script>
<script src="http://127.0.0.1:35729/livereload.js"></script>
複製代碼
這段代碼中$GC就指的是初始化一些變量,而後$GS中就是引入咱們項目中依賴的公共js,如jquery、common.js等。其次是經過GL.js這個模塊加載器來加載咱們的業務js
這樣咱們就能夠在咱們的業務ftl中經過data-moduls來引入每一個頁面中的業務js了
a.ftl
<div class="g-container gp-user-info J_UserInfo" id="g-cfg" data-module="a" data-fo-appcode="1" data-header-fixed="1" data-page="infirmary"></div>
複製代碼
a.js
GH.run(function() {
GH.dispatcher('.J_Home', function() {
return {
init: function() {
this.switchPatient();
},
/**
*
* 切換就診人檔案
*/
switchPatient: function() {
console.log(1);
}
}
})
}, [GH.modules['validator'],GH.modules['datepicker']]);
複製代碼
dispatcher就是至關於頁面的分發器,固然每一個頁面只能擁有一個獨立的分發器,run()方法就是咱們封裝在GH.js的公共調用方法
那麼咱們項目中引入 一些公用的插件要怎麼引入呢?
那麼咱們就在GH.js裏封裝了GH.modules()方法來引入插件,這裏就不詳細的說明了。
這裏順帶也提一下ftl,什麼是ftl?ftl就是相似於咱們的html同樣,可是它不一樣的地方就是它是基於freemarker語法來進行編寫的一種後端模版引擎。咱們項目中一些能夠同步加載的數據均可以利用freemarker的語法在ftl中直接進行操做
這種先後端不分離的模式有哪些優缺點呢?
優勢:雖然在開發效率上比不上純先後端分離的模式(vue+webpack,react+webpack),可是針對一些對於兼容性要求很高的多頁項目,這種開發模式也是可取的。
缺點:對後端服務依賴太強,每每後端服務一旦出現報錯或者掛掉後,前端的工做就沒有辦法開展下去了,從而加大了先後端的開發成本。