gulp進階構建專案由淺入深
閱讀目錄
- gulp基本安裝和使用
- gulp API介紹
- gulp一些常用外掛
- gulp構建小型專案的基本過程
gulp基本安裝和使用
Gulp的構建過程:gulp是使用nodejs中的stream(流),首先通過gulp.src()方法獲取到我們需要的檔案流(stream),然後把檔案流通過pipe()方法把流匯入到gulp的外掛中,最後通過外掛處理後的流再通過pipe()方法匯入到gulp.dest()中,gulp.dest()方法把流中的內容寫入到檔案中。
1. Gulp安裝:
首先我們需要安裝nodejs,然後進行全域性安裝;安裝如下:
sudo npm install gulp –g
全域性安裝後,還需要切換到專案的根目錄下,單獨為單個專案進行安裝下;安裝如下:
sudo npm install gulp
如果想在安裝的時候把gulp寫進package.json檔案的依賴中,則可以加上 –save-dev
sudo npm install –save-dev gulp
2. 如何使用gulp?
在專案的根目錄下新建一個 gulpfile.js檔案,之後就可以定義一個任務了;
比如如下簡單的任務:程式碼如下:
var gulp = require('gulp'); gulp.task('default',function(){ console.log('hello world'); });
現在我專案的目錄結構假如是如下樣子:
最後我們進行命令列切換到專案的根目錄下,執行gulp命令後,就可以在控制檯看到consoe.log的列印的訊息了;
gulp API介紹
最常見的我們使用四個API,gulp.task() gulp.src() gulp.dest() gulp.watch();
該方法是獲取我們需要的檔案流,這個流裡面的內容不是原始的檔案流,而是一個虛擬檔案物件流(Vinyl files);該方法有2個引數
globs型別是 String 或 Array , 該檔案流可以是一個單獨的字串形式,也可以是一個陣列形式;
options是一個物件型別;該物件型別有一個我們常用的base欄位配置 options.base是經常會使用的到;
比如如下程式碼:
var gulp = require('gulp'); var uglify = require('gulp-uglify'); gulp.task("uglify-js",function(){ return gulp.src("src/js/*.js") .pipe(uglify()) .pipe(gulp.dest('build')); }); gulp.task('default',['uglify-js']); // 寫入到 build/a.js 和 build/index.js
如下目錄結構:
我們使用base欄位來繼續編寫如下程式碼:
var gulp = require('gulp'); var uglify = require('gulp-uglify'); gulp.task("uglify-js",function(){ return gulp.src("src/js/*.js",{ base: 'src' }) .pipe(uglify()) .pipe(gulp.dest('build')); }); gulp.task('default',['uglify-js']);
我們再在專案的根目錄下面執行gulp命令可以看到專案的目錄結構變為如下:
因此我們可以理解base欄位為相對於路徑來進行打包,最後生成 build/js/*.js檔案;
看看Gulp用到的glob的匹配規則:
Gulp內部使用了node-glob模組來實現檔案匹配功能。該檔案匹配類似於JS中的正規表示式;如下匹配:
* 匹配檔案路徑中的0個或者多個字元,但是不會匹配路徑分隔符。比如: 可以匹配 abc.js,x.js,aaa,abc/(路徑分隔符出現在末尾也可以匹配);但是不能匹配類似於這樣的路徑分隔符 abc/aa.js
*.* 可以匹配a.xxx; xxxx.yyyy;等
*/*/*.js 可以匹配a/b/c.js,但不是不能匹配 a/b.js 或者 a/b/c/d.js
** 可以匹配路徑中的0個或者多個目錄及其子目錄。比如:能匹配abc,a/b.js,
a/b/c.js,x/y/z等等;
**/*.js 能夠匹配a.js , a/a.js,a/aaa/aaaa/a.js等等;也就是說只要以.js結尾的,不管前面有多少個檔案或者分隔符都可以匹配;
a/**/z 能匹配a/z,a/b/z, a/b/c/z等等。
a/**b/z 能匹配a/b/z,a/sb/z, 不是不能匹配a/x/y/xxb/z;
?.js 能匹配a.js,b.js,c.js,相當於js正則裡面的一樣匹配0個或者1個,優先匹配;
[xyz].js 能匹配x.js,y.js,z.js,類似於js正則一樣,中括號中的任意一個字元;
[^xyz].js 除了中括號中的x,y,z中的其他的任意一個字元;
當有多種匹配模式的時候可以使用陣列,如下:
gulp.src([‘js/*.js’,’css/*.css’]);
我們也可以排除一些檔案,可以使用!, 比如如下程式碼:
gulp.src([‘js/*.js’,’css/*.css’,’!reset.css’]) ; 匹配所有的js/目錄下的js檔案及匹配css/目錄下的css檔案,但是不包括reset.css檔案;但是不能把排除寫在第一個元素位置;
比如如下程式碼: gulp.src([‘!reset.css’,’css/*.css’]); 這樣的是排除不掉的,這種方式我們在css中可以理解為後面的程式碼覆蓋前面的,因此需要寫在後面才能排除當前的;
該方法可以理解為把目標的原始檔通過pipe方法匯入到gulp外掛中,最後把檔案流寫到目標檔案中;如果該檔案不存在的話,則會自動建立它;比如上面的gulp.src(),目錄結構一剛開始build目錄是沒有的,通過打包後自動建立build資料夾;
欄位path: 檔案被寫入的路徑(輸出的目錄),也可以傳入一個函式,在函式中返回相應的路徑。
欄位options也是一個物件型別;
Gulp.dest(path) 生成的檔案路徑是相對於gulp.src()中有萬用字元開始出現的那部分路徑。
比如如下程式碼:
var gulp = require('gulp'); var uglify = require('gulp-uglify'); gulp.task("uglify-js",function(){ return gulp.src("src/js/*.js") .pipe(uglify()) .pipe(gulp.dest('build')); }); gulp.task('default',['uglify-js']);
gulp.src()上面有萬用字元的是 *.js, 因此最後生成的檔案路徑是 build/*.js;
但是如果沒有出現萬用字元的情況下,比如如下程式碼:
gulp.src("src/js/a.js")
.pipe(gulp.dest('build'));
那麼最後生成的路徑就是 build/a.js 了;
當然我們可以在gulp.src()方法中配置base屬性,如果沒有配置base屬性的話,那麼預設生成的路徑就是相對於萬用字元出現的那部分路徑;如果設定了base屬性的話,那麼就相對於base的那個設定的路徑;假如現在的源目錄結構為src/js/下游很多js檔案
比如如下程式碼:
var gulp = require('gulp'); var uglify = require('gulp-uglify'); gulp.task("uglify-js",function(){ return gulp.src("src/js/*.js",{ base: 'src' }) .pipe(uglify()) .pipe(gulp.dest('build')); }); gulp.task('default',['uglify-js']);
是相對於src檔案下的,因此最後生成的路徑為 build/js/*.js
該方法是定義一個任務;
name: 是任務的名字;
deps: {Array} 型別是陣列型別;一個包含任務列表的陣列,這些任務會在你當前任務執行之前完成;比如如下程式碼:
gulp.task('mytask', ['one', 'two', 'task', 'names'], function() {
// 做一些事
});
比如上面的程式碼,我們想要完成'mytask'這個任務的話,首先會執行依賴陣列中的那些任務,但是如果依賴任務裡面又使用了非同步的方法,比如使用setTimeout這樣的時候,那麼這個時候,我再執行mytask這個任務的時候,就不會等待該依賴任務完成後再執行了;比如如下程式碼:
var gulp = require('gulp'); gulp.task('one',function(){ //one是一個非同步執行的任務 setTimeout(function(){ console.log('one is done') },5000); }); //two任務雖然依賴於one任務,但並不會等到one任務中的非同步操作完成後再執行 gulp.task('two',['one'], function(){ console.log('two is done'); }); gulp.task('default',['two']);
上面的例子中我們執行two任務時,會先執行one任務,但不會去等待one任務中的非同步操作完成後再執行two任務,而是緊接著執行two任務。所以two任務會在one任務中的非同步操作完成之前就執行了。
但是如果我們想等待one任務中的setTimeout執行完成後,再執行two這個任務該怎麼辦呢?
我們可以使用如下方法,程式碼如下:
var gulp = require('gulp'); gulp.task('one',function(fn){ // fn 為任務函式提供的回撥 用來通知該任務已經完成 //one是一個非同步執行的任務 setTimeout(function(){ console.log('one is done'); fn(); },1000); }); //two任務雖然依賴於one任務,但並不會等到one任務中的非同步操作完成後再執行 gulp.task('two',['one'], function(){ console.log('two is done'); }); gulp.task('default',['two']);
用來監聽檔案的變化,當檔案發生改變的時候,我們可以用它來執行相應的任務;
引數如下:
glob: 為要監聽的檔案匹配模式,規則和gulp.src中的glob相同;
opts: 為一個可選的配置物件,一般不怎麼用;
tasks: 為檔案變化後要執行的任務,為一個陣列;
比如程式碼如下:
gulp.task('two', function(){
console.log('two is done');
});
gulp.watch('js/**/*.js',['two'])
gulp一些常用外掛
用來重新命名檔案流中的檔案。
安裝:npm install --save-dev gulp-rename
比如如下程式碼:
var gulp = require('gulp'), rename = require('gulp-rename'), uglify = require("gulp-uglify"); gulp.task('rename', function () { gulp.src('src/**/*.js') .pipe(uglify()) //壓縮 .pipe(rename('index.min.js')) .pipe(gulp.dest('build/js')); }); gulp.task('default',['rename']); //關於gulp-rename的更多強大的用法請參考https://www.npmjs.com/package/gulp-rename
安裝:npm install --save-dev gulp-uglify
還是上面的gulpfile.js程式碼如下:
var gulp = require('gulp'), rename = require('gulp-rename'), uglify = require("gulp-uglify"); gulp.task('rename', function () { gulp.src('src/**/*.js') .pipe(uglify()) //壓縮 .pipe(rename('index.min.js')) .pipe(gulp.dest('build/js')); }); gulp.task('default',['rename']);
安裝:npm install --save-dev gulp-minify-css
程式碼如下:
var gulp = require('gulp'), minifyCss = require("gulp-minify-css"); gulp.task('minify-css', function () { gulp.src('src/**/*.css') // 要壓縮的css檔案 .pipe(minifyCss()) //壓縮css .pipe(gulp.dest('build')); }); gulp.task('default',['minify-css']);
安裝:npm install --save-dev gulp-minify-html
程式碼如下:
var gulp = require('gulp'), minifyHtml = require("gulp-minify-html"); gulp.task('minify-html', function () { gulp.src('src/**/*.html') // 要壓縮的html檔案 .pipe(minifyHtml()) //壓縮 .pipe(gulp.dest('build')); }); gulp.task('default',['minify-html']);
安裝:npm install --save-dev gulp-concat
程式碼如下:
var gulp = require('gulp'), concat = require("gulp-concat"); gulp.task('concat', function () { gulp.src('src/**/*.js') //要合併的檔案 .pipe(concat('index.js')) // 合併匹配到的js檔案並命名為 "index.js" .pipe(gulp.dest('build/js')); }); gulp.task('default',['concat']);
安裝:npm install –save-dev gulp-less
Gulpfile.js程式碼如下:
var gulp = require('gulp'), less = require("gulp-less"); gulp.task('compile-less', function () { gulp.src('src/less/*.less') .pipe(less()) .pipe(gulp.dest('build/css')); }); gulp.task('default',['compile-less']);
安裝:npm install –save-dev gulp-sass
程式碼如下:
var gulp = require('gulp'), sass = require("gulp-sass"); gulp.task('compile-sass', function () { gulp.src('src/sass/*.sass') .pipe(sass()) .pipe(gulp.dest('build/css')); }); gulp.task('default',['compile-sass']);
安裝:npm install –save-dev gulp-imagemin
程式碼如下:
var gulp = require('gulp'); var imagemin = require('gulp-imagemin'); gulp.task('uglify-imagemin', function () { return gulp.src('src/images/*') .pipe(imagemin()) .pipe(gulp.dest('build/images')); }); gulp.task('default',['uglify-imagemin']);
browserify是一個使用node支援的CommonJS模組標準 來為瀏覽器編譯模組的,可以解決模組及依賴管理;
先來看看使用gulp常見的問題:
1. 使用 gulp 過程中,偶爾會遇到 Streaming not supported 這樣的錯誤。這通常是因為常規流與 vinyl 檔案物件流有差異、
gulp 外掛預設使用了只支援 buffer (不支援 stream)的庫。比如,不能把 Node 常規流直接傳遞給 gulp 及其外掛。
比如如下程式碼:會丟擲異常的;
var gulp = require('gulp'); var uglify = require('gulp-uglify'); var concat = require('gulp-concat'); var rename = require('gulp-rename'); var fs = require('fs'); gulp.task('bundle', function() { return fs.createReadStream('./test.txt') .pipe(uglify()) .pipe(rename('bundle.min.js')) .pipe(gulp.dest('dist/')); }); gulp.task('default',['bundle']);
gulp 選擇預設使用內容轉換成 buffer 的 vinyl 物件流,以方便處理。當然,設定 buffer: false 選項,可以讓 gulp 禁用 buffer:
比如如下gulpfile.js程式碼如下:
var gulp = require('gulp'); var fs = require('fs'); gulp.task('bundle', function() { return gulp.src('./src/js/app.js', {buffer: false}).on('data', function(file) { var stream = file.contents; stream.on('data', function(chunk) { console.log('Read %d bytes of data', chunk.length); }); }); }) gulp.task('default',['bundle']);
執行如下:
2. Stream 和 Buffer 之間轉換
基於依賴的模組返回的是 stream, 以及 gulp 外掛對 stream 的支援情況,有時需要把 stream 轉換為 buffer。比如很多外掛只支援 buffer,如 gulp-uglify、使用時可以通過 gulp-buffer 轉換:Stream轉換Buffer
如下gulpfile.js程式碼:
var gulp = require('gulp'); var source = require('vinyl-source-stream'); var buffer = require('gulp-buffer'); var uglify = require('gulp-uglify'); var fs = require('fs'); gulp.task('bundle', function() { return fs.createReadStream('./src/js/app.js') .pipe(source('app.min.js')) // 常規流轉換成 vinyl 物件 .pipe(buffer()) .pipe(uglify()) .pipe(gulp.dest('dist/')); }) gulp.task('default',['bundle']);
如下所示:
3. 從 Buffer 到 Stream之間轉換
也可以通過使用 gulp-streamify(https://www.npmjs.com/package/gulp-streamify) 或者 gulp-stream (https://www.npmjs.com/package/gulp-stream)外掛,讓只支援 buffer 的外掛直接處理 stream。
如下gulpfile.js程式碼:
var gulp = require('gulp'); var wrap = require('gulp-wrap'); var streamify = require('gulp-streamify'); var uglify = require('gulp-uglify'); var gzip = require('gulp-gzip'); gulp.task('bundle', function() { return gulp.src('./src/js/app.js', {buffer: false}) .pipe(wrap('(function(){<%= contents %>}());')) .pipe(streamify(uglify())) .pipe(gulp.dest('dist')) .pipe(gzip()) .pipe(gulp.dest('dist')); }); gulp.task('default',['bundle']);
如下所示:
4. 使用browserify進行Stream 向 Buffer 轉換
vinyl-source-stream + vinyl-buffer
vinyl-source-stream(https://www.npmjs.com/package/vinyl-source-stream) : 將常規流轉換為包含 Stream 的 vinyl 物件;
vinyl-buffer(https://www.npmjs.com/package/vinyl-buffer) 將 vinyl 物件內容中的 Stream 轉換為 Buffer。
gulpfile.js程式碼如下:
var browserify = require('browserify'); var gulp = require('gulp'); var uglify = require('gulp-uglify'); var source = require('vinyl-source-stream'); var buffer = require('vinyl-buffer'); gulp.task('browserify', function() { return browserify('./src/js/app.js') .bundle() .pipe(source('bundle.js')) // gives streaming vinyl file object .pipe(buffer()) // convert from streaming to buffered vinyl file object .pipe(uglify()) .pipe(gulp.dest('./dist/js')); }); gulp.task('default',['browserify']);
vinyl-source-stream 使用指定的檔名bundle.js建立了一個 vinyl 檔案物件例項,因此可以不再使用 gulp-rename(gulp.dest 將用此檔名寫入結果)。
如下所示:
5. 使用browserify多檔案操作
5-1. 使用Gulp和Browserify單個檔案操作也可以如下:
var gulp = require('gulp'); var browserify = require('browserify'); var source = require('vinyl-source-stream'); gulp.task('browserify', function(){ return browserify( {entrieis:['./src/js/app.js']}) .bundle() .pipe(source("bundle.js")) .pipe(gulp.dest('dist')); }); gulp.task('default',['browserify']);
5-2 多檔案操作如下:
var gulp = require('gulp'); var source = require('vinyl-source-stream'); var browserify = require('browserify'); var glob = require('glob'); var es = require('event-stream'); gulp.task('browserify', function(done) { glob('./src/**/*.js', function(err, files) { if(err) { done(err) }; var tasks = files.map(function(entry) { return browserify({ entries: [entry] }) .bundle() .pipe(source(entry)) .pipe(gulp.dest('./dest')); }); es.merge(tasks).on('end', done); }) }); gulp.task('default',['browserify']);
5-3 也可以使用gulp.src和browserify一起使用;程式碼如下:
var gulp = require('gulp'); var source = require('vinyl-source-stream'); var browserify = require('browserify'); var glob = require('glob'); var es = require('event-stream'); var buffer = require('vinyl-buffer'); gulp.task('browserify', function(done) { gulp.src('./src/**/*.js',function(err,files) { if(err) { done(err) } files.forEach(function(file){ return browserify({ entries: [file] }) .bundle() .pipe(source(file)) .pipe(buffer()) .pipe(gulp.dest('./dest')); }); }); }); gulp.task('default',['browserify']);
browserify深入學習;
1.前言:
之前我們做專案的時候,比如需要jquery框架的話,我們可能需要下載jquery原始碼,然後引入到我們的專案中,之後在html檔案中像如下引入即可:
<script src="path/to/jquery.js"></script>
2.bower學習
之後我們學習了 Bower,因此我們安裝了Bower,然後進入我們的專案檔案根目錄中 在命令列中使用bower安裝jquery;如下命令:
bower install jquery
之後會在我們的根目錄中生成一個 bower_components檔案,裡面包含了jquery檔案,因此我們需要在我們的html檔案中這樣引入jquery了;
<script src="bower_components/jquery/dist/jquery.js"></script>
如下所示:
3. npm&Browserify學習
我們現在又可以使用 命令列用npm安裝jQuery。進入專案的根目錄後,執行如下命令:
npm install --save-dev jquery
接著我們在命令列中全域性安裝 browserify;命令如下:
sudo npm install -g browserify
現在我們就可以在命令列中使用 browserify命令了;
比如現在我在我的專案目錄下的原始檔 src/js/a.js 下想要使用jquery的話,我們可以在a.js程式碼如下引用:
var $ = require('jquery'); $(function(){ // 獲取頁面中的DOM元素 console.log($("#jquery2")); }); function a() { console.log("a.js"); } a();
再進入命令列相對應的js檔案中,進行如下命令:
browserify a.js -o dest.js
執行命令後會在同目錄下生成dest.js,該檔案包含jquery.js和a.js;然後我們把dest檔案引入到我們需要的html檔案中即可訪問;
4. gulp和Browserify 一起使用
結合gulp一起使用時,我們只需要把Browserify安裝到我們的專案內即可;因此進入專案的根目錄中,進行如下命令安裝:
npm install --save-dev browserify
然後在專案的根目錄中在gulpfile.js檔案中加入如下程式碼:
var gulp = require("gulp"); var browserify = require("browserify"); var sourcemaps = require("gulp-sourcemaps"); var source = require('vinyl-source-stream'); var buffer = require('vinyl-buffer'); gulp.task("browserify", function () { var b = browserify({ entries: "./src/js/a.js", debug: true }); return b.bundle() .pipe(source("bundle.js")) .pipe(buffer()) .pipe(sourcemaps.init({loadMaps: true})) .pipe(sourcemaps.write(".")) .pipe(gulp.dest("./dist")); }); gulp.task('default',['browserify']);
a.js程式碼還是如下:
var $ = require('jquery'); $(function(){ // 獲取頁面中的DOM元素 console.log($("#jquery2")); }); function a() { console.log("a.js"); } a();
進入專案的根目錄中執行命令 gulp即可,在目錄中會生成dist目錄(包含bundle.js和bundle.js.map)兩個檔案;之後在html檔案頁面上引入
dist目錄檔案下的bundle.js即可;
在上面的程式碼中,debug: true是告知Browserify在執行同時生成內聯sourcemap用於除錯。
如果我們把debug設定成false的話;在瀏覽器中訪問頁面,可以看到如下:
如果我們把debug設定成true的話,在瀏覽器中訪問頁面,可以看到如下:
引入gulp-sourcemaps並設定loadMaps: true是為了讀取上一步得到的內聯sourcemap,並將其轉寫為一個單獨的sourcemap檔案。
如果我們把loadMaps設定成false的話,我們在瀏覽器訪問頁面如下圖所示:
如果我們把loadMaps設定成true的話,我們在瀏覽器訪問頁面如下圖所示:
vinyl-source-stream用於將Browserify的bundle()的輸出轉換為Gulp可用的[vinyl][](一種虛擬檔案格式)流。
vinyl-buffer用於將vinyl流轉化為buffered vinyl檔案(gulp-sourcemaps及大部分Gulp外掛都需要這種格式)。
如果程式碼比較多,可能一次編譯需要很長時間。這個時候,我們可以使用[watchify][]。它可以在你修改檔案後,
只重新編譯需要的部分(而不是Browserify原本的全部編譯),這樣,只有第一次編譯會花些時間,此後的即時變更重新整理則十分迅速。
如下程式碼:
var watchify = require('watchify'); var browserify = require('browserify'); var gulp = require('gulp'); var source = require('vinyl-source-stream'); var buffer = require('vinyl-buffer'); var gutil = require('gulp-util'); var sourcemaps = require('gulp-sourcemaps'); var assign = require('lodash.assign'); // 在這裡新增自定義 browserify 選項 var customOpts = { entries: ['./src/js/a.js'], debug: true }; var opts = assign({}, watchify.args, customOpts); var b = watchify(browserify(opts)); // 在這裡加入變換操作 // 比如: b.transform(coffeeify); gulp.task('js', bundle); // 這樣你就可以執行 `gulp js` 來編譯檔案了 b.on('update', bundle); // 當任何依賴發生改變的時候,執行打包工具 b.on('log', gutil.log); // 輸出編譯日誌到終端 function bundle() { return b.bundle() // 如果有錯誤發生,記錄這些錯誤 .on('error', gutil.log.bind(gutil, 'Browserify Error')) .pipe(source('bundle.js')) // 可選項,如果你不需要快取檔案內容,就刪除 .pipe(buffer()) // 可選項,如果你不需要 sourcemaps,就刪除 .pipe(sourcemaps.init({loadMaps: true})) // 從 browserify 檔案載入 map // 在這裡將變換操作加入管道 .pipe(sourcemaps.write('./')) // 寫入 .map 檔案 .pipe(gulp.dest('./dist')); } gulp.task('default',['js']);
5. 使用Browserify來組織JavaScript檔案
還是上面那個專案,假如src/js檔案內有2個js檔案,分別為a.js和b.js;假如現在a.js想引用b.js的模組,就像seajs那樣通過require來引用如何做?
現在我們可以在b.js這樣編寫程式碼;把我們的程式碼模組通過module.exports 或 exports模組對外提供介面,和其他的比如seajs一樣編寫程式碼
即可:比如現在b.js程式碼如下:
function b() {
console.log("b.js");
}
module.exports = b;
那麼a.js程式碼如下:
var b = require('./b');
function a() {
b();
console.log("a.js");
}
a();
然後再在gulpfile.js檔案程式碼還是如下:
var gulp = require("gulp"); var browserify = require("browserify"); var sourcemaps = require("gulp-sourcemaps"); var source = require('vinyl-source-stream'); var buffer = require('vinyl-buffer'); gulp.task("browserify", function () { var b = browserify({ entries: "./src/js/a.js", debug: true }); return b.bundle() .pipe(source("bundle.js")) .pipe(buffer()) .pipe(sourcemaps.init({loadMaps: true})) .pipe(sourcemaps.write(".")) .pipe(gulp.dest("./dist")); }); gulp.task('default',['browserify']);
在命令列中執行gulp,即可生成bundle.js檔案;引用該檔案即可解決模組依賴的問題;我們開啟bundle.js檔案檢視程式碼如下:
(function e(t,n,r){ /* function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require; if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'"); throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e]; return s(n?n:e)},l,l.exports,e,t,n,r)} return n[o].exports}var i=typeof require=="function"&&require; for(var o=0;o<r.length;o++)s(r[o]);return s */ })({1:[function(require,module,exports){ var b = require('./b'); function a() { b(); console.log("a.js"); } a(); },{"./b":2}],2:[function(require,module,exports){ function b() { console.log("b.js"); } module.exports = b; },{}]},{},[1])
該函式有3個引數,
第一個引數是一個物件;第二個引數是一個空物件{};第三個引數是一個[1];
第一個引數是一個object;它的每一個key都是一個數字,作為模組的id,每一個數字key對應的值是長度為2的陣列。可以看下,第一個key數字1
模組中的陣列中的第一個元素是a.js程式碼;陣列中第二個元素是a模組的依賴項,第二個key數字2模組中陣列第一個元素是b.js程式碼;陣列中的第二個
元素是空物件{};因為b模組沒有依賴項;因此為{};
我們的檔案程式碼通過一個匿名函式被包裝起來,這樣做的好處是:我們的瀏覽器中並沒有require這樣的解決依賴的東西,但是我們又想像seajs,
requireJS等一樣使用require來引入檔案解決模組依賴的檔案的時候,因此 Browserify實現了require、module、exports這3個關鍵字。
第2個引數幾乎總是空的{}。它如果有的話,也是一個模組map;
第3個引數是一個陣列,指定的是作為入口的模組id。a.js是入口模組,它的id是1,所以這裡的陣列就是[1]。
那麼 Browserify是如何實現了require、module、exports這3個關鍵字的呢?
我們前面被註釋的程式碼將解析require、module、exports這三個3個引數,然後讓一切執行起來。
這段程式碼是一個函式,來自於browser-pack專案的[prelude.js][]。
上面我們看到在Browserify打包檔案的時候,它會自動使用匿名函式進行包裝;因此我們在編寫程式碼的時候一般可以不需要考慮全域性變數的問題了;
不需要在函式中新增像型別匿名函式的結構 (function(){})();
gulp.watch()方法可以監聽檔案的動態修改,它接受一個glob或者glob陣列(和gulp.src()一樣)以及一個任務陣列來執行回撥。下面我們來看下
gulp.watch()方法的使用;比如現在gulpfile.js任務程式碼如下:
var gulp = require('gulp'); var concat = require('gulp-concat'); var uglify = require('gulp-uglify'); var paths = { scripts: ['src/js/**/*.js'], css: ['src/css/**/*.css'], // 把原始檔html放在src下,會自動打包到指定目錄下 html: ['src/html/**/*.html'] }; gulp.task('scripts', function() { return gulp.src(paths.scripts) .pipe(concat('all.js')) .pipe(uglify()) .pipe(gulp.dest('build/js')); }); gulp.task('css', function() { return gulp.src(paths.css) .pipe(concat('all.css')) .pipe(gulp.dest('build/css')); }); // 監聽html檔案的改變 gulp.task('html',function(){ return gulp.src(paths.html) .pipe(gulp.dest('html/')); }); // Rerun the task when a file changes gulp.task('watch', function() { gulp.watch(paths.scripts, ['scripts']); gulp.watch(paths.css, ['css']); gulp.watch(paths.html, ['html']); }); // The default task (called when you run `gulp` from cli) gulp.task('default', ['scripts', 'css', 'html','watch']);
監聽src檔案下的js和css及html的檔案的變化,我們在相關的專案根目錄命令列中執行gulp後,當我們改變css或者js檔案或html的時候,
可以監聽檔案的動態修改,因此儲存重新整理瀏覽器即可生效,這是gulp-watch的基本功能;如下所示:
會把src下的檔案css和js自動打包到build下,src下的html檔案會打包到專案根目錄下的html檔案下;
上面的gulp.watch 回撥函式有一個包含觸發回撥函式資訊的event物件:比如我們把gulp watch任務改成如下:
當每次更改檔案的時候 都會觸發change事件;程式碼改為如下:
gulp.task('watch', function() { var watch1 = gulp.watch(paths.scripts, ['scripts']); var watch2 = gulp.watch(paths.css, ['css']); var watch3 = gulp.watch(paths.html, ['html']); watch1.on('change', function (event) { console.log('Event type: ' + event.type); // Event type: changed console.log('Event path: ' + event.path); // Event path: /Users/tugenhua/gulp/src/css/a.css }); watch2.on('change', function (event) { console.log('Event type: ' + event.type); // Event type: changed console.log('Event path: ' + event.path); // Event path: /Users/tugenhua/gulp/src/css/a.css }); watch3.on('change', function (event) { console.log('Event type: ' + event.type); // Event type: changed console.log('Event path: ' + event.path); // Event path: /Users/tugenhua/gulp/src/css/a.css }); });
除了change事件,還可以監聽很多其他的事件:
end 在watcher結束時觸發
error 在出現error時觸發
ready 在檔案被找到並正被監聽時觸發
nomatch 在glob沒有匹配到任何檔案時觸發
Watcher物件也包含了一些可以呼叫的方法:
watcher.end() 停止watcher(以便停止執行後面的任務或者回撥函式)
watcher.files() 返回watcher監聽的檔案列表
watcher.add(glob) 將與指定glob相匹配的檔案新增到watcher
watcher.remove(filepath) 從watcher中移除個別檔案
上面是通過gulp-watch來動態監聽html,css和js檔案的改變,但是需要重新重新整理頁面才能生效;
該外掛的作用是當檔案被修改的時候,它能實時重新整理網頁,這樣的話就不需要我們實時重新整理了;但是該外掛需要在我們伺服器下生效;因此
我們需要使用 gulp-connect 建立一個伺服器;下面是gulpfile.js程式碼如下;使用liveReload實現實時重新整理;
var gulp = require('gulp'); var connect = require('gulp-connect'); var uglify = require("gulp-uglify"); var concat = require("gulp-concat"); var mincss = require("gulp-minify-css"); //自動重新整理 var livereload = require("gulp-livereload"); /* 設定路徑 */ var paths = { src : "src/", css : "src/css/", scripts : "src/js/", scss : "src/scss/", img : "src/images/", html : "src/html/", build : "build" } // 建立一個webserver 伺服器 gulp.task('webserver', function() { connect.server({ port: 8000, livereload: true }); }); gulp.task('scripts', function() { return gulp.src(paths.scripts+ "**/*.js") .pipe(concat('all.js')) .pipe(uglify()) .pipe(gulp.dest(paths.build + '/js')); }); gulp.task('css', function() { return gulp.src(paths.css+ "**/*.css") .pipe(concat('all.css')) .pipe(mincss()) .pipe(gulp.dest(paths.build + '/css')); }); // 監聽html檔案的改變 gulp.task('html',function(){ return gulp.src(paths.html + "**/*.html") .pipe(gulp.dest('html/')); }); //reload server gulp.task('reload-dev',['scripts','css','html'],function() { return gulp.src(paths.src + '**/*.*') .pipe(connect.reload()); }); // Watch gulp.task('watch', function() { //監聽生產環境目錄變化 gulp.watch(paths.src + '**/*.*',['reload-dev']); }) gulp.task('default', ['webserver','reload-dev','watch']);
BroserSync在瀏覽器中展示變化的功能與LiveReload非常相似,但是它有更多的功能。實現靜態伺服器,也是能實時重新整理頁面的;BrowserSync也可以在不同瀏覽器之間同步點選翻頁、表單操作、滾動位置等功能。
安裝如下命令:
npm install --save-dev browser-sync
如下gulpfile檔案是動態監聽js,css和html檔案的變化實時更新;如下程式碼:
var gulp = require('gulp'); var connect = require('gulp-connect'); var uglify = require("gulp-uglify"); var concat = require("gulp-concat"); var mincss = require("gulp-minify-css"); //自動重新整理 var browserSync = require('browser-sync').create(); var reload = browserSync.reload; /* 設定路徑 */ var paths = { src : "src/", css : "src/css/", scripts : "src/js/", scss : "src/scss/", img : "src/images/", html : "src/html/", build : "build" } gulp.task('scripts', function() { return gulp.src(paths.scripts+ "**/*.js") .pipe(concat('all.js')) .pipe(uglify()) .pipe(gulp.dest(paths.build + '/js')) .pipe(reload({stream:true})); // inject into browsers }); gulp.task('css', function() { return gulp.src(paths.css+ "**/*.css") .pipe(concat('all.css')) .pipe(mincss()) .pipe(gulp.dest(paths.build + '/css')) .pipe(reload({stream:true})); // inject into browsers }); // 監聽html檔案的改變 gulp.task('html',function(){ return gulp.src(paths.html + "**/*.html") .pipe(gulp.dest('html/')) .pipe(reload({stream:true})); // inject into browsers }); // 建立本地伺服器,並且實時更新頁面檔案 gulp.task('browser-sync', ['scripts','css','html'],function() { var files = [ '**/*.html', '**/*.css', '**/*.js' ]; browserSync.init(files,{ server: { //baseDir: "./html" } }); }); //gulp.task('default', ['webserver','reload-dev','watch']); gulp.task('default', ['browser-sync'], function () { gulp.watch("**/*.css", ['css']); gulp.watch("**/*.html", ['html']); gulp.watch("**/*.js", ['scripts']); });
對 browser-sync 更多的學習 請看文件(http://www.browsersync.cn/docs/api/)
gulp構建小型專案的基本過程
比如我現在一個小專案的基本架構如下所示:
src資料夾:是原始檔的目錄結構;build資料夾是通過構建後生成的檔案;
src存放檔案如下:
common(該目錄是存放公用的外掛css檔案和js檔案)
html目錄是存放目前的html檔案
images目錄存放所有在專案中用到的圖片
js目錄是在專案中用到的所有的js檔案;
less檔案是存放需要預編譯成css檔案;
這上面幾個目錄都會通過gulpfile.js打包到build目錄下;通過上面的學習browserify(可以解決js的模組化依賴問題)及學習 browserSync(實現自動重新整理效果),因此目前該專案打包有2個優點:
1. 可以使用require,exports,和moudle這三個引數實現js模組化組織及載入的問題,它不需要依賴於seajs或者requireJS;
2. 可以實時監聽html,css,js檔案的修改,從而不需要重新整理頁面,可以提高工作效率;
現在我把package.json用到的依賴包放到下面來:
{ "name": "testProject", "version": "0.0.1", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "devDependencies": { "browser-sync": "^2.12.10", "browserify": "^13.0.1", "event-stream": "^3.3.2", "glob": "^7.0.3", "gulp": "^3.9.1", "gulp-buffer": "0.0.2", "gulp-clean": "^0.3.2", "gulp-concat": "^2.6.0", "gulp-connect": "^4.0.0", "gulp-gzip": "^1.3.0", "gulp-imagemin": "^3.0.1", "gulp-less": "^3.1.0", "gulp-livereload": "^3.8.1", "gulp-marked": "^1.0.0", "gulp-minify-css": "^1.2.4", "gulp-rename": "^1.2.2", "gulp-sourcemaps": "^1.6.0", "gulp-str-replace": "0.0.4", "gulp-streamify": "^1.0.2", "gulp-uglify": "^1.5.3", "gulp-util": "^3.0.7", "gulp-watch": "^4.3.6", "gulp-wrap": "^0.13.0", "lodash.assign": "^4.0.9", "node-glob": "^1.2.0", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0", "watchify": "^3.7.0" } }
專案用到的話,直接npm install 就可以把所有的包載入進來;
gulpfile.js程式碼如下:
var gulp = require('gulp'); var less = require('gulp-less'); var mincss = require('gulp-minify-css'); var concat = require("gulp-concat"); var uglify = require("gulp-uglify"); var clean = require('gulp-clean'); var browserify = require("browserify"); var sourcemaps = require("gulp-sourcemaps"); var source = require('vinyl-source-stream'); var buffer = require('vinyl-buffer'); var replace = require('gulp-str-replace'); var imagemin = require('gulp-imagemin'); //自動重新整理 var browserSync = require('browser-sync').create(); var reload = browserSync.reload; var fs = require('fs'); var fileContent = fs.readFileSync('./package.json'); var jsonObj = JSON.parse(fileContent); var argv = process.argv.pop(); var DEBUGGER = (argv === "-D" || argv === "-d") ? true : false; /* 基礎路徑 */ var paths = { css : 'src/common/css/', less : 'src/less/', scripts : "src/js/", img : "src/images/", html : "src/html/", build : "build", src : 'src' } var resProxy = "專案的真實路徑"; var prefix = "專案的真實路徑"+jsonObj.name; if(DEBUGGER) { resProxy = "http://localhost:3000/build"; prefix = "http://localhost:3000/build"; } // 先清理檔案 gulp.task('clean-css',function(){ return gulp.src(paths.build + "**/*.css") .pipe(clean()); }); gulp.task('testLess', ['clean-css'],function () { return gulp.src([paths.less + '**/*.less',paths.css+'**/*.css']) .pipe(less()) .pipe(concat('index.css')) .pipe(mincss()) .pipe(replace({ original : { resProxy : /\@{3}RESPREFIX\@{3}/g, prefix : /\@{3}PREFIX\@{3}/g }, target : { resProxy : resProxy, prefix : prefix } })) .pipe(gulp.dest(paths.build + "/css")) .pipe(reload({stream:true})); }); // 監聽html檔案的改變 gulp.task('html',function(){ return gulp.src(paths.html + "**/*.html") .pipe(replace({ original : { resProxy : /\@{3}RESPREFIX\@{3}/g, prefix : /\@{3}PREFIX\@{3}/g }, target : { resProxy : resProxy, prefix : prefix } })) .pipe(gulp.dest(paths.build+'/html')) .pipe(reload({stream:true})); }); // 對圖片進行壓縮 gulp.task('images',function(){ return gulp.src(paths.img + "**/*") .pipe(imagemin()) .pipe(gulp.dest(paths.build + "/images")); }); // 建立本地伺服器,並且實時更新頁面檔案 gulp.task('browser-sync', ['testLess','html','browserify'],function() { var files = [ '**/*.html', '**/*.css', '**/*.less', '**/*.js' ]; browserSync.init(files,{ server: { //baseDir: "./html" } }); }); // 解決js模組化及依賴的問題 gulp.task("browserify",function () { var b = browserify({ entries: ["./src/js/index.js"], debug: true }); return b.bundle() .pipe(source("index.js")) .pipe(buffer()) .pipe(sourcemaps.init({loadMaps: true})) .pipe(gulp.dest("./build/js")) .pipe(uglify()) .pipe(sourcemaps.write(".")) .pipe(replace({ original : { resProxy : /\@{3}RESPREFIX\@{3}/g, prefix : /\@{3}PREFIX\@{3}/g }, target : { resProxy : resProxy, prefix : prefix } })) .pipe(gulp.dest("./build/js")) .pipe(reload({stream:true})); }); gulp.task('default',['testLess','html','images','browserify'],function () { gulp.watch(["**/*.less","**/*.css"], ['testLess']); gulp.watch("**/*.html", ['html']); gulp.watch("**/*.js", ['browserify']); }); gulp.task('server', ['browser-sync','images'],function () { gulp.watch(["**/*.less","**/*.css"], ['testLess']); gulp.watch("**/*.html", ['html']); gulp.watch("**/*.js", ['browserify']); });
如果我們在命令列中 執行gulp server -d 那就是開發環境,會自動開啟一個伺服器;如果執行gulp的話,就是線上的正式環境了;程式碼會通過replace外掛替換成線上的環境;
使用replace外掛的好處可以這樣引入檔案
<script src="@@@PREFIX@@@/js/index.js"></script>
<link rel="stylesheet" href="@@@PREFIX@@@/css/index.css"/>
所有的圖片都可以使用這樣的@@@PREFIX@@@來引入的,這樣的話就可以指向本地的環境和線上的環境了;方便開發;