从使用到案例分析如何使用Gulp

我们在《自动化构建篇之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"
  },
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容