Grunt

221 阅读11分钟

Grunt

Grunt 的基本使用

1. 初始化项目

mkdir 02-grunt-sample
cd ./02-grunt-sample
yarn init

2. 添加 grunt 模块

yarn add grunt

3. 添加 gruntfile.js 文件

code gruntfile.js

项目根目录下添加 gruntfile.js 文件。

4. 编写 gruntfile.js 文件

a. 注册任务:

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

module.exports = grunt => {
    grunt.registerTask('demo', () => {
        console.log('Task demo: hello grunt.')
    })
}

registerTask:第一个参数指定任务名称;第二个参数可以指定任务函数,即任务发生时主动执行的函数;

运行:

yarn grunt demo

yarn:会自动到 node_module 寻找我们需要的插件指令;

grunt:指明了我们使用的插件名称;

demo:指明了要执行的任务名称;

image.png

b. 添加多任务,添加任务描述:

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

module.exports = grunt => {
    grunt.registerTask('demo', () => {
        console.log('Task demo: hello grunt.')
    })

    grunt.registerTask('second', '任务描述', () => {
        console.log('Task second: hello second.')
    })
}

registerTask: 当第二个参数是一个字符串是,该字符串为该任务的一个描述;

查看:

yarn grunt --help

image.png

c. default 任务:

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

module.exports = grunt => {
    grunt.registerTask('demo', () => {
        console.log('Task demo: hello grunt.')
    })

    grunt.registerTask('second', '任务描述', () => {
        console.log('Task second: hello second.')
    })

    grunt.registerTask('default', () => {
        console.log('Task default: hello default.')
    })
}

运行:

yarn grunt

注册任务的时,如果名称设置为 default,那么此任务会成为 grunt 的默认任务。且执行此任务时,无须指定该任务的名称。根据此特性,default 任务一般用于映射其他的任务。

image.png

d. default 任务映射其他任务:

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

module.exports = grunt => {
    grunt.registerTask('demo', () => {
        console.log('Task demo: hello grunt.')
    })

    grunt.registerTask('second', '任务描述', () => {
        console.log('Task second: hello second.')
    })

    // grunt.registerTask('default', () => {
    //     console.log('Task default: hello default.')
    // })

    grunt.registerTask('default', ['demo', 'second'])
}

运行: image.png

e. grunt 对异步任务的支持:

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

module.exports = grunt => {
    grunt.registerTask('demo', () => {
        console.log('Task demo: hello grunt.')
    })

    grunt.registerTask('second', '任务描述', () => {
        console.log('Task second: hello second.')
    })

    // grunt.registerTask('default', () => {
    //     console.log('Task default: hello default.')
    // })

    grunt.registerTask('default', ['demo', 'second'])


    grunt.registerTask('async', () => {
        setTimeout(() => {
            console.log('Task async: async task working...')
        }, 1000);
    })
}

运行:

image.png

我们发现,setTimeout 的任务并没有直接执行。这其实是 grunt 的一个特点:

grunt 默认支持同步模式,如须异步操作,必须使用 this.async() 方法得到回调函数,在异步操作完成后调用此函数以标识此任务已经完成。

改进:

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

module.exports = grunt => {
    grunt.registerTask('demo', () => {
        console.log('Task demo: hello grunt.')
    })

    grunt.registerTask('second', '任务描述', () => {
        console.log('Task second: hello second.')
    })

    // grunt.registerTask('default', () => {
    //     console.log('Task default: hello default.')
    // })

    grunt.registerTask('default', ['demo', 'second'])


    // grunt.registerTask('async', () => {
    //     setTimeout(() => {
    //         console.log('Task async: async task working...')
    //     }, 1000);
    // })

    grunt.registerTask('async', function() {
        const done = this.async()
        setTimeout(() => {
            console.log('Task async: async task working...')
            done()
        }, 1000);
    })
}

image.png

Grunt 标记任务失败

1. 注册任务:

module.exports = grunt => {
    grunt.registerTask('first', () => {
        console.log("Task first: hello first");
    })

    grunt.registerTask('second', () => {
        console.log("Task second: this is a failed task.")
        return false
    })

    grunt.registerTask('third', () => {
        console.log("Task third: hello third.");
    })
}

在 first 任务中,可以通过 return false 来标记当前任务失败;

运行:

yarn grunt first

image.png

2. 任务列表中有失败任务,会中断其后续任务的执行:

module.exports = grunt => {
    grunt.registerTask('first', () => {
        console.log("Task first: hello first");
    })

    grunt.registerTask('second', () => {
        console.log("Task second: this is a failed task.")
        return false
    })

    grunt.registerTask('third', () => {
        console.log("Task third: hello third.");
    })

    grunt.registerTask('default', ['first', 'second', 'third'])
}

运行:

yarn grunt

在 default 任务当中,second 是已知的失败任务,因此 third 任务的执行会被中断。 image.png

3. 使用 --force 参数强制执行所有任务:

yarn grunt --force

image.png

4. 异步任务标记失败:

module.exports = grunt => {
    grunt.registerTask('first', () => {
        console.log("Task first: hello first");
    })

    grunt.registerTask('second', () => {
        console.log("Task second: this is a failed task.")
        return false
    })

    grunt.registerTask('third', () => {
        console.log("Task third: hello third.");
    })

    grunt.registerTask('default', ['first', 'second', 'third'])

    grunt.registerTask('async', function() {
        const done = this.async()

        setTimeout(() => {
            console.log('Task async: async task working...')
            done(false)
        }, 1000);
    })
}

由上可见,我们可以通过给异步回调函数 this.async() 传入 false 参数,实现异步任务标记失败。

运行:

yarn grunt async

image.png

Grunt 的配置方法

1. Grunt 任务添加配置选项:

module.exports = grunt => {
    grunt.initConfig({
        first: {
            path: "../03-grunt-failed-sample/gruntfile.js"
        }
    })

    grunt.registerTask('first', () => {
        let firstConfig = grunt.config('first')
        console.log("firstConfig: ", firstConfig)

        let path = grunt.config('first.path')
        console.log("path: ", path)
    })
}

initConfig():可通过该方法为任务添加配置选项。该方法接收一个对象作为参数,对象的键一般是任务的名称,值可以任意数据类型。

2. initConfig() 运行使用:

yarn grunt first

image.png

Grunt 多目标任务

1. 多目标任务的注册:

module.exports = grunt => {
    // 多目标模式,可以让任务根据配置形成多个子任务
    grunt.registerMultiTask('build', () => {
        console.log('build task')
    })
}

registerMultiTask():可通过该方法注册多目标任务。

运行:

image.png

此时运行任务会报错。因为运行多目标任务时,我们需要为多目标任务配置不同的目标,配置方式是 —— grunt.initConfig():

module.exports = grunt => {
    grunt.initConfig({
        build: {
            css: 'style.css',
            js: 'index.js'
        }
    })

    // 多目标模式,可以让任务根据配置形成多个子任务
    grunt.registerMultiTask('build', function() {
        console.log('build task')
    })
}

配置时,一般使用任务的名称(build)作为配置的名称,然后配置的每一个属性名称就是我们的目标名称。

运行:

yarn grunt build

image.png

当直接运行该多目标任务(build)时,会同时运行两个子任务,实际上 grunt 当中称为多目标,即当前任务(build)中有两个目标:css 目标、js 目标。

2. 运行指定目标:

yarn grunt build:css

运行命令是:yarn grunt 任务名称:目标名称

image.png

3. 获取目标信息:

module.exports = grunt => {
    grunt.initConfig({
        build: {
            css: 'style.css',
            js: 'index.js'
        }
    })

    // 多目标模式,可以让任务根据配置形成多个子任务
    grunt.registerMultiTask('build', function() {
        console.log('build task')

        console.log(`target: ${this.target}, data: ${this.data}`)
    })
}

当运行多目标任务时,我们可以通过 this.target 获取目标名称,通过 this.data 获取目标配置数据;

运行:

yarn grunt build

image.png

yarn grunt build:css

image.png

4. 多目标任务中的 options 关键字:

我们在多目标任务中指定的每一个属性都会成为该任务的一个目标,直接运行未指定目标的多目标任务时,所有的任务都会被执行。但是以 options 关键字命名的属性,不会成为该任务的目标,而是会被作为该任务的配置选项。

module.exports = grunt => {
    grunt.initConfig({
        build: {
            options: {
                path: "../04-grunt-config-sample/gruntfile.js",
            },
            css: 'style.css',
            js: 'index.js'
        }
    })

    // 多目标模式,可以让任务根据配置形成多个子任务
    grunt.registerMultiTask('build', function() {
        console.log('build task')

        console.log(`target: ${this.target}, data: ${this.data}`)
    })
}

运行:

yarn grunt build

image.png

可以看到 options 并未被执行。

5. 获取多目标任务的配置选项:

我们可以通过 this.options() 来获取多目标任务的配置选项。

module.exports = grunt => {
    grunt.initConfig({
        build: {
            options: {
                path: "../04-grunt-config-sample/gruntfile.js",
            },
            css: 'style.css',
            js: 'index.js'
        }
    })

    // 多目标模式,可以让任务根据配置形成多个子任务
    grunt.registerMultiTask('build', function() {
        console.log('build task')

        console.log(`target: ${this.target}, data: ${this.data}`)

        console.log("options: ", this.options())
    })
}

运行:

yarn grunt build

image.png

6. options 配置选项的覆盖:

如果多目标任务的某个目标也定义了 options,那么在运行此目标时,目标中的 options 会覆盖任务中的 options。

module.exports = grunt => {
    grunt.initConfig({
        build: {
            options: {
                path: "../04-grunt-config-sample/gruntfile.js",
            },
            css: {
                options: {
                    path: "./",
                },
                file: 'style.css'
            },
            js: 'index.js'
        }
    })

    // 多目标模式,可以让任务根据配置形成多个子任务
    grunt.registerMultiTask('build', function() {
        console.log('build task')

        console.log(`target: ${this.target}, data: ${this.data}`)

        console.log("options: ", this.options())
    })
}

运行:

image.png

可以看到,css 目标中的 options 覆盖了任务中的 options,但是 js 目标中由于未定义 options,所以 js 目标中获取到的依旧是任务中的 options。

Grunt 插件的使用

插件的使用意义:由于很多的构建任务都是通用的(例如:压缩代码)。因此,社区当中就出现了很多预设的插件,这些插件内部会封装一些通用的构建任务,而我们的构建过程正是由这些通用的构建任务组成的。

插件的使用方法:

graph TD
npm安装插件 --> gruntfile.js载入插件提供的任务 --> 根据插件的文档完成相关的配置选项

插件 grunt-contrib-clean 的使用:

grunt-contrib-clean: 用于自动清除项目开发过程当中产生的临时文件。

1. 安装插件

yarn add grunt-contrib-clean

2. 载入插件

 module.exports = grunt => {
     grunt.loadNpmTasks('grunt-contrib-clean')
 }

3. 插件使用

yarn grunt clean

image.png

从运行结果提示 clean 任务未配置对应的目标。由此可以知道 clean 任务是一种多目标任务,需要通过 initConfig() 方法去配置不同的目标。

4. 添加任务的配置选项

a. 直接指定文件路径:
 module.exports = grunt => {
     grunt.initConfig({
         clean: {
             // 键为目标名称,值为该目标所要清除的文件路径
             temp: 'tmp/a.js'
         }
     })

     grunt.loadNpmTasks('grunt-contrib-clean')
 }
yarn grunt clean:temp

image.png

b. 通配符方式指定文件路径:
 module.exports = grunt => {
     grunt.initConfig({
         clean: {
             // 键为目标名称,值为该目标所要清除的文件路径
             temp: 'tmp/*.txt'
         }
     })

     grunt.loadNpmTasks('grunt-contrib-clean')
 }
yarn grunt clean:temp

image.png

清除 tmp 目录下,所有后缀为 .txt 的文件。

 module.exports = grunt => {
     grunt.initConfig({
         clean: {
             // 键为目标名称,值为该目标所要清除的文件路径
             temp: 'tmp/**'
         }
     })

     grunt.loadNpmTasks('grunt-contrib-clean')
 }
yarn grunt clean:temp

image.png

清除 tmp 目录下所有子目录以及子目录下的所有文件。

实现常用的构建任务(grunt-sass,grunt-babel):

1. 项目初始化:

mkdir 07-grunt-demo
cd ./07-grunt-demo
yarn init
yarn add grunt
code gruntfile.js
yarn add grunt-sass sass --dev

2. 载入任务(grunt-sass):

module.exports = grunt => {
    grunt.initConfig({

    })

    grunt.loadNpmTasks('grunt-sass')
}

3. 配置目标:

module.exports = grunt => {
    grunt.initConfig({
        sass: {
            main: {
                files: {
                    'dist/css/main.scss': 'src/scss/main.scss', // 输出路径:源路径
                }
            }
        }
    })

    grunt.loadNpmTasks('grunt-sass')
}

4. 运行 sass 任务:

yarn grunt sass

image.png

由运行结果提示可知:缺少了 implementation 选项,该选项用于指定 grunt-sass 当中使用哪个模块对 sass 文件进行编译,因此我们需要为当前的 sass 任务添加一个配置选项(options).

5. 调整(为任务增加 implementation 选项):

const sass = require('sass')

module.exports = grunt => {
    grunt.initConfig({
        sass: {
            options: {
                implementation: sass
            },
            main: {
                files: {
                    'dist/css/main.css': 'src/scss/main.scss'
                }
            }
        }
    })

    grunt.loadNpmTasks('grunt-sass')
}

运行:

image.png

image.png

可以看到 dist/css/main.css 已经被生成。

6. 为 sass 任务添加其他配置选项(sourceMap):

const sass = require('sass')

module.exports = grunt => {
    grunt.initConfig({
        sass: {
            options: {
                sourceMap: true, 
                implementation: sass
            },
            main: {
                files: {
                    'dist/css/main.css': 'src/scss/main.scss'
                }
            }
        }
    })

    grunt.loadNpmTasks('grunt-sass')
}

运行:

image.png

image.png

7. 安装 grunt-babel 插件:

yarn add grunt-babel @babel/core @babel/preset-env --dev

8. 安装 load-grunt-tasks 插件:

yarn add load-grunt-tasks --dev

load-grunt-task:可以减少 loadNpmTasks 方法的使用,可以自动加载所有的 grunt 插件中的任务;

9. 配置 babel 转换的 preset 选项:

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: {
                presets: ['@babel/preset-env'] // @babel/preset-env: es 语法的编译
            },
            main: {
                files: {
                    'dist/js/a.js': 'src/js/a.js'
                }
            }
        }
    })

    // grunt.loadNpmTasks('grunt-sass')
    loadGruntTasks(grunt) // 自动加载所有的 grunt 插件中的任务
}

10. 运行 babel 任务:

yarn grunt babel

image.png

image.png

11. 为 babel 任务添加其他配置选项(sourceMap):

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'] // @babel/preset-env: es 语法的编译
            },
            main: {
                files: {
                    'dist/js/a.js': 'src/js/a.js'
                }
            }
        }
    })

    // grunt.loadNpmTasks('grunt-sass')
    loadGruntTasks(grunt) // 自动加载所有的 grunt 插件中的任务
}

image.png

image.png

12. 监测文件修改后自动编译(grunt-contrib-watch):

yarn add grunt-contrib-watch --dev

13. 添加 grunt-contrib-watch 的任务:

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'] // @babel/preset-env: es 语法的编译
            },
            main: {
                files: {
                    'dist/js/a.js': 'src/js/a.js'
                }
            }
        },
        watch: {
            js: {
                // 监听 js 文件的变化
                files: ['src/js/*.js'],
                tasks: ['babel'] // 当 files 当中的文件发生修改时,需要执行的任务列表
            },
            css: {
                files: ['src/scss/*.scss'],
                tasks: ['sass']
            }
        }
    })

    // grunt.loadNpmTasks('grunt-sass')
    loadGruntTasks(grunt) // 自动加载所有的 grunt 插件中的任务
}

14. 运行 watch 任务:

yarn grunt watch

image.png

运行 watch 任务后,并不会直接执行 babel 任务或者 sass 任务,它只会监听自己目标对应的文件,一但文件发生了变化,它才会执行响应的任务;

image.png

15. 将 watch 任务映射到 default 任务中:

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'] // @babel/preset-env: es 语法的编译
            },
            main: {
                files: {
                    'dist/js/a.js': 'src/js/a.js'
                }
            }
        },
        watch: {
            js: {
                // 监听 js 文件的变化
                files: ['src/js/*.js'],
                tasks: ['babel'] // 当 files 当中的文件发生修改时,需要执行的任务列表
            },
            css: {
                files: ['src/scss/*.scss'],
                tasks: ['sass']
            }
        }
    })

    // grunt.loadNpmTasks('grunt-sass')
    loadGruntTasks(grunt) // 自动加载所有的 grunt 插件中的任务

    grunt.registerTask('default', ['sass', 'babel', 'watch'])
}

image.png

此时,sass 任务和 babel 任务会先被编译一次以后,再执行 watch 任务,以监听后续文件的变化再进行自动编译。