构建工具gulp浅谈
引言:
js作为一门脚本语言,在较早时候,只能通过<script>标签插进html去运行,单个的js文件离开了html就没有了意义。
如果一个网站功能很多,要按照功能划分写十几个js文件,那么就要插入十几个<script src="">去引那些js文件,还需要注意顺序和位置,一方面难以维护,另一方面增加了网页加载时的请求数量。
有了node之后,单独的js文件离开了html以后也可以在终端run起来,这样前端也可以在命令行里进行编程了,再然后,模块化标准实施之后,js有了引入和导出的概念,这带来了革命性的变化,不用再像之前提到的那样在html里写大量的<script src="">去运行js,只需要一个作为入口的script标签,另外的js文件做为模块导出并导入到这个入口js中。
但是,问题在于,一旦js文件以<script src="">的形式插入html,那么require、export、import之类的模块语法就会报错,因为浏览器不支持模块化。模块语法是建立在node的环境下才有的。像webpack、rollup、gulp等打包工具的一个作用就是插入script标签的同时还可以让开发者在js文件之间使用require、export、import这些语法进行模块化开发,并且能够智能的将这些js模块合并压缩成一个紧实的js文件,同时可以对代码压缩和优化使其在生产环境中能够运行的更高效。当然,现代的打包工具已经不局限在将多个 js 文件打包成一个 js,而是成为了开发流程的一部分,保证开发能够使用较为高级的语法、处理兼容问题、静态资源优化等。
gulp是什么
- 自动化构建工具,把项目工作抽象成自动化任务用 gulp 构建自动化工作流,处理项目中的资源,基于(stream)流式操作。开发人员要写一系列的任务(task),加载组件、一步一步地处理 stream。gulp 更偏向将开发流程中让人痛苦或耗时的任务自动化,从而减少所浪费的时间。
- 出现在社区模块化时代,也就是AMD、CMD、UMD等模块化规范出现并且nodejs现世后的时期,因为其依赖于node的文件读写机制。
- 这是一个官方的示例
const { src, dest, parallel } = require('gulp');
const pug = require('gulp-pug');
const less = require('gulp-less');
const minifyCSS = require('gulp-csso');
const concat = require('gulp-concat');
function html() {
return src('client/templates/*.pug')
.pipe(pug())
.pipe(dest('build/html'))
}
function css() {
return src('client/templates/*.less')
.pipe(less())
.pipe(minifyCSS())
.pipe(dest('build/css'))
}
function js() {
return src('client/javascript/*.js', { sourcemaps: true })
.pipe(concat('app.min.js'))
.pipe(dest('build/js', { sourcemaps: true }))
}
exports.js = js;
exports.css = css;
exports.html = html;
exports.default = parallel(html, css, js);
gulp核心api
Api | 描述 | 参数 | 返回值 |
---|---|---|---|
src ( ) | 创建一个流,用于从文件系统读取 Vinyl 对象。 | src(globs, [options]) | 返回一个可以在管道的开始或中间使用的流,用于根据给定的 globs 添加文件。 |
dest ( ) | 创建一个用于将 Vinyl 对象写入到文件系统的流。输出数据流到目标路径。 | dest(directory, [options]) | 返回一个可以在管道的中间或末尾使用的流,用于在文件系统上创建文件。 |
symlink ( ) | 创建一个流(stream),用于连接 Vinyl 对象到文件系统。软链接。 | symlink(directory, [options]) | 每当 Vinyl 对象通过流被传递时,它将内容和其他细节写到给定目录下的文件系统。如果 Vinyl 对象具有 symlink 属性,将创建符号链接(symbolic link)而不是写入内容。创建文件后,将更新其元数据以匹配 Vinyl 对象。在文件系统上创建文件时,Vinyl 对象将被修改。 |
lastRun ( ) | 检索在当前运行进程中成功完成任务的最后一次时间。当与 src() 组合时,通过跳过自上次成功完成任务以来没有更 改的文件,使增量构建能够加快执行时间。 |
lastRun(task, [precision]) | 返回一个时间戳(以毫秒为单位),表示任务的最后完成时间。如果任务尚未运行或已经失败,则返回 undefined 。为了避免缓存无效状态(invalid state),因此,如果任务出错,则返回值为 undefined 。 |
series ( ) | 将任务函数和/或组合操作组合成更大的操作,这些操作将按顺序依次执行。嵌套深度没有强制限制。 | series(...tasks) | 返回一个组合操作,它将注册为任务或嵌套在其他 series 和/或 parallel 组合中。当执行组合操作时,所有任务将按顺序运行。如果一个任务中发生错误,则不会运行后续任务。 |
parallel ( ) | 将任务功能和/或组合操作组合成同时执行的较大操作。嵌套组合的深度没有强制限制。 | parallel(...tasks) | 返回一个组合操作,它将注册为任务或嵌套在其他 series 和/或 parallel 组合中。当执行组合操作时,所有任务将以最大并发性运行。如果一个任务发生错误,其他任务可能不确定地完成,也可能不完成。 |
watch ( ) | 监听 globs 并在发生更改时运行任务。任务与任务系统的其余部分被统一处理。 | watch(globs, [options], [task]) | chokidar的一个实例,用于对监听设置进行细粒度控制。 |
registry ( ) | 允许将自定义的注册表插入到任务系统中,以期提供共享任务或增强功能。 | registry([registryInstance]) | 如果传递了 registryInstance ,则不会返回任何内容。如果没有传递参数,则返回当前注册表实例。 |
gulp核心配置文件gulpfile
命名:gulpfile.js文件或是文件夹。对于typescript,使用文件重命名为gulpfile.ts并安装ts-node、typescript、@babel/core模块。对于babel,重命名为gulpfile.babel.js并安装@babel/core、@babel/register模块。
gulpfile分割:每个任务(task)可以被分割为独立的文件,然后导入(import)到 gulpfile 文件中并组合。目录中需要包含一个名为index.js的文件,其它文件按照模块规范,导出符合task规范的函数在index.js文件中导入即可。当使用typescript时,文件夹名称需要改为gulpfile.ts,同时目录下的文件改为.ts结尾。
值得注意的是,只有在index.js文件中通过exports.[taskName]的形式导出的函数才能注册到公开的task树中。通过exports.default导出的为默认执行。
gulp核心机制
- 示例图:
task:每个 gulp 任务(task)都是一个异步的 JavaScript 函数,此函数是一个可以接收 callback 作为参数的函数,或者是一个返回 stream、promise、event emitter、child process 或 observable 类型值的函数,必须保证return以上类型值或是执行callback(),否则会告警Did you forget to signal async completion?
task需要是具名函数,因为函数名称就是task的名称,不过匿名函数也不会报错,但是会造成误解。
vinyl
vinyl: 虚拟的文件格式,描述文件的元数据对象,vinyl对象可以在插件中流转,也可以被vinyl适配器所使用,例如dest()函数。每一个Vinyl实例代表一个独立的文件、目录或者symlink符号连接。实际项目中,除了需要一个简洁的方式去描述一个文件之外,我们还需要访问这些文件,所以vinyl适配器对外暴露接口src()和dest(),它们都最终返回一个流(stream),src接口生产一个Vinyl 对象,dest接口消费一个Vinyl 对象。
stream
gulp在流转vinyl对象时是基于stream方式,使其在一次数据流转过程就可以进行多次的处理操作。
stream流的工作方式:
流(stream)是node.js处理流式数据的抽象接口,流是可读可写的,所有的流都是EventEmitter实例。Writable - 可写入数据的流(例如 fs.createWriteStream())。
Readable - 可读取数据的流(例如 fs.createReadStream())。
Duplex - 可读又可写的流(例如 net.Socket)。
Transform - 在读写过程中可以修改或转换数据的 Duplex 流。
例子:从字符串创建文件
function test2 (cb) {
var src = require('stream').Readable({ objectMode: true })
src._read = function () {
this.push(new Vinyl({
cwd: "/",
base: "/",
path: '/file.js',
contents: Buffer.from('var x = 123', 'utf-8')
}))
this.push(null)
}
src.pipe(dest('build/'))
cb()
}
gulp内部,vinyl对象输出流通过through2包装为Transform流进行流转,所以使用src接口得到的是一个Transform流。
常用插件
- gulp-load-plugins:自动加载 package.json 中的 gulp 插件
- gulp-rename: 重命名
- gulp-uglify:文件压缩
- gulp-concat:文件合并
- gulp-less:编译 less
- gulp-sass:编译 sass
- gulp-clean-css:压缩 CSS 文件
- gulp-htmlmin:压缩 HTML 文件
- gulp-babel: 使用 babel 编译 JS 文件
- gulp-eslint:eslint校验
- gulp-imagemin:压缩 jpg、png、gif 等图片。注意版本兼容。
- gulp-livereload:当代码变化时,它可以帮我们自动刷新页面
gulp的适用场景
- 多页面,静态资源密集操作型场景
- 轻量化的任务,单独打包css文件,轻量而且逻辑清晰。
- 流程化,一些特殊的任务,将图片发布至图床,下载某些特定文件处理后存放至本地等
gulp中使用rollup
const { rollup } = require('rollup');
const typescript = require('rollup-plugin-typescript2');
const babel = require ('gulp-babel');
const { src, dest } = require('gulp')
const uglify = require('gulp-uglify')
// Rollup 提供了基于 promise 的 API,在 `async` 任务(task)中工作的很好
module.exports = async function rol() {
const bundle = await rollup({
input: 'src/vendor/main.ts',
plugins: [
typescript({
check: false,
clean: true,
tsconfigOverride: { compilerOptions: { removeComments: true } }
})
]
});
await bundle.write({
file: 'output/bundle.js',
format: 'umd'
});
src ('output/bundle.js')
.pipe (
babel ({
presets: ['@babel/env'],
})
)
.pipe (uglify())
.pipe (rrename({ extname: '.min.js' }))
.pipe (dest ('output/'))
}
gulp中使用webpack
const { src, dest } = require('gulp');
const gulpWebpack = require('webpack-stream');
const webpack = require('webpack');
module.exports = function testWebpack (cb) {
return src('src/entry.js')
.pipe(gulpWebpack({
watch: true,
mode: 'development',
...[options]
}, webpack))
.pipe(dest('wp_dist/'));
}
结尾
gulp现在使用的场景已经很少了,可能更多的是做开发流程的规范(也很少了),不再是主力开发打包工具,不太推荐使用啦,毕竟插件都好久没有维护了。尽量webpack能解决的就使用webpack解决,实在解决不了可以尝试使用,因为gulp可以以一种侵入式不大的方式使用。