gulp或者grunt中有哪些神一般的task?


generator-gulp-angular (Swiip/generator-gulp-angular · GitHub)

這是一個Yeoman Generator,最近項目里稍微改了一下在用。其中有兩個task我覺得很實用。

一個是Inject,代碼如下

"use strict";

var gulp = require("gulp");

var $ = require("gulp-load-plugins")();

var wiredep = require("wiredep").stream;

module.exports = function(options) {
gulp.task("inject", ["scripts", "styles"], function () {
var injectStyles = gulp.src([
options.tmp + "/serve/app/**/*.css",
"!" + options.tmp + "/serve/app/vendor.css"
], { read: false });

var injectScripts = gulp.src([
options.src + "/app/**/*.js",
"!" + options.src + "/app/**/*.spec.js",
"!" + options.src + "/app/**/*.mock.js"
])
.pipe($.angularFilesort()).on("error", options.errorHandler("AngularFilesort"));

var injectOptions = {
ignorePath: [options.src, options.tmp + "/serve"],
addRootSlash: true
};

return gulp.src(options.src + "/*.html")
.pipe($.inject(injectStyles, injectOptions))
.pipe($.inject(injectScripts, injectOptions))
.pipe(wiredep(options.wiredep))
.pipe(gulp.dest(options.tmp + "/serve"));

});
};

這個task其實信息量還是有點大的,首先是 gulp-load-plugins 這個插件,它可以自動載入所有devDependencies插件,這樣就不需要在開頭寫一堆require了。

接下來是gulp-inject,這個插件通過在文件中尋找特定的tag,將指定的文件插入到目標文件中,比如Less、Sass的import,CMD的Module,html的JS及css依賴

然後是wiredep,通過檢查bower.json注入依賴,類似於gulp-inject,不過需要bower的包做的比較好,有些支持不是很好。

gulp-angular-filesort,調整angular各模塊的依賴順序。

這個文件擴展一下適用還是比較廣的,大部分模塊化的項目都可以用到,可以說是這個generator裡面比較出彩的一部分了。

第二個是Build,這是一個task的集合,我這裡拿出其實兩個比較重要的:partials和html

gulp.task("partials", function () {
return gulp.src([
options.src + "/app/**/*.html",
options.tmp + "/serve/app/**/*.html"
])
.pipe($.minifyHtml({
empty: true,
spare: true,
quotes: true,
loose: true
}))
.pipe($.angularTemplatecache("templateCacheHtml.js", {
module: "foo",
root: "/app"
}))
.pipe(gulp.dest(options.tmp + "/partials/"));
});

partials這個task的作用是將所有Angular的template合併起來,最後通過angular的templateCache服務將原本的Html載入,優點是減少請求數,在app不大的時候能顯著的提高載入速度。

gulp.task("html", ["otherStyles", "partials"], function () {
var partialsInjectFile = gulp.src(options.tmp + "/partials/templateCacheHtml.js", {read: false});
var partialsInjectOptions = {
starttag: "&",
ignorePath: options.tmp + "/partials",
addRootSlash: false
};

var htmlFilter = $.filter("*.html");
var jsFilter = $.filter("**/*.js");
var cssFilter = $.filter("**/*.css");
var assets;

return gulp.src(options.tmp + "/serve/*.html")
.pipe($.inject(partialsInjectFile, partialsInjectOptions))
.pipe($.replace("/bower", "../bower"))
.pipe(assets = $.useref.assets())
.pipe($.rev())
.pipe(jsFilter)
.pipe($.ngAnnotate())
.pipe($.stripDebug())
.pipe($.uglify({preserveComments: $.uglifySaveLicense})).on("error", options.errorHandler("Uglify"))
.pipe(jsFilter.restore())
.pipe(cssFilter)
.pipe($.csso())
.pipe(cssFilter.restore())
.pipe(assets.restore())
.pipe($.useref())
.pipe($.revReplace())
.pipe(htmlFilter)
.pipe($.minifyHtml({
empty: true,
spare: true,
quotes: true,
conditionals: true,
loose: true
}))
.pipe(htmlFilter.restore())
.pipe(gulp.dest(options.dist + "/"))
.pipe($.size({title: options.dist + "/", showFiles: true}));
});

html基本就是最後的打包流程了,包括注入資源的合併,html、css、js壓縮,版本號自動添加,Angular依賴注入修正等,基本上中小型項目需要做的都做了。

這個generator還提供其他的很多功能,由於項目限制就沒有使用了,總之這是一個結構清晰、代碼風格也比較規範的workflow,是比較適合初學者學習的,缺點就是經常來個大更新,之前版本的代碼完全不能兼容,要升級的話還是有點麻煩,具體代碼可以參見項目的Repo。

以上。


介紹兩個插件。

這兩個插件,是因為我花了近一天的時間各種搜索和試用,一直沒找到合適的,尼瑪然後就自己寫了個。

所以,我覺得,應該還是挺有用的,因為我一直沒找到其他順手的啊。

1、gulp-merge-link

用來合併JS和CSS引用的。

e.g

gulp.task("merge",function(){gulp.src("*.css").pipe(
merge({
"css/all.css":["**/*.css"],
"js/all.js":["**/*.js"]
},{debug:true}
)).pipe(gulp.dest("dist"))})

執行gulp merge前:

&
& & &

因為是在gulp-css-spriter基礎上改了一丟丟,所以取名為2,

另外也因為雪碧圖插件一堆堆的,各種重名只有加個2了,

gulp-css-spriter是我覺得在這一堆堆里算是最好用的了,但是還有有兩點很不爽,

1)過濾採用的是在css文件中聲明的方式,我說過了,我對於這種語法很不爽!

2)不能實現分組。

介於此,在原基礎上,參數options添加了ignore屬性,

1)過濾:ignore:[ "img/*.gif" ]

2)分組:創建多個task,

好了,求贊。。。我就這點樂趣了。。。


這個 task 寫完後感覺挺有趣的. 這個 task 可以讓 gulp 監測 gulpfile 自身的改動, 並且重新啟動自己 :D. 完蛋了, 人工智慧崛起後這種程序好可怕的趕腳.

這個 task 的用途是用 gulp watch 文件的改動並啟動相應的 task ( 我們用 gulp 做增量開發基本都會這麼搞 ). 有趣的地方在於如果 gulpfile 自身發生了改動, 那麼 gulp 將會重新啟動自己. 為了達到這個目的, 這裡需要做兩層 watch, 通過 gulp watch 去啟動 child process 的 deep-watch, 這樣當 gulp 自身改變了以後, 他重啟的部分是 deep-watch. 愚蠢的人類啊, 以為逃出 Matrix 來到 Sion 就是現實了?

var Path = require("path");
var Chalk = require("chalk");

var gulp = require("gulp");
var watch = require("gulp-watch");

// ... some other codes ...

var tasks = {
// ...
};
var gulpfiles = ["**/gulpfile.js", "**/tasks/*.js"].concat(ignores);

function dowatch ( taskName ) {
watch( paths[taskName], function ( file ) {
if ( file.event !== "unlink" ) {
gulp.start(taskName);
}
}).on("error", function (e) {
console.warn(e.message);
});
}
gulp.task("deep-watch", function() {
for ( var name in tasks ) {
dowatch(name);
}
});

gulp.task("watch", function() {
// reload if gulpfile changed
var p;
function restart () {
if ( p ) {
p.kill();
console.log( Chalk.yellow.bold("gulpfile changed, deep-watch restart!") );
}
var spawn = require("child_process").spawn;
p = spawn("gulp", ["deep-watch"], {stdio: "inherit"});
}
watch( gulpfiles, restart ).on("error", function (e) {
console.log( Chalk.red(e.message) );
});
restart();
});


我覺得官方文檔的很多秘籍非常好

gulp-docs-zh-cn/recipes at master · lisposter/gulp-docs-zh-cn · GitHub (中文版本)

貼出目錄來

  • 自動發布工作流
  • 整合 streams 來處理錯誤
  • 刪除文件和文件夾
  • 使用 watchify 加速 browserify 編譯
  • 增量編譯打包,包括處理整所涉及的所有文件
  • 將 buffer 變為 stream (內存中的內容)
  • 在 gulp 中運行 Mocha 測試
  • 僅僅傳遞更改過的文件
  • 從命令行傳遞參數
  • 只重新編譯被更改過的文件
  • 每個文件夾生成單獨一個文件
  • 串列方式運行任務
  • 擁有實時重載(live-reloading)和 CSS 注入的伺服器
  • 通過 stream 工廠來共享 stream
  • 指定一個新的 cwd (當前工作目錄)
  • 分離任務到多個文件中
  • 使用外部配置文件
  • 在一個任務中使用多個文件來源
  • Browserify + Uglify2 和 sourcemaps
  • Browserify + Globs
  • 同時輸出一個壓縮過和一個未壓縮版本的文件
  • Swig 亦即 YAML front-matter 模板


既然問gulp和grunt,自然想到:

https://www.npmjs.com/package/gulp-grunt

https://www.npmjs.com/package/grunt-gulp

這兩個插件作者不一樣,名字怎麼這麼像呢?前者讓你在gulp里調用grunt的任務,後者能在grunt里開gulp的任務。神器啊。


自己有一個task: hanan198501/grunt-cptpl · GitHub

不能說神,但是極大方便了前端 template 的管理。這款task使前端開發也可以像後端一樣,把模板文本存放在單獨的文件中(而不是放在dom節點但或者手工js拼接字元串), 然後將其編譯成javascript文件。使我們的開發工作從繁瑣的dom操作和JS拼串中解放出來,提高我們的開發效率。最主要的,模板文件作為單獨文件存放,可以使我們的項目代碼邏輯更加清晰,更具可維護性。

都支持啥模板引擎?

grunt-cptpl支持各大主流模板引擎:artTemplate、 Handlebars 、Hogan 、 Underscore、 juicer、 doT、 kissy、 baiduTemplate。 對於不在此列的引擎,提供了自定義編譯方法介面。

這玩意都做了啥?

grunt-cptpl會讀取每個模板文件的文本內容,用指定模板引擎的預編譯方法將其包裹起來,生成一個新的javascript文件。這個javascript文件文件裡面的內容,其實就是模板引擎的預編譯方法調用,傳入的參數為模板文件的文本內容。這樣我們就有了一個編譯好的模板函數,要渲染的時候把數據傳給它就好了。

歡迎一試哈~


剛剛寫了一個,用於express的自動構建,可以一行指令新建一個新的頁面及路由。

gulp.task("new_view",function(name){
return gulp.src("templates/page.handlebars")
.pipe(template({name: name?name:"new"}))
.pipe(rename({
basename:name
}))
.pipe(gulp.dest("views"));
})

gulp.task("new_router",function(name){
return gulp.src("templates/router.js")
.pipe(template({name: name?name:"new"}))
.pipe(rename({
basename:name
}))
.pipe(gulp.dest("routes"));
})

gulp.task("new_less",function(name){
return gulp.src("templates/style.less")
.pipe(template({name: name?name:"new"}))
.pipe(rename({
basename:name
}))
.pipe(gulp.dest("src/less"));
})

gulp.task("inject_app",function(name){
return gulp.src("app.js")
.pipe(replace("//newFilesRouter", "//newFilesRouter
var router_"+name+" = require("./routes/"+name+"");"))
.pipe(replace("//newRouter", "//newRouter
app.use("/"+name+"", router_"+name+");"))
.pipe(gulp.dest("./"))
})

gulp.task("new",function (name,router) {
if(router)
return runsequence(["new_view","new_less","new_router","inject_app"]);
else
return runsequence(["new_view","new_less"]);
})

這些task可以生成對應名稱的頁面,樣式文件和配置路由,同時在app.js中註冊路由。

執行代碼

npm new --name newpage --router

記得yeoman有一個基於grunt的angular構建工具可以做到在控制台生成新的組件,但是基於gulp的沒有見到過,另外就是注入js文件用了一點奇技淫巧,替換標識注釋的同時再次更新的標識注釋,用replace實現了inject。


useref,合併html中指定的js,css

browser-sync,更改頁面自動刷新瀏覽器,雙屏必備,那一個酸爽


補充一下:

gulp-connect-proxy 代理外部的介面

如果基於現成的介面開發,就省了 mock 數據這一步,更方便;

我當前的個人項目的開發模式 python/php/ruby/postgrest + gulp/grunt, 一邊都是後端的介面先開發完,然後開始開發前端,這個代理插件給我帶來了極大的方便,

有人說直接在後端伺服器上開發,那就沒法使用 gulp-server-livereload(@droiz)這種好用的自刷新的組件(或者其他的前端工具了)了,我還是偏喜歡前後端開發環境分離

var Proxy = require("gulp-connect-proxy");
...
// A local web server for dev convenience
gulp.task("server", function() {
connect.server({
root: "./dist",
port: 22532,
middleware: function (connect, opt) {
opt.route = "/proxy";
var proxy = new Proxy(opt);
return [proxy];
}
});
});


自己有一個task,不算神,但日常來說還是挺方便。livereload+sass自動編譯+簡易webserver。

從編輯器複製來的,湊活看吧,,,

var gulp = require("gulp"),

sass = require("gulp-sass"),

sourcemaps = require("gulp-sourcemaps"),

server = require("gulp-server-livereload");

gulp.task("start", function() {

gulp.src("stylesheet/style.scss")

.pipe(sourcemaps.init())

.pipe(sass().on("error", sass.logError))

.pipe(sourcemaps.write("./map"))

.pipe(gulp.dest("./stylesheet"));

gulp.src("stylesheet/style2.scss")

.pipe(sourcemaps.init())

.pipe(sass().on("error", sass.logError))

.pipe(sourcemaps.write("./map"))

.pipe(gulp.dest("./stylesheet"));

});

gulp.task("default", ["webserver"], function() {

gulp.watch(["stylesheet/style.scss", "stylesheet/style2.scss"], ["start"])

.on("change", function(event) {

console.log("rebuild "+event.path);

});

});

gulp.task("webserver", function() {

gulp.src("./")

.pipe(server({

defaultFile: "index.html",

livereload: {

enable: true,

filter: function(filePath, cb) {

cb( !(/node_modules/.test(filePath)) );

}

}

}));

});


沒人回答?好,我來。

不是glup黨,所以說說我用的grunt。

0 concat 基礎工具。一般來說我覺得它很有用,在開發環境中

1 cssmin 這個task比較牛,自動優化合併css,不是單單的壓縮,還有自動生成css patch,比如你寫transition類css3,會自動為你補上瀏覽器前綴。grunt-contrib 主力維護項目,之前還為它repo過issue

2 requirejs 如果是用AMD 的requirejs 做模塊載入器,首選它作為js壓縮工具,內置的r.js 是專門為requirejs設計的js合併壓縮parser,將define都合併到一個文件里。

3 imagemin node圖片壓縮首選,有幾級level優化,追求pref

4 還有個工具忘記叫什麼名字了, 之前用過, 用來檢索所有項目 HTML 和 CSS 文件, 分析CSS依賴, 自動刪除掉沒有用的CSS, 尤其是大項目, 做這樣的優化很有必要, 在開發過程中, 有些CSS有可能在改了又改之後就沒有地方需要了。jsconf 上有介紹過


這個必須是[grunt-usemin](yeoman/grunt-usemin · GitHub)啊,功能很強大,但是配置沒人看得懂!!


front-end-separate(前後端分離腳手架)

front-end-separate

一個前後端分離的腳手架工具(自主研發)

為什麼選擇grunt而不是gulp

如果你也和我一樣喜歡grunt這種配置的方式,那麼我相信這個腳手架覺對十分適合你

所有靜態資源都md5全並壓縮打包,css,js,img,html

已在生產環境驗證

基於express和grunt的前後端分離框架

模板引擎使用的是nunjucks,好處是可以實現模版繼承,又不像jade一樣把html標籤都簡化了

express提供路由服務

項目中app為原代碼文件(開發用),dist為打包後的文件(用於線上)

開發使用app,線上使用dist,支持一鍵cdn部署,加速你的項目

項目啟動時,修改任何express代碼,可以實現自動重啟–基於nodemon

支持sass圖片精靈(自動打包精靈圖片,再也不用手動去拼湊了)

基於grunt md5 打包合併

線上輸出的html已經壓縮成一行(讓你的代碼更有Geeker范)

###怎麼使用:

clone 代碼

啟動命令行:

如果沒有安裝grunt,請先全局安裝grunt bash $ npm i grunt-cli -g 安裝npm包(可能需要一段時間,請耐心等待)

$ npm i

開發模式(可以打開瀏覽器localhost:3001開始開發,埠配置文件里可以更改)

$ grunt

打包

$ grunt build

打包成CDN模式(config/config.js中可配置cdn路徑)

$ grunt buildCdn

browserSync(可以實現更改靜態資源自動刷新了)

$ grunt serve

瀏覽器輸入localhost:3001,你就可以看到漂亮的頁面了

打包命令 grunt build 會生成dist文件夾,裡面可以看到js、css都加了md5綴

tip:scss推薦用Webstorm自帶的File watch功能,非常方便(安裝node-sass即可)

-關於圖片精靈

$ grunt sprite 執行即可得到精靈圖片,如需配置請去config/grunt/sprite.js下配置更多的圖片精靈

在scss中引用@import 「sprite」 樣式中寫如:@include sprite($index_bg); 即可使用;

如果大家喜歡的話,請點一下star此項目或follow一下本人,即是對本人最大的支持

我會繼續完善這個項目的,並一直維護下去,如果有任何問題,歡迎在issues裡面提出

感謝大家支持!

——Nobody


在node中使用gulp,自動刷新browserSync,應該要怎麼引入 入口文件app.js ? -&> nodemon !

// 程序入口
gulp.task("nodemon", function (cb) {
var called = false;
return nodemon({
script: "app.js",
ext: "js",
ignore: ["public/**"],
env: {"NODE_ENV": "development"}
})
.on("start", function onStart() {
if(!called){cb();}
called = true;
})
.on("restart", function() {
setTimeout(function() {
console.log("-------- restart --------");
reload({stream: false});
}, 1000);
});
});
// 監聽變化
gulp.task("browser-sync", ["nodemon"], function(){
browserSync.init({
files: ["public/**","views/**"],
proxy: "http://localhost:3000",
port: 4000,
browser: ["/Applications/Google Chrome Canary.app/"],
notify: true,
});
});

前人的基礎上修改了一下,能做到:

1. 監聽 項目 靜態資源模塊以外 js文件變化(如:修改route文件),重啟服務 自動刷新頁面。

2. 監聽 模板文件,靜態資源文件變化,自動刷新頁面。

其他 task 可以根據項目情況,自主選擇加入。


默默打個廣告(內心:宣傳的好機會!)

前幾天寫了一個gulp插件,作用是自定義可帶參數的task命令(不是來自命令行的參數

感覺應該有點用,不要臉地推一下

首先是 github 地址:GitHub - CJY0208/gulp-foal: Allow you to run task with param (NOT from cmd) when gulp"s running.

上用法示例

"use strict";

var gulp = require("gulp");
var clean = require("gulp-clean");
var foal = require("gulp-foal");

//使用 foal.task(...) 來定義一個 foal 任務
foal.task("clean_some", function(cleanPath) {
//為了順序執行 foal 任務,請別省略 return 語句
return gulp.src(cleanPath)
.pipe(clean());
});

gulp.task("default", function(cb) {
//使用 foal.run(...) 來執行帶參數(當然也可以不帶)的 foal 任務
foal.run(clean_some("test_path"), cb);
});

關於 foal.run(...)

採用了類似 gulp-sequence 調用方式,就是說能這樣用

foal.run(task1("param"), [task2("param"), task3()], task4("param"), cb);

gulp-sequence地址: GitHub - teambition/gulp-sequence: Run a series of gulp tasks in order

關於foal.task(...)

foal 任務默認被綁定在全局環境下, 如果不希望這樣,可以指定bindToFoal參數

foal.task("test", {
bindToFoal: true
}, function(param) {/*...*/});

foal.run(foal.test(param));

我說完了...默默求贊


我做了一個簡單的方案,針對單頁面的js和css打包處理,對當前項目侵入少,我的方案https://github.com/saopang/gulp-page,支持base64,支持雪碧圖,md5改了3處源碼,js支持簡單的合併壓縮混淆方案,也支持webpack打包,支持es6,歡迎star,京東的jdf和百度的fis和騰訊的tmt都是基於項目的打包方案,適合他們公司自己,但使用起來麻煩而且對國內開源項目沒有信心,網上的方案五花八門 。總結了下用useref的可配置性不強,用gulprev配合其他插件的md5會有文件冗餘,而且很多的sourcemap也有問題,我的方案是單獨放到一個dist目錄,發布到線上就刪掉maps目錄,既不會泄露源碼又方便調試。主要是對網上很多方案失望,不適合自己的項目,所以自己做了一個,歡迎大家提建議!


後端nodejs,項目構建工具grunt,模塊打包webpack。

之前,每次改動伺服器js代碼後,都要手動重啟伺服器,真是疼;

之前,每次改動邏輯代碼後,都要重新打包js,真是疼;

之前,每次改動css後,都要刷新瀏覽器,真是疼;

現在,一切都是自動化,一切都是所見即所得,以下 ,是我的app項目自動化配置。

文件壓縮跟加密webpack都能幹,grunt主要是監測文件改動。

var path = require("path");

var webpack = require("webpack");

var webpackConfig = require("./project/webpack.config.js");

module.exports = function (grunt) {

grunt.initConfig({

pkg: grunt.file.readJSON("package.json"),

react: {

dynamic_mappings: {

files: [

{

expand: true,

cwd: "project/js",

src: ["*.js"],

dest: "project/src",

ext: ".js"

}

]

}

},

uglify: {

options: {

banner: "/*! &<%= pkg.name %&> &<%= grunt.template.today("yyyy-mm-dd") %&> */
"

},

build: {

// Grunt will search for "**/*.js" under "lib/" when the "uglify" task

// runs and build the appropriate src-dest file mappings then, so you

// don"t need to update the Gruntfile when files are added or removed.

files: [

{

expand: true, // Enable dynamic expansion.

cwd: "project/", // Src matches are relative to this path.

src: ["bundle.js"], // Actual pattern(s) to match.

dest: "project/", // Destination path prefix.

ext: ".min.js", // Dest filepaths will have this extension.

}

]

}

},

jshint: {

files: ["project/src/*.js"],

options: {

globals: {

exports: true

}

}

},

cssmin: {

options: {

banner: "/*! &<%= pkg.name %&> &<%= grunt.template.today("yyyy-mm-dd") %&> */
"

},

dynamic_mappings: {

// Grunt will search for "**/*.js" under "lib/" when the "uglify" task

// runs and build the appropriate src-dest file mappings then, so you

// don"t need to update the Gruntfile when files are added or removed.

files: [

{

expand: true, // Enable dynamic expansion.

cwd: "project/css", // Src matches are relative to this path.

src: ["*.css"], // Actual pattern(s) to match.

dest: "project/build", // Destination path prefix.

ext: ".css", // Dest filepaths will have this extension.

}

]

}

},

filerev: {

options: {

algorithm: "md5",

length: 16

},

files: {

expand: true,

cwd: "project/",

src: ["bundle.js"],

dest: "project"

}

},

express: {

options: {

port: 3000,

background: true

},

dev: {

options: {

script: "./project/app.js",

}

}

},

webpack: {

options: webpackConfig,

dev: {

devtool: "sourcemap",

debug: true

}

},

watch: {

connect: {

files: ["project/*.js", "project/libs/*.js", "project/views/*.ejs", "project/public/css/*.css"],

options: {

livereload: true

}

},

webpack: {

files: ["project/libs/*.js", "project/webpack.config.js"],

tasks: ["webpack:dev"],

options: {

spawn: false,

}

},

express: {

files: ["project/app.js", "project/server/*.js"],

tasks: ["express:dev"],

options: {

spawn: false

}

}

}

});

// grunt.loadNpmTasks("grunt-react");

// grunt.loadNpmTasks("grunt-contrib-uglify");

// grunt.loadNpmTasks("grunt-contrib-jshint");

// grunt.loadNpmTasks("grunt-contrib-cssmin");

// grunt.loadNpmTasks("grunt-contrib-connect");

// grunt.loadNpmTasks("grunt-contrib-imagemin");

// grunt.loadNpmTasks("grunt-usemin");

// grunt.loadNpmTasks("grunt-filerev");

grunt.loadNpmTasks("grunt-webpack");

grunt.loadNpmTasks("grunt-contrib-watch");

grunt.loadNpmTasks("grunt-express-server");

grunt.registerTask("default", ["webpack:dev", "express", "watch"]);

};


怒答一記~

Material-ui項目里學習到的整個gulp

  1. 提取出來了,針對jsx(es6),用到了babel browserify

  2. 沒有添加css預處理,可以自行添加sass或者less...

  3. uglify在最終build的時候把注釋去掉,就能用了喲~

github地址 : https://github.com/dcalsky/gulp-jsx-es6

star了以後建構項目就不yeoman自動生成的gulp了(雖然可以自己寫,但是肯定沒這麼好噠)


推薦閱讀:

前端框架可以直接使用,為何需要nodejs/gulp等工具?

TAG:JavaScript | 編程 | Nodejs | grunt | gulp |