一个全面详细的gulp工作流

本文详述了一个全面的gulp工作流,包括开发阶段的less/sass编译、模版预编译、本地开发server的开启和文件监听,再到编译阶段的requirejs打包、图片压缩、HTML处理,最后到发布的文件哈希添加、路径更新和CDN上传等步骤,全面覆盖前端项目构建的各个环节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

开发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工作流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值