基于gulp的前端自动化构建实例
2018-05-20 本文已影响35人
我的宠物

一、解决的问题
刚入职公司时,公司有一个基于thinkphp框架的网站项目。由于没有任何自动化构建,开发时存在如下痛点:
- 无法使用es6
- 无法使用scss
- 开发时无法动态刷新网页(livereload)
- 同有js/css的压缩功能
- 前端资源上传到cdn上,没有根据文件内容自动生成新的hash后缀,每次更新前端代码,都需要手动强制刷新CDN缓存。
本实例完成后,以上痛点都已解决。
二、gulp还是webpack?
gulp是基于流的自动化构建工作,具有易于使用、构建快速、插件高质、易于学习的优点。虽然当前webpack已经是spa页面开发的必备编译打包工具,但是如果是针对服务端渲染的前端资源构建,gulp使用起来也十分方便,并且可编程性更强,更能满足多样的前端构建需求。本项目是基于gulp的一系列插件来实现。
三、用到的gulp插件
插件名称 | 作用 |
---|---|
gulp-sass | 编译scss |
gulp-cssnano | 压缩css |
postcss-sprites | 生成精灵图片 |
gulp-postcss | 编译post-css |
gulp-babel | 编译es6 |
gulp-uglyfly | 压缩js |
gulp-server-livereload | 静态资源代码和页面自动刷新 |
gulp-mem | 将前端资源编译到内存中,而非硬盘上 |
gulp-sequence | gulp任务序列 |
gulp-watch | gulp监听文件改变执行相应任务 |
gulp-clean | 删除文件、文件夹 |
gulp-rev | 生成js、css对应的manifest.json |
gulp-rev-collector | 根据manifest.json在html页面中生成对应的js、css HASH名后缀 |
gulp-shell | 执行shell命令 |
四、项目代码结构
-
gulp脚本代码结构:
gulp-code-structure.png
文件名 | 内容 |
---|---|
gulpfile.js | gulp插件入口文件,放在项目根目录下 |
backend.js | 定义后端服务器 |
gulp/basic.js | 较低层级的子任务 |
gulp/task.js | 较高层级的gulp任务(由gulp/basic.js中的较低层级的子任务组合而成) |
-
前端资源代码结构:
前端资源文件
五、具体代码
gulpfile.js
//gulpfile.js入口文件,主要定义一些配置选项,和定义gulp默认任务
let gulp = require('gulp');
let shell = require('gulp-shell');
let requireDir = require("require-dir");
let GulpMem = require("gulp-mem");
const gulpMem = new GulpMem();
gulpMem.enableLog = false;
gulp.opts = {
srcDir: 'Public/',
staticDir: '../static/www/',
gulpMem: gulpMem,
};
requireDir("./gulp");
gulp.task("default",function(){
gulp.start("dev");
});
backend.js
let ip;
function getIp(){
if(ip){
return ip;
}
let interfaces = require('os').networkInterfaces();
for (let devName in interfaces) {
let iface = interfaces[devName];
for (let i = 0; i < iface.length; i++) {
let alias = iface[i];
if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
localIp = alias.address;
return alias.address;
}
}
}
}
function backend() {
let ip = getIp();
let server;
switch (ip){
case "192.168.9.28":
//johney IP
server = `http://${ip}:7010`;
break;
//如果是其它人的IP, 请自己添加case条件
default:
server = "http://localhost:7010";
break;
}
return server;
}
module.exports = backend;
gulp/basic.js
let gulp = require("gulp");
let uglyfly = require("gulp-uglyfly");
let gulpSass = require('gulp-sass');
let cssnano = require('gulp-cssnano');
let rev = require('gulp-rev');
let cleanFile = require('gulp-clean');
let revCollector = require('gulp-rev-collector');
let gulpSequence = require('gulp-sequence');
let rename = require('gulp-rename');
let sprites = require('postcss-sprites');
let gulpFilter = require("gulp-filter");
let postcss = require('gulp-postcss');
let babel = require("gulp-babel");
let gulpif = require("gulp-if");
let gutil = require("gulp-util");
let gulpMem = gulp.opts.gulpMem;
gulp.task("clean1",function(cb){
return gulp.src([
gulp.opts.srcDir + 'css/',
gulp.opts.srcDir + 'js/',
gulp.opts.srcDir + 'images.sprite/',
])
.pipe(cleanFile({force:true}));
});
gulp.task("clean2",function(cb){
return gulp.src(gulp.opts.staticDir, {read:false})
.pipe(cleanFile({force:true}));
});
function isJplayer(file){
//判断是js且并非jplayer.js
return /jquery\.jplayer\./.test(file.history[0]);
}
gulp.task("js-dev", function (cb) {
return gulp.src(gulp.opts.srcDir + 'es/**/*.js',{base:gulp.opts.srcDir + 'es'})
//.pipe(gulpif(isJplayer, gutil.noop(), gutil.noop()))
.pipe(babel({
"presets": ["es2015"]
}))
.pipe(gulpMem.dest(gulp.opts.srcDir + 'js'));
});
gulp.task("js1", function (cb) {
return gulp.src(gulp.opts.srcDir + 'es/**/*.js',{base:gulp.opts.srcDir + 'es'})
//.pipe(gulpif(isJplayer, gutil.noop(), gutil.noop()))
.pipe(babel({
"presets": ["es2015"]
}))
.pipe(gulp.dest(gulp.opts.srcDir + 'js'));
});
gulp.task("js2",["js1"],function(cb){
return gulp.src(gulp.opts.srcDir + 'js/**/*.js',{base:gulp.opts.srcDir})
.pipe(uglyfly()) //js压缩
.pipe(rev())
.pipe(gulp.dest(gulp.opts.staticDir))
.pipe(rev.manifest())
.pipe(gulp.dest(gulp.opts.staticDir + 'revmanifest/js/'))
});
gulp.task("sprite_scss-dev", function (cb) {
return gulp.src([gulp.opts.srcDir + 'scss/**/*.sprite.scss'])
.pipe(gulpSass().on('error', gulpSass.logError))
.pipe( postcss([ sprites({
spritePath: './Public/images.sprite/',
groupBy: function (image) {
var url = image.styleFilePath.split('/').pop().split('.');
return Promise.resolve(url[0]);
}
}) ]))
.pipe(gulpMem.dest(gulp.opts.srcDir + 'css/'));
});
gulp.task("sprite_scss1", function (cb) {
return gulp.src([gulp.opts.srcDir + 'scss/**/*.sprite.scss'])
.pipe(gulpSass().on('error', gulpSass.logError))
.pipe( postcss([ sprites({
spritePath: './Public/images.sprite/',
groupBy: function (image) {
var url = image.styleFilePath.split('/').pop().split('.');
return Promise.resolve(url[0]);
}
}) ]))
.pipe(gulp.dest(gulp.opts.srcDir + 'css/'));
});
gulp.task("sprite_scss2", ["sprite_scss1"] , function (cb) {
return gulp.src([gulp.opts.srcDir + 'css/**/*.sprite.css'],{base:gulp.opts.srcDir})
.pipe(cssnano({
isSafe:true,
autoprefixer: {
add: true
}
}))
.pipe(rev())
.pipe(gulp.dest(gulp.opts.staticDir))
.pipe(rev.manifest())
.pipe(gulp.dest(gulp.opts.staticDir + 'revmanifest/css.sprite/'))
});
gulp.task('scss-dev', function (cb) {
return gulp.src([
gulp.opts.srcDir + 'scss/**/*.scss',
"!" + gulp.opts.srcDir + 'scss/**/*.sprite.scss',
],{base:gulp.opts.srcDir + 'scss'})
.pipe(gulpSass().on('error', gulpSass.logError))
.pipe(gulpMem.dest(gulp.opts.srcDir + 'css/'));
});
gulp.task('scss1', function (cb) {
return gulp.src([
gulp.opts.srcDir + 'scss/**/*.scss',
"!" + gulp.opts.srcDir + 'scss/**/*.sprite.scss',
],{base:gulp.opts.srcDir + 'scss'})
.pipe(gulpSass().on('error', gulpSass.logError))
.pipe(gulp.dest(gulp.opts.srcDir + 'css/'));
});
gulp.task('scss2',["scss1"], function (cb) {
return gulp.src([
gulp.opts.srcDir + 'css/**/*.css',
"!" + gulp.opts.srcDir + 'css/**/*.sprite.css',
],{base:gulp.opts.srcDir})
.pipe(cssnano({
isSafe:true,
autoprefixer: {
add: true
}
}))
.pipe(rev())
.pipe(gulp.dest(gulp.opts.staticDir))
.pipe(rev.manifest())
.pipe(gulp.dest(gulp.opts.staticDir + 'revmanifest/css/'))
});
gulp.task("copy2",function(cb){
//实际上要拷备的是images images.sprite 资源
return gulp.src([gulp.opts.srcDir +"other/*.*",
gulp.opts.srcDir +"fonts/**/*.*",
gulp.opts.srcDir +"images/**/*.*",
gulp.opts.srcDir +"img/**/*.*",
gulp.opts.srcDir +"images.sprite/**/*.*",
],{base:gulp.opts.srcDir})
.pipe(gulp.dest(gulp.opts.staticDir))
});
gulp.task("html2",function(cb){
"use strict";
return gulp.src([
gulp.opts.staticDir + 'revmanifest/**/*.json',
gulp.opts.srcDir+'../Home/View/**/*.html'
])
.pipe(revCollector({
replaceReved: true
}) )
.pipe(gulp.dest(gulp.opts.srcDir + '/../Home/View/') );
});
gulp/task.js
let gulp = require('gulp');
let gulpSequence = require('gulp-sequence');
let gulpWatch = require("gulp-watch");
let gulpSass = require('gulp-sass');
let cssnano = require('gulp-cssnano');
let sprites = require('postcss-sprites');
let gulpFilter = require("gulp-filter");
let postcss = require('gulp-postcss');
let babel = require("gulp-babel");
let server = require('gulp-server-livereload');
let injectReload = require("gulp-inject-reload");
let gutil = require("gulp-util");
let gulpif = require("gulp-if");
let express = require('express');
let gulpMem = gulp.opts.gulpMem;
let backend = require("../backend");
gulp.task("devbuild", function (cb) {
return gulpSequence("clean1",["js-dev", "scss-dev","sprite_scss-dev"] ,cb);
});
gulp.task("sandbox", function (cb) {
return gulpSequence("clean2",["js2","scss2","sprite_scss2",], "copy2", "html2", "clean1",cb);
});
gulp.task("prod", function (cb) {
return gulpSequence("clean2",["js2","scss2","sprite_scss2",], "copy2", "html2", "clean1",cb);
});
gulp.task('watch', ["devbuild"], function () {
gulpWatch([
gulp.opts.srcDir + 'scss/**/*.scss',
"!" + gulp.opts.srcDir + 'scss/**/*.sprite.scss',
],
{
base:gulp.opts.srcDir,
verbose:true,
},
function (file, cb) {
return gulp.src(file.path, {base:gulp.opts.srcDir + "scss/"})
.pipe(gulpSass().on('error', gulpSass.logError))
.pipe(gulpMem.dest(gulp.opts.srcDir + 'css/'))
});
gulpWatch([gulp.opts.srcDir + 'scss/**/*.sprite.scss'],
{
base:gulp.opts.srcDir,
verbose:true,
},
function (file, cb) {
return gulp.src(file.path, {base:gulp.opts.srcDir + "scss/"})
.pipe(gulpSass().on('error', gulpSass.logError))
.pipe( postcss([ sprites({
spritePath: './Public/images.sprite/'
}) ]))
.pipe(gulpMem.dest(gulp.opts.srcDir + 'css/'))
});
function isJplayer(file){
//判断是js且并非jplayer.js
return /jquery\.jplayer\./.test(file.history[0]);
}
gulpWatch([gulp.opts.srcDir + 'es/**/*.js'],
{
base:gulp.opts.srcDir,
verbose:true,
},
function (file, cb) {
return gulp.src(file.path, {base:gulp.opts.srcDir + "es/"})
// .pipe(gulpif(isJplayer, gutil.noop(), gutil.noop()))
.pipe(babel({
"presets": ["es2015"]
}))
.pipe(gulpMem.dest(gulp.opts.srcDir + 'js'));
});
});
gulp.task('dev',["watch"], function() {
let app = express();
app.use(gulpMem.middleware);
app.listen(3001);
return gulp.src(['./Public','./Home/View','./Tpl/Home']) //会监听的地址
.pipe(server({
host:"0.0.0.0",
port:3000,
livereload: {
enable: true,
filter: function(filename, cb) {
cb(/\.(scss|js|png|svg|jpg|jpeg|html|htm)$/.test(filename)); //监控文件改变
}
},
directoryListing: true,
open: false,
proxies:[
{
source:"/Public/js",
target:"http://127.0.0.1:3001/Public/js"
},
{
source:"/Public/css",
target:"http://127.0.0.1:3001/Public/css"
},
{
source:"/",
target:backend(),
},
]
}));
});
package.json
{
"scripts": {
"dev": "gulp dev",
"sandbox": "gulp sandbox",
"prod": "gulp prod"
}
}
六、需要后端配合的地方
开发时,页面中必须引入livereload.js才能实现动态刷新,而生产环境下,又不能引入livereload.js。我采用的做法是后端php判断当前环境动态引入livereload.js。
项目后端语言为php,框架用了thinkphp
<if condition="$Think.const.ENVIRONMENT_TYPE eq 'Dev' " >
<script src="http://127.0.0.1:35729/livereload.js"></script>
</if>
七、使用方法
- dev环境
执行npm run dev,浏览器访问http://localhost:3000。
- sandbox环境
执行npm run sandbox,会更新所有Home/View/目录下的html的js后缀名和css后缀名,最终前端资源生成在../static目录下面。
Home/View下面的的html文件更新前:
before-update-hash.png
Home/View下面的的html文件更新后:
after-update-hash.png
PS:本项目的js和css是通过下面的php模板代码引入的。
<volist name="CSSLIST" id="vo">
<link type="text/css" rel="stylesheet" href="/Public/{$vo}?{:C('CSS_JSS_VERSION')}" >
</volist>
<volist name="JSLIST" id="vo">
<script type="text/javascript" src="/Public/{$vo}?{:C('CSS_JSS_VERSION')}"></script>
</volist>
//所以页面上只需要定义```$CSSLIST``` ```$JSLIST```变量即可。
- production环境
同沙箱
