本篇博文的内容根据 Introduction to Gulp.js 系列文章 拓展而来,其代码、依赖包及目录结构部分均有所更改,更多详细内容,敬请参考原文及作者 Github
我们在上一篇博文 Gulp 前端自动化构建工具 中,已经对 Gulp 有了初步的了解,我们通过将所有任务写到 gulpfile.js
文件中进行编译,这当然是最直观的方法,但当我们需要执行的任务过多时,gulpfile.js
文件就会变的特别的巨大,这很不利于我们之后的维护及修改,所以我们要做的第一件事就是将 gulpfile.js
文件进行分割,分成一个个小的任务文件,每一个文件只完成特定的任务,这也是我们常说的模块化处理,每一任务文件不与其他文件产生直接交互,并通过赋值的方式在文件内部调用全局变量,下图是我们整个项目的目录结构,在文章的接下来部分,将会给大家详细讲解
文件结构
我们先来简单介绍下我们的文件目录结构,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
文件夹下有一个 config.js
文件,主要是各任务的路径匹配及文件配置,具体如下图所示
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
文件进行更改,在这里只是提供一个示例方法
CSS 依赖包
接下来我将根据作用的文件类型不同,来对所引入的依赖包来作简单的介绍,而关于各插件的更多用配置及用法,还请查看相应插件的 Github 主页
代码语言:javascript复制// 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)
]))
})
可以看到,我们除了引入 stylelint
和 postcss-reporter
这两个插件之外,还引入了 gulp-postcss
这一插件集合,在这里想要跟大家介绍的是,PostCSS 是一个使用 JS 解析样式的插件集合,它可以用来审查 CSS 代码,也可以增强 CSS 的语法(比如变量和混合宏),还支持未来的 CSS 语法、行内图片等等,而本文所使用到的大部分 CSS 插件,均是来自 PostCSS,关于更多的 PostCSS 的介绍,而通过 w3cplus 的 PostCSS 深入学习系列文章 进行学习
stylelint 是一个代码审查插件,除了审查 CSS 语法外,还能审查类 CSS 语法,帮助我们审查出重复的 CSS 样式、不规范的代码、无效颜色值、无意义的浏览器前缀以及我们所配置的一些审查规则,我们可以根据自身项目的需求来设置不同的规则
rules
使用 0, 1, 2 来代表规则启用状态不同,具体的规则可在 Rules.md 中查找,当然,如果你觉得手动配置规则太麻烦,也可以直接使用 stylelint 官方的配置文档
"extends": "stylelint-config-standard"
审查完之后,我们通过 postcss-reporter 插件在控制台记录 PostCSS 的消息
我们在 CSS 样式这部分引入了大量的 PostCSS 插件,各插件的部分功能如下所示,demo 运行效果就不在这里详细展示,童鞋们可在文章末尾下载项目代码运行测试即可
- autoprefixer 处理浏览器私有前缀
- cssnext 使用 CSS 未来的语法
- precss 预处理插件包,可实现像 Less、Sass 预处理器的功能
- postcss-color-rgba-fallback 给
rgba()
颜色添加一个十六进制的颜色作为降级处理,在 IE8 中是不支持rgba()
颜色的 - postcss-opacity 给 IE 浏览器添加滤镜属性,IE8 不支持
opacity
属性 - postcss-pseudoelements 将伪元素的
::
转换为:
- postcss-vmin 使用
vm
为vmin
做降级处理,IE9 - pixrem 给
rem
添加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));
});
我们之前介绍过 Less 在 Gulp 的用法,这里再贴一下 Sass 的部分,相对于直接将 Sass 转换成 CSS,我们还加入了 PostCSS 的一些插件
代码语言:javascript复制// 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))
});
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));
});
在这里,src
和 dest
路径相同的意义在于,我们将经过审查编译压缩过后的代码进行编码,而不会影响之前已执行的操作,若是任务执行的顺序相反,则会导致编码过后的文件无法执行后续的操作,同样的,在 build.js
中,我们也是先执行其他任务,最后才执行 base64 任务
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())
})
细心的童鞋可能发现了,在 production
目录下有 4 个 optimize.js
文件,分别是对应 HTML CSS JS Images 文件,尽管我们建立这些任务,但在项目中并没有全都使用到,这里只是给大家多一种选择方式
生成精灵图的插件有很多,我们在这里选择的是 sprity 插件,反正我折腾了这么多个插件之后,这一个是最友好的,我是在 Windows 7 环境下进行测试的,不管你使用的是哪个精灵图生成插件,都必须要安装图片引擎,我们在这里安装的是 sprity-gm 图片引擎,同时还需要下载安装 GraphicsMagick 和 Imagemagick 引擎,安装成功之后,电脑重启,下载地址请戳 >>> 图片引擎 | Download
若是在 Windows 10 环境下,只需安装 sprity-lwip 图片引擎即可,Mac 环境下没有测试过
代码语言:javascript复制// 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)))
})
JS 依赖包
在 CSS 部分我们使用到了 stylelint 代码审查插件,而在 JS 部分也有类似的代码审查插件 gulp-jshint,需要注意的是,gulp-jshint 和 jshnt 要一起下载安装,其他一些插件也有类似的要求,具体以 Github 主页为准,JS 代码审查完成之后,通过 jshint-stylish 插件指定一个外部报告器
代码语言:javascript复制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))
})
通过引入 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.js
和 handleErrors.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);
})
代码语言:javascript复制// 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));
}
}
代码语言:javascript复制// 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
中进行配置
还需要在 packfile.json
文件里进行配置,具体代码如下
喜欢使用 ES6 的童鞋一定不能忘了引入 gulp-babel 插件
代码语言:javascript复制// 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))
})
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);
})
该章节的内容到这里就全部结束了,源码及思维导图我已经发到了 GitHub Gulp_Niangao 上了,有需要的同学可自行下载