使用 Browserify 和 Gulp 构建 React 应用

JS 世界变化很快,现在有一些新的工具出现在了我们的视野中。今天,我们将探讨 Browserify、Gulp 和 React,看看它们是否适合我们的项目。您可能听说过它们,但还没有时间去尝试。因此,我们将了解使用 Browserify、使用 Gulp 和使用 React 的优缺点。因为了解我们的选择肯定没有坏处。

Browserify:为浏览器打包 Node 模块

Browserify 是一款开发工具,它允许我们在浏览器中编写 Node 风格的模块,或者包含来自 npm 的实际 Node 模块。模块以单独的文件编写,可以进行 `export` 操作,并且模块可以通过 `require` 引入其他模块。Browserify 然后可以解析我们的主 JS 模块,构建依赖关系树,并将所有内容打包在一起。

一个很棒的事情是,现在 NPM 上的数万个模块都可以用于我们的项目。依赖项在 `package.json` 中定义,如果我们的项目 `requires` 它们,Browserify 将把这些依赖项与我们的 JS 一起打包。例如,请看这个 `package.json`

/* package.json */
{
  "name": "hipApp",
  "description": "Showing off hip stuff",
  "dependencies": {
    "browserify": "~3.44.x",
    "gulp": "3.8.x",
    "react": "0.11.x",
    "underscore": "*"
  }
}

一旦我们运行 `npm install`,我们就可以在项目中使用 React 和 Underscore 等模块。现在我们只需在项目中 `require` 它们即可

/* app.js */
var React = require('react');
var myModule = require('./myModule');
// ...

然后我们调用 Browserify

browserify --debug app.js > bundle.js

Browserify 将为我们包含来自 npm 的 React。请注意,它甚至会找出要包含哪些本地模块。我们包含了 `./myModule`,它是与 `app.js` 相同文件夹中的另一个模块。

让我们将这种依赖项加载风格与 AMD 等技术进行比较,AMD 主要由 RequireJS 实现。它们都是 JS 模块定义 API,但实现方式不同。Browserify 遵循 CommonJS,CommonJS 适用于服务器端,而 RequireJS 遵循 AMD,AMD 适用于浏览器端。但是,任何一种都可以在任何环境中使用。

Browserify 的一个很棒之处在于,所有 NPM 模块都可用于我们的项目,而且数量还在不断增加(已超过 86K)。它的模块也不需要包装在 `define` 调用中。

尽管 Browserify 需要预先加载所有模块,这意味着需要构建步骤。AMD 是异步的,因此可以延迟加载模块,只需要刷新页面即可。尽管我们可以使用 Gulp 自动化 Browserify 的构建步骤。

Gulp:流式构建系统

Gulp 是一种 JS 构建系统,类似于 Grunt,它利用“流”或管道,并专注于代码而非配置。构建系统通常设置为监视项目的变化,然后自动处理常见的构建步骤,例如打包、预编译或压缩。Gulp 和 Grunt 都有大量的插件来帮助完成这些事情。Browserify 就是其中一个插件。

让我们来看一个 Gulpfile 的示例。它包含了一些 React JSX 文件的功能,我们还没有讨论过,但稍后会派上用场。请阅读 Gulpfile 中的注释以了解详情

/* gulpfile.js */

// Load some modules which are installed through NPM.
var gulp = require('gulp');
var browserify = require('browserify');  // Bundles JS.
var del = require('del');  // Deletes files.
var reactify = require('reactify');  // Transforms React JSX to JS.
var source = require('vinyl-source-stream');
var stylus = require('gulp-stylus');  // To compile Stylus CSS.

// Define some paths.
var paths = {
  css: ['src/css/**/*.styl'],
  app_js: ['./src/js/app.jsx'],
  js: ['src/js/*.js'],
};

// An example of a dependency task, it will be run before the css/js tasks.
// Dependency tasks should call the callback to tell the parent task that
// they're done.
gulp.task('clean', function(done) {
  del(['build'], done);
});

// Our CSS task. It finds all our Stylus files and compiles them.
gulp.task('css', ['clean'], function() {
  return gulp.src(paths.css)
    .pipe(stylus())
    .pipe(gulp.dest('./src/css'));
});

// Our JS task. It will Browserify our code and compile React JSX files.
gulp.task('js', ['clean'], function() {
  // Browserify/bundle the JS.
  browserify(paths.app_js)
    .transform(reactify)
    .bundle()
    .pipe(source('bundle.js'))
    .pipe(gulp.dest('./src/'));
});

// Rerun tasks whenever a file changes.
gulp.task('watch', function() {
  gulp.watch(paths.css, ['css']);
  gulp.watch(paths.js, ['js']);
});

// The default task (called when we run `gulp` from cli)
gulp.task('default', ['watch', 'css', 'js']);

只需安装 NPM 依赖项,运行 `./node_modules/.bin/gulp`,它就会在后台为我们处理所有事情。我们的文件由 `gulp.watch` 监视,任务会自动运行,并且通过流和管道以干净的方式完成。每当我们修改任何 JS/CSS 时,我们都可以像使用 AMD 一样刷新浏览器。

使用 Grunt 还是 Gulp 取决于个人喜好。两者都有大量的模块可用,不过 Gulp 比较新。Grunt 主要通过配置完成,而 Gulp 主要通过代码和流完成。不过,Gulp 可能会更快一些,因为它不需要中间文件来完成任务。因此,我们的构建系统就位后,让我们进入重头戏:React。

React:声明式和响应式组件

React 是 Facebook 用于构建可重用 Web 组件的 JS 库。它不是像 AngularJS 这样的完整 MVC 框架;React 专注于组件的视图渲染,不依赖任何框架,并且可以平滑地集成到大多数项目中。

Facebook 表示 React 的目的是构建随时间变化的数据的大型应用程序。Facebook 需要一个不会接管整个应用程序的东西。他们可以混合使用可以与遗留组件集成的组件。如果您想了解更多信息,React 的作者之一 Pete Hunt 在 Quora 上写了一些关于 React 的论点

React 没有像传统应用程序中那样使用命令式单向数据绑定,也没有像 Angular 中那样使用双向数据绑定,而是实现了单向响应式数据流。React 的组件是声明式定义的,并且在数据更改时会自动重新渲染,而无需手动注册监听器和处理程序来更新 DOM,或者设置链接函数和数据绑定。就像一个函数一样,数据输入,组件输出。

为了方便起见,让我们来看一个基于 React 主页的示例,它只显示一个姓名

/** @jsx React.DOM */
var React = require('react');  // Browserify!

var HelloMessage = React.createClass({  // Create a component, HelloMessage.
  render: function() {
    return 
Hello {this.props.name}
; // Display a property. } }); React.renderComponent( // Render HelloMessage component at #name. , document.getElementById('name'));

您可能已经注意到,我们的 Javascript 中有一些标记。React 有一个名为 JSX 的语法糖。它需要编译成 JS,这将通过我们之前使用的 Gulpfile 中的 Reactify 插件自动完成。不过,如果需要,React 也有一个 JSX 编译器。请注意,JSX 不是必需的;React 有普通的 JS API,但那样就没那么有趣了。

组件使用 `createClass` 创建。与函数类似,组件可以在渲染过程中以 `props` 的形式接收参数。在上面的示例中,`name="John"` 传递给组件,然后通过 `this.props.name` 引用。请注意,组件只能由一个节点组成。如果我们希望有多个 DOM 节点,则必须将它们都包装在一个根节点下。

除了通过 `props` 获取输入数据之外,组件还可以拥有一个内部的可变状态,可以通过 `this.state` 访问。这是另一个示例,这次是一个计时器,基于 React 的主页

/** @jsx React.DOM */
var React = require('react');

var Timer = React.createClass({
  getInitialState: function() {  // Like an initial constructor.
    return {
        seconds: 0
    };
  },
  incrementTimer: function() {  // A helper method for our Timer.
    this.setState({  // Use setState to modify state.
        seconds: this.state.seconds + 1  // Never modify state directly!
    });
  },
  componentDidMount: function() {  // A method run on initial rendering.
    setInterval(this.incrementTimer, 1000);
  },
  render: function() {
    return (
      
Seconds Elapsed: {this.state.seconds}
); } }); React.renderComponent(, document.getElementById('timer'));

我们有一个 `setInterval` 修改组件的状态,每 1000 毫秒触发一次刷新。不过在更实际的应用中,状态更有可能通过用户输入或通过 XHR 获取的数据来修改,而不是通过简单的间隔来修改。

以上是 React 的一些基础知识。如果可重用声明式组件和响应式渲染恰好是您项目中所需要的,您可以访问 React 入门指南。祝您的开发顺利。无论您是否决定使用这些工具,了解您的选择总是很有利的。

关于 Kevin Ngo

Kevin 是 Mozilla 的虚拟现实开发者,也是 A-Frame(一个开源 WebVR 框架)的核心开发者。他在 Twitter 上的用户名是 @ngokevin_。

更多 Kevin Ngo 的文章…

关于 Robert Nyman [荣誉编辑]

Mozilla Hacks 的技术布道师和编辑。发表演讲和博客文章,内容涉及 HTML5、JavaScript 和开放网络。Robert 是 HTML5 和开放网络的坚定支持者,自 1999 年以来一直从事 Web 前端开发工作,在瑞典和纽约市都有工作经历。他还在 http://robertnyman.com 定期发表博客文章,喜欢旅行和结识新朋友。

更多 Robert Nyman [荣誉编辑] 的文章…


9 条评论

  1. Victor Bjelkholm

    说到 React 和 Firefox OS。我用 React 构建了一个 Firefox OS(实际上是开放式 Web 应用),它的性能非常好,即使在像 Open ZTE 这样性能较低的设备上也是如此,这是我的主要目标设备。

    React 也易于重构,而不会破坏现有代码,因为所有内容都是组件。它非常适合构建 Web 应用程序,一旦您掌握了单向数据流,所有内容就会融合在一起。

    请查看我在 Firefox OS 上的 React 应用程序示例:http://victor.bjelkholm.com/worldnews/

    对于想要安装它的人,它也在 Marketplace 上:https://marketplace.firefox.com/app/worldnews/

    项目的源代码即将发布,我正在将项目拆分成两个部分。

    2014 年 8 月 22 日 08:50

    1. Robert Nyman [编辑]

      不错,感谢分享!

      2014 年 8 月 25 日 00:21

    2. Pete Doherty

      我非常有兴趣在源代码可用后查看它。

      2014 年 8 月 27 日 09:41

      1. Victor Bjelkholm

        您好,Pete!

        一旦我分享了前端源代码,我一定会在此线程中添加一条评论。目前,只有后端是公开的,可以在这里查看:https://github.com/VictorBjelkholm/HeadlinieAPI

        此致

        2014 年 8 月 27 日 11:36

  2. Eric Njanga

    很棒的文章。我习惯使用 Gulp,但我之前从未使用过 Browserify 或 React。我会尽快尝试这些工具。
    感谢这篇文章。

    2014 年 8 月 27 日 10:10

  3. Josh Habdas

    看看一个结合这三种技术的示例应用程序会很有趣。就我个人而言,我不喜欢在浏览器中使用 NPM 模块,因为 NPM 模块不一定是针对浏览器编写的,而且我听说对等依赖项解析会导致,例如,下载两个版本的 jQuery,然后将其发送给客户端。

    在我看来,我更倾向于使用 Bower 或 Component 进行客户端包管理。此外,对于那些对 React 感兴趣的人,可以查看 Facebook 的 Flux 框架。

    2014 年 8 月 28 日 14:23

  4. Bruno

    非常好,但在提供的 package.json 示例中有一个语法错误…最后一个逗号不应该存在 :)

    2014 年 9 月 5 日 12:25

  5. Bruno

    我还需要在 package.json 文件中添加“gulp”

    2014 年 9 月 5 日 13:17

  6. Palmer

    我爱这个流程。

    2014 年 9 月 15 日 17:07

本文的评论已关闭。