Grunt —— 最早的前端自动化构建系统

目录

  • Grunt
    • Grunt的基本使用
      • 安装
      • 起步
      • 添加任务描述
      • 默认任务
      • 添加执行任务列表
      • 异步任务
    • Grunt标记任务失败
      • 如果任务失败如何表示?
      • 如何让后面的任务继续执行呢?
      • 异步任务如何标记失败?
    • Grunt配置选项方法 —— initConfig
    • Grunt 多目标任务
      • 如何配置多目标任务?
      • 如果在函数中拿到配置、目标和值?
    • Grunt插件的使用
      • 步骤
      • 实例
      • 常用插件
        • grunt-sass
        • grunt-babel
        • grunt-contrib-watch

Grunt

Grunt官网

特点

Grunt是最早的前端构建系统,插件生态非常的完善。由于其工作过程是基于临时文件实现的,每一步都要读写磁盘,所以构建速度相对较慢。

Grunt的基本使用

安装

npm i -g grunt

起步

  1. 添加一个gruntfile.js的文件,进行配置

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

module.exports = grunt => {
  // 注册任务,第一个参数是任务名称,第二个参数是任务函数(当任务发生时自动执行此函数)
  grunt.registerTask('foo', () => {
    console.log('hello grunt')
  })
}
  1. 命令行中执行grunt foo就可以看到执行了foo任务
grunt foo

# Running "foo" task
# hello grunt

# Done.      

添加任务描述

如果第二个参数是字符串,那么默认是任务描述

module.exports = grunt => {
  grunt.registerTask('foo', '任务描述', () => {
    console.log('hello grunt')
  })
}

使用grunt -h可以看到

image

默认任务

如果任务的名称叫default那么为默认任务

module.exports = grunt => {
  grunt.registerTask('default', '任务描述', () => {
    console.log('default grunt')
  })
}

执行的时候不用添加任务名称就可以直接执行

grunt

# Running "default" task
# default grunt

# Done.

添加执行任务列表

default任务后面可以是一个数组,数组里面的元素是任务名称

module.exports = grunt => {
  grunt.registerTask('foo', () => {
    console.log('foo grunt')
  })

  grunt.registerTask('bar',() => {
    console.log('bar grunt')
  })

  grunt.registerTask('default', ['foo', 'bar'])
}

执行的时候会按照顺序依次执行任务

grunt

# Running "foo" task
# foo grunt

# Running "bar" task
# bar grunt

# Done.

异步任务

如果在任务中写异步代码,可能会出不来

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

这里的运行结果为空,如果要解决这个问题,需要这样写

grunt.registerTask('async-task', function() {
    // 通过this的async方法得到一个异步方法
    const done = this.async()
    setTimeout(() => {
      console.log('async task working~')
      // 完成过后调用异步函数表示该任务已经被完成,知道done被执行,grunt才会结束
      done()
    },1000)
 })

Grunt标记任务失败

如果任务失败如何表示?

如果在运行的时候,文件找不到了,可以标记任务为失败任务。失败任务只要返回false即可

grunt.registerTask('bad', () => {
    console.log('bad grunt')
    return false
})

运行之后,就会抛出错误,然后后面的任务就会中断。

grunt bad

# Running "bad" task
# bad grunt
# Warning: Task "bad" failed. Use --force to continue.

# Aborted due to warnings.

如何让后面的任务继续执行呢?

grunt.registerTask('foo', () => {
    console.log('foo grunt')
})

grunt.registerTask('bad', () => {
    console.log('bad grunt')
    return false
})

grunt.registerTask('bar',() => {
    console.log('bar grunt')
})

grunt.registerTask('default', ['foo', 'bad', 'bar'])

如果在后面添加--force参数,那么后面的任务就会继续执行

grunt --force
# Running "foo" task
# foo grunt

# Running "bad" task
# bad grunt
# Warning: Task "bad" failed. Used --force, continuing.

# Running "bar" task
# bar grunt

# Done, but with warnings.

异步任务如何标记失败?

grunt.registerTask('async-task-fail', function() {
    const done = this.async()
    setTimeout(() => {
      console.log('async task fail~')
      // 执行done函数的时候,参数传false,就会标记为失败任务
      done(false)
    },1000)
})

Grunt配置选项方法 —— initConfig

Grunttask配置都是在Gruntfile 中的 grunt.initConfig 方法中指定的。此配置主要是以任务名称命名的属性,也可以包含其他任意数据。

// 接收一个对象,键一般与任务名保持一致
grunt.initConfig({
    foo: 'bar'
})

grunt.registerTask('foo', () => {
    // 根据config方法获取配置,接收字符串参数,参数是配置的键
    console.log(grunt.config('foo')) //bar
})

foo也可以是个对象

grunt.initConfig({
    foo: {
        bar: 123
    }
})

grunt.registerTask('foo', () => {
    console.log(grunt.config('foo.bar')) // 123
})

Grunt 多目标任务

除了普通任务,Grunt还支持多目标任务,也可以理解为子任务。之后在我们实现Grunt构建任务时非常有用。

PS: 使用registerMultiTask方法要搭配config,不然会报错

image

如何配置多目标任务?

多目标模式,可以让任务根据配置形成多个子任务

module.exports = grunt => {
  grunt.initConfig({
  // build有两个目标任务,一个css和一个js,执行的时候两个都会执行
    build: {
    // options作为配置选项,不作为目标任务
      options: {
        foo: 'bar'
      },
      css: '1',
      js: '2'
    }
  })
  // 多目标模式有对应函数,第一个参数是任务名称,第二个参数是任务函数
  grunt.registerMultiTask('build', function() {
    console.log('build task')
  })
}

执行一下,可以看到cssjs的目标任务都执行了

grunt build

# Running "build:css" (build) task
# build task

# Running "build:js" (build) task
# build task

# Done.

也可以直接运行单个目标任务

grunt build:css

# Running "build:css" (build) task
# build task

# Done.

如果在函数中拿到配置、目标和值?

  • options中的东西通过this.options()方法去拿
  • 目标任务名通过this.target去拿
  • 目标任务对应的值通过this.data去拿
grunt.registerMultiTask('build', function() {
    console.log(this.options())
    console.log(`target: ${this.target}, data: ${this.data}`)
})

执行得到

grunt build:css

# Running "build:css" (build) task
# { foo: 'bar' }
# target: css, data: 1

# Done.

如果目标任务本身也有options,则会对外面的options进行覆盖

grunt.initConfig({
    build: {
        options: {
            foo: 'bar'
        },
        css: {
            options: {
                foo: 'baz'
            }
        },
        js: '2'
    }
})

执行得到css目标任务的target进行了覆盖

grunt build

# Running "build:css" (build) task
# { foo: 'baz' }
# target: css, data: [object Object]

# Running "build:js" (build) task
# { foo: 'bar' }
# target: js, data: 2

# Done.

Grunt插件的使用

插件机制是Grunt的核心,因为很多构建任务是通用的,例如:压缩代码。
一般我们都是通过通用的构建任务构成的。
插件的命名规则都是grunt-contrib-<taskName>

步骤

  • 安装插件
  • Grunt中导入插件
  • 去插件文档中配置相关的选项

实例 —— clean插件

clean插件用来清除我们在项目开发当中产生的临时文件

  1. 安装插件npm install grunt-contrib-clean
  2. 在文件中引用
const { loadNpmTasks } = require("grunt")

module.exports = grunt => {
  grunt.initConfig({
    clean: {
      temp: 'temp/*.txt' //清空目录,可以是单个文件路径'temp/app.js',也可以是通配符匹配路径 temp/**

    }
  })
  grunt.loadNpmTasks('grunt-contrib-clean')
}
  1. 在命令行中执行grunt clean,就会看到temp目录下的所有txt文件被删除了。

常用插件

grunt-sass
  1. 安装npm i grunt-sass sass --save-dev
  2. 使用
const { loadNpmTasks } = require("grunt")
const sass = require('sass')
module.exports = grunt => {
  grunt.initConfig({
    sass: {
      // 不加配置会报错,这个配置选项使我们在处理的时候使用哪个模块去处理sass的编译
      // Fatal error: The implementation option must be passed to the Sass task
      options:{
        implementation: sass,
        // 可选参数,会生成对应的sourceMap文件
        sourceMap: true
      },
      main: {
        // 键是输出路径,值是原路径
        files: {
          'css/style.css': 'scss/main.scss'
        }
      }
    }
  })
  grunt.loadNpmTasks('grunt-sass')
}
  1. 执行grunt sass,可以看到sass被成功编译。
  2. 剩下的配置选项需要去官方文档中查找
grunt-babel
  1. 安装npm i grunt-babel @babel/core @babel/preset-env
  2. 为了减少loadNpmTasks的使用,可以安装npm i load-grunt-tasks
  3. 使用
const sass = require('sass')
const loadGruntTasks = require('load-grunt-tasks')
module.exports = grunt => {
  grunt.initConfig({
    babel: {
    // 最新的转换规则
      options: {
        presets: ['@babel/preset-env'],
        // 会生成对应的sourceMap文件
        sourceMap: true
      },
      main: {
        // 键是输出,值是输入
        files: {
          'dist/js/app.js': 'src/js/app.js'
        }
      }
    }
  })
  // 自动加载所有的grunt插件中的任务
  loadGruntTasks(grunt)
}
  1. 执行grunt babel,可以看dist文件夹中es6的语法被成功编译。
  2. 剩下的配置选项需要去官方文档中查找
grunt-contrib-watch

监听文件修改并自动编译

  1. 安装npm i grunt-contrib-watch
  2. 使用
const sass = require('sass')
const loadGruntTasks = require('load-grunt-tasks')
module.exports = grunt => {
  grunt.initConfig({
    // sass插件
    sass: {
      options:{
        implementation: sass,
        sourceMap: true
      },
      main: {
        files: {
          'css/style.css': 'scss/main.scss'
        }
      }
    },
    // babel插件
    babel: {
      options: {
        presets: ['@babel/preset-env'],
        sourceMap: true
      },
      main: {
        files: {
          'dist/js/app.js': 'src/js/app.js'
        }
      }
    },
    // 监听插件
    watch: {
      // 对js的任务
      js: {
        // 监听的文件路径,还有监听修改之后执行的任务名称
        files: ['src/js/*.js'],
        tasks: ['babel']
      },
      // 对css的任务
      css: {
        files: ['scss/*.scss'],
        tasks: ['sass']
      }
    }
  })
  // 自动加载所有的grunt插件中的任务
  loadGruntTasks(grunt)
}
  1. 执行grunt watch,然后修改对应的文件可以看到实时改变。

  2. 由于watch执行的时候不会自动先执行sassbabel,所以需要定义一个default任务,先执行一遍之后再监听。

//先执行sass和babel再执行watch
grunt.registerTask('default', ['sass', 'babel', 'watch'])

这个时候执行grunt即可。

grunt      

# Running "sass:main" (sass) task

# Running "babel:main" (babel) task

# Running "watch" task
# Waiting...
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 223,689评论 6 521
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 95,685评论 3 401
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 170,676评论 0 366
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,496评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,512评论 6 399
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,044评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,423评论 3 427
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,399评论 0 278
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,923评论 1 323
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,973评论 3 343
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,117评论 1 354
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,761评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,440评论 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,929评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,045评论 1 275
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,609评论 3 380
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,147评论 2 363

推荐阅读更多精彩内容