- 原文:Webpack for React
- 翻译:hugo
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 会查看所有的项目依赖文件(通过 require 和 import 引入),使用 loaders 处理它们,最后打包成 JavaScript 文件。
开始
通过 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 文件夹中。最后,项目结构如下图所示:
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 将 main.js 和 Greeter.js 文件打包成了 bundle.js。如果你在浏览器中打开 index.html,结果将如下所示:
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 命令将根据可用的配置构建应用程序。 结果如下所示:
添加快捷启动
在终端输入类似这样的 `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 脚本执行了:
生成 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-loader
和 style-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 中不存在的特性(如 variables
,nesting
,mixins
,inheritance
等)编写 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 中,插件可以将自己注入到构建过程中以执行相关特定的任务。
loaders
和 plugins(插件)
常常被混淆,但它们是完全不同的东西。大概来说,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 文件会添加如下的版权信息:
下面是一些常用插件
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 很简单,你需要进行两个配置:
- 将
HotModuleReplacementPlugin
添加到 webpack 中。 - 将
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
文件中,OccurenceOrder
和 UglifyJS
插件是内置的,我们只需要安装 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来创建更好的开发体验。
最后修改于 2019-04-16