Webpack for React

Webpack 是什么?

多年来,Web 开发从简单的几乎不使用 JavaScript 的静态页面演变发展成具有复杂 JavaScript 和大型依赖树(依赖于多个其他文件的文件)的全功能 Web 应用程序。

为了帮助开发应用所面对的日益复杂问题,社区也提出了不同的方法和做法,例如:

  • JavaScript 模块化的应用,允许我们将程序划分并组织成多个文件。
  • JavaScript 预处理器(允许我们使用新的特性,而这些特性仅在将来的 JavaScript 版本中才可用)和compile-to-JavaScript 的语言(例如 CoffeeScript )

虽然这样的进步非常有帮助,但是,也给开发过程带来一些必要的额外步骤:我们需要把文件打包并且转换成浏览器能够理解的内容。而这就需要像 Webpack 这类的工具。

Webpack 是一个现代 JavaScript 应用程序的模块打包器:能够分析你的项目结构,找到 JavaScript 模块和其他的资源,然后将所有这些模块打包成少量的 bundle,由浏览器加载。

Webpack 和其他的构建工具(例如 Grunt 和 Gulp)相比有什么特性?

Webpack 和 Grunt/Gulp 是不一样的,它不仅仅是一个构建工具,Webpack 的优点使得 Webpack 能够替代 Grunt 和 Gulp。

Grunt 和 Gulp 这类构建工具的工作方式是:在配置文件中,你可以指定运行的任务和步骤,然后进行转换、组合和压缩这些文件。
Webpack 入门-胡自自博客
Webpack 的工作方式是:它会分析你整个项目。指定一个入口文件,Webpack 会查看所有的项目依赖文件(通过 require 和 import 引入),使用 loaders 处理它们,最后打包成 JavaScript 文件。
Webpack 入门-胡自自博客

开始

通过 npm 全局安装 Webpack

npm install -g webpack

或者安装到你的项目目录

npm install --save-dev webpack

创建示例

让我们来创建一个使用 Webpack 的示例项目。创建一个新的文件夹:例如 webpack-sample-project。在终端运行下面的命令,在webpack-sample-project 文件夹内新建 package.json 文件 ,这是一个标准的 npm 说明文件,其中包含项目的各种信息,并让开发人员指定依赖关系(可以自动下载并安装)并定义脚本任务。

npm init

init 命令会在终端询问一系列有关项目的问题(例如项目名称,描述,作者信息等),不过你不用担心,如果你不打算在 npm 中发布你的模块,按回车键默认即可。

当 package.json 文件创建完成后,在终端输入下面命令,添加 webpack 作为项目依赖关系并安装它:

npm install --save-dev webpack

Webpack 安装完成后,我们回到 webpack-sample-project 目录,里面有2个文件夹: app 文件夹和 public 文件夹,app 文件夹存放项目源代码和 JavaScript 模块,public 文件夹存放浏览器需要访问的公共文件(包括使用 webpack 打包生成的 JavaScript 文件以及一个 index.html 文件)。接下来让我们创建3个文件:index.html 新建到 public 文件夹中,main.js 和 Greeter.js 新建到 app 文件夹中。最后,项目结构如下图所示:
Webpack 入门-胡自自博客
index.html 包含了一个基本的 HTML 页面,用于加载我们打包后的 JavaScript 文件(命名为 bundle.js

<!DOCTYPE html><html lang="en">  <head>    <meta charset="utf-8">    <title>Webpack Sample Project</title>  </head>  <body>    <div id='root'>    </div>    <script src="bundle.js"></script>  </body></html>

接下来,让我们回到之前创建的文件:main.js 和 Greeter.js。Greeter.js 返回一个带有问候信息的新 HTML元素的函数。 main.js 文件会将 Greeter 模块返回的 HTML 元素插入页面。

// main.jsvar greeter = require('./Greeter.js');document.getElementById('root').appendChild(greeter());// Greeter.jsmodule.exports = function() {  var greet = document.createElement('div');  greet.textContent = "Hi there and greetings!";  return greet;};

运行你的第一个构建

Webpack 基本命令语法是 “webpack {entry file} {destination for bundled file}”。注意:Webpack 需要指定一个入口文件,它将自动确定所有项目的依赖关系。另外,如果你没有全局安装 webpack(使用命令 npm install -g webpack 安装),则需要在项目的 node_modules 文件夹中引用 webpack 命令。对于示例项目,webpack 命令使用方式如下:

node_modules/.bin/webpack app/main.js public/bundle.js

你应该能在终端看到如下输出:
Webpack 入门-胡自自博客
提示:Webpack 将 main.js 和 Greeter.js 文件打包成了 bundle.js。如果你在浏览器中打开 index.html,结果将如下所示:
Webpack 入门-胡自自博客

Webpack 配置

Webpack 有许多高级选项,允许使用 loaders 和 plugins 来对加载的模块转换。虽然我们也可以在命令行中使用这些配置,但是这很不直观且容易出错。最好的方法是使用 webpack 配置文件:一个简单的 JavaScript 文件,可以在其中放置与构建有关的所有配置信息。

在根目录下创建 webpack.config.js 文件

module.exports = {  entry:  __dirname + "/app/main.js",  output: {    path: __dirname + "/public",    filename: "bundle.js"  }}

注:“__dirname”是 node.js 中的一个全局变量,它指向当前执行脚本所在的目录。

你可以简单在终端运行 webpack 命令(非全局安装需使用 node_modules/.bin/webpack 命令),而不需要任何参数,由于 webpack.config 文件已存在,因此webpack 命令将根据可用的配置构建应用程序。 结果如下所示:
Webpack 入门-胡自自博客

添加快捷启动

在终端输入类似这样的 `node_modules/.bin/webpack` 长命令,无聊且容易出错。还好,npm 可以通过设置 package.json 文件中的 `scripts`,只需执行一个简单命令(例如 `npm start`),就能够实现和长命令一样的效果。我们可以看看下面:

{  "name": "webpack-sample-project",  "version": "1.0.0",  "description": "Sample webpack project",  "scripts": {    "start": "webpack"  },  "author": "Cássio Zen",  "license": "ISC",  "devDependencies": {    "webpack": "^1.12.9"  }}

start 是一个特殊的脚本,你可以直接在终端使用命令 npm start 执行。 你可以创建任何其他脚本名称,和 start 不同的时,执行它们你将需要使用命令 npm run {script name}(例如 npm run build)。 可以从下面的图中看出,webpack 命令被 npm start 脚本执行了:
Webpack 入门-胡自自博客

生成 source maps

Webpack 有许多的配置项,先让我们来看看其中的一个配置项:Source Maps。
把所有 JavaScript 模块打包到一个(或多个)bundle 文件,具有许多优点。一个明显的缺点是在浏览器中无法进行原代码调试:尝试调试原代码是非常有挑战性的。 然而,Webpack 可以在打包时生成 source maps: source maps 提供了将打包文件中的代码映射回其原始源文件的方式,使代码在浏览器中可读和易于调试。

要配置 Webpack 以生成指向原始文件的 source maps,需要配置 devtool,它有以下四种不同的配置选项:

devtool 选项 描述
source-map Generate a complete, full featured source map in a separate file. This option has the best quality of source map, but it does slow down the build process.
cheap-module-source-map Generate source map in a separate file without column-mappings. Stripping the column mapping favors a better build performance introducing a minor inconvenience for debugging: The browser developer tools will only be able to point to the line of the original source code, but not to a specific column (or character).
eval-source-map Bundles the source code modules using “eval”, with nested, complete source map in the same file. This option does generate a full featured source map without a big impact on build time, but with performance and security drawbacks in the JavaScript execution. While it’s a good option for using during development, this option should never be used in production.
cheap-module-eval-source-map The fastest way to generate a source map during build. The generated source map will be inlined with the same bundled JavaScript file, without column-mappings. As in the previous option, there are drawbacks in JavaScript execution time, so this option is not appropriate for generating production-ready bundles.

了解更多,请查看 webpack source-mapping

从描述中可以看出,由上往下构建打包时间越来越快。
对中小型项目来说,“eval-source-map”是一个很好的选择:它生成一个完整的 source map,不过,你只能在开发过程中使用它。 继续对项目中的 webpack.config.js 文件进行如下配置:

module.exports = {  devtool: 'eval-source-map',  entry:  __dirname + "/app/main.js",  output: {    path: __dirname + "/public",    filename: "bundle.js"  }}

Webpack 开发服务器

Webpack 有一个可选的服务器可用于本地开发。 它是一个小型的 node.js express app,它提供静态文件服务,并根据您的 Webpack 配置打包资源文件,缓存在内存中,并在更改源文件时自动刷新浏览器。 它是一个单独的 npm 模块,需要作为项目依赖安装:

npm install --save-dev webpack-dev-server

webpack-dev-server 可以由 webpack.config.js 文件中的 devserver 进行配置,相关的配置如下:

devserver 选项 描述
contentBase 默认情况下, webpack-dev-server 为根文件夹提供本地服务器,如果想为其他目录下的文件提供本地服务器,应该在这里设置其所在目录(例如我们的示例项目设置为 “public”)
port 设置默认监听端口,如果省略,默认为”8080“
inline 设置为 true,当源文件改变时会自动刷新页面
colors 设置终端输出字体的颜色
historyApiFallback 开发单页应用时非常有用,它依赖于 HTML5 history API,如果设置为 true,所有的跳转将指向 / ,也就是 index.html 文件

把这些命令都加到 webpack 的配置文件中,现在的 webpack.config.js 文件配置如下所示:

module.exports = {  devtool: 'eval-source-map',  entry:  __dirname + "/app/main.js",  output: {    path: __dirname + "/public",    filename: "bundle.js"  },  devServer: {    contentBase: "./public",    colors: true,    historyApiFallback: true,    inline: true  } }

现在你可以执行 webpack-dev-server 启动服务器:

node_modules/.bin/webpack-dev-server

为了方便起见,您可以在项目的 package.json 文件中的 “scripts” 添加如下命令,通过调用 “npm start” 来运行服务器,如下所示(在脚本内无需填写 “node_modules / .bin” 这样的完整路径):

{  "name": "webpack-sample-project",  "version": "1.0.0",  "description": "Sample webpack project",  "scripts": {    "start": "webpack-dev-server --progress"  },  "author": "Cássio Zen",  "license": "ISC",  "devDependencies": {    "webpack": "^1.12.9",    "webpack-dev-server": "^1.14.0"  }}

Loaders(加载器)

Webpack 最令人激动的功能之一就是 loaders。 通过使用 loaders,Webpack 可以调用外部的脚本和工具来处理源文件。 例如将 JSON 文件解析为纯 JavaScript,或将下一代的 JavaScript(ES6,ES7) 代码转换为当前浏览器可以理解的常规 JavaScript 代码。loaders 对于 React 开发也是必不可少的,因为它们可将 React 的 JSX 转换为 JavaScript。

Loaders 需要单独安装,并在 webpack.config.js 中的 “modules” 选项下进行配置。 Loaders 配置包括:

  • test:一个用以匹配 loader 所处理文件的拓展名的正则表达式(必须)
  • loader:loader 的名称(必须)
  • include/exclude: 手动添加必须处理的文件(文件夹)或忽略不需要处理的文件(文件夹)(可选);
  • query:为 loader 提供额外的设置选项(可选)

用示例查看更清晰,更改示例应用程序,并将问候语文本移到单独的 Json 配置文件。 首先安装 Webpack 的 json-loader 模块:

npm install --save-dev json-loader

接下来,在 webpack.config.js 文件中添加 json-loader:

module.exports = {  devtool: 'eval-source-map',  entry:  __dirname + "/app/main.js",  output: {    path: __dirname + "/public",    filename: "bundle.js"  },  module: {    loaders: [      {        test: /\.json$/,        loader: "json"      }    ]  },  devServer: {    contentBase: "./public",    colors: true,    historyApiFallback: true,    inline: true  }}

最后,创建一个 config.json 文件,并在 Greeter.js 中 require。 新的 config.json 文件的源代码如下所示:

{  "greetText": "Hi there and greetings from JSON!"}

更新后的 Greeter.js 如下所示:

var config = require('./config.json');module.exports = function() {  var greet = document.createElement('div');  greet.textContent = config.greetText;  return greet;};

Babel

Babel 是一个编译 JavaScript 的平台,它非常强大,强大到可以让你:

  • 使用下一代的 JavaScript 编程(ES6 / ES2015,ES7 / ES2016 等等),即使这些标准现在还未被当前的浏览器完全的支持;
  • 使用 JavaScript 的拓展语言,例如 React 的 JSX。

Babel 是一个独立的工具,但是也可以和 Webpack 配合使用。

安装和配置 Babel

Babel 是若干个 npm 模块包,它的核心功能由 babel-core 包提供。如果你在代码中希望使用不同的功能和扩展,则需要分别安装单独的包(最常见的是分别是用于编译 ES6 和 React 的 JSX 的 babel-preset-es2015 和 babel-preset-react 包),使用下面的命令安装:

npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react

和其他的 loader 一样,你可以在示例项目中的 webpack.config.js 文件中配置 babel,详细的配置请看下面:

module.exports = {  devtool: 'eval-source-map',  entry:  __dirname + "/app/main.js",  output: {    path: __dirname + "/public",    filename: "bundle.js"  },  module: {    loaders: [      {        test: /\.json$/,        loader: "json"      },      {        test: /\.js$/,        exclude: /node_modules/,        loader: 'babel',        query: {          presets: ['es2015','react']        }      }    ]  },  devServer: {    contentBase: "./public",    colors: true,    historyApiFallback: true,    inline: true  }}

现在,webpack 的配置可以让我们在项目中使用 ES6 模块和语法,以及 JSX。接下来,我们修改示例代码。让我们先安装 React 和 React-DOM:

npm install --save react react-dom

接下来让我们修改 Greeter.js ,使用 ES6 语法定义并返回一个 React 组件,代码如下:

import React, {Component} from 'react'import config from './config.json';class Greeter extends Component{  render() {    return (      <div>        {config.greetText}      </div>    );  }}export default Greeter

最后,让我们更新 main.js 文件,使用 ES6 语法渲染 Greeter.js 组件:

// main.jsimport React from 'react';import {render} from 'react-dom';import Greeter from './Greeter';render(<Greeter />, document.getElementById('root'));

Babel 的配置文件

Babel 可以完全在 webpack.config.js 文件中配置,但是,Babel 本身就有许多配置项,如果全部都放在 webpack.config.js 文件中,webpack.config.js 会很臃肿,所以,许多开发人员会单独创建一个文件用于配置 Babel:即 .babelrc(请注意,名称前的小数点)
让我们将 babel 的配置提取出来,分为两个配置文件进行配置,如下所示:

// webpack.config.jsmodule.exports = {  devtool: 'eval-source-map',  entry:  __dirname + "/app/main.js",  output: {    path: __dirname + "/public",    filename: "bundle.js"  },  module: {    loaders: [      {        test: /\.json$/,        loader: "json"      },      {        test: /\.js$/,        exclude: /node_modules/,        loader: 'babel'      }    ]  },  devServer: {...} // Omitted for brevity}// .babelrc{  "presets": ["react", "es2015"]}

Beyond JavaScript

Webpack 最独特的特点之一就是将每个文件视为一个模块。通过对应的 loaders,对 JavaScript 代码,CSS,字体等进行处理。 Webpack 可以根据 @import 和 CSS 中的 URL 将所有有依赖关系的文件构建,预处理和打包。

样式

Webpack 提供两种 loaders 来处理样式:`css-loader` 和 `style-loader`。两者处理的任务不同,`css-loader` 负责查找 @import 和 url(…) 并处理它们, `style-loader`将所有的计算后的样式加入页面中。两者结合使用,能够把样式嵌入到 webpack 打包后的 JS 文件中。

接下来,我们安装 css-loaderstyle-loader 和更新 webpack.config.js 文件:

npm install --save-dev style-loader css-loader
// webpack.config.jsmodule.exports = {  devtool: 'eval-source-map',  entry:  __dirname + "/app/main.js",  output: {    path: __dirname + "/build",    filename: "bundle.js"  },  module: {    loaders: [      {        test: /\.json$/,        loader: "json"      },      {        test: /\.js$/,        exclude: /node_modules/,        loader: 'babel'      },      {        test: /\.css$/,        loader: 'style!css'      }    ]  },  devServer: {...}}

Note: The exclamation point (“!”) can be used in a loader configuration to chain different loaders to the same file types.

接着,在项目中新建 main.css 文件,对应用的一些元素设置默认样式,写入如下内容:

html {  box-sizing: border-box;  -ms-text-size-adjust: 100%;  -webkit-text-size-adjust: 100%;}*, *:before, *:after {  box-sizing: inherit;}body {  margin: 0;  font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;}h1, h2, h3, h4, h5, h6, p, ul {  margin: 0;  padding: 0;}

Webpack 是通过诸如 import, require, url 等方式与入口文件建立依赖关系,这意味着我们创建的 main.css 文件也必须在应用程序中的某个地方导入,以便 webpack “找到”它。在示例项目中,我们从 main.js 入口文件导入 main.css

import React from 'react';import {render} from 'react-dom';import Greeter from './Greeter';import './main.css';    // //使用import导入css文件render(<Greeter />, document.getElementById('root'));

CSS Modules

在过去的几年里,JavaScript 通过新的语言特性、更好的工具和更好的实践方法(例如模块化)发展得非常迅速。

模块让开发人员将复杂的代码分解成具有依赖关系的小型、干净和独立的单元。由优化工具、依赖关系管理和 load 管理可以自动完成整合。

但是大多数样式表仍然非常庞大,全局的声明使得开发和维护变得非常困难。

最近一个名为 CSS modules 的项目旨在将所有这些优势引入 CSS。通过 CSS modules,所有的类名,动画名称默认都只作用于当前模块。Webpack 从一开始包含对 CSS modules 的支持,它内置在 CSS loader 中,你所需要做的就是通过传递 “modules” 查询字符串来激活它。然后就可以直接把 CSS 的类名传递到组件的代码中。(这样做只对当前组件有效,不必担心在不同的模块中使用相同的类名造成冲突)编辑 webpack.config.js 以启用 CSS modules(如下所示)

module.exports = {  devtool: 'eval-source-map',  entry:  __dirname + "/app/main.js",  output: {...},  module: {    loaders: [      {        test: /\.json$/,        loader: "json"      },      {        test: /\.js$/,        exclude: /node_modules/,        loader: 'babel'      },      {        test: /\.css$/,        loader: 'style!css?modules' // 启用 css modules      }    ]  },  devServer: {...}}

接下来,在 app 文件夹下创建一个 greeter.css 文件:

.root {  background-color: #eee;  padding: 10px;  border: 3px solid #ccc;}

接着更新 Greeter.js 文件:

import React, {Component} from 'react';import config from './config.json';import styles from './Greeter.css'; // 导入 cssclass Greeter extends Component{  render() {    return (      <div className={styles.root}>        {config.greetText}      </div>    );  }}export default Greeter

CSS modules 是一个很大的主题。更深入的 CSS modules 知识已经超出本文的范围,但你可以在 GitHub 的 官方文档 中了解更多信息。

CSS 预处理器

CSS 预处理器(如Sass 和 Less)是对原生 CSS 的拓展,它们允许你使用 CSS 中不存在的特性(如 variablesnestingmixinsinheritance等)编写 CSS,CSS 预处理器可以把这些特殊类型的语句转化为浏览器可识别的普通 CSS。

你可能已经熟悉了,在 webpack 里使用 loaders 进行配置就可以使用了,下面是常用的 CSS loaders:

  • Less loader
  • Sass loader
  • Stylus loader

此外还有一个用于 CSS 转换的工具:PostCSS,它可以帮助 CSS 实现更多功能,更多信息可以访问官方文档

举例说明,我们使用 PostCSS loader 的 autoprefixer 插件(给 CSS 自动添加前缀),给 CSS 代码添加适应不同浏览器的前缀。首先安装 PostCSS 和使用 autoprefixer 插件:

npm install --save-dev postcss-loader autoprefixer

接下来,在 webpack 配置文件中添加 postcss-loader,配置需要使用到的哪些插件(这里我们只使用 autoprefixer):

module.exports = {  devtool: 'eval-source-map',  entry: __dirname + "/app/main.js",  output: {...},  module: {    loaders: [      {        test: /\.json$/,        loader: "json"      },      {        test: /\.js$/,        exclude: /node_modules/,        loader: 'babel'      },      {        test: /\.css$/,        loader: 'style!css?modules!postcss'      }    ]  },  postcss: [    require('autoprefixer')  // 添加 autoprefixer 插件  ],  devServer: {...}}

Plugins(插件)

Webpack 可以通过插件扩展功能。在 Webpack 中,插件可以将自己注入到构建过程中以执行相关特定的任务。

loadersplugins(插件) 常常被混淆,但它们是完全不同的东西。大概来说,loaders 会处理每个源文件(例如 JSX、LESS),一次一个,而 plugins 不对单个源文件进行操作:它们作用于整个构建过程。

Webpack 有许多内置插件,但也有很多第三方插件可用。
在该节中,我们将在开发中体验一些最常用的插件。

开始使用插件

使用插件前,使用 npm 安装它。安装完成后,在 webpack.config.js 配置文件中的 plugins 数组内添加该插件对象的一个实例。

接着继续我们的示例,添加一个给打包后代码添加版权声明的插件:bannerPlugin
webpack.config.js 详细的配置如下:

var webpack = require('webpack');  // 导入 webpackmodule.exports = {  devtool: 'eval-source-map',  entry:  __dirname + "/app/main.js",  output: {...},  module: {    loaders: [      { test: /\.json$/, loader: "json" },      { test: /\.js$/, exclude: /node_modules/, loader: 'babel' },      { test: /\.css$/, loader: 'style!css?modules!postcss' }    ]  },  postcss: [    require('autoprefixer')  ],  plugins: [    new webpack.BannerPlugin("Copyright Flying Unicorns inc.")  // 添加版权插件  ],  devServer: {...}}

打包后的 JS 文件会添加如下的版权信息:
Webpack 入门-胡自自博客
下面是一些常用插件

HtmlWebpackPlugin

在第三方 Webpack 插件中,其中最有用的莫过于 HtmlWebpackPlugin 插件。
这个插件的作用是根据一个 index.html 模板,生成一个自动引用你打包后的 JS 文件的新 的 HTML5 文件。

使用下面的命令安装 HtmlWebpackPlugin:

npm install --save-dev html-webpack-plugin

接下来,按照下面步骤对项目结构进行一些修改:
1:移除 public 文件夹:因为利用 HtmlWebpackPlugin 插件能够自动生成 HTML5 页面,因此你可以删除在 public 文件夹中手动创建的 index.html 文件。同时前面 CSS 的配置已经将 CSS 打包进 JS 文件中,所以整个 public 文件夹将不再需要,可以完全删除。
2:在 app 目录下,创建一个 HTML 模板文件 index.tmpl.html ,该模板包含 title 等元素,在构建过程中,html-webpack-plugin 插件会依据此模板生成最终的 html 页面,会自动添加所依赖的 css, js,manifest,favicon 等文件,index.tmpl.html 模板源代码如下:

<!DOCTYPE html><html lang="en">  <head>    <meta charset="utf-8">    <title>Webpack Sample Project</title>  </head>  <body>    <div id='root'>    </div>  </body></html>

3:更新 webpack 配置,添加 HTMLWebpackPlugin 插件到 webpack 的 plugins 中,同时新建一个 build 文件夹,用于接受构建打包后的输出文件。

// webpack.config.jsvar webpack = require('webpack');var HtmlWebpackPlugin = require('html-webpack-plugin');  // 导入 html-webpack-pluginmodule.exports = {  devtool: 'eval-source-map',  entry:  __dirname + "/app/main.js",  output: {    path: __dirname + "/build",     // 构建后的文件导出到build目录    filename: "bundle.js"  },  module: {    loaders: [      { test: /\.json$/, loader: "json" },      { test: /\.js$/, exclude: /node_modules/, loader: 'babel' },      { test: /\.css$/, loader: 'style!css?modules!postcss' }    ]  },  postcss: [    require('autoprefixer')  ],  plugins: [    new HtmlWebpackPlugin({      template: __dirname + "/app/index.tmpl.html"  // 配置 HtmlWebpackPlugin    })  ],  devServer: {    colors: true,    historyApiFallback: true,    inline: true  }}

模块热替换

Webpack 著名的一个特性是模块热替换:即 Hot Module Replacement(HMR),它允许你修改组件代码后,浏览器自动刷新实时预览修改后的效果。

在 Webpack 中启用 HMR 很简单,你需要进行两个配置:
1. 将 HotModuleReplacementPlugin 添加到 webpack 中。
2. 将 hot 参数添加到 Webpack Dev Server 中。
不过,配置完这些后,JS 模块还不能自动热加载,还需要在你的JS模块中执行一个Webpack提供的API才能实现热加载,虽然这个API不难使用。还有一个比较实用的方法:使用我们熟悉的 Babel。

如你所见,Babel 和 Webpack 可以一起工作,它们都能够转换 JavaScript 文件。在我们的示例里,配置了 Webpack 和 Babel 将 JSX 和 ES6 代码转换成浏览器可以理解的普通 JavaScript。使用 Babel 插件,可以使 Webpack 进行额外的转换,让你所有的组件代码支持 HMR。

让我们重新整理下有些混乱的思路:

  • Webpack 和 Babel 是独立的工具
  • 两者可以一起工作
  • 两者都可以通过插件扩展功能
  • HMR 是一个 Webpack 插件,当你更改代码后,它可以让你在浏览器看到实时刷新后的结果。但需要你做一些额外相关配置工作,才能使 HMR 工作。
  • Babel 有一个名为 react-transform-hmr 的插件,可以在所有的 React 组件中自动插入所需的 HMR 代码(即让 HMR 生效)。

让我们更新示例,以启用热加载。从 Webpack 配置开始修改,如下所示:

var webpack = require('webpack');var HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = {  devtool: 'eval-source-map',  entry: __dirname + "/app/main.js",  output: {    path: __dirname + "/build",    filename: "bundle.js"  },  module: {    loaders: [      { test: /\.json$/, loader: "json" },      { test: /\.js$/, exclude: /node_modules/, loader: 'babel' },      { test: /\.css$/, loader: 'style!css?modules!postcss' }    ]  },  postcss: [    require('autoprefixer')  ],  plugins: [    new HtmlWebpackPlugin({      template: __dirname + "/app/index.tmpl.html"    }),    new webpack.HotModuleReplacementPlugin()  // 启用热加载插件  ],  devServer: {    colors: true,    historyApiFallback: true,    inline: true,    hot: true  // hot 参数  }}

接下来安装 Babel 所需的插件:

npm install --save-dev babel-plugin-react-transform react-transform-hmr

然后更新根目录下的 .babelrc 配置文件,添加我们安装的插件:

{  "presets": ["react", "es2015"],  "env": {    "development": {    "plugins": [["react-transform", {       "transforms": [{         "transform": "react-transform-hmr",         // if you use React Native, pass "react-native" instead:         "imports": ["react"],         // this is important for Webpack HMR:         "locals": ["module"]       }]       // note: you can put more transforms into array       // this is just one of them!     }]]    }  }}

npm start 启动服务器,并尝试修改 Greeter.js 文件中的内容,浏览器应该会实时显示更新后的内容了。

生产环境构建

到目前为止,我们已经使用 webpack 构建了一个完整的开发环境。但是在生产环境,可能还需要对打包后的文件进行额外处理,例如压缩,缓存以及分离 CSS 和 JavaScript。

对于完整或复杂的项目,将 Webpack 配置分割成多个文件是保持一切更有条理的好习惯。 在示例项目根目录下,创建一个名为 webpack.production.config.js 的新文件,并填写一些基本设置。

webpack.production.config.js 包含项目所需的基本配置,它和我们的 webpack.config.js 文件非常相似,具体如下:

// webpack.production.config.jsvar webpack = require('webpack');var HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = {  entry: __dirname + "/app/main.js",  output: {    path: __dirname + "/build",    filename: "bundle.js"  },  module: {    loaders: [      {        test: /\.json$/,        loader: "json"      },      {        test: /\.js$/,        exclude: /node_modules/,        loader: 'babel'      },      {        test: /\.css$/,        loader: 'style!css?modules!postcss'      }    ]  },  postcss: [    require('autoprefixer')  ],  plugins: [    new HtmlWebpackPlugin({      template: __dirname + "/app/index.tmpl.html"    }),  ],}

接着我们编辑 package.json 文件,添加一个新的构建任务,该任务在生产环境中运行Webpack,并分配新创建的配置文件:

// package.json{  "name": "webpack-sample-project",  "version": "1.0.0",  "description": "Sample webpack project",  "scripts": {    "start": "webpack-dev-server --progress",    "build": "NODE_ENV=production webpack --config ./webpack.production.config.js --progress"  // 添加生产环境构建任务  },  "author": "Cássio Zen",  "license": "ISC",  "devDependencies": {...},  "dependencies": {...}}

优化插件

Webpack 提供了一些在发布阶段非常有用的优化插件,大多来自于 webpack 社区,可以通过 npm 安装,可以通过使用以下 Webpack 插件来实现上述生产构建的所有期望特征(如压缩,缓存以及分离 CSS):

  • OccurenceOrderPlugin:Webpack提供了识别模块的ID。使用此插件,Webpack 将分析并优先处理分配最小ID的常用模块。
  • UglifyJsPlugin:压缩 JavaScript 代码。
  • ExtractTextPlugin:CSS 单独导出,分离 CSS 和 JS(样式不再打包到 JavaScript 文件中)。

让我们开始添加这些插件到 webpack.production.config.js 文件中,OccurenceOrderUglifyJS 插件是内置的,我们只需要安装 ExtractTextPlugin 即可:

npm install --save-dev extract-text-webpack-plugin

webpack.production.config.js 文件配置如下:

var webpack = require('webpack');var HtmlWebpackPlugin = require('html-webpack-plugin');var ExtractTextPlugin = require('extract-text-webpack-plugin');module.exports = {  entry: __dirname + "/app/main.js",  output: {    path: __dirname + "/build",    filename: "bundle.js"  },  module: {    loaders: [      {        test: /\.json$/,        loader: "json"      },      {        test: /\.js$/,        exclude: /node_modules/,        loader: 'babel'      },      {        test: /\.css$/,        loader: ExtractTextPlugin.extract('style', 'css?modules!postcss')      }    ]  },  postcss: [    require('autoprefixer')  ],  plugins: [    new HtmlWebpackPlugin({      template: __dirname + "/app/index.tmpl.html"    }),    new webpack.optimize.OccurenceOrderPlugin(),    new webpack.optimize.UglifyJsPlugin(),    new ExtractTextPlugin("style.css")  ]}

终端运行 npm run build 命令,在 build 目录中查看打包后的文件,代码应该都被压缩了。

缓存

缓存无处不在(CDNSs, ISPs, 网络设备, Web 浏览器…),一种简单而有效的方式使用缓存就是确保你的文件名是独一无二的(即如果文件内容发生变化,文件名也应该更改)Webpack 可以为打包后的文件名添加哈希值,只需将[name],[id]和[hash]的特殊字符串组合添加到输出文件名配置中即可,详细配置如下:

// webpack.production.config.jsvar webpack = require('webpack');var HtmlWebpackPlugin = require('html-webpack-plugin');var ExtractTextPlugin = require('extract-text-webpack-plugin');module.exports = {  entry: __dirname + "/app/main.js",  output: {    path: __dirname + "/build",    filename: "[name]-[hash].js"  // 文件名添加哈希值  },  module: {    loaders: [      {        test: /\.json$/,        loader: "json"      },      {        test: /\.js$/,        exclude: /node_modules/,        loader: 'babel'      },      {        test: /\.css$/,        loader: ExtractTextPlugin.extract('style', 'css?modules!postcss')      }    ]  },  postcss: [    require('autoprefixer')  ],  plugins: [    new HtmlWebpackPlugin({      template: __dirname + "/app/index.tmpl.html"    }),    new webpack.optimize.OccurenceOrderPlugin(),    new webpack.optimize.UglifyJsPlugin(),    new ExtractTextPlugin("[name]-[hash].css")    ]}

总结

Webpack是一个用于处理和打包你所有项目模块的神奇工具。在本文中,你学习了如何正确配置Webpack以及如何使用Webpack中的loaders和plugins来创建更好的开发体验。