React 已经成为当下最热门的前端框架之一,其虚拟 DOM 和组件化开发让前端开发更加丰富灵活,而 Webpack 凭借它异步加载和可分离打包等优秀的特性,更为 React 的开发提供了便利,其优秀的特性不再赘述。

本文将对 React、Babel、Webpack 的环境搭建进行阐述,指出搭建过程中存在的一些坑,并提出相应的解决方法。

跟随本文档进行操作,最后你将得到一个属于自己的基于 React+Webpack 的框架项目。

新建 React 项目

目录结构

如上图所示即为新建的项目目录结构。

.idea 文件夹保存的是 IDEA 编译器的一些设置文件;asset 文件夹保存的是压缩之后的文件;node_modules 文件夹存放的是使用 npm 安装 react、babel 及 webpack 后生成的相应文件。

.babelrc 文件是 babel 的配置文件;package.json 文件是记录 npm 安装及开发依赖信息的文件;test.htmltest.jstest.css 文件是该项目的测试文件,分别保存 html 信息、js 代码以及样式信息;webpack.config.js 文件是保存 webpack 配置信息的文件。

修改编译器为 JSX Harmony

由于 react 代码需要 JSX 来写,因此我们要修改编译器相应的配置(以下以 IDEA 15.0.2 为例)。如上图所示,修改相应的配置后,使用 JSX 编写代码就不会出现错误提示。

准备工作

安装 React、Babel 及 Webpack 需要使用 npm,因此需要安装 Node.js(建议安装最新版本的 Node.js,目前更新到 V8.1.0),也可以使用 cnpm 进行安装,使用 cnpm 安装速度更快,也更加稳定。

cnpm:npm 在国内的映射,目前使用的是淘宝镜像,安装方法:在 cmd 命令窗口中输入如下代码即可进行安装。

npm install -g cnpm --registry=https://registry.npm.taobao.org

安装 npm

首先要安装 npm,在项目文件夹中打开 cmd 命令窗口,输入如下代码即可完成 npm 的安装,同时创建 node_modules 文件夹。

npm install

安装完成后再输入如下代码即可完成 npm 的初始化,同时新建 package.json 文件。

npm init

安装 react

在 cmd 命令窗口中输入如下代码安装 react 模块。

npm install react --save

由于 react 升级后把 reactDOM 独立出来了,因此还需要安装 react-dom,输入如下代码。

npm install react-dom --save

安装成功后在 package.json 文件中会有相应的体现,如下图所示。

react + reactDOM

安装 webpack

什么是 webpack

事实上它是一个打包工具,而不是像 RequireJS 或 SeaJS 这样的模块加载器,通过使用 webpack,能够像 Node.js 一样处理依赖关系,然后解析出模块之间的依赖,将代码打包

为什么要用 webpack

如今的很多网页其实可以看作是功能丰富的应用,它们拥有着复杂的 JavaScript 代码和一大堆依赖包。为了简化开发的复杂度,前端社区涌现出了很多好的实践方法。

  1. 模块化,让我们可以把复杂的程序细化为小的文件
  2. 类似于 TypeScript 这种在 JavaScript 基础上拓展的开发语言,使我们能够实现目前版本的 JavaScript 不能直接使用的特性,并且之后还能转换为 JavaScript 文件使浏览器可以识别
  3. Scss、less 等 CSS 处理器
  4. ……

这些改进确实大大的提高了我们的开发效率,但是利用它们开发的文件往往需要进行额外的处理才能让浏览器识别,而手动处理又是十分繁琐的,这就为 webpack 类的工具的出现提供了需求。

安装 webpack

全局安装 webpack,需要在 cmd 命令窗口中输入如下的代码。

npm install webpack -g

在本项目中安装 webpack,需要在 cmd 命令窗口中输入如下的代码。

npm install webpack --save-dev

安装成功后,在 package.json 文件中会有相应的体现,如下图所示。

成功安装 webpack

webpack 配置

每个项目下面都必须配置有一个 webpack.config.js,它的作用如同常规的 gulpfile.js / Gruntfile.js,是配置 webpack 的一个配置文件项,它告诉 webpack 需要做什么。因此在项目根目录中创建 js 文件 webpack.config.js,文件内容大概如下所示。

var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
  // 配置生成 Source Maps,选择合适的选项
  // devtool: 'eval-source-map',
  // 页面入口文件配置
  entry: __dirname + '/test.js',
  // 入口文件输出配置
  output: {
    path: __dirname + '/asset/',
    filename: 'bundle.js'
  },
  module: {
    // 加载器配置
    loaders: [
      {
        test: /\.json$/,
        loader: 'json-loader'
      },
      {
        test: /\.jsx?$/,
        exclude: [/node_modules/, /.idea/],
        loader: 'babel-loader'
      },
      {
        test: /\.css$/,
        loader: ['style-loader', 'css-loader'] // 添加对样式表的处理
      }
    ]
  },
  // 插件
  plugins: [
    new webpack.BannerPlugin("Copyright Room 407 8th Building."), // 在这个数组中 new 一个就可以了
    new HtmlWebpackPlugin({
      template: __dirname + "/test.html" //new 一个这个插件的实例,并传入相关的参数
    }),
    new webpack.optimize.UglifyJsPlugin(),
    new webpack.DefinePlugin({
      'process.env': {
          'NODE_ENV': JSON.stringify('production')
      }
    })
  ]
  // 其它解决方案配置
  // resolve: {
    // extensions: ['.js', '.json']
  // }
};

现在对 webpack.config.js 配置文件主要的几个配置项进行说明

entry:页面入口文件,在这里指明主入口的 js 文件就可以了(__dirname 是 node.js 定义的一个变量,指当前文件路径)

output:文件打包压缩后的输出路径配置,里面包括输出的路径及输出文件名称的配置

module:定义了对模块的处理逻辑,这里用 loaders 定义了一系列的加载器以及一系列的正则。当需要加载的文件匹配 test 的正则时,就会调用后面的 loader 对文件进行处理,这正是 webpack 强大的原因。比如这里定义了以 .js 结尾的文件都用 babel-loader 做处理,而以 .jsx 结尾的文件会先经过 jsx-loader 处理,然后经过 babel-loader 处理。当然这些 loader 都需要使用 npm 进行安装

plugins:这里定义了需要使用的一些插件

安装 loader 示例

上述 loader 可以使用 npm 进行安装,在 cmd 命令窗口中输入代码即可,如下所示。

npm install babel-loader --save-dev
npm install css-loader --save-dev
npm install jsx-loader --save-dev
npm install json-loader --save-dev
npm install style-loader --save-dev

安装成功后,在 package.json 文件中会有相应的体现。

Babel

什么是 Babel

Babel 其实是一个编译 JavaScript 平台,它的强大之处表现在可以通过编译帮你达到以下的目的。

  1. 下一代的 JavaScript 标准(ES6、ES7),这些标准目前并未被当前的浏览器完全支持,Babel 可以对其进行转换,将其转换为当前浏览器都支持的语言
  2. 可以转换基于 JavaScript 进行了扩展的语言,例如 react 的 JSX

安装 Babel

在 cmd 命令窗口中输入如下代码即可完成安装。

npm install babel -g

Babel 提供 babel-cli 工具,用于命令行转码,在 cmd 命令窗口输入如下代码即可完成安装,安装完成后在 package.json 文件中会有相应的体现。

npm install babel-cli --save-dev

如果某些代码需要调用 Babel 的 API 进行转码,就需要使用 babel-core 模块,在 cmd 命令窗口中输入如下代码进行安装。

npm install babel-core --save-dev

Babel 的配置文件是 .babelrc ,存放在项目的根目录下,使用 Babel 的第一步,就是配置这个文件,首先新建 .babelrc 文件,文件默认内容如下。

{
  "presets": [],
  "plugins": []
}

presets 字段设定转码规则,官方提供以下的规则集,可以根据需要进行安装,安装完成后在 package.json 文件中会有相应的体现。

# ES2015 转码规则
npm install --save-dev babel-preset-es2015

# react 转码规则
npm install --save-dev babel-preset-react

# ES7 不同阶段语法提案的转码规则(共有 4 个阶段),选装一个
npm install --save-dev babel-preset-stage-0
npm install --save-dev babel-preset-stage-1
npm install --save-dev babel-preset-stage-2
npm install --save-dev babel-preset-stage-3

安装之后在 .babelrc 文件中加入配置

{
  "presets": [
    "es2015",
    "react",
    "stage-0"
  ],
  "plugins": []
}

同时安装 babel-loader,在 cmd 命令窗口中输入如下代码进行安装,安装成功后在 package.json 文件中会有相应的体现。

npm install babel-loader --save-dev

babel-loader 的使用在之前 webpack.config.js 的配置中已经进行了阐述。

_注_:有的用户不想单独创建 .babelrc 文件对 babel 进行配置,那么可以将相应的配置写在 webpack.config.js 文件中,如下所示。

module: {
  // 加载器配置
  loaders: [
    {
      test: /\.json$/,
      loader: 'json-loader'
    },
    {
      test: /\.jsx?$/,
      exclude: [/node_modules/, /.idea/],
      loader: 'babel-loader',
      query: {
        presets: ['react', 'es2015']
      }
    },
    {
      test: /\.css$/,
      loader: 'style-loader!css-loader?modules' // 添加对样式表的处理
    }
  ]
}

至此就完成了开发环境的配置

示例项目代码

开发环境配置完成后,接下来就是要编写相应的代码,本示例项目的代码如下所示。

test.html 文件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>React 测试</title>
</head>
<body>
<div id="root1">
  <!-- This element's contents will be replaced with your component. -->
</div>
<div id="root2">
  <!-- This element's contents will be replaced with your component. -->
</div>
<div id="root3">
  <!-- This element's contents will be replaced with your component. -->
</div>
<div id="root4">
  <!-- This element's contents will be replaced with your component. -->
</div>
<div id="root5">
  <!-- This element's contents will be replaced with your component. -->
</div>
<div id="root6">
  <!-- This element's contents will be replaced with your component. -->
</div>
<!-- 引入test.js -->
<script type="text/javascript" src="asset/bundle.js"></script>
</body>
</html>

test.css 文件

html {
  width: 100%;
  height: 100%;
}

body {
  width: 100%;
  height: 100%;
}

.domNode {
  width: 300px;
  height: 200px;
  position: relative;
  background-color: #D3D3D3;
}

test.js 文件

import React from 'react';
import ReactDOM from 'react-dom';
import styles from './test.css'; // 导入 css 文件

// 第一个例子
class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }
  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }
  componentWillUnmount() {
    clearInterval(this.timerID);
  }
  tick() {
    this.setState({
      date: new Date()
    });
  }
  render() {
    return (
      <div className={styles.domNode}>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
ReactDOM.render(
  <Clock />,
  document.getElementById('root1')
);

// 第二个例子
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

function App () {
  return (
    <div className={styles.domNode}>
      <Welcome name="Sara"/>
      <Welcome name="Cahal"/>
      <Welcome name="Edite"/>
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root2')
);

// 第三个例子
function LoginButton(props) {
  return (
    <button onClick={props.onClick}>
      Login
    </button>
  );
}

function LogoutButton(props) {
  return (
    <button onClick={props.onClick}>
      Logout
    </button>
  );
}

function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}

function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}

class LoginControl extends React.Component {
  constructor(props) {
    super(props);
    this.handleLoginClick = this.handleLoginClick.bind(this);
    this.handleLogoutClick = this.handleLogoutClick.bind(this);
    this.state = {isLoggedIn: false};
  }

  handleLoginClick() {
    this.setState({isLoggedIn: true});
  }

  handleLogoutClick() {
    this.setState({isLoggedIn: false});
  }

  render() {
    const isLoggedIn = this.state.isLoggedIn;

    let button = null;
    if (isLoggedIn) {
      button = <LogoutButton onClick={this.handleLogoutClick}/>;
    } else {
      button = <LoginButton onClick={this.handleLoginClick}/>;
    }

    return (
      <div className={styles.domNode}>
        <Greeting isLoggedIn={isLoggedIn}/>
        {button}
      </div>
    );
  }
}
ReactDOM.render(
  <LoginControl />,
  document.getElementById('root3')
);

// 第四个例子
function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div className={styles.domNode}>
      <h1>Hello!</h1>
      {
        unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
    </div>
  );
}

const messages = ['React', 'Re: React', 'Re:Re: React', 'Re:Re:Re: Fuck React'];
ReactDOM.render(
  <Mailbox unreadMessages={messages} />,
  document.getElementById('root4')
);

// 第五个例子
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li key={number.toString()}>
    {number}
  </li>
);
ReactDOM.render(
  <ul>{listItems}</ul>,
  document.getElementById('root5')
);

// 第六个例子
function BoilingVerdict(props) {
  if (props.celsius >= 100) {
    return <p>The water would boil.</p>;
  }
  return <p>The water would not boil.</p>;
}

class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }

  handleChange(e) {
    this.setState({temperature: e.target.value});
  }

  render() {
    const temperature = this.state.temperature;
    return (
      <fieldset>
        <legend>Enter temperature in Celsius:</legend>
        <input
          value={temperature}
          onChange={this.handleChange}/>
        <BoilingVerdict
          celsius={parseFloat(temperature)}/>
      </fieldset>
    );
  }
}

ReactDOM.render(
  <Calculator />,
  document.getElementById('root6')
);

运行 webpack

代码编写完成后运行 webpack 对现有的项目代码进行打包,在 cmd 命令窗口输入如下代码即可运行。

webpack

如果不愿意输入 webpack 来启动相应的打包操作,可以在 package.json 文件的 scripts 标签中加入如下所示的代码。

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "start": "webpack"
  }

完成后即可通过在 cmd 命令窗口中输入如下所示的代码启动 webpack。

npm start

运行结果如下图所示。

运行结果

运行成功后将会生成压缩文件(生成文件的路径及名称都是之前在 webpack.config.js 文件中配置好的),打开入口的 html 文件即可浏览编码的结果,如下图所示。

结果展示

到这里你就拥有一个属于自己的 React+Webpack 的项目了。