Skip to content

Latest commit

 

History

History
865 lines (610 loc) · 35.5 KB

File metadata and controls

865 lines (610 loc) · 35.5 KB

二、Webpack 入门

这一章是关于网页的:它是什么,如何使用它,以及我们为什么关心它。然而,在我们深入网页之前,我要坦白。

在上一章关于应用设置的内容中,我们有点作弊。我们需要添加文件夹结构的最后一部分——React 文件的存放位置。

正如我们在上一章的依赖项一节中所讨论的,React 的杀手级功能之一是用户界面的组件化——将它们分解为相关 HTML 和 JavaScript 的小块。例如,“保存”按钮可能是一个组件,位于表单组件内部,靠近概要信息组件,等等。

组件结构的美妙之处在于,与 UI 特定部分相关的所有内容都放在一起(关注点分离),而且,这些部分都是简单易读的文件。作为一名开发人员,您可以通过浏览文件夹结构轻松找到所需内容,而不是滚动浏览单个 JavaScript 文件。

在本章中,我们将介绍以下主题:

  • 如何构建我们的 React 项目
  • 设置 Webpack
  • 添加开发服务器
  • 使用 Babel 开始 JavaScript 透明
  • 激活热重新加载
  • 生产性建筑

我们的项目结构

让我们看看这在实践中是什么样子。在我们的chatastrophe项目文件夹中,创建一个src文件夹(应该在项目文件夹根目录中的publicnode_modules文件夹旁边)。

src文件夹是我们所有 React 文件的存放位置。为了说明这将是什么样子,让我们创建一些模拟文件。

src内,制作另一个文件夹,名为components。在该文件夹中,让我们创建三个 JavaScript 文件。你可以随意给它们取名字,但举个例子,我会叫它们Component1.jsComponent2.jsComponent3.js

想象一下,这些组件文件中的每一个都包含了我们的用户界面。我们需要所有三个文件来构建一个完整的 UI。我们怎样才能全部进口?

好的,当我们需要使用 JavaScript 文件时,我们可以做我们到目前为止所做的事情。我们可以为index.html中的每个组件创建script标签。那是蛮力的方式。

然而,随着我们的应用的增长,这种方法很快就会变得笨拙。例如,像 Facebook 这样的应用将有数万个组件。我们不能写成千上万的标签!

理想情况下,我们只有一个script标记,所有 JavaScript 组合在一起。我们需要一个工具,它可以将我们的各种文件压缩在一起,为我们提供这两个方面的最佳效果——为开发人员提供有组织的、分离的代码,为用户提供压缩的、优化的代码。

“但是等等,斯科特,”你可能会说,“如果我们把所有的代码放在一个文件中,浏览器下载会不会花更长的时间?有小的、独立的文件不是一件好事吗?”

你完全正确。我们不希望最终回到一个单一的文件,但我们也不希望有数千个单独的文件。我们需要一个由少量代码文件组成的愉快的媒介,我们将达到这个媒介。然而,首先,让我们看看如何使用我们的新朋友--Webpack将多个 JavaScript 文件捆绑成一个文件。

欢迎来到网页

本节的目标是将位于index.html(负责呈现“Hello from React!”)中脚本标记中的 JavaScript 移到src文件夹中的 JavaScript 文件中,然后将其捆绑并通过 Webpack 注入 HTML。

这听起来很复杂,但由于 Webpack 的魔力,它比听起来简单。让我们开始:

  1. 首先,我们需要安装 Webpack:
yarn add webpack@3.5.4

如果您查看package.json,您应该会看到我们的依赖项下列出的网页。对于这本书,我将使用版本 3.5.4;如果遇到任何无法解释的问题,请尝试使用yarn add webpack@3.5.4指定此版本:

  1. 现在,我们需要告诉 Webpack 该怎么做。让我们首先将 React 代码移动到src文件夹中。在chatastrophe/src中,创建一个名为index.js的文件。
  2. 然后,键入以下代码:
console.log(‘hello from index.js!);

我们的目标是在浏览器控制台中显示此问候语。

  1. 好的,让我们试试 Webpack。在终端中,键入以下内容:
node_modules/.bin/webpack src/index.js public/bundle.js

您的终端现在应该如下所示:

这有什么用?它告诉 Webpack 获取第一个文件并将其(以及它需要的所有内容,即它需要的每个文件)复制到第二个文件(Webpack 为我们创建的文件,因为它不存在)。

如果你打开新创建的public/bundle.js,你会看到很多 Webpack 模板……在底部,我们的console.log

好吧,这样就行了;我们可以要求我们的index.html中的这个文件来查看我们的console.log,但这并没有充分利用 Webpack 的潜力。我们试试别的吧。

捆绑文件

让我们来看看 WebPACK 如何将 JavaScript 文件组合在一起。通过以下步骤添加第二个 JavaScript 文件:

  1. 在我们的src文件夹中,创建另一个文件。我们称之为index2.js,因为缺乏创造力。
  2. 在内部,添加第二个console.log
console.log(‘Hello from index2.js!);
  1. 然后,回到index.js(第一个),我们需要另一个文件,如下所示:
require('./index2.js');
console.log('Hello from index.js!');

这基本上意味着index.js现在告诉 Webpack,“嘿,我需要这个其他索引!”

  1. 好的,让我们重新运行与前面相同的 Webpack 命令:
node_modules/.bin/webpack src/index.js public/bundle.js

同样,我们将只指定src/index.js,但如果您查看控制台输出,您将看到 Webpack 现在也会捕获另一个文件:

  1. 打开public/bundle.js,滚动至底部,您将看到两个控制台日志。

这就是 Webpack 的美。我们现在可以扩展我们的应用以包含任意数量的 JavaScript 文件,并使用 Webpack 将它们合并为一个文件。

  1. 好的,让我们确保这些控制台日志正常工作。在我们的public/index.html中,在其他三个下面添加另一个脚本标签:
<script src="bundle.js"></script>
  1. 重新加载页面,打开控制台,您将看到:

移动我们的反应

足够的控制台日志;现在,让我们使用 Webpack 来处理一些有用的代码:

  1. 删除我们的index2.js,并删除index.js中的所有代码。然后将我们的 React 代码复制粘贴到index.js中,删除index.html中的前三个脚本标签。
  2. 这样做之后,您的index.html中应该只有一个脚本标记(用于bundle.js的标记),您的index.js应该由以下行组成:
ReactDOM.render(React.createElement('h1', false, 'Hello from React!'), document.getElementById('root'))
  1. 不过,在运行 Webpack 之前,我们遇到了一个问题。我们删除了需要 React 和 ReactDOM 的脚本标记,但是我们仍然需要一种方法在我们的index.js中访问它们。
  2. 我们可以用我们要求的index2.js相同的方式来做,也就是说,输入require(‘../node_modules/react/dist/react.js’),但这需要大量的输入。此外,我们将在代码中使用来自node_modules的许多依赖项。
  3. 幸运的是,以这种方式需要模块是很常见的,因此require函数足够聪明,可以仅根据名称获取依赖项,这意味着我们可以将其添加到index.js的开头:
var React = require('react');
var ReactDOM = require('react-dom');

然后我们可以在代码中使用这些包,就像以前一样!

  1. 好的,让我们试试看。再次运行 Webpack:
node_modules/.bin/webpack src/index.js public/bundle.js

它将显示以下输出:

现在,您可以在我们的index.js中看到 Webpack 捆绑在一起的所有文件:React、其所有依赖项和 ReactDOM。

重新加载页面,您应该看到没有任何更改。然而,我们的应用现在更具可伸缩性,我们可以更好地组织文件。当我们添加依赖项时,我们不再需要添加另一个<script>标记;我们只需要在代码中使用它。

捷径

输入那个长的 Webpack 命令很无聊,会让我们面临潜在的错误(如果我们输入错误bundle.js并最终生成错误的文件怎么办?)。为了我们自己的理智,让我们简化这个过程。

首先,让我们决定我们的index.js将是我们的应用的入口点,这意味着它将需要应用中的所有其他文件(或者更确切地说,它将需要一些需要一些其他文件的文件,这些文件需要一些其他文件,等等)。

相反,我们的bundle.js将是我们的输出文件,所有绑定的代码都放在那里。

因此,这两个文件将始终是我们在终端中为 Webpack 命令提供的参数。因为它们不会改变,所以让我们将 Webpack 配置为始终使用它们。

在我们的项目文件夹中(不是在src中,而是在顶层文件夹中),创建一个名为webpack.config.js的文件。在其中,输入以下内容:

module.exports = {
  entry:  __dirname + "/src/index.js",
  output: {
   path: __dirname + "/public",
   filename: "bundle.js",
   publicPath: "/",
  }
};

我们将入口点定义为index.js的路径(__dirname是一个全局变量,它获取当前目录,也就是说,无论我们在哪里运行webpack命令)。然后定义输出文件。

现在,我们只需在终端中运行node_modules/.bin/webpack,无需任何参数,即可得到相同的结果:

node_modules/.bin/webpack

这是一个很好的改进,但我们是开发人员,所以我们很懒,想要走更多的捷径。让我们缩短那个node_modules/.bin/webpack命令。

npm的一个很酷的特性是能够编写脚本来执行常用任务。让我们试试看。在我们的package.json中,创建脚本部分;其中,制作一个名为build的脚本,其值为node_modules/.bin/webpack命令:

{
  "name": "chatastrophe",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
 "scripts": {
 "build": "node_modules/.bin/webpack",
 },
  "dependencies": {
    "react": "15.6.1",
    "react-dom": "15.6.1",
    "webpack": "3.5.4",
  }
}

然后,在终端中,您可以运行npm run buildyarn build。他们做同样的事情:运行 Webpack 命令并捆绑我们的文件!

哇,我们的生活越来越轻松了。我们能再懒一点吗?

简言之,是的。

我们的开发服务器

如果我们想更新我们的代码(比如,将h1更改为h2),我们必须进行更改,重新运行yarn build,然后为我们希望看到的每个更改重新加载页面。这将大大降低我们开发过程的速度。

理想情况下,每次更改 JavaScript 时,Webpack 命令都会自动重新运行,并重新加载页面。那将是一个多么奢侈的世界啊!

幸运的是,有一个名为webpack-dev-server的包正是为了这个目的。要安装它,只需运行yarn add webpack-dev-server

在开始之前,让我们简要介绍一下 Dev 服务器是如何工作的。它在我们机器的后台运行一个小节点应用,提供我们公用文件夹中的文件,以便我们可以通过访问浏览器中的localhost:3000来查看它们。同时,它监视bundle.js的源文件,当它们更改时重新处理,然后重新加载页面。

为了让它工作,我们需要指定要为哪个文件夹提供服务(public),然后进行一些基本配置。

在我们的webpack.config.js中,在右括号前添加以下内容(我们这里有完整的代码):

devServer: {
  contentBase: "./public",
  historyApiFallback: true,
  inline: true,
}

contentBase这样做,将public设置为要服务的文件夹,historyApiFallback让我们的单页应用看起来像一个多页应用,inline是在文件更改时自动刷新页面的位:

module.exports = {
  entry: __dirname + "/src/index.js",
  output: {
   path: __dirname + "/public",
   filename: "bundle.js",
   publicPath: "/"
  },
 devServer: {
 contentBase: "./public",
 historyApiFallback: true,
 inline: true,
 }
};

好的,让我们试试。首先,我们将在package.json中添加一个新脚本,称为start

"scripts": {
  "build": "node_modules/.bin/webpack",
  "start": "node_modules/.bin/webpack-dev-server"
},

这将运行我们的开发服务器(确保您首先运行了yarn add webpack-dev-server。在终端中,输入**yarn start**。您将看到我们的网页正在编译,并注意到我们的应用正在端口8080上运行。让我们跳到浏览器中的http://localhost:8080,我们应该可以看到我们的应用。

最后一个测试是将我们的index.js中的文本从Hello from React更改为Hello from Webpack!。浏览器选项卡应自动重新加载并反映更改,而无需重新运行 Webpack 命令。

Webpack 加载程序

我们即将步入未来。

到目前为止,在本书中,我们一直在使用 JavaScript 的旧形式。该语言最近(2015 年)进行了改头换面,增加了一些便利和新功能。这个新版本被称为ECMAScript 2015,简称ES6。与旧的 JavaScript(ES5)相比,它的使用更令人愉快,但存在一个问题。

所有的互联网浏览器都完全能够运行 JavaScript,但许多用户使用的是还不能运行 ES6 的旧浏览器。所以,作为开发人员,我们希望使用 ES6,但我们如何才能做到这一点,并让我们的网站在旧浏览器上工作?

关键是 ES6 没有做 ES5 做不到的事情,它只是让编写更容易。

例如,在数组中循环之前是这样做的:

var arr = [1, 2, 3, 4];
for (var i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}

现在,它是这样做的:

[1, 2, 3, 4].forEach(num => console.log(num));

较旧的浏览器可以理解第一个,但不能理解第二个,但代码可以做同样的事情。因此,我们需要做的就是将第二个代码段转换为第一个。这就是巴贝尔进来的地方。Babel是一个 JavaScript 传输工具;把它想象成一个翻译人员。我们为它提供了漂亮的 ES6 代码,并将其转换为更难看但对浏览器更友好的 ES5 代码。

我们将把 Babel 粘贴到我们的 Webpack 构建过程中,这样当我们捆绑所有 JavaScript 文件时,我们也可以传输它们。

首先,我们将安装 Babel,以及它的一系列插件和附加组件,以使它与 React 配合使用。停止开发服务器,然后运行以下操作:

yarn add babel-core babel-loader babel-preset-es2015 babel-preset-react babel-plugin-transform-class-properties

哎呀,一下子就有很多包了!下一步的重要步骤是babel-loader。这是一个 Webpack 加载器,我们使用它抓取(然后传输)我们的 JavaScript 文件,然后将它们传递到 Webpack 进行绑定。让我们把它插入 Webpack。

在我们的webpack.config.js中,制作一个模块对象,其中包含一个 loaders 数组:

然后,我们可以在数组中定义加载器。

我们将用四个键创建一个对象:test、exclude、loader 和 query:

  • 测试是加载程序用来确定应该传输哪些文件的测试。对于 Babel,我们希望在所有 JavaScript 文件上运行,因此我们的测试将针对以.js结尾的文件:
test: /\.js$/
  • 排除是不可以运行的。我们可以跳过整个node_modules文件夹,因为包已经在 ES5 中:
exclude: /node_modules/
  • 加载器就是我们的加载器:
loader: ‘babel-loader’
  • 最后,我们将使用查询来定义我们的预设(巴贝尔将使用什么来传输 JavaScript):
query: {
  presets: ['es2015','react'],
  plugins: ['transform-class-properties']
}

以下是完整文件的外观:

module.exports = {
  entry: __dirname + "/src/index.js",
  output: {
   path: __dirname + "/public",
   filename: "bundle.js",
   publicPath: "/"
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015','react'],
          plugins: ['transform-class-properties']
        }
      },
    ]
  },
  devServer: {
    contentBase: "./public",
    historyApiFallback: true,
    inline: true,
  }
};

运行**yarn start**并查找错误。如果没有,我们可以把它作为一个试驾,并编写一些 ES6 代码。

我们的第一个 ES6

让我们打开我们的src/index.js,看看如何给它调味。

首先,我们可以用新的import语法替换require调用。看起来是这样的:

import React from ‘react’;
import ReactDOM from 'react-dom';

它比较干净,让我们可以做一些很酷的事情,稍后我们会看到。

对 React 和 ReactDOM 都这样做,然后我们最终可以替换我们的React.createElement调用。

正如您可能猜到的,通过为我们需要的每个 HTML 元素调用React.createElement来构建一个复杂的 UI 是非常笨拙的。我们想要 JavaScript 的强大功能,但要有 HTML 的可读性。

输入 JSX;JSX是一种语法,看起来像 HTML,但实际上是 JavaScript。换句话说,它可以编译成React.createElement,就像我们的 ES6 JavaScript 可以编译成 ES5 一样。

它也有一些缺陷,因为它不是真正的 HTML,但我们会做到的。最后要注意的是,JSX 让一些开发人员非常不舒服;他们说在 JavaScript 中包含 HTML 看起来很奇怪。我个人不同意,但这是一个意见问题。无论您的审美立场如何,JSX 都提供了很多便利,所以让我们试试看。

我们可以简单地将代码行转换为:

ReactDOM.render(<h1>Hello from ES6!</h1>, document.getElementById('root'));

运行yarn start(或者,如果它已经在运行,它应该自动刷新)。如果巴贝尔工作正常,就不会有任何变化。我们的第一个 JSX 完成了!

当然,我们将更多地使用 JSX,看看它与 HTML 的区别,以及它为我们作为开发人员提供了哪些优势。然而,现在,让我们让我们的生活更轻松。

拆分我们的应用

为了更好地组织我们的应用(并在下一节中发挥一些魔力),让我们将 JSX 从ReactDOM.render移到一个单独的文件中。这将确保在整个文件结构中很好地分离关注点。

index.js旁边的src文件夹中,创建一个名为App.js的文件。在内部,我们将创建一个名为App的函数,它返回我们的 JSX:

import React from 'react';

const App = () => {
  return <h1>Hello from React!!</h1>
};

export default App;

注意底部的export语句;这意味着当我们导入文件时,我们将自动将此函数作为默认导入。下面我们将看到一个非默认导入的示例,这将使这一点更加清楚。

如果我们跳回到index.js,我们现在可以从'./App’导入App。然后,我们渲染它,如图所示:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App'

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

请注意,我们使用它就像使用 HTML(或者更确切地说,JSX)标记一样。我们将在接下来的章节中更多地讨论原因;现在,重要的是我们的应用更有条理,视图逻辑(JSX)与渲染逻辑(ReactDOM.render)分开。

热装

我们在开发过程中取得了一些相当大的成功。在我们深入进行 Webpack 配置之前,我还想增加一个方便。

想象一下,一个应用包含一个表单,当用户单击编辑按钮时,该表单以模式弹出。重新加载页面时,该模式将关闭。现在,假设您是试图微调表单的开发人员。每次调整后,您的开发服务器都会重新加载页面,迫使您重新打开模式。在这种情况下,这有点烦人,但想想像浏览器游戏这样的东西,回到原来的位置需要点击几下。

简言之,我们需要一种方法来重新加载 JavaScript,同时仍保留应用的当前状态,而无需重新加载页面本身;这称为热重新加载。我们使用 Webpack 交换已经更改的 UI 部分,而无需重新加载所有内容。

为此,我们将使用丹·阿布拉莫夫的react-hot-loader包。让我们安装它,看看我们将如何配置 Webpack 来很好地使用它。

要安装,请键入yarn add react-hot-loader@3.0.0。在撰写本文时,版本 3 仍处于测试阶段;如果 Thread 提示您选择 3.0 的测试版,请选择最新版本(对我来说,我选择了 beta.7):

yarn add react-hot-loader@3.0.0

要使其正常工作,我们需要做四件事:

  1. 打开 Webpack 自己的热模块替换插件。
  2. 使用 React Hot Loader 作为我们应用的入口点,以便 Webpack 查找源文件。
  3. 将 React Hot Loader 连接到巴贝尔。
  4. 使用我们的开发服务器启用热重新加载。

安装 Webpack 的HMR插件其实很简单。在我们的webpack.config.js中,首先需要文件顶部的 Webpack,以便我们可以访问包:

var webpack = require('webpack');

Our Webpack file is not processed by Babel, so we will still use require instead of import.

然后,在我们的devServer键上方,添加一个名为plugins的新键,该键的值为一个数组,其中包括new webpack.HotModuleReplacementPlugin()作为唯一项:

module: {
  loaders: [
    {
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'babel-loader',
      query: {
        presets: ['es2015','react'],
        plugins: ['transform-class-properties']
      }
    },
  ]
},
plugins: [
 new webpack.HotModuleReplacementPlugin()
],
devServer: {
  contentBase: "./public",
  historyApiFallback: true,
  inline: true,
}

重新启动服务器以检查错误,然后继续执行步骤 2。

现在,我们的index.js是我们的 Webpack 入口点;它执行该文件中的代码,并从该执行中使用的文件包中派生。我们想先执行react-hot-loader包。让我们将输入键修改为如下所示:

entry: [
  'react-hot-loader/patch',
  __dirname + "/src/index.js"
 ],

要使其与我们的开发服务器配合使用,我们需要添加更多的代码:

entry: [
   'react-hot-loader/patch',
   'webpack-dev-server/client?http://localhost:8080',
   'webpack/hot/only-dev-server',
   __dirname + "/src/index.js"
 ],

此配置意味着 Webpack 将在转到我们的代码之前在这些路径中执行代码。

再次尝试重新启动服务器。如果有错误,检查打字错误;否则,继续前进!

接下来,我们想添加一个 Babel 插件,这样我们重新加载的热文件就可以用babel-loader编译。只需使用react-hot-loader中包含的 Babel 插件更新我们的 Babel 配置,如图所示:

loaders: [
  {
    test: /\.js$/,
    exclude: /node_modules/,
    loader: 'babel-loader',
    query: {
      presets: ['es2015','react'],
      plugins: ['react-hot-loader/babel', 'transform-class-properties']
    }
  },
]

我们还需要打开 Dev 服务器的热重新加载;通过在我们的devServer配置中添加hot: true来实现:

devServer: {
  contentBase: "./public",
  historyApiFallback: true,
  inline: true,
  hot: true
},

最后一步,我们需要在index.js中添加一些代码。将以下内容添加到文件底部:

if (module.hot) {
  module.hot.accept('./App', () => {
    const NextApp = require('./App').default;
    ReactDOM.render(
     <App/>,
     document.getElementById('root')
    );
  });
}

前面的代码基本上会在文件更改时将我们的应用的新版本发送到ReactDOM.render

好吧,让我们试一试。重新启动服务器,然后打开localhost:8080。尝试编辑文本Hello from React!,并观察 HTML 的更新,而无需重新加载页面;整洁的

热模块更换将使我们的生活更加轻松,特别是当我们开始构建具有不同状态的应用时——重新加载页面将重置这些状态。

生产性建筑

到目前为止,我们一直专注于在开发环境中使用 Webpack,但我们还需要考虑将我们的应用部署到生产环境中,以及可能涉及的内容。

当我们将应用发送到万维网时,我们不想发送任何不必要的东西(记住,我们的目标是性能);我们希望部署最低限度的资源。

我们需要的是:

  • index.html页面(缩小)
  • CSS 文件(缩小)
  • JavaScript 文件(缩小)
  • 所有图像资产
  • 资产清单(前面静态文件的列表)

我们有一些,但不是全部。让我们使用 Webpack 自动生成一个包含所有这些内容的build文件夹,我们可以稍后部署。

首先,一个缩小的index.html。我们希望 Webpack 获取我们的public/index.html文件,缩小它,自动添加适当的脚本和 CSS 链接,然后将其添加到build文件夹中。

由于我们的网页制作流程与开发流程不同,所以让我们复制一下我们的webpack.config.js并将其命名为webpack.config.prod.js。在本章余下的大部分时间里,我们将使用webpack.config.prod.js,而不是webpack.config.js

首先,从webpack.config.prod.js中删除devServer键。我们不会在生产中使用 Dev 服务器,也不会使用热重新加载。我们需要删除entry下的两条devServer特定行,以及热重新加载行,因此现在看起来如下所示:

entry: __dirname + "/src/index.js",

另外,在我们的webpack.config.prod.js中,让我们通过更改“输出”下的这一行来指定我们的输出文件夹现在是chatastrophe/build

path: __dirname + "/public",

需要将其更改为:

path: __dirname + "/build",

我们还需要添加一个publicPath,这样build中的index.html就可以在同一文件夹中查找捆绑的 JavaScript:

output: {
  path: __dirname + "/build",
  filename: "bundle.js",
  publicPath: './'
},

让我们将环境设置为生产环境,以便 React 不会显示其(开发中的有用)警告。我们也可以删除HotModuleReplacementPlugin

plugins: [
  new webpack.DefinePlugin({
    'process.env': {
      NODE_ENV: JSON.stringify('production')
    }
  }),
],

接下来,我们将使用一个名为HtmlWebpackPlugin的新 Webpack 插件。它做的和听起来一样--为我们打包 HTML!让我们使用yarn add html-webpack-plugin安装它,然后使用以下选项添加它:

plugins: [
  new webpack.DefinePlugin({
    'process.env': {
      NODE_ENV: JSON.stringify('production')
    }
  }),
  new HtmlWebpackPlugin({
    inject: true,
    template: __dirname + "/public/index.html",
    minify: {
      removeComments: true,
      collapseWhitespace: true,
      removeRedundantAttributes: true,
      useShortDoctype: true,
      removeEmptyAttributes: true,
      removeStyleLinkTypeAttributes: true,
      keepClosingSlash: true,
      minifyJS: true,
      minifyCSS: true,
      minifyURLs: true,
    },
  }),
],

别忘了在webpack.config.prod.js顶部要求,就像我们要求的网页一样:

var HtmlWebpackPlugin = require('html-webpack-plugin');

是时候测试一下了!在您的package.json中,更新我们的构建脚本以使用我们的新配置,如下所示:

"build": "node_modules/.bin/webpack --config webpack.config.prod.js",

然后,运行yarn build

您应该会在项目目录中看到一个build文件夹。如果你打开build/index.html,你会看到它很漂亮,而且粘在一起。然而,有一个问题;在压缩的代码中,您应该看到两个脚本标记,都需要bundle.js

这是我们前面用HtmlWebpackPlugin指定的inject选项的结果。插件为我们添加了脚本标签!多么方便,除了我们自己已经在public/index.html中添加了它。

这里有一个简单的解决方案——让我们将HtmlWebpackPlugin配置(和 require 语句)复制到webpack.config.js(原始配置文件)。但是,我们可以删除minify键及其所有选项,因为这在开发中不是必需的:

// webpack.config.js
plugins: [
  new webpack.HotModuleReplacementPlugin(),
  new HtmlWebpackPlugin({
    inject: true,
    template: __dirname + '/public/index.html',
  })
],

然后,从public/index.html中删除脚本标记,并再次尝试yarn start来测试我们的开发环境是否正常工作,yarn build来测试我们的产品构建。

好的,我们的构建中有一个缩小的 HTML 文件,并且我们也改进了我们的开发启动过程。下一个任务是确保 CSS 被缩小并复制到构建文件夹中。

在我们的 Webpack 配置(包括生产和开发)中,我们使用babel-loader加载我们的 JavaScript 文件;我们将用 CSS 做类似的事情。

为此,我们将组合两台装载机:css-loaderstyle-loader

You can read more about why it’s recommended to use both on the style-loader GitHub page at https://github.com/webpack-contrib/style-loader.

使用以下组件安装这两个组件:

 yarn add css-loader style-loader

我们通过在babel-loader配置下添加以下代码,将它们添加到webpack.config.prod.jswebpack.config.js中:

这些插件所做的是获取 React 代码所需的 CSS 文件,并将其转换为注入 HTML 的<style>标记。现在,这对我们没有多大帮助,因为我们的 CSS 位于我们的public/assets文件夹中。我们把它移到src,然后在App.js中要求它:

import React from 'react';
import './app.css';

const App = () => {
  return <h1>Hello from React!!</h1>
};

export default App;

然后,我们可以从public/index.html中删除链接标签并重新启动服务器。

如果我们在浏览器中检查 HTML 的头部,我们会看到一个<style>标记,其中包含所有 CSS。整洁

现在,你可能会注意到,当我们刷新页面时,会有一些未设置样式的内容;这是因为我们的应用现在需要在添加样式之前启动 React。我们将在接下来的章节中讨论这个问题,请放心。

运行yarn build并查看bundle.js。如果您搜索“开始初始样式”,您将看到 CSS 在 JavaScript 中的绑定位置。另外,请注意,与 HTML 相比,JavaScript 的可读性如何。下一步是注意缩小它!

幸运的是,这样做相当容易。我们只是在production文件中添加了另一个 Webpack 插件。在HtmlWebpackPlugin之后,添加以下内容:

plugins: [
  new HtmlWebpackPlugin({
    inject: true,
    template: __dirname + '/public/index.html',
    minify: {
      removeComments: true,
      collapseWhitespace: true,
      removeRedundantAttributes: true,
      useShortDoctype: true,
      removeEmptyAttributes: true,
      removeStyleLinkTypeAttributes: true,
      keepClosingSlash: true,
      minifyJS: true,
      minifyCSS: true,
      minifyURLs: true
    }
  }),
  new webpack.optimize.UglifyJsPlugin({
    compress: {
      warnings: false,
      reduce_vars: false
    },
    output: {
      comments: false
    },
    sourceMap: true
  })
]

再次运行yarn build,您应该看到我们的bundle.js已经变成了一条线。这对人类来说不是很好,但对浏览器来说要快得多。

好了,我们快结束了。接下来,我们要确保所有资产文件都复制到我们的build文件夹中。

我们可以通过将另一个加载器添加到我们的网页配置中,称为file-loader。我们将使用yarn add file-loader@0.11.2安装它。让我们看看代码是什么样子的(注意,这只适用于我们的webpack.config.prod.js文件):

module: {
  loaders: [
    {
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'babel-loader',
      query: {
        presets: ['es2015', 'react'],
        plugins: ['react-hot-loader/babel', 'transform-class-properties']
      }
    },
    {
      test: /\.css$/,
      use: [{ loader: 'style-loader' }, { loader: 'css-loader' }]
    },
 {
 exclude: [/\.html$/, /\.(js|jsx)$/, /\.css$/, /\.json$/],
 loader: 'file-loader',
 options: {
 name: 'static/media/[name].[ext]'
 }
 }</strong>
  ]
},

注意,我们排除了 HTML、CSS、JSON 和 JS 文件。这些被我们的其他加载程序覆盖,所以我们不想复制文件。

我们还将这些资产放在一个static文件夹中,就像我们的public文件夹中的assets文件夹一样。

然而,file-loader将仅适用于我们的 JavaScript 代码所需的那些文件。我们有我们的图标和图标,目前只在我们的index.html中使用,所以 Webpack 找不到它们。

为此,我们将使用 JavaScript 而不是 Webpack(因为 Webpack 只关注我们的src文件夹)。

创建自定义脚本

在目录的根目录中创建一个新文件夹,并将其命名为scripts。在里面,制作一个名为copy_assets.js的文件。

在这里,我们将把public中的所有内容复制到build,不包括我们的index.html

要做到这一点(你猜对了),我们还需要一个包;运行yarn add fs-extra

然后,在copy_assets.js中要求它,如图所示:

var fs = require('fs-extra');

fs-extra是一个用于在节点环境中操作文件的包。它有一个名为copySync的方法,我们将在这里使用它。

代码相当简单:

fs.copySync('public', 'build', {
 dereference: true,
 filter: file => file !== 'public/index.html'
});

这表示将public文件夹中的所有内容复制到build文件夹中,但index.html文件除外。

If you have a bundle.js in your public folder from our previous Webpack config, you can delete it now.

现在,要在构建时运行此命令,请将其添加到package.json中的构建脚本中:

 "scripts": {
   "build": "node scripts/copy_assets.js && node_modules/.bin/webpack --config 
    webpack.config.prod.js",
   "start": "node_modules/.bin/webpack-dev-server"
 },

copy_assets命令放在我们的 Webpack 命令之前是个好主意,只是为了确保我们不会在没有传输它们的情况下意外复制public中的任何 JavaScript 资产。

制作资产清单

作为最后一步,我们需要生成的所有静态资产的列表。一旦我们开始缓存它们以节省加载时间,这将非常有用。幸运的是,这是一个简单的步骤,另一个插件!

yarn add webpack-manifest-plugin添加到webpack.config.prod.js插件下,配置如下:

var ManifestPlugin = require('webpack-manifest-plugin');
// Then, under plugins:
new ManifestPlugin({
  fileName: 'asset-manifest.json',
}),

好的,让我们一起试试。运行**yarn build,然后在浏览器中打开index.html。它看起来应该与运行yarn start**完全相同。您还应该在我们的build文件夹中看到一个index.html、一个bundle.js、一个asset-manifest.json和一个assets文件夹。

总结

唷!这是一个很大的配置。好消息是,现在我们已经完全准备好开始编写 React,并构建我们的应用。这就是我们下一步要做的!

在本章中,我们介绍了与 Webpack 相关的所有内容,添加了一系列方便的功能以加快我们的开发。在下一章中,我们将开始开发过程,并开始构建 React 应用。这就是乐趣的开始!