gulp是一款流行的前端构建工具,能够帮咱们完成许多工做:监听文件修改、刷新浏览器、编译Less/Scss、压缩代码、添加md五、合并文件等。gulp的配置和使用特别简单,学习gulp过程当中顺便写了一个小示例。javascript
能够在GitHub上下载该示例:https://github.com/gymmer/gulp-examplecss
sudo npm install -g gulp
。-g
表示安装到全局环境,能够在任何项目中使用gulp。npm init
建立package.json
。npm install --save-dev gulp
。--save-dev
表示安装的同时更新package.json
中的依赖。示例采用了以下的项目结构:html
. ├── README.md // git的README文件 ├── dist // 生产环境目录。经过gulp构建自动生成 ├── gulpfile.js // gulp的配置文件 ├── node_modules // node必备 ├── package.json // node必备 └── src // 开发环境目录。存放项目的源代码 ├── css // 样式目录。保存CSS文件和字体文件 │ ├── compile // CSS文件目录。保存less/sass编译后生成的CSS文件 │ ├── fonts // 字体文件目录。保存Font Awesome或其余字体文件 │ ├── lib // CSS文件目录。保存引用的第三方CSS文件,如Bootstrap │ └── user // CSS文件目录。保存用户自定义的CSS文件 ├── img // 图片目录。保存图片文件 ├── index.html // 项目的HTML入口文件 ├── js // 脚本目录。保存JavaScript文件 │ ├── lib // JS文件目录。保存引用的第三方JS文件,如jQuery │ └── user // JS文件目录。保存用户自定义的JS文件 ├── less // Less文件目录 └── sass // Sass文件目录
示例中,CSS和JS将第三方库和用户自定义文件分开存放,虽然为gulp的配置带来稍许难度,可是更有利于项目的逻辑结构。前端
gulp生态圈有不少成熟的插件,示例中用到的有:java
"devDependencies": { "gulp": "^3.9.1", "gulp-cache": "^0.4.6", "gulp-clean": "^0.3.2", "gulp-clean-css": "^3.3.1", "gulp-compass": "^2.1.0", "gulp-connect": "^5.0.0", "gulp-htmlmin": "^3.0.0", "gulp-imagemin": "^3.2.0", "gulp-jshint": "^2.0.4", "gulp-less": "^3.3.0", "gulp-load-plugins": "^1.5.0", "gulp-notify": "^3.0.0", "gulp-plumber": "^1.1.0", "gulp-rev": "^7.1.2", "gulp-rev-collector": "^1.1.1", "gulp-sass": "^3.1.0", "gulp-uglify": "^2.1.2", "jshint": "^2.9.4" }
gulp的配置依赖于文件gulpfile.js
。这是示例配置文件一览,接下来将介绍每一个插件:node
'use strict'; var gulp = require('gulp'); var plugins = require('gulp-load-plugins')(); // JS代码校验。只校验用户自定义的js文件,不校验第三方js文件,如jQuery gulp.task('jslint', function() { return gulp.src('src/js/user/**/*.js') .pipe(plugins.jshint()) .pipe(plugins.jshint.reporter()); }); // 监听全部文件改动:自动刷新 gulp.task('reload', function() { return gulp.src('src/**/*') .pipe(plugins.connect.reload()); }); // 编译less文件,并监听less文件改动:从新编译+自动刷新 gulp.task('less', function() { return gulp.src('src/less/**/*.less') .pipe(plugins.plumber({errorHandler: plugins.notify.onError('Error: <%= error.message %>')})) // 防止less出错,自动退出watch .pipe(plugins.less()) .pipe(gulp.dest('src/css/compile')) .pipe(plugins.connect.reload()); }); // 编译sass文件,并监听sass文件改动:从新编译+自动刷新 gulp.task('sass', function() { return gulp.src('src/sass/**/*.{sass,scss}') .pipe(plugins.plumber({errorHandler: plugins.notify.onError('Error: <%= error.message %>')})) // 防止sass出错,自动退出watch // (1) 若是只使用 sass, 请使用sass插件: // .pipe(plugins.sass({ // outputStyle: 'expanded' // 可选:nested (默认) | expanded | compact | compressed // })) // (2) 若是使用 compass, 请使用compass插件: .pipe(plugins.compass({ css: 'src/css/compile', sass: 'src/sass', image: 'src/img', style: 'expanded' // 可选:nested (默认) | expanded | compact | compressed })) .pipe(gulp.dest('src/css/compile')) .pipe(plugins.connect.reload()); }); // 监听文件改动 gulp.task('watch', function() { gulp.watch('src/**/*', ['reload']); gulp.watch('src/less/**/*.less', ['less']); gulp.watch('src/less/**/*.{sass,scss}', ['sass']); }); // 运行一个服务器 gulp.task('server', function() { plugins.connect.server({ root: 'src', port: 8080, // Can not be 80 livereload: true }); }); // 默认任务 gulp.task('default', function() { gulp.run('jslint', 'reload', 'less', 'sass', 'watch', 'server'); }); // build gulp.task('clean', function() { return gulp.src('dist') .pipe(plugins.clean()); }); gulp.task('build-js', function() { return gulp.src('src/**/*.js') .pipe(plugins.uglify()) // JS压缩 .pipe(plugins.rev()) // 添加MD5 .pipe(gulp.dest('dist')) // 保存JS文件 .pipe(plugins.rev.manifest()) // 生成MD5映射 .pipe(gulp.dest('dist/rev/js')); // 保存映射 }); gulp.task('build-css', ['less', 'sass'], function() { // 编译less/sass return gulp.src('src/**/*.css') .pipe(plugins.cleanCss()) // CSS压缩 .pipe(plugins.rev()) // 添加MD5 .pipe(gulp.dest('dist')) // 保存CSS文件 .pipe(plugins.rev.manifest()) // 生成MD5映射 .pipe(gulp.dest('dist/rev/css')); // 保存映射 }); gulp.task('build-html', ['build-js', 'build-css'], function() { // 依赖:需先生成映射 return gulp.src(['dist/rev/**/*.json', 'src/**/*.html']) .pipe(plugins.revCollector()) // 根据映射,替换文件名 .pipe(plugins.htmlmin({collapseWhitespace: true})) // HTML压缩 .pipe(gulp.dest('dist')); // 保存HTML文件 }); gulp.task('build-fonts', function() { return gulp.src('src/**/*.{eot,ttf,woff,woff2,otf}') .pipe(gulp.dest('dist')); }); gulp.task('build-img', function() { return gulp.src('src/**/*.{png,jpg,gif,jpeg,svg}') .pipe(plugins.cache(plugins.imagemin({ // 图片压缩 interlaced: true }))) .pipe(gulp.dest('dist')); }); gulp.task('build', ['clean'], function() { return gulp.run('build-html', 'build-fonts', 'build-img', function() { gulp.src('dist/rev').pipe(plugins.clean()); }); });
一般开发环境和生产环境对项目构建的需求是不同的。开发环境侧重于调试代码,如监听文件修改、CSS预编译等。而生产环境侧重于项目的上线与部署,如压缩代码、压缩图片、添加md5等。为此,示例定义了两项gulp任务,以知足开发与生产的不一样需求。git
另外,每个gulp插件都有丰富的功能和配置,本示例的目的不在于讲解每一个插件的详细用法,而在于演示如何组合使用经常使用插件的基本功能,完成平常开发任务。github
官网:https://www.npmjs.com/package/gulp-load-pluginsnpm
安装:
npm install --save-dev gulp-load-plugins
使用gulp插件的通常过程是:
package.json
:npm install --save-dev gulp-foo-plugin
gulpfile.js
中加载插件:var foo = require('gulp-foo-plugin')
gulp.src('src/*.js').pipe(foo())
若是加载太多gulp插件,gulpfile.js
文件开头会有大量require
代码,致使文件冗长,而且容易遗漏:
var gulp = require('gulp'); var foo = require('gulp-foo-plugin'); var boo = require('gulp-boo-plugin'); var bar = require('gulp-bar-plugin'); gulp.task('foo', function() { return gulp.src('src/*.js') .pipe(foo()) .pipe(gulp.dest('dist/*.js')); }); gulp.task('boo', function() { return gulp.src('src/*.css') .pipe(boo()) .pipe(gulp.dest('dist/*.css')); }); gulp.task('bar', function() { return gulp.src('src/*.less') .pipe(bar()) .pipe(gulp.dest('dist/*.less')); });
而gulp-load-plugins
能够避免这样的问题。
gulp-load-plugins
:npm install --save-dev gulp-load-plugins
npm install --save-dev gulp-foo-plugin, gulp-boo-plugin, gulp-bar-plugin
gulpfile.js
中只需加载gulp-load-plugins
:var plugins = require('gulp-load-plugins')()
plugins
对象的方法进行调用。以下:var gulp = require('gulp'); var plugins = require('gulp-load-plugins')(); gulp.task('foo', function() { return gulp.src('src/*.js') .pipe(plugins.fooPlugin()) .pipe(gulp.dest('dist/*.js')); }); gulp.task('boo', function() { return gulp.src('src/*.css') .pipe(plugins.booPlugin()) .pipe(gulp.dest('dist/*.css')); }); gulp.task('bar', function() { return gulp.src('src/*.less') .pipe(plugins.barPlugin()) .pipe(gulp.dest('dist/*.less')); });
这样,就没必要写冗长的require()
了!
gulp-load-plugins
会自动寻找package.json
中以gulp-
为前缀的依赖项。因此必定记得更新package.json
。
本示例将大量使用gulp-load-plugins
。
官网:https://www.npmjs.com/package/gulp-connect
安装:
npm install --save-dev gulp-connect
本地调试时一般须要搭建一个web服务器。我选用的是gulp-connect
,配合gulp.watch()
还能够实现自动刷新浏览器。
// 运行一个服务器 gulp.task('server', function() { plugins.connect.server({ root: 'src', // 根目录 port: 8080, // 端口号。经测试,若是设置为80会报错。 livereload: true // 启用livereload,刷新浏览器 }); });
gulp-connect
还能够启动多个server,并为每一个server指定名称。具体看应用场景。
利用gulp-connect
的livereload功能,和gulp.watch()
函数,能够实现监听文件的修改,并自动刷新浏览器。
// 监听全部文件改动:自动刷新 gulp.task('reload', function() { return gulp.src('src/**/*') .pipe(plugins.connect.reload()); }); // 监听文件改动 gulp.task('watch', function() { gulp.watch('src/**/*', ['reload']); gulp.watch('src/less/**/*.less', ['less']); gulp.watch('src/less/**/*.{sass,scss}', ['sass']); });
这里比较暴力,直接监听了全部文件的修改。能够根据文件类型定义具体的规则。好比,但愿修改less/sass文件的同时,从新编译并刷新浏览器。那么须要在less
/sass
任务中,触发connect.reload()
。less
/sass
任务在后文有详细介绍。
官网:https://www.npmjs.com/package/gulp-jshint
安装:
npm install --save-dev gulp-jshint jshint
代码校验可使程序更健壮、更严谨,哪怕漏掉一个可有可无的分号,都逃不过jshint
的法眼。
使用比较简单。须要注意的是,在本示例的项目结构中,第三方JS文件和自定义JS文件分开存储,所以只校验了自定义的JS文件。
// JS代码校验。只校验用户自定义的js文件,不校验第三方js文件,如jQuery gulp.task('jslint', function() { return gulp.src('src/js/user/**/*.js') .pipe(plugins.jshint()) .pipe(plugins.jshint.reporter()); });
官网:https://www.npmjs.com/package/gulp-less
安装:
npm install --save-dev gulp-less gulp-plumber gulp-notify
编译less至CSS文件。基本用法为:
gulp.task('less', function() { return gulp.src('src/less/**/*.less') .pipe(plugins.less()) .pipe(gulp.dest('src/css/compile')); });
虽然编译生成了CSS,可是并不能知足需求。
首先,前面介绍过,若是但愿修改less文件的同时,编译less并刷新浏览器,则须要在less
任务中,触发connect.reload()
。
其次,当编译less时出现语法错误,会致使watch终止,而错误信息须要进入命令行中查看。因而你会发现,修改less后浏览器不会自动刷新了,查看CSS发现less没有被编译,而整个过程居然没有主动提醒我!解决方法是gulp-plumber
+gulp-notify
。
完整的解决方案以下:
// 编译less文件,并监听less文件改动:从新编译+自动刷新 gulp.task('less', function() { return gulp.src('src/less/**/*.less') .pipe(plugins.plumber({errorHandler: plugins.notify.onError('Error: <%= error.message %>')})) // 防止less出错,自动退出watch .pipe(plugins.less()) .pipe(gulp.dest('src/css/compile')) .pipe(plugins.connect.reload()); });
官网:https://www.npmjs.com/package/gulp-sass
安装:
npm install --save-dev gulp-sass gulp-plumber gulp-notify
与Less相比,几点不一样是:
.sass
和.scss
。最保险的方法是都编译一下。// 编译sass文件,并监听sass文件改动:从新编译+自动刷新 gulp.task('sass', function() { return gulp.src('src/sass/**/*.{sass,scss}') .pipe(plugins.plumber({errorHandler: plugins.notify.onError('Error: <%= error.message %>')})) // 防止sass出错,自动退出watch .pipe(plugins.sass({ outputStyle: 'expanded' // CSS格式 })) .pipe(gulp.dest('src/css/compile')) .pipe(plugins.connect.reload()); });
官网:https://www.npmjs.com/package/gulp-compass
准备:需安装compass:
gem install compass
安装:
npm install --save-dev gulp-compass gulp-plumber gulp-notify
Compass是Sass的神器。使用Compass插件的配置要稍稍复杂:
gulp.task('compass', function() { return gulp.src('src/sass/**/*.{sass,scss}') .pipe(plugins.plumber({errorHandler: plugins.notify.onError('Error: <%= error.message %>')})) // 防止sass出错,自动退出watch .pipe(plugins.compass({ css: 'src/css/compile', sass: 'src/sass', image: 'src/img', style: 'expanded' // CSS格式 })) .pipe(gulp.dest('src/css/compile')) .pipe(plugins.connect.reload()); });
gulp-sass
和gulp-compass
都是编译Sass,何须分家,搞得那么累?因而,我合并了这两个任务,应用时根据具体场景,注释掉相应代码便可。
// 编译sass文件,并监听sass文件改动:从新编译+自动刷新 gulp.task('sass', function() { return gulp.src('src/sass/**/*.{sass,scss}') .pipe(plugins.plumber({errorHandler: plugins.notify.onError('Error: <%= error.message %>')})) // 防止sass出错,自动退出watch // (1) 若是只使用 sass, 请使用sass插件: // .pipe(plugins.sass({ // outputStyle: 'expanded' // })) // (2) 若是使用 compass, 请使用compass插件: .pipe(plugins.compass({ css: 'src/css/compile', sass: 'src/sass', image: 'src/img', style: 'expanded' })) .pipe(gulp.dest('src/css/compile')) .pipe(plugins.connect.reload()); });
这么多小任务,是时候组合一下了。考虑到最常使用的是开发环境,而生产环境只是在上线迭代时才接触。所以,示例将defalut
任务分配给开发环境。
// 默认任务 gulp.task('default', function() { gulp.run('jslint', 'reload', 'less', 'sass', 'watch', 'server'); });
官网:https://www.npmjs.com/package/gulp-clean
安装:
npm install --save-dev gulp-clean
每次构建目录,若是但愿清理上次构建的文件,可使用gulp-clean
插件,免去了手动删除的操做。
gulp.task('clean', function() { return gulp.src('dist') .pipe(plugins.clean()); });
官网:https://www.npmjs.com/package/gulp-uglify
安装:
npm install --save-dev gulp-uglify
方法:
gulp.task('minify-js', function() { return gulp.src('src/**/*.js') .pipe(plugins.uglify()) // JS压缩 .pipe(gulp.dest('dist')); // 保存JS文件 });
官网:https://www.npmjs.com/package/gulp-clean-css
安装:
npm install --save-dev gulp-clean-css
方法:
gulp.task('minify-css', ['less', 'sass'], function() { // 编译less/sass return gulp.src('src/**/*.css') .pipe(plugins.cleanCss()) // CSS压缩 .pipe(gulp.dest('dist')); // 保存CSS文件 });
须要注意的是,本示例在压缩CSS以前,进行了一次Less/Sass编译,防止CSS文件丢失。
有的教程会提到使用gulp-minify-css
,不过npm官网已不推荐gulp-minify-css
:
This package has been deprecated. Please use gulp-clean-css instead.
官网:https://www.npmjs.com/package/gulp-htmlmin
安装:
npm install --save-dev gulp-htmlmin
方法:
gulp.task('minify-html', function() { return gulp.src('src/**/*.html') .pipe(htmlmin({collapseWhitespace: true})) // HTML压缩 .pipe(gulp.dest('dist')); // 保存HTML文件 });
有的教程会提到使用gulp-minify-html
,不过npm官网已不推荐gulp-minify-html
:
This package has been deprecated in favor of gulp-htmlmin, which should be faster and more comprehensive.
官网:https://www.npmjs.com/package/gulp-imagemin
安装:
npm install --save-dev gulp-imagemin gulp-cache
方法:
gulp.task('build-img', function() { return gulp.src('src/**/*.{png,jpg,gif,jpeg,svg}') .pipe(plugins.cache(plugins.imagemin({ // 图片压缩 interlaced: true }))) .pipe(gulp.dest('dist')); });
官网:https://www.npmjs.com/package/gulp-rev
安装:
npm install --save-dev gulp-rev
项目上线时 ,一般会为静态资源文件添加一个“后缀”,用来解决缓存更新问题。经常使用的后缀有时间戳、版本号、文件MD5值。本示例采用MD5值。
gulp-rev
插件会根据原文件生成一个新文件,其文件名会自动追加MD5。原文件与新文件的映射关系也须要保留下来,路径替换时会用到映射关系。
方法:
gulp.task('MD5', function() { return gulp.src('src/*.css') .pipe(plugins.rev()) // 添加MD5 .pipe(gulp.dest('dist')) .pipe(plugins.rev.manifest()) // 生成MD5映射 .pipe(gulp.dest('./rev')); // 保存映射 });
将压缩JS、压缩CSS和添加MD5这三项任务组合起来,便有了:
gulp.task('build-js', function() { return gulp.src('src/**/*.js') .pipe(plugins.uglify()) // JS压缩 .pipe(plugins.rev()) // 添加MD5 .pipe(gulp.dest('dist')) // 保存JS文件 .pipe(plugins.rev.manifest()) // 生成MD5映射 .pipe(gulp.dest('dist/rev/js')); // 保存映射 }); gulp.task('build-css', ['less', 'sass'], function() { // 编译less/sass return gulp.src('src/**/*.css') .pipe(plugins.cleanCss()) // CSS压缩 .pipe(plugins.rev()) // 添加MD5 .pipe(gulp.dest('dist')) // 保存CSS文件 .pipe(plugins.rev.manifest()) // 生成MD5映射 .pipe(gulp.dest('dist/rev/css')); // 保存映射 });
官网:https://www.npmjs.com/package/gulp-rev-collector
安装:
npm install --save-dev gulp-rev-collector
添加MD5会致使静态资源文件名的更改。所以HTML文件中须要对路径作替换,才能保证正确的引用关系。
须要注意的是,路径替换的依据是gulp-rev
插件生成的映射关系文件,所以它依赖build-js
和build-css
任务。
gulp.task('build-html', ['build-js', 'build-css'], function() { return gulp.src(['dist/rev/**/*.json', 'src/**/*.html']) .pipe(plugins.revCollector()) // 根据映射,替换文件名 .pipe(htmlmin({collapseWhitespace: true})) // HTML压缩 .pipe(gulp.dest('dist')); // 保存HTML文件 });
项目中有一些文件在整个周期中不会被修改,上线发布时也无需任何处理,如字体文件。那么,直接将这类文件复制到构建目录便可。
gulp.task('build-fonts', function() { return gulp.src('src/**/*.{eot,ttf,woff,woff2,otf}') .pipe(gulp.dest('dist')); });
生产环境的配置已大体完成。将上述任务组合:
gulp.task('build', ['clean'], function() { return gulp.run('build-html', 'build-fonts', 'build-img'); });
添加MD5任务中生成的映射文件,在完成路径替换后就结束了本身的使命。能够将其删除,使构建目录更加清爽。方法是,在build
任务中增长回调参数,使用gulp-clean
插件清空映射文件目录。
gulp.task('build', ['clean'], function() { return gulp.run('build-html', 'build-fonts', 'build-img', function() { gulp.src('dist/rev').pipe(plugins.clean()); }); });
若是使用git作版本控制,有一些目录是不须要提交的,能够用.gitignore
文件指定。例如:
/node_modules /dist /.sass-cache .DS_Store
gulp-sourcemaps
gulp-concat
gulp-rename