我们在《自动化构建篇之Gulp(一)》中已经初步介绍过了Gulp
以及Gulp
的简单的使用方式,了解了Gulp
中的任务本质上就是让数据在各个流中流转,经过多次转换流达到我们想要的目的。那么接下来我们将从案例的角度入手,从我们日常开发的各个方面遇到的问题去使用Glup
.
前期需要自己准备一个需要被自动化构建的项目,不需要复杂,只需要我们日常常用的 scss、js、img等素材即可。
样式编译
const {src,dest} = require("gulp")
const sass = require("gulp-sass")
const style = () => {
return src("src/assets/styles/*.scss", {base: "src"})
.pipe(sass({ outputStyle: 'expanded' }))
.pipe(dest("dist"))
}
module.exports = {
style
}
因为我们再写入到dist
文件夹下的话,是需要保留本身的路径的,所以在src
的时候需要传入一个{base: "<base目录>"}
脚本编译
const babel = require("gulp-babel")
const script = () => {
return src("src/assets/scripts/*.js", {base: "src"})
.pipe(babel({presets: ["@babel/preset-env"]}))
.pipe(dest("dist"))
}
我们这边需要了解的是 babel
本身是一个ECMAscript
的一个转换平台,本身并没有转换能力。而preset-env
是一个预插件组,可以灵活选择需要的插件。至于babel-core
的作用是使用期内部提供的诸多api。
页面模板编译
在这个地方我们就需要使用一个名叫gulp-swig
的一个插件,使我们可对模板进行编译。
const page = () => {
return src("src/*.html", {base: "src"})
.pipe(swig({data})) //
.pipe(dest("dist"))
}
这里在swig({ data })
就是为了传入模板的参数
下面我们就用之前说过的合并任务的形式改造一下。由于我们的编译是相互不影响的所以我们这里采用parallel
const compile = parallel(style,script,page)
module.exports = {
compile
}
截止到目前我们最基本的编译是已经结束了。
图片和字体文件转换
很显然目前我们用到的仍然还不满足我们日常开发的需要,比如图片和文字文件等的转换如何处理。
- 图片转换我们这里使用到的是
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"))
}
并且我们可以使用imagemin
压缩svg格式的字体
对于其他我们无需编译的文件只需要写入dist
文件夹即可
const extra = () => {
return src("public/**", { base: "public" })
.pipe(dest("dist"))
}
const compile = parallel(style, script, page, image,
font)
const build = parallel(compile, build)
module.exports = {
compile,
build
}
这个时候我们又发现了,多次编译dist
假如没有被覆盖的情况下,可能会有历史版本的一些文件存在,那么就需要我们构建之前先去清理dist
目录。这个时候我们可以使用一个del
的一个插件。注意这不是一个gulp
插件。并且这个任务需要在构建执行前执行,所以就需要使用series
串行起这两个任务。
const clean = () => {
return del(["dist"])
}
const compile = parallel(style, script, page, image,
font)
const build = series(clean, parallel(compile, extra))
之前构建的已经搞完了,接下来就是为我们开发服务了
自动加载插件
随着我们的代码越来越复杂,我们会引入越来越多的插件不利于我们去维护,那么这个时候我们可以使用 gulp-load-plugins
。它的作用是去引入所有已安装的gulp
插件,所有的插件都是 plugins 的一个属性。
const loadPlugins = require('gulp-load-plugins')
const plugins = loadPlugins()
使用已有插件的方式如 plugins.sass
开发服务器
为方便我们日常调试,我们本地启动开发服务器的需要使用browser-sync
//引入
const browserSync = require('browser-sync')
const bs = browserSync.create() //创建一个实例
const serve = () => {
bs.init({
notify: false,
port: 2080,
// open: false,
// files: 'dist/**',
server: {
baseDir: ["dist"], //项目根目录
routes: {
'/node_modules': 'node_modules',//比如修改html的指向目录
}
}
})
}
热更新
有了开发服务器就少不了热更新,热更新我们利用的还是gulp
本身的API--watch
const serve = () => {
watch('src/assets/styles/*.scss', style)
watch('src/assets/scripts/*.js', script)
watch('src/*.html', page)
watch([
'src/assets/images/**',
'src/assets/fonts/**',
'public/**'
], bs.reload)
bs.init({
notify: false,
port: 2080,
// open: false,
// files: 'dist/**',
server: {
baseDir: ['dist', 'src', 'public'],
routes: {
'/node_modules': 'node_modules'
}
}
})
}
我们在serve
的任务中加入watch,在开发过程中不需要去实时编译图片字体文件等,只需要watch css, js, html
即可。我们可以通过使用browser-sync
的reload方法,达到图片变更通知浏览器更新的目的。
文件引入处理
我们使用自动化构建的最终的目的肯定是dist目录下就是最终的代码,但是有时候在入口文件的index.html
中引入了一些文件是依赖里的文件,导致无法查找到该文件,我们用webpack
的时候其实就知道需要将这些文件打包成vender
,同样我们这里利用一个叫做gulp-useref
.
这里有个特别的点就是有个构建引用注释
<!-- 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 -->
我们的目的就是将link的css文件编译到注释的目录下,需要在gulpfile.js
中使用一下这个插件
const useref = () => {
return src('dist/*.html', { base: 'dist' })
.pipe(plugins.useref({ searchPath: ['dist', '.'] }))
.pipe(dest('dist'))
}
而编译结果
<link rel="stylesheet" href="assets/styles/vendor.css">
<link rel="stylesheet" href="assets/styles/main.css">
我们这个任务是在build的过程中执行,但是需要在编译完之后,所以就需要稍微调整一下
const build = series(clean, parallel(series(compile, useref), image, font, extra))
其实我们现在已经能够满足从开发到构建上线的基本诉求了。但是为了时上线的代码更简洁需要我们去将打包好的文件压缩。
文件压缩
-
css
压缩使用gulp-clean-css
-
js
压缩使用gulp-uglify
-
html
压缩使用gulp-htmlmin
使用方式
const useref = () => {
return src('dist/*.html', { base: 'temp' })
.pipe(plugins.useref({ searchPath: ['temp', '.'] }))
// html js css
.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'))
}
该过程只在build的时候使用。我们满心的以为终于搞完了,运行yarn gulp build
的时候,我们发现我们的文件有的被压缩的是正常的,有的文件是空的。原因是啥呢?
我们可以观察到我们再useref中读的文件和写的文件是同一个文件,这就造成了上述我们描述的问题。而我们的dist
目录本身要的是一个最终的结果,所以我们可以将之前的处理过程暂存,存储在一个暂存目录下,如下:
const style = () => {
return src('src/assets/styles/*.scss', { base: 'src' })
.pipe(plugins.sass({ outputStyle: 'expanded' }))
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true }))
}
...
const build = series(
clean,
parallel(
series(complie, useref),
image,
font,
extra
)
)
//将 useref之前的过程也就是`complie`全部暂存
整理
最终我们整理我们目前的任务,发现只需要对外提供build
develop
即可,
为了我们使用方便,也便于后续其他人看我们的构建过程,我们可以在package.json
文件的scripts
中假如几条命令:
"scripts": {
"build": "gulp build",
"develop": "gulp develop"
},