开发dev
gulp-load-plugins
使用gulp-load-plugins模块,可以加载package.json文件中所有的gulp模块,而不用一个一个地去加载。
var gulp = require('gulp'), gulpLoadPlugins = require('gulp-load-plugins'), $= gulpLoadPlugins(); gulp.task('js', function () { return gulp.src('js/*.js') .pipe($.jshint()) .pipe($.jshint.reporter('default')) .pipe($.uglify()) .pipe($.concat('app.js')) .pipe(gulp.dest('build')); });
一般在gulpfile的开头我们都通过gulp-load-plugins来将所有的gulp模块加载进来。
然后我们就要定义各个gulp任务了。
gulp.task('default', function(cb) {
$.runSequence('dev:local', 'dev:connect', 'dev:watch', cb);
});
run-sequence
gulp里的task都是异步并发执行的,有的时候我们需要一连串的task按顺序执行,这时就需要run-sequence。
runSequence('task1', 'task2', ['task3', 'task4'], 'task5')
task1完成后才会执行task2,以此类推。
注意到task3和task4被放在中括号里了,这表明,task3和task4可以并发执行的,但两个都执行完后才会执行task5。这里要说明的是,每个task要么返回一个stream,即return gulp.src().pipe().pipe(),要么支持回调函数,即gulp.task(‘task1’, function (done) { action1(done); }),满足了这两点才能保证正常的执行顺序,因为这是gulp对异步task的基本要求。
这里我们通过定义default任务来定义gulp命令发出时顺序执行的任务。
一般在开发阶段我们需要依次完成以下几个工作:
- 一些编译工作,可以同步进行
- less/sass编译
- 模版预编译
- 开启本地开发server
- 监听文件变化
下面来详细地记录一下。
less/sass编译
首先要将less/sass源文件编译成css文件,这样页面才可以使用。
gulp.task('dev:less',function(){
return gulp.src('/less/*.less')
.pipe($.plumber())
.pipe($.less())
.pipe($.connect.reload())
.pipe(gulp.dest('/styles'))
})
gulp-plumber
防止由gulp插件错误导致的pipe break,一般用在流的最开始处。
可以传入函数来处理错误gulp-less
将less文件编译成css文件gulp-connet
启动一个webserver,在后面详细说明。这里使用就是在每次less编译完毕后都刷新浏览器
最后我们将编译好的css文件放入目标文件夹中,页面就可以引用了。
模版预编译
有时我们会使用一些模版,他们需要被编译之后才能在页面中显示。
这里就要根据使用的不同的模版来选择相应的gulp编译插件来使用。
开启本地开发server
gulp-connect不仅能够自动启动一个web服务器,还可以监听文件的改动自动刷新浏览器,解放F5。
最简单的启动一个web服务器:
gulp.task('webserver',function(){
$.connect.server();
})
没有做任何配置,那么就是在localhost:8080可以看到gulpfile.js同级目录下的index.html。
为了浏览器自动刷新,我们要配置livereload,一般是下面两个步骤:
- 在启动服务器的时候运行livereload:
gulp.task('webserver',function(){
$.connect.server({
livereload:true
});
})
- 在任务中,有文件更新时就通知livereload,让其刷新浏览器。
这里就像之前在less任务中看到的那样:
gulp.task('dev:less',function(){
return gulp.src('/less/*.less')
.pipe($.plumber())
.pipe($.less())
.pipe($.connect.reload())
.pipe(gulp.dest('/styles'))
})
在中间less编译完成后,就调用$.connect.reload()
来通知livereload刷新浏览器。
除了livereload之外,webserver还有其他一些基本的配置选项:
- root:根目录,默认为gulpfile所在的目录
- port:端口
- middleware:中间件
有时我们会需要一些中间件来完成一些附加的工作。
比如现在我的工程中使用了ssi,那么就需要一个中间件获取ssi引入的线上的文件,那么我就需要gulp-connect-ssi这样一个 gulp-connect的中间件。
gulp.task('webserver', function () {
$.connect.server({
root: _.app,
port: 80,
livereload: true,
middleware: function(){
return [$.connectSsi({
baseDir: __dirname + '/app',
ext: '.html',
domain: 'http://example.com/',
method: 'readOnLineIfNotExist'
onlineEncoding: 'GBK',
localEncoding: 'utf8'
})];
}
});
});
我们在connect的middleware配置中使用gulp-connect-ssi,用于下载引用到的页面片。
监听文件变化
通过gulp.watch命令,可以在文件变动的时候执行相应的任务。
一般在一个工程中,都要监听less/sass、html和js的变化:
gulp.task('dev:watch', function() {
gulp.watch(['/less/**/*.less'], ['dev:less']); // 监听less
gulp.watch(['/**/*.htm?(l)', '!' + _.app + '/tpls/**/*.html'], ['dev:html']);
gulp.watch(['/tpls/**/*.html'], ['dev:' + _.templatePreProcessor]);
gulp.watch(['/scripts/**/*.js'], ['dev:js']);
});
比如我们更改了less文件,就会执行dev:less,这个任务中则会执行编译、浏览器刷新等一系列操作。
而像js与html文件,也无需编译,直接刷新浏览器就好。
// 绑定html监听回调
gulp.task('dev:html', function() {
return gulp.src('/**/*.htm?(l)')
.pipe($.plumber(swallowError))
.pipe($.connect.reload());
});
// 绑定js监听回调
gulp.task('dev:js', function() {
return gulp.src( '/scripts/**/*.js')
.pipe($.plumber(swallowError))
.pipe($.connect.reload());
});
编译build
在编译阶段,我们就要对代码做进一步的打包压缩等工作了,方便生产环境中使用。
编译阶段要完成的工作:
- 首先包括刚刚开发阶段的一些编译工作
- less/sass编译
- 模版预编译
- 清空build目录
- 接下来一些打包和压缩工作可以同步进行,具体看工程中使用到了什么,比如:
- requirejs打包
- css压缩、雪碧图合并和压缩
- 图片压缩
- 处理html
- css js img 内联
- 开启build目录下webserver
requirejs打包
RequireJS有一个优化工具,执行以下操作
- 将相关脚本合并到构建层中,并通过UglifyJS(默认)将其缩小。
- 通过内联@import引用的CSS文件并删除注释来优化CSS。
这个优化器optimizer是r.js的一部分,它被设计为在完成开发之后作为构建或打包步骤的一部分运行,并可以为用户部署代码。
gulp.task('build:requirejs', function(cb) {
require('requirejs').optimize({
baseUrl: _.app + '/scripts/',
optimize: 'none',
include: ['config'],
mainConfigFile:_.app + '/scripts/config.js',
excludeShallow:['jquery'],
out: _.build + '/scripts/main.js',
preserveLicenseComments: true,
useStrict: true,
wrap: true
}, function() {
cb(); // 成功,通知gulp任务结束
}, function() {
cb(); // 失败,通知gulp任务结束
});
});
这里我们使用了requirejs的optimize方法,并进行一些配置。
最终的结果就是会在build目录下的’/scripts’路径下生成打包后的一个js文件,即为main.js。
雪碧图合并,压缩css和雪碧图
gulp-sprite-generator
一个万能的gulp雪碧图插件,可以根据css样式内容生成雪碧图,并更新css中的图片引用。
gulp.task('sprites', function() {
var spriteOutput;
spriteOutput = gulp.src("./src/css/*.css")
.pipe(sprite({
baseUrl: "./src/image",
spriteSheetName: "sprite.png",
spriteSheetPath: "/dist/image"
});
spriteOutput.css.pipe(gulp.dest("./dist/css"));
spriteOutput.img.pipe(gulp.dest("./dist/image"));
});
这里是简单的示例,通过配置图片的路径进行雪碧图生成,最后再将生成的css和雪碧图放到编译目录下。
实际应用中,我们可能会增加一些操作,比如压缩css和图片:
gulp.task('build:sprites', function() {
var spriteOutput = gulp.src(_.app + '/styles/main.css')
.pipe($.spriteGenerator({
baseUrl: _.app + '/images/sprites',
spriteSheetName: 'sprite.png',
spriteSheetPath:'../images/sprites',
algorithm: 'binary-tree', // 布局算法可参考:https://github.com/twolfson/layout
filter:[
function(image) {
return image.url.indexOf('/sprites/') !== -1; // 只处理sprites目录下图片
}
]
}));
var spriteCss = spriteOutput.css.pipe($.cleanCss({compatibility: 'ie7'})) // 压缩css
.pipe(gulp.dest(_.build + '/styles/'));
var spriteImg = spriteOutput.img.pipe($.imagemin({ // 压缩雪碧图
progressive: true, // 针对jpg
use: [$.imageminPngquant({quality: '65-80', speed: 4})] // png
})).pipe(gulp.dest(_.build + '/images/sprites/'));
return $.mergeStream(spriteCss, spriteImg);
});
这里又用到了压缩css、压缩图片的一些插件,不多加赘述了。
最后的mergeStream用于将多个stream流合并为一个,返回以便下面的流程使用。
图片压缩
除了对雪碧图做处理之外,其他一些没有使用雪碧图的图片也要进行压缩,使用的还是gulp-imagemin这个插件。
处理html
将所有素材与资源都打包压缩完毕后,也要对html进行处理。
- 压缩
- 内联包含inline属性的所有
<!-- located at src/html/index.html -->
<html>
<head>
<!-- inline src/js/inlineScript.js -->
<script src="../js/inlineScript.js" inline></script>
</head>
<body>
</body>
</html>
src/js/inlineScript.js
function test() {
var foo = 'lorem ipsum';
return foo;
}
最终输出:
<html>
<head>
<script>function test(){var a="lorem ipsum";return a}</script>
</head>
<body>
</body>
</html>
开启build目录下webserver
和开发阶段启动server没有区别,只不过这次的根目录是编译目录build。
发布publish
其实一般编译过后的工程已经可以使用了,但是根据各个公司部署方式的区别可能还要做一些最后的工作。
- 增加这些素材的hash(js、img、css)
- 更改html中引用素材的路径
- 将使用到的素材上传到CDN上
增加文件hash
我们每一次部署,js、css等文件都可能有更新,但是在客户那边由于缓存的关系可能并不能看到更新的效果,我们也不能强制用户清缓存刷新。
为线上文件增加hash值可以解决这个问题,用户访问的页面引用的素材名称和之前不同,所以一定会去引用更新过的素材。
gulp-rev
通过将内容哈希附加到文件名来进行静态资产修订
// 增加img文件hash
gulp.task('rev:img', function() {
return gulp.src(_.build + '/images/**/*.png')
.pipe($.rev())
.pipe(gulp.dest(_.dist + '/images'))
.pipe($.rev.manifest())
.pipe(gulp.dest(_.dist + '/rev/images'));
});
首先$.rev()
就是进行hash。
$.rev.manifest()
则是将原始路径映射到修订路径上,具体的映射关系写在一个json文件中,像这样:
{
"css/unicorn.css": "css/unicorn-d41d8cd98f.css",
"js/unicorn.js": "js/unicorn-273c2c123f.js"
}
有时我们会在这个步骤中对js进行一个压缩,这是要使用到gulp-sourcemaps。
什么是sourcemap?
简单说,Source map就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置。
有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码。这无疑给开发者带来了很大方便。(我们不想在调试的时候看到一堆压缩后的代码)。
于是我们可以在hash的过程中穿插sourcemap和压缩的过程:
gulp.task('rev:js', function() {
return gulp.src(_.build + '/scripts/*.js')
.pipe($.sourcemaps.init())
.pipe($.rev())
.pipe($.uglify({output: {ascii_only: true}}) ) // 压缩js(暂时不压缩,等rev以后压缩)
.pipe($.sourcemaps.write('./'))
.pipe(gulp.dest(_.dist + '/scripts'))
.pipe($.rev.manifest())
.pipe(gulp.dest(_.dist + '/rev/scripts'));
});
更改html中引用素材的路径
我们对文件做了hash处理,此时就要更改html中引用它们的路径。同时也可以在此直接把相对路径改为CDN的绝对路径。
gulp-rev-collector
根据之前rev.manifest()生成的manifest文件中的映射关系,替换html中引用静态资源的链接
gulp.task('rev', function () {
return gulp.src(['rev/**/*.json', 'templates/**/*.html'])
.pipe( revCollector({
replaceReved: true,
dirReplacements: {
'css': 'www.cdn.com/dist/css',
'/js/': 'www.cdn.com/dist/js/',
}
}) )
.pipe( gulp.dest('dist') );
});
其中dirReplacements选项明确要替换的目录路径。 gulp-rev生成的manifest文件并没有目录信息,我们需要在这里配置。这样替换后就可以引用到正确目录下的静态资源:
"/css/style.css" => "www.cdn.com/dist/css/style-1d87bebe.css"
"/js/script1.js" => "www.cdn.com/dist/script1-61e0be79.js"
将使用到的素材上传到CDN上
这一步就需要自己实现辣~不多说
总结
以上就是一个比较全面详细的gulp工作流。