gulp--搭建基础构建过程

定义

官方定义是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 实战

  1. 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
}

  1. 脚本编译(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
}
  1. 页面模板编译
  • 安装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
}

  1. 图片压缩和处理图片字体,因为图片包含一些二进制信息,在我们的生产环境是不需要用到的。
  • 安装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
}

  1. 删除文件和其他文件
  • 安装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'])
}
  1. 自动加载插件
  • 安装gulp-load-plugins,名称是gulp-sass,则通过plugins.sass,如果是gulp-sass-dev,则为gulp.sassDev
  1. 开发服务器:热更新开发服务器
  • 安装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
}

  1. 监听变化以及构建优化
  • 使用gulp的watch
  • 使用browser-sync启动服务
  1. 先在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',
      },
    },
  })
}
  1. 监听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',
      },
    },
  })
}
  1. 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'))
  )
}
  1. 补充
  • 开发leader写好了构建流程后,在pacakge.json文件里写好外露的api
  • 在.gitngnore里写忽略文件
"scripts": {
    "clean": "gulp clean",
    "build": "gulp build",
    "develop": "gulp develop"
  },
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,295评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,928评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,682评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,209评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,237评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,965评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,586评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,487评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,016评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,136评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,271评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,948评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,619评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,139评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,252评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,598评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,267评论 2 358

推荐阅读更多精彩内容