Gulp,一個基於流的構建工具。css
這是本身寫的一個構建的demo,只是一個純演示的示例,並無完成什麼項目工做。下面根據這個demo介紹一下Gulp。html
上代碼:node
gulpfile.jsjquery
'use strict'; /** * 環境 */ const env = process.argv.slice(6)[0] || 'development'; process.env.NODE_ENV = env; const isDev = env == 'development'; /** * 依賴 */ const browserSync = require('browser-sync'); const reload = browserSync.reload; const watchify = require('watchify'); const browserify = require('browserify'); const babelify = require('babelify'); const envify = require('envify/custom'); const del = require('del'); const source = require('vinyl-source-stream'); const buffer = require('vinyl-buffer'); const gulp = require('gulp'); const $ = require('gulp-load-plugins')(); /** * path */ const pathConf = require('./path.js'); const path = pathConf.path; const filePath = pathConf.filePath; /** * browserify & watchfy */ // browserify conf const customOpts = { entries: ['./src/main.js'], debug: true }; // watchfy conf const opts = Object.assign({}, watchify.args, customOpts); // init watchfy const browserify_watch = watchify(browserify(opts)); browserify_watch.transform(babelify, { presets: ["es2015", "stage-2"], plugins: ["transform-runtime"] }); browserify_watch.transform(envify({ NODE_ENV: env })); function jsBundler (ids){ return browserify_watch .bundle() .on('error', $.util.log) .pipe(source("build.js")) .pipe(buffer()) .pipe($.if(isDev, $.sourcemaps.init({loadMaps: true}))) .pipe($.uglify()) .pipe($.if(isDev, $.sourcemaps.write("."))) .pipe(gulp.dest(path.dist)) } browserify_watch.on('update', jsBundler); browserify_watch.on('log', $.util.log); /** * gulp task */ gulp.task('cleanAll', () => { del([ path.temp + '**/.{html.js.css}', path.dist + '**/.{html.js.css}', '!' + path.dist + 'video/**' ]); }); gulp.task('serve', () => { browserSync({ server: { // proxy: 'xx.xx.xx.xx', baseDir: __dirname } }); }); gulp.task('jsBundle', jsBundler); gulp.task('less', () => { return gulp.src(filePath.src_less) .pipe($.less()) .on("error", function (error){ $.util.log(error); this.emit("end"); }) .pipe(gulp.dest(path.temp + 'css/')); }); gulp.task('cssBundle', ['less'], () => { return gulp.src([filePath.temp_css]) .pipe($.if(isDev, $.sourcemaps.init({loadMaps: true}))) .pipe($.concat('build.css')) .pipe($.minifyCss()) .pipe($.if(isDev, $.sourcemaps.write('.'))) .pipe(gulp.dest(path.dist)); }); gulp.task('watchLess', () => { gulp.watch([filePath.src_less], ['cssBundle']); }); gulp.task('watchDist', () => { gulp.watch(['index.html', path.dist + '*.*'], reload); }); /** * cli * '$ gulp build' for development * '$ npm run dev' for development * '$ npm run pro' for production */ gulp.task('build', $.sequence( 'cleanAll' , ['jsBundle', 'cssBundle'] , ['watchLess', 'watchDist'] , 'serve' ));
package.jsonwebpack
{ "name": "gulp", "description": "gulp", "author": "james", "scripts": { "dev": "gulp build --gulpfile gulpfile.js --env development", "pro": "gulp build --gulpfile gulpfile.js --env production" }, "dependencies": { "jquery": "^3.1.1" }, "devDependencies": { "babel-core": "^6.17.0", "babel-plugin-transform-runtime": "^6.15.0", "babel-preset-es2015": "^6.16.0", "babel-preset-stage-2": "^6.17.0", "babel-preset-stage-3": "^6.17.0", "babel-runtime": "^6.11.6", "babelify": "^7.3.0", "browser-sync": "^2.17.3", "browserify": "^13.1.0", "del": "^2.2.2", "envify": "^3.4.1", "gulp": "^3.9.1", "gulp-changed": "^1.3.2", "gulp-concat": "^2.6.0", "gulp-debug": "^2.1.2", "gulp-filter": "^4.0.0", "gulp-if": "^2.0.1", "gulp-inject": "^4.1.0", "gulp-jshint": "^2.0.1", "gulp-less": "^3.1.0", "gulp-load-plugins": "^1.3.0", "gulp-minify-css": "^1.2.4", "gulp-minify-html": "^1.0.6", "gulp-notify": "^2.2.0", "gulp-rename": "^1.2.2", "gulp-rev-all": "^0.9.7", "gulp-sequence": "^0.4.6", "gulp-sourcemaps": "^2.0.1", "gulp-uglify": "^2.0.0", "gulp-util": "^3.0.7", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0", "watchify": "^3.7.0" } }
文件結構以下:es6
結構說明:web
main.js 爲入口文件npm
path.js 定義了一些路徑變量json
src 文件夾存放原始代碼gulp
dis 文件夾存放打包後的代碼
temp 文件夾存放零食文件
經過browserify轉譯commjs和es6語法,經過gulp-less轉譯less語法,把轉譯後的文件輸出到dist文件夾。
下面經過一些主要代碼介紹一下Gulp的工做方式:
先看這段代碼:
function jsBundler (ids){ return browserify_watch .bundle() .on('error', $.util.log) .pipe(source("build.js")) .pipe(buffer()) .pipe($.if(isDev, $.sourcemaps.init({loadMaps: true}))) .pipe($.uglify()) .pipe($.if(isDev, $.sourcemaps.write("."))) .pipe(gulp.dest(path.dist)) }
它主要是在watchify監聽到依賴的js文件有變更以後,會自動調用的一個回調函數。函數內部實現步驟爲:
1.經過browserify以main.js爲入口文件,打包依賴的全部js,並經過browserify的es2015預設插件來轉譯es6語法。最後將生成的流經過管道傳輸給vinyl-source-stream這個插件
2.普通流經過vinyl-source-stream處理之後再傳輸給vinyl-buffer
3.vinyl-buffer處理之後的流再傳輸給gulp-sourcemaps插件(若是環境條件判斷經過的話),生成原始文件和打包後的文件映射關係(便於調試)
4.再傳輸給gulp-uglify插件進行壓縮和混淆
5.輸出soucemap
6.輸出打包後的js文件到dist文件夾
看到這個流程難免有些疑問:爲啥browserify傳輸出流不能直接給gulp插件使用呢,要先經過vinyl插件的系列處理才能夠?
緣由是:browserify處理後輸出的流只是普通的node.js流,gulp的插件須要的是通過處理的vinyl流。vinyl能夠把普通的node.js的流轉換爲Vinyl File Object Stream。這樣,至關於把普通的流接入到了Gulp的處理體系內。若是須要完整流,能夠經過vinyl-buffer插件接收完整個流轉成二進制數據之後再進行處理,一般gulp-soucemaps、gulp-uglify這些須要完整文件進行映射或者替換的插件都須要buffer一次。不然在任務運行的時候會報流不支持的錯誤:
vinyl流的特色是它是Object風格的,除了流的內容content,還有path等屬性。記錄了文件的內容和文件名、文件路徑的信息。
一個很好的例子能夠解釋爲何須要這樣:
gulp.task("css", () => { gulp.src("./src/**/*.css") .pipe(gulp.dest("./dist/css/")); });
這個Gulp任務很簡單,就是把src文件夾下面全部的css(不管子文件夾層級)都'搬運"到./dist/css/這個路徑下。任務執行結果固然代表這是一次成功的搬運。
單仔細看看不難發現一個蹊蹺的事情:./src/下的文件夾是不定的,能夠是不少層,也能夠只有一層,每一個css文件能夠是任何名字,那麼在dest到./dist/css/還能對應上文件夾層級和文件名,這是怎麼作到的呢?
其實就是經過vinyl流(對象)的path那些屬性來知道文件夾層級和文件名的。普通的node.js流只傳輸String或Buffer類型,也就是隻關注content。
Gulp不僅用到了文件的內容,並且還用到了這個文件的相關信息。所以,vinyl流就會有contents、path這樣的多個屬性了。
可是Gulp自己並不能直接生成vinyl流對象,而是依賴了一個叫作vinyl-fs的node模塊,這是一個相似於文件適配器的模塊。它提供三個方法:.src()、.dest()和.watch(),其中.src()將生成vinyl流對象,而.dest()將使用vinyl流,進行寫入操做。
在Gulp的包中能夠看到源碼:
var vfs = require('vinyl-fs'); // ... Gulp.prototype.src = vfs.src; Gulp.prototype.dest = vfs.dest; // ...
能夠看到gulp的src和dest方法其實就是vinyl-fs的src和dest方法。
深刻探究以後能夠發現這麼一條依賴關係:gulp -> vinyl-fs ->vinyl -> glob-stream -> node-glob
從gulp.src開始到gulp.dest結束這一系列的工做流程大概是這樣的:
首先經過node-glob來匹配路徑和文件,而後glob-stream把匹配到的文件流式讀取, 再經過vinyl輸出Gulp體系須要用到的vinyl流對象,src完成工做後 流被pipe下去,直到gulp.dest時候,經過先前已知的文件路徑和文件名進行寫入操做。
若是是從webpack/browserify的普通流開始,會有它們本身的一套文件流失讀取方式和輸出,只要經過vinyl改造後輸出vinyl流對象就能接入進Gulp體系。
值得注意的是:寫入是一個異步的操做。任務結束之後,不必定表示文件已經所有寫入!
Gulp比起Grunt更具的node.js風格, 並且其依賴的orchestrator的特性就是最大併發的執行任務。在提供了大併發和node.js異步操做特性的同時也帶來了很差的一點:鏈式任務很差控制。
這也是爲何build這個task是經過gulp-sequence來嚴格按照既定順序執行的(經過返回流和其餘異步處理的方式也能夠達到效果):
gulp.task('build', $.sequence( 'cleanAll' , ['jsBundle', 'cssBundle'] , ['watchLess', 'watchDist'] , 'serve' ));
咱們必定不會但願這類事情發生:一邊寫入文件一邊又在刪除;下一個任務須要上個任務提供必要文件,但在執行的時候文件尚未寫入完;等等....
Gulp常被用於和老前輩Grunt相比較,通常都說Gulp的速度快,這其實只是Gulp基於管道流的處理速度相對於Grunt不停地生成臨時文件要快。讀取和寫入(I/O)操做還要看機器自身狀況。
Gulp意爲"狼吐虎咽",即擁有基於流的快速與支持高併發的特性。