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