隨着React
、Angular2
、Redux
等前沿的前端框架愈來愈流行,使用webpack
、gulp
等工具構建前端自動化項目也隨之變得愈來愈重要。鑑於目前業界廣泛更流行使用webpack
來構建es6(ECMAScript 2015)前端項目,網上的相關教程也比較多;相對來講使用gulp
來構建es6項目的中文教程就比較少。css
通過一段時間的摸索,我以爲其實使用gulp
也能夠很方便地構建es6項目。如下是我感受gulp
和webpack
主要的不一樣之處:html
gulp
的任務機制和流式管道函數和webpack
的配置參數風格有着顯著區別,它能使開發者更清晰地瞭解項目的構建流程。前端
因爲gulp
是編程式風格的,因此使用起來可定製化的功能也就更靈活一些,可應對一些構建過程較爲複雜的狀況。react
本文特此給你們介紹下如何使用gulp
配合browserify
來構建基於es6的前端項目。webpack
browserify
與webpack
都是當下流行的commonjs模塊(或es6模塊)合併打包工具,打包後的js文件能夠直接運行在瀏覽器環境中。git
不少人都知道,webpack
功能全面,能夠對js、css、甚至圖片、字體文件統一進行合併打包,而且插件豐富。而browserify
的特色是職責單一,只負責js模塊合併打包,有些項目也並不須要將css等資源文件和js打包在一塊兒的功能;它的代碼風格也相似管道函數,和gulp
的契合度較高;在github上也能夠找到至關多的browserify
插件,如熱替換、代碼分割等等。es6
有一篇文章對
browserify
和webpack
的對比進行了探討:webpack 跟 browserify 比到底有什麼好?github
本文中使用的示例項目是我爲重構過去搞的UI組件庫而建的項目,使用browserify
構建的分支地址請戳這裏。這個項目目前已改用gulp
+webpack
構建,可是保留了原先用browserify
構建的分支代碼可供參考。web
項目目錄npm
dist (生產代碼目錄,存放生成合並後的各種文件)
js
構建出的項目js文件
fonts
...
css
構建出的項目css文件
examples (示例目錄)
src (開發代碼目錄)
styles (樣式文件目錄)
base.js (打包入口文件)
...
test (單元測試目錄)
vendor (第三方依賴庫)
babelHelpers.js
...
gulpfile.js (gulp配置文件)
package.json
LICENSE
README.md
示例項目目錄大致如上所示,其中使用babel
進行es6至es5轉換,並使用eslint
進行js代碼檢驗。你們看到這裏可能有疑問,爲何項目中沒有babel及eslint的配置文件.babelrc
和.eslintrc
呢?緣由就是這些配置文件裏的內容實際上是能夠直接配置在gulpfile.js
中的相關插件內的。
在這裏只列出項目依賴的各類包,大體分爲以下幾類:
{ ... "devDependencies": { /*browserify包及相關插件*/ "browserify": "^13.0.0", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0", "standalonify": "^0.1.3", /*babel相關插件*/ "babelify": "^7.2.0", "babel-plugin-external-helpers": "^6.4.0", "babel-plugin-transform-es2015-classes": "^6.5.2", "babel-plugin-transform-es2015-modules-commonjs": "^6.5.2", "babel-plugin-transform-object-assign": "^6.3.13", "babel-preset-es2015": "^6.3.13", "babel-preset-react": "^6.3.13", "babel-preset-stage-0": "^6.3.13", /*eslint相關插件*/ "babel-eslint": "^5.0.0", "estraverse": "^4.2.0", "estraverse-fb": "^1.3.1", /*gulp包及相關插件*/ "gulp": "^3.9.0", "gulp-clean": "^0.3.1", "gulp-concat": "^2.6.0", "gulp-cssnano": "^2.1.1", "gulp-eslint": "^2.0.0", "gulp-if": "^2.0.0", "gulp-jasmine": "^2.2.1", "gulp-less": "^3.0.5", "gulp-rename": "^1.2.2", "gulp-sequence": "^0.4.4", "gulp-uglify": "^1.5.1", /*postcss相關插件*/ "gulp-postcss": "^6.1.0", "autoprefixer": "^6.3.4", /*外部依賴包*/ "nornj": "^0.3.0", "react": "^0.14.8", "react-dom": "^0.14.8", /*其餘依賴包*/ "jsdom": "^8.1.0", "yargs": "^4.2.0", ... }, ... }
gulpfile.js即爲gulp
的配置文件,其做用相似於webpack
的webpack.config.js文件。在代碼風格方面,與webpack.config.js的配置參數風格不一樣的是,gulpfile.js更偏向編程風格。gulpfile.js總體結構以下所示:
//引入依賴的各類包: var gulp = require('gulp'), browserify = require('browserify'), ... //定義一些全局函數及變量 function getJsLibName() { ... } ... //定義各類任務 gulp.task('build-all-js', ...); gulp.task('build-all-css', ...); gulp.task('build', ['build-all-js', 'build-all-css', ...]); ... //定義默認任務 gulp.task('default', ['build']);
使用gulp
須要定義各類任務來處理各種文件的構建生成。如例中所示,定義build-all-js任務來構建js文件,執行任務時須輸入命令:
gulp build-all-js
能夠定義一個默認任務,通常在這個任務裏依次執行所有子任務,執行時輸入命令:
gulp
關於
gulp
基礎使用方法的更多細節你們能夠參考這篇文章:前端構建工具gulpjs的使用介紹及技巧
配合gulp
使用browserify
須要引入的包:
var browserify = require('browserify'), source = require('vinyl-source-stream'), buffer = require('vinyl-buffer'), standalonify = require('standalonify'), argv = require('yargs').argv;
建立gulp
任務build-js:
gulp.task('build-js', function () { return browserify({ entries: './src/base.js' //指定打包入口文件 }) .plugin(standalonify, { //使打包後的js文件符合UMD規範並指定外部依賴包 name: 'FlareJ', deps: { 'nornj': 'nj', 'react': 'React', 'react-dom': 'ReactDOM' } }) .transform(babelify, ...) //使用babel轉換es6代碼 .bundle() //合併打包 .pipe(source(getJsLibName())) //將常規流轉換爲包含Stream的vinyl對象,而且重命名 .pipe(buffer()) //將vinyl對象內容中的Stream轉換爲Buffer .pipe(gulp.dest('./dist/js')); //輸出打包後的文件 }); function getJsLibName() { var libName = 'flarej.js'; if (argv.min) { //按命令參數"--min"判斷是否爲壓縮版 libName = 'flarej.min.js'; } return libName; }
和webpack
相似,browserify
也須要指定打包的入口文件。在本例中只有一個入口文件,browserify
會自動分析文件內依賴的各js模塊,最終生成一個完整的打包文件。
使用standalonify
插件使打包後的js文符合UMD規範,並能夠指定不將一些外部依賴包打進包內。一開始我使用了dependify
,以後發現它生成的包有bug且做者又不維護,因而就參考它重發了一個更完善的standalonify
。使用這個插件打出來的包能夠更好地生成依賴包的信息,此功能就相似於webpack
中的externals參數。例如UMD中的AMD部分會這樣生成:
... else if (typeof define === 'function' && define.amd) { define(["nornj","react","react-dom"], ...) ...
其實使用browserify
自帶的standalone屬性也能夠打出UMD包,並配合browserify-shim插件也能夠排除外部依賴包,可是打包後依賴包的信息只能定義爲全局的。
而後使用bundle方法進行js模塊合併打包,如代碼爲es6環境則需在此以前執行transform方法進行es6轉es5。
browserify
在打包後需要進行Stream轉換纔可和gulp
配合,在這裏須要使用vinyl-source-stream
和vinyl-buffer
這兩個包。
在使用vinyl-source-stream
時能夠將打包文件重命名,此時可用yargs
包提供的獲取命令參數功能來決定是否使用壓縮版命名。例如命名爲壓縮版需輸入命令:
gulp build-js --min
最後使用gulp.dest方法指定打包後文件保存的目錄。
關於
browserify
更詳細的技術資料你們能夠參考這篇文章:browserify使用手冊
因爲es6代碼目前大部分瀏覽器還未能徹底支持,所以通常都須要轉換爲es5後執行。本示例中使用babel
配合browserify
在打包的過程當中進行轉換,babel
的版本爲6.0+。須要引入babelify
,這個包是browserify
的一個transform插件。使用方法以下:
gulp.task('build-js', function () { return browserify({ entries: './src/base.js' }) .plugin(standalonify, ...) .transform(babelify, { //此處babel的各配置項格式與.babelrc文件相同 presets: [ 'es2015', //轉換es6代碼 'stage-0', //指定轉換es7代碼的語法提案階段 'react' //轉換React的jsx ], plugins: [ 'transform-object-assign', //轉換es6 Object.assign插件 'external-helpers', //將es6代碼轉換後使用的公用函數單獨抽出來保存爲babelHelpers ['transform-es2015-classes', { "loose": false }], //轉換es6 class插件 ['transform-es2015-modules-commonjs', { "loose": false }] //轉換es6 module插件 ... ] }) .bundle() ... });
babelify
插件的配置項格式與.babelrc
文件徹底相同。在babel
升級6.0+後與以前的5.x差異較大,它拆分爲了不少個模塊須要分別引入。這些模塊都須要單獨安裝各自的npm包,具體請查看package.json文件。
presets項須要使用es201五、stage-x、react三個模塊:
es2015
,用於轉換es6代碼
stage-x
,用於轉換更新的es7語法,x是指es7不一樣階段的語法提案,目前有0-3可用
react
,用於轉換React的jsx代碼。
plugins項可引入轉換時須要的插件。通常來講babel-preset-es2015這個包中已經包含了大多數轉換es6代碼的模塊,但也有部分模塊須要在plugins中引入。例如:
transform-object-assign
,用於轉換Object.assign
如轉換時使用loose模式(設置了loose爲true時代碼纔可適應IE8,默認爲false),則須要單獨引入這些模塊的包。如transform-es2015-classes
即爲轉換es6 class的包,若有須要可設置loose模式爲true。
external-helpers
,這個模塊的做用是將babel轉換後的一些公用函數單獨抽出來,這樣就能夠減小轉換後的冗餘代碼量。具體使用方法爲先全局安裝babel:
npm install babel-cli -g
而後執行命令:
babel-external-helpers #可加-t參數按不一樣方式生成,值爲global|umd|var,默認爲global
這樣就能夠在命令行中生成babelHelpers的代碼,而後將之保存爲babelHelpers.js,在本例中放在vendor目錄內。
因爲本例中使用external-helpers方式進行es6轉換,故須要將babelHelpers.js與browserify
打包後的項目js文件進行鏈接合併:
var concat = require('gulp-concat'), sequence = require('gulp-sequence'), gulpif = require('gulp-if'), uglify = require('gulp-uglify'); //定義鏈接js任務 gulp.task('concat-js', function () { var jsLibName = getJsLibName(); return gulp.src(['./vendor/babelHelpers.js', './dist/js/' + jsLibName]) .pipe(concat(jsLibName)) .pipe(gulpif(argv.min, uglify())) .pipe(gulp.dest('./dist/js')); }); //將兩個任務串聯起來 gulp.task('build-all-js', sequence('build-js', 'concat-js'));
先使用gulp-concat
插件將babelHelpers.js和項目js文件進行鏈接合併。
而後使用gulp-if
插件判斷當前執行命令是否輸入了--min
參數,若是是則使用gulp-uglify
插件進行js代碼壓縮。
定義build-all-js任務來將build-js和concat-js任務串聯起來,可是須要使用gulp-sequence
插件才能保證這兩個任務是按順序執行的。
最後,在/dist/js目錄下會生成最終的項目js文件。
本例中使用jasmine
進行單元測試。代碼比較簡單,執行全部test目錄內以"Spec"結尾的文件:
var jasmine = require('gulp-jasmine'); gulp.task("test", function () { return gulp.src(["./test/**/**Spec.js"]) .pipe(jasmine()); });
執行命令:
gulp test
便可在命令行中查看測試結果。
本例中使用eslint
進行js代碼檢驗,需引入gulp-eslint
插件:
var eslint = require('gulp-eslint'); gulp.task('eslint', function () { return gulp.src(['./src/**/*.js']) //獲取src目錄內所有js文件 .pipe(eslint({ //此處eslint的各配置項格式與.eslintrc文件相同 "rules": { "camelcase": [2, { "properties": "always" }], "comma-dangle": [2, "never"], "semi": [2, "always"], "quotes": [2, "single"], "strict": [2, "global"] }, "parser": "babel-eslint" })) .pipe(eslint.format()) .pipe(eslint.failAfterError()); });
執行命令:
gulp eslint
便可在命令行中查看js代碼檢測結果。
另外若是是在es6環境下使用gulp-eslint
,那麼還須要安裝babel-eslint
這個包。此處有個小坑,就是babel-eslint
包是依賴estraverse
和estraverse-fb
包的,但這兩個包其實卻須要單獨安裝。
例中的css及字體文件也須要合併構建,這裏只簡單介紹一下構建css的流程:
var less = require('gulp-less'), cssnano = require('gulp-cssnano'), postcss = require('gulp-postcss'), autoprefixer = require('autoprefixer'); function getCssLibName() { var libName = 'flarej.css'; if (argv.min) { libName = 'flarej.min.css'; } return libName; } //構建項目css文件 gulp.task('build-css', function () { return gulp.src('./src/styles/base.less') .pipe(less()) //轉換less .pipe(rename(getCssLibName())) //重命名轉換後的css文件 .pipe(gulp.dest('./dist/css')); }); //將normalize.css與項目css進行合併 gulp.task('concat-css', function () { var cssLibName = getCssLibName(); return gulp.src(['./vendor/normalize.css', './dist/css/' + cssLibName]) .pipe(concat(cssLibName)) //鏈接合併 .pipe(gulpif(argv.min, cssnano())) //執行css壓縮 .pipe(postcss([autoprefixer({ browsers: ['last 50 versions'] })])) //自動補廠商前綴 .pipe(gulp.dest('./dist/css')); }); //將兩個任務串聯起來 gulp.task('build-all-css', sequence('build-css', 'concat-css'));
本例中的gulp
默認任務即爲構建所有代碼,輸入命令:
gulp #可加"--min"參數構建壓縮版
便可執行,具體構建流程以下:
更多細節你們能夠查看本文示例的源代碼。
(完)