资讯专栏INFORMATION COLUMN

基于 Gulp + Browserify 构建 ES6 环境下的自动化前端项目

yuanxin / 639人阅读

摘要:本文特此给大家介绍下如何使用配合来构建基于的前端项目。最后,在目录下会生成最终的项目文件。执行单元测试本例中使用进行单元测试。

随着ReactAngular2Redux等前沿的前端框架越来越流行,使用webpackgulp等工具构建前端自动化项目也随之变得越来越重要。鉴于目前业界普遍更流行使用webpack来构建es6(ECMAScript 2015)前端项目,网上的相关教程也比较多;相对来说使用gulp来构建es6项目的中文教程就比较少。

经过一段时间的摸索,我觉得其实使用gulp也可以很方便地构建es6项目。以下是我感觉gulpwebpack主要的不同之处:

gulp的任务机制和流式管道函数和webpack的配置参数风格有着显著区别,它能使开发者更清晰地了解项目的构建流程。

由于gulp是编程式风格的,所以使用起来可定制化的功能也就更灵活一些,可应对一些构建过程较为复杂的情况。

本文特此给大家介绍下如何使用gulp配合browserify来构建基于es6的前端项目。

Browserify vs Webpack

browserifywebpack都是当下流行的commonjs模块(或es6模块)合并打包工具,打包后的js文件可以直接运行在浏览器环境中。

很多人都知道,webpack功能全面,可以对js、css、甚至图片、字体文件统一进行合并打包,并且插件丰富。而browserify的特点是职责单一,只负责js模块合并打包,有些项目也并不需要将css等资源文件和js打包在一起的功能;它的代码风格也类似管道函数,和gulp的契合度较高;在github上也可以找到相当多的browserify插件,如热替换、代码分割等等。

有一篇文章对browserifywebpack的对比进行了探讨:webpack 跟 browserify 比到底有什么好?

示例项目

本文中使用的示例项目是我为重构过去搞的UI组件库而建的项目,使用browserify构建的分支地址请戳这里。这个项目目前已改用gulp+webpack构建,但是保留了原先用browserify构建的分支代码可供参考。

项目结构目录

项目目录

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中的相关插件内的。

配置package.json

在这里只列出项目依赖的各种包,大致分为如下几类:

{
  ...
  "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

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的使用介绍及技巧

使用Browserify进行js模块合并

配合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-streamvinyl-buffer这两个包。

在使用vinyl-source-stream时可以将打包文件重命名,此时可用yargs包提供的获取命令参数功能来决定是否使用压缩版命名。例如命名为压缩版需输入命令:

gulp build-js --min

最后使用gulp.dest方法指定打包后文件保存的目录。

关于browserify更详细的技术资料大家可以参考这篇文章:browserify使用手册

使用Babel将es6代码转换为es5

由于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项需要使用es2015、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目录内。

生成最终的js代码

由于本例中使用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

即可在命令行中查看测试结果。

执行js代码检验

本例中使用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包是依赖estraverseestraverse-fb包的,但这两个包其实却需要多带带安装。

生成css及字体文件

例中的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"参数构建压缩版

即可执行,具体构建流程如下:

更多细节大家可以查看本文示例的源代码。

(完)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/79173.html

相关文章

  • 前端模块化和构建工具

    摘要:以前一直对前端构建工具的理解不深,经过几天的研究特意来总结一下,第一次写博客,有写错的请多多见谅,该文章我也从其他博客拷了一些内容,如果有冒犯之处,请指出。强大的设计使得它更像是一个构建平台,而不只是一个打包工具。 以前一直对前端构建工具的理解不深,经过几天的研究特意来总结一下,第一次写博客,有写错的请多多见谅,该文章我也从其他博客拷了一些内容,如果有冒犯之处,请指出。 如今,网页不再...

    ad6623 评论0 收藏0
  • 使用gulp 进行ES6开发

    摘要:已经转码成了已经转码成了合并压缩并重命名的文件使用如果我们使用了中的,通过进行模块化开发,那么通过转码后,将被转码成符合规范的和等,但是浏览器还是不认识,这时可以使用对代码再次进行构建。 一说起ES6,总会顺带看到webpack、babel、browserify还有一些认都不认识的blabla名词,对于gulp才会一点点的我来说,内心简直是崩溃的,上网查了一些文章,探索着用gulp搭起...

    lauren_liuling 评论0 收藏0
  • 自定义前端构建工具生成器 generator-pg-cloud

    摘要:自定义前端构建工具生成器近期公司前端一直在做效率提升,流程优化,很荣幸这个担子落在了我身上,除了一些培训,分享之外,自己弄了个基于的前端构建环境生成器,在此分享给大家,觉得有用的请试用。,不出意料的话,构建环境已经生成完毕了。 自定义前端构建工具生成器generator-pg-cloud 近期公司前端一直在做效率提升,流程优化,很荣幸这个担子落在了我身上,除了一些培训,分享之外,自己...

    snowell 评论0 收藏0
  • 做一个合格的前端gulp资源大集合

    摘要:承接前一篇做一个合格的前端,自动化构建工具入门教程故而整理了如下插件资源大全。接下来我会逐一开源观点网开发过程中的前后端技术,如全文索引自定义富文本编辑器图片上传压缩水印等等。 承接前一篇《做一个合格的前端,gulp自动化构建工具入门教程》故而整理了如下gulp插件资源大全。**【我的新作观点网:http://www.guandn.com (观点网是一个猎获新奇、收获知识、重在独立思考...

    Baoyuan 评论0 收藏0
  • 我的React开发之路1:React的环境搭建

    摘要:是大脸书出的一个前端开发框架。与其说是一个框架,我更加认为更是一种模式,从年月份开始接触,我就认为这个框架以后一定会火。是一个单向数据流的框架,不同于和的双向数据绑定的单向数据流可以数据模式更加单一,更利于前端的维护。 React是大脸书出的一个前端开发框架。与其说是一个框架,我更加认为React更是一种模式,从2015年10月份开始接触React,我就认为这个框架以后一定会火。Rea...

    aisuhua 评论0 收藏0

发表评论

0条评论

yuanxin

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<