preface

Recently, I am working on a PC terminal project. As the project needs to be compatible with IE8, the company adopts the gulp based front-end and freemarker based back-end model in terms of technology selection for development.

So what does the gulp+ Freemarker process look like? I’m just going to do a little bit of analysis here.

First, the front end is based on GULP

Front-end technology stack:

  • gulp
  • jquery
  • ajax
  • less…

Front-end project structure:

├─ SRC source directory │ ├─ Common ├─ less Public Style ├─ JS Public JS ├─ img Images │ ├─ JS js │ ├─ SRC source directory │ ├─ Common ├─ less Public Style ├─ JS Public JS ├─ img Images │ ├─ JS js │ ├─ less Style ├─.esLintrc.js EsLint Rule Config ├─ Package. json Project file ├─ gulpfile.js Config file ├─ Server.js Local ServiceCopy the code

Gulpfile.js and server.js

gulpfile.js

Those familiar with GULP know that we usually call the whole project in two environments, namely development environment and production environment

Configuration of the development environment:

Var gulp = require("gulp"), less = require("gulp-less"), clean = require("gulp-clean"), header = require("gulp-header");  /** * less compile * @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')); }); 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 output * @return {[type]} [description] * development environment called * / gulp. Task (" imgOutput, "[]" cleanImg ", function () {gulp. SRC ('/SRC/img / * * *. * '). The pipe (gulp. Dest (" dist/img ")})Copy the code

A brief analysis of the above code:

In the development environment, we need to compile less, js, img under our project SRC and public less, js, img under common, so we need to use gulp.task() method to create a compile task. Once the task is created, we need to point to the file we need to compile via gulp.src()

Finally, we use gulp.pipe() to create one pipe after another, for example

gulp.pipe(plumber({errorHandler: errorHandler}))

Function errorHandler(e) {function errorHandler(e) {function errorHandler(e) { gutil.log(e); }Copy the code

The console printed an error message during compilation.

gulp.pipe(addHeader())

*/ var addHeader = function() {return header(banner, {PKG: config, moment: moment}); };Copy the code

After compiling, add the compile time to the header of the file

gulp.pipe(gulp.dest(‘dist/js’))

Output the compiled file to the dist directory

Production environment configuration:

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")); }); 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")) })Copy the code

The configuration of the production environment is similar to the configuration of the development environment. The difference is that in the production environment, we need to use gulp-CSsmin and gulp-Uglify to compress the CSS and JS and reduce the size of the file.

Cleancss and CleanJS are used to clean up the CSS and JS generated by the package every time we compile it, so that the package code is up to date every time we compile it.

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());
});
Copy the code

Development Environment Listening

gulp.task("watch", function() { livereload.listen(); Gulp. Watch ([' SRC/LESS /*.less',' SRC /common/ *.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"))}) Automatically refresh the page gulp. Watch ([' dist / * * / *. CSS, 'dist / / *. * * js, ftlPath]), on (' change' livereload. Changed); });Copy the code

During the development of the project, we need to use gulp.watch() to monitor the code changes in the project in real time, and use the gulp-livereload plug-in to refresh our page in real time, so as to improve our development efficiency.

After gulpfile.js, let’s look at 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. The name, the port = process. The env. The port | | '9091' / / GZIP compression app. Use (compress ()); 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)})Copy the code

Here we use the Express framework in Node to build local services for us, and here we focus on static resource project access

Two arguments are passed through the app.use() method, where projectName represents the projectName we defined in package.json, as 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" }Copy the code

. The second parameter to express the static (‘ dist) means to our service agent to compile dist files after packaging, such as: http://192.168.128.68:9091/phip_ehr/js/ * *. Js

This way we can easily get all the statics for the entire project.

Second, the backend is based on freemarker

Back-end technology stack:

  • java
  • freemarker …

Here the back-end project structure I only intercept with our front-end related directory to illustrate

Back-end project structure:

├─ Templates │ ├─ Home ├─ Layouts Page Layout ├─ Views Business Code (FTL) ├─ WidgetsCopy the code

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>Copy the code

The code above is the layout structure of the entire project page

${widget(“headInner”,page.bodyAttributes)}

This method means introducing some of our front-end static common styles and some common meta tags.

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"/>Copy the code

${staticServer} specifies the domain name to be used in the configuration file config of the back-end project

* *. The mursi. AttributesMap. StaticServer = http://192.168.128.68:9091 * *. The mursi. AttributesMap. ImgServer = http://192.168.128.68:9091Copy the code

Now that we’ve introduced our common styles, how do we introduce business styles next?

<#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>Copy the code

This code is used to introduce our business style, which means using the style property in the page object encapsulated by the backend framework, and then iterating through the style tags on all pages

Then in our business code (FTL) we will be able to introduce our business style through the addStyle method

${page.addStyle("audit.css")}
Copy the code

${widget(“header”,page.bodyAttributes)}

This method means introducing a common header in our page

${placeholder}

This means introducing the main content section of our page

${widget(“footerJs”)}

This means importing js files into our page

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> <! <script type="text/javascript" SRC ="${staticServer}/phip_ehr/js/ gl.js? _ = ${version} "> < / script > < script SRC =" http://127.0.0.1:35729/livereload.js "> < / script >Copy the code

In this code, GC refers to initializing some variables, GC refers to initializing some variables, and GS refers to introducing the public JS that we rely on in the project, such as jquery, common.js, etc. The second is to load our business JS through the module loader gl.js

This allows us to use data-Moduls in our business FTL to introduce business JS in each page

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>
Copy the code

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']]);Copy the code

The dispatcher is the equivalent of a dispatcher for pages. Of course, each page can only have a separate dispatcher. The run() method is the public call method that we encapsulate in gh.js

So how do we introduce some common plug-ins into our project?

So we’ve encapsulated the gh.modules () method in gh.js to introduce plugins, which we won’t go into here.

By the way, WHAT is FTL? FTL is similar to our HTML, but different in that it is a back-end template engine written with Freemarker syntax. Some of the synchronously loaded data in our project can be manipulated directly in FTL using Freemarker’s syntax

Summary:

What are the advantages and disadvantages of this model?

Advantages: Although it is not as efficient as the pure front and back end separation mode (VUE + Webpack, React + Webpack), it is also desirable for some multi-page projects that require high compatibility.

Disadvantages: It is too dependent on the back-end service. Once the back-end service fails or fails, the front-end work cannot be carried out, thus increasing the development cost of the front-end and back-end services.