定义
官方定义是the streaming build system,基于流的构建系统。使用文件流目的是实现构建管道的概念,这样使用插件的时候,就可以有一种比较统一的方式。
基本使用https://gulpjs.com/
组合任务
// series 组合串行任务
// parallel 同步执行任务
const { series, parallel } = require('gulp')
const task1 = done => {
setTimeout(() => {
console.log('task1 working~')
done()
}, 1000)
}
const task2 = done => {
setTimeout(() => {
console.log('task2 working~')
done()
}, 1000)
}
const task3 = done => {
setTimeout(() => {
console.log('task3 working~')
done()
}, 1000)
}
// 让多个任务按照顺序依次执行,编译和部署是串行,可以串行执行
exports.foo = series(task1, task2, task3)
// 让多个任务同时执行,css和js可以同步执行
exports.bar = parallel(task1, task2, task3)
异步任务
const fs = require('fs')
// 1. 通过回调方式执行,错误优先
exports.callback = done => {
console.log('callback task')
done()
}
// 多任务同时执行时,报错后,后面的任务不会再次执行
exports.callback_error = done => {
console.log('callback task')
done(new Error('task failed'))
}
// 2. promise方式
exports.promise = () => {
console.log('promise task')
// 需要return Promise对象,不需要返回任何的值,因为gulp会忽略所有返回参数
return Promise.resolve()
}
exports.promise_error = () => {
console.log('promise task')
return Promise.reject(new Error('task failed'))
}
// 3. es7 提供的async,node版本为8以上可以使用
const timeout = time => {
return new Promise(resolve => {
setTimeout(resolve, time)
})
}
exports.async = async () => {
await timeout(1000)
console.log('async task')
}
// 4. 较为常见的stream方式,读取文件流,返回stream对象
exports.stream = () => {
// 读取文件文件流
const read = fs.createReadStream('yarn.lock')
// 写入文件流
const write = fs.createWriteStream('a.txt')
// 起到文件复制的作用,从一个流 流到另一个流,将read的写入write中
read.pipe(write)
//结束时机是read end事件触发的时候,read都有一个end事件
return read
}
// 模拟gulp监听end事件
exports.stream = done => {
const read = fs.createReadStream('yarn.lock')
const write = fs.createWriteStream('a.txt')
read.pipe(write)
// gulp接收到了readStream,会注册一个end事件,去监听这个任务的结束
read.on('end', () => {
done()
})
}
构建过程核心原理
读入:读取文件、 加工:压缩文件 、输出:写入文件
屏幕快照 2021-06-20 22.36.04.png
const fs = require('fs')
const { Transform } = require('stream')
exports.default = () => {
// 文件读取流
const readStream = fs.createReadStream('normalize.css')
// 文件写入流
const writeStream = fs.createWriteStream('normalize.min.css')
// 文件转换流
const transformStream = new Transform({
// 核心转换过程
transform: (chunk, encoding, callback) => {
const input = chunk.toString()
const output = input.replace(/\s+/g, '').replace(/\/\*.+?\*\//g, '') // 将css注释删掉
// node的callback是错误优先的
callback(null, output)
}
})
// return 出去,gulp就可以根据流的状态判断任务是否完成
return readStream
.pipe(transformStream) // 转换
.pipe(writeStream) // 写入
}
gulp 文件操作API
对比node的文件操作API,gulp的更加强大和更容易使用,对于文件的转换流,一般都是使用插件来完成。流程一般为通过gulp的src, dest两个方法,来实现文件的读写。
const { src, dest } = require('gulp')
const cleanCSS = require('gulp-clean-css')
const rename = require('gulp-rename')
exports.default = () => {
return src('src/*.css')
.pipe(cleanCSS())
.pipe(rename({ extname: '.min.css' }))
.pipe(dest('dist'))
}
gulp 实战
- gulp样式编译。
- 安装gulp-sass,内部会自动安装node-sass,会对C++有依赖
const {src, dest} = require('gulp')
const sass = require('gulp-sass') // 基本每个插件都返回一个函数
const style = () => {
return src('src/assets/styles/*.scss', {base: 'src'}) // 当使用 dest() 写入文件系统时,将从输出路径中删除 base ,以保留目录结构。这里指定下base是src。
.pipe(sass({outputStyle: 'expanded'})) // sass默认不处理下滑线的scss文件,会认为下划线的是主文件(main.scss)依赖的文件,所以不会转换会被忽略掉,例如_icons.scss
.pipe(dest('dist')) // 以覆盖的方式写入,不会删掉以前的文件
}
module.exports = {
style
}
- 脚本编译(es6)
- 安装gulp-babel、@babel/core、@babel/preset-env
const {src, dest} = require('gulp')
const babel = require('gulp-babel') // 基本每个插件都返回一个函数
// 单独安装gulp-babel只提供一个转换平台,具体转换流程是依赖插件的,但这个只是帮助调用@babel/core里面的api,所以要安装其他babel插件
const script = () => {
return src('src/assets/scripts/*.js', {base: 'src'}) // 当使用 dest() 写入文件系统时,将从输出路径中删除 base ,以保留目录结构。这里指定下base是src。
.pipe(babel({presets: ['@babel/preset-env']})) // 要是没有写@babel/preset-env,则有转换无效的感觉
.pipe(dest('dist')) // 以覆盖的方式写入,不会删掉以前的文件
}
module.exports = {
script
}
- 页面模板编译
- 安装gulp-swig
const {src, dest} = require('gulp')
const swig = require('gulp-swig') // 基本每个插件都返回一个函数
const data = {
menus: [
{
name: 'Home',
icon: 'aperture',
link: 'index.html'
},
{
name: 'Features',
link: 'features.html'
},
{
name: 'About',
link: 'about.html'
},
{
name: 'Contact',
link: '#',
children: [
{
name: 'Twitter',
link: 'https://twitter.com/w_zce'
},
{
name: 'About',
link: 'https://weibo.com/zceme'
},
{
name: 'divider'
},
{
name: 'About',
link: 'https://github.com/zce'
}
]
}
],
pkg: require('./package.json'),
date: new Date()
}
// 注意:有时直接修改模板,不会立即热更新,主要是因为swig模板引擎缓存的机制导致页面不会变化
// 此时,需要额外将swig选项中的cache设置为false
const page = () => {
return src('src/*.html', { base: 'src' })
.pipe(plugins.swig({ data, defaults: { cache: false } })) // 防止模板缓存导致页面不能及时更新
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true }))
}
module.exports = {
page
}
- 图片压缩和处理图片字体,因为图片包含一些二进制信息,在我们的生产环境是不需要用到的。
- 安装gulp-imagemin(内部依赖c++的模块,需要在GitHub上下载一些程序集)
const {src, dest} = require('gulp')
const imagemin = require('gulp-imagemin') // 基本每个插件都返回一个函数
const image = () => {
return src('src/assets/images/**', { base: 'src' })
.pipe(imagemin())
.pipe(dest('dist'))
}
const font = () => {
return src('src/assets/fonts/**', { base: 'src' })
.pipe(imagemin())
.pipe(dest('dist'))
}
module.exports = {
image,
font
}
- 删除文件和其他文件
- 安装del ,这个不是gulp插件,但是能在gulp里面使用,因为gulp可以通过自定义实现一个插件
的形式,来在gulp里面的pipe使用,而del为一个promise
const {src, dest} = require('gulp')
const del = require('del')
const extra = () => {
return src('public/**', { base: 'public' })
.pipe(dest('dist'))
}
const clean = () => {
return del(['dist'])
}
- 自动加载插件
- 安装gulp-load-plugins,名称是gulp-sass,则通过plugins.sass,如果是gulp-sass-dev,则为gulp.sassDev
- 开发服务器:热更新开发服务器
- 安装browser-sync,支持在代码修改过后,自动热更新,不是gulp插件,只是通过gulp来管理
const browserSync = require('browser-sync')
// 通过create方法创建服务
const bs = browserSync.create()
// 启动服务
// 单独定义一个任务启动
const serve = () => {
bs.init({
server: {
notify: false,
port: 3002,
open: false, // 是否自动打开浏览器
// 网页跟目录,填写网页加工过后的根目录
baseDir: 'dist',
// 优先与basedir,先找routes里面配置的文件,没有再找baseDir的
routes: {
'/node_modules': 'node_modules',
},
},
})
}
module.exports = {
serve
}
- 监听变化以及构建优化
- 使用gulp的watch
- 使用browser-sync启动服务
- 先在bs.init 下的files配置写入配置
const serve = () => {
bs.init({
server: {
notify: false,
port: 3002,
files: 'dist/**', // 用来去被browsersync监听的文件,可以配置通配符,指定被监听的文件
open: false, // 是否自动打开浏览器
// 网页跟目录,填写网页加工过后的根目录
baseDir: 'dist',
// 优先与basedir,先找routes里面配置的文件,没有再找baseDir的
routes: {
'/node_modules': 'node_modules',
},
},
})
}
- 监听src下面文件的变化
const { watch } = require('gulp')
const serve = () => {
watch('src/assets/styles/*.scss', style)
watch('src/assets/scripts/*.js', script)
watch('src/*.html', page)
// watch('src/assets/images/**', image)
// watch('src/assets/fonts/**', font)
// watch('public/**', extra)
// 在开发阶段一般不会经常改,所以不需要监听以下几个文件
// 只要在发布上线前,编译一遍就好了
// 这样开发环境就减少了一起编译
watch(['src/assets/images/**', 'src/assets/fonts/**', 'public/**'], bs.reload) // 三种文件变化过后,执行reload方法
bs.init({
server: {
notify: false,
port: 3002,
files: 'dist/**', // 用来去被browsersync可以配置通配符,指定被监听的文件
open: false, // 是否自动打开浏览器
// 网页跟目录,填写网页加工过后的根目录
baseDir: 'dist',
// 优先与basedir,先找routes里面配置的文件,没有再找baseDir的
routes: {
'/node_modules': 'node_modules',
},
},
})
}
- useref文件引用处理
- 安装gulp-useref(引用关系),gulp-if
- gulp-useref这是一款可以将HTML引用的多个CSS和JS合并起来,减小依赖的文件个数,从而减少浏览器发起的请求次数。gulp-useref根据gulp的构建注释将HTML中需要合并压缩的区块找出来,对区块内的所有文件进行合并,然后删掉构建注释。注意:它只负责合并,不负责压缩!,如果需要做其他操作,可以配合gulp-if插件使用
// 结果放在dist,构建放在temp,目的是防止读写冲突,写不进文件
const useref = () => {
return (
src('temp/*.html', { base: 'temp' }) // 这里找的是打包后的目录下的文件,temp是打包后的文件夹
.pipe(plugins.useref({ searchPath: ['temp', '.'] })) // 确定注释里面的文件,可以在哪个目录文件找,这里是指定temp文件夹找,找不到就在根目录下找
// useref处理后会有三种文件类型html js css
.pipe(plugins.if(/\.js$/, plugins.uglify())) // gulp-if用来判断是哪种类型的文件
.pipe(plugins.if(/\.css$/, plugins.cleanCss()))
.pipe(
plugins.if(
/\.html$/,
plugins.htmlmin({
collapseWhitespace: true, // 自动压缩空白符号和换行符
minifyCSS: true, // 压缩内敛的css
minifyJS: true, // 压缩内敛的js
})
)
)
.pipe(dest('dist'))
)
}
- 补充
- 开发leader写好了构建流程后,在pacakge.json文件里写好外露的api
- 在.gitngnore里写忽略文件
"scripts": {
"clean": "gulp clean",
"build": "gulp build",
"develop": "gulp develop"
},