随着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"参数构建压缩版
便可执行,具体构建流程以下:
更多细节你们能够查看本文示例的源代码。
(完)