本文将会介绍如何使用gulp4来搭建项目脚手架,如果您还在使用gulp3或更老的版本,您也以通过本文的一些思想将之前的项目进行完善,更新。如果gulp不是你们团队的重点,也可以移步我的另一篇文章:
用 webpack 4.0 撸单页/多页脚手架 (jquery, react, vue, typescript)
前言
由于本文重点是介绍gulp4.0搭建脚手架的思路,相关插件的用法以及项目结构的设计,由于gulp的基本用法很简单,如果不熟悉可以移步官网自行研究学习。该脚手架的设计思路和功能如下:
同时为了提高开发环境的效率,这里我们参考webpack的配置,区分开发环境和生产环境,在接下来将会具体介绍。
脚手架用到的第三方插件介绍
- gulp-jshint ——js语法检测
- gulp-util ——终端控制台打印自定义错误信息
- http-proxy-middleware ——设置代理,配合gulp-connect使用
- gulp-less ——将less编译成css
- gulp-file-include ——用于文件模块化导入,如用include的方式导入公共部分
- gulp-connect ——用于启动本地服务器
- gulp-clean ——清理目录
- gulp-uglify --压缩js
- gulp-minify-css ——压缩css
- gulp-autoprefixer ——自动添加浏览器前缀
- imagemin-pngquant ——png图片压缩
- gulp-imagemin ——图压缩
- gulp-cache ——设置gulp打包的缓存,一般用于img
- gulp-md5-plus ——将文件名进行md5处理便于打包更新
当然gulp还有很多常用的插件可以更好的为我们的项目服务,大家也可以整合自己的插件让项目更加完善。
项目目录设计
1.src目录,即我们开发项目时的源目录,具体结构如下:
我们定义views是我们视图层,即页面文件的目录,js目录为业务逻辑的脚本文件,lib存放第三方框架,include目录为公共部分的存放目录,我们可以用gulp-file-include来导入到html中,images和css大家都比较清楚,分别时存放image和css文件的目录。
2. dist目录,即输出的目录,具体结构如下:
可以看到我们会看到src打包后的目录对应static目录,为什么我们会加一层static呢?我的设计是如果项目使用node等服务层框架,我们可以用gulp一并打包放入dist下,这样dist就是一个完整的包括前后端服务的项目目录了,当然大家也可以直接将src打包后的文件和文件夹直接放到dist下,根具业务需求灵活设计吧。
在这里我要说一点,由于笔者亲测gulp-md5-plus有时候打包不稳定,可能不会给html自动添加对应的md5后缀,所以笔者在这块做了特殊的处理,如果大家在工作中有更好的方案,可以及时和笔者沟通交流。
3. gulpfile文件配置
由于我们要区分开发环境和生产环境,所以这里我们使用两个不同的配置文件,根据NODE_ENV来区分用哪个文件。
我们将配置文件统一放到build目录下,config为公共配置文件,gulp.dev.js和gulp.prod.js分别为开发和生产环境配置文件。我们整体的目录结构如下:

脚手架完整源码(部分插件和配置会给出详细注释)
- config.js
1module.exports = { 2 dist: './dist/static', // 配置构建目录 3}
- gulp.dev.js
1const gulp = require('gulp'); 2// js 3const Jshint = require("gulp-jshint"); //js检查 4const Gutil = require('gulp-util'); 5const Proxy = require('http-proxy-middleware'); 6// const Webpack = require('webpack'); 7// const WebpackConfig = require('./webpack.config.js'); 8 9// css 10const Less = require('gulp-less'); // 编译less 11 12// html 13const FileInclude = require('gulp-file-include'); // 文件模块化 14 15// server 16const Connect = require('gulp-connect'); //引入gulp-connect模块 17 18const Clean = require('gulp-clean'); // 清理目录 19 20// 配置文件 21const config = require('./config'); 22const { dist } = config; 23 24// html 25async function html() { 26 return gulp.src('src/views/*.html') 27 .pipe(FileInclude({ // HTML模板替换,具体用法见下文 28 prefix: '##', 29 basepath: '@file' 30 })).on('error', function(err) { 31 console.error('Task:copy-html,', err.message); 32 this.end(); 33 }) 34 .pipe(gulp.dest(dist)) // 拷贝 35 .pipe(Connect.reload()) 36} 37 38// css 39async function css() { 40 return await gulp.src('src/css/*.less') 41 .pipe(Less()) //编译less 42 .pipe(gulp.dest(dist + '/css')) //当前对应css文件 43 .pipe(Connect.reload());//更新 44} 45 46// js 47// const compilerJS = Webpack(WebpackConfig); 48 49async function js() { 50 return await gulp.src('src/js/**') 51 .pipe(Jshint())//检查代码 52 // .pipe(Babel({ 53 // presets: ['es2015'] 54 // })) 55 .on('error', function(err) { 56 Gutil.log(Gutil.colors.red('[Error]'), err.toString()); 57 }) 58 .pipe(gulp.dest(dist + '/js')) // 拷贝 59 .pipe(Connect.reload()); //更新 60 61 // 使用es6+可以单独配置 62 // compilerJS.run(function(err, stats) { 63 // if(err) throw new Gutil.PluginError("webpack:js", err); 64 // Gutil.log("[webpack]", stats.toString({ 65 // colors: true 66 // })); 67 // cb() 68 // }); 69} 70 71// image 72async function image() { 73 return await gulp.src('src/images/*') 74 .pipe(gulp.dest(dist + '/images')); 75} 76 77// clean dir 78async function clean() { 79 // 不设置allowEmpty: true会报File not found with singular glob 80 return await gulp.src(dist, {allowEmpty: true}).pipe(Clean()); 81} 82 83// 服务器函数 84async function server() { 85 Connect.server({ 86 root:dist, //根目录 87 // ip:'192.168.11.62',//默认localhost:8080 88 livereload:true, //自动更新 89 port:9909, //端口 90 middleware: function(connect, opt) { 91 return [ 92 Proxy('/api', { 93 target: 'http://localhost:8080', 94 changeOrigin:true 95 }), 96 Proxy('/otherServer', { 97 target: 'http://IP:Port', 98 changeOrigin:true 99 }) 100 ] 101 } 102 }) 103} 104 105module.exports = { 106 html, 107 css, 108 js, 109 image, 110 clean, 111 server 112}
- gulp.prod.js
1const gulp = require('gulp'); 2// const Rename = require('gulp-rename'); // 重命名 3// js 4const Uglify = require('gulp-uglify'); // 压缩js 5// const Babel = require('gulp-babel'); 6// css 7const Minifycss = require('gulp-minify-css'); // 压缩css 8const Less = require('gulp-less'); // 编译less 9const Autoprefixer = require('gulp-autoprefixer'); // 浏览器前缀 10// html 11const MinifyHtml = require("gulp-minify-html"); //压缩html 12const FileInclude = require('gulp-file-include'); // 文件模块化 13// image 14const Imagemin = require('gulp-imagemin'); 15const Pngquant = require('imagemin-pngquant'); //png图片压缩插件 16const Cache = require('gulp-cache'); 17 18const Clean = require('gulp-clean'); // 清理目录 19 20// md5 发版本的时候为了避免浏览器读取了旧的缓存文件,需要为其添加md5戳 21const md5 = require("gulp-md5-plus"); 22 23const config = require('./config'); 24const { dist } = config; 25// html 26async function html() { 27 return gulp.src('src/views/*.html') 28 .pipe(FileInclude({ // HTML模板替换,具体用法见下文 29 prefix: '##', 30 basepath: '@file' 31 })) 32 // .pipe(MinifyHtml()) 33 .on('error', function(err) { 34 console.error('Task:copy-html,', err.message); 35 this.end(); 36 }) 37 .pipe(gulp.dest(dist)) // 拷贝 38} 39 40// css 41async function css() { 42 return await gulp.src('src/css/**') 43 .pipe(Less()) //编译less 44 .pipe(Autoprefixer({ 45 cascade: true, //是否美化属性值 默认:true 像这样: 46 //-webkit-transform: rotate(45deg); 47 // transform: rotate(45deg); 48 remove: true //是否去掉不必要的前缀 默认:true 49 })) 50 .pipe(Minifycss({ // 压缩css 51 //类型:Boolean 默认:true [是否开启高级优化(合并选择器等)] 52 advanced: true, 53 //保留ie7及以下兼容写法 类型:String 默认:''or'*' [启用兼容模式; 'ie7':IE7兼容模式,'ie8':IE8兼容模式,'*':IE9+兼容模式] 54 compatibility: '', 55 //类型:Boolean 默认:false [是否保留换行] 56 keepBreaks: false, 57 //保留所有特殊前缀 当你用autoprefixer生成的浏览器前缀,如果不加这个参数,有可能将会删除你的部分前缀 58 keepSpecialComments: '*' 59 })) 60 .pipe(gulp.dest(dist + '/css')) 61 .pipe(md5(10, dist + '/*.html', { 62 mappingFile: 'manifest.json', 63 connector: '.' // 文件名和hash的连接符 64 })) 65 .pipe(gulp.dest(dist + '/css')) //当前对应css文件 66} 67 68// js 69async function js() { 70 return await gulp.src('src/js/**') 71 // .pipe(Babel({ 72 // presets: ['es2015'] 73 // })) 74 .pipe(Uglify()) // 压缩js 75 .pipe(gulp.dest(dist + '/js')) 76 .pipe(md5(10, dist + '/*.html', { 77 mappingFile: 'manifest.json', 78 connector: '.' 79 })) 80 .pipe(gulp.dest(dist + '/js')) // 拷贝 81} 82 83// image 84async function image() { 85 return await gulp.src('src/images/*') 86 .pipe(Cache(Imagemin({ 87 optimizationLevel: 5, //类型:Number 默认:3 取值范围:0-7(优化等级) 88 progressive: true, //类型:Boolean 默认:false 无损压缩jpg图片 89 interlaced: true, //类型:Boolean 默认:false 隔行扫描gif进行渲染 90 multipass: true, //类型:Boolean 默认:false 多次优化svg直到完全优化 91 svgoPlugins: [{removeViewBox: false}],//不要移除svg的viewbox属性 92 use: [Pngquant()] //使用pngquant深度压缩png图片的imagemin插件 93 }))) 94 .pipe(gulp.dest(dist + '/images')); 95} 96 97 98// clean dir 99async function clean() { 100 // 不设置allowEmpty: true会报File not found with singular glob 101 return await gulp.src(dist, {allowEmpty: true}).pipe(Clean()); 102} 103 104 105 106module.exports = { 107 html, 108 css, 109 js, 110 image, 111 clean 112}
- gulpfile.js
1const gulp = require('gulp'); 2 3// 根据环境引入不同的配置文件 4let buildConfig; 5if(process.env.NODE_ENV === 'dev') { 6 buildConfig = require('./build/gulp.dev'); 7 gulp.task('server', buildConfig.server); // 本地服务 8 9} else { 10 buildConfig = require('./build/gulp.prod'); 11 // gulp.task('md5', gulp.series(buildConfig.md5Css, buildConfig.md5Js)); 12 gulp.task('clean', buildConfig.clean); // 清理目录 13} 14 15gulp.task('html', buildConfig.html); // 打包html 16gulp.task('js', buildConfig.js); // 打包js 17gulp.task('css', buildConfig.css); // 打包css 18gulp.task('images', buildConfig.image); // 打包image 19gulp.task('sources', gulp.series('html', gulp.parallel('js', 'css', 'images'))); 20 21 22// 监听文件变化 23gulp.task('watch', async () => { 24 gulp.watch('src/views/*', gulp.series('html')); // 监听HTML变化 25 gulp.watch('src/js/**', gulp.series('js')); // 监听js变化 26 gulp.watch('src/css/*', gulp.series('css')); // 监听css变化 27 gulp.watch('src/images/*', gulp.series('images')); // 监听image变化 28}); 29 30// build 31if(process.env.NODE_ENV === 'dev') { 32 gulp.task('dev', gulp.series('sources', 'server', 'watch')); 33} else { 34 gulp.task('build', gulp.series('sources')); 35} 36 37
- package.json
1{ 2 "dependencies": { 3 "@babel/core": "^7.4.5", 4 "babel-preset-es2015": "^6.24.1", 5 "gulp": "^4.0.2", 6 "gulp-autoprefixer": "^6.1.0", 7 "gulp-babel": "^8.0.0", 8 "gulp-cache": "^1.1.2", 9 "gulp-clean": "^0.4.0", 10 "gulp-connect": "^5.7.0", 11 "gulp-file-include": "^2.0.1", 12 "gulp-imagemin": "^6.0.0", 13 "gulp-jshint": "^2.1.0", 14 "gulp-less": "^4.0.1", 15 "gulp-md5-plus": "^1.0.3", 16 "gulp-minify-css": "^1.2.4", 17 "gulp-minify-html": "^1.0.6", 18 "gulp-rename": "^1.4.0", 19 "gulp-uglify": "^3.0.2", 20 "gulp-util": "^3.0.8", 21 "http-proxy-middleware": "^0.19.1", 22 "http-server": "^0.11.1", 23 "imagemin-pngquant": "^8.0.0", 24 "jshint": "^2.10.2", 25 "jsonfile": "^5.0.0", 26 "webpack": "^4.35.2" 27 }, 28 "scripts": { 29 "start": "NODE_ENV=dev gulp dev", 30 "build": "NODE_ENV=prod gulp clean && gulp build", 31 "serve": "http-server dist/static -p 3000" 32 }, 33 "devDependencies": {} 34} 35
要想获取项目完整源码和demo,请移步gulp4_multi_pages。
最后
该脚手架任然有需要完善的地方,比如如何兼容uglify和babel,md5需要使用两次的情况,如果更好的解决方案,欢迎随时交流。在脚手架选型上,也不一定非要用gulp,webpack,一般的经验是传统型的静态网站适合用gulp,由于不需要编译es6,所以有更小的体积,当然也可以用webpack,本文主要是给大家提供一使用gulp4搭建个脚手架的思路,希望能有所收获。
更多推荐
- 如何用不到200行代码写一款属于自己的js类库)
- 让你瞬间提高工作效率的常用js函数汇总(持续更新)
- 一张图教你快速玩转vue-cli3
- 3分钟教你用原生js实现具有进度监听的文件上传预览组件
- 3分钟教你用原生js实现具有进度监听的文件上传预览组件
- 使用Angular8和百度地图api开发《旅游清单》
- js基本搜索算法实现与170万条数据下的性能测试
- 《前端算法系列》如何让前端代码速度提高60倍
- 《前端算法系列》数组去重
- vue高级进阶系列——用typescript玩转vue和vuex
- 前端三年,谈谈最值得读的5本书籍
欢迎关注趣谈前端,获取更多<strong>前端知识精粹</strong>和<strong>学习社群</strong>:
