文章输出主要来源:拉勾大前端高新训练营(链接) 与 各技术官网。小哥哥小姐姐请不要嫌弃啰嗦,下面肯定都是干货。
1. 前端构建工具介绍
正如grunt官网所说,构建工具的使用就是为了解决自动化的问题,它可以将我们在开发阶段的代码自动构建为生产部署所需的代码。
对于需要反复重复的任务,例如压缩(minification)、编译、单元测试、linting等,自动化工具可以减轻你的劳动,简化你的工作。
引自grunt官网
常用的构建工具有grunt, gulp, fis等,webpack本质为模块打包工具。
2. Grunt
grunt生态庞大,本身也笨重,目前新项目中很少有grunt的使用,这里就对它做基本的介绍。
2.1 grunt的基本使用
1. 在项目中安装grunt
yarn add grunt -D
or
npm install grunt --save-dev
2. 在项目根目录创建gruntfile.js文件
Gruntfile可以定义为Gruntfile.js
或 Gruntfile.coffee
,用来配置或定义任务(task)并加载Grunt插件的。
3. 编写简单的gruntfile配置
Gruntfile中导出一个函数,其接收一个参数grunt,通过参数grunt可以调用grunt暴露的api。通过grunt.registerTask()
方法(grunt.registerTask()
是grunt.task.registerTask()
的别名),可以创建一个任务,registerTask
方法有三种用法:
- 第一个参数为任务名称,第二个参数为回调函数,执行具体的任务内容
- 第一个参数为任务名称,第二个参数为任务说明,第三个参数为回调函数,执行具体的任务内容
- 第一个参数为任务名称,第二个参数为数组,串行执行多个任务
module.exports = grunt => {
grunt.registerTask('foo', () => {
console.log('hello grunt');
});
grunt.registerTask('bar', '任务描述', () => {
console.log('other task');
});
grunt.registerTask('baz', ['foo', 'bar'])
}
通过yarn grunt taskName
或npx grunt taskName
可以执行具体的任务,通过yarn grunt --help
或npx grunt --help
可查看帮助信息,其中包含在gruntfile中定义的任务。
2.2 grunt中的任务
上述已经简单介绍了自定义任务的方法,这里再具体进行介绍。
1. 默认任务
指定任务名称为default
可以设置默认任务,通过yarn grunt
ornpx grunt
不加任务名可以直接执行默认任务。
grunt.registerTask('default', '任务描述', () => {
console.log('default task');
});
默认任务的通常用法为传入一个数组
grunt.registerTask('default', ['foo', 'bar'])
2. 异步任务
grunt任务默认支持同步模式,如果需要执行异步任务,需要使用this.async()
及其返回值进行异步模式任务的创建
例:this.async()
返回一个函数,在任务结束时,可以调用这个函数结束任务
grunt.registerTask('async-task', function() {
const done = this.async();
setTimeout(() => {
console.log('async task working~');
done();
})
})
注意: 在异步任务中registerTask里传入的函数不能是箭头函数,因为这里需要使用this.async(),而箭头函数中没有this的概念
3. 失败的任务
对于普通的任务,可以通过返回false
进行标识任务失败了,对于异步任务,需要为done(false)
传入fasle
代表任务失败
// 普通的失败任务
grunt.registerTask('foo', () => {
console.log('hello grunt');
return fasle
});
// 异步失败任务
grunt.registerTask('async-task', function() {
const done = this.async();
setTimeout(() => {
console.log('async task working~');
done(false);
})
})
如果串联执行多个任务,其中有任务失败后grunt就会停止执行后续任务,可以通过yarn grunt taskName --force
的形式指定强制执行后续的任务。
4. 任务配置选项
grunt中提供了一个initConfig
的方法为为当前项目初始化一个配置对象。grunt.initConfig()
是grunt.config.init()
方法的别名。深入了解配置相关api
其参数为一个字对象,对象的key一般与任务名保持一致。可以通过grunt.config(key)
获取到具体的配置信息,其中key可以使用key1.key2
的形式获取深层的配置信息
grunt.initConfig({
greeting: {
hello: 'hello world',
}
});
grunt.registerTask('greet', () => {
console.log('config: ', grunt.config('greeting'))
console.log('config: ', grunt.config('greeting.hello'))
})
以上代码运行yarn grunt greet
后输出
Running "greet" task
config: { hello: 'hello world' }
config: hello world
Done.
✨ Done in 0.84s.
5. 多目标模式任务
通过grunt.registerMultiTask()
方法可以创建一个多目标任务,也叫复合任务(grunt.registerMultiTask()
是grunt.task.registerMultiTask()
的别名)
多目标任务必须配合initConfig
配置对应的目标
例:initConfig参数对象中的key值需要与对应的任务名相同,该key对应的值也必须为一个对象,除了options
选项之外,其中一个key就代表一个目标。
grunt.initConfig({
build: {
js: {
options: {
hi: 'hi',
},
js: 'js'
},
css: 'css',
options: {
hello: 'hello'
}
}
});
// 多目标模式任务
grunt.registerMultiTask('build', function() {
console.log('build task')
console.log('target: ', this.target, 'data: ', this.data, 'options: ', this.options())
})
在多目标任务中,我们可以通过this.target
获取到当前的任务目标,通过this.data
获取到目标对应的配置值,通过this.options()
方法可以获取到配置选项,这里的配置选项如果没有在目标中配置,则会取值为build任务下配置的options选项。
运行yarn grunt build
输出如下:
Running "build:js" (build) task
build task
target: js data: { options: { hi: 'hi' }, js: 'js' } options: { hello: 'hello', hi: 'hi' }
Running "build:css" (build) task
build task
target: css data: css options: { hello: 'hello' }
Done.
✨ Done in 0.54s.
通过yarn grunt multiTask:target
的方式还可以单独执行某人任务目标,例如yarn grunt build:js
输出如下
Running "build:js" (build) task
build task
target: js data: { options: { hi: 'hi' }, js: 'js' } options: { hello: 'hello', hi: 'hi' }
Done.
✨ Done in 1.97s.
2.3 grunt插件
grunt拥有很多插件,插件的命名一般为grunt-contrib-pluginName
,且插件一般都是多目标任务,通过grunt.initConfig()
方法可以为插件配置需要的配置项。
插件的使用通过grunt.loadNpmTasks(pluginName)
方法进行加载,grunt.loadNpmTasks(pluginName)
是grunt.task.loadNpmTasks(pluginName)
的别名。如果安装的是本地的插件则可使用grunt.loadTasks(pluginName)
或grunt.task.loadTasks(pluginName)
插件的使用示例:
1. 安装插件(以grunt-contrib-clean为例)
yarn add grunt-contrib-clean -D
or
npm install grunt-contrib-clean -D
2. 加载插件
grunt.loadNpmTasks('grunt-contrib-clean');
3. 按照插件文档在initConfig()中为插件添加配置项
grunt.initConfig({
clean: {
temp: 'temp/app.js'
}
})
grunt.loadNpmTasks('grunt-contrib-clean');
通过yarn grunt clean
即可执行任务,改操作会根据我们的配置删除掉temp/app.js
文件
grunt常用插件与搜索,点击可以查看grunt官方的插件列表,里面有一些常用的grunt插件,点击到具体的插件中也会有具体的插件使用方式。
load-grunt-task解决多插件带来的多次引入插件问题
如果我们的项目需要加载多个插件,则要多次进行loadNpmTasks
的调用load-grunt-task
包解决了这个问题,通过yarn add load-grunt-task
,使用方式为
const loadGruntTasks = require('load-grunt-task')
moudle.exports = grunt => {
...
loadGruntTasks(grunt);
}
接下来就只需在initConfig中进行各种插件的配置就好了,但是相应的插件还是需要通过npm下载的,只是可以不在gruntfile中显式加载。
2.3 小结
本小节介绍了grunt的基本使用,详细api与更多插件可查询grunt中文官网
3. Gulp.js
Gulp是目前最为流行的构建工具之一,使用简单且高效。gulp中文官网
3.1 gulp的基本使用
gulp之所以流行,原因之一也是它使用简单,api也非常少,这里就来介绍一下gulp的使用方式。
1. 安装gulp
yarn add gulp -D
or
npm install gulp --save-dev
2. 在项目根目录创建gulpfile文件
gulpfile文件为一个gulpfile.js
的js文件
例:
exports.default = defaultTask(done) {
console.log('this is default gulp task')
done();
}
以上通过exports.defaults
导出了一个默认的gulp任务,运行yan gulp
则会运行默认的任务。
3.2 gulp中的任务
以上介绍了gulp中默认任务的定义方式,下面介绍其他类型的任务定义
1. 普通的自定义任务
gulp中的任务通过以下exports.taskName
的方式进行定义
exports.taskName = function(done){
// 任务具体内容
done()
}
最新的gulp中取消了同步任务,因此在定义普通的任务时,参数中接收一个done回调,在任务结束后需要执行done()
,否则会报错。
gulp之前的任务定义方式目前仍然被保留着,通过task
api仍然可已定义任务(不推荐)
例:
const { task } = require('gulp');
function build(done) {
// 任务具体内容
done();
}
task(build);
通过yarn gulp taskName
或npx gulp taskName
的方式即可运行自定义的任务
2. 组合任务
gulp中提供了两个api用来创建组合任务,分别为series
和parallel
series():
将任务函数和/或组合操作组合成更大的操作,这些操作将按顺序依次执行。parallel():
将任务功能和/或组合操作组合成同时执行的较大操作。
对于使用 series()
和 parallel()
进行嵌套组合的深度没有强制限制。
例:
// 串行任务示例
const { series } = require('gulp');
function foo(done) {
console.log('foo task');
done()
}
function bar(done) {
console.log('foo task');
done()
}
exports.build = series(foo, bar);
执行yarn gulp build
,输出
[18:49:35] Starting 'build'...
[18:49:35] Starting 'foo'...
foo task
[18:49:35] Finished 'foo' after 1.08 ms
[18:49:35] Starting 'bar'...
foo task
[18:49:35] Finished 'bar' after 436 μs
[18:49:35] Finished 'build' after 3.49 ms
✨ Done in 1.93s.
// 并行任务示例
const { parallel } = require('gulp');
function foo(done) {
console.log('foo task');
done()
}
function bar(done) {
console.log('foo task');
done()
}
exports.build = parallel(foo, bar);
执行yarn gulp build
,输出
[18:51:35] Starting 'build'...
[18:51:35] Starting 'foo'...
[18:51:35] Starting 'bar'...
foo task
[18:51:35] Finished 'foo' after 830 μs
foo task
[18:51:35] Finished 'bar' after 1.14 ms
[18:51:35] Finished 'build' after 2.51 ms
✨ Done in 0.99s.
通过观察执行时间,也可以发现并行任务时间是比串行短的。在多个任务独立的情况下可以使用并行任务组合,如需顺序执行则需要使用串行任务进行组合。
3. 异步任务
gulp中的任务都是异步任务。异步任务带来的问题就是何时确定异步任务执行完毕。
-
回调形式的异步任务:
第一种为我们以上介绍的通过传参传入一个
done
回调函数,在运行done()
之后确定任务执行结束。function foo(done) { console.log('foo task'); done() } module.exports = { foo }
由于
done
也是错误优先的回调,因此任务失败则需向done
回调中传入一个错误即可function foo(done) { console.log('foo task'); done(new Error('task failed')) } module.exports = { foo }
如果一个任务失败,那么后续任务将不会继续执行。
-
promise类型的异步任务:
第二种方式为promise类型的异步任务,函数通过返回promise,如果promise状态为resolve则任务成功,如果为reject则失败
// 成功任务 const promise_success_task = () => { return Promise.resolve() } // 失败任务 const promise_failed_task = () => { return Promise.reject(new Error('task failed')) } module.exports = { promise_success_task, promise_failed_task }
通过
async/await
语法糖的方式也是一样的。 -
返回 stream的方式
除了上述方式,gulp还支持其他的异步任务,详情见异步执行文档,其中第一个介绍的就是
stream
,这也是在gulp中最常用的一种方式,因为gulp中处理文件都是通过流进行处理的。例:gulp监听了流的结束事件,我们也可以通过手动执行done回调进行处理
// 直接返回stream exports.stream = () => { const readStream = fs.createReadStream('./package.json'); const writeStream = fs.createWriteStream('temp.txt'); readStream.pipe(writeStream); return readStream; // readStream完成后会触发end事件,gulp通过end事件就可以得知任务处理完成 } // 自己模拟gulp处理stream的操作 exports.stream1 = done => { const readStream = fs.createReadStream('./package.json'); const writeStream = fs.createWriteStream('temp.txt'); readStream.pipe(writeStream); readStream.on('end', () => { done(); }) }
3.3 gulp的工作原理
gulp官方介绍gulp是基于流(stream)的自动化构建工具,也正是基于流,gulp 在构建过程中并不把文件立即写入磁盘,从而提高了构建速度。
gulp基于流的构建流程为:
输入 (读取流) --> 加工 (转换流) --> 输出 (写入流)
在中间转换流中,可以对文件进行不同的操作,最后通过写入流将文件写入目标位置即可完成构建。
3.4 gulp中的文件操作api
gulp中提供了两个api用于创建读写文件流:
src()
用于创建一个流,用于从文件系统读取Vinyl 对象。dest()
创建一个用于将 Vinyl对象写入到文件系统的流。
Vinyl 是gulp团队提供的一个开源npm包,Vinyl是用来描述文件的元数据对象。Vinyl对象的主要属性是文件系统中文件核心的 path
和 contents
核心方面,它可以用于描述来自多个源的文件(本地文件系统或任何远程存储选项上)。
Vinyl提供了描述文件的方法,Vinyl适配器提供了访问这些文件的方法,其暴露了 src(globs, [options])
方法,返回一个生成 Vinyl 对象的流; 还有一个dest(folder, [options])
方法,返回一个使用 Vinyl 对象的流。
gulp中的src()
与dest
正是对这两个方法的封装。
使用示例:
const { src, dest } = require('gulp');
// 压缩css插件
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-clean-css
插件可以对css进行压缩,gulp-rename
可以对文件进行重命名。这两个插件都是通过转换流方式对文件进行修改,最后通过dest
api进行文件的写入。
这也是gulp搭配插件之后的用法。