前端工程自动化工具gulp配置使用心得

     gulp是一款非常火热的前端工程自动化工具,可以实现诸多任务的自动执行来提升开发效率。有了gulp可以帮助我们实现es2015+向es5、es3的转化,对sass、less的转化,页面热更新等,大大解放了我们的双手,带来了一种全新的开发体验。

     今天花了大半天的时间,跟着老师的视频一步一的搭建gulp前端工程自动化环境,总的来说,还是挺流畅的,但是也踩了不少的坑。比如,gulp和webpack等工具版本的更新,在使用方式上有一些不一样的地方。下面我就将我的配置配置过程分享出来,如果你能刷到这篇帖子,并且能对您有多帮助,那么我将会无比的开心。
     首先,要说明的是,我这里打算使用gulp4.x,虽然讲课的老师用的是gulp3.x,反正要学就学最新的,没错的。

一、需求分析

     我们的目的就是,放心地写代码,使用最新的ecmascript语法,能够自动地监听文件的变化,页面能够热重载,我们就是想让gulp帮我们完成这些繁琐的工作。

二、项目目录介绍

image.png

从图中可以看出,我们的目录只主要有三个大的分类,分别是/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任务中,我们可以看出,我们将要依次进行cleancsspagesscripts、["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点了,就这样吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容