Grunt与Gulp的不同
自nodeJS登上前端舞台,自动化构建变得越来越流行。目前最流行的当属grunt和gulp,这两个光看名字挺像,功能也差不多,不过gulp能在grunt这位大哥如日中天的境况下开辟出自己的一片天地,有着她独到的优点。
- 易用 Gulp相比Grunt更简洁,而且遵循代码优于配置策略,维护Gulp更像是写代码。
- 高效 Gulp相比Grunt更有设计感,核心设计基于Unix流的概念,通过管道连接,不需要写中间文件。
- 高质量 Gulp的每个插件只完成一个功能,这也是Unix的设计原则之一,各个功能通过流进行整合并完成复杂的任务。例如:Grunt的imagemin插件不仅压缩图片,同时还包括缓存功能。他表示,在Gulp中,缓存是另一个插件,可以被别的插件使用,这样就促进了插件的可重用性。目前官方列出的有673个插件。
- 易学 Gulp的核心API只有5个,掌握了5个API就学会了Gulp,之后便可以通过管道流组合自己想要的任务。
- 流 使用Grunt的I/O过程中会产生一些中间态的临时文件,一些任务生成临时文件,其它任务可能会基于临时文件再做处理并生成最终的构建后文件。而使用Gulp的优势就是利用流的方式进行文件的处理,通过管道将多个任务和操作连接起来,因此只有一次I/O的过程,流程更清晰,更纯粹。
- 代码优于配置 维护Gulp更像是写代码,而且Gulp遵循CommonJS规范,因此跟写Node程序没有差别。
Gulp作为当下最流行的前端构建系统,其核心特点就是高效,易用。
因为使用Gulp非常简单:
- 现在项目中安装Gulp的开发依赖,然后在根目录添加Gulpfile.js文件用于编写构建任务;
- 然后在命令行终端使用Gulp模块的 cli 去运行这些任务
Gulp的基本使用
1.初始化package.json
yarn init
2.安装Gulp模块 作为开发依赖 , 安装gulp的时候会同时安装gulp-cli的模块(也就是说在node_modules下面的bin目录中会有一个gulp.cmd命令后续可以通过这个命令运行构建任务)
yarn add gulp --dev
3.创建gulpfile.js文件,定义一些需要gulp执行的构建任务
- 因为这个文件运行在node环境中,所以它可以使用commonJs的规范
- 这个文件定义构建任务的方式就是------>通过导出函数成员去定义(通过exports导出)
- 最新的gulp当中取消了同步模式,约定每一个任务都必须是异步任务。当我们执行完任务后必须通过回调函数标记任务已经完成(函数成员的形参得到)
// gulp的入口文件
// 这个文件定义构建任务的方式就是------>通过导出函数成员去定义
//gulpfile中就相当于定义了一个foo 的任务
exports.foo = (done)=>{
console.log('foo workIng!');
done()
}
//gulpfile中于定义默认任务 ,函数(任务)名称是default
exports.default = (done)=>{
console.log('default workIng!');
done()
}
// gulp4.0以前,注册gulp任务的方式需要通过gulp模块;里面的方法去实现
const gulp = require('gulp')
gulp.task('bar' , (done)=>{
console.log('bar workIng!');
done()
})
yarn gulp foo //执行foo任务
yarn gulp //执行默认任务
yarn gulp bar //执行bar任务
Gulp的创建组合任务(并行任务和串行任务)
- 这里首先定义了三个函数,可以把这种未被导出的成员函数理解为私有任务
- 这三个成员函数并不能直接被gulp运行,可以通过gulp模块提供的series、parallel这两个API把它们组合成一个组合任务
- 创建一个串行任务foo:这个任务通过series函数创建
- 创建一个并行任务bar:这个任务通过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);
}
// series是一个函数,可以接受任意个数的参数,每个参数都可以是一个任务
// foo开始后,这三个任务会按顺序依次执行
exports.foo = series(task1,task2,task3)
// 创建bar成员,这个成员用parallel去包装一下 task1,task2,task3
// bar开始后,这三个任务会同时启动,并行执行
exports.bar = parallel(task1,task2,task3)
Gulp的异步任务
1.通过回调的方式去解决
// 任务的函数中接收一个回调函数形参
exports.callBack = (done)=>{
console.log("callBack task!");
//在任务执行完毕后调用这个回调函数,从而通知gulp任务完成
// 这个回调函数和node中的回调函数一个标准,都是一种叫做错误优先的回调函数
// 当我们在执行过程中报出一个错误,阻止剩下任务执行的时候,可以通过给回调函数传入一个参数是错误对象就可以了
// done();
done( new Error('task failed!') );
// 命令行执行中会报出这个错误,而且如果是多个任务同时执行,那后续的任务也不会再工作了
}
2.Promise
exports.promise = ()=>{
console.log("promise task!");
// 在任务的执行函数中return 一个promise对象
// resolve中不需要返回任何值,因为gulp会忽略掉它
// return Promise.resolve();
// 如果return 的是reject,那么gulp会认为它是一个失败的任务
// 同样也会终止后续任务的执行
return Promise.reject( new Error('task failed!') );
}
async await
const timeout = (time)=>{
return new Promise((resolve,reject)=>{
setTimeout(resolve,time)
})
}
exports.async = async ()=>{
const res = await timeout(1000)
console.log( 'async task!' )
}
第一点和第二点是JS中处理异步函数的常见方式,在gulp中都被支持。除了这些方式gulp中还支持另外几种方式。
3.stream
因为构建系统大都是在处理文件, 所以stream这种方式是最为常用的一种。
- 在任务函数中需要返回一个stream对象
- 例如在fs模块中提供的 createReadStream 创建一个读取文件的文件流对象
- 通过writeReadStream 创建一个写入文件的文件流对象
- 通过pipe的方式导入到writeReadStream 中,文件复制
- 把readStream 给return出来
const fs = require("fs");
exports.stream = ()=>{
const readStream = fs.createReadStream('package.json')
const writeStream = fs.createWriteStream('temp.txt')
readStream.pipe(writeStream);
return readStream;
}
执行yarn gulp stream可以发现这个任务也是可以正常开始正常结束,它结束的时机就是readStream 在 end的时候 ,因为stream 中都有一个end 事件,一旦文件读取完成后就会触发end事件,从而gulp就知道任务已完成
模拟一下gulp中做的事情:
- 接收到readStream后为它注册了一个end事件;
- 在end事件中结束了任务的执行
- 执行stream2,可以发现任务也可以正常结束,这意味着其实gulp也只是注册这个事件去监听任务的结束罢了。
exports.stream2 = (done)=>{
const readStream = fs.createReadStream('package.json')
const writeStream = fs.createWriteStream('temp.txt')
readStream.pipe(writeStream);
readStream.on('end',()=>{
done()
})
}
Gulp构建过程核心工作原理
构建过程大多数情况下都是将文件读取出来,进行转换过后写入另一个位置。
接下来通过原始的node文件流API模拟实现一下这个过程。
- 创建文件的读取流,
- 导入stream模块的transform类,这个类型可以创建文件转换流对象
- 创建文件的写入流
- 把读取到的数据导入转换流进行转换后 最后导入到写入文件流中,完成文件的转换构建过程
- 最后通过return的方式把 steam 返回,这样gulp就可以根据流的状态判断任务是否执行完成
const fs = require("fs");
const { Transform } = require("stream");
exports.default = ()=>{
// 文件读取流
const read = fs.createReadStream('normallize.css');
// 文件写入流
const write = fs.createWriteStream('normallize.min.css');
// 文件转换流
// Transform中需要指定一个transform属性,这个属性就是转换流的核心转换过程
const transform = new Transform({
transform:(chunk,encoding,callBack)=>{
// chunk可以获得读取流中读取到的内容,它读出来的是一个字节数组,可以通过toString的方式将它转换为字符串
const input = chunk.toString(); //input就是读取到文件的文本内容
const output = input.replace(/\s+/g,'').replace(/\/\*.+?\*\//g, '') //通过replace把空白字符、css注释全部替换掉,赋值给output变量
// 在callBack中将转换后的结果返回出去
callBack(null,output) //callBack是一个错误优先的回调函数,第一个参数是错误对象
}
})
// 把读取到的数据导入转换流进行转换后 最后导入到写入文件流中,完成文件的转换构建过程
read
.pipe(transform)
.pipe(write)
return read;
}
这就是gulp 中一个常规任务的核心工作过程:
输入(读取文件) -> 加工(转换流的转换逻辑) -> 输出(写入文件)
Gulp的官方定义就是 The streaming build system,基于流的构建过程, 因为gulp希望实现一个构建管道的概念,在后续做一些扩展插件的话就会有一个很统一的方式。
Gulp文件操作API
Gulp中也为我们提供了专门用于去创建读取流和写入流的API, 更强大也更易于使用。 至于负责文件加工的转换流一般是通过独立的插件来提供。
所以一般通过Gulp创建构建任务的工作流程 :先通过src方法去创建读取流,再通过插件提供的转换流来实现文件加工,最后通过Gulp提供的dist方法创建写入流从而写入到目标文件
src()
接受 glob 参数,并从文件系统中读取文件然后生成一个 Node 流(stream)。它将所有匹配的文件读取到 内存 中并通过流(stream)进行处理。dest()
可以用在管道(pipeline)中间用于将文件的中间状态写入文件系统- 通过插件提供的转换流来实现文件加工
// 安装gulp-clean-css插件 , 这个插件提供了压缩css代码的转换流
const cleancss = require("gulp-clean-css")
// 安装gulp-rename的插件 , 这个插件
const rename = require('gulp-rename')
const { src , dest } = require("gulp");
exports.default = ()=>{
// 相当于node原始api,gulp提供的api更强大一些,,在这里可以使用通配符的方式去匹配批量文件
const read = src('src/normallize.css')
const write = dest('dist')
read
.pipe(cleancss()) //文件流先pipe到cleancss文件转换流中进行转换
.pipe(rename({
extname:'.min.css' //extname 用于指定重命名的扩展名为.min.css
})) //文件流先pipe到rename文件转换流中进行转换
.pipe(write)
// 将创建的读取流return 出去,这样gulp就可以控制我们的任务完成
return read;
}
这种通过src去pipe 到 一个或多个插件转换流,再pipe到写入流这样的过程,就是我们使用gulp的常规过程。
Gulp自动化构建案例
一起来学习下如何使用gulp 来完成一个网页应用的 自动化构建工作流。
style样式编译(sass)
安装:gulp-sass
yarn add gulp-sass --dev
注意:gulp-sass会自动帮我们安装node-sass的核心转换模块
const { src, dest, parallel, series, watch } = require('gulp')
const sass = require('gulp-sass')
const style = ()=>{
// src读取流 //base转换时基准路径,保留src后面原始目录结构
return src('src/assets/styles/*.scss' , {base:'src'})
.pipe(sass({outputStyle:'expanded'})) //插件提供的一般都是方法,调用返回一个文件转换流
.pipe(dest('dist'))
}
module.exports = {
style
}
js 脚本编译 ( babel )
安装:gulp-babel
注意: gulp-babel这个插件只是帮我们唤醒babel-core模块中的转换过程 ,所以我们需要手动安装
yarn add gulp-htmlmin gulp-uglify gulp-clean-css --dev
const babel = require('gulp-babel')
const script = ()=>{
// src读取流 //base转换时基准路径,保留src后面的路径
return src('src/assets/scripts/*.js' , {base:'src'})
//babel默认只是ECMAScript的一个转换平台,具体做转换的其实是babel它内部的一些插件
// 而preset就是一些插件的集合,比如preset-env就是最新的ES最新的一些整体特性的打包,使用它就会把所有特性全部做转换
// 也可以安装对应的babel转换插件,然后指定对应的转换插件,它就会只转换对应的特性
.pipe(babel({presets:[ '@babel/preset-env' ] }))
.pipe(dest('dist'))
}
页面模板编译
模板文件就是html文件,为了把html中一些数据抽象出来使用了模板引擎,这里使用的模板引擎是swig。
安装 swig的转换插件:
yarn add gulp-swig --dev
const swig = require('gulp-swig')
const page = () => {
return src('src/*.html', { base: 'src' })
.pipe(swig({ data })) //data是页面中需要用到的数据
.pipe(dest('dist'))
}
module.exports = {
page
}
执行: yarn gulp page
parallel 将三个任务进行组合,同时编译
const { src, dest, parallel } = require('gulp')
const compile = parallel(style, script, page)
module.exports = {
compile
}
执行: yarn gulp compile
有了以上三个任务,那么src下面主体需要去转换编译的事情就完成了。
图片和字体文件转换
上面完成了页面文件、脚本文件和样式文件的编译,还需要处理一下图片和字体文件。
安装:gulp-imagemin (无损的图片压缩,只是删除了一些元数据的信息)
imagemin 插件内部依赖的模块同样是一些通过c++完成的模块,就需要去下载二进制的程序集。
const image = ()=>{
return src('src/assets/images/**' , {base:'src'})
.pipe(imagemin())
.pipe(dest('dist'))
}
const font = ()=>{
return src('src/assets/fonts/**' , {base:'src'})
// 有的时候字体文件夹下可能会有svg文件也可以通过imagemin压缩一下,对于imagemin不能处理的文件它不会去处理
.pipe(imagemin())
.pipe(dest('dist'))
}
const compile = parallel( style , script , html , image , font )
module.exports = {
compile
}
其它文件及文件清除
上面src文件夹下的文件就都处理完成了,现在拷贝一下public目录下的文件。
// 额外的文件直接通过拷贝的方式拷贝过去就可以了
const extra = ()=>{
return src('public/**' , {base:'public'})
.pipe(dest('dist'))
}
// compile完成src文件夹下的所有文件且完成转换
const compile = parallel( style , script , html , image , font )
// build通过parallel在组合的基础上再次组合将compile任务和extra进行组合,后续可以使用build完成所有文件的构建
const build = parallel( compile , extra )
module.exports = {
build
}
安装del:
yarn add del --dev
它不是gulp的插件,但是在gulp中可以使用,因为gulp并不只是通过src找文件流然后最终pipe到dist中。
del可以帮我们删除指定文件,且它是一个promise方法
// 有了del后可以通过del指定一个数组,放入任意的文件路径
const clean = ()=>{
// 就不再是return一个src之类的东西
// 它返回的是一个promise,意味着在delete完成后gulp可以标记clean任务完成
return del(['dist'])
}
自动加载插件
随着我们的构建任务越来越复杂,使用到的插件也越来越多,如果都是手动的加载插件的话,那么require的操作会非常多,在这里可以通过gulp-load-plugins插件来解决这个小问题。
安装:
yarn add gulp-load-plugins --dev
// 通过load-plugins提供的api自动去加载全部的plugin
const loadPlugins = require('gulp-load-plugins')
// plugins是一个对象,所有的插件都会自动成为这个对象下面的属性
const plugins = loadPlugins()
开发服务器
除了对文件的构建操作外,还需要一个开发服务器,用于在开发时调试应用。使用Gulp去启动管理这个开发服务器。这样就可以配合其他构建任务去实现修改文件后自动编译并且自动更新浏览器页面。
这个模块可以提供给我们一个开发服务器。相比较普通使用express创建的服务器它更强大的功能,它支持代码更改之后的自动热更新到浏览器中。
yarn add browser-sync --dev
const browserSync = require('browser-sync')
// 它会自动创建一个开发服务器
const bs = browserSync.create()
// 将开发服务器单独定义到 serve 的任务中去启动
// 启动这个任务会自动唤醒浏览器并打开页面
const serve = ()=>{
bs.init({
notify:false,
port:'2080',
// open:false, //启动服务时 取消自动打开页面
files: 'dist/**', // 文件修改之后 自动更新 浏览器 //热更新
server:{
baseDir:'dist', //server中要指定一下网站的根目录
routes:{
// 页面中有些文件路径指向,优先于baseDir的配置
'/node_modules':'node_modules'
}
}
})
}
监视文件变化
有了开发服务器后,就要考虑src目录下的源代码修改后自动编译,这个需要借助gulp提供的另一个API:watch
watch会监视一个文件路径的通配符,根据这些文件的变化决定是否要执行一些任务。
const serve = ()=>{
// watch接收两个参数,第一个参数是globs--通配符(所有产生构建任务的路径),第二个参数就是要执行的任务
watch( 'src/assets/styles/*.scss' , style )
watch( 'src/assets/scripts/*.js' , script )
watch( 'src/*.html' , html )
//watch( 'src/assets/images/*.*' , image )
//watch( 'src/assets/fonts/**' , font )
//watch( 'public/**' , extra )
//对于监听图片,字体和其他一些静态文件,在开发阶段没有什么意义,会减慢构建速度
// 这样就可以实现文件修改后自动编译到dist目录,然后自动同步到浏览器
bs.init({
notify:false,
port:'2080',
// open:false, //启动服务时 取消自动打开页面
files: 'dist/**', // 文件修改之后 自动更新 浏览器
server:{
baseDir:['dist','src','public'], //server中要指定一下网站的根目录
routes:{
// 页面中有些文件路径指向,优先于baseDir的配置
'/node_modules':'node_modules'
}
}
})
}
这样就可以实现之前的设想:文件修改后自动编译到dist目录,然后自动同步到浏览器。
构建优化
-
考虑到如果serve 之前不存在 编译后的dist目录,所以需要增加一个组合任务:develop,去执行一下compile任务:
const develop = series(compile , serve)
-
之前我们compile里面的 image,font任务可以拿掉,因为html,scss,js文件必须要经过编译后才能在浏览器环境运行,而图片、字体还有那些静态资源在开发阶段则不需要进行编译,可以直接请求源文件,这样在开发阶段就可以减少一次构建过程。bs.init的baseDir配置项可以加入这些源文件路径
-
image,font任务就可以加入build任务中最后在build项目时统一进行编译
//compile在上线之前也会用到,不过它是一个子任务,主要在开发阶段使用 const compile = parallel( style , script , html ) //build任务就是上线之前要执行的任务 const build = series( clean, parallel( compile , extra ) ) const develop = series(compile , serve)
-
图片、字体还有那些静态资源在开发阶段也要进行热更新:在serve任务中再添加一个watch监听它们的源文件
watch( [ 'src/assets/images/**', 'src/assets/fonts/**', 'public/**' ] ,bs.reload) //监听这三种文件变化后,调用bs.reload方法,reload也可以理解为一个任务,因为在gulp中一个任务就是一个函数
这样develop任务就以一个最小的代价把应用跑起来了,上线之前执行build任务以最大模型方式把所有的任务都执行一次。
useref文件引用处理(引用关系)
html文件中会有一些引用node_modules 文件中的一些依赖,这些文件没有被我们打包到dist目录中。此时如果将dist目录部署上线后会出现问题,因为找不到这些文件。在开发阶段没有出现问题是因为在开发服务器中做了录音映射。
可以借助useref插件解决这个问题,它会自动帮我们解决html中的构建注释,比如:
它会自动将注释的开始标签和结束标签中的文件打包到一个文件中。
<!-- build:css assets/styles/vendor.css -->
<link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.css">
<!-- endbuild -->
<!-- build:css assets/styles/main.css -->
<link rel="stylesheet" href="assets/styles/main.css">
<!-- endbuild -->
安装 yarn add gulp-useref --dev
const useref = () => {
//这个时候找的html是dist目录下的html
return src('dist/*.html', { base: 'dist' })
//把创建的读取流pipe到useref插件,这个插件会被自动加载进来,useref插件会创建一个转换流
//这个转换流会把html中的构建注释做一个转换:searchPath找到这些文件dist和根目录
.pipe(plugins.useref({ searchPath: ['dist', '.'] }))
.pipe(dest('dist'))
}
useref的作用:自动的把useref遇到的构建注释当中引入的资源全部合并到同一个文件当中。
文件压缩
安装
yarn add gulp-if --dev
yarn add gulp-htmlmin gulp-uglify gulp-clean-css --dev
const useref = () => {
return src('temp/*.html', { base: 'temp' })
.pipe(plugins.useref({ searchPath: ['temp', '.'] }))
// if 会自动创建转换流,只是在内部会根据判断条件决定是否执行这个任务
.pipe(plugins.if(/.js$/, plugins.uglify()))
.pipe(plugins.if(/.css$/, plugins.cleanCss()))
.pipe(plugins.if(/.html$/, plugins.htmlmin({
collapseWhitespace: true, // 折叠空白字符
minifyCSS: true, // 内部标签压缩
minifyJS: true,
})))
.pipe(dest('dist'))
}
重新规划构建过程
因为执行useref任务的时候同时涉及到对dist目录下文件的读写操作,所以需要一个中间目录暂时存放一下临时文件。html,css,js文件在开发阶段需要频繁的编译转换,而image、font和其他静态文件则只是在build的时候才会去进行转换。总而言之,只有会被useref影响到的我们才会去修改。
补充
通过上面的案例完成后需要整理一下,把需要单独使用的任务导出,一些组合起来使用的任务则不必都导出来,其他使用者用起来会方便一些
- 一般将clean、develop、serve任务导出使用,其他任务则被加入其中自动化使用
- 还可以把这三个任务定义到package.json中的scripts中,这样更容易理解一点,使用更方便
//注意:npmScripyts会自动找到我们所执行的命令在node_modules中的命令文件。就不需要yarn去找这个命令了
"scripts": {
"clean": "gulp clean",
"build": "gulp build",
"develop": "gulp develop"
}
- 在gitignore文件中需要忽略掉生成的这些目录:temp、dist
在开发过程中创建的这个构建的自动化工作流,在开发时会被重复使用到;但是不推荐每次创建项目都把它拷贝过去直接使用。因为这个工作流难免有一些不足、或者随着时间推移有些插件或模块更新时使用方式出现改变。到时候所以项目的这个构建文件都需要被修改。
完整代码
const { src, dest, parallel, series, watch } = require('gulp')
const del = require('del')
const browserSync = require('browser-sync')
// 通过load-plugins提供的api自动去加载全部的plugin
const loadPlugins = require('gulp-load-plugins')
// plugins是一个对象,所有的插件都会自动成为这个对象下面的属性
const plugins = loadPlugins()
// 它会自动创建一个开发服务器
const bs = browserSync.create()
const cwd = process.cwd();
let config = {
};
const fromData = require(`${cwd}/pages.config.js`)
config = Object.assign( {} , config , fromData )
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: config,
date: new Date()
}
// 有了del后可以通过del指定一个数组,放入任意的文件路径
const clean = ()=>{
// 就不再是return一个src之类的东西
// 它返回的是一个promise,意味着在delete完成后gulp可以标记clean任务完成
return del(['dist','temp'])
}
const style = ()=>{
// src读取流 //base转换时基准路径,保留src后面的路径
return src('src/assets/styles/*.scss' , {base:'src'})
.pipe(plugins.sass({outputStyle:'expanded'})) //插件提供的一般都是方法,调用返回一个文件转换流
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true }))
}
const script = ()=>{
// src读取流 //base转换时基准路径,保留src后面的路径
return src('src/assets/scripts/*.js' , {base:'src'})
//babel默认只是ECMAScript的一个转换平台,具体做转换的其实是babel它内部的一些插件
// 而preset就是一些插件的集合,比如preset-env就是最新的ES最新的一些整体特性的打包,使用它就会把所有特性全部做转换
// 也可以安装对应的babel转换插件,然后指定对应的转换插件,它就会只转换对应的特性
.pipe(plugins.babel({presets:[ require('@babel/preset-env') ] }))
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true }))
}
const html = ()=>{
console.log('编译html文件')
return src('src/*.html' , {base:'src'})
.pipe(plugins.swig({data , defaults: { cache: false }}))
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true }))
}
const image = ()=>{
return src('src/assets/images/**' , {base:'src'})
.pipe(plugins.imagemin())
.pipe(dest('dist'))
}
const font = ()=>{
return src('src/assets/fonts/**' , {base:'src'})
// 有的时候字体文件夹下可能会有svg文件也可以通过imagemin压缩一下,对于imagemin不能处理的文件它不会去处理
.pipe(plugins.imagemin())
.pipe(dest('dist'))
}
// 额外的文件直接通过拷贝的方式拷贝过去就可以了
const extra = ()=>{
return src('public/**' , {base:'public'})
.pipe(dest('dist'))
}
// 将开发服务器单独定义到 serve 的任务中去启动
// 启动这个任务会自动唤醒浏览器并打开页面
const serve = ()=>{
// watch接收两个参数,第一个参数是globs--通配符(所有产生构建任务的路径),第二个参数就是要执行的任务
watch( 'src/assets/styles/*.scss' , style )
watch( 'src/assets/scripts/*.js' , script )
watch( 'src/*.html' , html )
// watch( 'src/assets/images/**' , image )
// watch( 'src/assets/fonts/**' , font )
// watch( 'public/**' , extra )
// 对于监听图片,字体和其他一些静态文件,在开发阶段没有什么意义,会减慢构建速度
watch( [
'src/assets/images/**',
'src/assets/fonts/**',
'public/**'
] ,bs.reload) //监听这三种文件变化后,调用bs.reload方法,reload也可以理解为一个任务,因为在gulp中一个任务就是一个函数
// 这样就可以实现文件修改后自动编译,然后自动同步到浏览器
bs.init({
notify:false,
port:'2080',
// open:false, //启动服务时 取消自动打开页面
// files: 'dist/**', // 文件修改之后 自动更新 浏览器
server:{
baseDir:['temp','src','public'], //server中要指定一下网站的根目录
routes:{
// 页面中有些文件路径指向,优先于baseDir的配置
'/node_modules':'node_modules'
}
}
})
}
const useref = ()=>{
return src('temp/*.html' , {base:'dist'})
.pipe(plugins.useref({searchPath:['dist' , '.']}))
// if 会自动创建转换流,只是在内部会根据判断条件决定是否执行这个任务
.pipe(plugins.if(/.js$/, plugins.uglify()))
.pipe(plugins.if(/.css$/, plugins.cleanCss()))
.pipe(plugins.if(/.html$/, plugins.htmlmin({
collapseWhitespace: true, // 折叠空白字符
minifyCSS: true, // 内部标签压缩
minifyJS: true,
})))
.pipe(dest('dist'))
}
// compile完成src文件夹下的所有文件且完成转换
const compile = parallel( script , style , html )
// build通过parallel在组合的基础上再次组合将compile任务和extra进行组合,后续可以使用build完成所有文件的构建
// 加入clean任务后,clean任务就不能和其他任务同时进行了。必须保证clean任务先完成后再执行接下来的build任务,所以需要使用series
const build = series( clean, parallel( series(compile , useref) , extra , image , font ) );
const develop = series( compile , serve )
module.exports = {
clean,
compile,
build,
develop,
}
下一篇学习如何提取多个项目中共同的自动化构建过程。