最近在作一个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),可是针对一些对于兼容性要求很高的多页项目,这种开发模式也是可取的。
缺点:对后端服务依赖太强,每每后端服务一旦出现报错或者挂掉后,前端的工做就没有办法开展下去了,从而加大了先后端的开发成本。