gulp 前端自動化實踐

gulp 簡介

gulp是一個基于Nodejs的自動化任務(wù)運行器冈在,能自動化地完成javascript/coffee/sass/less/html/image/css 等文件的的測試艾凯、檢查灾票、合并、壓縮簇爆、格式化在孝、瀏覽器自動刷新崭篡、部署文件生成,并監(jiān)聽文件在改動后重復(fù)指定的這些步驟浸须。

在實現(xiàn)上惨寿,它借鑒了Unix操作系統(tǒng)的管道(pipe)思想,前一級的輸出删窒,直接變成后一級的輸入裂垦,使得在操作上非常簡單。

gulp的核心設(shè)計

核心詞是streaming(流動式)肌索。Gulpjs的精髓在于對Nodejs中Stream API 的利用蕉拢。

流(stream)的概念來自Unix,核心思想是do one thing well。一個大工程系統(tǒng)應(yīng)該由各個小且獨立的管子連接而成诚亚。

我們以經(jīng)典的Nodejs讀取文件邏輯來說明stream和傳統(tǒng)方式的差異晕换。使用fs模塊讀取一個json文件,傳統(tǒng)的方式代碼如下

var dataJson, fs; 
fs = require('fs'); 
dataJson = 'public/json/test.json'; 
exports.all = function(req, res) { 
  fs.readFile(dataJson,function(err, data){ 
    if (err) { 
      console.log(err); 
    } else { 
      res.end(data); 
    } 
  }); 
};

fs.readFile()是將文件全部讀進內(nèi)存亡电,然后觸發(fā)回調(diào)届巩。有兩方面的瓶頸。

  • 讀取大文件時容易造成內(nèi)存泄露
  • 深惡痛絕的回調(diào)大坑

下面我們看看使用流的方式

var fs = require('fs');
var readStream = fs.createReadStream('data.json');
var writeStream = fs.createWriteStream('data1.json');

readStream.on('data', function(chunk) { // 當有數(shù)據(jù)流出時份乒,寫入數(shù)據(jù)
    if (writeStream.write(chunk) === false) { // 如果沒有寫完恕汇,暫停讀取流
        readStream.pause();
    }
});

writeStream.on('drain', function() { // 寫完后,繼續(xù)讀取
    readStream.resume();
});

readStream.on('end', function() { // 當沒有數(shù)據(jù)時或辖,關(guān)閉數(shù)據(jù)流
    writeStream.end();
});

或者直接使用

fs.createReadStream('/path/to/source').pipe(fs.createWriteStream('/path/to/dest'));

先創(chuàng)建一個有狀態(tài)的只讀的流瘾英,然后調(diào)用stream.pipe(res)。pipe方法是stream的核心方法颂暇。這句話的代碼可以理解為res對象接收從stream來的數(shù)據(jù),并予以處理輸出缺谴。所以gulppipe()并不是gulp的方法,而是流對象的方法耳鸯。切記:pipe()返回的res的返回的對象湿蛔。

gulp的安裝與使用

  • 全局安裝

      npm install gulp -g
    
  • 切換到項目目錄執(zhí)行npm初始化,生成pakeage.json

      npm init
    
  • 開發(fā)依賴安裝

      npm install gulp --save-dev //項目內(nèi)集成gulp
      npm install gulp-name --save-dev//項目內(nèi)集成gulp第三方插件
    
  • 項目根目錄下創(chuàng)建配置文件

      gulpfile.js
    
  • 在gulpfile.js中引用gulp

      var gulp = require('gulp')
      var name = require('gulp-name')  //插件調(diào)用
    
  • 在gulpfile.js中配置gulp任務(wù)

      gulp.task('task_name', function () {
          return gulp.src('file source path')
              .pipe(...)
              .pipe(...)
              // till the end
              .pipe(...);
      });
    

gulp task_name可以來執(zhí)行不同的任務(wù)膀曾。這個task_name可以是自定義的,也可以是默認的任務(wù)(task_name為‘default’),默認任務(wù)執(zhí)行的時候阳啥,可以不用拼上task_name ,直接使用gulp來執(zhí)行

//例子1(默認):
gulp.task('default',function() {
    console.log('我是default')
});
//運行結(jié)果:
$ gulp
[21:54:33] Using gulpfile ~/Desktop/nodeJS/gulp/gulpfile.js
[21:54:33] Starting 'default'...
我是default
[21:54:33] Finished 'default' after 120 μs

//例子2(自定義)
gulp.task('task1',function () {
    console.log('我是task1')
})
//運行結(jié)果:
$ gulp task1  //后面跟上自定義的任務(wù)名稱
[21:58:00] Using gulpfile ~/Desktop/nodeJS/gulp/gulpfile.js
[21:58:00] Starting 'task1'...
我是task1
[21:58:00] Finished 'task1' after 121 μs

//例子3(復(fù)合)
var gulp = require('gulp')

gulp.task('task', function(){
    console.log('hello,i am task')
})
gulp.task('task2', function(){
    console.log('hello,i am task2')
})
gulp.task('task3', function(){
    console.log('hello,i am task3')
})
gulp.task('task4', function(){
    console.log('hello,i am task4')
})
gulp.task('task5', function(){
    console.log('hello,i am task5')
})
gulp.task('default', ['task','task2','task3','task4','task5'], function(){
    console.log('hello,i am default')
})

//運行結(jié)果:
D:\test\gulpTest>gulp   //先按順序執(zhí)行數(shù)組內(nèi)依賴項添谊,后執(zhí)行默認
[10:52:56] Using gulpfile D:\test\gulpTest\gulpfile.js
[10:52:56] Starting 'task'...
hello,i am task
[10:52:56] Finished 'task' after 140 μs
[10:52:56] Starting 'task2'...
hello,i am task2
[10:52:56] Finished 'task2' after 59 μs
[10:52:56] Starting 'task3'...
hello,i am task3
[10:52:56] Finished 'task3' after 52 μs
[10:52:56] Starting 'task4'...
hello,i am task4
[10:52:56] Finished 'task4' after 56 μs
[10:52:56] Starting 'task5'...
hello,i am task5
[10:52:56] Finished 'task5' after 54 μs
[10:52:56] Starting 'default'...
hello,i am default
[10:52:56] Finished 'default' after 55 μs

gulp的核心API

gulp.task(taskName, deps, callback)

name:任務(wù)名稱,不能包含空格;
deps: 依賴任務(wù)察迟,依賴任務(wù)的執(zhí)行順序按照deps中聲明順序斩狱,先于taksName執(zhí)行;
callback,指定任務(wù)要執(zhí)行的一些操作扎瓶,支持異步執(zhí)行所踊。

下面提供幾個特殊用法

  • 接受一個callback

      // 在 shell 中執(zhí)行一個命令
      var exec = require('child_process').exec;
      gulp.task('jekyll', function(cb) {
        // 編譯 Jekyll
        exec('jekyll build', function(err) {
          if (err) return cb(err); // 返回 error
          cb(); // 完成 task
        });
      });
    
  • 返回一個stream

      gulp.task('somename', function() {
        var stream = gulp.src('client/**/*.js')
          .pipe(minify())
          .pipe(gulp.dest('build'));
        return stream;
      });
    
  • 返回一個promise

      var Q = require('q');
    
      gulp.task('somename', function() {
        var deferred = Q.defer();
      
        // 執(zhí)行異步的操作
        setTimeout(function() {
          deferred.resolve();
        }, 1);
      
        return deferred.promise;
      });
    

gulp.src(globs,options)

該函數(shù)通過一定的匹配模式,用來取出待處理的源文件對象

globs作為需要處理的源文件匹配符路徑

  • src/a.js:指定具體文件概荷;

  • *:匹配所有文件 例:src/*.js(包含src下的所有js文件)秕岛;

  • **:匹配0個或多個子文件夾 例:src/**/*.js(包含src的0個或多個子文件夾下的js文件);

  • {}:匹配多個屬性 例:src/{a,b}.js(包含a.js和b.js文件) src/*.{jpg,png,gif}(src下的所有jpg/png/gif文件)乍赫;

  • “!”:排除文件 例:!src/a.js(不包含src下的a.js文件)瓣蛀;

      gulp.src(['src/js/*.js','!src/js/test.js'])
              .pipe(gulp.dest('dist'))
    

options包括如下內(nèi)容

  • options.buffer: boolean,默認true,設(shè)置為false將返回file.content的流并且不緩存文件雷厂,處理大文件很好用

  • options.read: boolean惋增,默認true,是否讀取文件

  • options.base: 設(shè)置輸出路徑以某個路徑的某個組成部分為基礎(chǔ)向后拼接

      gulp.src('client/js/**/*.js')
        .pipe(minify())
        .pipe(gulp.dest('build'));  // 寫入 'build/somedir/somefile.js'
      
      gulp.src('client/js/**/*.js', { base: 'client' })
        .pipe(minify())
        .pipe(gulp.dest('build'));  // 寫入 'build/js/somedir/somefile.js'
    

dest()

該函數(shù)用來設(shè)置目標流的輸出,一個流可以被多次輸出改鲫。如果目標文件夾不存在诈皿,則創(chuàng)建之。文件被寫入的路徑是以所給的相對路徑根據(jù)所給的目標目錄計算而來像棘。類似的稽亏,相對路徑也可以根據(jù)所給的 base來計算

gulp.task('sass', function(){
  return gulp.src('public/sass/*.scss')
    .pipe(concat('style1.js'))
    .pipe(gulp.dest('public/sass/'))//目錄下生成style1.js
    .pipe(sass(
      {'sourcemap=none': true}
    ))
    .pipe(concat('style.css'))
    .pipe(gulp.dest('public/sass/'))//目錄下生成style.css

});

pipe()

該函數(shù)使用類似管道的原理,將上一個函數(shù)的輸出傳遞到下一個函數(shù)的輸入

watch()

該函數(shù)用來監(jiān)聽源文件的任何改動缕题。每當更新監(jiān)聽文件時截歉,回調(diào)函數(shù)會自動執(zhí)行。

注意:別忘記將watch任務(wù)放置到default任務(wù)中

gulp.task('watch',function(){
  gulp.watch('public/sass/*.scss',['sass'],function(event){
   console.log(event.type);//added或deleted或changed
   console.log(event.paht);//變化的路徑
  });
    
});
gulp.task('default', ['sass','watch']);

run()

該函數(shù)能夠盡可能的并行運行多個任務(wù)烟零,并且可能不會按照指定的執(zhí)行順序

gulp.task('end',function(){
    gulp.run('task1','task3','task2');
});

gulp 工作流

下面展示一下實際項目中瘪松,gulp的前端自動化流程。

配置gulpfile.js中的目錄

我們新建一個文件锨阿,名為gulpfileConfig.js來管理靜態(tài)資源的目錄

var src = 'public';//默認目錄文件夾

module.exports = {
  sass: {
    src : src + '/sass/*.scss',
    dest : src + '/css/'
  },
  css: {
    src : src + '/css/*.css',
    dest:'dist/css'
  },
  js: {
    src : src + '/js/*.js',
    dest: 'dist/js'
  },
  images: {
    src : src + '/images/**/*',
    dest : 'dist/images'
  },
  zip: {
    src : './**/*',
    dest : './release/'
  }
};

然后宵睦,在gulpfile.js中引入,并使用

var gulpConfig = require('./gulpfileConfig');
var cssConfig = gulpConfig.css;

gulp.src(cssConfig.src)

下文中默認使用gulpfileConfig.js中的配置項

根據(jù)參數(shù)配置構(gòu)建平臺

gulp.task('set-platform', function() {
  console.log('當前構(gòu)建平臺:' + gulp.env.platform);//當前構(gòu)建平臺:web
  gulp.env.platform = gulp.env.platform || 'web';

  // 根據(jù)傳進來的平臺參數(shù)墅诡,設(shè)置項目public/目錄下的系統(tǒng)icon
  gulp.src('./public/' + gulp.env.platform + '.ico')
    .pipe(rename('favicon.ico'))
    .pipe(gulp.dest('./public'));

  // 根據(jù)傳進來的平臺參數(shù)壳嚎,設(shè)置平臺相關(guān)的主scss文件(包含對應(yīng)平臺的主色調(diào))
  gulp.src('./public/sass/mixins/base-' + gulp.env.platform + '.scss')
    .pipe(rename('base.scss'))
    .pipe(gulp.dest('./public/sass/mixins'));

  // 根據(jù)傳進來的平臺參數(shù),設(shè)置對應(yīng)平臺的配置文件
  return gulp.src('./config/' + gulp.env.platform + '.js')
    .pipe(rename('index.js'))
    .pipe(gulp.dest('./config'));
});

//構(gòu)建入口,傳入特定的參數(shù)
gulp publish --platform web

從上文可以看出烟馅,我們可以通過gulp在構(gòu)建開始時通過不同的參數(shù)说庭,給項目變更成對應(yīng)的文件內(nèi)容,是不是很厲害焙糟?

gulp && clean

清理構(gòu)建生成的文件夾口渔。一般在構(gòu)建開始時清理掉上一次生成的歷史文件。

var clean = require('gulp-clean');

gulp.task('clean-dist', function(){
  return gulp.src('dist')
    .pipe(clean());
});

gulp && sass

編譯sass穿撮。一般都是將編譯生成好的CSS文件輸出到項目CSS目錄,用于下一步進行CSS的自動化操縱痪欲。

var sass = require('gulp-ruby-sass');
var sassConfig = gulpConfig.sass;

gulp.task('sass', function () {
  return sass(sassConfig.src)
    .pipe(gulp.dest(sassConfig.dest))
});

gulp && css

對目標文件夾內(nèi)的css文件悦穿,進行壓縮,添加md5后綴的操作业踢,同時生成映射文件栗柒,并輸出到指定文件夾內(nèi)。這個映射文件知举,會自動替換掉html文件的頭文件中瞬沦,引用的這個加了md5后綴的css文件。

var minifycss = require('gulp-minify-css');         //壓縮CSS
var rev = require('gulp-rev');//對文件名加MD5后綴

gulp.task('publish-css',function(){
    return gulp.src(cssConfig.src)
        .pipe(minifycss())
        .pipe(rev())
        .pipe(gulp.dest(cssConfig.dest))            //輸出到文件本地
        .pipe(rev.manifest())
        .pipe(gulp.dest(cssConfig.dest));
});

md5后綴的原因是為了解決瀏覽器緩存的問題:希望瀏覽器能夠緩存資源文件雇锡,但是有希望當文件內(nèi)容變化了的時候逛钻,瀏覽器能夠自動替換老文件。怎么讓瀏覽器檢測到文件的變化呢锰提?簡單曙痘,對文件大小進行md5,生成的隨機串拼到文件后就行啦立肘,這樣文件內(nèi)容如果不變的話边坤,瀏覽器依舊緩存;如果文件有變動谅年,md5值發(fā)生改變茧痒,文件名變化,瀏覽器就引用新的文件內(nèi)容融蹂。

gulp && js

類似于css的自動化操作旺订,對js文件進行錯誤檢查并輸出、混淆殿较、生成md5后綴耸峭、生成sourceMap文件并輸出

var sourcemaps = require('gulp-sourcemaps');
var uglify = require('gulp-uglify');//壓縮js
var jsConfig =gulpConfig.js;
var jshint = require('gulp-jshint');//js 代碼檢查
var rev = require('gulp-rev');
var revCollector = require('gulp-rev-collector');

function jsProcess(toAddComment){
    return gulp.src(jsConfig.src)
        .pipe(jshint('.jshintrc'))//錯誤檢查
        .pipe(jshint.reporter('default'))//對錯誤進行輸出
        .pipe(sourcemaps.init())
        .pipe(uglify())
        .pipe(rev())
        .pipe(sourcemaps.write('/maps',{addComment: toAddComment,sourceMappingURLPrefix: '/js'}))
        .pipe(gulp.dest(jsConfig.dest))
        .pipe(rev.manifest())
        .pipe(gulp.dest(jsConfig.dest));
}

// 用于添加map標記(本地開發(fā)使用)
gulp.task('publish-js-addMap', function (){
    return jsProcess(true);
});

// 不添加map標記(線上版本)
gulp.task('publish-js', function (){
    return jsProcess(false);
});

注1
使用rev()這個工具方法,會對某個文件淋纲,比如ajax.js進行md5加密劳闹,在文件名后面拼上md5串,變成ajax-fba6bf63c7.js,而rev.manifest()則會在rev-manifest文件里,記錄這組對應(yīng)關(guān)系本涕,

{
"ajax.js": "ajax-fba6bf63c7.js"
}

_

注2
sourcemaps.write('/maps',{addComment: toAddComment,sourceMappingURLPrefix: '/js'})這句代碼业汰,會給每個加密的js文件生成用于解密的map文件,同時在文件末尾標注map文件的位置

var ajax={init:function(){return window.ActiveXObject?new ActiveXObject("...
//# sourceMappingURL=/js/maps/ajax-fba6bf63c7.js.map

gulp && image

對所有的圖片進行md5菩颖,生成映射文件

gulp.task('publish-image',function(){
  return gulp.src('public/images/**/*.{jpg,png,gif}')
    .pipe(rev())
    .pipe(gulp.dest('dist/images'))
    .pipe(rev.manifest())
    .pipe(gulp.dest('dist/images'));
});

gulp && html

將靜態(tài)文件里的所有文件引用样漆,根據(jù)上文中生成好的映射文件,都替換成md5后綴的格式晦闰。

gulp.task('publish-view', function () {
    return gulp.src(['dist/**/*.json','views/**/*.html'])
        .pipe(revCollector({
            replaceReved:true
        }))
        .pipe(gulp.dest('dist/views'));
});

比如

<script src="/js/format.js"></script>

替換成

<script src="/js/format-f02584610e.js"></script>

gulp && css->image

替換css文件中引用的圖片名為md5后綴形式放祟。

gulp.task('replace-image-inCss', function() {
  return gulp.src(['dist/images/*.json','dist/css/*.css'])
    .pipe(revCollector({
      replaceReved:true
    }))
    .pipe(gulp.dest('dist/css'));
});

gulp && copy

文件復(fù)制操作

gulp.task('copy-other-files', function () {
  return gulp.src('public/favicon.ico')
    .pipe(gulp.dest('dist'));
});

gulp && browerify

編譯reactjs

var gulp = require('gulp')
var browserify = require('browserify')
var reactify = require('reactify')
var source = require('vinyl-source-stream')
var streamify = require('gulp-streamify')

gulp.task('browserify',function(){
  browserify('./public/js/react_main_components.js')
    .transform(reactify)
    .bundle()
    .pipe(source('productList.js'))
    .pipe(streamify(uglify().on('error', gutil.log)))
    .pipe(gulp.dest('./public/js/reactCompoments/dist'))
});

gulp && zip

var zip = require('gulp-zip');
var zipConfig = gulpConfig.zip;

gulp.task('zip', function() {
  // 打包時,排除掉上次生成過的release里的壓縮包
  return gulp.src([zipConfig.src,'!./release/*.*'])
    .pipe(zip('demo.zip'))
    .pipe(gulp.dest(zipConfig.dest))
});

最后

合并工作流呻右,串行執(zhí)行各類任務(wù)跪妥。根據(jù)不同需求,執(zhí)行不同的構(gòu)建流

// gulp publish --platform jinhui或jinfeng
gulp.task('publish',function(callback){
    runSequence('set-platform', 'clean-dist','sass',['publish-js', 'publish-css'],'publish-image','publish-view','replace-image-inCss','copy-other-files','zip',callback);
});
gulp.task('publish-addMap',function(callback){
    runSequence('set-platform', 'clean-dist','sass',['publish-js-addMap', 'publish-css'],'publish-image','publish-view','replace-image-inCss','copy-other-files','zip',callback);
});
  • gulp 使用小節(jié)

  • Gulp CSS合并声滥、壓縮與MD5命名及路徑替換

  • 附錄:常見的jshintrc文件示例

      {
        //循環(huán)或者條件語句必須使用花括號包圍
        "curly":false,
        //強制使用三等號
        "eqeqeq":false,
        //禁止重寫原生對象的原型眉撵,比如 Array , Date
        "freeze":false,
        //代碼縮進
        "indent":false,
        //禁止單引號雙引號混用
        "quotmark":false,
        //變量未使用
        "unused":false,
        //嚴格模式
        "strict":false,
        //最大嵌套深度
        "maxdepth": 10,
        //最多參數(shù)個數(shù)
        "maxparams": 10,
        //復(fù)雜度檢測
        "maxcomplexity":false,
        //最大行數(shù)檢測
        "maxlen": 1500,
        // 禁止定義之前使用變量落塑,忽略 function 函數(shù)聲明
        "latedef":false,
        // 構(gòu)造器函數(shù)首字母大寫
        "newcap":false,
        //禁止使用 arguments.caller 和 arguments.callee 纽疟,未來ECM5會被棄用
        "noarg":false,
        //變量未定義
        "undef":false,
        // 兼容低級瀏覽器 IE 6/7/8/9
        "es3":false,
        // 控制“缺少分號”的警告
        "boss":false
      }
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市憾赁,隨后出現(xiàn)的幾起案子污朽,更是在濱河造成了極大的恐慌,老刑警劉巖缠沈,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件膘壶,死亡現(xiàn)場離奇詭異,居然都是意外死亡洲愤,警方通過查閱死者的電腦和手機颓芭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來柬赐,“玉大人亡问,你說我怎么就攤上這事「厮危” “怎么了州藕?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長酝陈。 經(jīng)常有香客問我床玻,道長,這世上最難降的妖魔是什么沉帮? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任锈死,我火速辦了婚禮贫堰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘待牵。我一直安慰自己其屏,他們只是感情好,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布缨该。 她就那樣靜靜地躺著偎行,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贰拿。 梳的紋絲不亂的頭發(fā)上蛤袒,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音膨更,去河邊找鬼汗盘。 笑死,一個胖子當著我的面吹牛询一,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播癌椿,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼健蕊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了踢俄?” 一聲冷哼從身側(cè)響起缩功,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎都办,沒想到半個月后嫡锌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡琳钉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年势木,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片歌懒。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡啦桌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出及皂,到底是詐尸還是另有隱情甫男,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布验烧,位于F島的核電站板驳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏碍拆。R本人自食惡果不足惜若治,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一慨蓝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧直砂,春花似錦菌仁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至洽蛀,卻和暖如春摹迷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背郊供。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工峡碉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人驮审。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓鲫寄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親疯淫。 傳聞我的和親對象是個殘疾皇子地来,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 前言 本文默認你已經(jīng)安裝好node環(huán)境,并且熟悉node命令熙掺,和window cd命令未斑。 gulp簡介 基于nod...
    9I閱讀 1,973評論 4 50
  • gulpjs是一個前端構(gòu)建工具,與gruntjs相比币绩,gulpjs無需寫一大堆繁雜的配置參數(shù)蜡秽,API也非常簡單,學(xué)...
    依依玖玥閱讀 3,149評論 7 55
  • gulpjs是一個前端構(gòu)建工具缆镣,與gruntjs相比芽突,gulpjs無需寫一大堆繁雜的配置參數(shù),API也非常簡單费就,學(xué)...
    井皮皮閱讀 1,291評論 0 10
  • 在現(xiàn)在的前端開發(fā)中诉瓦,前后端分離、模塊化開發(fā)力细、版本控制睬澡、文件合并與壓縮、mock數(shù)據(jù)等等一些原本后端的思想開始...
    Charlot閱讀 5,431評論 1 32
  • gulpjs是一個前端構(gòu)建工具眠蚂,與gruntjs相比煞聪,gulpjs無需寫一大堆繁雜的配置參數(shù),API也非常簡單逝慧,學(xué)...
    小裁縫sun閱讀 921評論 0 3