《二十九》自动化构建工具 Gulp
基于 Gulp4。
Gulp:是一个工具包,可以帮助自动化和增强工作流。
Gulp 和 Webpack 的区别:
- Gulp 的核心理念是
task runner
。可以定义一系列任务,等到任务被执行;是基于文件Stream 的构建流;可以使用 Gulp 的插件体系来完成任务。 - Webpack 的核心理念是
module bundler
。是一个模块化的打包工具;可以使用各种各样的 Lodaer 来加载不同的模块;可以使用各种各样的插件在打包的生命周期中完成其他的任务。
Gulp 相对于 Webpack 来说更加地简单易用,适合编写一些自动化的任务,让它们挨个自动去执行,但是对于目前大型的项目(React、Vue、Angular 等)并不会使用 Gulp 来构建。
Gulp 默认不支持模块化。
Gulp 的基本使用:
每个 Gulp 任务都是一个异步的 JavaScript 函数;接收一个回调函数作为参数;必须明确地表示被执行完成,可以通过两种方式:执行作为参数的回调函数,或者返回一个 stream、promise、event emitter
、child process
或者 observable 类型的函数。
Gulp 中任务可以分为公开任务和私有任务。
- 公开任务:在
gulpfile.js
文件中导出的任务被称为公开任务,这些任务可以通过 gulp 命令直接调用。 - 私有任务:被设计为在内部使用,通常作为
series()
或者parallel()
任务组合的组成部分。
Gulp 单个任务:
-
新建
gulp-demo
文件夹,并npm init -y
初始化该项目。 -
在该文件夹下本地安装 Gulp:
npm install gulp
。 -
在项目根目录下新建
gulpfile.js
文件,并定义任务。// gulpfile.js // 定义任务 const test = callback => { console.log('test 任务被执行') callback() } // 导出任务 module.exports = { test }
在 Gulp4 之前,定义任务的写法为:
// gulpfile.js const gulp = require("gulp") gulp.task('test', callback => { console.log('test 任务被执行') callback() })
-
运行
npx gulp test
命令,会自动去fulpfile.js
文件中查找对应的任务并执行。
默认任务:
如果觉得觉得在 npx gulp
命令还得跟着任务名比较麻烦,可以导出一个 default 任务。直接运行 npx gulp
命令将会自动执行该 default 任务。
// gulpfile.js
// 定义并导出默认任务
module.exports.default = callback => {
console.log('default 任务被执行')
callback()
}
Gulp 多个任务:
Gulp 提供了两个组合方法,它们都可以接收任意数量的任务函数或者已经组合的操作。
series()
:串行任务组合。多个任务会挨个执行,只有执行完前一个才能执行后一个。parallel()
:并行任务组合。多个任务或同时执行,顺序无法确定。
const {parallel} = require('gulp')
// 定义任务
const task1 = callback => {
setTimeout(() => {
console.log('task1 任务被执行')
callback()
}, 2000)
}
const task2 = callback => {
setTimeout(() => {
console.log('task2 任务被执行')
callback()
}, 1000)
}
const parallelTask = parallel(task1, task2)
// 导出任务
module.exports = {
parallelTask
}
Gulp 读取和写入文件:
Gulp 暴露了 src()
和 dest()
方法用于处理计算机上存放的文件。
-
src()
:接收一个 glob 字符串或由多个 glob 字符串组成的数组作为参数,将所有从文件系统中匹配到的文件读取到内存中,并产生一个 Stream 流,通过该 Stream 流进行处理。src()
产生的 Stream 流应当从任务函数中返回,发生异步任务完成的信号。
glob 的匹配规则:*
:在一个字符串中,匹配任意数量的字符。例如src/*.js
。**
:在多个字符串匹配中,匹配任意数量的字符。通常用在匹配某个目录下的文件。例如src/**/*.js
。!
:glob 匹配时是按照在数组中的位置依次进行匹配的,glob 数组中的取反 glob 必须跟在一个非取反 glob 后面,表示后面取反 glob 删除掉前面非取反 glob 匹配到的匹配项中的一部分。例如['src/**/*.js', 'src/vendor/']
glob 字符串或者 glob 数组至少需要匹配到一个匹配项,否则
src
将会报错。 -
dest()
:接收一个输出目录作为参数,会产生一个 Node Stream 流,通过该 Stream 流将内容输出到文件中。
Stream 流提供的主要的 API 就是 pipe()
方法,pipe()
方法接收一个转换流或者可写流。
- 新建
src/index.js
文件,并编写代码。const fn = () => { console.log('index') } fn()
- 在
gulpfile.js
中编写 Gulp 读取和写入文件的方法。const {src, dest} = require('gulp') const output = () => { // 读取 src 中的所有内容并输出到 dist 目录下 return src('./src/*.js').pipe(dest('./dist')) } module.exports = { output }
- 运行
npx gulp output
命令,会发现,生成了 dist 目录,并复制了 src 目录下的文件及其内容。
Gulp 中的插件:
以 gulp-babel
插件和 gulp-terser
插件为例,可以对 JavaScript 代码进行转换。
- 安装
gulp-babel
:npm install gulp-babel -D
。 - 安装 Babel 的核心代码:
npm install @babel/core -D
。 - 安装 Babel 中的预设:
npm install @babel/preset-env -D
。 - 安装
gulp-terser
:npm install gulp-terser -D
。 - 在
gulpfile.js
文件中编写代码使用插件。const {src, dest} = require('gulp') const babel = require('gulp-babel') const terser = require('gulp-terser') const output = () => { // 读取 src 中的所有内容 return src('./src/*.js') // 使用 babel 进行转换 .pipe(babel({ presets: ['@babel/preset-env'], })) // 对其进行压缩和丑化 .pipe(terser()) // 输出到 dist 目录下 .pipe(dest('./dist')) } module.exports = { output }
- 运行
npx gulp output
命令,会发现,生成了 dist 目录,复制了 src 目录下的文件及其内容,并对其进行了压缩和丑化。
Gulp 的文件监听:
可以使用 Gulp 提供的 watch()
函数监听源文件的变化来执行指定的任务。
const {src, dest, watch} = require('gulp')
const output = () => {
return src('./src/*.js').pipe(dest('./dist'))
}
// 监听 src 中所有内容的变化,只要发生变化就执行 output 任务
watch('./src/*.js', output)
module.exports = {
output
}
Gulp 案例:
- 新建
src/index.html
文件,并编写代码。// src/index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> </body> </html>
- 安装
gulp-htmlmin
插件处理 HTML 文件:npm install gulp-htmlmin -D
。 - 新建
src/css/index.less
文件,并编写代码。@mainSize: 24px; body{ size: @mainSize; user-select: none; }
- 安装
gulp-less
插件处理 Less 文件:npm install gulp-less -D
。安装
gulp-less
插件会自动安装 Less。 - 安装
gulp-postcss
自动给 CSS 属性添加浏览器前缀:npm install gulp-postcss -D
。 - 安装 PostCSS:
npm install postcss -D
。 - 安装
postcss-preset-env
插件:npm install postcss-preset-env -D
。 - 新建
src/js/index.js
文件,并编写代码。// src/js/index.js const fn = () => { console.log('index111') } fn()
- 安装
gulp-babel
插件转换 JavaScript 文件:npm install gulp-babel -D
。 - 安装 Babel 的核心代码:
npm install @babel/core -D
。 - 安装 Babel 中的预设:
npm install @babel/preset-env -D
。 - 安装
gulp-terser
插件压缩丑化 JavaScript 文件:npm install gulp-terser -D
。 - 安装
gulp-inject
插件将打包后的 CSS、JavaScript 文件注入到 HTML 文件中:npm install gulp-inject -D
。 - 在
src/index.html
中编写要注入文件的魔法注释。<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <!-- inject:css --> <!-- endinject --> </head> <body> <!-- inject:js --> <!-- endinject --> </body> </html>
- 安装
del
插件删除指定的文件夹:npm install del -D
。 - 安装
browser-sync
插件开启一个本地服务:npm install browser-sync -D
。 - 新建
gulpfile.js
文件,并编写任务。const {src, dest, watch, series, parallel} = require('gulp') const htmlMin = require('gulp-htmlmin') const less = require('gulp-less') const postcss = require('gulp-postcss') const postcssPresetEnv = require('postcss-preset-env') const babel = require('gulp-babel') const terser = require('gulp-terser') const inject = require('gulp-inject') const del = require('del') const browseSync = require('browser-sync') // 处理 HTML 文件的任务 const htmlTask = () => { return src('./src/*.html', {base: './src'})// base:基础文件夹,在输出文件夹下会根据此文件夹下的目录生成同样对应的目录 // 对 HTML 文件进行压缩 .pipe(htmlMin({collapseWhitespace: true})) .pipe(dest('./dist')) } // 处理 Less 文件的任务 const lessTask = () => { return src('./src/css/*.less', {base: './src'}) // 对 HTML 文件进行压缩 .pipe(less()) .pipe(postcss([postcssPresetEnv()])) .pipe(dest('./dist')) } // 处理 JavaScript 文件的任务 const jsTask = () => { return src('./src/js/*.js', {base: './src'}) // 对 JS 文件进行转换 .pipe(babel({presets: ['@babel/preset-env']})) // 对 JS 文件进行压缩和丑化 .pipe(terser({mangle: {toplevel: true}})) .pipe(dest('./dist')) } // 将打包后的 CSS、JavaScript 文件注入到 HTML 文件中 const injectHtml = () => { return src('./dist/*.html') .pipe(inject(src(['./dist/css/*.css', './dist/js/*.js']), {relative: true})) // relative 属性用来配置注入后使用相对路径 .pipe(dest('./dist')) } // 删除指定的文件 const cleanTask = () => { return del(['dist']) } // 开启一个本地服务 const bs = browseSync.create() // 创建本地服务器对象 const serveTask = () => { // 监听 src 中的文件发生变化自动重新执行任务进行打包和注入 watch('./src/*.html', series(htmlTask, injectHtml)) watch('./src/css/*.less', series(lessTask, injectHtml)) watch('./src/js/*.js', series(jsTask, injectHtml)) bs.init({ port: 8080, // 自动打开浏览器 open: true, // dist 中的文件发生变化自动刷新浏览器 files: './dist/*', server: { // 服务器服务于哪个文件夹 baseDir: './dist', } }) } // 生产阶段打包 const build = series(cleanTask, parallel(htmlTask, lessTask, jsTask), injectHtml) // 开发阶段打包并开启本地服务 const serve = series(build, serveTask) module.exports = { build, serve }
- 运行
npx gulp serve
,会发现, HTML、CSS、JavaScript 文件都被打包到了 dist 文件夹下,并且 CSS、JavaScript 注入到了 HTML 文件中,而且开启了一个本地父亲。