这一章是关于网页的:它是什么,如何使用它,以及我们为什么关心它。然而,在我们深入网页之前,我要坦白。
在上一章关于应用设置的内容中,我们有点作弊。我们需要添加文件夹结构的最后一部分——React 文件的存放位置。
正如我们在上一章的依赖项一节中所讨论的,React 的杀手级功能之一是用户界面的组件化——将它们分解为相关 HTML 和 JavaScript 的小块。例如,“保存”按钮可能是一个组件,位于表单组件内部,靠近概要信息组件,等等。
组件结构的美妙之处在于,与 UI 特定部分相关的所有内容都放在一起(关注点分离),而且,这些部分都是简单易读的文件。作为一名开发人员,您可以通过浏览文件夹结构轻松找到所需内容,而不是滚动浏览单个 JavaScript 文件。
在本章中,我们将介绍以下主题:
- 如何构建我们的 React 项目
- 设置 Webpack
- 添加开发服务器
- 使用 Babel 开始 JavaScript 透明
- 激活热重新加载
- 生产性建筑
让我们看看这在实践中是什么样子。在我们的chatastrophe项目文件夹中,创建一个src文件夹(应该在项目文件夹根目录中的public和node_modules文件夹旁边)。
src文件夹是我们所有 React 文件的存放位置。为了说明这将是什么样子,让我们创建一些模拟文件。
在src内,制作另一个文件夹,名为components。在该文件夹中,让我们创建三个 JavaScript 文件。你可以随意给它们取名字,但举个例子,我会叫它们Component1.js、Component2.js和Component3.js。
想象一下,这些组件文件中的每一个都包含了我们的用户界面。我们需要所有三个文件来构建一个完整的 UI。我们怎样才能全部进口?
好的,当我们需要使用 JavaScript 文件时,我们可以做我们到目前为止所做的事情。我们可以为index.html中的每个组件创建script标签。那是蛮力的方式。
然而,随着我们的应用的增长,这种方法很快就会变得笨拙。例如,像 Facebook 这样的应用将有数万个组件。我们不能写成千上万的标签!
理想情况下,我们只有一个script标记,所有 JavaScript 组合在一起。我们需要一个工具,它可以将我们的各种文件压缩在一起,为我们提供这两个方面的最佳效果——为开发人员提供有组织的、分离的代码,为用户提供压缩的、优化的代码。
“但是等等,斯科特,”你可能会说,“如果我们把所有的代码放在一个文件中,浏览器下载会不会花更长的时间?有小的、独立的文件不是一件好事吗?”
你完全正确。我们不希望最终回到一个单一的文件,但我们也不希望有数千个单独的文件。我们需要一个由少量代码文件组成的愉快的媒介,我们将达到这个媒介。然而,首先,让我们看看如何使用我们的新朋友--Webpack将多个 JavaScript 文件捆绑成一个文件。
本节的目标是将位于index.html(负责呈现“Hello from React!”)中脚本标记中的 JavaScript 移到src文件夹中的 JavaScript 文件中,然后将其捆绑并通过 Webpack 注入 HTML。
这听起来很复杂,但由于 Webpack 的魔力,它比听起来简单。让我们开始:
- 首先,我们需要安装 Webpack:
yarn add webpack@3.5.4如果您查看package.json,您应该会看到我们的依赖项下列出的网页。对于这本书,我将使用版本 3.5.4;如果遇到任何无法解释的问题,请尝试使用yarn add webpack@3.5.4指定此版本:
- 现在,我们需要告诉 Webpack 该怎么做。让我们首先将 React 代码移动到
src文件夹中。在chatastrophe/src中,创建一个名为index.js的文件。 - 然后,键入以下代码:
console.log(‘hello from index.js!’);我们的目标是在浏览器控制台中显示此问候语。
- 好的,让我们试试 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 文件:
- 在我们的
src文件夹中,创建另一个文件。我们称之为index2.js,因为缺乏创造力。 - 在内部,添加第二个
console.log:
console.log(‘Hello from index2.js!’);- 然后,回到
index.js(第一个),我们需要另一个文件,如下所示:
require('./index2.js');
console.log('Hello from index.js!');这基本上意味着index.js现在告诉 Webpack,“嘿,我需要这个其他索引!”
- 好的,让我们重新运行与前面相同的 Webpack 命令:
node_modules/.bin/webpack src/index.js public/bundle.js同样,我们将只指定src/index.js,但如果您查看控制台输出,您将看到 Webpack 现在也会捕获另一个文件:
- 打开
public/bundle.js,滚动至底部,您将看到两个控制台日志。
这就是 Webpack 的美。我们现在可以扩展我们的应用以包含任意数量的 JavaScript 文件,并使用 Webpack 将它们合并为一个文件。
- 好的,让我们确保这些控制台日志正常工作。在我们的
public/index.html中,在其他三个下面添加另一个脚本标签:
<script src="bundle.js"></script>- 重新加载页面,打开控制台,您将看到:
足够的控制台日志;现在,让我们使用 Webpack 来处理一些有用的代码:
- 删除我们的
index2.js,并删除index.js中的所有代码。然后将我们的 React 代码复制粘贴到index.js中,删除index.html中的前三个脚本标签。 - 这样做之后,您的
index.html中应该只有一个脚本标记(用于bundle.js的标记),您的index.js应该由以下行组成:
ReactDOM.render(React.createElement('h1', false, 'Hello from React!'), document.getElementById('root'))- 不过,在运行 Webpack 之前,我们遇到了一个问题。我们删除了需要 React 和 ReactDOM 的脚本标记,但是我们仍然需要一种方法在我们的
index.js中访问它们。 - 我们可以用我们要求的
index2.js相同的方式来做,也就是说,输入require(‘../node_modules/react/dist/react.js’),但这需要大量的输入。此外,我们将在代码中使用来自node_modules的许多依赖项。 - 幸运的是,以这种方式需要模块是很常见的,因此
require函数足够聪明,可以仅根据名称获取依赖项,这意味着我们可以将其添加到index.js的开头:
var React = require('react');
var ReactDOM = require('react-dom');然后我们可以在代码中使用这些包,就像以前一样!
- 好的,让我们试试看。再次运行 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 build或yarn 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 命令。
我们即将步入未来。
到目前为止,在本书中,我们一直在使用 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 代码。
让我们打开我们的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要使其正常工作,我们需要做四件事:
- 打开 Webpack 自己的热模块替换插件。
- 使用 React Hot Loader 作为我们应用的入口点,以便 Webpack 查找源文件。
- 将 React Hot Loader 连接到巴贝尔。
- 使用我们的开发服务器启用热重新加载。
安装 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-loader和style-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.js和webpack.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 应用。这就是乐趣的开始!







