隨着React、Angular二、Redux等前沿的前端框架愈來愈流行,使用webpack、gulp等工具構建前端自動化項目也隨之變得愈來愈重要。鑑於目前業界廣泛更流行使用webpack來構建es6(ECMAScript 2015)前端項目,網上的相關教程也比較多;相對來講使用gulp來構建es6項目的中文教程就比較少。css
通過一段時間的摸索,我以爲其實使用gulp
也能夠很方便地構建es6項目。如下是我感受gulp
和webpack
主要的不一樣之處:html
gulp
的任務機制和流式管道函數和webpack
的配置參數風格有着顯著區別,它能使開發者更清晰地瞭解項目的構建流程。gulp
是編程式風格的,因此使用起來可定製化的功能也就更靈活一些,可應對一些構建過程較爲複雜的狀況。本文特此給你們介紹下如何使用gulp
配合browserify
來構建基於es6的前端項目。前端
browserify
與webpack
都是當下流行的commonjs模塊(或es6模塊)合併打包工具,打包後的js文件能夠直接運行在瀏覽器環境中。react
不少人都知道,webpack
功能全面,能夠對js、css、甚至圖片、字體文件統一進行合併打包,而且插件豐富。而browserify
的特色是職責單一,只負責js模塊合併打包,有些項目也並不須要將css等資源文件和js打包在一塊兒的功能;它的代碼風格也相似管道函數,和gulp
的契合度較高;在github上也能夠找到至關多的browserify
插件,如熱替換、代碼分割等等。webpack
有一篇文章對
browserify
和webpack
的對比進行了探討:webpack 跟 browserify 比到底有什麼好?git
本文中使用的示例項目是我爲重構過去搞的UI組件庫而建的項目,使用browserify
構建的分支地址請戳這裏。這個項目目前已改用gulp
+webpack
構建,可是保留了原先用browserify
構建的分支代碼可供參考。es6
示例項目目錄大致如上所示,其中使用babel
進行es6至es5轉換,並使用eslint
進行js代碼檢驗。你們看到這裏可能有疑問,爲何項目中沒有babel及eslint的配置文件.babelrc
和.eslintrc
呢?緣由就是這些配置文件裏的內容實際上是能夠直接配置在gulpfile.js
中的相關插件內的。github
在這裏只列出項目依賴的各類包,大體分爲以下幾類:web
{ ... "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總體結構以下所示:npm
//引入依賴的各類包: 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插件也能夠排除外部依賴包,可是打包後依賴包的信息只能定義爲全局的。
browserify
在打包後需要進行Stream轉換纔可和gulp
配合,在這裏須要使用vinyl-source-stream
和vinyl-buffer
這兩個包。vinyl-source-stream
時能夠將打包文件重命名,此時可用yargs
包提供的獲取命令參數功能來決定是否使用壓縮版命名。例如命名爲壓縮版需輸入命令:gulp build-js --min
關於
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文件。es2015
,用於轉換es6代碼stage-x
,用於轉換更新的es7語法,x是指es7不一樣階段的語法提案,目前有0-3可用react
,用於轉換React的jsx代碼。transform-object-assign
,用於轉換Object.assigntransform-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代碼壓縮。gulp-sequence
插件才能保證這兩個任務是按順序執行的。本例中使用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代碼檢測結果。
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"參數構建壓縮版
便可執行,具體構建流程以下:
更多細節你們能夠查看本文示例的源代碼。
(完)