gulp是一款非常火热的前端工程自动化工具,可以实现诸多任务的自动执行来提升开发效率。有了gulp可以帮助我们实现es2015+向es5、es3的转化,对sass、less的转化,页面热更新等,大大解放了我们的双手,带来了一种全新的开发体验。
今天花了大半天的时间,跟着老师的视频一步一的搭建gulp前端工程自动化环境,总的来说,还是挺流畅的,但是也踩了不少的坑。比如,gulp和webpack等工具版本的更新,在使用方式上有一些不一样的地方。下面我就将我的配置配置过程分享出来,如果你能刷到这篇帖子,并且能对您有多帮助,那么我将会无比的开心。
首先,要说明的是,我这里打算使用gulp4.x,虽然讲课的老师用的是gulp3.x,反正要学就学最新的,没错的。
一、需求分析
我们的目的就是,放心地写代码,使用最新的ecmascript语法,能够自动地监听文件的变化,页面能够热重载,我们就是想让gulp帮我们完成这些繁琐的工作。
二、项目目录介绍
从图中可以看出,我们的目录只主要有三个大的分类,分别是/app, /server, /tasks,至于其功能,我已经在图上做了标注。
三、通过express框架生成服务端代码
express -e .
npm install
通过上面的两行命令,服务端的环境就查创建好了,express框架不在过多介绍。
四、构建gulp任务
1、编写gulpfile.babel.js文件
因为我们要使用babel,所以这里建立的文件名为gulpfile.babel.js,具体地可以这个https://www.gulpjs.com.cn/docs/getting-started/javascript-and-gulpfiles/链接。
import requireDir from "require-dir";
requireDir("./tasks");
gulpfile.babel.js中的内容很简单,因为我们所有的gulp任务都是放在/tasks目录下的,所以我们需要在gulpfile.babel.js中引入整个/tasks目录,当然你需要安装“require-dir”,由于本项目中需要安装的第三方包比较多,所以我们会在最后统一给出要安装的包的列表。
2、创建命令行输入工具集
在进行下面所有的操作时,我们可以先创建一个可以在命令行进行交互的工具,这个工具我们使用第三方包来进行构建。
新建文件/tasks/util/args.js
import yargs from "yargs";
const args = yargs
.option("production", {
boolean: true,
default: false,
describe: "min all scripts",
})
.option("watch", {
boolean: true,
default: false,
describe: "watch all files",
})
.option("verbose", {
boolean: true,
default: false,
describe: "log",
})
.option("sourcemaps", {
describe: "force the creation of soucemaps",
})
.option("port", {
string: true,
default: 8080,
describe: "server port",
});
这个文件的具体内容就不做过多的解释了,其目的只要是可以接受到命令输入的一些参数。比如在命令行输入
gulp --watch
那么,我们就可以用args.watch来获取到这个数据,用来判断在后续的任务是否中监听文件变化的操作。
3、创建转化js的任务
在这个构建任务中,最重要也最繁琐的就算是对js的处理了,因为要涉及到对es2015+代码的处理,所以搞懂了对js任务的处理,就能搞懂对其他任务的处理,如css,ejs模版引擎等。
新建/tasks/scripts.js
import gulp from "gulp";
import gulpif from "gulp-if";
import concat from "gulp-concat";
import webpack from "webpack";
import gulpWebpack from "webpack-stream";
import named from "vinyl-named";
import livereload from "gulp-livereload";
import plumber from "gulp-plumber";
import rename from "gulp-rename";
import uglify from "gulp-uglify";
import { log, colors } from "gulp-util";
import args from "./util/args";
gulp.task("default", () => {
return gulp
.src(["app/js/index.js"])
.pipe(
plumber({
errorHandle: function () {},
})
)
.pipe(named())
.pipe(
gulpWebpack({
module: {
rules: [{ test: /\.js$/, loader: "babel-loader" }],
},
})
)
.pipe(gulp.dest("server/public/js"))
.pipe(
rename({
basename: "cp",
extname: ".min.js",
})
)
.pipe(
uglify({ compress: { properties: false }, output: { quote_keys: true } })
)
.pipe(gulp.dest("server/public/js"))
.pipe(gulpif(args.watch, livereload()));
});
这段代码中引入的第三方模块比较多,下面先来看一下他们各自的功能,至于具体的使用,我们可以在npm官网上自行查看(英文好针真的很重要)。
名称 | 功能 |
---|---|
gulp | - |
gulp-if | gulp中做if循环判断用 |
gulp-concat | 合并文件,减少功能请求 |
webpack | - |
webpack-stream | 以流的形式运行webpack,方便地与gulp集成 |
vinyl-named | 给文件起名字 |
gulp-livereload | 实现页面地热更新 |
gulp-plumber | 防止由gulp插件错误引起的管道破裂 |
gulp-rename | 对文件重命名 |
gulp-uglify | 压缩js文件 |
gulp-util | gulp提供地工具函数(已经被弃用了) |
另外一个我们应该注意地是,这个任务我们起名字叫default,因为gulp会首先区寻找gulp任务中地default任务去执行,为了测试地方便我们把它设置了default,等所有地开发完毕之后,我们会将其重新命名为scripts。
首先,我们先看一下gulp常用的api方法及其功能如下图所示:
api | 用途 |
---|---|
src() | 接受 glob 参数,并从文件系统中读取文件然后生成一个 Node 流(stream)。它将所有匹配的文件读取到内存中并通过流(stream)进行处理。 |
dest() |
dest() 接受一个输出目录作为参数,并且它还会产生一个 Node 流(stream),通常作为终止流(terminator stream)。当它接收到通过管道(pipeline)传输的文件时,它会将文件内容及文件属性写入到指定的目录中 |
pipe() | 用于连接转换流(Transform streams)或可写流(Writable streams) |
- |
src() 也可以放在管道(pipeline)的中间,以根据给定的 glob 向流(stream)中添加文件。新加入的文件只对后续的转换可用。如果 glob 匹配的文件与之前的有重复,仍然会再次添加文件。 |
- | dest() 可以用在管道(pipeline)中间用于将文件的中间状态写入文件系统。当接收到一个文件时,当前状态的文件将被写入文件系统,文件路径也将被修改以反映输出文件的新位置,然后该文件继续沿着管道(pipeline)传输。 |
watch() |
watch() 方法利用文件系统的监控程序(file system watcher)将 globs 与 任务(task) 进行关联。它对匹配 glob 的文件进行监控,如果有文件被修改了就执行关联的任务(task) |
当然,gulp还有很多一些api和比较难的东西,本人也没有太深入的理解,可以到gulp的官方文档上面进行查阅。
了解了常用的方法api后,我们可以对上面的任务流程进行逐步分解了,具体地请看下表:
流程 |
---|
1.src()读取文件,产生文件元数据对象 |
2. plumber()对读取过程中的错误进行处理,防止碎裂管道 |
3. named ()对文件进行任意命名 |
4. gulpWebpack ()使用webpack对模块进行处理,包括babel转换等 |
5. dest ()保存阶段性中间文件 |
6.rename()对文件进行重命名操作 |
7. uglify ()压缩代码 |
8.dest()保存处理后的文件 |
9.watch()监听文件的变化,对资源进行热更新操作 |
4、对模版的处理
新建/tasks/pages.js
import gulp from "gulp";
import gulpif from "gulp-if";
import livereload from "gulp-livereload";
import args from "./util/args";
gulp.task("default", () => {
return gulp
.src("app/**/*.ejs")
.pipe(gulp.dest("server"))
.pipe(gulpif(args.watch, livereload()));
});
相对于对js的处理,对模版的处理就显得比较简单了,有一点需要注意,
.pipe(gulp.dest("server"))
这个地方,我们把文件最终输入地址定为“server”,但是我们的目标地址是“server/views”呀,这是怎么回事呀。根据官网文档的解释,在src方法中/.ejs之前叫在使用dest()方法的时候会被省去,也就是会把/.ejs保留下来,也就是会把views/index.ejs保留下来。所以上面的操作就是OK的。
5、对css的处理
新建/tasks/css.js
import gulp from "gulp";
import gulpif from "gulp-if";
import livereload from "gulp-livereload";
import args from "./util/args";
gulp.task("css", () => {
return gulp
.src("app/**/*.css")
.pipe(gulp.dest("server/public"))
.pipe(gulpif(args.watch, livereload()));
});
5、监听服务端代码的变化
新建/tasks/server.js
import gulp from "gulp";
import gulpif from "gulp-if";
import liveserver from "gulp-live-server";
import args from "./util/args";
gulp.task("server", (cb) => {
if (!args.watch) return cb();
var server = liveserver.new(["--harmony", "server/bin/www"]);
server.start();
gulp.watch(["server/public/**/*.js", "server/public/**/*.ejs"], function (
file
) {
server.notify.apply(server, [file]);
});
gulp.watch(["server/routes/**/*.js", "server/app.js"], function () {
server.start.bind(server)();
});
});
这个任务自然是监听服务端代码的更改的,当然我们需要安装第三方包gulp-live-server来帮助我们实现这个功能,至于这个包的具体用法,可以自行查看npm包。
6、创建浏览器监听任务
新建/tasks/brower.js
上面我们完成的任务中,server是对/server目录下的文件变化进行监听,scripts是将/app/js目录下的js文件打包进/server/js目录下,css和pages任务也都是实现了类似的功能。现在的问题是, 一旦/app目录中的文件发生了变化,怎么样通过pages、css、scripts等任务,将变化后的文件更新到/server目录下。这里我们就需要创建brower这个任务了,通过这个任务,我们可以实时监听文件的变化,最后将变化后的文件存放到/server目录下。
import gulp from "gulp";
import gulpif from "gulp-if";
import gutil from "gulp-util";
import args from "./util/args";
gulp.task("brower", (cb) => {
if (!args.watch) return cb();
gulp.watch("app/**/*.js", ["scripts"]);
gulp.watch("app/**/*.ejs", ["pages"]);
gulp.watch("app/**/*.css", ["css"]);
});
7、清除之前打包文件的任务(clean)
新建/tasks/clean.js
在前端代码(/app目录下)发生改变后,改变后的文件就会被保存在/server目录下,为了使打包后的代码保持干净,我们希望在每次打包之前都能清除掉/server/public 目录下的内容,那么我们就新建了一个clean任务。
import gulp from "gulp";
import del from "del";
import args from "./util/args";
gulp.task("clean", () => {
return del(["server/public", "server/views"]);
});
8、构建打包(整个流程结构)任务(build)
通过上面所有的任务,我们可以实现我们所有的需求了,现在是时候来把这个流程梳理一下了,把他们存进一个队列中,让他们按照指定顺序进行。
新建/tasks/build
import gulp from "gulp";
import gulpSequence from "gulp-sequence";
gulp.task(
"build",
gulpSequence("clean", "css", "pages", "scripts", ["browser", "server"])
);
从build任务中,我们可以看出,我们将要依次进行clean、css、pages、scripts、["brower","server"]任务。
9.设置任务入口
因为gulp默认会寻找default任务,也就是说default任务是我们任务的主入口。
新建/tasks/default.js
import gulp from "gulp";
gulp.task("default", ["build"]);
四、运行、调试、优化
终于把环境配置好了,是不是我们就可以愉快的使用我们的环境来做一些非常有趣的事情呢,但是当在命令行运行 gulp 命令时,会先后报两个错误。
错误一:忘记了
位置:/tasks/default.js
// 不能使用这种方式
gulp.task("default", ["build"]);
// 要使用下面的这种方式
gulp.task("default", gulp.series("build"));
错误二:TypeError: gulp.on(...).on(...).on(...).on(...).start is not a function
位置:/tasks/build.js
原因:gulp4 不再支持下面的调用,要使用gulp.series()的这种方式
gulp.task(
"build",
gulpSequence("clean", "css", "pages", "scripts", ["browser", "server"])
);
经过查找资料得知,这都是gulp升级到4.x带来的后果,没办法,只能将版本后降到3.x。
npm install --save-dev gulp@3.9.1
再次用gulp ,我们的任务都跑完了,没毛病,但是,怎么服务器没有启动呀,浏览器输入了http://localhost:3000x显示服务器没有启动,怎么办,找了一圈,发现/tasks/util/args.js 没有导出模块,乖乖加上吧。
export default args;
这次确实可以了,服务器也起来了,但是页面显示为白板,这是因为我们的/app/views/index.ejs中没有任何内容呀,添加内容后,刷新页面,内容也出来了。
美中不足的是,这个刷新得让我们手动实现,有没有办法修改文件保存后自动刷新了,答案肯是有的。不过我们又得借助第三方的模块connect-livereload。
在/server/app.js中添加代码
app.use(express.static(path.join(__dirname, "public")));
app.use(require("connect-livereload")());
app.use("/", indexRouter);
注意,前两行代码一种不能呼唤位置,也就是说第二行的代码一直要在第一行代码执行之后执行,这样会保证资源加载完全。
好了,已经是凌晨1点了,就这样吧。