???? gulp是一款非吃舻耍火熱的前端工程自動化工具,可以實現(xiàn)諸多任務(wù)的自動執(zhí)行來提升開發(fā)效率匆骗。有了gulp可以幫助我們實現(xiàn)es2015+向es5、es3的轉(zhuǎn)化,對sass、less的轉(zhuǎn)化,頁面熱更新等只损,大大解放了我們的雙手,帶來了一種全新的開發(fā)體驗。
???? 今天花了大半天的時間,跟著老師的視頻一步一的搭建gulp前端工程自動化環(huán)境闲勺,總的來說申尤,還是挺流暢的橙喘,但是也踩了不少的坑磁奖。比如身诺,gulp和webpack等工具版本的更新幔托,在使用方式上有一些不一樣的地方谬哀。下面我就將我的配置配置過程分享出來谦屑,如果你能刷到這篇帖子充蓝,并且能對您有多幫助卑笨,那么我將會無比的開心桶良。
???? 首先疲牵,要說明的是,我這里打算使用gulp4.x责静,雖然講課的老師用的是gulp3.x,反正要學就學最新的熄赡,沒錯的。
一、需求分析
???? 我們的目的就是礁凡,放心地寫代碼紊浩,使用最新的ecmascript語法,能夠自動地監(jiān)聽文件的變化,頁面能夠熱重載,我們就是想讓gulp幫我們完成這些繁瑣的工作调榄。
二伦籍、項目目錄介紹
從圖中可以看出剩燥,我們的目錄只主要有三個大的分類,分別是/app, /server, /tasks,至于其功能,我已經(jīng)在圖上做了標注苦丁。
三棵磷、通過express框架生成服務(wù)端代碼
express -e .
npm install
???? 通過上面的兩行命令赌莺,服務(wù)端的環(huán)境就查創(chuàng)建好了,express框架不在過多介紹傲绣。
四禁舷、構(gòu)建gulp任務(wù)
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中的內(nèi)容很簡單,因為我們所有的gulp任務(wù)都是放在/tasks目錄下的租谈,所以我們需要在gulpfile.babel.js中引入整個/tasks目錄憔足,當然你需要安裝“require-dir”,由于本項目中需要安裝的第三方包比較多州袒,所以我們會在最后統(tǒng)一給出要安裝的包的列表。
2姐扮、創(chuàng)建命令行輸入工具集
???? 在進行下面所有的操作時杯缺,我們可以先創(chuàng)建一個可以在命令行進行交互的工具亲铡,這個工具我們使用第三方包來進行構(gòu)建疑务。
新建文件/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",
});
???? 這個文件的具體內(nèi)容就不做過多的解釋了,其目的只要是可以接受到命令輸入的一些參數(shù)纺阔。比如在命令行輸入
gulp --watch
???? 那么批什,我們就可以用args.watch來獲取到這個數(shù)據(jù),用來判斷在后續(xù)的任務(wù)是否中監(jiān)聽文件變化的操作碧信。
3、創(chuàng)建轉(zhuǎn)化js的任務(wù)
???? 在這個構(gòu)建任務(wù)中,最重要也最繁瑣的就算是對js的處理了歧杏,因為要涉及到對es2015+代碼的處理,所以搞懂了對js任務(wù)的處理迷守,就能搞懂對其他任務(wù)的處理犬绒,如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官網(wǎng)上自行查看(英文好針真的很重要)咐鹤。
名稱 | 功能 |
---|---|
gulp | - |
gulp-if | gulp中做if循環(huán)判斷用 |
gulp-concat | 合并文件拗秘,減少功能請求 |
webpack | - |
webpack-stream | 以流的形式運行webpack,方便地與gulp集成 |
vinyl-named | 給文件起名字 |
gulp-livereload | 實現(xiàn)頁面地熱更新 |
gulp-plumber | 防止由gulp插件錯誤引起的管道破裂 |
gulp-rename | 對文件重命名 |
gulp-uglify | 壓縮js文件 |
gulp-util | gulp提供地工具函數(shù)(已經(jīng)被棄用了) |
???? 另外一個我們應(yīng)該注意地是祈惶,這個任務(wù)我們起名字叫default雕旨,因為gulp會首先區(qū)尋找gulp任務(wù)中地default任務(wù)去執(zhí)行,為了測試地方便我們把它設(shè)置了default捧请,等所有地開發(fā)完畢之后凡涩,我們會將其重新命名為scripts。
???? 首先疹蛉,我們先看一下gulp常用的api方法及其功能如下圖所示:
api | 用途 |
---|---|
src() | 接受 glob 參數(shù)活箕,并從文件系統(tǒng)中讀取文件然后生成一個 Node 流(stream)。它將所有匹配的文件讀取到內(nèi)存中并通過流(stream)進行處理氧吐。 |
dest() |
dest() 接受一個輸出目錄作為參數(shù)讹蘑,并且它還會產(chǎn)生一個 Node 流(stream),通常作為終止流(terminator stream)筑舅。當它接收到通過管道(pipeline)傳輸?shù)奈募r座慰,它會將文件內(nèi)容及文件屬性寫入到指定的目錄中 |
pipe() | 用于連接轉(zhuǎn)換流(Transform streams)或可寫流(Writable streams) |
- |
src() 也可以放在管道(pipeline)的中間,以根據(jù)給定的 glob 向流(stream)中添加文件翠拣。新加入的文件只對后續(xù)的轉(zhuǎn)換可用版仔。如果 glob 匹配的文件與之前的有重復(fù),仍然會再次添加文件误墓。 |
- | dest() 可以用在管道(pipeline)中間用于將文件的中間狀態(tài)寫入文件系統(tǒng)蛮粮。當接收到一個文件時,當前狀態(tài)的文件將被寫入文件系統(tǒng)谜慌,文件路徑也將被修改以反映輸出文件的新位置然想,然后該文件繼續(xù)沿著管道(pipeline)傳輸。 |
watch() |
watch() 方法利用文件系統(tǒng)的監(jiān)控程序(file system watcher)將 globs 與 任務(wù)(task) 進行關(guān)聯(lián)欣范。它對匹配 glob 的文件進行監(jiān)控变泄,如果有文件被修改了就執(zhí)行關(guān)聯(lián)的任務(wù)(task) |
???? 當然,gulp還有很多一些api和比較難的東西恼琼,本人也沒有太深入的理解妨蛹,可以到gulp的官方文檔上面進行查閱。
???? 了解了常用的方法api后晴竞,我們可以對上面的任務(wù)流程進行逐步分解了蛙卤,具體地請看下表:
流程 |
---|
1.src()讀取文件,產(chǎn)生文件元數(shù)據(jù)對象 |
2. plumber()對讀取過程中的錯誤進行處理,防止碎裂管道 |
3. named ()對文件進行任意命名 |
4. gulpWebpack ()使用webpack對模塊進行處理颤难,包括babel轉(zhuǎn)換等 |
5. dest ()保存階段性中間文件 |
6.rename()對文件進行重命名操作 |
7. uglify ()壓縮代碼 |
8.dest()保存處理后的文件 |
9.watch()監(jiān)聽文件的變化神年,對資源進行熱更新操作 |
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”呀艾扮,這是怎么回事呀既琴。根據(jù)官網(wǎng)文檔的解釋,在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、監(jiān)聽服務(wù)端代碼的變化
新建/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)();
});
});
???? 這個任務(wù)自然是監(jiān)聽服務(wù)端代碼的更改的抛虫,當然我們需要安裝第三方包gulp-live-server來幫助我們實現(xiàn)這個功能松靡,至于這個包的具體用法,可以自行查看npm包建椰。
6雕欺、創(chuàng)建瀏覽器監(jiān)聽任務(wù)
新建/tasks/brower.js
???? 上面我們完成的任務(wù)中,server是對/server目錄下的文件變化進行監(jiān)聽棉姐,scripts是將/app/js目錄下的js文件打包進/server/js目錄下屠列,css和pages任務(wù)也都是實現(xiàn)了類似的功能。現(xiàn)在的問題是伞矩, 一旦/app目錄中的文件發(fā)生了變化笛洛,怎么樣通過pages、css乃坤、scripts等任務(wù)苛让,將變化后的文件更新到/server目錄下。這里我們就需要創(chuàng)建brower這個任務(wù)了侥袜,通過這個任務(wù)蝌诡,我們可以實時監(jiān)聽文件的變化,最后將變化后的文件存放到/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浦旱、清除之前打包文件的任務(wù)(clean)
新建/tasks/clean.js
???? 在前端代碼(/app目錄下)發(fā)生改變后,改變后的文件就會被保存在/server目錄下九杂,為了使打包后的代碼保持干凈颁湖,我們希望在每次打包之前都能清除掉/server/public 目錄下的內(nèi)容宣蠕,那么我們就新建了一個clean任務(wù)。
import gulp from "gulp";
import del from "del";
import args from "./util/args";
gulp.task("clean", () => {
return del(["server/public", "server/views"]);
});
8甥捺、構(gòu)建打包(整個流程結(jié)構(gòu))任務(wù)(build)
???? 通過上面所有的任務(wù)抢蚀,我們可以實現(xiàn)我們所有的需求了,現(xiàn)在是時候來把這個流程梳理一下了镰禾,把他們存進一個隊列中皿曲,讓他們按照指定順序進行。
新建/tasks/build
import gulp from "gulp";
import gulpSequence from "gulp-sequence";
gulp.task(
"build",
gulpSequence("clean", "css", "pages", "scripts", ["browser", "server"])
);
從build任務(wù)中吴侦,我們可以看出屋休,我們將要依次進行clean、css备韧、pages劫樟、scripts、["brower","server"]任務(wù)织堂。
9.設(shè)置任務(wù)入口
???? 因為gulp默認會尋找default任務(wù)叠艳,也就是說default任務(wù)是我們?nèi)蝿?wù)的主入口。
新建/tasks/default.js
import gulp from "gulp";
gulp.task("default", ["build"]);
四易阳、運行附较、調(diào)試、優(yōu)化
???? 終于把環(huán)境配置好了闽烙,是不是我們就可以愉快的使用我們的環(huán)境來做一些非常有趣的事情呢翅睛,但是當在命令行運行 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 不再支持下面的調(diào)用捕发,要使用gulp.series()的這種方式
gulp.task(
"build",
gulpSequence("clean", "css", "pages", "scripts", ["browser", "server"])
);
經(jīng)過查找資料得知,這都是gulp升級到4.x帶來的后果很魂,沒辦法扎酷,只能將版本后降到3.x。
npm install --save-dev gulp@3.9.1
???? 再次用gulp 遏匆,我們的任務(wù)都跑完了法挨,沒毛病,但是幅聘,怎么服務(wù)器沒有啟動呀凡纳,瀏覽器輸入了http://localhost:3000x顯示服務(wù)器沒有啟動,怎么辦帝蒿,找了一圈荐糜,發(fā)現(xiàn)/tasks/util/args.js 沒有導(dǎo)出模塊,乖乖加上吧。
export default args;
???? 這次確實可以了暴氏,服務(wù)器也起來了延塑,但是頁面顯示為白板,這是因為我們的/app/views/index.ejs中沒有任何內(nèi)容呀答渔,添加內(nèi)容后关带,刷新頁面,內(nèi)容也出來了沼撕。
???? 美中不足的是宋雏,這個刷新得讓我們手動實現(xiàn),有沒有辦法修改文件保存后自動刷新了务豺,答案肯是有的好芭。不過我們又得借助第三方的模塊connect-livereload。
在/server/app.js中添加代碼
app.use(express.static(path.join(__dirname, "public")));
app.use(require("connect-livereload")());
app.use("/", indexRouter);
???? 注意冲呢,前兩行代碼一種不能呼喚位置,也就是說第二行的代碼一直要在第一行代碼執(zhí)行之后執(zhí)行招狸,這樣會保證資源加載完全敬拓。
???? 好了,已經(jīng)是凌晨1點了裙戏,就這樣吧乘凸。