浅谈前端自动化构建 -- Grunt、Gulp、FIS

前言

笔记来源:拉勾教育 大前端高薪训练营
阅读建议:内容较多,建议通过左侧导航栏进行阅读

前端自动化构建

基本介绍

一切重复工作本应自动化。将开发中的源代码,自动化的转换成生产环境中可以运行的程序,转换过程称为 自动化构建工作流。

作用

1,脱离运行环境兼容带来的问题

2,使用提高效率的语法、规范和标准

3、构建转换那些不被支持的特性

自动化构建初体验

  • 1,安装 sass 模块,并将其作为开发依赖进行安装

    $ yarn add sass --dev # or npm install sass --save-dev
    
  • 2,使用命令将 sass文件转换为 css文件

    $ .\node_modules\.bin\sass sass/main.scss css/style.css
    

    通过上面的命令,可以看到转换比较繁琐,下面我们来简化一下。

  • 3,使用 NPM Scripts,包装构建命令,实现自动化构建工作流的最简方式

    在这里插入图片描述
    然后,在命令行界面使用包装后的命令,将 sass 文件转换为 css 文件

    $ yarn bulid # or npm run build
    

    注意

    使用yarn运行命令时,中间的 run 可以省略,而 npm 不可以

  • 4,安装 browser-sync 模块,用于启动一个测试服务器,使其运行我们的项目

    $ yarn add browser-sync --dev # or npm install browser-sync --save-dev
    

    然后,使用 NPM Scripts,进行包装命令,如下图所示:

    在这里插入图片描述
    接着,在命令行界面使用包装后的命令,启动测试服务器

    $ yarn serve # or npm run serve
    

    此时,process会自动启动一个web服务器,并且唤起浏览器,运行我们的网页。

    运行结果,如下图所示:

    在这里插入图片描述
    然后,为了使我们在启动服务之前,先执行sass文件的转换命令,我们可以添加一个preserve命令,用于在启动服务之前,先执行转换命令

    在这里插入图片描述
    其次,要想实现 sass 文件在改变时,就去转换成css 文件,我们需要对其进行监听。

    代码示例,如下图所示:

    在这里插入图片描述
    然而,这种在启动服务时,会一直等待文件的变化,造成阻塞,导致后面的 browser-sync 无法执行。

    阻塞效果,如下图所示:

    在这里插入图片描述
    此时,我们就需要借助一个 npm-run-all 模块,去解决上述问题。

  • 5,安装 npm-run-all 模块,使其可以一次执行多个命令,即合并命令

    $ yarn add npm-run-all --dev # or npm install npm-run-all --save-dev
    

    package.json 中添加包装命令,如下图所示:

    在这里插入图片描述
    此时,当我们修改sass文件时,会自动实现编译转换,如下图所示:

    在这里插入图片描述
    最后,我们再去 browser-sync 后面添加一个 --files 参数,这个参数可以让 browser-sync 在启动过后,去监听项目下的一些文件的变化,一旦当文件发生变化过后,browser-sync 会自动将这些文件的变化同步到浏览器,从而更新浏览器的界面。

    在这里插入图片描述

自动化构建工具

Grunt

基本介绍

最早的构建系统,插件系统很完善,工作过程是基于临时文件去实现的,需要利用磁盘去读写每一个文件,所以构建速度较慢。

基本使用

准备工作
  • 1,首先创建项目文件夹,并初始化 package.json 包管理文件,在这里我们使用快速创建

      $ mkdir project-name
      $ cd project-name
      $ yarn init --yes # or npm init -y
    
  • 2,安装 grunt 模块

      $ yarn add grunt --dev # or npm install grunt --save-dev
    
  • 3,使用命令添加一个 grunt 的 入口文件 gruntfile.js ,并进行编码

      $ code gruntfile.js
    

    gruntfile.js 是Grunt 的入口文件,用于定义一些需要 Grunt 自动执行的任务,并且需要导出一个函数,此函数接收一个 grunt 的形参,内部提供一些创建任务时可以用到的 API。

API
grunt.registerTask()

grunt.registerTask() 方法用来注册普通任务,他有几种不同的传参方式。

一、传参方式

  • 1,接收两个参数时,第一个参数为指定任务的名字;第二个参数为指定任务函数,即当任务发生时,所要去执行的一系列操作。

    代码示例如下:

      // Grunt 的入口文件
      module.exports = grunt => {
          grunt.registerTask('foo', () => {
              console.log("hello");
          })
      }
    
  • 2、接收三个参数时,第一个参数为指定任务的名字;第二个参数为字符串时,即为任务描述;第三个参数为指定任务函数,即当任务发生时,去执行的函数。

    代码示例如下:

      module.exports = grunt => {
          grunt.registerTask('bar', '任务描述', () => {
              console.log('other task-')
          })
      }
    

    上述 bar 任务传入了任务描述,则此描述信息会在 grunt 的帮助信息中显示出来,执行如下命令查看:

      $ yarn grunt --help
    

    结果展示,如下图所示:

    在这里插入图片描述

  • 3,接收两个参数,第一个参数为指定任务的名字;第二个参数为数组,即为任务列表。

    代码示例如下:

      // Grunt 的入口文件
      module.exports = grunt => {
          grunt.registerTask('foo', () => {
              console.log("hello");
          })
      
          grunt.registerTask('bar', '任务描述', () => {
              console.log('other task-');
          })
      
          grunt.registerTask('tasklist', ['foo', 'bar'])
      }
    

    此时,当运行 tasklist 任务时,grunt 将会依次运行任务列表中的 foo 任务 和 bar 任务。

二、具体使用

  • 1,使用命令,运行注册的任务

      $ yarn grunt foo # yarn grunt 任务名称
    

    在 grunt 的任务列表中,存在一个默认运行任务,即任务名称为 default。

    代码示例如下:

      module.exports = grunt => {
          grunt.registerTask('default', () => {
              console.log('default task-');
          })
      }
    

    当任务名称为 default 时,使用grunt运行任务时,可以将任务名称省略,默认运行 default 任务

      $ yarn grunt
    

    运行结果,如下图所示:

    在这里插入图片描述
    然而,一般我们会使用 default 去映射一些其他的任务,即registerTask() 方法的第二个参数,需要传入一个 taskList 数组,当执行 default 时,grunt 将会依次执行数组中的任务。

    代码示例如下:

      // Grunt 的入口文件
      module.exports = grunt => {
          grunt.registerTask('foo', () => {
              console.log("hello");
          })
      
          grunt.registerTask('bar', '任务描述', () => {
              console.log('other task-');
          })
      
          grunt.registerTask('default', ['foo', 'bar'])
      }
    
  • 2,Grunt 是否异步任务支持,使用 setTimeout 进行异步任务的模拟操作。

    代码示例如下:

      module.exports = grunt => {
          grunt.registerTask('async-task', () => {
              setTimeout(() => {
                  console.log('async task working-');
              }, 1000)
          })
      }
    

    运行结果,如下图所示:

    在这里插入图片描述
    通过上面的结果,我们可以看到,使用 grunt 运行异步任务时,并没有打印出异步任务中 “async task working-”,这是因为 grunt 默认支持 同步模式。 那么,如何解决 grunt 对异步操作的不支持问题呢?

    代码示例如下:

      // Grunt 的入口文件
      module.exports = grunt => {
          grunt.registerTask('async-task', function () {
              // 在异步操作完成后,再去调用这个回调函数
              const done = this.async()
              setTimeout(() => {
                  console.log('async task working-');
                  // 标识这个任务已经被完成,告知grunt这是一个异步任务
                  // 使之等待 done 的执行,
                  // 直到done()被执行,grunt 才会结束这个任务的执行
                  done()
              }, 1000)
          })
      }
    

    通过使用 this.async() 方法,去告知 grunt 这是一个异步任务,让他就去等待这个任务结束以后,再进行任务的结束。

  • 3,Grunt 如何将任务标记成失败任务

    代码示例如下:

      module.exports = grunt => { 
          grunt.registerTask('bad', () => {
              console.log('bad workding-');        
              return false // 返回false,则表示任务失败
          })
      }
    

    可以看到,grunt 是通过返回 false 的方法,将任务标记为失败任务的。

    运行结果,如下图所示:

    在这里插入图片描述
    如果一个任务列表中存在失败任务,那么他后面的任务还会不会运行呢?

    代码示例如下:

      module.exports = grunt => {
          grunt.registerTask('foo', () => {
              console.log('foo task-');
          })
      
          grunt.registerTask('bar', () => {
              console.log('bar task-');
          })
          
          grunt.registerTask('default', ['foo', 'bad', 'bar'])	
      }
    

    运行结果,如下图所示:

    在这里插入图片描述
    可以看到,bar 任务没有被执行。即,如果任务列表中存在失败任务,那么只运行单独的 yarn grunt 命令,则会导致失败任务后面的任务不会被执行。

    那么,我们该如何使失败任务后面的任务继续运行呢?

    此时,我们需要采用强制执行的命令,以便后面的任务可以继续运行,即:

      $ yarn grunt --force
    

    运行结果,如下图所示:

    在这里插入图片描述
    可以看到,bar 任务被强制运行了。

    在上面所说的都是同步任务,那么异步任务又该如何标记为失败任务呢?

    代码示例如下:

      module.exports = grunt => {
          // 异步任务如何标记为失败任务
          grunt.registerTask('bad-async', function () {
              const done = this.async()
              setTimeout(() => {
                  console.log('bad async');            
                  done(false) // 传入一个false实参,即标记为这是一个失败的任务
              }, 1000);
          })
      }
    

    可以看到,异步任务就是在使用this.async()方法的时候,向里面传入一个false即可。

    运行结果,如下图所示:

    在这里插入图片描述

grunt.initConfig()

grunt.initConfig()是用来添加一些配置选项的API,接收一个 { } 对象形式的参数,对象的属性名(键),一般与任务名保持一致; 值可以是任意类型。

  • 1,配置选项

    代码示例如下:

      module.exports = grunt => {
          grunt.initConfig({
              str: 'string',
              obj: {
                  property: 123
              }
          })
      }
    
  • 2,获取配置数据

    通过 grunt.config() 方法,获取对应属性的值,传入的参数是设置的属性名。

    代码示例如下:

      module.exports = grunt => {
          grunt.initConfig({
              str: 'string',
              obj: {
                  property: 123
              }
          })
      
          grunt.registerTask('str', () => {
              console.log(grunt.config('str'));  // string
          })
          grunt.registerTask('obj', () => {
              console.log(grunt.config('obj'));  // { property: 123 }
              console.log(grunt.config('obj.property'));  // 123
          })
      	grunt.registerTask('tasklist', ['str', 'obj'])
      }
    

    运行结果,如下图所示:

    在这里插入图片描述

grunt.registerMultiTask()

grunt.registerMultiTask()采用多目标模式,可以让任务根据配置形成多个子任务。

该方法接收两个参数,第一个参数为指定任务的名称;第二个参数为指定任务的函数,当任务运行过程中,所要去执行的操作,由于我们会用到this,因此不建议使用箭头函数。

  • 1, 注册任务,并运行任务

    代码示例如下:

      module.exports = grunt => {
          grunt.registerMultiTask('build', function () {
              console.log('bulid task working-')
          })
      }
    

    运行结果,如下图所示:

    在这里插入图片描述
    可以看到,提示没有找到 build 任务的目标,也可以理解为没有找到子任务。这是因为多任务模式,需要在配置选项中添加任务目标的配置。

  • 2,配置任务目标

    代码示例如下:

      module.exports = grunt => {
          // 多目标模式,可以让任务根据配置形成多个子任务
          grunt.initConfig({
              build: {           // 属性名和任务名保持一致,属性值只能为对象形式
                  css: {},       // 每一个属性代表一个目标,属性名即为目标名
                  js: '2'
              }
          })
      
          grunt.registerMultiTask('build', function () {
               console.log('bulid task working-')
          })
      }
    

    运行结果,如下图所示:

    在这里插入图片描述
    可以看到,运行了两个目标任务,一个为 css任务,一个为 js任务,即运行多目标。

    那么,如何运行某一个具体的目标任务呢?根据上面的提示,可以执行如下命令:

      $ yarn grunt build:js # yarn grunt taskname:target-attr-name
    

    运行结果,如下图所示:

    在这里插入图片描述

  • 3,获取运行目标数据

    通过 this.target 获取运行目标任务,通过 this.data 获取配置数据

    代码示例如下:

      module.exports = grunt => {
          // 多目标模式,可以让任务根据配置形成多个子任务
          grunt.initConfig({
              build: {        // 属性名和任务名保持一致,属性值只能为对象形式
                  css: { },   // 每一个属性代表一个目标,属性名即为目标名
                  js: '2'
              }
          })
      
          grunt.registerMultiTask('build', function () {
      		// 通过this.target 获取运行目标
      		console.log(`target: ${this.target}`) 
      		// 通过this.data 获取配置数据
      		console.log(`data: ${this.data}`) 
          })
      }
    

    运行结果,如下图所示:

    在这里插入图片描述

  • 4,options 配置选项

    在任务的配置属性中,添加 options 属性,此时,options不可以看做是目标,它是作为任务的配置选项出现的。可以通过 this.options() 获取任务的配置选项

    代码示例如下:

      module.exports = grunt => {
          // 多目标模式,可以让任务根据配置形成多个子任务
          grunt.initConfig({
              build: {        // 属性名和任务名保持一致,属性值只能为对象形式
                  options: {   
                      foo: 'bar',
                      count: 1
                  },
                  css: { },   
                  js: '2'
              }
          })
          
          grunt.registerMultiTask('build', function () {
              // 获取任务的配置选项
              console.log(this.options());
      		// 通过this.target 获取运行目标
      		console.log(`target: ${this.target}`) 
      		// 通过this.data 获取配置数据
      		console.log(`data: ${this.data}`) 
          })
      }
    

    运行结果,如下图所示:

    在这里插入图片描述
    注意

    如果目标中也存在配置选项,那么目标中的配置选项会覆盖任务中相同属性名的配置选项

    代码示例如下:

      module.exports = grunt => {
          // 多目标模式,可以让任务根据配置形成多个子任务
          grunt.initConfig({
              build: {        // 属性名和任务名保持一致,属性值只能为对象形式
                  options: {  
                      foo: 'bar',
                      count: 111
                  },
                  css: { 
                      options: {
                          foo: 'baz' // 会覆盖任务配置选项中的 foo 属性
                      }
                  },  
                  js: '2'
              }
          })
      
          grunt.registerMultiTask('build', function () {
              // 获取任务的配置选项
              console.log(this.options());
      		// 通过this.target 获取运行目标
      		console.log(`target: ${this.target}`) 
      		// 通过this.data 获取配置数据
      		console.log(`data: ${this.data}`) 
          })
      }
    

    运行结果,如下图所示:

    在这里插入图片描述

grunt.loadNpmTasks()

grunt.loadNpmTasks() 方法,用来加载插件中提供的任务,它接收一个参数,参数为插件名称

  • 语法
      grunt.loadNpmTasks('grunt-contrib-pluginname')
    
Plugins
  • grunt 插件,常用命名方式
      grunt-contrib-pluginname
    
  • 使用 grunt 运行插件中的任务时,完整插件名称后面的 pluginname,其实就是其提供的任务名称。
      $ yarn grunt pluginname # or npm run grunt pluginname
    
grunt-contrib-clean

grunt-contrib-clean 插件,用来清除在项目开发过程中产生的临时文件。

  • 1,安装插件模块

      $ yarn add grunt-contrib-clean # or npm install grunt-contrib-clean
    
  • 2,基本使用

    该插件中所提供的任务,是一种多目标任务,因此需要在 initConfig() 中进行配置。

    代码示例如下:

      module.exports = grunt => {
          grunt.initConfig({
              clean: {
                  temp: 'temp/app.js', // 所要清除的文件的具体路径
                  allTxt: 'temp/*.txt', // 使用通配符*,删除所有txt文件
                  allFiles: 'temp/**'   // 使用**的形式,删除temp整个文件夹
              }
          })
          
          grunt.loadNpmTasks('grunt-contrib-clean')
      }
    

    运行结果,如下图所示:

    在这里插入图片描述
    通过对比可以看到,通过 grunt-contrib-clean 插件 可以清除一些临时文件,如上面的 temp目录。

grunt-sass

grunt-sass 插件是一个npm的模块,它在内部通过npm依赖sass,它需要一个npm提供sass模块进行支持,因此两个模块都需要安装。

  • 1, 安装插件模块

      $ yarn add grunt-sass sass --dev # or npm install grunt-sass sass --save-dev
    
  • 2,基本使用

    该插件中所提供的任务,是一种多目标任务,因此需要在 initConfig() 中进行配置。

    代码示例如下:

      module.exports = grunt => {
          grunt.initConfig({
              sass: {   // 配置目标
                  main: { // main目标中需要指定sass中的输入文件,以及最终输出的css的文件路径
                      files: {
                          // 属性名(键),需要输出的css的路径
                          // 属性值,需要输入的sass文件的源路径
                          'dist/css/main.css': 'src/scss/main.scss'   
                      }
                  }
              }
          })
          grunt.loadNpmTasks('grunt-sass')
      }
    

    运行结果,如下图所示:

    在这里插入图片描述
    可以看到,会提示没有去指定一个 implementation 的选项,这是因为 grunt-sass 需要使用implementation 去指定grunt-sass中使用哪一个模块去处理sass的编译。

    代码示例如下:

      const sass = require('sass')
      
      module.exports = grunt => {
          grunt.initConfig({
              sass: {   // 配置目标
                  options: {
                      sourceMap: true, // 编译时,生成对应的sourceMap文件
                      implementation: sass // 指定grunt-sass中使用哪一个模块去处理sass的编译
                  },
                  main: { // main目标中需要指定sass中的输入文件,以及最终输出的css的文件路径
                      files: {
                          'dist/css/main.css': 'src/scss/main.scss'  
                      }
                  }
              }
          })
          grunt.loadNpmTasks('grunt-sass')
      }
    

    运行结果,如下图所示:

    在这里插入图片描述
    通过对比可以看到,通过 grunt-sass 插件 可以将 sass 文件编译成 css 文件,实现预编译。

load-grunt-tasks

load-grunt-tasks 模块,用于减少loadNpmTasks() 方法的使用。

  • 1,安装模块

      $ yarn add load-grunt-tasks --dev # or npm install load-grunt-tasks -save-dev
    
  • 2,基本使用

    代码示例如下:

      const loadGruntTasks = require('load-grunt-tasks')
      
      module.exports = grunt => {
          loadGruntTasks(grunt) // 自动加载所有的 grunt 插件中的任务
      }
    
grunt-babel

grunt-babel 插件,用来编译 ES6 语法。它需要使用 Babel 的核心模块 @babel/core,以及 Babel 的预设@babel/preset-env。

  • 1,安装插件模块

      $ yarn add grunt-babel @babel/core @babel/preset-env --dev
    
  • 2,基本使用

    代码示例如下:

      const loadGruntTasks = require('load-grunt-tasks')
      
      module.exports = grunt => {
          grunt.initConfig({
              babel: {
                  options: { 
                      sourceMap: true,
                      // 指定转换所有ECMAScript中的特性
                      presets: ['@babel/preset-env'] 
                  },
                  main: {
                      files: {
                          'dist/js/app.js': 'src/js/app.js'
                      }
                  }
              }
          })
          loadGruntTasks(grunt) // 自动加载所有的 grunt 插件中的任务
      }
    

    运行 yarn grunt babel 结果,如下图所示:

    在这里插入图片描述
    通过对比可以看到,通过 grunt-babel 插件 可以将 ES6 语法 编译成 ES5 等语法,实现兼容。

grunt-contrib-watch

grunt-contrib-watch 插件,是指当文件发生改变时,可以实现自动跟踪编译。

  • 1,安装插件模块

      $ yarn add grunt-contrib-watch --dev # or npm install grunt-contrib-watch -save-dev 
    
  • 2,基本使用

    代码示例如下:

      const sass = require('sass')
      const loadGruntTasks = require('load-grunt-tasks')
      
      module.exports = grunt => {
          grunt.initConfig({
              sass: {  
                  options: {
                      sourceMap: true,
                      implementation: sass
                  },
                  main: { 
                      files: {
                          'dist/css/main.css': 'src/scss/main.scss'
                      }
                  }
              },
              babel: {
                  options: { 
                      sourceMap: true,
                      presets: ['@babel/preset-env'] 
                  },
                  main: {
                      files: {
                          'dist/js/app.js': 'src/js/app.js'
                      }
                  }
              },
              watch: {
                  js: { 
                      files: ['src/js/*.js'], // 此时不需要输出任何的文件,只要监听源文件即可   
                      tasks: ['babel'] // 设置当监听的文件发生改变时,需要去执行的任务
                  },
                  css: { // .scss 就是sass的新扩展名
                      files: ['src/scss/*.scss'],
                      tasks: ['sass']
                  }
              }
          })
          loadGruntTasks(grunt) // 自动加载所有的 grunt 插件中的任务
          
          // 使用映射,确保在启动时,运行各种编译任务,然后再启动监听
          grunt.registerTask('default', ['sass', 'babel', 'watch'])
      }
    
  • 3,运行命令

      $ yarn grunt
    

Gulp

基本介绍

基本作用

很好的解决了 Grunt 当中构建速度非常慢的问题,它是基于内存去实现的,它对于文件的处理环节都是在内存中完成的,相对于 Grunt 的磁盘读写,速度就快了很多。

另外,默认支持同时处理多个任务,因此,效率比较高。相对于 Grunt ,比较直观易懂,插件生态也同样相对完善,称为最流行的前端构建系统。

工作原理

下面将介绍 gulp 构建过程的核心工作原理。

  • 定义

    The streaming build system (基于流的构建系统)

  • 工作流程

    在这里插入图片描述
    具体步骤

    通过读取流将需要转换的文件进行读取,然后通过转换流的转换逻辑将其转换成我们想要的结果,再通过写入流写入到指定的文件位置。

  • 代码操作

    代码示例如下:

      const fs = require('fs')
      const { Transform } = require('stream')
      
      exports.default = () => {
          // 文件读取流
          const read = fs.createReadStream('normalize.css')
          // 文件写入流
          const write = fs.createWriteStream('normalize.min.css')
          // 文件转换流
          const transform = new Transform({
              transform: (chunk, encoding, callback) => {
                  // transform 是指转换流核心转换过程实现
                  // chunk => 获取文件读取流中读取到的文件内容(Buffer)--> 结果:字节数组
                  const input = chunk.toString()  // toString()转换,拿到文件的文本内容
                  const output = input.replace(/\s+/g, '').replace(/\/\*.+?\*\//g, '')
                  callback(null, output) // 错误优先回调函数,将output返回出去,传入说明成功
              }
          })
      
          // 把读取出来的文件流导入到写入文件流
          read.
              pipe(transform) // 将文件读取流转换成文件转换流
              pipe(write)     // 将文件转换流写入到文件写入流中
      
          return read
      }
    
异步任务

下面将阐述 gulp 中处理异步任务的三种方式。

回调函数

回调函数是最常用的一种方式。

  • 1,成功回调函数

    代码示例如下:

      exports.callback = done => {
          console.log('callback task~');
          done() // done 是一个函数,用来标识任务完成
      }
    
  • 2,错误回调函数,优先原则,即后面的任务不会执行

    代码示例如下:

      exports.callback_error = done => {
          console.log('callback task~');
          done(new Error('task falied!'))
      }
    
Promise
  • 1,成功异步任务

    代码示例如下:

      exports.promise = () => {
          console.log('promise task~')
          return Promise.resolve() // 成功,意味着任务结束
      }
    
  • 2,失败异步任务

    代码示例如下:

      exports.promise_error = () => {
          console.log('promise task~')
          return Promise.reject(new Error('task failed')) // 失败,任务结束
      }
    
async / await

async await 是 Promise 的语法糖,因此需要返回一个 Promise对象,node版本要在 8 以上。

  • 1,成功异步任务

    代码示例如下:

      const timeout = time => {
          return new Promise(resolve => {
              setTimeout(resolve, time)
          })
      }
      
      exports.async = async () => {
          await timeout(1000)
          console.log('async task~');
      }
    
  • 2,失败异步任务

    代码示例如下:

      exports.async_error = async () => {
          await new Promise((resolve, reject) => {
              reject(new Error('task failed'))
          })
      }
    

基本使用

准备工作
  • 1,首先创建项目文件夹,并初始化 package.json 包管理文件,在这里我们使用快速创建

      $ mkdir project-name
      $ cd project-name
      $ yarn init --yes # or npm init -y
    
  • 2,安装 gulp 模块,同时会安装一个 Gulp CLI 的命令

      $ yarn add gulp --dev # or npm install gulp --save-dev
    
  • 3,使用命令添加一个 gulp 的 入口文件 gulpfile.js ,并进行编码

      $ code gulpfile.js
    

    gulpfile.js 是 Gulp 的入口文件,用来定义一些 gulp 执行的构建任务。因为这个文件运行在 nodeJs 环境中,所以可以使用commonJs规范进行代码的编写。

gulp 初体验
  • 1,定义构建任务的方式,就是通过 exports 导出函数成员的方式进行定义

    代码示例如下:

      // gulp 的入口文件
      
      exports.foo = () => {
          console.log('foo task working~');
      } 
    

    运行结果,如下图所示:

    在这里插入图片描述
    可以看到,提示说这个任务没有完成,这是因为最新的 gulp 中取消了同步代码模式,约定每一个任务都必须是一个异步任务。当任务执行完成后,需要通过调用回调函数,或者其它的一些方式去标记这个任务已经完成。

  • 2, 使用回调函数的形式,标识任务完成

    代码示例如下:

      // Gulp 的入口文件
      exports.foo = done => {              // done 是个函数
          console.log('foo task working~');
          done() 
      }
    

    运行结果,如下图所示:

    在这里插入图片描述
    可以看到,提示已经完成 foo 任务。

  • 3,与 grunt 类似,gulp 的默认任务 也是default 任务

    代码示例如下:

      // Gulp 的默认任务
      exports.default = done => { 
          console.log('default task working~');
          done() 
      }
    

    运行结果,如下图所示:

    在这里插入图片描述
    可以看到,使用 yarn gulp 命令,默认运行 default 任务。

  • 4,gulp 4.0 以前,需要使用Gulp中的一个 gulp 的 tasks() 方法去注册任务。

    代码示例如下:

      const gulp = require('gulp')
      
      gulp.task('bar', done => {   // gulp 4.0 以后保留了这个API , 但是不推荐使用
          console.log('bar working~');
          done()
      })
    
Gulp API
series 和 parallel

创建组合任务时,可以使用 series 实现串行任务,使用 parallel 实现并行任务,使用这两种方法对实际创建构建工作流很有用。

  • 1,定义三个未被导出的成员函数,即私有的任务,他们无法通过 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)
      }
    
  • 2,通过 gulp 提供的 series方法 将其包装成串行任务,进行任务的运行

    代码示例如下:

      const { series } = require('gulp')
      // series 是一个函数,每一个参数都可以是一个任务
      exports.foo = series(task1, task2, task3)
    

    运行结果,如下图所示:

    在这里插入图片描述
    可以看到,依次执行 task1 任务、task2任务、task3任务。

    串行任务,一般用在项目部署上,即需要先执行编译的任务,再进行部署。

  • 3,通过 gulp 提供的 parallel 方法 将其包装成并行任务,进行任务的运行

    代码示例如下:

      // parallel 是一个函数,每一个参数都可以是一个任务
      exports.bar = parallel(task1, task2, task3)
    

    运行结果,如下图所示:

    在这里插入图片描述
    可以看到,task1任务、task2任务、task3任务同时启动,最后再分别完成任务列表中的每一个任务。

    并行任务,一般用于同时开启编译 js 和 css 文件时。

src 和 dest

gulp 文件操作 API,主要为 src 和 dest 。其中 src 创建读取流,参数为需要读取的目标源文件;dest 创建写入流,参数为写入的目标文件目录。

  • 转换单一文件

    代码示例如下:

      const { src, dest } = require('gulp') // 导入 gulp 提供的文件操作API:src 和 dest
      
      exports.default = () => {
          return src('src/normalize.css')
              .pipe(dest('dist/normalize.min.css')) // 写入目标目录
      }
    
  • 使用通配符* 转换多个文件

    代码示例如下:

      const { src, dest } = require('gulp')
      
      exports.default = () => {
          return src('src/*.css')
              .pipe(dest('dist')) // 写入目标目录
      }
    
watch

watch 方法,会自动监视一个文件路径的通配符,根据这些文件的变化,来决定是否要重新执行某一个任务。

  • 具体操作

    代码示例如下:

      const { src, dest, parallel, series, watch } = require('gulp')
      
      // 文件清除,不是 gulp 的插件
      const del = require('del')
      
      // 开发服务器
      const browserSync = require('browser-sync')
      
      // 自动加载全部插件
      const loadPlugins = require('gulp-load-plugins')
      
      // // 返回一个 plugins的对象
      const plugins = loadPlugins() 
      // { babel: [Getter], imagemin: [Getter], sass: [Getter], swig: [Getter] }
      
      // browserSync() 提供一个方法,用于创建服务器
      const bs = browserSync.create()
      
      // 页面中存在的动态数据
      const data = {
          menus: [
              {
                  name: 'Home',
                  icon: 'aperture',
                  link: 'index.html'
              }
          ],
          pkg: require('./package.json'),
          date: new Date()
      }
      
      // 清除文件
      const clean = () => { 
          return del('dist')
      }
      
      // 样式编译
      const style = () => {
          // 通过添加配置选项base属性,设置转换的基准路径,这样就会把src后面的一系列路径保留下来
          return src('src/assets/styles/*.scss', { base: 'src' })
              // outputStyle 属性,指定转换后的css文件按照完全展开的格式进行生成
              .pipe(plugins.sass({ outputStyle: 'expanded' }))
              .pipe(dest('dist'))
              .pipe(bs.reload({ stream: true })) // 以文件流的方式,往浏览器推
      }
      
      // 脚本编译
      const script = () => {
          return src('src/assets/scripts/*.js', { base: 'src' })
              .pipe(plugins.babel({ presets: ['@babel/preset-env'] }))
              .pipe(dest('dist'))
              .pipe(bs.reload({ stream: true })) // 以文件流的方式,往浏览器推
      }
      
      // 页面模板编译
      const page = () => {
          return src('src/*.html', { base: 'src' })
              // 使用plugins.swig模板引擎时,会将一些数据使用 {{}} 形式,进行动态加载
              // 因此,需要将所加载的数据,添加到配置选项中
              .pipe(plugins.swig({ data }))
              .pipe(dest('dist'))
              .pipe(bs.reload({ stream: true })) // 以文件流的方式,往浏览器推
      }
      
      // 图片压缩编译
      const image = () => {
          return src('src/assets/images/**', { base: 'src' })
              .pipe(plugins.imagemin())
              .pipe(dest('dist'))
      }
      
      // 字体文件编译
      const font = () => {
       // 字体文件可以直接拷贝进目标文件,但存在 .svg文件,因此可以使用plugins.imagemin() 进行转换
          return src('src/assets/fonts/**', { base: 'src' })
              .pipe(plugins.imagemin())
              .pipe(dest('dist'))
      }
      
      // 其他文件编译
      const extra = () => {
          // 直接拷贝的方式
          return src('public/**', { base: 'public' })
              .pipe(dest('dist'))
      }
      
      // 自动唤醒浏览器,打开对应的网站
      const serve = () => {
          /**
           * watch 方法接收两个参数
           * 第一个参数globs,即监听文件的路径,可以使用通配符
           * 第二个参数,即需要执行的任务,一般是设置编译的任务
           */
          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)
      
          // 初始化web服务器的核心配置
          bs.init({
              notify: false,     // 禁止弹出 “是否连接browser-sync” 提示
              port: 2080,        // 设置启动端口号,默认 3000
              // open: false,    // 设置是否在启动服务器时,自动打开浏览器
              // files: 'dist/**',// 设置哪些文件被监听,使其改变时自动更新浏览器,使用watch时,此属性可以省略,
              server: {
                 // 减少构建过程,使图片、字体、其他文件使用'src', 'public'中的
                 // 当在dist目录下找不到文件时,会依次往下查找
                 baseDir: ['dist', 'src', 'public'],  // 指定网站的根目录
                 routes: { // 将某一个路径(key)指定为另一个路径(value) 优先于 baseDir
                  '/node_modules': 'node_modules'
                 }
              }
          })
      }
      
      // 创建并行任务,完成 src目录下面需要编译的文件
      const compile = parallel(style, script, page)
      
      // 上线之前执行的任务
      const build = series(clean, parallel(compile, image, font, extra))
      
      // 开发阶段
      const develop = series(clean, compile, serve)
      
      module.exports = {
          build,     // 生产打包
          develop    // 开发
      }
    
Gulp Plugins

每一个插件模块,都会返回一个函数。

gulp-sass

gulp-sass 模块 用来将 sass 样式文件转换成 css 样式文件,同时 gulp-sass 模块 需要npm提供sass模块进行支持,因此两个模块都需要安装。

  • 1,安装插件模块

      $ yarn add gulp-sass sass --dev # or npm install gulp-sass sass --save-dev
    
  • 2,将sass文件转换成 css文件

    代码示例如下:

      const { src, dest } = require('gulp')
      // 使用sass依赖将sass 文件转换为 css
      const sass = require('gulp-sass')
      
      const style = () => {
          // 通过添加配置选项base属性,设置转换的基准路径,这样就会把src后面的一系列路径保留下来
          return src('src/assets/styles/*.scss', { base: 'src' })
                  // outputStyle 属性,指定转换后的css文件按照完全展开的格式进行生成
                  .pipe(sass({ outputStyle: 'expanded' }))
                  .pipe(dest('dist'))
      }
      
      module.exports = {
          style
      }
    

    运行结果,如下图所示:

    在这里插入图片描述
    可以看到,在我们生成的 dist 目录下,并没有_icons.css 和 _variables.css,这是因为 sass 模块在工作过程中,会默认将以 _ (下划线) 开头的文件认为是在主文件中依赖的一些文件,不会被转换,会被忽略掉。

gulp-babel

gulp-babel 模块,用来将 ES6 语法转换成 ES5 语法,但是 gulp-babel 模块只是唤醒 @babel/core 模块中的转换过程,并不会自动的去调用 @babel/core模块中的转换方法,因此,需要同时安装 @babel/core模块。并且,若需要将ECMAScript 所有的新特性进行转换时,还需要安装 @babel/preset-env 模块。

  • 1,安装插件模块

      $ yarn add gulp-babel @babel/core @babel/preset-env --dev 
    
  • 2,将 ES6 语法编译为 ES5 语法

    代码示例如下:

      const { src, dest } = require('gulp')
      // 使用Babel 依赖将 ES6 语法编译为 ES5以下语法
      const babel = require('gulp-babel')
      
      // 脚本编译
      const script = () => {
          return src('src/assets/scripts/*.js', { base: 'src' })
                  .pipe(babel({ presets: ['@babel/preset-env'] }))
                  .pipe(dest('dist'))
      }
      
      module.exports = {
          script
      }
    
gulp-swig

gulp-swig 模块,用来将使用 swig 模板引擎的 html文件转换成正常的 html文件。

  • 1,安装插件模块

      $ yarn add gulp-swig --dev # or npm install gulp-swig --save-dev
    
  • 2,转换 swig 模板引擎

    代码示例如下:

      const { src, dest, parallel } = require('gulp')
      // 将使用 swig 模板引擎的 html文件转换成正常的 html文件
      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()
        }
      
      // 页面模板编译
      const page = () => {
          return src('src/*.html', { base: 'src' })
                  // 使用swig模板引擎时,会将一些数据使用 {{}} 形式,进行动态加载
                  // 因此,需要将所加载的数据,添加到配置选项中
                  .pipe(swig({ data }))
                  .pipe(dest('dist'))
      }
      
      // 创建并行任务
      const compile = parallel(page)
      
      module.exports = {
          compile
      }
    
gulp-imagemin

gulp-imagemin 模块,用来对图片进行压缩后,转换到目标目录

  • 1,安装插件模块

      $ yarn add gulp-imagemin --dev # or npm install gulp-imagemin --save-dev
    
  • 2,将图片压缩后编译

    代码示例如下:

      const { src, dest, parallel } = require('gulp')
      // 将图片进行压缩后转换
      const imagemin = require('gulp-imagemin')
      
      // 图片压缩编译
      const image = () => {
          return src('src/assets/images/**', { base: 'src' })
                  .pipe(imagemin())
                  .pipe(dest('dist'))
      }
      // 字体文件编译
      const font = () => {
          // 字体文件可以直接拷贝进目标文件,但存在 .svg文件,因此可以使用imagemin() 进行转换
          return src('src/assets/fonts/**', { base: 'src' })
                  .pipe(imagemin())
                  .pipe(dest('dist'))
      }
      
      // 创建并行任务,完成 src目录下面需要编译的文件
      const compile = parallel(style, script, page, image, font)
      
      module.exports = {
          compile
      }
    
gulp-rename
  • 1,安装插件模块

      $ yarn add gulp-rename --dev # or npm install gulp-rename --save-dev
    
  • 2,指定转换文件的扩展名

    代码示例如下:

      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' })) // extname 属性,用于指定重命名的扩展名
              .pipe(dest('dist')) // 写入目标目录
      }
    
gulp-load-plugins

gulp-load-plugins 模块,可以实现自动的加载全部的 plugins,减少 require 的使用。

  • 1,安装插件模块

      $ yarn add gulp-load-plugins --dev # or npm install gulp-load-plugins --save-dev
    
  • 2,语法

    代码示例如下:

      // 自动加载全部插件
      const loadPlugins = require('gulp-load-plugins')
      
      // 返回一个 包含所有使用到的插件的 plugins的集合对象
      const plugins = loadPlugins() 
      // { babel: [Getter], imagemin: [Getter], sass: [Getter], swig: [Getter] }
    
  • 3,使用语法

    语法如下:

      plugins.xxx // xxx 代表插件的名称,即去掉 gulp- 前缀后的名称,若有多级,采用驼峰命名
    
gulp-useref

useref 模块,会自动处理 HTML文件中的构建注释,但是只有在编译后的 HTML文件中才会存在构建注释,因此,这个插件提供的 useref 任务,需要在编译后再运行。

  • 1,构建注释 ,即包含一个开始的 build 和一个结束的 endbuild,会将里面包裹的多个标签,合并一个文件,如下面的 vendor.css。

    代码示例如下:

        <!-- 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 -->
    
  • 2,安装插件模块

      $ yarn add gulp-useref --dev # or npm install gulp-useref --save-dev
    
  • 3,处理构建注释,使多个文件进行合并

    代码示例如下:

      const { src, dest, parallel, series, watch } = require('gulp')
      // 自动加载全部插件
      const loadPlugins = require('gulp-load-plugins')
      // // 返回一个 plugins的对象
      const plugins = loadPlugins() 
      
      // 文件引用处理
      const useref = () => {
          return src('dist/*.html', { base: 'dist' })
                  .pipe(plugins.useref({ searchPath: ['dist', '.'] }))
                  .pipe(dest('dist'))
      }
      
      // 创建并行任务,完成 src目录下面需要编译的文件
      const compile = parallel(style, script, page)
      
      // 上线之前执行的任务
      const build = series(clean, parallel(compile, image, font, extra))
      
      const develop = series(clean, compile, serve, useref)
      
      module.exports = {
          build,     // 生产打包
          develop    // 开发
      }
    
gulp-if

gulp-if模块,可以用来判断读取流中的文件类型,在其内部会自动创建转换流。

  • 1,安装插件模块

      $ yarn add gulp-if --dev # or npm install gulp-if --save-dev
    
  • 2,具体操作

    代码示例如下:

      const { src, dest } = require('gulp')
      // 自动加载全部插件
      const loadPlugins = require('gulp-load-plugins')
      // 返回一个 plugins的对象
      const plugins = loadPlugins() 
      
      // 文件引用处理
      exports.useref = () => {
          return src('dist/*.html', { base: 'dist' })
                  .pipe(plugins.useref({ searchPath: ['dist', '.'] }))
                  // html js css 
                  .pipe(plugins.if(/\.js$/, plugins.uglify()))
                  .pipe(dest('dist'))
      }
    
gulp-htmlmin

gulp-htmlmin 模块,对 HTML文件进行压缩,一般在生产上线之前使用

  • 1,安装插件模块

      $ yarn add gulp-htmlmin --dev # or npm install gulp-htmlmin --save-dev
    
  • 2,具体操作

    代码示例如下:

      const { src, dest } = require('gulp')
      // 自动加载全部插件
      const loadPlugins = require('gulp-load-plugins')
      // 返回一个 plugins的对象
      const plugins = loadPlugins() 
      
      // 文件引用处理
      exports.useref = () => {
          return src('dist/*.html', { base: 'dist' })
                  .pipe(plugins.useref({ searchPath: ['dist', '.'] }))
                  // html        
                  .pipe(plugins.if(/\.html$/, plugins.htmlmin({ 
                       // collapseWhitespace选项属性,清除所有的空白字符,否则只默认删除空格符
                      collapseWhitespace: true,
                      minifyCSS: true, // 压缩页面内的 style标签
                      minifyJS: true   // 压缩页面内的 script标签
                  })))
                  .pipe(dest('dist'))
      }
    
gulp-uglify

gulp-uglify 模块,对 JS文件进行压缩,一般在生产上线之前使用

  • 1,安装插件模块

      $ yarn add gulp-uglify --dev # or npm install gulp-uglify --save-dev
    
  • 2,具体操作

    代码示例如下:

      const { src, dest } = require('gulp')
      // 自动加载全部插件
      const loadPlugins = require('gulp-load-plugins')
      // 返回一个 plugins的对象
      const plugins = loadPlugins() 
      
      // 文件引用处理
      exports.useref = () => {
          return src('dist/*.html', { base: 'dist' })
                  .pipe(plugins.useref({ searchPath: ['dist', '.'] }))
                  // js 
                  .pipe(plugins.if(/\.js$/, plugins.uglify()))
                  .pipe(dest('dist'))
      }
    
gulp-clean-css

gulp-clean-css 提供压缩文件的转换流。

  • 1,安装插件模块

      $ yarn add gulp-clean-css --dev # or npm install gulp-clean-css --save-dev
    
  • 2,生成压缩文件的转换流

    代码示例如下:

      const { src, dest } = require('gulp')
      const cleanCss = require('gulp-clean-css')
      
      exports.default = () => {
          return src('src/*.css')
              .pipe(cleanCss())   // 将文件读取流转换成压缩文件的转换流
              .pipe(dest('dist'))
      }
    
Extra Plugins

不是 gulp 的插件,属于额外的插件,但是可以在 gulp 中使用。

del

del 模块,用来删除指定文件,它是一个 Promise方法,因此 gulp 支持 Promise模式。

  • 1,安装插件模块

      $ yarn add del --dev # or npm install del --save-dev
    
  • 2,编译之前,先清除原来生成的 dist目录

    代码示例如下:

      const { src, dest, parallel, series } = require('gulp')
      // 文件清除
      const del = require('del')
      
      // 清除文件
      const clean = () => { 
          return del('dist')
      }
      
      // 创建并行任务,完成 src目录下面需要编译的文件
      const compile = parallel(style, script, page, image, font)
      // 创建串行任务
      exports.build = series(clean, parallel(compile, extra))
    
browser-sync

browser-sync 模块,提供开发服务器,他支持修改过后自动热更新到浏览器中,让我们可以即时的看到页面效果。可以通过 gulp 进行管理

  • 1,安装插件模块

      $ yarn add browser-sync --dev # or npm install browser-sync --save-dev
    
  • 2,具体操作

    代码示例如下:

      const { src, dest, parallel, series } = require('gulp')
      const browserSync = require('browser-sync') // 热更新开发服务器依赖
      const bs = browserSync.create() // browserSync() 提供一个方法,用于创建服务器
      
      // 自动唤醒浏览器,打开对应的网站
      exports.serve = () => {
          // 初始化web服务器的核心配置
          bs.init({
              notify: false,     // 禁止弹出 “是否连接browser-sync” 提示
              port: 2080,        // 设置启动端口号,默认 3000
              // open: false,    // 设置是否在启动服务器时,自动打开浏览器
              files: 'dist/**',  // 设置哪些文件被监听,使其改变时自动更新浏览器
              server: {
                 baseDir: 'dist',  // 指定网站的根目录
                 routes: { // 将某一个路径(key)指定为另一个路径(value) 优先于 baseDir
                  '/node_modules': 'node_modules'
                 }
              }
          })
      }
    
其他文件编译

这里指的其他文件就是额外的文件,比如 public目录。

  • 具体操作

    代码示例如下:

      const { src, dest, parallel } = require('gulp')
      
      // 其他文件编译
      const extra = () => {
          // 直接拷贝的方式
          return src('public/**', { base: 'public' })
                  .pipe(dest('dist'))
      }
      
      // 创建并行任务,完成 src目录下面需要编译的文件
      const compile = parallel(style, script, page, image, font)
      const build = parallel(compile, extra)
      module.exports = {
          build    
      }
    
封装工作流
目的

如何提取一个可复用的自动化工作流?

准备工作
  • 1,依据准备工作,创建 gulp-pages目录结构,如下图所示:

    在这里插入图片描述

  • 2,将 package.json 中指向的入口文件地址,改为 “lib/index.js”,如下图所示:

    在这里插入图片描述

  • 3,编写 lib/index.js 入口文件,此入口文件,就是上面所说的 gulpfile.js文件。

    代码示例如下:

      const { src, dest, parallel, series, watch } = require('gulp')
      
      const del = require('del')
      const browserSync = require('browser-sync')
      
      const loadPlugins = require('gulp-load-plugins')
      
      const plugins = loadPlugins() 
      const bs = browserSync.create()
      
      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()
      }
      
      const clean = () => { 
          return del(['dist', 'temp'])
      }
      
      const style = () => {
          return src('src/assets/styles/*.scss', { base: 'src' })
              .pipe(plugins.sass({ outputStyle: 'expanded' }))
              .pipe(dest('temp'))
              .pipe(bs.reload({ stream: true })) 
      }
      
      const script = () => {
          return src('src/assets/scripts/*.js', { base: 'src' })
              .pipe(plugins.babel({ presets: ['@babel/preset-env'] }))
              .pipe(dest('temp'))
              .pipe(bs.reload({ stream: true })) 
      }
      
      const page = () => {
          return src('src/*.html', { base: 'src' })
              .pipe(plugins.swig({ data }))
              .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' })
              .pipe(plugins.imagemin())
              .pipe(dest('dist'))
      }
      
      const extra = () => {
          return src('public/**', { base: 'public' })
              .pipe(dest('dist'))
      }
      
      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,  
              server: {
                 baseDir: ['temp', 'src', 'public'], 
                 routes: { 
                  '/node_modules': 'node_modules'
                 }
              }
          })
      }
      
      const useref = () => {
        	return src('temp/*.html', { base: 'temp' })
                  .pipe(plugins.useref({ searchPath: ['temp', '.'] }))
                  .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'))
      }
      
      const compile = parallel(style, script, page)
      
      const build = series(
          clean, 
          parallel(
              series(compile, useref), 
              image, 
              font, 
              extra
          ))
      
      const develop = series(compile, serve)
      
      module.exports = {
          clean,
          build,     
          develop  
      }
    
  • 4,将项目所需要的依赖,添加到 package.js 中,此处安装的是项目所需依赖,而不是项目开发依赖。

    代码示例如下:

      {
        "name": "gulp-pages",
        "version": "1.0.0",
        "description": "static web app workflow",
        "main": "lib/index.js",
        "bin": "bin/gulp-pages.js",
        "license": "MIT",
        "files": ["lib", "bin"],   // 需要发布的文件夹
        "directories": {
          "lib": "lib"
        },
        "scripts": {
          "lint": "standard --fix"
        },
        "dependencies": {
          "@babel/core": "^7.12.7",
          "@babel/preset-env": "^7.12.7",
          "browser-sync": "^2.26.13",
          "del": "^6.0.0",
          "gulp": "^4.0.2",
          "gulp-babel": "^8.0.0",
          "gulp-clean-css": "^4.3.0",
          "gulp-cli": "^2.3.0",
          "gulp-htmlmin": "^5.0.1",
          "gulp-if": "^3.0.0",
          "gulp-imagemin": "^7.1.0",
          "gulp-load-plugins": "^2.0.5",
          "gulp-sass": "^4.1.0",
          "gulp-swig": "^0.9.1",
          "gulp-uglify": "^3.0.2",
          "gulp-useref": "^5.0.0",
          "sass": "^1.29.0"
        },
        "devDependencies": {
          "standard": "^16.0.3"
        },
        "engines": {
          "node": ">=6"
        }
      }
    
  • 5,使用命令,安装项目所需的全部依赖

      $ yarn # or npm i
    
  • 6,依据准备工作,创建 gulp-demo目录结构,如下图所示:

    在这里插入图片描述

  • 7,将 gulp-pages 模块链接到全局范围,在 gulp-pages 的命令终端执行

      $ yarn link # or npm link
    
  • 8, 将 gulp-pages 模块作为 gulp-demo 的依赖模块,在 gulp-demo 的命令终端执行

      $ yarn link gulp-pages # or npm link gulp-pages
    

    运行结果,如下图所示:

    在这里插入图片描述
    可以看到,gulp-demo 的目录结构中,会添加一个node_modules的文件夹,其目录结构和 gulp-pages 的一模一样。这其实是一个软连接,当 gulp-pages 中的内容改变时,gulp-demo 会随着改变。

基本操作
简单工作流

在上面书写的 gulpfile.js 文件代码中,我们采用的是定义任务,再将任务以模块进行导出的形式。现在,我们导入了公共的 gulp-pages 模块,因此,可以直接导出载入的模块。

  • 1,在 gulp-demo 的 gulpfile.js 文件中,导出载入模块后的命令

    代码示例如下:

      module.exports = require('gulp-pages')
    

    运行结果,如下图所示:

    在这里插入图片描述
    可以看到,此时运行会报错。因为用到的 gulp命令是从bin目录中获取的,而此时是不存在这个目录的,此时可以先手动安装 gulp,使编译成功。

  • 2,手动安装 gulp 依赖,临时解决 gulp 不识别问题

      $ yarn add gulp --dev # or npm install gulp --save-dev
    

    运行结果,如下图所示:

    在这里插入图片描述
    可以看到,此时运行会报错。因为在 index.js中,使用了配置数据,但是每个项目的配置数据可能都有所不同,考虑到约定大于配置,因此需要将配置数据单独抽象出来,形成配置文件,被 gulpfile.js 文件引入。

  • 3,在 gulp-demo 创建 pages.config.js 配置文件,书写配置数据(数据省略,可以参考上面的data数据)

    代码示例如下:

      module.exports = {
          data: {
              // config data
            }
      }
    
  • 4,改造 gulp-pages 的入口文件 index.js,将原来的 data配置数据 改为以下代码

    代码示例如下:

      // 返回当前命令行所在的工作目录
      const cwd = process.cwd()
      let config = {
        // default config
      }
      
      // 使用 try ... catch ,防止 pages.config.js 不存在的情况
      try {
        const loadConfig = require(`${cwd}/pages.config.js`)
        config = Object.assign({}, config, loadConfig)
      } catch (e) {}
      
      const page = () => {
        return src('src/*.html', { base: 'src' })
          .pipe(plugins.swig({ data: config.data })) // 此时,data属性名和属性值不同,不可以简写
          .pipe(dest('temp'))
          .pipe(bs.reload({ stream: true }))
      }
    

    运行结果,如下图所示:

    在这里插入图片描述
    可以看到,此时运行会报错。这是因为 根据上面的写法,会在 gulp-demo的node_modules中查找对应的依赖,而 gulp-demo 中并没有。

  • 5,解决找不到 ‘@babel/preset-env’ 依赖的问题

    代码示例如下:

      const script = () => {
        return src('src/assets/scripts/*.js', { base: 'src' })
          .pipe(plugins.babel({ presets: [require('@babel/preset-env')] }))
          .pipe(dest('temp'))
          .pipe(bs.reload({ stream: true }))
      }
    

    可以看到,采用了require 导包的方法,因为 require 会从当前文件开始,逐级往上查找对应的依赖包。

    运行结果,如下图所示:

    在这里插入图片描述

强化工作流

上面已经完成了基本的自动化构建工作流,下面需要深化包装工作流,使其更加灵活。

  • 1,抽象路径配置,将原来写死的路径,改成配置路径

    代码示例如下:

      // 模块的入口文件
      const { src, dest, parallel, series, watch } = require('gulp')
      
      const del = require('del')
      const browserSync = require('browser-sync')
      
      const loadPlugins = require('gulp-load-plugins')
      
      const plugins = loadPlugins()
      const bs = browserSync.create()
      
      // 返回当前命令行所在的工作目录
      const cwd = process.cwd()
      let config = {
        // default config
        build: {
          src: 'src',
          dist: 'dist',
          temp: 'temp',
          public: 'public',
          path: {
            styles: 'assets/styles/*.css',
            scripts: 'assets/scripts/*.js',
            pages: '*.html',
            images: 'assets/images/**',
            fonts: 'assets/fonts/**'
          }
        }
      }
      
      // 使用 try ... catch ,防止 pages.config.js 不存在的情况
      try {
        const loadConfig = require(`${cwd}/pages.config.js`)
        config = Object.assign({}, config, loadConfig)
      } catch (e) { }
      
      const clean = () => {
        return del([config.build.dist, config.build.temp])
      }
      
      const style = () => {
        // 利用cwdp 配置当前的工作目录
        return src(config.build.path.styles, { base: config.build.src, cwd: config.build.src })
          .pipe(plugins.sass({ outputStyle: 'expanded' }))
          .pipe(dest(config.build.temp))
          .pipe(bs.reload({ stream: true }))
      }
      
      const script = () => {
        return src(config.build.path.scripts, { base: config.build.src, cwd: config.build.src })
          .pipe(plugins.babel({ presets: [require('@babel/preset-env')] }))
          .pipe(dest(config.build.temp))
          .pipe(bs.reload({ stream: true }))
      }
      
      const page = () => {
        return src(config.build.path.pages, { base: config.build.src, cwd: config.build.src })
          .pipe(plugins.swig({ data: config.data }))  // 此时,data属性名和属性值不同,不可以简写
          .pipe(dest(config.build.temp))
          .pipe(bs.reload({ stream: true }))
      }
      
      const image = () => {
        return src(config.build.path.images, { base: config.build.src, cwd: config.build.src })
          .pipe(plugins.imagemin())
          .pipe(dest(config.build.dist))
      }
      
      const font = () => {
        return src(config.build.path.fonts, { base: config.build.src, cwd: config.build.src })
          .pipe(plugins.imagemin())
          .pipe(dest(config.build.dist))
      }
      
      const extra = () => {
        return src('**', { base: config.build.public, cwd: config.build.public })
          .pipe(dest(config.build.dist))
      }
      
      const serve = () => {
        watch(config.build.path.styles, { cwd: config.build.src }, style)
        watch(config.build.path.scripts, { cwd: config.build.src }, script)
        watch(config.build.path.pages, { cwd: config.build.src }, page)
        watch([
          config.build.path.images,
          config.build.path.fonts,
        ], { cwd: config.build.src }, bs.reload)
        watch('**', { cwd: config.build.public }, bs.reload)
        
        bs.init({
          notify: false,
          port: 2080,
          server: {
            baseDir: [config.build.temp, config.build.src, config.build.public],
            routes: {
              '/node_modules': 'node_modules'
            }
          }
        })
      }
      
      const useref = () => {
        return src(config.build.path.pages, { base: config.build.temp, cwd: config.build.temp })
          .pipe(plugins.useref({ searchPath: [config.build.temp, '.'] }))
          .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(config.build.dist))
      }
      
      const compile = parallel(style, script, page)
      
      const build = series(
        clean,
        parallel(
          series(compile, useref),
          image,
          font,
          extra
        ))
      
      const develop = series(compile, serve)
      
      module.exports = {
        clean,
        build,
        develop
      }
    
  • 2,gulpfile.js文件比较冗余,可以直接执行下面命令代替 gulpfile.js 文件

      $ # --cwd 设置当前执行目录
      $ yarn gulp --gulpfile ./node_modules/gulp-pages/lib/index.js --cwd 
    
包装 Gulp CLI

上面的命令需要传参,不太易操作,下面采用脚手架的形式,代替上面的操作。

  • 1,创建 cli 的入口文件,一般存放在 项目根目录/bin目录中,这里命名为 gulp-pages.js。

    目录结构,如下图所示:

    在这里插入图片描述

  • 2,在 package.json 文件中,添加 cli 入口文件的配置指向,其余配置省略

    代码示例如下:

      // 配置方式一
      {
            "bin": "bin/gulp-pages.js"
      }
            
      // 配置方式二
      {
           "bin": {
               "gp": "bin/gulp-pages.js"
           } 
      }
    

    此时,需要重新将 gulp-pages 模块 link到全局。

  • 3,配置 gulp-pages.js 入口文件,替代上述的手动输入

    代码示例如下:

      #!/usr/bin/env node
      
      process.argv.push('--cwd')
      process.argv.push(process.cwd())          // 指定当前命令的执行工作目录
      process.argv.push('--gulpfile')           
      process.argv.push(require.resolve('..'))  // 指向 gulpfile 的入口文件
      
      require('gulp/bin/gulp')                  // 集成 gulp
    
  • 4,在 gulp-demo目录 命令行界面,使用命令,运行任务

      $ gulp-pages clean
      $ gulp-pages build
      $ gulp-pages develop
    

    运行结果,与未包装时一致。

发布模块
使用模块
  • 1,新建项目文件夹,并创建public文件夹、src文件夹、pages.config.js文件等

      $ mkdir project-name
      $ cd project-name
    

    目录结构,如下图所示:

    在这里插入图片描述

  • 2,创建并初始化 package.json 包管理文件

      $ yarn init --yes # or npm init -y
    
  • 3,安装发布的 alisone-gulp-pages 模块

      $ yarn add alisone-gulp-pages --dev # or npm install alisone-gulp-pages --save-dev
    
  • 4,运行 gulp-pages 中暴露出的 任务命令

      $ yarn alisone-gulp-pages clean
      $ yarn alisone-gulp-pages build
      $ yarn alisone-gulp-pages develop
    
  • 5,在 NPM Scripts中,进行配置,即 package.json 中的 scripts 属性

    代码示例如下:

      {
          "scripts": {
              "clean": "alisone-gulp-pages clean",
              "build": "alisone-gulp-pages build",
              "develop": "alisone-gulp-pages develop"
           }
      }
    
  • 6,运行命令,进行操作

      $ yarn clean   # or npm run clean
      $ yarn build   # or npm run build
      $ yarn develop # or npm run develop
    

FIS

基本介绍

百度的前端团队推出的一款构建系统,微内核特点,更像是一种捆绑套餐,高度集成,即把项目中的一些需求都尽可能的集中在内部,例如资源加载、模块化开发、代码部署、性能优化等,在国内比较流行。

基本使用

  • 1,全局安装 或 局部安装 fis3

      $ yarn global add fis3 # or npm install fis3 -g
    
  • 2,运行 fis3 中默认的构建任务 release,会将项目中所有需要被构建的目录,存放到一个临时目录中。

      $ fis3 release 
    
  • 3,指定项目的输出目录为 output

      $ fis3 release -d output
    
  • 4,创建 fis-conf.js 配置文件,进行资源定位

    代码示例如下:

      // 利用 fis 资源定位
      
      // match() 的第一个参数,是指选择器
      // 后面的参数,是对于匹配到的文件的配置
      fis.match('*.{js,scss,png}', {
          release: '/assets/$0' // $0 指的是当前文件的原始结构
      })
    
  • 5,安装 fis-parser-node-sass 插件模块,用来编译 sass 文件

      $ yarn global add fis-parser-node-sass # or npm install fis-parser-node-sass -g
    
  • 6,在 fis-conf.js 配置文件中,配置 sass 的编译并压缩

    代码示例如下:

      fis.match('**/*.scss', {
          rExt: '.css',                    // 修改编译后的扩展名
          parser: fis.plugin('node-sass'), // 自动载入编译插件 
          optimizer: fis.plugin('clean-css') // css的压缩插件,内置插件
      })
    
  • 7,安装 fis-parser-babel-6.x 插件模块,将 ES6 语法转换为 ES5 语法

      $ yarn global add fis-parser-babel-6.x # or npm install fis-parser-babel-6.x -g
    
  • 8,在 fis-conf.js 配置文件中,配置 ES6 转换为 ES5

    代码示例如下:

      fis.match('**/*.js', {
          parser: fis.plugin('babel-6.x'), // 自动载入编译插件 
          optimizer: fis.plugin('uglify')  // js的压缩插件,内置插件
      })
    
  • 9,使用 fis3 inspect 命令,查看在转换过程中,有哪些文件被转换

      $ fis3 inspect
    
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值