从零开始构建你的 Gulp

作者: Nian糕 | 来源:发表于2017-08-08 18:57 被阅读108次
    Unsplash

    本篇博文的内容根据 Introduction to Gulp.js 系列文章 拓展而来,其代码、依赖包及目录结构部分均有所更改,更多详细内容,敬请参考原文及作者 Github

    我们在上一篇博文 Gulp 前端自动化构建工具 中,已经对 Gulp 有了初步的了解,我们通过将所有任务写到 gulpfile.js 文件中进行编译,这当然是最直观的方法,但当我们需要执行的任务过多时,gulpfile.js 文件就会变的特别的巨大,这很不利于我们之后的维护及修改,所以我们要做的第一件事就是将 gulpfile.js 文件进行分割,分成一个个小的任务文件,每一个文件只完成特定的任务,这也是我们常说的模块化处理,每一任务文件不与其他文件产生直接交互,并通过赋值的方式在文件内部调用全局变量,下图是我们整个项目的目录结构,在文章的接下来部分,将会给大家详细讲解

    文件目录结构

    1. 文件结构

    我们先来简单介绍下我们的文件目录结构,node_modules 文件夹下为依赖包,gulp 文件夹下为任务文件,src 文件夹下为项目的引用文件,该目录下的文件均为测试文件,各位童鞋可根据自身需求进行修改替换,build 文件夹为 gulp 过后的生产文件

    因为 package.json 文件里所罗列的依赖包太多,在这里就不再具体展示,童鞋们可先自行下载 package.json 文件,运行 npm install 命令进行项目依赖包的下载,亦可通过下载整个项目进行学习,需要注意的是,插件的更新或是依赖包的缺少都可能导致项目无法正常运行,可根据报错信息进行依赖包的更新或修改

    gulpfile.js 文件非常的短,只有短短两行,我们通过 require-dir 依赖包的作用,将 ./gulp/tasks 目录下的所有任务载入

    // gulpfile.js
    const requireDir = require('require-dir'); 
    requireDir('./gulp/tasks', { recurse: true });
    
    gulp文件夹

    根据上图我们可以看到,gulp 文件夹下有一个 config.js 文件,主要是各任务的路径匹配及文件配置,具体如下图所示

    config.js

    2. default 默认任务

    当我们运行 gulp 命令时,Gulp 将会执行 default 默认任务,而该任务具体代码如下所示:

    // default.js
    const gulp = require('gulp');
    gulp.task('default', ['watch']);
    

    可以看到 default 任务并没有执行任何操作,但执行 defalut 任务前,我们需要先执行 watch 任务,我们再来看看 watch 任务里的具体代码

    // watch.js
    const gulp   = require('gulp'),
          config = require('../../config').watch;
    
    gulp.task('watch', ['browsersync'], () => {
        gulp.watch(config.styles, ['styles', 'lint-styles']);
        gulp.watch(config.scripts, ['scripts', 'jshint']);
        gulp.watch(config.images, ['optimize-images']);
        gulp.watch(config.sprites, ['sprites']);
    })
    
    运行结果

    可以看到,watch 任务监听了四个文件路径下的文件更改,涉及到了 9 个任务的运行,并没有涵盖我们定义的所有任务,这是因为这 9 个任务已经满足了我们日常的开发需求,至于其他任务,可以通过运行指定任务名来完成相应的操作,当然,各位童鞋也可以根据自身需求来对 watch 文件进行更改,在这里只是提供一个示例方法

    3. CSS 依赖包

    接下来我将根据作用的文件类型不同,来对所引入的依赖包来作简单的介绍,而关于各插件的更多用配置及用法,还请查看相应插件的 Github 主页

    // lint-styles.js
    const gulp      = require('gulp'),
          postcss   = require('gulp-postcss'),
          stylelint = require('stylelint'),
          reporter  = require('postcss-reporter'),
          config    = require('../../config');
    
    gulp.task('lint-styles', () => {
        return gulp.src(config.lintStyles.src)
                   .pipe(postcss([
                       stylelint(config.lintStyles.options.stylelint),
                       reporter(config.lintStyles.options.reporter)
                   ]))
    })
    

    可以看到,我们除了引入 stylelintpostcss-reporter 这两个插件之外,还引入了 gulp-postcss 这一插件集合,在这里想要跟大家介绍的是,PostCSS 是一个使用 JS 解析样式的插件集合,它可以用来审查 CSS 代码,也可以增强 CSS 的语法(比如变量和混合宏),还支持未来的 CSS 语法、行内图片等等,而本文所使用到的大部分 CSS 插件,均是来自 PostCSS,关于更多的 PostCSS 的介绍,而通过 w3cplusPostCSS 深入学习系列文章 进行学习

    stylelint 是一个代码审查插件,除了审查 CSS 语法外,还能审查类 CSS 语法,帮助我们审查出重复的 CSS 样式、不规范的代码、无效颜色值、无意义的浏览器前缀以及我们所配置的一些审查规则,我们可以根据自身项目的需求来设置不同的规则

    config.js

    rules 使用 [ 0, 1, 2 ] 来代表规则启用状态不同,具体的规则可在 Rules.md 中查找,当然,如果你觉得手动配置规则太麻烦,也可以直接使用 stylelint 官方的配置文档

    "extends": "stylelint-config-standard"
    

    审查完之后,我们通过 postcss-reporter 插件在控制台记录 PostCSS 的消息

    postcss-reporter config.js

    我们在 CSS 样式这部分引入了大量的 PostCSS 插件,各插件的部分功能如下所示,demo 运行效果就不在这里详细展示,童鞋们可在文章末尾下载项目代码运行测试即可

    • autoprefixer 处理浏览器私有前缀
    • cssnext 使用 CSS 未来的语法
    • precss 预处理插件包,可实现像 Less、Sass 预处理器的功能
    • postcss-color-rgba-fallbackrgba() 颜色添加一个十六进制的颜色作为降级处理,在 IE8 中是不支持 rgba() 颜色的
    • postcss-opacity 给 IE 浏览器添加滤镜属性,IE8 不支持 opacity 属性
    • postcss-pseudoelements 将伪元素的 :: 转换为 :
    • postcss-vmin 使用 vmvmin 做降级处理,IE9+
    • pixremrem 添加 px 作为降级处理,IE8+
    • postcss-import 使用 @import 合并样式表
    • cssnano 删除空格和最后一个分号,删除注释,优化字体权重,丢弃重复的样式规则,优化 calc(),压缩选择器,减少手写属性,合并规则
    • postcss-font-magician 使用自定义字体
    // styles.js
    const gulp              = require('gulp'),
          postcss           = require('gulp-postcss'),
          autoprefixer      = require('autoprefixer'),
          cssnext           = require('cssnext'),
          precss            = require('precss'),
          colorRgbaFallback = require('postcss-color-rgba-fallback')
          opacity           = require('postcss-opacity'),
          pseudoelements    = require('postcss-pseudoelements'),
          vmin              = require('postcss-vmin'),
          pixrem            = require('pixrem'),
          atImport          = require('postcss-import'),
          mqpacker          = require('css-mqpacker'),
          cssnano           = require('cssnano'),
          fontMagician      = require('postcss-font-magician'),
          config            = require('../../config').styles;
    
    gulp.task('styles',() => {
        const processors = [
            autoprefixer,
            cssnext,
            precss,
            colorRgbaFallback,
            opacity,
            pseudoelements,
            vmin,
            pixrem,
            atImport,
            mqpacker,
            cssnano,
            fontMagician
        ];
        return gulp.src(config.src)
                   .pipe(postcss(processors))
                   .pipe(gulp.dest(config.dest));
    });
    
    config.js

    我们之前介绍过 Less 在 Gulp 的用法,这里再贴一下 Sass 的部分,相对于直接将 Sass 转换成 CSS,我们还加入了 PostCSS 的一些插件

    // sass.js
    const gulp         = require('gulp'),
          postcss      = require('gulp-postcss'),
          sass         = require('gulp-sass'),
          autoprefixer = require('autoprefixer'),
          cssnano      = require('cssnano'),
          config       = require('../../config').sass;
    
    gulp.task('sass',() => {
        const processors = [
            autoprefixer,
            cssnano
        ];
        return gulp.src(config.src)
                   .pipe(sass().on('error',sass.logError))
                   .pipe(postcss(processors))
                   .pipe(gulp.dest(config.dest))
    });
    

    4. images 依赖包

    gulp-base64 插件,能够把一些小的 icon 转换成 base64 编码,因为图片转换后会比原尺寸大 30% 左右,所以不推荐将尺寸较大的图片进行 base64 编码转换

    // base64.js
    const gulp    = require('gulp'),
          base64  = require('gulp-base64'),
          config  = require('../../config').base64;
    
    gulp.task('base64', ['styles'], () => {
        return gulp.src(config.src)
                   .pipe(base64(config.options))
                   .pipe(gulp.dest(config.dest));
    });
    

    在这里,srcdest 路径相同的意义在于,我们将经过审查编译压缩过后的代码进行编码,而不会影响之前已执行的操作,若是任务执行的顺序相反,则会导致编码过后的文件无法执行后续的操作,同样的,在 build.js 中,我们也是先执行其他任务,最后才执行 base64 任务

    config.js build.js

    imagemin 插件,将目录下的所有 jpg ,png 格式的图片进行压缩,我们还利用了 gulp-cache 插件,该插件的作用是代理 Gulp 的缓存,所以我们通过利用缓存,保存已经压缩过的图片,以保证只有新建或者修改过的图片才会被压缩,最后通过 gulp-size 显示压缩过后的图片大小

    // optimize-images.js
    const gulp     = require('gulp'),
          imagemin = require('gulp-imagemin'),
          cache    = require('gulp-cache'),
          size     = require('gulp-size'),
          config   = require('../../config').optimize.images;
    
    gulp.task('optimize-images', () => {
        return gulp.src(config.src)
                   .pipe(cache(imagemin(config.options)))
                   .pipe(gulp.dest(config.dest))
                   .pipe(size())
    })
    
    config.js 运行结果

    细心的童鞋可能发现了,在 production 目录下有 4 个 optimize.js 文件,分别是对应 HTML CSS JS Images 文件,尽管我们建立这些任务,但在项目中并没有全都使用到,这里只是给大家多一种选择方式

    production 目录

    生成精灵图的插件有很多,我们在这里选择的是 sprity 插件,反正我折腾了这么多个插件之后,这一个是最友好的,我是在 Windows 7 环境下进行测试的,不管你使用的是哪个精灵图生成插件,都必须要安装图片引擎,我们在这里安装的是 sprity-gm 图片引擎,同时还需要下载安装 GraphicsMagick 和 Imagemagick 引擎,安装成功之后,电脑重启,下载地址请戳 >>> 图片引擎 | Download

    若是在 Windows 10 环境下,只需安装 sprity-lwip 图片引擎即可,Mac 环境下没有测试过

    // sprites.js
    const gulp   = require('gulp'),
          gulpif = require('gulp-if'),
          sprity = require('sprity'),
          config = require('../../config').sprites;
    
    gulp.task('sprites',() => {
          return sprity.src(config.options)
                       .pipe(gulpif('*.png', gulp.dest(config.dest.image), gulp.dest(config.dest.css)))
    })
    
    config.js sprites_1 sprites_2 sprites.css

    5. JS 依赖包

    在 CSS 部分我们使用到了 stylelint 代码审查插件,而在 JS 部分也有类似的代码审查插件 gulp-jshint,需要注意的是,gulp-jshintjshnt 要一起下载安装,其他一些插件也有类似的要求,具体以 Github 主页为准,JS 代码审查完成之后,通过 jshint-stylish 插件指定一个外部报告器

    const gulp    = require('gulp'),
          jshint  = require('gulp-jshint'),
          stylish = require('jshint-stylish'),
          config  = require('../../config').jshint;
    
    gulp.task('jshint', () => {
        return gulp.src(config.src)
                   .pipe(jshint())
                   .pipe(jshint.reporter(stylish))
    })
    
    config.js 运行结果

    通过引入 browserify 插件,使得我们可以在浏览器中加载 Node.js 模块,而 watchify 插件可以加速 browserify 的编译,而 vinyl-source-stream 把普通的 Node Stream 转换为 Vinyl File Object Stream,我们在之前的文章有提到过,Gulp 使用的 Stream 并不是普通的 Node Stream,而是一种名为 Vinyl File Object Stream 的虚拟文件格式,主要包含了路径 [path] 及内容 [contents] 两个属性,此外,我们还引入了 bundleLogger.jshandleErrors.js 两个文件,处理错误信息及记录绑定的过程,而 browserify-shim 插件则是能够帮助我们加载类似 jQuery 或 Modernizr 的非 CommonJS 文件

    // script.js
    const gulp         = require('gulp'),
          browsersync  = require('browser-sync'),
          browserify   = require('browserify'),
          source       = require('vinyl-source-stream'),
          watchify     = require('watchify'),
          bundleLogger = require('../../util/bundleLogger'),
          handleErrors = require('../../util/handleErrors'),
          config       = require('../../config').browserify;
    
    gulp.task('scripts', callback => {
        browsersync.notify('Compiling JavaScript');
        var bundleQueue = config.bundleConfigs.length;
        var browserifyThis = bundleConfig => {
            var bundler = browserify({
                cache: {}, packageCache: {}, fullPaths: false,
                entries: bundleConfig.entries,
                // Add file extentions to make optional in your requires
                extensions: config.extensions,
                debug: config.debug
            })
    
            var bundle = () => {
                bundleLogger.start(bundleConfig.outputName);
                return bundler
                      .bundle()
                      .on('error', handleErrors)
                      .pipe(source(bundleConfig.outputName))
                      .pipe(gulp.dest(bundleConfig.dest))
                      .on('finish', reportFinished)
    
            }
            if(global.isWatching) {
                bundler = watchify(bundler);
                bundler.on('update', bundle);
            }
    
            var reportFinished = () => {
                bundleLogger.end(bundleConfig.outputName)
                if(bundleQueue) {
                    bundleQueue--;
                    if(bundleQueue === 0) {
                        callback();
                    }
                }
            }
            return bundle();
        }
        config.bundleConfigs.forEach(browserifyThis);
    })
    
    // bundleLogger.js
    const gutil        = require('gulp-util'),
          prettyHrtime = require('pretty-hrtime');
    var   startTime;
    
    module.exports = {
        start: filepath => {
            startTime = process.hrtime();
            gutil.log('Bundling', gutil.colors.green(filepath));
        },
        // watch: bundleName => {
        //     gutil.log('Watching files required by', gutil.colors.yellow(bundleName));
        // },
        end: filepath => {
            var taskTime     = process.hrtime(startTime),
                prettyTime = prettyHrtime(taskTime);
            gutil.log('Bundled', gutil.colors.green(filepath), 'in', gutil.colors.magenta(prettyTime));
        }
    }
    
    // handleErrors.js
    const notify = require("gulp-notify");
    
    module.exports = function() {    
        var args = Array.prototype.slice.call(arguments);
    
        // Send error to notification center with gulp-notify
        notify.onError({
            title: "Compile Error",
            message: "<%= error.message %>"
        }).apply(this, args);
    
        // Keep gulp from hanging on this task
        this.emit('end');
    }
    

    每增加一个需要 Gulp 的 JS 文件,建议在 bundleConfigs 中进行配置

    config.js

    还需要在 packfile.json 文件里进行配置,具体代码如下

    packfile.json

    喜欢使用 ES6 的童鞋一定不能忘了引入 gulp-babel 插件

    // babel.js
    const gulp       = require('gulp'),
          babel      = require('gulp-babel'),
          uglify     = require('gulp-uglify'),
          browserify = require('browserify'),
          source     = require('vinyl-source-stream'),
          config     = require('../../config').babel;
    
    gulp.task('babel', () => {
        gulp.src(config.src)
            .pipe(babel(config.options))
            .pipe(uglify())
            .pipe(gulp.dest(config.dest))
    })
    
    config.js

    6. Browsersync

    browser-sync 插件,其作用是能让浏览器实时、快速响应 HTML、CSS、JS、Sass、Less 等文件更改并自动刷新页面,更重要的是,可以同时在 PC、平板、手机等设备下进项调试,我们可以使用 Browsersync 提供的静态服务器,对我们的 html 文件进行测试,也可以使用代理服务器,来对 php 文件进行测试,而我们在这里使用的静态服务器

    // browser-sync.js
    const gulp        = require('gulp'),
          browsersync = require('browser-sync'),
          config      = require('../../config').browsersync;
    
    gulp.task('browsersync', ['build'], () => {
        browsersync.init(config.development);
        // browsersync.init(config.production);
    })
    
    config.js 运行结果 思维导图

    该章节的内容到这里就全部结束了,源码及思维导图我已经发到了 GitHub Gulp_Niangao 上了,这里还有一篇 Gulp 入门指南,有需要的同学可自行下载

    End of File

    行文过程中出现错误或不妥之处在所难免,希望大家能够给予指正,以免误导更多人,最后,如果你觉得我的文章写的还不错,希望能够点一下喜欢关注,为了我能早日成为简书优秀作者献上一发助攻吧,谢谢!^ ^

    相关文章

      网友评论

        本文标题:从零开始构建你的 Gulp

        本文链接:https://www.haomeiwen.com/subject/pnoplxtx.html