Webpack-5-启动和运行指南-全-

Webpack 5 启动和运行指南(全)

原文:zh.annas-archive.org/md5/D84E54A317E3F5B84C857CD1B0FA20B6

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

当我被要求写这本培训书时,我意识到关于 Webpack 及其用途的了解很少。通常这是开发人员偶然发现并在工作中学习的东西,这可能是一个非常费力的过程。Webpack.js 网站上有一些文档,以及一些可靠的资源,比如 Medium。然而,这些资源往往从专家的角度来与读者交流,而我个人发现这并不理想。

作为一名网页开发讲师,我看到非常有技术和智慧的人可能存在盲点和知识空白。作为一名讲师,我被告知,并且也传达这个信息,那就是“没有愚蠢的问题”。许多没有教学背景的人可能会建议他们不想对你说教,宁愿你不问愚蠢的问题。我们发现,如果学生宁愿保持沉默而不问问题,这是有害的。

我打算尽可能保持简单。也许我已经失败了,使用了“定制”这样的词,尽管如此,前提是我们所有人都会有让人啪啪打脑袋的时刻,我们本应该做某事,后来意识到我们做错了。对吧?这种事发生在我们最聪明的人身上。此外,大多数讲师可能不愿意对你进行详尽的解释,因为他们担心冒犯你。问题在于,总会有一些平凡的细节,开发人员认为显而易见,但可能有多种解释。当我讲课时的规则是:“没有愚蠢的问题”,所以我希望证明这个理论的必要性。

这本书适合谁

这本书是为希望通过学习 Webpack 来开始他们的 Web 项目依赖管理的 Web 开发人员而写的。假定读者具有 JavaScript 的工作知识。

本书涵盖的内容

第一章,“Webpack 5 简介”,将向您介绍 Webpack——具体来说,是 Webpack 5 版本。它将概述围绕 Webpack 的核心概念以及它的使用方式。

第二章,“使用模块和代码拆分”,将详细介绍模块和代码拆分,以及 Webpack 5 的一些突出和有趣的方面,这些方面对于理解 Webpack 至关重要。

第三章,“使用配置和选项”,将探讨配置的世界,了解其局限性和能力,以及选项在其中的作用。

第四章,“API、插件和加载器”,将深入探讨 API、加载器和插件的世界。这些 Webpack 的特性阐述了平台的能力,从配置和选项出发。

第五章,“库和框架”,将讨论库和框架。我们对插件、API 和加载器的研究表明,有时我们不想使用诸如库之类的远程代码,但有时我们确实需要。Webpack 通常处理本地托管的代码,但有时我们可能需要使用库。这为我们引入了这个话题。

第六章,“生产、集成和联合模块”,将深入介绍这个主题,并希望解决开发人员可能存在的任何疑虑。

第七章,“调试和迁移”,将讨论热模块替换和实时编码,并深入了解一些严肃的教程。

第八章,编写教程和实时编码技巧,将向您展示 Webpack 5 的工作示例,特别是 Webpack 5 相对于早期版本的差异。将有纯 JavaScript 教程以及常见的框架,Vue.js 将是一个不错的选择。

为了充分利用本书

您可以在github.com/PacktPublishing/Webpack-5-Up-and-Running找到本书所有章节中使用的代码。为了充分利用本书,您需要以下内容:

  • JavaScript 的基本知识。

  • 确保您已安装最新版本的 Webpack 5。

  • 您需要使用命令行界面,如命令提示符或其他您选择的命令行实用程序。

  • 您将需要 Node.js,JavaScript 运行环境。

  • 确保您已安装最新版本的 Node.js(webpack 5 至少需要 Node.js 10.13.0(LTS));否则,您可能会遇到许多问题。

  • 您需要在本地计算机上安装具有管理员级别权限的npm。Webpack 和 Webpack 5 在 Node.js 环境中运行,这就是为什么我们需要它的包管理器——NPM。

  • 截至撰写本文时,最新版本是 Webpack 5。访问webpack.js.org查找适合您的最新版本。

下载示例代码文件

您可以从www.packt.com的帐户中下载本书的示例代码文件。如果您在其他地方购买了本书,可以访问www.packtpub.com/support并注册,以便文件直接发送到您的邮箱。

您可以按照以下步骤下载代码文件:

  1. 登录或注册www.packt.com

  2. 选择“支持”选项卡。

  3. 点击“代码下载”。

  4. 在搜索框中输入书名,并按照屏幕上的说明操作。

下载文件后,请确保使用以下最新版本的解压缩或提取文件夹:

  • Windows 使用 WinRAR/7-Zip

  • Mac 使用 Zipeg/iZip/UnRarX

  • Linux 使用 7-Zip/PeaZip

该书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Webpack-5-Up-and-Running。如果代码有更新,将在现有的 GitHub 存储库中更新。

我们还有其他代码包来自我们丰富的图书和视频目录,可在github.com/PacktPublishing/找到。快去看看吧!

使用的约定

本书中使用了许多文本约定。

CodeInText:表示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。以下是一个示例:“以下行是package.json文件中的代码片段。”

代码块设置如下:

"scripts": {
"build": "webpack --config webpack.config.js"
}

当我们希望引起您对代码块的特定部分的注意时,相关行或项目将以粗体显示:

<!doctype html>
<html>
 <head>
 <title>Webpack - Test</title>
 <script src="img/lodash@4.16.6"></script>
 </head>
 <body>
 <script src="img/index.js"></script>
 </body>
</html>

任何命令行输入或输出都以以下形式编写:

npm install --save-dev webpack-cli

粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词会以这种形式出现在文本中。以下是一个示例:“从管理面板中选择系统信息。”

警告或重要说明会以这种形式出现。

提示和技巧会出现在这样的形式中。

第一章:Webpack 5 简介

本书面向有经验的 JavaScript 开发人员,旨在通过逐步的过程带您完成一个特定示例项目的开发和生产。当您完成本指南时,您应该能够完全设置和部署一个可工作的捆绑应用程序。

本章将向您介绍 Webpack——具体来说,是 Webpack 5 版本。它将包括对 Webpack 周围的核心概念以及其用法的概述。

本章面向对 Webpack 和 Webpack 5 新手程序员。本章将涵盖初始设置,以及对该过程的概述,并将向您展示如何部署您的第一个捆绑应用程序。

本章将涵盖以下主题:

  • Webpack 5 的基础知识

  • 设置 Webpack

  • 创建一个示例项目

技术要求

您可以在本书的所有章节中找到使用的代码github.com/PacktPublishing/Webpack-5-Up-and-Running

  • 要使用本指南,您需要对 JavaScript 有基本的了解。

  • 确保您已安装了 Webpack 5 的最新版本。

  • 您将需要使用命令行,如命令提示符或您选择的其他命令行实用程序。

  • 您将需要 Node.js,JavaScript 运行环境。

  • 确保你已经安装了最新版本的 Node.js;否则,你可能会遇到很多问题。

  • 您需要在本地计算机上安装npm并具有管理员级别的权限。Webpack 和 Webpack 5 在 Node.js 环境中运行,这就是为什么我们需要它的包管理器 npm。

  • 截至撰写本文时,最新版本是 Webpack 5。访问webpack.js.org找到适合您的最新版本。

Webpack 5 的基础知识

基本上,Webpack 是一个用于 JavaScript 应用程序的模块打包工具。Webpack 接受一系列 JavaScript 文件,以及构成应用程序的图像文件等依赖项,并构建所谓的依赖图。依赖图是这些文件和依赖项在应用程序中如何排序和链接的表示,并显示文件之间的交互方式。

然后,这个依赖图形成了一个模板,捆绑器在将所有依赖项和文件压缩成更小的集合时会遵循这个模板。然后 Webpack 能够将这些文件捆绑成更大、但通常更少的文件集。这消除了未使用的代码、重复的代码以及重写的需要。在某种程度上,代码可以更简洁地格式化。

Webpack 递归地构建应用程序中的每个模块,然后将所有这些模块打包成少量的捆绑包。在大多数情况下,捆绑的应用程序将包含一个脚本,非常适合被程序(如 Web 浏览器)读取,但对程序员来说太复杂了。因此,开发人员将会拿一组源文件并对程序的这一部分进行更改,然后将这些源文件捆绑成一个输出——一个捆绑的应用程序。

捆绑最初是为了提高浏览器的阅读性能,但它还有许多其他优点。一旦 Webpack 捆绑了一组源文件,它通常会遵循一种系统化和常规的文件结构。代码中的错误可能会中断捆绑操作;本书将指导您如何克服这些问题。

现在,让我们探索 Webpack 5 周围的一般概念。

Webpack 5 背后的一般概念

在这里,我们将开始理解 Webpack 的关键概念和目的,而不是期望您有任何先前的了解。捆绑通常在桌面上使用 Node.js 或npm命令行界面CLI)(通常是命令提示符)上进行。

Webpack 是一个构建工具,将所有资产放入一个依赖图中。这包括 JavaScript 文件、图像、字体和层叠样式表CSS)。它将Sassy CSSSCSS)和 TypeScript 文件分别放入 CSS 和 JavaScript 文件中。只有当代码与后者格式兼容时,Webpack 才能做到这一点。

在 JavaScript 和其他语言编程时,源代码通常会使用诸如require()的语句,将一个文件指向另一个文件。Webpack 将检测这个语句,并确定所需的文件作为依赖项。这将决定最终 JavaScript 捆绑包中的文件如何处理。这还包括将 URL 路径替换为内容传送网络CDN)——这实质上是一组代理服务器网络——与本地文件。

以下图表是 Webpack 的一般目的的表示,即获取一组文件或依赖项并以优化的形式输出内容:

现在,让我们更仔细地看一些您可能不熟悉但在使用 Webpack 时可以被视为常用术语的术语。

术语

本节将涵盖 Webpack 5 中使用的术语。这将包括本地术语,以及一些更不寻常的缩写词。

  • 资产:这是 Webpack 中经常使用的一个术语,用于防止概念的混淆。它指的是软件在生成捆绑应用程序时收集的图像文件,甚至是数据或脚本文件。

  • 捆绑:这指的是 Webpack 编译应用程序后输出的应用程序。这是原始或源应用程序的优化版本——这将在后面的章节中详细讨论原因。捆绑器将这些文件合并成一个文件,这使得解包和破解变得非常困难。它还提高了浏览器的性能。它通过确保处理器保持在最佳水平,并删除任何不符合标准的编码结构来实现这一点。这也鼓励开发人员更加认真地采用惯例。如果存在任何不安全的编程,这些位置更容易被识别、隔离和纠正。

  • SASS:CSS 的增强版本。Webpack 处理这段代码就像处理 CSS 一样;然而,这可能是一个让你感到困惑的短语,所以了解一下是值得的。

  • SCSS:这只是用于给 SASS 增加额外功能的语法版本的名称。值得知道的是,Webpack 能够转译这两种语法。

  • 转译:这是 Webpack 5 将一组输入源代码转换为更优化的输出分发代码的过程。这是通过删除未使用或重复的代码来完成的。转译用于将一组文件转换为更简单的一组文件。例如,SCSS 通常包含可以轻松存储在 CSS 文件中的脚本。您还可以将 SCSS 转译为 CSS,或将 TypeScript 转译为 JavaScript。

  • TypeScript:对于未经训练的人来说,TypeScript 是一种在许多方面类似于 JavaScript 的代码类型。例如,浏览器最常运行 JavaScript,因此在可能的情况下使用 JavaScript 可能更合适。当前,Webpack 5 将在前者允许时将 TypeScript 转译为 JavaScript。

  • CDN:CDN 是一组代理服务器网络,提供高可用性和高性能。一些例子是谷歌 API,如谷歌字体,以及其他类似的工具,所有 JavaScript 开发人员无疑都很熟悉。

  • 依赖图:在 Webpack 5 中,依赖图是表示多个资产相互依赖的有向图。Webpack 5 将映射资产和依赖项的列表,并记录它们在应用程序中如何相互依赖。它使用这个来推导出一个适当的输出文件结构。

尽管 JavaScript 是入口点,但 Webpack 意识到您的其他资产类型(如 HTML、CSS 和 SVG)都有自己的依赖关系,这些依赖关系应该作为构建过程的一部分进行考虑。

Webpack 由输入输出组成。输出可以由一个或多个文件组成。除了捆绑模块外,Webpack 还可以对您的文件执行许多功能。输入是指在捆绑之前,原始文件的原始结构。输出是指捆绑后的文件在其新的和优化的文件结构中的结果。因此,输入由源文件组成,输出可以由开发文件或生产文件组成。

输入和输出以及源代码和开发代码之间经常混淆。

源代码指的是捆绑之前的原始应用程序。开发代码指的是将应用程序放入 Node.js 环境并以开发模式捆绑后的应用程序。在生产模式下会产生一个更“紧凑”的捆绑版本,但这个版本很难进行工作。因此,在捆绑后可以在一定程度上修改开发代码,这非常有用,例如在您修改数据库连接配置的情况下。

在使用 Webpack 5 时,这些短语可能会出现,重要的是您不要对它们感到太困惑。

大多数其他术语将在我们遇到它时进行解释,或者如果您熟悉 JavaScript,它是如此常见,我们假设您了解这些术语。

这总结了您在使用 Webpack 时会遇到的大部分术语。现在,我们将探讨软件的工作原理。

Webpack 的工作原理

Webpack 通过生成一组源文件中资产的依赖图来工作,然后从中转换出一组优化的分发文件。这些源和分发文件分别包含源代码和分发代码。这些分发代码形成了输出。分发只是输出或捆绑的另一个名称。

Webpack 首先在源文件中找到一个入口点,然后构建一个依赖图。在 Webpack 5 中,选择入口点是可选的,选择的方式将改变构建过程的性质,无论是速度还是输出优化。

Webpack 5 能够转换、捆绑或打包几乎任何资源或资产。

我们已经对软件的工作原理进行了良好的概述;之前使用过 Webpack 的有经验的用户可能会认为这个概述很基础,所以让我们来看看这个当前版本中有什么新东西。

Webpack 5 中有什么新功能?

备受欢迎的 Webpack 模块捆绑器已经经历了一次大规模更新,发布了第 5 版。Webpack 5 提供了巨大的性能改进、更动态的可扩展性和基本的向后兼容性。

Webpack 5 接替了第 4 版,第 4 版并非总是与许多可用的各种加载器向后兼容,这些加载器通常更兼容第 2 版,这意味着如果不使用第 2 版,开发人员通常会在命令行中遇到弃用警告。Webpack 5 现在已经解决了这个问题。

第 5 版的另一个重要卖点是联邦模块。我们将在稍后的第六章中更详细地讨论这一点,生产、集成和联邦模块。然而,总结一下,联邦模块本质上是捆绑应用程序以利用和与远程存储的单独捆绑中的模块和资产进行交互的一种方式。

Webpack 5 的优点总结如下:

  • Webpack 5 提供了对 HTTP 请求的控制,这提高了速度和性能,也减轻了安全问题。

  • Webpack 5 相对于竞争对手 Browserify 和 systemjs 有一些优势,特别是速度。构建时间直接取决于配置,但比最近的竞争对手更快。

  • 使用 Webpack 5 几乎不需要任何配置,但您始终可以选择配置。

  • 与其他替代方案相比,使用起来可能更复杂,但这主要是由于其多功能和范围,值得克服。

  • Webpack 5 具有优化插件,可以很好地删除未使用的代码。它还具有许多相关功能,例如树摇动,我们将在本书后面更详细地讨论。

  • 它比 Browserify 更灵活,允许用户选择更多的入口点并使用不同类型的资产。在捆绑大型 Web 应用程序和单页面 Web 应用程序时,它在速度和灵活性方面也更好。

Webpack 现在被认为是应用程序开发和 Web 开发中非常重要的工具,它可以改变结构并优化所有 Web 资产的加载时间,例如 HTML、JS、CSS 和图像。现在让我们实际使用 Webpack。为了做到这一点,我们将首先看一下可能对您来说是新的东西——如果您到目前为止只使用原生 JavaScript——模式。

模式

一旦您理解了一般概念,运行构建时需要了解的第一件事就是模式。模式对于 Webpack 的工作和编译项目至关重要,因此最好在继续之前简要但重要地介绍一下这个主题。

模式使用 CLI,这是我们稍后将更详细介绍的一个过程。如果您习惯使用原生 JavaScript,这可能对您来说是新的。但是,请放心,这不是一个难以理解的复杂主题。

Webpack 附带两个配置文件,如下所示:

  • 开发配置:这使用webpack-dev-server(热重载)、启用调试等。

  • 生产配置:这将生成一个在生产环境中使用的优化、最小化(uglify JS)、源映射的捆绑包。

自从发布第 5 版以来,Webpack 默认通过简单地向命令添加mode参数来处理模式功能。Webpack 不能仅使用package.json来查找模式以确定正确的构建路径。

现在我们已经掌握了基本原理,是时候进入实际设置了。

设置 Webpack

本书将逐步介绍一个示例项目的开发,我相信您会发现这是学习如何使用 Webpack 5 的简单方法。

Webpack 5 在本地机器上打包所有依赖项。理论上,这可以远程完成,但为了避免给第一次使用者带来任何困惑,我将强调使用本地机器。

对于大多数项目,建议在本地安装软件包。当引入升级或破坏性更改时,这样做会更容易。

我们将从npm安装开始。npm 是您将与 Webpack 5 一起使用的软件包管理器。一旦在本地机器上安装了它,您就可以使用 CLI,例如命令提示符,来使用npm命令。

安装了npm后,您可以继续下一步,即打开 CLI。有很多选择,但为了本教程的缘故,我们将使用命令提示符。

让我们一步一步地分解这个过程,这样您就可以跟上:

  1. 安装npm软件包管理器,您将与 Wepback 5 一起使用它。

  2. 打开 CLI(在本教程中,我们将使用命令提示符)并输入以下内容:

mkdir webpack4 && cd webpack5
npm init -y
npm install webpack webpack-cli --save-dev

让我们分解一下代码块。前面的命令首先会在您的本地计算机上创建一个名为webpack5的新目录。然后,它将把当前目录(cd)标识为webpack5。这意味着通过 CLI 进行的任何进一步的命令都将是相对于该目录进行的。接下来的命令是初始化npm。这些基本命令及其含义的完整列表可以在本章末尾的进一步阅读部分找到。这部分内容很有趣,我相信您会学到一些新东西。然后,我们在本地安装 Webpack 并安装webpack-cli——这是用于在命令行上运行 Webpack 的工具。

  1. 接下来,安装最新版本或特定版本的 Webpack,并运行以下命令。但是,在第二行,用您选择的版本替换<version>,例如5.00
npm install --save-dev webpack
npm install --save-dev webpack@<version>
  1. 下一个命令是npm install,它将在目录中安装 Webpack 5,并将项目保存在开发环境中。重要的是要注意开发环境和生产环境(或模式)之间的区别:
npm install --save-dev webpack-cli

以下行是package.json文件中的代码片段。我们需要这些输入文件来生成webpack.config.js文件,其中包含 Webpack 捆绑的配置信息。

  1. 我们必须确保package.json文件的编码如下:
"scripts": {
"build": "webpack --config webpack.config.js"
}

在使用 Webpack 5 时,您可以通过在 CLI 中运行npx webpack来访问其二进制版本。

我们还应该决定我们需要哪种类型的安装;任何重新安装都会覆盖先前的安装,所以如果您已经按照前面的步骤进行了操作,就不用担心了。

  1. 如果适用,现在让我们进行安装。

有两种类型的安装:

    • 全局:全局安装将锁定您的安装到特定版本的 Webpack。

以下npm安装将使 Webpack 全局可用:

npm install --global webpack
    • 本地:本地安装将允许您在项目目录中运行 Webpack。这需要通过npm脚本完成:
npm install webpack --save-dev

每次在新的本地计算机上开始新项目时,您都需要执行所有前面的步骤。完成安装后,是时候把注意力转回到构建项目上了。

创建一个示例项目

现在,我们将创建一个实验项目,具有以下目录结构、文件及其内容。

以下代码块是指您本地计算机上的一个文件夹。它说明了 Webpack 通常使用的格式和命名约定。您应该遵循此格式,以确保您的项目与本教程保持一致,如下所示:

  1. 首先设置项目树
webpack5-demo
 |- package.json
  |- index.html
  |- /src
  |- index.js

项目树向我们展示了我们将要处理的文件。

  1. 现在让我们仔细看一下索引文件,因为它们将成为我们前端的关键,从src/index.js开始:
function component() {
 let element = document.createElement('div');
// Lodash, currently included via a script, is required for this 
// line to work
 element.innerHTML = _.join(['Testing', 'webpack'], ' ');
 return element;
}
document.body.appendChild(component());

index.js包含我们的 JS。接下来的index.html文件是我们用户的前端。

  1. 它还需要设置,所以让我们打开并编辑index.html
<!doctype html>
<html>
 <head>
 <title>Webpack - Test</title>
 <script src="img/lodash@4.16.6"></script>
 </head>
 <body>
 <script src="img/index.js"></script>
 </body>
</html>

请注意前面的<script src="img/lodash@4.16.6">标签。这是指使用lodash库。index.js文件(而不是index.html文件)需要调用此库。Webpack 将从库中获取所需的模块,并使用它们来构建捆绑包的依赖关系图。

Lodash 是一个提供函数式编程任务的 JavaScript 库。它是在 MIT 许可下发布的,基本上使处理数字、数组、字符串和对象变得更容易。

需要注意的是,如果没有明确说明您的代码依赖于外部库,应用程序将无法正常运行。例如,依赖项可能丢失或包含顺序错误。相反,如果包含了但未使用依赖项,浏览器将下载不必要的代码。

我们可以使用 Webpack 5 来管理这些脚本。

  1. 你还需要调整你的package.json文件,将你的软件包标记为私有,并删除主入口点。这是为了防止意外发布你的代码:
{
 "name": "webpack5",
 "version": "1.0.0",
 "description": "",
 "private": true,
 "main": "index.js",
 "scripts": {
 "test": "echo \"Error: no test specified\" && exit 1"
 },
 "keywords": [],
 "author": "",
 "license": "ISC",
 "devDependencies": {
 "webpack": "⁵.0.0",
 "webpack-cli": "³.1.2"
 },
 "dependencies": {}
 }

你可以从前面代码中的粗体文本中看到如何进行这些修改。请注意,我们的入口点将设置为index.js。这是 Webpack 在开始捆绑编译时将读取的第一个文件(请参阅依赖图的先前定义)。

如果你想了解更多关于package.json文件的信息,请访问docs.npmjs.com/getting-started/,这里提供了关于npm的信息。

我们现在已经完成了第一个演示应用程序捆绑的源代码。这构成了我们现在将通过 Webpack 运行以生成我们的第一个捆绑应用程序的输入或源文件。

捆绑你的第一个项目

Web 打包简单地意味着捆绑项目。这是 Webpack 的本质,从这个非常简单的介绍开始学习应用程序是一个很好的方法。

首先,我们需要通过略微改变我们的目录结构来将源代码与分发代码分开。这个源代码用于编写和编辑,分发代码是经过最小化和优化的捆绑,是我们构建过程的结果。

现在,我们将详细介绍构建我们的第一个项目的每个步骤:

  1. 我们将首先构建项目和目录结构。首先注意/src/dist这两个术语;它们分别指的是源代码和分发代码:
webpack5-demo
|- package.json
  |- /dist
  |- index.html
  |- index.js
|- /src
|- index.js
  1. 要将lodash依赖项与index.js捆绑,我们需要在本地安装该库:
npm install --save lodash

在安装将捆绑到生产捆绑包的软件包时,应使用以下命令:

npm install --save 

如果你正在为开发目的安装软件包(例如,一个代码检查工具、测试库等),你应该使用以下命令:

npm install --save-dev
  1. 现在,让我们使用src/main.jslodash导入到我们的脚本中:
import_ from 'lodash';

function component() {
let element = document.createElement('div');
 // Lodash, currently included via a script, is required for this 
// line to work
element.innerHTML = _.join(['Hello', 'Webpack'], ' ');
return element;
}
document.body.appendChild(component());
  1. 接下来,更新你的dist/index.html文件。我们将删除对lodash库的引用。

这样做是因为我们将在本地安装库进行捆绑,不再需要远程调用库:

<!doctype html>
<html>
<head>
<title>Getting Started</title>
  <script src="img/lodash@4.16.6"></script>
  //If you see the above line, please remove it.
</head>
<body>
  <script src="img/main.js"></script>
</body>
</html>
  1. 接下来,我们将使用命令行运行npx webpacknpx命令随 Node 8.2/npm 5.0.0 或更高版本一起提供,并运行 Webpack 二进制文件(./node_modules/.bin/webpack)。这将把我们的脚本src/index.js作为入口点,并生成dist/main.js作为输出:
npx webpack
...
Built at: 14/03/2019 11:50:07
Asset Size Chunks Chunk Names
main.js 70.4 KiB 0 [emitted] main
...
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

如果没有错误,构建可以被视为成功。

请注意,警告不被视为错误。警告只是因为尚未设置模式而显示的。

我不会担心这个,因为 Webpack 将默认为生产模式。我们将在本指南的后面处理模式设置。

  1. 当你在浏览器中打开index.html时,你应该看到以下文本:
Testing Webpack5

万岁——我们已经完成了我们的第一个应用程序捆绑,我敢打赌你一定为自己感到非常自豪!这是一个开始的基本步骤;我们将在后面的章节中继续学习 Webpack 的更复杂的元素,并开始将它们应用到需要捆绑的现有项目中。

摘要

总之,Webpack 5 是一个非常多才多艺的捆绑工具,几乎使用了每一种可想象的方法来优化应用程序的大小并提高整体性能。了解它是非常值得的,本指南将向你展示你需要了解的一切。

现在,你应该了解 Webpack 背后的基本概念,以及基本术语。你现在也应该知道如何安装先决条件,比如 Node.js,并设置和部署——以及制作——你的第一个捆绑使用命令行。

在下一章中,我们将详细介绍模块和代码拆分,以及 Webpack 5 的一些更显著和有趣的方面,这些方面对理解 Webpack 至关重要。

问题

以下是与本章相关的一系列问题,您应该尝试回答以帮助您的学习。答案可以在本书的评估部分中找到:

  1. 什么是 Webpack?

  2. Webpack 中的捆绑包是什么?

  3. 根据本指南,Webpack 的最新版本是多少?

  4. Webpack 在哪个环境中工作?

  5. 什么是依赖图?

  6. 在捆绑时,以下命令缺少哪个入口?

npm --save lodash

  1. 我们在 Webpack 5 中使用的包管理器的名称是什么?

  2. 如何使用命令行删除lodash库?

  3. 在使用 Webpack 5 时,源代码和分发代码之间有什么区别?

  4. 在设置项目时,为什么要调整package.json文件?

第二章:使用模块和代码拆分

本章将探讨 Webpack 5 中的模块和代码拆分。模块是一种按功能将代码分组的方式。代码拆分是 Webpack 用来自动构建这些模块的方法;它将项目中的代码分割成最适合完成项目的功能和结构的模块。

本章涵盖的主题如下:

  • 解释模块

  • 理解代码拆分

  • 预取和预加载模块

  • 最佳实践

解释模块

Webpack 使用称为模块的元素。它使用这些模块来构建依赖图。

模块是处理相关功能的代码部分;根据模块化构建项目将提高功能。例如,与未使用模块构建的项目相比,只有与相关操作相关的代码需要运行。

说到这里,下一件要理解的事情是模块的具体功能,这将在接下来的部分中讨论。

模块的功能

模块是一组代码片段:例如,相似语言的代码具有共同的功能——也就是说,它是应用程序中相同功能或操作的一部分。

通常,Webpack 5 中的模块根据使用的脚本语言进行分组,如下所示:

前面的图表应该有助于说明大多数人在探索 Webpack 构建内容时看到的内容。

然后将应用程序分成模块和资产。正如我们在第一章中所解释的那样,资产基本上是开发人员不认为是脚本的图像和视频。然后,目录结构通常被细分为这些模块,通常在它们自己的目录中。

将应用程序分成模块将自然使调试过程更容易。这也将有助于我们进行验证和测试。

以这种方式构建应用程序可以确保在良好编写的代码和更加可疑的代码之间建立边界。当然,这有助于目录导航,因为每个模块都有一个明确定义的目的。

许多平台使用模块,这是一个您在一般的 Web 开发中肯定会习惯的术语。然而,每个平台略有不同。

Webpack 5 根据模块的依赖关系表达方式来形成这些模块。以下是 Webpack 5 表达它们的一些示例:

  • 通过2015 ECMAScriptimport语句

  • 通过**CommonJS **的require()语句

  • 通过异步模块定义 (ASM)的definerequire语句

  • 通过样式表中的 imageURL

  • 通过样式表中的@import语句

总之,模块化的代码使事情变得更容易,了解 Webpack 如何表达依赖关系将帮助您了解应该如何编译代码。从这里开始,自然的下一步是查看支持的模块类型以及加载器如何与它们一起工作。

支持的模块语言和加载器

为了确保 Webpack 5 支持这些模块,它们必须用可以理解和处理的编程语言编写。Webpack 5 通过使用称为加载器的东西来实现这一点。

加载器使 Webpack 在竞争对手捆绑器中真正脱颖而出。简单来说,加载器告诉 Webpack 如何处理不是 JavaScript 或其他 Webpack 自动理解的预定义代码(如 JSON 或 HTML)的代码。Webpack 5 将会将这些处理过的代码作为依赖项包含在您的捆绑包中。

Webpack 5 拥有一个开发者社区,称为 Webpack 社区,他们构建了这些加载器。这些加载器目前支持大量的语言和处理器;一些例子如下:

  • TypeScript

  • SASS

  • LESS

  • **C++ **

  • Babel

  • Bootstrap

有关可用加载器的完整列表,请参阅本章末尾的“进一步阅读”部分。

成为 Webpack 社区的一部分意味着您可以编写自己的加载器!这是值得考虑的事情,因为这可能是满足项目要求的最佳方式。

有许多更多的加载器可用于 Webpack 社区。使用加载器意味着 Webpack 5 可以被描述为一个动态平台,允许定制几乎任何技术堆栈。在本章中,我们将开始积极地使用加载器作为一些示例用例的一部分,您可以练习自己编码。

在开发过程中,您可能会遇到“封装”一词,特别是在处理模块和加载器时。

要理解封装,您首先需要了解软件有时可以独立开发,直到需要互动才会出现。为了使软件在项目中一起工作,必须在两个技术堆栈之间创建一个依赖关系。这就是“封装”一词的含义。

封装是一个简单的主题,但模块化编码的下一个领域是解析。这是一个更广泛的主题,因此已经作为自己的子部分进行了详细说明。

启用新的资产模块类型是 v5 的一个实验性功能。资产模块类型类似于file-loaderurl-loaderraw-loader(自 alpha.19 以来的experiments.asset)数据 URL 和相关选项自 beta.8 以来得到支持。

模块解析

模块解析是通过解析器进行的。解析器帮助您通过其绝对路径找到一个模块——在整个项目中通用的模块路径。

请注意,一个模块可以作为另一个模块的依赖项,例如以下情况:

import example from 'path/to/module';

无论依赖模块是来自另一个库(而不是解析器本身)还是应用程序本身,解析器都将帮助找到所需的模块代码以包含在捆绑包中。Webpack 5 也可以在捆绑时使用enhance-resolve来解析路径。

Webpack 5 的解析规则意味着,使用enhanced-resolve方法,Webpack 5 可以解析三种文件路径:

  • 绝对路径

  • 相对路径

  • 模块路径

以下部分将详细说明每个文件路径的含义,并且每个部分都将有一个示例。随着我们开始构建项目捆绑包,这将变得更加重要。

绝对路径

对于初学者来说,绝对路径是指文件路径和项目使用的所有文件和资产的位置。这个共同的位置有时被称为homeroot目录。以下是一个命令行位置的示例:

import 'C:\\Users\\project\\file';

前一行是绝对路径的一个示例。术语“绝对”是每个 JavaScript 开发人员都应该熟悉的内容。它涉及到系统中普遍存在的对象文件或目录的位置。

如果我们已经有了绝对路径,就像前一行一样,就不需要进一步解析了。

相对路径

相对路径是指一个对象文件或目录到另一个位置的位置。在这种情况下,它是“上下文”目录的位置——开发进行的当前工作位置:

import '../src/file';

在前面的示例中,资源文件的目录被认为是“上下文”目录。资源文件是指import()语句、require()语句或对外部文件的调用发生的文件。

在这种情况下,相对路径与上下文目录路径相结合,然后产生绝对路径。

模块路径

模块路径是并非所有 JavaScript 开发人员都习惯的东西。在 Webpack 中,它指的是相对于模块的位置。在下面的代码片段中,module将被用于你希望使用的任何特定模块名称的名称——例如你项目中现有模块的名称:

import 'module/sub-directory/file';

Webpack 5 搜索所有在resolve.module指令中指定的模块的目录。可以使用resolve.alias配置为每个原始模块文件路径创建别名。使用这种方法,Webpack 5 会检查文件路径以及它指向文件还是目录。

Webpack 5 有一个名为resolve.extension的选项。如果路径没有文件扩展名,这个解析器将指示 Webpack 可以用于解析的扩展名。这些可能包括.js.jsx或类似的扩展名。

如果文件路径不指向文件而只指向目录,Webpack 5 会搜索该目录中的package.json文件。然后 Webpack 5 使用resove.main字段配置中指定的字段来搜索package.json中包含的字段,并从中确定要使用的正确上下文文件路径。

如果目录中没有package.json文件,或者主字段没有返回有效路径,Webpack 5 会简单地搜索resolve.main配置中指定的文件名。

文件扩展名的解析方式类似,但使用resolve.extension选项。

到目前为止,我们已经涵盖了模块、路径解析、支持的语言和加载程序。下一个重要的理解是代码拆分——它是什么,以及 Webpack 如何利用它来形成它的模块和一般输出。

理解代码拆分

代码拆分允许用户将代码拆分成各种捆绑包,然后按需或并行加载。Webpack 的开发人员认为这是 Webpack 的“最引人注目的功能之一”(Webpack.js.org)。

代码拆分有两个关键优势——这个过程可以用来实现更小的捆绑包,并控制资源加载的优先级。这可以导致加载时间的改善。

Webpack 5 中有三种通用的代码拆分方法:

  • 入口点:这是使用入口点配置手动拆分代码。

  • 防止重复:这种方法使用SplitChunksPlugin来运行一个称为dedupe的过程,将代码拆分成称为chunks的模块组。

  • 动态导入:这种方法使用内联函数在模块内部进行调用以拆分代码。

一个 chunk 指的是一组模块。这是 Webpack 使用的一个术语,在其他平台上并不经常遇到。

dedupe 是一个使用机器学习快速执行匹配、去重和实体解析的 Python 库。它有助于从姓名和地址的电子表格中删除重复条目。

有了这三种方法的概述,我们现在可以在接下来的章节中详细讨论每一种方法。让我们从入口点开始。

入口点

使用入口点可能是执行代码拆分的最简单方法。这是一个手动操作,因此不像其他方法那样自动化。

我们现在将看一下从主捆绑包中拆分一个模块的开发。为此,我们将从一些实际工作开始。然后,我们将讨论重复和动态导入的概念。

我们现在将回到我们在第一章中工作的项目,Webpack 5 简介。这一次,我们将利用到目前为止在本章中学到的知识。

首先,创建一个工作目录。在这种情况下,我们使用了上一章中使用的目录名称。遵循相同的约定可能是一个好主意,这样你就可以在继续阅读本书的过程中跟踪项目的发展。

在下面的例子中,我们将做以下操作:

  1. 组织一个项目文件夹结构,以开始一个展示入口点如何工作的项目。您应该在练习项目目录中构建这组目录。这与在桌面上创建文件夹的方式相同。为了本示例,我们将称此文件夹为webpack5-demo(但您可以选择任何您喜欢的名称):
package.json
webpack.config.js
/dist
/src
 index.js
/node_modules
/node_modules/another-module.js
  1. 如果您使用的代码缺少最后一行文本(用粗体标记),请确保添加。这可以在命令行上完成;如果您决定使用命令行,请参考第一章,Webpack 5 简介,以获取指导。您可能已经注意到了another-module.js的包含。您可能不会认为这是一个典型的构建,但是您需要包含这个示例。

最终,您可以随意命名项目,但是为了遵循此实践项目,您应该使用到目前为止使用的相同命名约定以防混淆。

为了跟踪此项目的开发,使用您的集成开发环境IDE)或记事本,您应该创建前面提到的每个文件和文件夹。**/**字符表示一个文件夹。请注意another-module.js文件;它位于/node_modules目录中。

现在,我们将编辑并编译一个构建,从another-module.js文件开始。

  1. 在您选择的 IDE 或记事本中打开another-module.js
import _ from 'lodash';
console.log(
  _.join(['Another', 'module', 'loaded!'], ' ')
 );

// webpack.config.js 
 const path = require('path');
 module.exports = {
   mode: 'development',
   entry: {
     index: './src/index.js',
     another: './src/another-module.js'
 },
 output: {
   filename: '[name].bundle.js',
   path: path.resolve(__dirname, 'dist')
  }
 };

该文件基本上导入了lodash,确保加载的模块记录在控制台日志中,将 Webpack 构建模式设置为开发模式,并设置 Webpack 开始映射应用程序中的资产进行捆绑的入口点,并设置输出捆绑名称和位置。

  1. 现在,通过在命令行中输入上下文目录的位置(您正在开发的目录)并输入以下内容来使用npm运行构建:
npm run build

这就是您需要产生捆绑输出或开发应用程序的全部内容。

  1. 然后,检查是否成功编译。当在命令行中运行构建时,您应该看到以下消息:
...
 Asset Size Chunks Chunk Names
 another.bundle.js 550 KiB another [emitted] another
 index.bundle.js 550 KiB index [emitted] index
 Entrypoint index = index.bundle.js
 Entrypoint another = another.bundle.js
 ...

成功!但是,在使用开发人员应该注意的入口点时,可能会出现一些潜在问题:

  • 如果入口块之间存在重复的模块,它们将包含在两个捆绑包中。

对于我们的示例,由于lodash也作为./src/index.js文件的一部分导入到项目目录中,它将在两个捆绑包中重复。通过使用SplitChunksPlugin可以消除此重复。

  • 它们不能用于根据应用程序的编程逻辑动态拆分代码。

现在,我们将介绍如何防止重复。

使用 SplitChunksPlugin 防止重复

SplitChunksPlugin允许将常见依赖项提取到入口块中,无论是现有的还是新的。在以下步骤中,将使用此方法来从前面示例中去重lodash依赖项。

以下是从前面示例的项目目录中找到的webpack.config.js文件中的代码片段。此示例显示了使用该插件所需的配置选项:

  1. 我们将首先确保我们的配置与前面示例中的配置相同:
const path = require('path');
module.exports = {
  mode: 'development',
  entry: {
    index: './src/index.js',
    another: './src/another-module.js'
 },
 output: {
   filename: '[name].bundle.js',
   path: path.resolve(__dirname, 'dist')
 },
 optimization: {
   splitChunks: {
 chunks: 'all'
   }
  }
 };

使用optimization.splitChunks配置,重复的依赖项现在应该从index.bundle.jsanother.bundle.js中删除。lodash已被分离到一个单独的块和主捆绑包中。

  1. 接下来,执行npm run build
...
Asset Size Chunks Chunk Names
another.bundle.js 5.95 KiB another [emitted] another
index.bundle.js 5.89 KiB index [emitted] index
vendors~another~index.bundle.js 547 KiB vendors~another~index [emitted]    vendors~another~index
Entrypoint index = vendors~another~index.bundle.js index.bundle.js
Entrypoint another = vendors~another~index.bundle.js another.bundle.js
...

有其他由社区开发的加载器和插件可用于拆分代码。一些更值得注意的例子如下:

  • bundle-loader:用于拆分代码和延迟加载生成的捆绑包

  • promise-loader:类似于bundle-loader,但使用 promises

  • mini-css-extract-plugin:用于从主应用程序中拆分 CSS

现在,通过对如何防止重复的理解牢固,我们将转向一个更困难的主题——动态导入。

动态导入

动态导入本质上是 Webpack 上的按需导入。如果您已经捆绑了大量代码,但需要对其进行补丁,动态导入方法将会派上用场。这还包括动态代码拆分,即在构建包后拆分代码并优化它。

Webpack 5 支持两种方法来做到这一点:

  • 第一种方法使用了import()语法,符合 ECMAScript 的动态导入提案。

  • 第二种是特定于 webpack的方法,使用require.ensure方法(这是一种传统方法)。

以下是第一种方法的示例;目标是演示使用动态导入的现代方法,这在最近的项目中将更常见。

import()调用是对承诺的内部调用。承诺指的是从加载程序返回的信息。

在与旧版浏览器一起使用import()时,使用polyfill函数,例如es6-promisepromise-polyfill,来模拟承诺shim-loader是一个在 Webpack 5 环境中转换代码以使其工作的加载程序;这与使用imports-loaderexports-loader手动执行类似。

下一步是删除配置文件中的任何多余条目,其中包括optmization.splitChunks的引用,因为在接下来的演示中将不需要它:

  1. 现在,打开webpack.config.js文件并进行以下条目:
const path = require('path');
module.exports = {
 mode: 'development',
 entry: {
   index: './src/index.js'
   index: './src/index.js',
 },
 output: {
   filename: '[name].bundle.js',
   chunkFilename: '[name].bundle.js',
   path: path.resolve(__dirname, 'dist')
 },
 };

请注意chunkFilename的使用,它确定非入口块文件的名称。

前面的配置是为了准备您的项目使用动态导入。确保删除粗体文本,因为在处理相同代码时可能会看到这些。

回到项目中,我们需要更新它以删除未使用的文件的说明。

您可能已经设置了练习目录;但是,建议您从不包含任何实验代码的新目录集开始。

以下演示将使用动态导入来分离一个块,而不是静态导入lodash

  1. 打开index.js文件,确保进行以下条目:
function getComponent() {
  return import(/* webpackChunkName: "lodash" */ 'lodash').then((
      { default: _ }) => {
 var element = document.createElement('div');

 element.innerHTML = _.join(['Hello', 'Webpack'], ' ');

 return element;

 }).catch(error => 'An error occurred while loading 
     the component');
 }

  getComponent().then(component => {
    document.body.appendChild(component);
  })

在导入CommonJS模块时,此导入将不会解析module.exports的值;而是将创建一个人工命名空间对象。因此,在导入时我们需要一个默认值。

在注释中使用webpackChunkName将导致我们的单独包被命名为lodash.bundle.js,而不仅仅是[your id here].bundle.js。有关webpackChunkName和其他可用选项的更多信息,请参阅import()文档。

如果现在运行 Webpack,lodash将分离成一个新的包。

  1. 可以使用命令行界面CLI)运行npm run build。在 CLI 实用程序中,键入以下内容:
npm run build

运行构建时,您应该看到以下消息:

...
 Asset Size Chunks Chunk Names
 index.bundle.js 7.88 KiB index [emitted] index
 vendors~lodash.bundle.js 547 KiB vendors~lodash [emitted] vendors~lodash
 Entrypoint index = index.bundle.js
 ...

import()可以与异步函数一起使用,因为它返回一个承诺。这需要使用预处理器,例如syntax-dynamic-import Babel 插件。

  1. 使用src/index.js,进行以下修改以显示代码如何可以简化:
async function getComponent() {
 'lodash').then(({ default: _ }) => {
const element = document.createElement('div');
const { default: _ } = await import(/* webpackChunkName: "lodash" */ 'lodash');

element.innerHTML = _.join(['Hello', 'webpack'], ' ');

return element;
}

  getComponent().then(component => {
    document.body.appendChild(component);
  });

前面的示例使用了我们在动态导入部分中使用的相同文件。我们将多行代码转换为单行代码,用异步代码替换了返回函数,加快了我们的编码实践。您会发现它现在比以前的代码简单得多——它使用了相同的文件src/index.js,并实现了相同的功能。

我们经常简化代码以帮助加载时间。改善浏览速度的另一个关键方法是缓存。

缓存

在我们完成代码拆分的这一部分之前,我们将介绍缓存。缓存与之前的过程有关,毫无疑问,在编程过程中会遇到。对于初学者来说,缓存是存储先前计算的数据以便更快地提供的方法。它还与下一节关于预取和预加载有关,这些方法控制内存的使用方式。

了解缓存将确保您知道如何更有效地拆分代码。在下一个示例中,我们将看到如何做到这一点。在 Webpack 中,缓存是通过文件名哈希(当计算机递归跟踪文件位置时)完成的,特别是输出包的哈希化:

 module.exports = {
   entry: './src/index.js',
   plugins: [
    // new CleanWebpackPlugin(['dist/*']) for < v2 versions 
       of CleanWebpackPlugin
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'Output Management',
      title: 'Caching',
   }),
 ],
 output: {
  filename: 'bundle.js',
  filename: '[name].[contenthash].js',
  path: path.resolve(__dirname, 'dist'),
  },
};

请注意前面的代码块中的output键处理程序;在括号内,您将看到bundle.js文件名,下面是我们称之为哈希的内联元素。您应该用您的偏好替换括号内的术语。这种方法产生了一种替代输出,只有在内容更新时才会更新,并作为我们的缓存资源。

每个文件系统访问都被缓存,以便同一文件的多个并行或串行请求更快。在watch模式下,只有修改的文件才会从缓存中删除。如果关闭watch模式,则在每次编译之前都会清除缓存。

这将引导我们进入下一节,这也与导入有关——预取和预加载。

预取和预加载模块

在声明导入时,Webpack 5 可以输出一个资源提示。它会给浏览器以下命令:

  • preload(可能在当前导航期间需要)

  • prefetch(可能在未来的导航中需要)

“当前”和“未来”这些术语可能会令人困惑,但它们基本上指的是prefetch在用户需要之前加载内容,以某种方式提前加载和排队内容。这是一个简单的定义——接下来会有一个完整的解释——但总的来说,您可以从内存使用和用户体验的效率方面看到优缺点。

需要注意的是,在 Webpack 5 中,预取对Web AssemblyWASM)尚不起作用。

这个简单的prefetch示例可以有一个HomePage组件,它渲染一个LoginButton组件,当被点击时加载一个LoginModal组件。

LoginButton文件需要被创建;按照LoginButton.js中的说明进行操作:

import(/* webpackPrefetch: true */ 'LoginModal');

前面的代码将导致以下代码片段被附加到页面的头部:

 <linkrel="prefetch" href="login-modal-chunk.js"> 

这将指示浏览器在空闲时预取**login-modal-chunk.js**文件。

prefetch相比,preload指令有很多不同之处:

  • 使用preload指令的块与其父块并行加载,而预取的块在父块加载完成后开始加载。

  • 当预加载时,块必须由父块立即请求,而预取的块可以随时使用。

  • 使用preload指令的块在调用时立即下载。在浏览器空闲时下载预取的块。

  • 简单的preload指令可以有组件,它们总是依赖应该在单独块中的库。

使用preloadprefetch的选择在很大程度上取决于上下文;随着教程的进行,您将发现这可能如何适用于您。

根据前面的要点,您应该根据您的开发需求选择使用prefetchpreload。这在很大程度上取决于项目的复杂性,最终是开发人员做出的判断。

以下示例提出了一个想象的组件ChartComponent,在ChartComponent.js中需要一个我们称之为ChartingLibrary的库。它会在需要时立即导入库,并在渲染时显示LoadingIndicator

import(/* webpackPreload: true */ 'ChartingLibrary');

当请求ChartComponent时,也会通过<link rel="preload">请求charting-library-chunk

假设page-chunk加载完成得更快,页面将显示为LoadingIndicator,直到charting-library-chunk加载完成。这将提高加载时间,因为它只需要一个循环处理而不是两个。这在高延迟环境中尤其如此(在这些环境中,数据处理网络经常发生延迟)。

使用webpackPreload不正确可能会损害性能,因此在使用时要注意。

版本 5 中添加的一个功能是有用的,并与获取相关,即顶级等待,这是一个使模块可以作为大型异步函数的功能。这意味着它们将被异步处理。使用顶级等待,ECMAScript 模块ESM)可以等待资源,导致导入它们的其他模块在开始评估主体之前等待。

现在您应该了解了prefetchpreload的目的,以及如果使用不正确会如何影响性能。关于它们的使用决定将在很大程度上取决于您希望应用程序的性能如何。最好的方法是在进行正式捆绑包分析后再决定它们的使用,我们将在下一节中讨论。

最佳实践

与所有编程一样,有最佳实践可以确保最佳交付。这也是结束本章的一个很好的方式。如果遵循最佳实践,开发人员可以保护他们的应用程序免受安全漏洞和黑客攻击、性能不佳以及在团队协作或未来开发需要新开发人员时出现困难,从而使构建具有未来性。这后一点更适用于产品所有者或项目经理,而不是开发团队。

在 Webpack 方面,这里最重要的领域将是捆绑包分析和代码清理。

捆绑包分析

一旦开始拆分代码,分析输出并检查模块的最终位置将是有用的。充分利用捆绑包非常重要,因此捆绑包分析的正式程序可以被视为基本的,以及浏览器和安全性测试。建议使用官方分析工具。还有一些其他选项:

  • webpack-bundle-analyzer:这是一个插件和 CLI 实用程序,它将捆绑包内容表示为方便的交互式树状图,其中有缩放选项。

  • webpack-bundle-optimize-helper:这个工具将分析您的捆绑包,并提出减小捆绑包大小的建议。

  • webpack-visualizer:这用于可视化分析捆绑包,以查看哪些模块占用了太多空间,哪些可能是重复的。

  • webpack-chart:这提供了一个用于 Webpack 统计的交互式饼图。

树状图是一种用于使用嵌套图形(通常是矩形)显示分层数据的方法。

所有先前提到的工具都将有助于优化,这是 Webpack 的主要目的。

代码清理

另一种改进应用程序的方法是通过删除不需要的代码。当自动化时,这通常被称为树摇,我们将在后面的章节中讨论。当手动进行时,它被称为代码清理。由于这是一个在编程中不经常遇到的短语,可能需要给出一个定义。

代码清理是删除不需要或多余代码的过程,就像从一件西装上除去绒毛一样。这可能包括未使用的编码工件、错误的代码或其他任何不需要的东西。Webpack 在与Gulp等任务运行器集成时使用自动化过程来执行此操作。这将在下一章第六章中讨论,生产、集成和联合模块

如果您遵循这些步骤,那么毫无疑问,您的应用程序将发挥出最佳性能。代码拆分和模块化编程对于 Webpack 来说至关重要,需要牢固的理解,以防止在捆绑项目的复杂性不断提高时迷失方向。

总结

本章已经介绍了各种代码拆分实践,包括代码块和动态导入。现在,您将拥有扎实的知识基础,可以进行代码拆分和使用模块。这些是 Webpack 的基本特性,因此需要扎实的基础知识。

代码拆分和模块是 Webpack 应用程序结构上的必要性。对于需要大量编程的专业任务来说,代码块和动态导入将更加重要。

我们已经介绍了预取模块和捆绑分析——这些是需要清楚理解下一章内容的重要步骤,我们将在下一章中探讨配置的世界,了解其限制和能力,以及选项在其中发挥作用。

随着配置在 Webpack 开发中的中心地位和日常编程的重要性,这些概念变得更加重要。当涉及到生产环境并且需要项目正常运行时,选项变得更加重要。

为了测试您的技能,请尝试以下测验,看看您对本章涵盖的主题的理解是否达到标准。

问题

我们将以一组问题来结束本章,以测试您的知识。这些问题的答案可以在本书的后面,评估 部分找到。

  1. 代码拆分和模块化编程有何不同?

  2. 什么是代码块?

  3. 动态导入与入口点有何不同?

  4. preload 指令与 prefetch 指令有何优势?

  5. 代码清理是什么意思?

  6. 术语“promise”是什么意思?

  7. SplitChunksPlugin 如何防止重复?

  8. webpack-bundle-optimize-helper 工具提供了什么?

  9. webpack-chart 插件的作用是什么?

  10. 什么是树状映射?

进一步阅读

要查看完整的加载器列表,请转到 github.com/webpack-contrib/awesome-webpack

第三章:使用配置和选项

本章将包括配置和选项的实际用法,以及它们在任何给定构建中的相互关系和作用。它还将详细说明输出管理,也就是捆绑过程的输出和资产管理,以及作为依赖图的一部分的资产。这将涵盖文件放置和文件结构等子主题。

模块用于解决 JavaScript 具有全局函数的特性。Webpack 与这些模块一起工作,并隔离了变量和函数的暗示全局性质。

配置和选项是必要的,以便充分利用 Webpack。每个项目都是定制的,因此每个项目都需要对其参数进行特定的定制。本章将详细探讨这两个主题的确切性质,每个主题的限制以及何时使用它们。

本章讨论以下主题:

  • 理解配置

  • 理解资产管理

  • 理解输出管理

  • 探索 Webpack 5 的选项

理解配置

通过使用配置文件,在 Webpack 中进行配置通常是webpack.config.js,除非在特殊情况下可以有一个以上的文件分配给这个任务。在webpack.config.js的情况下,它是一个 JavaScript 文件,应该被修改以改变任何特定项目的配置设置。

在启动时,Webpack 和 Webpack 5 不需要配置文件,但软件会将src/index识别为默认项目输入。它还将结果输出到名为dist/main.js的位置。这个输出将被"缩小"并优化为生产环境。

缩小,或最小化,简单地指的是 Webpack 的主要功能之一:将使用的代码量减少到最小。这是通过消除重复、错误或多余的代码来实现的。

然而,一个 Webpack 项目通常需要改变其默认配置。默认配置是 Webpack 在没有任何加载器或特殊参数的情况下运行的方式,比如在第一章中描述的Webpack 5 简介Webpack 工作原理子部分。这是通过使用配置文件来完成的。开发人员应该创建一个名为webpack.config.js的文件,并将其放在项目的根文件夹中。这个文件将被 Webpack 自动检测和读取。

让我们通过探索使用多个配置文件来开始我们的讨论。

使用不同的配置文件

Webpack 5 提供了使用不同配置文件的选项,具体取决于情况。不仅如此,还可以使用命令行实用程序来更改正在使用的文件。在一个项目中使用多个捆绑包时,可能会遇到这种情况,这个主题将在本指南的后面详细介绍。以下代码片段显示了开发人员如何更改正在使用的配置文件。在这个例子中,一个文件被指向一个名为package.json的文件,这是 Webpack 经常使用的一个常见文件。这种技术被称为config flag

"scripts": {
  "build": "webpack --config example.config.js" }

请注意,Webpack 5 还允许自定义配置,正如在第一章中所解释的Webpack 5 简介,这是使用 Webpack 5 的一个显著优势。这是通过使用自定义配置文件来完成的。这与选项不同,因为这些变量不是使用命令行界面CLI)设置的。

使用选项

在 Webpack 中,选项指的是通过命令行而不是配置文件进行的设置,这是通过修改配置脚本来完成的。

在下面的例子中,我们将首先修改配置文件,简单地为我们的选项教程奠定基础。

在接下来的配置中,Node 的路径模块被使用,并且前缀是_dirname全局变量。Node 的路径模块只是 Node 用于处理文件或目录路径的实用程序。在操作系统之间工作时可能会出现文件路径问题,这可以防止这些问题发生,并确保相对路径正常工作。

示例中涉及的文件名为webpack.config.js。我们将用它来设置项目的模式,并且我们需要在到达选项之前这样做:

const path = require('path');

module.exports = {
  mode: "production", // "production" | "development" | "none"
  entry: "./app/entry", // string | object | array

在前面的代码块中,所选择的模式指示 Webpack 相应地使用其内置的优化。entry路径默认为./src。这是应用程序执行开始和捆绑开始的地方。

下面的代码块将显示相同文件的其余部分:

output: {
  path: path.resolve(__dirname, "dist"), // string
 filename: "bundle.js", // string
  publicPath: "/assets/", // string
  library: "MyLibrary", // string,
 libraryTarget: "umd", // universal module definition
  },

代码片段的这一部分显示了与 Webpack 发出结果相关的选项。

所有输出文件的目标目录必须是绝对路径(使用Node.js路径模块)。

filename指示入口块的文件名模板,publicPath统一资源定位符URL),指的是相对于相关 HTML 页面解析到输出目录的路径。简而言之,这意味着从您可能使用的 HTML 页面到捆绑项目文件的文件路径。代码的其余部分涉及导出库的名称和导出库的性质。

接下来的主题涉及与模块相关的配置。在处理输出选项之后,这将是项目开发中的下一个逻辑步骤:

module: { 
   rules: [
        {
        test: /\.jsx?$/,
        include: [
          path.resolve(__dirname, "app")
        ],
        exclude: [
          path.resolve(__dirname, "app/demo-files")
        ],

前面的代码块包括了对模块的规则,比如解析器选项和加载器的配置。这些都是匹配条件,每个都接受一个字符串或正则表达式。术语test的行为与include相同。它们都必须匹配,但对于exclude来说并非如此。exclude优先于testinclude选项。

为了最佳实践,RegExp应该只在文件名匹配时用于test。在使用路径数组时,应优先使用绝对路径而不是includeexclude选项。include选项应优先于exclude方法:

issuer: { test, include, exclude },
        enforce: "pre",
        enforce: "post",
        loader: "babel-loader",
        options: { presets: ["es2015"] },
},
      {
        test: /\.html$/,
        use: [ "htmllint-loader",
{
            loader: "html-loader",
            options: {
              / *...* /
            }
          }
        ]
      },

前面的代码块包括了对发行者和导入元素的来源的条件。代码还包括了标记这些规则的应用的选项,即使它们被覆盖了。然而,这是一个高级选项。

loader的引用指示应用哪个加载器。这是相对于上下文位置解析的。自 Webpack 2 以来,加载器后缀不再是可选的,为了清晰起见。还有空间应用多个其他选项和加载器。

在相同的配置中,我们将探讨可以在同一过程中应用的规则和条件,如下面的代码块所示:

{ oneOf: [ / rules / ] },
{ rules: [ / rules / ] },
{ resource: { and: [ / conditions / ] } }, 
{ resource: { or: [ / conditions / ] } },
{ resource: [ / conditions / ] },
{ resource: { not: / condition / } }],
    /* Advanced module configuration */
  },
  resolve: {

前面的代码块包括了嵌套规则,所有这些规则都与条件结合在一起是有用的。解释一下,注意以下每个命令及其表示的含义:

  • and选项只有在所有条件也匹配时才会进行匹配。

  • or匹配在条件匹配时应用——这是数组的默认值。

  • not指示条件是否不匹配。

还有一个选项用于解析模块请求;这不适用于解析加载器。以下示例显示了使用此resolve模块请求:

modules: [
      "node_modules",
      path.resolve(__dirname, "app")
    ], extensions: [".js", ".json", ".jsx", ".css"], 
    alias: { 
              "module": "new-module",
              "only-module$": "new-module",
              "module": path.resolve(__dirname, "app/third/module.js"),
           },
},

  performance: {
    hints: "warning", // enum
    maxAssetSize: 200000, // int (in bytes),
    maxEntrypointSize: 400000, // int (in bytes)
    assetFilter: function(assetFilename) {
    return assetFilename.endsWith('.css') || assetFilename.endsWith('.js');
    }
  },

前面的代码块显示了我们在本节中一直在遵循的相同配置文件。然而,让我们看一下一些关键元素。在path.resolve处,这指的是要查找模块的目录。直接下面的], extensions:指的是使用的文件扩展名。

在此部分之后是代码,按降序列出模块名称的别名列表。模块的别名是相对于当前位置上下文导入的,如下面的代码块所示:

devtool: "source-map", // enum
context: __dirname, // string (absolute path!)
target: "web", // enum
externals: ["react", /^@angular/],
serve: { //object
    port: 1337,
    content: './dist',
    // ...
  },
stats: "errors-only",

devtool配置通过为浏览器添加元数据来增强调试。请注意,source-map选项可能更详细,但这是以构建速度为代价的,web选项指示 Webpack 的主目录。入口和module.rules.loader选项相对于此目录解析,并指的是捆绑包应该运行的环境。serve配置允许您为webpack-serve提供选项,并精确控制显示哪些捆绑信息,例如以下内容:

devServer: { proxy: { // proxy URLs to backend development server '/api': 'http://localhost:3000' },
    contentBase: path.join(__dirname, 'public'), 
    compress: true, 
    historyApiFallback: true, 
    hot: true, 
    https: false, 
    noInfo: true, 

  },
  plugins: [

  ],
  // list of additional plugins

让我们解释前面的代码块。当它声明compress: true时,这启用了内容的gzip压缩。historyApiFallback: true部分是当遇到任何 404 页面加载错误时为真。hot: true文本指的是是否允许热模块替换;这取决于是否首先安装了HotModuleReplacementPluginhttps应设置为true以用于自签名对象或证书授权对象。如果noInfo键设置为true,则只会在热重新加载时获得错误和警告。

配置完成,现在可以运行构建。要做到这一点,使用以下命令:

npx webpack-cli init

一旦在命令行环境中运行了前面的代码,用户可能会被提示安装@webpack-cli/init,如果它尚未安装在项目中。

运行npx webpack-cli init后,根据配置生成期间所做的选择,可能会在项目中安装更多的包。以下代码块显示了运行 NPX Webpack 的 CLI 初始化时的输出:

npx webpack-cli init

 INFO For more information and a detailed description of each question, have a look at https://github.com/webpack/webpack-cli/blob/master/INIT.md
 INFO Alternatively, run `webpack(-cli) --help` for usage info.

 Will your application have multiple bundles? No
 Which module will be the first to enter the application? [default: ./src/index]
 Which folder will your generated bundles be in? [default: dist]:
 Will you be using ES2015? Yes
 Will you use one of the below CSS solutions? No

  babel-plugin-syntax-dynamic-import@6.18.0
  uglifyjs-webpack-plugin@2.0.1
  webpack-cli@3.2.3
  @babel/core@7.2.2
  babel-loader@8.0.4
  @babel/preset-env@7.1.0
  webpack@4.29.3
  added 124 packages from 39 contributors, updated 4 packages and audited 
  25221 packages in 7.463s
  found 0 vulnerabilities

Congratulations! Your new webpack configuration file has been created!

如果你在 CLI 中的输出看起来像前面的代码块,那么你的配置就成功了。这基本上是从命令行自动读取的,应该表示在前面的代码块中设置的所有选项都已记录。

我们已经通过配置和选项,你现在应该知道每个选项的区别和使用范围。现在自然而然地转向资产管理。

理解资产管理

资产主要通过依赖图进行管理,我们在第一章中已经介绍过,Webpack 5 简介

在 Webpack 伟大的出现之前,开发人员会使用诸如gruntgulp之类的工具来处理这些资产,并将它们从源文件夹移动到生产目录或开发目录(通常分别命名为/build/dist)。

JavaScript 模块也使用了相同的原则,但 Webpack 5 会动态捆绑所有依赖项。由于每个模块都明确声明了它的依赖项,未使用的模块将不会被捆绑。

在 Webpack 5 中,除了 JavaScript 之外,现在还可以包含任何其他类型的文件,使用加载器。这意味着使用 JavaScript 时可能的所有功能也可以被利用。

在接下来的小节中,我们将探讨实际的资产管理。将涵盖以下主题:

  • 为资产管理配置设置项目

  • 加载层叠样式表CSS)文件

  • 加载图像

  • 加载字体

  • 加载数据

  • 添加全局资产

然后,将有一个小节来总结。

每个小节都将有步骤和指导内容要遵循。这可能是一个相当大的主题,所以紧紧抓住!我们将从准备项目的配置开始。

为资产管理配置设置项目

为了在项目中设置资产管理配置,我们需要通过以下步骤准备我们的项目索引和配置文件:

  1. 首先,通过使用dist/index.html文件对示例项目进行微小的更改,如下所示:
  <!doctype html>
  <html>
    <head>
    <title>Asset Management</title>
    </head>
    <body>
     <script src="img/bundle.js"></script>
    </body>
  </html>
  1. 现在,使用webpack.config.js编写以下内容:
  const path = require('path');

  module.exports = {
    entry: './src/index.js',
    output: {
     filename: 'bundle.js',
     path: path.resolve(__dirname, 'dist')
    }
  };

前面的两个代码块只是显示了一个占位符索引文件,我们将用它来进行资产管理的实验。后一个代码块显示了一个标准配置文件,将索引文件设置为第一个入口点,并设置输出捆绑包的名称。这将在我们完成资产管理实验后为我们的项目准备捆绑。

您的项目现在已经设置好了资产管理配置。本指南现在将向您展示如何加载 CSS 文件。

加载 CSS 文件

示例项目现在将显示 CSS 的包含。这是一个非常容易掌握的事情,因为大多数从 Webpack 5 开始的前端开发人员应该对它很熟悉。

要加载 CSS 并运行构建,请执行以下步骤:

  1. 首先,使用以下命令行指令将style-loadercss-loader安装并添加到项目的模块配置中:
npm install --save-dev style-loader css-loader
  1. 接下来,向webpack.config.js文件添加以下内容:
  const path = require('path');

  module.exports = {
    entry: './src/index.js',
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist')
    },
   module: {
     rules: [
       {
         test: /\.css$/,
         use: [
           'style-loader',
           'css-loader'
         ]
       }
     ]
   }
  };

从前面的代码块中可以看出,以下添加是指向代码块末尾的style-loadercss-loader的使用。为了避免出现错误,您应该确保您的代码与示例相符。

style-loadercss-loader之间的区别在于前者确定样式将如何被注入到文档中,比如使用样式标签,而后者将解释@importrequire语句,然后解析它们。

建议同时使用这两个加载程序,因为几乎所有CSS操作在项目开发的某个阶段都涉及这些方法的组合。

在 Webpack 中,正则表达式用于确定应该查找哪些文件并将其提供给特定的加载程序。这允许将样式表导入到依赖它进行样式设置的文件中。当运行该模块时,一个带有字符串化 CSS 的<style>标签将被插入到 HTML 文件的<head>中。

  1. 现在,导航到目录结构,我们可以在以下示例中看到:
webpack5-demo 
package.json 
webpack.config.js 
/dist 
bundle.js 
index.html 
/src  
style.css 
index.js 
/node_modules

从这个结构中我们可以看到有一个名为style.css的样式表。我们将使用这个来演示style-loader的使用。

  1. src/style.css中输入以下代码:
.hello {
  color: blue;
}

上面的代码只是创建了一个颜色类样式,我们将使用它来附加样式到我们的前端,并展示 CSS 加载的工作原理。

  1. 同样,将以下内容追加到src/index.js中:
  import _ from 'lodash';
  import './style.css';

  function component() {
    const element = document.createElement('div');

    // Lodash, now imported by this script
    element.innerHTML = _.join(['Hello', 'Webpack'], ' ');
    element.classList.add('hello');

    return element;
  }

  document.body.appendChild(component());

前面的代码都发生在index.js文件中。它基本上创建了一个 JavaScript 函数,该函数在从浏览器调用它的任何文件中插入一个<div>元素。在这个示例中,它将是index.html文件,在目录结构示例中提到的。前面的代码将在网页上“连接”一个HTML元素,其中包含文本“Hello, Webpack”。我们将使用这个来测试style-loadercss-loader是否被正确使用。正如脚本的注释部分所述,这个元素附加将自动导入lodash以便与 Webpack 一起使用。

  1. 最后,运行build命令,如下所示:
npm run build

...
    Asset      Size  Chunks             Chunk Names
bundle.js  76.4 KiB       0  [emitted]  main
Entrypoint main = bundle.js
...

当在浏览器窗口中打开index.html文件时,您应该看到“Hello Webpack”现在以蓝色样式显示。

要查看发生了什么,请检查页面(不是页面源代码,因为它不会显示结果),并查看页面的头标签。最好使用谷歌的 Chrome 浏览器进行。它应该包含我们在index.js中导入的样式块。

您可以并且在大多数情况下应该最小化 CSS 以获得更好的生产加载时间。

下一个自然的步骤是开始添加图片。图片可以以与任何网站应用程序相同的方式添加到项目中。将这些图片放在图像文件夹中以任何所需的格式。这必须在/src文件夹中,但它们可以放在其中的任何位置。下一个步骤是使用 Webpack 加载图片,我们现在将进行这一步。

加载图片

现在,让我们尝试使用文件加载程序加载图像和图标,这可以很容易地整合到我们的系统中。

要做到这一点,执行以下步骤:

  1. 使用命令行,安装file-loader,如下所示:
npm install --save-dev file-loader
  1. 现在,使用通常的webpack.config.jsWebpack 配置文件,对其进行以下修改:
  const path = require('path');
  module.exports = {
    entry: './src/index.js',
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist')
    },
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [
            'style-loader',
            'css-loader'
          ]
        },
        {
          test: /\.(png|svg|jpg|gif)$/, 
          use: [  
             'file-loader'
          ]
        }
      ]
    }
  };

现在,由于前面的代码块中的代码,当您导入图像时,该图像将被处理到输出目录,并且与该图像相关联的变量将在处理后包含该图像的最终URL。当使用css-loader时,类似的过程将发生在CSS文件中图像文件的URL上。加载程序将识别这是一个本地文件,并将本地路径替换为输出目录中图像的最终路径。html-loader以相同的方式处理<img src="img/my-image.png" />

  1. 接下来,要开始添加图像,您需要导航到项目文件结构,看起来像这样:
  webpack5-demo
  |- package.json
  |- webpack.config.js
  |- /dist
    |- bundle.js
    |- index.html
  |- /src
 |- icon.png
    |- style.css
    |- index.js
  |- /node_modules

这个结构看起来与之前的项目非常相似,直接用于大部分“加载 CSS 文件”教程,只是增加了icon.png图像文件。

  1. 然后,导航到 JavaScript 前端文件src/index.js。以下代码块显示了内容:
import _ from 'lodash'; import './style.css';  
import Icon from './icon.png'; 
function component() { 
    const element = document.createElement('div'); 
    // Lodash, now imported by this script 
        element.innerHTML = _.join(['Hello', 'Webpack'], ' '); 
        element.classList.add('hello');  
    // Add the image to our existing div.  
    const myIcon = new Image(); myIcon.src = Icon; 
    element.appendChild(myIcon); 
    return element; 
} 
document.body.appendChild(component());

从前面的代码块可以看出,导入lodash将允许您的页面的HTML附加Hello Webpack文本。除此之外,这段代码只是用一些巧妙的 JavaScript 设置了我们的网页和图像。它首先创建一个名为Icon的变量,并为其赋予图像文件的URL的值。在代码的后面,它将这个值分配给一个名为myIcon的元素的源。

  1. 从这里开始,我们想要设置一些非常基本的样式来处理我们的图像。在src/style.css文件中,追加以下代码:
  .hello {
    color: red;
    background: url('./icon.png');
  }

当然,它将显示您的图像图标作为我们在HTML中分配代码的div的背景,其中应用了.hello类的地方文本变为红色

  1. 运行新的构建并打开index.html文件,如下所示:
npm run build

...
Asset                                 Size          Chunks         Chunk Names
da4574bb234ddc4bb47cbe1ca4b20303.png  3.01 MiB          [emitted]  [big]
bundle.js                             76.7 KiB       0  [emitted]         main
Entrypoint main = bundle.js
...

这将创建图标重复作为背景图像的效果。在Hello Webpack文本旁边还会有一个img元素。

通常,即使对于经验丰富的开发人员,这个命令也可能出错。例如,图像可能根本不加载,太大,或者无法正确捆绑。这可能是由多种因素造成的,包括以不寻常的方式使用加载程序。在使用长文件名时,Webpack 也可能会出现代码跳过的情况。

如果是这种情况,只需重复以下步骤:

  1. 使用命令行安装file-loader

  2. 按照前面的示例修改webpack.config.js文件。

  3. 检查项目文件结构和索引文件是否正确格式化以加载图像文件。

  4. 检查CSS是否也按您的要求格式化。

  5. 然后,使用npm和命令行运行构建。

  6. 检查索引文件是否正确加载图像。

如果检查该元素,可以看到实际的文件名已更改为类似于da4574bb234ddc4bb47cbe1ca4b20303.png的内容。这意味着 Webpack 在源文件夹中找到了我们的文件并对其进行了处理。

这为您提供了一个管理图像的坚实框架。在下一小节中,我们将讨论 Webpack 资产的字体管理。

加载字体

现在,我们将在资产的上下文中检查字体。文件和 URL 加载程序将接受通过它们加载的任何文件,并将其输出到您的构建目录。这意味着我们可以将它们用于任何类型的文件,包括字体。

我们将首先更新 Webpack 配置 JavaScript 文件,需要处理字体,如下所示:

  1. 确保更新配置文件。我们在这里更新了通常的webpack.config.js配置文件,但您会注意到在末尾添加了一些字体类型,例如.woff.woff2.eot.ttf.otf,如下面的代码块所示:
  const path = require('path');

  module.exports = {
    entry: './src/index.js',
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist')
    },
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [
            'style-loader',
            'css-loader'
          ]
        },
        {
          test: /\.(png|svg|jpg|gif)$/,
          use: [
            'file-loader'
          ]
        },
       {
         test: /\.(woff|woff2|eot|ttf|otf)$/,
         use: [
           'file-loader'
         ]
       }
      ]
    }
  };

此配置允许 Webpack 的file-loader合并字体类型,但我们仍然需要向项目添加一些字体文件。

  1. 现在,我们可以执行将字体添加到源目录的基本任务。下面的代码块说明了文件结构,指示新字体文件可以添加的位置:
  webpack5-demo
  |- package.json
  |- webpack.config.js
  |- /dist
    |- bundle.js
    |- index.html
  |- /src
    |- sample-font.woff
    |- sample-font.woff2
    |- icon.png
    |- style.css
    |- index.js
  |- /node_modules

注意src目录和sample-font.woffsample-font.woff2文件。这两个文件应该被您选择的任何字体文件替换。Web Open FontWOFF)格式通常建议与 Webpack 项目一起使用。

通过使用@font-face声明,可以将字体合并到项目的样式中。Webpack 将以与处理图像相同的方式找到本地 URL 指令。

  1. 使用src/style.css文件更新样式表,以在我们的主页上包含示例字体。这是通过在代码块顶部使用字体声明和在下面使用类定义来完成的,如下面的代码块所示:
 @font-face {
    font-family: 'SampleFont';
    src:  url('./sample-font.woff2') format('woff2'),
          url('./sample-font.woff') format('woff');
    font-weight: 600;
    font-style: normal;
  }

  .hello {
    color: blue;
 font-family: 'SampleFont';
    background: url('./icon.png');
  }

请注意,您必须将'SampleFont'文本更改为与您选择的字体文件相对应的文本。前面的代码显示了通过 CSS 加载字体以及设置自定义值,如font-weightfont-styleCSS代码然后使用.hello类将该字体分配给任何潜在的HTML元素。请注意,我们在前两个教程中已经为此准备好了我们的index.html文件,加载 CSS 文件加载图像

  1. 现在,像往常一样使用命令行实用程序以开发模式运行npm构建,如下所示:
npm run build

...
                                 Asset      Size  Chunks                    Chunk Names
5439466351d432b73fdb518c6ae9654a.woff2  19.5 KiB          [emitted]
 387c65cc923ad19790469cfb5b7cb583.woff  23.4 KiB          [emitted]
  da4574bb234ddc4bb47cbe1ca4b20303.png  3.01 MiB          [emitted]  [big]
bundle.js                                 77 KiB       0  [emitted]         main
Entrypoint main = bundle.js
...

再次打开index.html,看看我们使用的Hello Webpack示例文本是否已更改为新字体。如果一切正常,您应该能看到变化。

这应该作为一个简单的教程来理解字体管理。下一节将涵盖文件的数据管理,如可扩展标记语言XML)和JavaScript 对象表示JSON)文件。

加载数据

另一个有用的资源是数据。数据是一个非常重要的要加载的资源。这将包括JSON逗号分隔值CSV)、制表符分隔值TSV)和XML文件等文件。使用诸如import Data from './data.json'这样的命令默认情况下可以工作,这意味着JSON支持内置到 Webpack 5 中。

要导入其他格式,必须使用加载器。以下子节演示了处理所有三种格式的方法。应采取以下步骤:

  1. 首先,您必须使用以下命令行安装csv-loaderxml-loader加载器。
npm install --save-dev csv-loader xml-loader

前面的代码块只是显示了安装两个数据加载器的命令行。

  1. 打开并追加webpack.config.js配置文件,并确保其看起来像以下示例:
  const path = require('path');

  module.exports = {
    entry: './src/index.js',
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist')
    },
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [
            'style-loader',
            'css-loader'
          ]
        },
        {
          test: /\.(png|svg|jpg|gif)$/,
          use: [
            'file-loader'
          ]
        },
        {
          test: /\.(woff|woff2|eot|ttf|otf)$/,
          use: [
            'file-loader'
          ]
        },
        {
          test: /\.(csv|tsv)$/,
          use: [
            'csv-loader'
          ]
        },
        {
          test: /\.xml$/,
          use: [
            'xml-loader'
          ]
        }
      ]
    }
  };

在前面的代码块中,下部显示了csv-loaderxml-loader的使用。这次需要进行的修改是将数据加载到我们的项目中。

  1. 接下来,我们必须向源目录添加一个数据文件。我们将在我们的项目中添加一个XML数据文件,如下面代码块中的粗体文本所示:
  webpack5-demo
  |- package.json
  |- webpack.config.js
  |- /dist
    |- bundle.js
    |- index.html
  |- /src
    |- data.xml
    |- samplefont.woff
    |- sample-font.woff2
    |- icon.png
    |- style.css
    |- index.js
  |- /node_modules

查看您项目文件夹的src目录中的前述data.xml文件。让我们更仔细地查看一下这个文件里的数据,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<note>
  <to>Tim</to>
  <from>Jakob</from>
  <heading>Reminder</heading>
  <body>Call me tomorrow</body>
</note>

从前面的代码块中可以看出,内容是一个非常基本的XML数据集。我们将使用它来导入XML数据到我们项目的index.html页面中,并且需要正确格式化以确保其正常工作。

这四种类型的数据(JSONCSVTSVXML)中的任何一种都可以被导入,并且您导入的data变量将包含解析后的 JSON。

  1. 确保修改src/index.js文件以公开数据文件。注意./data.xml的导入,如下面的代码块所示:
  import _ from 'lodash';
  import './style.css';
  import Icon from './icon.png';
  import Data from './data.xml';

  function component() {
    const element = document.createElement('div');

    // Lodash, now imported by this script
    element.innerHTML = _.join(['Hello', 'Webpack'], ' ');
    element.classList.add('hello');

    // Add the image to our existing div.
    const myIcon = new Image();
    myIcon.src = Icon;

    element.appendChild(myIcon);

    console.log(Data);

    return element;
  }

  document.body.appendChild(component());

这次我们只需要添加import函数,几乎没有别的东西,来演示使用方法。熟悉 JavaScript 的人也会知道如何轻松地运行他们的特定项目。

  1. 运行构建并检查数据是否正确加载,方法如下:
npm run build

运行npm构建后,可以打开index.html文件。检查控制台(例如在 Chrome 中使用开发者工具)将显示导入后记录的数据。

与项目架构相关的是为项目消耗安排全局资产的方式。让我们在下一小节中探讨这一点。

添加全局资产

以前述方式加载资产允许模块以更直观、实用和可用的方式进行分组。

与包含每个资产的全局资产目录不同,资产可以与使用它们的代码分组。以下文件结构或树演示了一个非常实用和可用的示例:

  |- /assets
  |– /components
  |  |– /my-component |  |  |– index.jsx
  |  |  |– index.css
  |  |  |– icon.svg
  |  |  |– img.png

前面的例子使您的代码更具可移植性。如果您想将一个组件放在另一个目录中,只需将其复制或移动到那里。或者,如果您的开发工作遵循老式的方式,也可以使用基本目录。此外,别名也是一个选择。

用最佳实践结束教程

这是一个漫长的教程,你的一些代码可能已经出错了。清理这些代码并检查是否有任何错误是一个好习惯。

清理是一个好习惯。在接下来的部分中,理解输出管理,我们不会使用很多资产,所以让我们从那里开始。

  1. 我们开始用项目目录项目树结束。让我们检查它们是否正确。它应该看起来像下面这样:
  webpack5-demo
  |- package.json
  |- webpack.config.js
  |- /dist
    |- bundle.js
    |- index.html
  |- /src
    |- data.xml
    |- sample-font.woff
    |- sample-font.woff2
    |- icon.png
    |- style.css
    |- index.js
  |- /node_modules

在结束时,您应该删除与前面代码块中加粗文本相对应的文件。

这应该让你对项目文件和文件夹的外观有一个很好的了解。确保我们一直在使用的所有文件都在那里,并且在适当的文件夹中。

  1. 让我们检查我们配置的格式。

webpack.config.js上已经做了很多工作,我们必须确保内容格式正确。请参考以下代码块,并将其与您自己的代码进行对比,以确保正确。通常有用的是计算{的数量,并使用传统结构美化您的代码,以使这个过程更容易:

  const path = require('path'); module.exports = {
    entry: './src/index.js',
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist')
    },
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [
            'style-loader',
            'css-loader'
          ]
        },
        {
          test: /\.(png|svg|jpg|gif)$/,
          use: [
            'file-loader'
          ]
        },
        {
          test: /\.(woff|woff2|eot|ttf|otf)$/,
          use: [
            'file-loader'
          ]
        },
        {
          test: /\.(csv|tsv)$/,
          use: [
            'csv-loader'
          ]
        },
        {
          test: /\.xml$/,
          use: [
            'xml-loader'
          ]
        }
      ]
    }
  };

注意到对 CSS、图像文件、诸如.woff的字体以及独立处理程序中的数据文件(如.csv.xml)的广泛引用。所有这些都很重要,您应该花时间确保脚本准确,因为这是一个广泛的主题和实际练习,所以很多东西可能被忽视。

  1. 接下来,我们需要检查src/index.js文件的脚本,方法如下:
  import _ from 'lodash';
 import './style.css';
  import Icon from './icon.png';
  import Data from './data.xml';

  function component() {
    const element = document.createElement('div');

 // Lodash, now imported by this script
    element.innerHTML = _.join(['Hello', 'Webpack'], ' ');
 element.classList.add('hello');

    // Add the image to our existing div.
    const myIcon = new Image();
    myIcon.src = Icon;

    element.appendChild(sampleIcon);

    console.log(Data);

    return element;
  }

  document.body.appendChild(component());

再次,我们在这里结束,以便在使用多个教程后代码是可重用的,所以请确保在您的版本中删除加粗的文本。

我们已经经历了一系列的资产管理操作,并以项目整理过程结束。为了使其正常运行,您的所有代码应该看起来像包装部分中的以前的代码块。

现在您应该对 Webpack 如何管理这些资产以及在使用 Webpack 时如何管理它们有了清晰的理解。通过整理文件结构和代码,我们现在可以开始输出管理。

理解输出管理

输出是指从源文件创建的包。源文件在 Webpack 中被称为输入。输出管理指的是对这些新打包文件的管理。根据 Webpack 在构建开始时运行的模式,这些包将是开发包还是生产包。

Webpack 从源文件生成输出或包的过程称为编译。编译是 Webpack 5 组装信息(包括资产、文件和文件夹)的过程。配置的主题涉及 Webpack 中可能的各种选项和配置,这些选项和配置将改变编译的样式和方法。

开发包允许一些定制(例如本地测试),但生产包是成品和完全压缩的版本,准备发布。

在本章中,资产已经手动添加到了HTML文件中。随着项目的发展,手动处理将变得困难,特别是在使用多个包时。也就是说,存在一些插件可以使这个过程变得更容易。

现在我们将讨论这些选项,但首先要准备您现在非常繁忙的项目结构,这将成为项目发展中越来越重要的实践。

输出管理教程准备

首先,让我们稍微调整一下项目文件结构树,使事情变得更容易。这个过程遵循以下步骤:

  1. 首先,找到项目文件夹中的print.js文件,如下所示:
  webpack5-demo
  |- package.json
  |- webpack.config.js
  |- /dist
  |- /src
    |- index.js |- print.js  |- /node_modules

注意我们项目结构的添加——特别是print.js文件。

  1. 通过向src/print.js文件添加一些逻辑来追加代码,如下所示:
export default function printIt() {
  console.log('This is called from print.js!');
}

您应该在src/index.js文件中使用printIt()JavaScript 函数,就像前面的代码块中所示。

  1. 准备src/index.js文件,以导入所需的外部文件,并在其中编写一个简单的函数以允许交互,如下所示:
  import _ from 'lodash';
  import printMe from './print.js';

  function component() {
    const element = document.createElement('div');
    const btn = document.createElement('button');

    element.innerHTML = _.join(['Hello', 'Webpack'], ' ');

    btn.innerHTML = 'Click here then check the console!';
    btn.onclick = printIt();

    element.appendChild(btn);

    return element;
  }

  document.body.appendChild(component());

我们已经更新了我们的index.js文件,在顶部导入了print.js文件,并在底部添加了一个新的printIt();函数按钮。

  1. 我们必须更新dist/index.html文件。这次更新是为了准备拆分条目,并在下面的代码块中进行了说明:
  <!doctype html>
  <html>
    <head>
      <title>Output Management</title>
      <script src="img/print.bundle.js"></script>
    </head>
    <body>
      <script src="img/app.bundle.js"></script>
    </body>
  </html>

前面的HTML脚本将加载print.bundle.js文件,以及下面的bundle.jsapp.bundle.js文件。

  1. 接下来,确保项目的配置符合动态入口点。src/print.js文件将被添加为新的入口点。输出也将被更改,以便根据入口点名称动态生成包的名称。在webpack.config.js中,由于这个自动过程,不需要更改目录名称。下面的代码块显示了webpack.config.js的内容:
  const path = require('path');

  module.exports = {
    entry: './src/index.js',
    entry: {
      app: './src/index.js',
      print: './src/print.js'
    },
    output: {
      filename: 'bundle.js',
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };

配置简单地为我们正在工作的新文件index.jsprint.js设置了新的入口点。

  1. 确保您执行了构建。一旦您运行了npm构建,您将会看到以下内容:
...
Asset           Size      Chunks                  Chunk Names
app.bundle.js   545 kB    0, 1  [emitted]  [big]  app
print.bundle.js  2.74 kB  1     [emitted]         print
...

在浏览器中打开index.html文件后,您会看到 Webpack 生成了print.bundle.jsapp.bundle.js文件。我们现在应该检查它是否工作了!如果更改了入口点名称或添加了新的入口点,index HTML仍然会引用旧的名称。这可以通过HtmlWebpackPlugin来纠正。

设置 HtmlWebpackPlugin

HtmlWebpackPlugin将允许 Webpack 处理包含 JavaScript 的 HTML 文件。要开始使用它,我们需要使用命令行安装它,然后正确设置配置,如下所示:

  1. 首先,使用命令行实用程序安装插件,然后调整webpack.config.js文件,如下所示:
npm install --save-dev html-webpack-plugin

前面的代码块显示了在我们的项目中使用HtmlWebpackPlugin的安装。

  1. 接下来,我们需要将插件合并到我们的配置中。让我们看一下与该插件相关联的webpack.config.js文件,如下所示:
  const path = require('path');
 const HtmlWebpackPlugin = require('html-webpack-plugin');

  module.exports = {
    entry: {
      app: './src/index.js',
      print: './src/print.js'
    },
    plugins: [
 new HtmlWebpackPlugin({
        title: 'Output Management'
      })
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };

注意require表达式和plugins:选项键的使用,这两者都允许使用插件。

在运行构建之前,请注意HtmlWebpackPlugin将默认生成它的index.html文件,即使dist/文件夹中已经有一个。因此,现有文件将被覆盖。

为了最佳实践,复制现有的索引文件并将其命名为index2.html。将这个新文件放在原文件旁边,然后运行构建。

  1. 现在,使用命令行实用程序运行构建。一旦完成,你将在命令行实用程序窗口中看到以下结果,表明成功捆绑:
...
           Asset       Size  Chunks                    Chunk Names
 print.bundle.js     544 kB       0  [emitted]  [big]  print
   app.bundle.js    2.81 kB       1  [emitted]         app
      index.html  249 bytes          [emitted]
...

打开代码编辑器或记事本中的index.html文件将会显示插件已经创建了一个新文件,并且所有的捆绑包都被自动添加了。

另外,为什么不看一下html-webpack-template,它在默认模板的基础上提供了一些额外的功能呢?

这就结束了我们对 Webpack 的HtmlWebpackPlugin的教程。在接下来的小节中,我们将再次开始整理你的项目目录。

清理分发目录

在项目开发过程中,/dist文件夹会变得相当混乱。良好的做法涉及良好的组织,这包括在每次构建之前清理/dist文件夹。有一个clean-webpack-plugin插件可以帮助你做到这一点,如下所示:

  1. 首先安装clean-webpack-plugin。以下示例向你展示如何做到这一点:
npm install --save-dev clean-webpack-plugin 

插件安装完成后,我们可以重新进入配置文件。

  1. 使用webpack.config.js,在文件中进行以下条目:
  const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');
 const CleanWebpackPlugin = require('clean-webpack-plugin');

  module.exports = {
    entry: {
      app: './src/index.js',
      print: './src/print.js'
    },
    plugins: [
 new CleanWebpackPlugin(),
      new HtmlWebpackPlugin({
        title: 'Output Management'
      })
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };

注意使用CleanWebpackPlugin,继续使用const限定符。这将是module.export插件选项的添加,它将创建一个与插件相关联的新函数,并使插件在 Webpack 编译期间可用。

  1. 现在你应该运行一个npm构建,这将会将一个捆绑包输出到/dist分发文件夹中。

运行npm构建后,可以检查/dist文件夹。假设过程正常,你应该只看到新生成的文件,不再有旧文件。

我们已经生成了很多文件,为了帮助我们跟踪,有一个叫做清单的东西,我们接下来会介绍。

利用清单

Webpack 可以通过清单知道哪些文件正在生成。这使得软件能够跟踪所有输出捆绑包并映射模块。为了以其他方式管理输出,利用清单是个好主意。

Webpack 基本上将代码分为三种类型:开发人员编写的源代码;第三方编写的供应商代码;以及 Webpack 的运行时清单,它负责所有模块的交互。

运行时和清单数据是 Webpack 在浏览器中运行时连接你的模块化应用程序所需的。

如果你决定通过使用浏览器缓存来提高性能,这个过程将成为一个重要的事情。

通过在捆绑文件名中使用内容哈希,你可以告诉浏览器文件内容已经改变,从而使缓存失效。这是由运行时和清单的注入引起的,它们会随着每次构建而改变。

Webpack 有WebpackManifestPlugin,可以将清单数据提取到一个JSON文件中。

现在你已经了解了动态向你的 HTML 中添加捆绑包,让我们深入开发指南。或者,如果你想深入更高级的主题,我们建议重新阅读上一章节第二章中的代码拆分部分。

探索 Webpack 5 的选项

选项是可以使用 CLI 进行更改的一组变量。另一方面,通过更改文件内容来进行配置。但是可以使用配置文件来调整选项的设置。以下是当前由 Webpack 5 支持的选项列表:

  • 异步模块定义AMD

  • Bail

  • Cache

  • Loader

  • Parallelism

  • Profile

  • Records Path

  • Records Input Path

  • 记录输出路径

  • Name

以下各节将更详细地描述和说明每个选项。我们将从一些东西开始,经过对 Webpack 配置的粗略检查后,可能会让你感到困惑:AMD 选项。

AMD

AMD是一个object bool: false选项。它也是异步模块定义的缩写。本质上,它是为开发人员提供模块化 JavaScript 解决方案的格式。该格式本身是一个用于定义模块的提案,其中模块和依赖项都可以异步加载。

这允许您设置require.amddefine.amd的值。将amd设置为false将禁用AMD支持。

查看webpack.config.js文件,如下所示:

module.exports = {
  amd: {
    jQuery: true
  }
};

AMD的流行模块,例如 jQuery 版本 1.7.0 至 1.9.1,只有在加载程序指示允许在同一页上使用多个版本时才会注册为AMD模块。另一个类似的选项,就布尔变量而言,是Bail。让我们仔细看一下。

Bail

Bail是一个bool值。这将强制 Webpack 退出其捆绑过程。这将导致 Webpack 在第一个错误上失败,而不是容忍它。默认情况下,Webpack 将在终端(使用HMR时也在浏览器控制台)中以红色文本记录这些错误,但将继续捆绑。

要启用此选项,请打开webpack.config.js,如下所示:

module.exports = {
  bail: true
};

如果您希望 Webpack 在某些情况下退出捆绑过程,这将非常有帮助。也许您只想要项目的一部分捆绑。这完全取决于您。接下来是缓存。

Cache

缓存是一个指代bool对象的术语。它将缓存生成的 Webpack 模块和块;这提高了构建的速度。它通过在编译器调用之间保持对此对象的引用来实现。在观察模式和开发模式下,默认情况下启用缓存。

在观察模式下,在初始构建之后,Webpack 将继续监视任何已处理文件的更改。基本上,Webpack 配置 JavaScript文件应该在module.export运算符内包含watch: true操作数。

要启用缓存,可以在 webpack.config.js 中手动将其设置为true,如下例所示:

module.exports = {
  cache: false
};

webpack.config.js文件显示了允许共享缓存所需的配置,如下所示:

let SharedCache = {};

module.exports = {
  cache: SharedCache
};

前两个示例显示了缓存配置设置为falsesharedCache。这是 Webpack 中可以设置的两个布尔值。

警告:缓存不应在不同选项的调用之间共享。

Webpack 中还有一些可以设置的选项:Loader、Parallelism、Profile、Records Path、Records Input Path、Records Output Path 和 Name。让我们逐个进行解释,现在就开始吧。

Loader

这被表示为loader,并在以下代码块中展示了在 loader 上下文中公开自定义值:

use: [
    {
       loader: worker-loader
    }
]

您可以从前面的代码示例中看到如何在配置文件中使用此选项。这个示例对于遵循本指南并配置加载程序的任何人来说应该很熟悉。此示例仅使用worker-loader作为示例。其中一些选项是布尔值或二进制值,例如接下来要描述的profile选项。

Profile

profile选项将捕获应用程序的配置文件,然后可以使用Analyze工具进行分析,如下面的代码片段所示:

profile: true,

请注意,这是一个布尔值。您可以使用StatsPlugin来更好地控制配置文件。这也可以与parallelism选项结合使用以获得更好的结果。

并行性

并行性将限制并行处理模块的数量。这可以用于微调性能或更可靠的分析。以下示例将限制数字为1,但您可以根据需要更改:

parallelism: 1

Webpack 5 允许并行处理模块以及并行捆绑。这可能会占用内存,因此应该在较大的项目上注意此选项。

随着项目变得更加复杂,您可能希望记录编译过程,这可以帮助跟踪错误和错误。Records Path将帮助您做到这一点,我们现在将更仔细地看一下。

Records Path

Records Path 选项表示为字符串。应该使用此选项来生成包含记录的JSON文件。这些是用于在多个构建之间存储模块标识符的数据片段。这可以用于跟踪模块在构建之间的变化。要生成一个,只需指定一个位置,如下面的示例中使用webpack.config.js文件:

module.exports = {
  recordsPath: path.join(__dirname, 'records.json')
};

如果您有一个使用代码拆分的复杂项目,记录非常有用。这些记录的数据可用于确保在处理拆分包时缓存的行为是否正确。

尽管编译器生成此文件,但应使用源代码控制来跟踪它,并随时间保留其使用历史记录。

设置recordsPath也会将recordsInputPathrecordsOutputPath设置为相同的位置。

记录输入路径

此选项表示为字符串recordsInputPath,如下面的代码块所示:

module.exports = { 
recordsInputPath: path.join(__dirname, 'records.json'), 
};

它将指定从中读取最后一组记录的文件,并可用于重命名记录文件。相关的是 Records Output Path 选项,我们现在将对其进行讨论。

记录输出路径

Records Output Path 是一个字符串,用于指定记录应写入的位置。以下代码示例显示了如何在重命名记录文件时结合使用此选项与recordsInputPath。我们将使用webpack.config.js来完成这个操作:

module.exports = {
  recordsInputPath: path.join(__dirname, 'records.json'),
  recordsOutputPath: path.join(__dirname, 'outRecords.json')
};

上述代码将设置记录写入的位置。如果是输入记录,它将被写入__dirname/records.json。如果是输出记录,它将被写入__dirname/newRecords.json

我们需要讨论的下一个选项是 Name 选项。

名称

Name选项表示为字符串,表示配置的名称。在加载多个配置时应使用它。以下示例显示了应该成为webpack.config.js文件的一部分的代码:

module.exports = {
  name: 'admin-app'
};

在使用多个配置文件时,上述代码非常有用。该代码将将此配置文件命名为admin-app。这为您提供了一份选项的详细清单以及如何使用它们。现在让我们回顾一下本章中涵盖的内容。

总结

本章遵循了配置文件、资产管理和选项的实践。本章开始时带领读者了解了 Webpack 和配置的各种功能,并探讨了如何管理这些资产并相应地控制内容。您被引导了解了输入和输出管理,以及加载外部内容,如字体和图像。从那里,本章带领我们了解了选项和两者之间的区别,并向读者解释了可以通过简单配置实现的选项可以实现的内容。

然后,您将通过常见的选项方法以及如何使用它们进行了指导。您现在已经完全了解了选项和配置。您现在应该知道两者之间的区别以及采用的最佳方法,无论可能需要哪种技术。

在下一章中,我们将深入研究 API 的加载器和插件世界。Webpack 的这些功能阐述了平台的能力,从配置和选项中跳跃出来。

您将了解加载器和插件之间的区别,以及加载器使用默认不支持的语言和脚本的基本性质。许多这些加载器是由第三方开发人员提供的,因此插件填补了加载器无法使用的功能差距,反之亦然。

然后将扩展 API 的类似主题。API 基本上用于将应用程序连接到网络上的远程应用程序。这使它们具有与加载器类似的特征,并且它们经常用于本机脚本不可用的地方。

问题

为了帮助你学习,这里有一组关于本章涵盖的主题的问题(你会在本指南的后面找到答案):

  1. Webpack 5 中配置和选项的区别是什么?

  2. 配置标志是什么?

  3. 加载图像到 Webpack 项目需要哪个加载器?

  4. Webpack 允许导入哪种类型的数据文件而不使用加载器?

  5. Webpack 的清单记录表示什么?

  6. Bail 选项是做什么的?

  7. 并行选项是做什么的?

  8. Records Input Path 选项是做什么的?

  9. 将 AMD 设置为false会做什么?

  10. 什么是编译?

第四章:API、插件和加载器

应用程序编程接口API)通常用于远程站点程序之间的接口,例如当公司通过移动应用程序部分访问其网站功能作为集成系统的一部分时。

Webpack 旨在编译和优化本地化代码,因此了解本地化代码和外部 API 之间的区别对于操作软件至关重要。

插件和加载器类似。加载器基本上指示 Webpack 如何处理与更不寻常的编程语言和捆绑相关的特定任务。加载器通常由用户社区开发,而不是内部 Webpack 开发人员开发。另一方面,插件提供了一些加载器目前不提供的过程,因此在其操作上比加载器更通用。本章将在课程中提供每个功能的简明解释和详细示例。

Webpack 5 提供了丰富的插件接口。Webpack 中的大多数功能都使用这个插件接口,使得 Webpack 非常灵活。

本章将探讨插件、加载器和 API,以及每个的重要性以及每个功能在 Webpack 操作中的作用。

本章讨论的主题如下:

  • 加载器

  • API

  • 插件

加载器

加载器对于 Webpack 是基础的,其中许多加载器使得更多功能成为可能,特别是对于不是原生 ECMAScript 的脚本和框架,比如 JavaScript 和 JSON。

本章旨在为您提供可用加载器的广泛概述,以及您可能需要购买的一些加载器。当处理与您的项目特定的显著或独特的代码时,您应该搜索 Webpack 在线注册表,以确保代码可以被转译。

特别是,本节将讨论以下加载器:

  • cache-loader

  • coffee-loader

  • coffee-redux-loader

  • worker-loader

  • cover.js

  • i18n-loader

  • imports-loader

  • polymer-webpack-loader

  • script-loader

  • source-map-loader

  • less-loader

我们将讨论并举例说明每个加载器,如果适用的话,尽管有些可能不需要真正的详细说明。让我们从 cache-loader 开始。

cache-loader

缓存是我们在上一章中提到的内容。cache-loader 允许从加载器创建缓存。我们可以按照以下方式设置它:

  1. 首先通过命令行界面CLI)安装加载器,如下所示:
npm install --save-dev cache-loader

请注意,我们的配置中执行了其他加载器(请参见以下代码),并且将由以 cache-loader 开头的任何加载器生成的任何输出进行缓存。默认情况下,这将在项目文件夹中进行,但也可以配置为在数据库中进行。

  1. 要配置此内容,请使用 webpack.config.js
module.exports = {
 module: {
  rules: [
  {
    test: /\.ext$/,
    use: ['cache-loader', 'babel-loader'],
    include: path.resolve('src'),
   },
  ],
 },
};

请注意,cache-loader 放置在配置中的位置应该始终放在其他加载器之前。

这解释了安装和使用 cache-loader。我们将以相同的方式介绍其他几个加载器,从 worker-loader 开始。虽然它总是与其他加载器以链式或序列的方式使用,但由于它需要首先声明,因此我们应该首先讨论它。

worker-loader

worker-loader 本质上为开发人员提供了一个解决方案,用于在后台处理大型计算任务。要使加载器运行起来,我们将采取以下步骤:

  1. 首先,使用命令行安装 worker-loader
npm install worker-loader --save-dev

另外,还有一种内联方式从 App.js 文件导入 worker-loader。在任何 Webpack 项目目录中的这个文件中,如果还没有进行以下修改,可以进行以下修改:

import Worker from 'worker-loader!./Worker.js';
  1. 一旦导入了加载器,就使用 webpack.config.js 进行配置:
{
 module: {
  rules: [
  {
   test: /\.worker\.js$/,
   use: { 
      loader: 'worker-loader' 
      }
    }
   ]
  }
 }

use 指的是允许访问加载器所需的配置。

  1. App.js 中编写以下代码,以允许该文件成为加载器的导出位置:
import Worker from './file.worker.js';
const worker = new Worker();
worker.postMessage({ a: 1 });
worker.onmessage = function (event) {};
worker.addEventListener("message", function (event) {});

前面的代码还添加了一个event监听器,以便以后在开发控制台中进行测试。

  1. 最后,通过您喜欢的方法运行 Webpack 以查看结果:
npm run build

如果您选择了该方法,您应该看到worker-loader已安装并从App.js导入。这可以在控制台窗口中观察到,也可以通过查看页面源代码来观察到。

这为您提供了两种使用worker-loader的选择,可以通过命令行实用程序或通过App.js文件的配置。接下来,我们将讨论coffee-loader

coffee-loader

CoffeeScript 是 JavaScript 的简化形式,但它并不完全是 JavaScript,因此必须使用加载器才能在 Webpack 中使用它。

让我们按照以下步骤来使用coffee-loader

  1. 首先安装coffee-loader。要安装加载器,请使用以下命令行:
npm install --save-dev coffee-loader
  1. 确保您正在使用推荐的加载器配置进行测试,并使用literate键。为了进行测试,加载coffee-loader并将literate键设置为trueliterate键将确保加载器的使用由编译器解释:
module.exports = {
    module: {
      rules: [
       {
          test: /\.coffee.md$/,
          use: [{
          loader: 'coffee-loader',
          options: {
            literate: true
         }
      }]
    }]
  }
}

上述示例中的代码显示了如何使用加载器以及设置新规则。

  1. 如果需要,我们将向您展示如何安装coffee-reduxRedux是用于管理 JavaScript 应用程序状态的开源库。它经常与ReactAngular等库一起使用。要安装它,请键入以下命令:
npm i -D coffee-redux-loader

上述示例不仅将帮助您了解在包中安装和使用 CoffeeScript 的过程,还将帮助您了解这里未提及的加载器的工作原理,因为它们的工作方式基本相同。

但是,您会看到,已经使用了快捷安装和开发模式来设置命令行—i-D。在大多数情况下,这是有效的,尽管您可能会发现,在您的命令行实用程序与您使用的 Webpack 版本之间存在兼容性问题时,现在会有响应。

以这种方式做事可以节省您的时间,但是如果有疑问,请使用本指南中演示的冗长命令行约定。

现在,让我们转向coverjs,它的工作方式略有不同。

coverjs

coverjs允许对您的代码进行仪器化。这基本上意味着它允许对您的代码进行性能测量或监视。

coverjs加载器不需要与mocha-loader结合使用,因为它是独立的。reportHtml函数将附加到输出的主体部分。

在以下示例中,webpackOptions.js是代码的主题。在第一组花括号({)中是与模块导出程序相关的选项。在双花括号([{)中是绑定coverjs加载器和test:""语句的代码(表示每个文件都将被测试):

webpack - dev - server "mocha!./cover-my-client-tests.js"--options webpackOptions.js
// webpackOptions.js
module.exports = {
    output: "bundle.js",
    publicPrefix: "/",
    debug: true,
    includeFilenames: true,
    watch: true,
    postLoaders: [{
       test: "",
       exclude: [
         "node_modules.chai",
         "node_modules.coverjs-loader",
         "node_modules.webpack.buildin"
    ],
    loader: "coverjs-loader"
 }]
}
// cover-my-client-tests.js
require("./my-client-tests");
after(function() {
   require("cover-loader").reportHtml();
});

正如您所看到的,这个特定的加载器通过本地文件设置了它的选项。对此的修改将产生与上一章讨论的配置相同的效果。这应该是您在应用程序中启动coverjs所需的全部内容。接下来是一个更复杂的主题,涉及使用国际语言。

i18n-loader

i18n-loader处理国际化(i18n),这是准备应用程序以支持本地语言和文化设置的过程。让我们通过以下步骤进行设置:

  1. 从命令行安装开始:
npm install i18n-loader
  1. 现在,让我们从使用 CSS 开始。我们将为i18n设置我们的样式表。这是通过我们通常的项目样式表css/styles.css完成的。如果您导入另一个样式表,也可以在那里进行修改:
. / colors.json {
        "red": "red",
        "green": "green",
        "blue": "blue"
    }
    . / de - de.colors.json {
        "red": "rot",
        "green": "green"
    }

假设我们的区域设置是de-de-berlin(德语语言和区域设置,以此为例),现在可以调用加载器。

  1. 接下来,我们将通过使用index.js文件为i18n设置本地化代码的颜色方案:
var locale = require("i18n!./colors.json");

现在,等待状态准备就绪。这只需要一次,因为同一语言的所有区域设置都会合并成一个模块块:

locale(function() {
 console.log(locale.red); // prints red
 console.log(locale.blue); // prints blue
});

通常这都是在与之前相同的文件中完成的。前面的代码将在console.log函数中的locale变量中添加一个子节点,这将有助于测试。

  1. 现在,使用webpack.config.js配置加载器,并利用相关的可用选项。由于该加载器也有选项,如果你想要一次加载所有区域设置并且想要同步使用它们,你应该告诉加载器所有的区域设置:
{
    "i18n": {
        "locales": [
            "de",
            "de-de",
            "fr"
        ],
        // "bundleTogether": false
    }
}

请注意前面代码中的// "bundleTogether": false语句,这可以取消注释并设置为禁用区域设置的捆绑。

还有其他的调用方式。以下代码通过区域设置选择正确的文件:

require("i18n/choose!./file.js"); 

但是,这不会合并对象。在下面的代码中,第一行将连接所有符合条件的区域设置,第二行将合并生成的对象:

require("i18n/concat!./file.js"); 
require("i18n/merge!./file.js"); 

因此,在前面的代码块中,编译时会执行./file.js

require("i18n!./file.json") ==
   require("i18n/merge!json!./file.json")

前面的代码块只是加强了正则表达式。它确保require语句中的任何一个都会加载相同的文件。

如果你想在 Node.js 中使用require,不要忘记进行 polyfill。请参阅本章末尾的进一步阅读部分,了解相关的 Webpack 文档。

前面的代码块只是调整了你的项目,使其与德语领土和德语听众兼容。接下来是imports-loader

imports-loader

imports-loader允许你使用依赖于特定全局变量的模块。这对于可能依赖于全局变量的第三方模块很有用。imports-loader可以添加必要的require调用,使它们能够与 Webpack 一起工作。让我们通过以下步骤进行设置:

  1. 要在命令行中安装加载器,请使用以下语句:
npm install imports-loader

假设你有example.js文件,这个加载器允许你使用 jQuery 将导入的脚本附加到图像标签中,如下所示:

$("img").doSomeAwesomeJqueryPluginStuff();
  1. 然后可以通过配置imports-loader$变量注入到模块中,如下所示:
require("imports-loader?$=jquery!./example.js");

这将简单地将var $ = require("jquery");添加到example.js的开头。例如,如果你正在优化代码以在本地运行库,这可能会很有用。

类似地,使用polymer-loader可以优化代码或自动化流程以允许转换。这是我们讨论的下一个主题。

polymer-loader

polymer-loader用于将 HTML 文件转换为 JavaScript 文件。要配置加载器,请在webpack.config.js中使用以下代码:

{
  test: /\.html$/,
  include: Condition(s) (optional),
  exclude: Condition(s) (optional),
  options: {
    ignoreLinks: Condition(s) (optional),
    ignorePathReWrite: Condition(s) (optional),
    processStyleLinks: Boolean (optional),
    htmlLoader: Object (optional)
  },
  loader: 'polymer-webpack-loader'
},

polymer-webpack-loader短语允许开发人员在单个文档中编写 HTML、CSS 和 JavaScript 代码作为聚合元素,例如,同时仍然能够使用完整的 Webpack 系统,包括模块捆绑和代码拆分。

script-loader

script-loader基本上允许 JavaScript 在单个实例中加载。这适用于整个项目。让我们通过以下步骤进行设置:

  1. 要安装script-loader,在命令行中输入以下内容:
npm install --save-dev script-loader

请注意这在 Node.js 中不起作用。

  1. 使用webpack.config.js配置 Webpack,将exec'script.exec.js';导出:
module.exports = {
    module: {
        rules: [{
            test: /\.exec\.js$/,
            use: ['script-loader']
        }]
    }
}

还有一种内联的方法,如下所示:

import exec from 'script-loader!./script.js';

这应该是你在 Webpack 应用程序中使用script-loader所需要的全部内容。接下来是source-map-loader

source-map-loader

source-map-loader从项目中的所有 JavaScript 入口中提取现有的源映射。这包括内联源映射以及外部加载的源映射。所有源映射数据都按照你在webpack.config.js中使用devtool选项指定的源映射样式进行处理。以下代码显示了该配置:

module.exports = {
     module: {
     rules: [
     {
       test: /\.script\.js$/,
       use: [
   {
     loader: 'script-loader',
     options: {
           sourceMap: true,
              },
   },
        ]
    }
         ]
    }
 }

当使用具有源映射的第三方库时,此加载器可能非常有用。如果不将其提取并处理为包的源映射,浏览器可能会错误地解释源映射数据。此加载器允许在库和框架之间维护源映射数据的连续性,以确保轻松调试。

该加载器将从任何 JavaScript 文件中提取,包括node_modules目录中的文件。在设置includeexclude规则条件时,应注意优化捆绑性能。

less-loader

less-loader加载LESS(一种CSS类型)脚本。你应该以通常的方式使用命令行安装它,例如npm i less-loader。对于未经培训的人来说,LESS 是 CSS 的一种更简洁的语法形式,对于向后兼容性非常有用。

你应该将less-loadercss-loaderstyle-loader链接起来,以立即将所有样式应用到文档中。要配置此项,请使用以下示例代码,使用webpack.config.js文件:

module.exports = {
    module: {
        rules: [{
            test: /\.less$/,
            use: [{
                    loader: 'style-loader', // creates style nodes 
                                               from JS strings

                },
                {
                    loader: 'css-loader', // translates CSS 
                                              into  CommonJS
                },
                {
                    loader: 'less-loader', // compiles Less to CSS
                },
            ],
        }, 
      ],
    },
 };

你可以通过加载器选项将任何 LESS 特定选项传递给less-loader。由于这些选项作为程序的一部分传递给 LESS,它们需要以camelCase形式传递;以下示例在webpack.config.js中展示了如何做到这一点:

module.exports = {
    module: {
        rules: [{
            test: /\.less$/,
            use: [{
                    loader: 'style-loader',
                },
                {
                    loader: 'css-loader',
                },
                {
                    loader: 'less-loader',
                    options: {
                        strictMath: true,
                        noIeCompat: true,
                    },
                },
            ],
         },
       ],
    },
 };

请注意,LESS 不会将所有选项单独映射到camelCase。建议你检查相关的可执行文件,并搜索dash-case选项。

在生产模式下,通常建议使用MiniCssExtractPlugin将样式表提取到专用文件中。这样,你的样式就不会依赖于 JavaScript(稍后会详细介绍)。

在本节中,我们深入讨论了加载器,并对一些更有用的加载器进行了详细的检查。大多数加载器都遵循相同的安装和配置逻辑,并由 Webpack 社区构建,而不是由 Webpack 自己构建。我们将在本书的最后一章中更详细地讨论自定义加载器。

这里有太多其他加载器要提及,但这为你提供了一个非常坚实的基础,可以以各种富有想象力的方式与它们一起工作。与加载器通常处理的非本地脚本相关的东西是使用 Webpack 的 API。我们将在下一节中进行调查。

API

API 对于充分利用 Webpack 至关重要。简单来说,API 在应用程序和网站之间进行通信时是必需的。对于像 Webpack 这样的 JavaScript 捆绑器,这包括数据库和后端连接。

在使用 JavaScript 时,有大量的 API 可用,但我们无法在这里逐一介绍它们;然而,有一些更有用的常见但也复杂的 API 在使用 Webpack 进行编程时经常被用作工具。

这些工具是特定于 Webpack 的,允许在 Webpack 中进行广泛或多功能的功能,而不仅仅是简单地访问外部代码。其中最值得注意的工具是 Babel 和 Node.js。因此,让我们以这些工具为例,并在接下来的小节中学习它们的用法,首先是 Babel。

Babel 及其加载器构建者

如果你不知道,Babel 是一个主要将 ECMAScript 2015 及其前版本代码转换为与当前和旧版浏览器或环境兼容的 JavaScript 的工具链。Babel 可以为开发者做的主要事情如下:

  • 转换语法

  • 使用@babel/polyfill填充目标环境中缺失的功能

  • 执行源代码转换,称为代码修改

Webpack 使用 Babel 接口的 API。如果你尝试使用命令行安装它,你应该会收到以下消息:

The Node.js API for babel has been moved to babel-core.

如果收到此消息,意味着你已经安装了Babel并且在 Webpack 配置文件中使用了加载器的简短表示法(这在 Webpack 2 及更高版本中已不再有效)。

webpack.config.js中,您可能会看到以下脚本:

  {
    test: /\.m?js$/,
    loader: 'babel',
  }

由于安装了 Babel,Webpack 尝试加载babel包而不是babel-loader

为了解决这个问题,应该安装npm包 Babel,因为它在 Babel 版本 6 中已被弃用。如果您使用的是 Babel 版本 6,则应该安装@babel/cli@babel/core。如果您的一个依赖项正在安装 Babel,并且您无法自行卸载它,请在webpack.config.js中使用加载器的完整名称:

  {
    test: /\.m?js$/,
    loader: 'babel-loader',
  }

到目前为止,您所遵循的示例应该为您在一般情况下使用 Babel 奠定了良好的基础,但 Babel 的一个关键用途是自定义加载器。这是本书最后一章更全面地介绍的一个主题,但我们现在将讨论 Babel 如何与自定义加载器一起工作,特别是因为您可能不使用自定义的加载器。

babel-loader公开了一个loader-builder实用程序,允许用户为它处理的每个文件添加 Babel 配置的自定义处理。

.custom短语接受一个回调函数,将使用 Babel 的加载器实例进行调用。这样工具可以确保它使用与加载器本身相同的@babel/core实例。

在您想要自定义但实际上没有文件调用.custom的情况下,您还可以使用customize选项,并将其指向导出您自定义回调函数的文件的字符串。

可能了解其工作原理的最佳方法是通过一个实际的例子;让我们在以下练习中进行一次。

这个例子的目标是演示如何使用babel-loader来构建自定义加载器。

这个例子首先使用了一个自定义的文件名。这可以是任何你想要的,但为了这个练习,我们选择的名称是./my-custom-loader.js。您可以从这个位置或任何您想要的地方导出:

  1. 首先,通过使用以下代码在./my-custom-loader.js中创建一个自定义文件:
module.exports = require("babel-loader").custom(babel => {
 function myPlugin() {
 return {
 visitor: {},
 };
 }

在上面的代码块中,我们可以看到一个require语句。这使用了babel-loader,我们需要创建自定义加载器。

  1. 现在,我们需要配置我们的项目以设置传递给加载器的传递,如下所示:
return {
    customOptions({
        opt1,
        opt2,
        ...loader
    }) {
        return {
            custom: {
                opt1,
                opt2
            },
            loader,
        };
    },

请注意,custom:指的是提取加载器可能具有的任何自定义选项。还要注意两个选项后面的loader引用。这将删除两个custom选项并将选项传回。

  1. 然后,我们传递 Babel 的PartialConfig对象,将正常配置设置为return cfg.options
config(cfg) {
    if (cfg.hasFilesystemConfig()) {
        return cfg.options;
    }

    return {
        ...cfg.options,
        plugins: [
            ...(cfg.options.plugins || []),
            testPlugin,
        ],
    };
},

在上述代码中,当testPlugin语句被执行时,我们可以看到自定义插件的包含,然后将作为一个选项可用。

  1. 现在,让我们创建占位文本来测试自定义加载器。前面的代码应该生成类似以下的内容:
result(result) {
return {
    ...result,
    code: result.code + "\n// Generated by this custom loader",
    };
    },
    };
});

这个代码块显示了自定义加载器正在生成代码。

  1. 确保您的配置是正确的。请注意,您应该始终将__dirnamecustom-loader替换为您选择的名称。在 Webpack 配置模块中,键入以下内容:
.exports = {
    module: {
        rules: [{
            loader: path.join(__dirname, 'custom-loader.js'),
        }]
    }
};
customOptions(options: Object): {
    custom: Object,
    loader: Object
}

上面的代码块向您展示了如何设置和配置customOptions

  1. 根据加载器的选项,将自定义选项从babel-loader的选项中拆分出来:
config(cfg: PartialConfig): Object
  1. 给定 Babel 的PartialConfig对象,返回应传递给babel.transformoptions对象:
result(result: Result): Result

前面两个代码块都涉及到我们构建的自定义文件的内容,在这个例子中是./my-custom-loader.js

请注意,Babel 的result对象将允许加载器对其进行额外的调整。

这应该是你需要让自定义加载器与 Babel 一起工作的全部内容。阅读本书最后一章有关编写和自定义加载器的更多信息。在 Webpack 项目中经常使用的另一个关键 API 是 Node.js API。

Node.js API

当使用自定义开发流程时,Node.js API 非常有用。这是因为所有的报告和错误处理都是手动完成的。在这种情况下,Webpack 只是处理编译过程。请注意,在使用 Node.js API 时,stats配置选项将不会产生任何效果。

当您安装 Webpack 5 时,这个 API 将被安装;如果您按顺序阅读本节,可以参考第一章。让我们通过以下步骤设置这个 API:

  1. 首先,将webpack模块包含到您的 Node.js 脚本中。这是通过webpack.config.js文件完成的:
const webpack = require('webpack');

webpack({
  // Configuration Object
}, (some, stats) => { // Stats Object
  if (some || stats.hasErrors()) {
    // Handle errors here
  }
  // Done processing
});

在上面的例子中,提供了一个回调函数webpack(),它运行编译器。代码呈现了一些条件。这只是一个例子,当然应该用您的代码替换。同样,some术语应该被替换为与您的项目相关的正确对象名称。

请注意,some对象不会包括编译错误,而只包括与 Webpack 特定的问题,如错误配置相关的问题。这些错误将使用stats.hasErrors()函数来处理。

  1. 接下来,确保正确传递编译器实例。

如果 Webpack 函数没有提供回调,它将返回一个compiler实例。compiler实例可以手动触发webpack()函数,或者确保它在构建过程中观察更改(使用.run(callback).watch(watchOptions, handler)),甚至在不需要 CLI 的情况下运行构建本身。

compiler实例允许使用子编译器,并将所有捆绑、写入和加载工作委托给注册的插件。

有一个叫做hook属性,它是compiler实例的一部分。它的目的是在编译器的生命周期中注册任何插件到任何钩子事件。您可以使用WebpackOptionsDefaulterWebpackOptions Apply工具来配置这个编译器。

在构建运行完成后,之前提到的回调函数将被执行。使用这个函数来最终记录任何错误或统计信息。

Node.js API 只支持单个编译一次。并发的观察或构建可能会破坏输出捆绑包。

使用 API 调用运行类似于使用compiler实例。

  1. 现在我们应该使用webpack.config.js运行一个编译:
const webpack = require('webpack');

const compiler = webpack({
 // Configuration Object
});

compiler.run((some, stats) => { // Stats Object
});
  1. 从这里,我们还可以触发一个watch会话。当webpack()函数检测到更改时,它将再次运行并返回一个watching实例:
watch(watchOptions, callback);
const webpack = require('webpack');

const compiler = webpack({
 // Configuration Object
});

const watching = compiler.watch({
 // Example watchOptions
 aggregateTimeout: 300,
 poll: undefined
}, (some, stats) => { // Stats Object
 // Print watch/build result here...
 console.log(stats);
});

由于文件系统的不准确性可能会触发多次构建,如果检测到更改,前面代码块中的console.log语句可能会多次触发任何单个修改。检查stats.hash可以帮助您查看文件是否已更改。

  1. 使用这种方式的watch方法将返回一个watching实例和一个.close(callback)方法。调用这个方法将结束watching会话:
watching.close(() => {
 console.log('Watching Ended.');
});

请注意,使用invalidate观察函数将允许手动使当前编译无效,而不会停止watch过程。这很有用,因为一次只允许运行一次:

watching.invalidate();

由于多个同时的编译是受限制的,Webpack 提供了一个叫做MultiCompiler的东西来加快项目的开发。它是一个模块,允许 Webpack 在单独的编译器中运行多个配置。如果您的options参数是一个数组,Webpack 将应用单独的编译器,并在所有编译器完成其过程后执行任何回调:

var webpack = require('webpack');
webpack([
 { entry: './index1.js', output: { filename: 'bundle1.js' } },
 { entry: './index2.js', output: { filename: 'bundle2.js' } }
], (some, stats) => { // Stats Object
 process.stdout.write(stats.toString() + '\n');
})

上面的代码块向您展示了如何在webpack.config.js中配置项目以允许这个过程。

正如所解释的,尝试并行运行这些编译将产生不正确的输出。如果这是意外发生的,错误管理就变得很重要。

一般来说,错误处理包括三种类型的错误——严重的 Webpack 错误(例如配置错误)、编译错误(例如缺少资源)和编译警告。

以下代码块向您展示了如何配置您的项目——在本例中,使用 webpack.config.js——来处理这些错误:

const webpack = require('webpack');

webpack({
 // Configuration Object
}, (some, stats) => {
 if (some) {
   console.error(some.stack || some);
 if (some.details) {
   console.error(some.details);
 }
 return;
 }
const info = stats.toJson();
if (stats.hasErrors()) {
  console.error(info.errors);
 }
if (stats.hasWarnings()) {
  console.warn(info.warnings);
 }
// Log results...
});

在上述代码块中,some 元素表示这些错误作为一个变量。我们可以看到各种条件将在控制台日志中注册这些错误。

我们已经为您提供了关于如何使用 Webpack 的 API 的密集速成课程,所以如果您幸存下来,现在您是编程技能的专家。干得好!

现在我们已经探讨了各种加载器和 API(包括 Babel 和 Node.js),是时候看一下本章涵盖的最后一个功能了——插件。

插件

插件的作用是做任何加载器无法做到的事情。加载器通常帮助运行不适用于 Webpack 的代码,插件也是如此;然而,加载器通常由社区构建,而插件由 Webpack 的内部开发人员构建。

插件被认为是 Webpack 的支柱。该软件是建立在与您在 Webpack 配置中使用的相同的插件系统上的。

Webpack 有丰富的插件接口,Webpack 自身的大多数功能都使用它。以下是本节将详细介绍的可用插件的项目列表。每个名称旁边都有一个插件的简要描述:

  • BabelMinifyWebpackPlugin:使用 babel-minify 进行最小化

  • CommonsChunkPlugin:提取在块之间共享的公共模块

  • ContextReplacementPlugin:覆盖 require 表达式的推断上下文

  • HotModuleReplacementPlugin:启用 热模块替换HMR)(稍后详细介绍)

  • HtmlWebpackPlugin:轻松创建用于提供捆绑包的 HTML 文件

  • LimitChunkCountPlugin:设置分块的最小/最大限制,以更好地控制该过程

  • ProgressPlugin:报告编译进度

  • ProvidePlugin:使用模块而无需使用 import/require

  • TerserPlugin:启用对项目中 Terser 版本的控制

Webpack 社区页面上有许多其他插件可用;然而,上述列表说明了更显著和有用的插件。接下来,我们将详细解释每一个。

我们不会详细介绍最后三个插件,因为它们只需要按照通常的方式安装。

每个插件的安装都遵循与加载器相同的过程:

  1. 首先,使用命令行安装插件,然后修改配置文件以引入插件,类似于以下示例:
npm install full-name-of-plugin-goes-here-and-should-be-hyphenated-and-not-camelcase --save-dev

请记住,这只是一个通用示例;您将需要添加您的插件名称。与之前使用的相同的 Webpack 项目和相同的配置文件 webpack.config.js 一样,以下配置也是如此。

  1. 现在我们应该准备我们的配置文件:
const MinifyPlugin = require("full-name-of-plugin-goes-here-and-should-be-hyphenated-not-camelcase");
module.exports = {
 entry: //...,
 output: //...,
 plugins: [
 new MinifyPlugin(minifyOpts, pluginOpts)
 ]
}

这就结束了关于插件的一般介绍。我们采取了一般方法来防止过度复杂化。现在,我们可以继续讨论 Webpack 上可用的各种插件的一些有趣方面。在下一小节中,我们将讨论以下内容:

  • BabelMinifyWebpackPlugin

  • CommonsChunkPlugin

  • ContextReplacementPlugin

  • HtmlWebpackPlugin

  • LimitChunkCountPlugin

大多数插件由 Webpack 内部开发,并填补了加载器尚不能填补的开发空白。以下是一些更有趣的插件。让我们从 BabelMinifyWebpackPlugin 开始。

BabelMinifyWebpackPlugin

在本小节中,我们将安装 BabelMinifyWebpackPlugin。该插件旨在最小化 Babel 脚本。如前所述,最小化是指删除错误或多余的代码以压缩应用程序大小。要使用 babel-loader 并将 minify 作为预设包含在内,使用 babel-minify。使用此插件的 babel-loader 将更快,并且将在较小的文件大小上运行。

Webpack 中的加载器操作单个文件,minify的预设将直接在浏览器的全局范围内执行每个文件;这是默认行为。顶层范围中的一些内容将不被优化。可以使用以下代码和minifyOptions来优化文件的topLevel范围:

mangle: {
topLevel: true
}

node_modles被排除在babel-loader的运行之外时,排除的文件不会应用缩小优化,因为没有传递给minifer

使用babel-loader时,生成的代码不经过加载器,也不被优化。

插件可以操作整个块或捆绑输出,并且可以优化整个捆绑包,可以在缩小的输出中看到一些差异;但是,在这种情况下,文件大小将会很大。

Babel 插件在使用 Webpack 时非常有用,并且可以跨多个平台使用。我们将讨论的下一个插件是CommonsChunkPlugin,它旨在与多个模块块一起使用,这是 Webpack 非常本地的功能。

CommonsChunkPlugin

CommonsChunkPlugin是一个可选功能,它创建一个称为块的单独功能。块由多个入口点之间共享的公共模块组成。

这个插件已经在 Webpack 4(Legato)中被移除了。

查看SplitChunkPlugin将更多地了解 Legato 中如何处理块。

生成的分块文件可以通过分离形成捆绑包的公共模块来最初加载一次。这将被存储在缓存中以供以后使用。

以这种方式使用缓存将允许浏览器更快地加载页面,而不是强制它加载更大的捆绑包。

在您的配置中使用以下声明来使用这个插件:

new webpack.optimize.CommonsChunkPlugin(options);

一个简短而简单的安装,但是值得介绍。接下来,让我们使用ContextReplacementPlugin

ContextReplacementPlugin

ContextReplacementPlugin是指带有扩展名的require语句,例如以下内容:

require('./locale/' + name + '.json')

当遇到这样的表达式时,插件将推断./local/的目录和一个正则表达式。如果在编译时没有包含名称,那么每个文件都将作为模块包含在捆绑包中。这个插件将允许覆盖推断的信息。

下一个要讨论的插件是HtmlWebpackPlugin

HtmlWebpackPlugin

HtmlWebpackPlugin简化了创建用于提供捆绑包的 HTML 文件。这对于包含文件名中的哈希值的捆绑包特别有用,因为每次编译都会更改。

使用这种方法时,我们有三种选择——使用带有lodash模板的模板,使用您的加载器,或者让插件生成一个 HTML 文件。模板只是一个 HTML 模板,您可以使用lodash自动加载以提高速度。加载器或插件可以生成自己的 HTML 文件。这都可以加快任何自动化流程。

当使用多个入口点时,它们将与生成的 HTML 中的<script>标签一起包含。

如果 Webpack 的输出中有任何 CSS 资产,那么这些资产将包含在生成的 HTML 的<head>元素中的<link>标签中。例如,如果使用MiniCssExtractPlugin提取 CSS。

回到处理块。下一个要看的插件是处理限制块计数的插件。

LimitChunkCountPlugin

在按需加载时使用LimitChunkCountPlugin。在编译时,您可能会注意到一些块非常小,这会产生更大的 HTTP 开销。LimitChunkCountPlugin可以通过合并块来后处理块。

通过使用大于或等于1的值来限制最大块数。使用1可以防止将额外的块添加为主块或入口点块:

[/plugins/min-chunk-size-plugin]

保持块大小在指定限制以上不是 Webpack 5 中的一个功能;在这种情况下应该使用MinChuckSizePlugin

这就结束了我们对插件的介绍以及本章的总结。插件使 Webpack 能够以各种方式工作,而 Webpack 使开发人员能够构建加载程序并填补功能问题的空白。在处理大型项目或需要自动化的复杂项目时,它们是不可或缺的。

我们关于 API 的部分向您展示了,有时我们并不总是想使用本地代码,并为您提供了一个很好的过渡到我们将在下一章中讨论的库。

摘要

本章深入介绍了加载程序及其在 Webpack 中的使用方式。加载程序对于 Webpack 来说是基础,但插件是核心和支柱。本章带您了解了这些主题的主要特点,展示了每个主题的最佳用法以及何时切换使用它们的好时机。

然后我们探讨了插件及其使用方式,包括 Babel、自定义插件和加载程序。我们还研究了 API 及其使用方式,特别是那些在 Webpack 中实现更广泛功能的 API,比如 Babel 和 Node API。

在下一章中,我们将讨论库和框架。我们对插件、API 和加载程序的研究表明,有时我们不想使用诸如库之类的远程代码,但有时我们确实需要。Webpack 通常处理本地托管的代码,但有时我们可能需要使用库。这为我们提供了一个很好的过渡到这个主题。

问题

  1. 什么是i18n加载程序?

  2. Webpack 通常使用哪种工具链来转换 ECMA 脚本?

  3. Babel 主要用于什么?

  4. 哪个加载程序允许用户添加对 Babel 配置的自定义处理?

  5. polymer-webpack-loader是做什么的?

  6. polymer-webpack-loader为开发人员提供了什么?

  7. 在使用 Node.js API 时,提供的回调函数将运行什么?

第五章:库和框架

本章将探讨应用程序如何与库和框架一起工作。许多框架和库与 Webpack 一起工作。通常,这些是 JavaScript 框架。它们正变得越来越成为编程的核心部分,了解如何将它们集成到应用程序捆绑包中可能会成为一个日益增长的需求。

与其他 Webpack 元素的工作有些不同,使用库和框架。通过典型的例子和用例,本书将探讨 Angular 以及如何构建 Angular 框架以便进行包捆绑。这包括对 Webpack 捆绑的期望,期望的结果,优势和局限性。

完成本章后,您应该能够自信地使用这些主要框架和库与 Webpack 一起使用。您还将知道如何集成和安装它们,以及如何在集成中使用最佳实践。

在本章中,我们将涵盖以下主题:

  • 最佳实践

  • 使用 JSON

  • 使用 YAML

  • 使用 Angular

  • 使用 Vue.js

最佳实践

到目前为止,我们只涵盖了构建 Vanilla JavaScript,这不应与 Vanilla 框架混淆。尽管这是学习的最佳方式,但更有可能的是您将使用某种框架。Webpack 将与任何 JavaScript 或 TypeScript 框架一起工作,包括 Ionic 和 jQuery;然而,更棘手的框架包括 Angular、Vue 和 YAML。

现在,我们将开始使用 YAML,但在深入研究之前,您可能想知道是否可以集成后端框架。简单的答案是可以,但它们不会被捆绑。然而,唯一的集成级别是通过链接源代码,就像我们在大多数项目或 API 中所做的那样,比如 REST API。

正如我们已经讨论过的,Webpack 有生产模式和开发模式。生产模式将您的项目捆绑成最终状态,准备好进行网络交付或发布,并且提供了很少的调整空间。开发模式给开发人员自由修改数据库连接的权限;这是后端集成的方式。您的项目的后端可能是ASP.NETPHP,但有些后端更复杂,使用OpenAuth。作为开发人员,您需要对所有这些有一个概览。然而,本指南只涉及 Webpack。

请放心,所有这些框架都将集成,这是通过 REST API 完成的,它以JavaScript 对象表示JSON)格式返回数据。也可以使用AJAX来完成这一点。无论如何,请确保遵循安全的最佳实践,因为与使用服务器端脚本相比,对数据库的 JSON 调用并不安全。

如果您的项目使用 Ionic,那么您应该按照 Angular 的说明进行操作,因为 Ionic 框架是基于此的。

这应该为您提供了与后端和库一起工作的最佳实践的全面概述。现在我们将讨论您在 Webpack 中会遇到的每个常见库。让我们从 JSON 开始,因为它是最容易理解的,也是外部或后端代码和数据库与您的 Webpack 应用程序交互的最重要方式。

使用 JSON

当您使用框架时,大部分时间您需要在不同语言和应用程序之间进行通信。这是通过 JSON 完成的。JSON 在这方面与 YAML 类似,但更容易理解 Webpack 如何与 JSON 一起工作。

JSON 文件可以被 Webpack 的编译器理解,无需专用加载程序,因此可以被视为 Webpack 捆绑器的本地脚本,就像 JavaScript 一样。

到目前为止,本指南已经提到,JSON 文件在包组合中起着重要作用。Webpack 记录和跟踪加载器和依赖项的使用是通过 JSON 文件的模式进行的。这通常是package.json文件,有时是package.lock.json文件,它记录了每个安装包的确切版本,以便可以重新安装。在这种情况下,“包”是指加载器和依赖项的集合。

每个 JSON 文件必须正确编程,否则 Webpack 将无法读取。与 JavaScript 不同,代码中不允许使用注释来指导用户,因此您可能希望使用README文件向用户解释其内容和目的。

JSON 文件的结构与 JavaScipt 有所不同,并包含不同的元素数组,例如键。以以下代码块为例:

module: {
    rules: [{
    test: /\.yaml$/,
    use: 'js-yaml-loader',
    }]
}

这是package.json文件的一部分,我们稍后在本章中将使用。此块的内容基本上声明了模块的参数。命名模块用作键,并在其后加上冒号。这些键有时被称为名称,它们是选项的放置位置。此代码设置了一系列规则,在这个规则中是指示 Webpack 在转译内容模块时使用js-yaml-loader

您必须确保大括号和方括号的使用顺序正确,否则 Webpack 将无法读取代码。

作为 JavaScript 开发人员,您可能对 JSON 及其用法非常熟悉。但是,为了排除任何盲点,值得详细说明。YAML 是一个更复杂的框架,但您经常会遇到它,因此它只比 JSON 逐渐更复杂。现在让我们开始逐步了解它。

使用 YAML

YAML 是一种常见的框架,类似于 JSON 的使用方式。不同之处在于 YAML 更常用于配置文件,这就是为什么在使用 Webpack 时您可能会更频繁地或者第一次遇到它。

要在 Webpack 中使用 YAML,我们必须安装它。让我们开始吧:

  1. 您可以在命令行实用程序中使用npm安装 YAML。使用以下命令:
yarn add js-yaml-loader

请注意yarn语句。这是指 JavaScript 文件的开源包管理器,预装在Node.js中。它的工作方式类似于npm,应该是预安装的。如果您在此处使用的代码没有得到响应,请再次检查是否已预安装。

  1. 要从命令行界面检查 YAML 文件,您应该全局安装它们:
npm install -g js-yaml

  1. 接下来,打开配置文件webpack.config.js,并进行以下修改:
import doc from 'js-yaml-loader!./file.yml';

前一行将返回一个 JavaScript 对象。这种方法适用于不受信任的数据。请参阅进一步阅读部分,了解 GitHub YAML 示例。

  1. 之后,使用webpack.config.js配置文件来允许使用 YAML 加载器:
module: {
    rules: [{
    test: /\.yaml$/,
    use: 'js-yaml-loader',
    }]
}

您可能还想为 Webpack 使用 YAML front-matter 加载器。这是一个 MIT 许可的软件,它将 YAML 文件转换为 JSON,如果您更习惯于使用后者,这将特别有用。如果您是 JavaScript 开发人员,您很可能习惯使用 JSON,因为它往往比 YAML 更常用于 JavaScript。

此模块需要在您的设备上安装 Node v6.9.0 和 Webpack v4.0.0 的最低版本。Webpack 5 是本指南的主题,所以不应该有问题。但是,请注意,此功能仅适用于 Webpack 4 和 5。

以下步骤与前面的步骤分开,因为它们涉及安装yaml-loader而不是yaml-frontmatter,后者用于将 YAML 转换为 JSON 文件(这更符合 Webpack 包结构的典型情况):

  1. 首先,您需要使用命令行实用程序安装yaml-frontmatter-loader
npm install yaml-frontmatter-loader --save-dev

这个特定的命令行可能在语法上与本指南过去展示的命令行不同,但无论格式如何,这个命令都应该有效。

  1. 然后,按照以下方式向配置中添加加载器:
const json = require('yaml-frontmatter-loader!./file.md');

此代码将file.md文件作为 JavaScript 对象返回。

  1. 接下来,再次打开webpack.config.js,并对rules键进行以下更改,确保引用 YAML 加载器:
module.exports = {
  module: {
    rules: [
      {
         test: /\.md$/,
         use: [ 'json-loader', 'yaml-frontmatter-loader' ]
      }
    ]
  }
}
  1. 接下来,通过你喜欢的方法运行 Webpack 5,并查看结果!

如果你一口气通过了这一点,你可能已经足够勇敢去应对 Angular 了。这是一个更难处理的框架,所以让我们开始吧。

使用 Angular

Angular 是一个库和框架,与所有框架一样,它旨在使构建应用程序更容易。Angular 利用依赖注入、集成最佳实践和端到端工具,所有这些都可以帮助解决开发中的挑战。

Angular 项目通常使用 Webpack。在撰写本文时,使用的最新版本是Angular 9。每年都会推出更新版本。

现在,让我们看看 Webpack 在捆绑 Angular 项目或将 Angular 添加到现有项目时的工作原理。

Angular 寻找window.jQuery来确定是否存在 jQuery。查看以下源代码的代码块:

new webpack.ProvidePlugin({
  'window.jQuery': 'jquery'
});

要添加lodash映射,请将现有代码附加到以下内容:

new webpack.ProvidePlugin({
  _map: ['lodash', 'map']
});

Webpack 和 Angular 通过提供入口文件并让它包含从这些入口点导出的依赖项来工作。以下示例中的入口点文件是应用程序的根文件src/main.ts。让我们来看看:

  1. 我们需要在这里使用webpack.config.js文件。请注意,这是一个单入口点过程:
entry: {
 'app': './src/main.ts'
},

Webpack 现在将解析文件,检查它,并递归地遍历其导入的依赖项。

  1. src/main.ts中进行以下更改:
import { Component } from '@angular/core';
@Component({
 selector: 'sample-app',
 templateUrl: './app.component.html',
 styleUrls: ['./app.component.css']
})
export class AppComponent { }

Webpack 将识别出正在导入@angular/core文件,因此这将被添加到捆绑包含的依赖项列表中。Webpack 将打开@angualar/core文件,并跟踪其一系列import语句,直到从中构建出一个依赖图。这将从main.ts(一个 TypeScript 文件)构建出来。

  1. 然后,将这些文件作为输出提供给在配置中标识的app.js捆绑文件:
output: {
 filename: 'app.js'
}

包含源代码和依赖项的 JavaScript 文件是一个文件,输出捆绑文件是app.js文件。稍后将在index.html文件中使用 JavaScript 标签(<script>)加载它。

建议不要为所有内容创建一个巨大的捆绑文件,这是显而易见的。因此,建议将易变的应用程序代码与更稳定的供应商代码模块分开。

  1. 通过更改配置,将应用程序和供应商代码分开,现在使用两个入口点——main.tsvendor.ts,如下所示:
entry: {
 app: 'src/app.ts',
 vendor: 'src/vendor.ts'
},
output: {
 filename: '[name].js'
}

通过构建两个单独的依赖图,Webpack 会发出两个捆绑文件。第一个称为app.js,而第二个称为vendor.js。分别包含应用程序代码和供应商依赖项。

在上面的示例中,file name: [name]表示一个占位符,Webpack 插件 app 和 vendor 将其替换为入口名称。插件将在下一章节中详细介绍,所以如果你遇到困难,也许可以标记这一页,然后再回来看看。

  1. 现在,通过添加一个仅导入第三方模块的vendor.ts文件,指示 Webpack 哪些部分属于供应商捆绑,如下所示:
import '@angular/platform-browser';
import '@angular/platform-browser-dynamic';
import '@angular/core';
import '@angular/common';
import '@angular/http';
import '@angular/router';
// RxJS
import 'rxjs';

请注意提到rxjs。这是一个用于响应式编程的库,旨在使开发人员更容易地组合异步代码或回调。

其他供应商也可以以这种方式导入,例如 jQuery、Lodash 和 Bootstrap。还可以导入的文件扩展名包括 JavaScript 文件(.js)、TypeScript 文件(.ts)、层叠样式表 文件(.css)和 Syntactically Awesome Style Sheets 文件(.sass)。

Angular 可能是一个非常复杂的框架,并且与基于 Web 的应用程序非常相关。然而,您的特定需求可能更适合单页应用程序,这种情况下,Vue.js 将是大多数人首选的选择。现在让我们来看一下它。

使用 Vue.js

Vue.js 是另一个框架,但是它是开源的。它的使用显著性,或者说明显的目的领域,是单页应用SPAs)。这是因为该框架专注于提供无缝的体验,但功能比 Angular 少,可以与许多其他语言一起工作,并且运行速度非常快。使用 Vue.js 构建相当大的应用程序会导致在使用过程中加载非常缓慢,甚至编译速度更慢。

也许理解这一点的最好方法是考虑 jQuery 如何使用内联语句调用页面中的脚本,而 Angular 使用核心模块,每个模块都设计有特定的目的。Vue.js 介于纯粹简单的 jQuery 和 Angular 之间。

使用 Webpack 与 Vue.js 项目是通过使用专用加载器来完成的。

建议安装 vue-loadervue-template-compiler,除非您是 Vue.js 模板编译器的高级用户。按照以下步骤进行:

  1. 要按照这个示例进行,首先安装 vue-loadervue-template-compiler,使用以下代码:
npm install -D *vue-loader vue-template-compiler*

模板编译器必须单独安装,以便可以指定版本。

Vue.js 每次发布新版本都会发布相应版本的模板编译器。这两个版本必须同步,以便加载器生成的代码是运行时兼容的。因此,每次升级一个版本时,应该升级另一个版本。

与 Vue.js 相关的加载器与大多数加载器的配置略有不同。在处理扩展名为 .vue 的文件时,确保将 Vue.js 加载器的插件添加到您的 Webpack 配置中。

  1. 这是通过修改 Webpack 的配置来完成的,下面的示例使用 webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
 module: {
 rules: [
 // other rules
 {
   test: /\.vue$/,
   loader: 'vue-loader'
  }
 ]
},
 plugins: [
 // make sure to include the plugin.
 new VueLoaderPlugin()
 ]
}

这个插件是必需的,因为它负责克隆已定义的文件,并将它们应用于与 .vue 文件对应的语言块。

  1. 使用 Vue.js,我们添加以下内容:
new webpack.ProvidePlugin({ Vue: ['vue/dist/vue.esm.js', 'default'] });

先前的代码必须添加,因为它包含了 ECMAScript 模块的完整安装,用于与打包工具一起使用 Vue.js。这应该存在于项目的 npm 包的 /dist 文件夹中。注意 .ems. 表示 ECMAScript 模块。Vue.js 安装页面上显示了运行时和生产特定的安装方法,这些方法可以在本章的 进一步阅读 部分找到。UMDCommonJS 的安装方法类似,分别使用 vue.jsvue.common.js 文件。

由于我们的项目将使用 .esm 格式,了解更多关于它可能会很有用。它被设计为静态分析,这允许打包工具执行摇树,即消除未使用的代码。请注意,打包工具的默认文件是 pkg.module,它负责运行时 ECMAScript 模块编译。有关更多信息,请参阅 Vue.js 安装页面——URL 可在本章的 进一步阅读 部分找到。

这就结束了关于框架和库的内容。现在,您应该已经具备了处理复杂项目的强大能力,甚至可能会使用多个框架。

总结

本章涵盖了典型的框架以及如何开始使用它们。这包括应该遵循的安装过程以及需要的标准和外围设备。本指南关注最佳实践和安全性。在开始项目时,您应该提前遵循这些示例,密切关注程序、警告和要求。

本指南为您概述了其他框架,如用于回调的 RxJS 和 jQuery,以及在使用不寻常的文件扩展名时指引您正确方向。我们还探讨了在使用 Webpack 5 时 Angular 的核心功能和 Vue.js 的用法和安装程序,以及 Vue.js 如何更适合单页面应用程序,而 Angular 在更大的项目上的工作效果更好。

在涵盖了大部分核心主题之后,下一章我们将深入探讨部署和安装。在处理数据库并确保满足安全要求时,这将变得更加重要。下一章将对这个主题进行深入探讨,并希望能解决您作为开发人员可能遇到的任何问题。

进一步阅读

问题

  1. 与正在使用的Vue.js版本对应的编译器是什么?

  2. 在使用 Angular 时,本指南建议将易变代码和稳定供应商代码分开。这是通过使用两个入口点来实现的。它们是什么?

  3. 在使用 Webpack 时,使用 YAML 的最低安装要求是什么?

  4. 为什么应该全局安装 YAML 文件?

  5. 什么是 SPA?

  6. 处理.vue文件时,应该在哪里添加 Vue.js 的加载器?

  7. 以下代码行缺少什么?

import 'angular/http';

  1. 在使用 Angular 时,如何加载app.js

  2. 什么是 YARN?

  3. 默认的pkg.module文件用于什么?

第六章:生产、集成和联合模块

在本章中,我们将涵盖生产、集成和联合模块。我们将概述正确的部署程序、快捷方式和替代方案。尽管本章的一些内容已经在更多细节上进行了讨论,但再次复习一下是很好的,这样您就可以更清楚地了解到目前为止学到的内容。

到目前为止,我们已经讨论并执行了开发构建,并暗示要进入生产,但适当的发布级生产过程有点不同,涉及交叉检查和遵循最佳实践。

本书的这一部分将探讨我们可以使用的各种选项,以部署 Webpack 5 与各种 Web 实用程序。这将为您提供这些 Web 实用程序的概述,并解释哪些对于特定情况和平台更为合适,包括使用 Babel 进行部署。

所有这些主题都与我们在生产捆绑的开头部分相关,该部分还涵盖了生产和发布目的的部署主题。

在本章中,我们将涵盖以下主题:

  • 生产设置

  • Shimming

  • 渐进式 Web 应用程序

  • 集成任务运行器

  • GitHub

  • 提取样板

  • 模块联合

生产设置

现在我们了解了基础知识,我们可以继续学习如何实际部署生产捆绑包。在开发模式和生产模式下构建项目的目标有很大的不同。在生产模式下,目标转向使用轻量级源映射对构建进行最小化,并优化资产以提高加载时间。在开发模式下,强大的源映射至关重要,以及具有localhost服务器和实时重新加载或热模块替换。由于它们的不同目的,通常建议为每种模式编写单独的 Webpack 配置。

尽管它们之间存在差异,但通用配置应该在模式之间保持一致。为了合并这些配置,可以使用一个叫做webpack-merge的实用工具。这个通用配置过程意味着代码不需要在每个模式下重复。让我们开始吧:

  1. 首先打开命令行工具,安装webpack-merge,并以开发模式保存它,如下所示:
npm install --save-dev *webpack-merge*
  1. 现在,让我们来看一下项目目录。它的内容应该结构类似于以下内容:
 webpack5-demo
  |- package.json
  |- webpack.config.js
  |- webpack.common.js
  |- webpack.dev.js
  |- webpack.prod.js
  |- /dist
  |- /src
    |- index.js
    |- math.js
  |- /node_modules

请注意,前面的输出显示了在这个特定示例中存在额外的文件。例如,我们在这里包括了webpack.common.js文件。

  1. 让我们仔细看一下webpack.common.js文件:
  const path = require('path');
  const { CleanWebpackPlugin } = require('clean-webpack-plugin');
  const HtmlWebpackPlugin = require('html-webpack-plugin');

  module.exports = {
    entry: {
      app: './src/index.js'
    },
    plugins: [
      // new CleanWebpackPlugin(['dist/*']) for < v2 versions of 
      // CleanWebpackPlugin
      new CleanWebpackPlugin(),
      new HtmlWebpackPlugin({
        title: 'Production'
      })
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };

webpack.common.js文件处理CommonJS请求。它的做法与 ECMAScript 类似,但格式不同。让我们确保在ECMA环境中工作的webpack.config.js文件与CommonJS配置文件做同样的事情。注意入口点和捆绑名称,以及title选项。后者是模式的对应项,因此您必须确保项目中的两个文件之间存在对等性。

  1. 在这里,我们来看一下webpack.dev.js文件:
  const merge = require('webpack-merge');
  const common = require('./webpack.common.js');

  module.exports = merge(common, {
    mode: 'development',
    devtool: 'inline-source-map',
    devServer: {
      contentBase: './dist'
    }
  });

正如您所看到的,前面的代码提供了关于如何在开发模式下使用webpack.common.js的说明。这只是一个交叉检查的情况,以确保您的工作内容在最终生产中格式正确,并且在编译过程中不会出现错误。

  1. 如果您正在生产模式下工作,将调用以下文件webpack.prod.js
  const merge = require('webpack-merge');
  const common = require('./webpack.common.js');

  module.exports = merge(common, {
    mode: 'production',
  });

使用webpack.common.js,设置入口和输出配置,并包括在两种环境模式下都需要的任何插件。在使用webpack.dev.js时,模式应设置为开发模式。还要在该环境中添加推荐的devtool,以及简单的devServer配置。在webpack.prod.js中,模式当然设置为生产模式,加载TerserPlugin

请注意,merge() 可以在特定环境配置中使用,以便您可以轻松地在开发和生产模式中包含通用配置。值得注意的是,在使用 **webpack-merge** 工具时还有各种高级功能可用。

  1. 让我们在 webpack.common.js 中进行这些开发配置:
  { 
   "name": "development", 
   "version": "1.0.0", 
   "description": "", 
   "main": "src/index.js",
   "scripts": { 
   "start": "webpack-dev-server --open --config webpack.dev.js", 
   "build": "webpack --config webpack.prod.js" 
  }, 
    "keywords": [], 
    "author": "", 
    "license": "ISC", 
    "devDependencies": { 
      "clean-webpack-plugin": "⁰.1.17", 
      "css-loader": "⁰.28.4", 
      "csv-loader": "².1.1", 
      "express": "⁴.15.3",
      "file-loader": "⁰.11.2", 
      "html-webpack-plugin": "².29.0", 
      "style-loader": "⁰.18.2", 
      "webpack": "⁵.0.0",
      "webpack-dev-middleware": "¹.12.0",
      "webpack-dev-server": "².9.1", 
      "webpack-merge": "⁴.1.0", 
      "xml-loader": "¹.2.1"
   } 
 }

前面的示例只是展示了 CommonJS 的完成配置。请注意通过 devDependancies 选项加载的插件依赖项及其版本列表。

  1. 现在,运行这些脚本,看看输出如何变化。

  2. 以下代码显示了如何继续添加到生产配置:

 document.body.appendChild(component());

请注意,当处于生产模式时,Webpack 5 将默认压缩项目的代码。

TerserPlugin 是开始压缩的好地方,应该作为默认选项使用。然而,还有一些选择,比如 BabelMinifyPluginclosureWebpackPlugin

尝试不同的压缩插件时,请确保选择也会删除任何死代码,类似于我们在本书中早些时候描述的摇树。与摇树相关的是 shimming,我们将在下面讨论。

Shimming

Shimming,或者更具体地说,shim-loaders。现在是深入探讨这个概念的好时机,因为在您继续之前,您需要牢牢掌握它。

Webpack 使用的编译器可以理解用 ECMAScript 2015CommonJSAMD 编写的模块。值得注意的是,一些第三方库可能需要全局依赖,例如使用 jQuery 时。在这种情况下,这些库可能需要导出全局变量。模块的这种几乎破碎的特性是 Shimming 发挥作用的地方。

Shimming 可以让我们将一种语言规范转换为另一种。在 Webpack 中,这通常是通过专门针对您的环境的加载器来完成的。

Webpack 的主要概念是模块化开发的使用 - 孤立的模块,安全地包含,不依赖于隐藏或全局依赖关系 - 因此重要的是要注意,使用这样的依赖关系应该是稀少的。

当您希望对浏览器进行 polyfill 以支持多个用户时,Shimming 可以派上用场。在这种情况下,polyfill 只需要在需要的地方进行修补并按需加载。

Shimming 与修补有关,但往往发生在浏览器中,这使得它与渐进式网络应用程序高度相关。

在下一节中,我们将更详细地了解渐进式网络应用程序 - 它们对于 Webpack 5 非常重要。

渐进式网络应用

有时被称为 PWA,渐进式网络应用程序在线提供原生应用程序体验。它们有许多 contributing 因素,其中最显著的是应用程序在脱机和在线时都能正常运行的能力。为此,使用了服务工作者。

使您的网络应用程序能够脱机工作将意味着您可以提供诸如推送通知之类的功能。这些丰富的体验也将通过诸如服务工作者之类的设备提供给基于网络的应用程序。此脚本将在浏览器的后台运行,无论用户是否在正确的页面上,都会允许这些相同的通知甚至后台同步。

PWA 提供了网络的覆盖范围,但速度快、可靠,类似于桌面应用程序。它们也可以像移动应用一样引人入胜,并且可以提供相同的沉浸式体验。这展示了应用程序的新质量水平。它们也可以在跨平台兼容性中发挥作用。

响应式设计是网络朝这个方向的第一次推动,但使互联网更普遍的推动使我们走向了 PWA。为了发挥应用程序的潜力,您应该采用渐进式的方法。有关更多信息,请参阅 Google 关于此主题的资源:developers.google.com/web/progressive-web-apps/

在使用 Webpack 时,需要注册服务工作者,以便您可以开始将桌面功能集成到基于 Web 的 PWA 中。PWA 并不是为用户本地安装而设计的;它们通过 Web 浏览器本地工作。

通过将以下内容添加到您的代码中,可以注册服务工作者 - 在本例中,这是一个index.js文件:

  if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-
      worker.js').then(registration => {
       console.log('SW registered: ', registration);
    }).catch(registrationError => {
     console.log('SW registration failed: ', registrationError);
    });
  });
 }

完成后,运行npm build - 您应该在命令行窗口中看到以下输出:

SW registered

现在,应用程序可以在命令行界面中使用npm start来提供服务。

PWA 应该有一个清单、一个服务工作者,可能还有一个 workbox 来包装和保护服务工作者。有关清单的更多信息,请参见第三章,使用配置和选项。Workbox 是一个可以使用以下命令在命令行中安装的插件:

npm install workbox-webpack-plugin --save-dev

在假设的webpack.config.js文件中,可以在此处看到Workbox插件的示例配置:

const WorkboxPlugin = require('workbox-webpack-plugin');
new WorkboxPlugin.GenerateSW({
 clientsClaim: true,
 skipWaiting: true,
 }),

大括号{内的选项将鼓励服务工作者迅速到达该点,并且不允许它们扼杀先前的服务工作者。

随着项目变得更加复杂,您可能会考虑使用相关的任务运行器。让我们更详细地看一下这一点。

集成任务运行器

任务运行器处理自动化任务,如代码检查。Webpack 并不是为此目的而设计的 - 没有捆绑器是。然而,使用 Webpack,我们可以从任务运行器提供的高度关注度中受益,同时仍然具有高性能的捆绑。

虽然任务运行器和捆绑器之间存在一些重叠,但如果正确处理,它们可以很好地集成。在本节中,我们将探讨一些最流行的任务运行器的集成技术。

捆绑器通过准备 JavaScript 和其他前端代码以进行部署,转换其格式,使其适合于浏览器交付。例如,它允许我们将 JavaScript 拆分成块以进行延迟加载,从而提高性能。

捆绑可能是具有挑战性的,但其结果将消除许多繁琐的工作过程。

在本节中,我们将向您展示如何集成以下内容:

  • Gulp

  • Mocha

  • Karma

Gulp 可能是最知名的任务运行器,因此让我们首先从那里开始。它通过使用专用文件来利用,就像其他两个任务运行器一样。在这里,gulpfile.js文件将处理 Webpack 与 Gulp 的相互作用。让我们看看如何集成所有这些任务运行器:

  1. 首先,让我们看一下gulpfile.js文件:
const gulp = require('gulp'); 
const webpack = require('webpack-stream'); 
gulp.task('default', function() { 
   return gulp.src('src/entry.js') 
    .pipe(webpack({ 
 // Any configuration options... 
    })) 
 .pipe(gulp.dest('dist/')); 
});

这就是我们使用 Gulp 所需要做的。请注意gulp.task函数的使用,return入口点以及.pipe(**gulp**.dest('dist/'));输出位置。

  1. 以下是您可以使用的命令行代码来安装 Mocha:
npm install --save-dev *webpack mocha mocha-webpack*
*mocha-webpack* 'test/**/*.js'

有关更多信息,请访问 Webpack 社区存储库。

  1. 以下是您需要对karma.config.js文件进行的配置文件修改,以便与 Webpack 一起使用 Karma:
module.exports = function(config) {
  config.set({
    files: [
      { pattern: 'test/*_test.js', watched: false },
      { pattern: 'test/**/*_test.js', watched: false }
    ],
    preprocessors: {
      'test/*_test.js': [ 'webpack' ],
      'test/**/*_test.js': [ 'webpack' ]
    },
    webpack: {
      // Any custom webpack configuration...
    },
    webpackMiddleware: {
      // Any custom webpack-dev-middleware configuration...
    }
  });
};

webpackwebpackMiddleware选项已留空,以便您可以使用项目的特定配置填充此内容。如果您不使用它们,这些选项可以完全删除。出于本例的目的,我们不会这样做。

如果您希望在开发环境中使用它们,这些安装过程将对您有所帮助,但 GitHub 是一个越来越重要的工具。我们将在下一节中看一下它如何在开发中发挥关键作用。

GitHub

如您可能已经知道的那样,GitHub 是一个与 Webpack 配合良好的命令行代码托管平台。在使用 Webpack 时,您将与 GitHub 托管的大部分代码和项目一起工作。

GitHub 基于 Git 版本控制系统。使用 GitHub 与 Webpack 5 允许在线使用一些命令行操作。

Git 命令行指令通常在每个新条目和每个命令之前使用git命令。Webpack 的大部分内容文件都可以通过 GitHub 获得,GitHub 的 Webpack 页面可以在这里找到:github.com/webpack/webpack。Webpack 5 的开发可以在这里查看其进展阶段,这可能会很有趣,并且可以让您更好地预期其到来,如果您需要升级您的项目。其 URL 为github.com/webpack/webpack/projects/5.

作为开发人员,您可能经常使用 GitHub,但如果您是一名专注于 JavaScript 开发的开发人员,您可能经验有限。在处理 Webpack 项目时,GitHub 平台提供了大量的实时和协作机会。由于提供了版本控制和命令行功能,因此在本地执行软件开发的需求较少。这是 GitHub 在开发人员社区中如此受欢迎并成为开发人员工作证明的主要原因。

GitHub 允许开发人员共同在项目上工作。在处理捆绑项目时,这更加有用,因为一些命令行操作可以在线运行。GitHub 还允许敏捷工作流程或项目管理界面。敏捷方法允许团队通过专用数字平台进行协作,个人自组织。

在使用 GitHub 时,您可能会使用其他人的代码。这可能包括由团队开发的代码框架。即使是经验丰富的开发人员,如果他们对使用的逻辑不熟悉,这也可能变得非常困难。这就引出了样板的主题,通常是标准或有良好文档记录的代码,但尽管如此,您可能希望从项目中提取出这部分。这就是提取过程开始变得非常有用的地方。

提取样板

样板代码是需要包含在各个地方的代码部分,但几乎没有或没有进行修改。在使用被认为冗长的语言时,通常需要编写大量的代码。这一大段代码称为样板。它本质上与框架或库具有相同的目的,这些术语经常混淆或相互接受。

Webpack 社区提供了样板安装,例如安装多个常见依赖项的组合安装,如先决条件和加载程序。这些样板有多个版本,使用它们可以加快构建速度。请搜索 Webpack 社区页面(webpack.js.org/contribute/)或 Webpack 的 GitHub 页面(github.com/webpack-contrib)以获取示例。

也就是说,有时只需要部分样板。为此,可能需要提取 Webpack 的样板功能。

在使用其最小化方法时,Webpack 允许提取样板;也就是说,只有您需要的样板元素包含在捆绑包中。这是在编译过程中自动完成的过程。

缩小是 Webpack 5 提供这种提取过程的关键方式,也是这种类型的捆绑器可以使用的更显著的方式之一。还有另一个关键过程,对于 Webpack 5 来说是非常有用的,也是本地的。它带我们走向了捆绑包的不同方向,但是这是一个毫无疑问会从一个复杂或自定义的构建中跟随的过程,比如一个从模板开始提取的项目。这个过程被称为模块联邦。

模块联邦

模块联邦已被描述为 JavaScript 架构的一个改变者。它基本上允许应用程序在服务器之间运行远程存储的模块代码,同时作为捆绑应用程序的一部分。

一些开发人员可能已经了解一种称为GraphQL的技术。它基本上是一个由 Apollo 公司开发的用于在应用程序之间共享代码的解决方案。联邦模块是 Webpack 5 的一个功能,允许捆绑应用程序之间发生这种情况。

长期以来,最好的折衷方案是使用DllPlugin的外部依赖,它依赖于集中的外部依赖文件,但是这对于有机开发、便利性或大型项目来说并不理想。

使用模块联邦,JavaScript 应用程序可以在应用程序之间动态加载代码并共享依赖关系。如果一个应用程序在其构建中使用了联邦模块,但需要一个依赖项来提供联邦代码,Webpack 也可以从联邦构建的原始位置下载该依赖项。因此,联邦将有效地提供 Webpack 5 可以找到所需依赖代码的地图。

在考虑联邦时,有一些术语需要考虑:远程和主机。远程一词指的是加载到用户应用程序中的应用程序或模块,而主机指的是用户在运行时通过浏览器访问的应用程序。

联邦方法是为独立构建设计的,可以独立部署或在您自己的存储库中部署。在这种意义上,它们可以双向托管,有效地作为远程内容的主机。这意味着单个项目可能在用户的旅程中在托管方向之间切换。

构建我们的第一个联邦应用

让我们首先看一下三个独立的应用程序,分别标识为第一个、第二个和第三个应用程序。

我们系统中的第一个应用程序

让我们从配置第一个应用程序开始:

  1. 我们将在 HTML 中使用<app>容器。这个第一个应用程序是联邦系统中的远程应用程序,因此将被其他应用程序消耗。

  2. 为了暴露应用程序,我们将使用AppContainer方法:

const HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin =
   require("webpack/lib/container/ModuleFederationPlugin");

module.exports = {
  plugins: [
   new ModuleFederationPlugin({
    name: "app_one_remote",
    remotes: {
      app_two: "app_two_remote",
      app_three: "app_three_remote"
 },
 exposes: {
   'AppContainer':'./src/App'
 },
 shared: ["react", "react-dom","react-router-dom"]
 }),
 new HtmlWebpackPlugin({
   template: "./public/index.html",
   chunks: ["main"]
  })
 ]
} 

这个第一个应用程序还将消耗系统中其他两个联邦应用程序的组件。

  1. 为了允许这个应用程序消耗组件,需要指定远程范围。

  2. 所有这些步骤都应按照前面的代码块中指定的方式进行。

  3. 现在,让我们来看一下构建的 HTML 部分:

<head>
  <script src="img/app_one_remote.js"></script>
  <script src="img/app_two_remote.js"></script>
</head>
<body>
  <div id="root"></div>
</body>

前面的代码显示了 HTML 的<head>元素。app_one_remote.js在运行时连接运行时和临时编排层。它专门设计用于入口点。这些是示例 URL,您可以使用自己的位置。重要的是要注意,这个例子是一个非常低内存的例子,您的构建可能会大得多,但足够好理解这个原则。

  1. 为了消耗远程应用程序的代码,第一个应用程序有一个网页,从第二个应用程序中消耗对话框组件,如下所示:
const Dialog = React.lazy(() => import("app_two_remote/Dialogue")); 
const Page1 = () => { 
  return ( 
    <div> 
      <h1>Page 1</h1> 
        <React.Suspense fallback="Loading User Dialogue..."> 
          <Dialog /> 
        </React.Suspense> 
    </div> 
  ); 
}
  1. 让我们从导出我们正在使用的默认 HTML 页面开始,并设置路由,操作如下:
import { Route, Switch } from "react-router-dom";
import Page1 from "./pages/page1";
import Page2 from "./pages/page2";
import React from "react";
   const Routes = () => (
     <Switch>
       <Route path="/page1">
        <Page1 />
       </Route>
       <Route path="/page2">
        <Page2 />
       </Route>
     </Switch>
  );

前面的代码块显示了代码如何工作;它将从我们正在开发的系统中的每个页面导出默认路由。

这个系统是由三个应用程序组成的,我们现在来看第二个应用程序。

第二个应用程序

我们正在构建的系统由三个应用程序组成。这个应用程序将暴露对话框,使得这个序列中的第一个应用程序可以消耗它。然而,第二个应用程序将消耗第一个应用程序的<app>元素标识符。让我们来看一下:

  1. 我们将从配置第二个应用程序开始。这意味着我们需要指定app-one作为远程应用程序,并同时演示双向托管:
 const HtmlWebpackPlugin = require("html-webpack-plugin");
 const ModuleFederationPlugin =
   require("webpack/lib/container/ModuleFederationPlugin");
 module.exports = {
   plugins: [
     new ModuleFederationPlugin({
     name: "app_two_remote",
     library: { type: "var", name: "app_two_remote" },
     filename: "remoteEntry.js",
     exposes: {
       Dialog: "./src/Dialogue"
 },
   remotes: {
     app_one: "app_one_remote",
 },
   shared: ["react", "react-dom","react-router-dom"]
 }),
 new HtmlWebpackPlugin({
   template: "./public/index.html",
   chunks: ["main"]
  })
 ]
};
  1. 为了消耗,以下是根应用程序的样子:
import React from "react";
import Routes from './Routes'
const AppContainer = React.lazy(() =>
  import("app_one_remote/AppContainer"));

const App = () => {
  return (
   <div>
     <React.Suspense fallback="Loading App Container from Host">
       <AppContainer routes={Routes}/>
     </React.Suspense>
   </div>
  );
}
  1. 接下来,我们需要设置代码,以便我们可以导出默认的应用程序。以下是使用对话框时默认页面应该看起来的示例:
import React from 'react'
import {ThemeProvider} from "@material-ui/core";
import {theme} from "./theme";
import Dialog from "./Dialogue";

function MainPage() {
   return (
     <ThemeProvider theme={theme}>
       <div>
         <h1>Material User Iinterface App</h1>
         <Dialog />
      </div>
     </ThemeProvider>
  );
}
  1. 现在,我们需要导出默认的MainPage。这是通过我们系统中的第三个应用程序完成的。

第三个应用程序

让我们来看看我们的第三个和最后一个应用程序:

  1. 我们系统中的第三个应用程序将导出一个默认的MainPage。这是通过以下脚本完成的:
new ModuleFederationPlugin({
   name: "app_three_remote",
   library: { type: "var", name: "app_three_remote" },
   filename: "remoteEntry.js",
   exposes: {
     Button: "./src/Button"
 },
 shared: ["react", "react-dom"]
}),

如预期的那样,第三个应用程序看起来与之前的应用程序类似,只是它不从第一个应用程序中消耗<app>。这个应用程序是独立的,没有导航,因此不指定任何远程联邦组件。

在浏览器中查看系统时,您应该密切关注网络选项卡。代码可以在三个不同的服务器(可能)和三个不同的捆绑包(自然)之间进行联邦。

这个组件允许您的构建具有很大的动态性,但除非您希望利用服务器端渲染(SSR)或渐进式加载,否则您可能希望避免联邦整个应用程序容器,否则加载时间可能会受到严重影响。

加载问题是一个自然的关注点,但通常会导致项目规模变大的一个问题是潜在的重复的重复代码,这是使用多个并行捆绑的结果。让我们来看看 Webpack 5 是如何处理这个问题的。

重复问题

Webpack 的一个关键特性是去除重复的代码。在联邦环境中,宿主应用程序为远程应用程序提供依赖项。在没有可共享的依赖项的情况下,远程应用程序可以自动下载自己的依赖项。这是一种内置的冗余故障安全机制。

在大规模情况下手动添加供应商代码可能会很繁琐,但联邦功能允许我们创建自动化脚本。这是开发者的选择,但这可能是一个很好的机会,让您能够测试自己的知识。

我们已经提到了 SSR。您应该知道服务器构建需要一个commonjs库目标,以便它们可以与 Webpack 联邦一起使用。这可以通过 S3 流式传输、ESI,或者通过自动化 npm 发布来完成,以便您可以消耗服务器变体。以下代码显示了包括commonjs的一个示例:

module.exports = {
 plugins: [
  new ModuleFederationPlugin({
    name: "container",
    library: { type: "commonjs-module" },
    filename: "container.js",
    remotes: {
      containerB: "../1-container-full/container.js"
 },
   shared: ["react"]
  })
 ]
};

您可能希望使用target: "node"方法,以避免 URL 而选择文件路径。这将允许在为 Node.js 构建时使用相同的代码库但不同的配置进行 SSR。这也意味着单独的构建将成为单独的部署。

作为一家公司,Webpack 愿意展示 SSR 示例,您作为开发者社区的一部分可能已经制作了。他们将很乐意通过他们的 GitHub 页面接受拉取请求,因为他们有带宽,并且在这个过程中受益于曝光。

总结

在本章中,您了解了部署项目在线的过程。我们讨论了安装和设置过程,以及树摇。

首先,我们看了一下生产和开发模式,每种环境的性质,以及它们如何利用 Webpack。然后,我们看了 shimming,最佳使用方法,它的工作原理,以便我们可以修补代码,它与任务运行器的关系,以及它们与 Webpack 等打包工具的集成。

现在,你应该能够提取 boilerplate,集成各种任务运行器,并知道如何使用 GitHub。

在下一章中,我们将讨论热模块替换和实时编码,并掌握一些严肃的教程。

问题

  1. 术语 boilerplate 是什么意思?

  2. 摇树是做什么的?

  3. 术语 shimming 是什么意思?

  4. 渐进式 Web 应用的目的是什么?

  5. 任务运行器的作用是什么?

  6. 这一章提到了哪三个任务运行器?

  7. Webpack 的编译器可以理解哪三种规范编写的模块?

进一步阅读

第七章:调试和迁移

本章将进一步探讨迁移和调试,提供对这些主题的广泛概述和详细检查。

迁移是指将内容和项目从 Webpack 的早期版本迁移到较新版本的过程。我们将特别关注从 Webpack 3 版本迁移到 4 版本以及从 4 版本迁移到 5 版本的过程。我们还将介绍如何处理已弃用的插件以及如何删除或更新它们。这将包括在使用 Node.js v4 和 CLI 时的迁移。

本章将讨论resolve方法以及module.loaders现已被module.rules方法取代。它还将涵盖加载器的链接,包括不再需要或已删除的加载器的链接。

然后,本章将继续探讨调试。调试涉及消除复杂软件系统中出现的常见故障和错误的过程。本章将解释常见问题及其解决方案、故障排除、避免这些问题的最佳实践以及如何找到故障。

本章涵盖的主题如下:

  • 调试

  • 热模块替换

  • 添加实用程序

  • 迁移

调试

调试工具对于工作流程至关重要,特别是在贡献核心复制、编写加载器或任何其他复杂形式的编码时。本指南将带您了解在解决诸如性能缓慢或不可原谅的回溯等问题时最有用的实用工具。

  • 通过 Node.js 和 CLI 提供的stats数据

  • 通过node-nightly和最新的 Node.js 版本使用 Chrome DevTools

在 Webpack 5 中,截至撰写本文时,存在一些已知问题;例如,DevTools 不支持持久缓存和包含绝对路径的持久缓存文件尚不可移植。

在调试构建问题、手动筛选数据或使用工具时,stats数据非常有用。它可用于查找以下内容:

  • 构建错误和警告

  • 每个模块的内容

  • 模块编译和解析统计

  • 模块之间的相互关系

  • 任何给定块中包含的模块

此外,官方的 Webpack 分析工具将接受这些数据并为您可视化。

有时,当控制台语句无法完成工作时,需要更强大的解决方案。

正如前端开发人员社区中普遍认为的那样,Chrome DevTools 在调试应用程序时是不可或缺的,但它并不止步于此。从 Node.js v6.3.0+开始,开发人员可以使用内置的检查标志在 DevTools 中调试 Node.js 程序。

这个简短的演示将利用node-nightly包,该包提供了最新的检查功能。这提供了创建断点、调试内存使用问题、在控制台中公开对象等功能:

  1. 首先全局安装node-nightly包:
npm install --global node-nightly
  1. 现在必须使用命令行来运行此包以完成安装:
node-nightly
  1. 现在,使用node-nightlyinspect标志功能,我们可以开始调试任何 Webpack 项目。需要注意的是,现在无法运行 npm 脚本;相反,需要表达完整的node_module路径:
node-nightly --inspect ./node_modules/webpack/bin/webpack.js
  1. 输出应该在命令行实用程序窗口中显示如下内容:
Debugger listening on ws://127.0.0.1:9229/c624201a-250f-416e-a018-300bbec7be2c For help see https://nodejs.org/en/docs/inspector
  1. 现在,转到 Chrome 的检查功能(chrome://inspect),任何活动脚本现在应该在远程目标标题下可见。

单击每个脚本下的“检查”链接将在会话中为节点打开专用的调试器或 DevTools 链接,这将自动连接。请注意,NiM 是 Chrome 的一个方便的扩展,每次进行检查时都会自动在新标签中打开 DevTools。这对于较长的项目非常有用。

还可以使用inspect-brk标志,它会在任何脚本的第一个语句上中断,允许查看源代码,设置断点,并临时停止和启动进程。这也允许程序员继续向所讨论的脚本传递参数;这可能对进行并行配置更改很有用。

所有这些都与一个关键功能有关,这也是本指南中先前提到的令人兴奋的热模块替换(HMR)主题。下一节将介绍它是什么以及如何使用它,以及教程。

热模块替换

HMR 可能是 Webpack 中最有用的元素。它允许运行时更新需要完全刷新的模块。本节将探讨 HMR 的实现,以及它的工作原理和为什么它如此有用。

非常重要的一点是,HMR 不适用于生产模式,也不应该在生产模式下使用;它只应该在开发模式下使用。

值得注意的是,根据开发人员的说法,插件的内部 HMR API 在将来的 Webpack 5 更新中可能会发生变化。

要启用 HMR,我们首先需要更新我们的webpack-dev-server配置,并使用 Webpack 内置的 HMR 插件。这个功能对提高生产力非常有用。

删除print.js的入口点也是一个好主意,因为它现在将被index.js模块使用。

任何使用webpack-dev-middleware而不是webpack-dev-server的人现在应该使用webpack-hot-middleware包来启用 HMR:

  1. 要开始使用 HMR,我们需要返回到配置文件webpack.config.js。按照这里的修改:
  const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');
  const CleanWebpackPlugin = require('clean-webpack-plugin');
  const webpack = require('webpack');

  module.exports = {
    entry: {
       app: './src/index.js',
       print: './src/print.js'
       app: './src/index.js'
    },
    devtool: 'inline-source-map',
    devServer: {
      contentBase: './dist',
 hot: true
    },
    plugins: [
      // new CleanWebpackPlugin(['dist/*']) for < v2 versions of 
      // CleanWebpackPlugin
      new CleanWebpackPlugin(),
      new HtmlWebpackPlugin({
        title: 'Hot Module Replacement'
      }),
      new webpack.HotModuleReplacementPlugin()
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };

请注意前面代码中的添加——hot:选项设置为true,并添加了'Hot Module Replacement'插件,以及在 HMR 配置中创建新的 Webpack 插件。所有这些都应该被做来使用插件和 HMR。

  1. 可以使用命令行修改webpack-dev-server配置,命令如下:
webpack-dev-server --hot 

这将允许对捆绑应用程序进行临时更改。

  1. index.js现在应该更新,以便当检测到print.js的更改时,Webpack 可以接受更新的模块。更改在以下示例中以粗体显示;我们只是用import表达式和函数暴露print.js文件:
  import _ from 'lodash';
 import printMe from './print.js';

  function component() {
    const element = document.createElement('div');
    const btn = document.createElement('button');

    element.innerHTML = _.join(['Hello', 'Webpack'], ' ');

    btn.innerHTML = 'Click me and check the console!';
    btn.onclick = printMe;

    element.appendChild(btn);

    return element;
  }

  document.body.appendChild(component());

 if (module.hot) {
    module.hot.accept('./print.js', function() {
      console.log('Accepting the updated printMe module');
      printMe();
    })
  }

如果您更改print.js中的控制台日志,则将在浏览器控制台中看到以下输出。现在,强制性的printMe()按钮已经消失,但稍后可以更新:

  export default function printMe() {
    console.log('This got called from print.js!');
    console.log('Updating print.js...')
  }

查看控制台窗口应该会显示以下输出:

[HMR] Waiting for update signal from WDS...
main.js:4395 [WDS] Hot Module Replacement enabled.
  2main.js:4395 [WDS] App updated. Recompiling...
  main.js:4395 [WDS] App hot update...
  main.js:4330 [HMR] Checking for updates on the server...
  main.js:10024 Accepting the updated printMe module!
  0.4b8ee77….hot-update.js:10 Updating print.js...
  main.js:4330 [HMR] Updated modules:
  main.js:4330 [HMR]  - 20

前面的代码块显示 HMR 正在等待来自 Webpack 的信号,如果发生 HMR,命令行实用程序可以执行自动捆绑修订。当命令行窗口保持打开时,它也会显示这一点。Node.js 有一个类似的 API 可以使用。

在使用 Node.js API 时使用 DevServer

在使用DevServer和 Node.js API 时,不应将dev server选项放在 Webpack 配置对象上;而应始终将其作为第二参数传递。

在这里,DevServer 只是指在开发模式下使用 Webpack,而不是watchingproduction模式。要在 Node.js API 中使用 DevServer,请按照以下步骤进行操作:

  1. 该函数放在webpack.config.js文件中,如下:
new WebpackDevServer(compiler, options)

要启用 HMR,首先必须修改配置对象以包括 HMR 入口点。webpack-dev-server包包括一个名为addDevServerEntryPoints的方法,可以用来执行此操作。

  1. 接下来是使用dev-server.js的简短示例:
const webpackDevServer = require('webpack-dev-server');
const webpack = require('webpack');

  const config = require('./webpack.config.js');
  const options = {
    contentBase: './dist',
    hot: true,
    host: 'localhost'
};

webpackDevServer.addDevServerEntrypoints(config, options);
const compiler = webpack(config);
const server = new webpackDevServer(compiler, options);

server.listen(5000, 'localhost', () => {
  console.log('dev server listening on port 5000');
});

HMR 可能很困难。为了证明这一点,在我们的示例中,单击在示例网页中创建的按钮。显然控制台正在打印旧函数。这是因为事件处理程序绑定到原始函数。

  1. 为了解决这个问题以便与 HMR 一起使用,必须使用module.hot.accept更新绑定到新函数。请参阅以下示例,使用index.js
  import _ from 'lodash';
  import printMe from './print.js';

  function component() {
    const element = document.createElement('div');
    const btn = document.createElement('button');

    element.innerHTML = _.join(['Hello', 'Webpack'], ' ');

    btn.innerHTML = 'Click me and view the console!';
    btn.onclick = printMe;  // onclick event is bind to the 
                            // original printMe function

    element.appendChild(btn);

    return element;
  }

  document.body.appendChild(component());
 let element = component(); // Store the element to re-render on 
                             // print.js changes
  document.body.appendChild(element);

  if (module.hot) {
    module.hot.accept('./print.js', function() {
      console.log('Accepting the updated printMe module!');
      printMe();
      document.body.removeChild(element);
 element = component(); 
      document.body.appendChild(element);
    })
  }

通过解释,btn.onclick = printMe;是绑定到原始printMe函数的onclick事件。let element = component();将存储元素以便在print.js发生任何更改时重新渲染。还要注意element - component();语句,它将重新渲染组件并更新单击处理程序。

这只是您可能会遇到的陷阱的一个例子。幸运的是,Webpack 提供了许多加载程序,其中一些稍后将讨论,这使得 HMR 变得不那么棘手。现在让我们来看看 HMR 和样式表。

HMR 和样式表

使用style-loader可以更轻松地使用 HMR 处理 CSS。此加载程序使用module.hot.accept在更新 CSS 依赖项时修补样式标签。

在我们的实际示例的下一个阶段,我们将采取以下步骤:

  1. 首先,使用以下命令安装两个加载程序:
npm install --save-dev style-loader css-loader
  1. 接下来,更新配置文件webpack.config.js以使用这些加载程序:
  const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');
  const CleanWebpackPlugin = require('clean-webpack-plugin');
  const webpack = require('webpack');

  module.exports = {
    entry: {
      app: './src/index.js'
    },
    devtool: 'inline-source-map',
    devServer: {
      contentBase: './dist',
      hot: true
    },
    module: {
      rules: [
        {
 test: /\.css$/,
          use: ['style-loader', 'css-loader']
        }
      ]
    },
    plugins: [
      // new CleanWebpackPlugin(['dist/*']) for < v2 versions of 
        // CleanWebpackPlugin
      new CleanWebpackPlugin(),
      new HtmlWebpackPlugin({
        title: 'Hot Module Replacement'
      }),
      new webpack.HotModuleReplacementPlugin()
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };

热加载样式表就像将它们导入模块一样简单,您可以从前面的配置示例和接下来的目录结构示例中看到。

  1. 确保按照以下结构组织项目文件和目录:
 webpack5-demo
  | - package.json
  | - webpack.config.js
  | - /dist
    | - bundle.js
  | - /src
    | - index.js
    | - print.js
    | - styles.css
  1. 通过向样式表添加body样式来将文档主体的背景与其关联的蓝色进行样式化。使用styles.css文件执行此操作:
body {
  background: blue;
}
  1. 之后,我们需要确保内容正确加载到index.js文件中,如下所示:
  import _ from 'lodash';
  import printMe from './print.js';
 import './styles.css'; 
  function component() {
    const element = document.createElement('div');
    const btn = document.createElement('button');

    element.innerHTML = _.join(['Hello', 'Webpack'], ' ');

    btn.innerHTML = 'Click me and check the console!';
    btn.onclick = printMe;  // onclick event is bind to the 
                            // original printMe function

    element.appendChild(btn);

    return element;
  }

  let element = component();
  document.body.appendChild(element);

  if (module.hot) {
    module.hot.accept('./print.js', function() {
      console.log('Accepting the updated printMe module!');
      document.body.removeChild(element);
      element = component(); // Re-render the "component" to update 
                             // the click handler
      document.body.appendChild(element);
    })
  }

现在,当body标签背景类的样式更改为红色时,颜色变化应立即注意到,无需刷新页面,表明了热编码的实时性。

  1. 现在,您应该使用styles.css对背景进行这些更改:
  body {
    background: blue;
    background: red;
  }

这以一种非常简单的方式演示了如何进行实时代码编辑。这只是一个简单的例子,但它是一个很好的介绍。现在,让我们进一步探讨一些更棘手的内容——加载程序和框架。

其他加载程序和框架

我们已经介绍了许多可用的加载程序,这些加载程序使 HMR 与各种框架和库更加顺畅地交互。其中一些更有用的加载程序在这里进行了描述:

  • Angular HMR:只需对主NgModule文件进行简单更改,即可完全控制 HMR API(不需要加载程序)。

  • React Hot Loader:此加载程序可以实时调整 React 组件。

  • Elm Hot Webpack Loader:此加载程序支持 Elm 编程语言的 HMR。

  • Vue Loader:此加载程序支持 Vue 组件的 HMR。

我们已经讨论了 HMR 和相关加载程序和框架,但有一件事我们尚未讨论——与我们迄今为止涵盖的内容相关的添加实用程序。我们将在接下来的部分中深入了解。

添加实用程序

在这种情况下,实用程序意味着负责一组相关功能的文件或模块,旨在优化、分析、配置或维护。这与应用程序形成对比,后者倾向于执行直接面向用户的任务或一组任务。因此,在这种情况下,您可以将实用程序视为前端的一部分,但它被隐藏在后台用于后台任务。

首先,在示例项目中添加一个实用程序文件。在src/math.js中执行此操作,以便导出两个函数:

  1. 第一步将是组织项目目录:
webpack5-demo
|- package.json
|- webpack.config.js
|- /dist
  |- bundle.js
  |- index.html
|- /src
  |- index.js
  |- math.js
|- /node_modules

项目树显示了您的文件和文件夹的外观,您将注意到其中一些新的添加,例如math.js

  1. 现在让我们更仔细地看一下math.js的编码:
export function square(x) {
  return x * x;
}

export function cube(x) {
  return x * x * x;
}

您会发现它们是简单易导出的函数;它们将在稍后显现。

  1. 还要确保在配置文件webpack.config.js中将 Webpack 模式设置为development,以确保捆绑包不被最小化:
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
  },
  mode: 'development',
  optimization: {
    usedExports: true
  }
};
  1. 有了这个,接下来更新入口脚本以利用其中一种新方法,并为简单起见删除lodash。这是使用src/index.js文件完成的:
  import _ from 'lodash';
  import { cube } from './math.js';

  function component() {
    const element = document.createElement('div');
    const element = document.createElement('pre');

    // Lodash, now imported by this script
    element.innerHTML = _.join(['Hello', 'webpack'], ' ');
    element.innerHTML = [
      'Hello Webpack!',
      '5 cubed is equal to ' + cube(5)
    ].join('\n\n');

    return element;
  }

  document.body.appendChild(component());

从上面的例子中,我们可以看到square方法没有从src/math.js模块中导入。这个函数可以被视为死代码,基本上是一个未使用的导出,可以被删除。

  1. 现在,再次运行npm构建以检查结果:
npm run build
  1. 完成后,找到dist/bundle.js文件——它应该在第 90-100 行左右。搜索文件以查找类似以下示例的代码,以便按照此过程进行:
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
  'use strict';
  /* unused harmony export square */
  /* harmony export (immutable) */ __webpack_exports__['a'] = cube;
  function square(x) {
    return x * x;
  }

  function cube(x) {
    return x * x * x;
  }
});

在这个例子中,现在会看到一个unused harmony export square的注释。请注意,它没有被导入。但是,它目前仍然包含在捆绑包中。

  1. ECMA 脚本并不完美,因此重要的是向 Webpack 的编译器提供关于代码纯度的提示。packages.json属性将有助于处理这些副作用:
{
  "name": "your-project",
  "sideEffects": false
}

上述代码不包含副作用;因此,应将该属性标记为false,以指示 Webpack 删除未使用的导出。

在这种情况下,副作用被定义为在导入时执行特殊行为的脚本,而不是暴露多个导出等。一个例子是影响全局项目并且通常不提供导出的polyfills

  1. 如果代码具有副作用,可以提供一个数组作为补救措施,例如以下示例:
{
  "name": "your-project",
  "sideEffects": [
    "./src/some-side-effectful-file.js"
  ]
}

此示例中的数组接受相对和绝对模式。

  1. 请注意,任何导入的文件都会受到摇树的影响。例如,如果使用CSS-loader导入 CSS 文件,则必须将其添加到副作用列表中,以防止在生产模式下意外删除它:
{
  "name": "your-project",
  "sideEffects": [
    "./src/some-side-effectful-file.js",
    "*.css"
  ]
}
  1. 最后,sideEffects也可以从module.rules配置选项中设置。因此,我们使用importexport语法排队将死代码删除,但我们仍然需要从捆绑包中删除它。要做到这一点,将mode配置选项设置为production。这是通过以下方式在配置文件webpack.config.js中完成的:
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  mode: 'development',
  optimization: {
    usedExports: true
  }
  mode: 'production'
};

--optimize-minimize标志也可以用于启用TerserPlugin。现在我们已经了解了这一点,可以再次运行npm构建。

现在清楚地看到整个捆绑包已经被最小化和混淆。仔细观察会发现square函数已经消失;取而代之的是一个混淆的 cube 函数:

function r(e){return e*e*e}n.a=r

通过最小化和摇树,我们的捆绑包现在小了几个字节!虽然在这个假设的例子中可能看起来不多,但在处理具有复杂依赖关系树的大型应用程序时,摇树可以显著减少捆绑包大小。

ModuleConcatenationPlugin是摇树功能的必需品。它通过使用mode: "production"来添加。如果您没有使用它,请记得手动添加ModuleConcatenationPlugin

必须完成以下任务才能充分利用摇树功能:

  • 使用 ES2015 模块语法(即importexport)。

  • 确保没有编译器将您的 ECMAScript 语法转换为 CommonJS 模块。

  • package.json文件中添加一个sideEffects属性。

  • 使用production配置选项来启用各种优化,包括摇树和最小化。

在进行摇树时,通常有助于将您的应用程序视为一棵树。在这个类比中,源代码和库将分别是绿叶和树的活部分。然而,死代码将代表枯叶。摇动这棵树将删除现在无效的代码。

这在迁移时尤其重要,值得考虑。考虑到 Webpack 版本之间的代码弃用变化,重要的是在尝试任何类似操作之前使软件达到最佳状态。这将防止出现非常棘手的错误。

迁移

迁移涉及从一个 Webpack 版本迁移到另一个版本。这通常涉及升级到最新版本。作为 Web 开发人员,你可能已经知道在处理其他软件时会有一些棘手的问题,所以这一部分可能是一个重要的部分,也许在未来的开发过程中你可以参考一下。

为了提供更详细的指南,包括了从 Webpack 3.0 迁移到 Webpack 4.0 的迁移策略,所以让我们在继续到版本 5 之前先进行一下这部分。

从版本 3 迁移到版本 4 的先决条件

在我们开始从 Webpack 版本 3 迁移到 4 的项目之前,有几个先决条件需要澄清:

  • Node.js

  • 命令行

  • 插件

  • 模式

对于使用 Node.js 版本 4 或更低版本的开发人员,升级到 Node.js 版本 6 或更高版本是必要的。在命令行方面,CLI 已经移动到一个名为webpack-cli的单独包中。在使用 Webpack 4 之前,你需要安装它。

在更新插件时,许多第三方插件需要升级到最新版本以确保兼容,所以请注意这一点。浏览项目以找到需要更新的插件也是一个好主意。另外,请确保将新的模式选项添加到你的配置中:

  1. 首先在你的配置中将模式设置为productiondevelopment,根据配置类型而定,如下面的代码片段所示,使用webpack.config.js
module.exports = {
    mode: 'production',
}

还有一种替代方法,可以通过 CLI 传递模式,就像下面的例子一样:

--mode production

前面的例子展示了通过命令行在production模式下进行 Webpack 命令的后半部分。下面的例子展示了在development模式下的相同情况:

--mode development
  1. 下一步是移除弃用的插件;这些插件应该从你的配置文件中移除,因为它们在生产模式下是默认的。下面的例子将向你展示如何在webpack.config.js中进行编辑:
module.exports = {
 plugins: [
   new NoEmitOnErrorsPlugin(),
   new ModuleConcatenationPlugin(),
   new DefinePlugin({ "process.env.NODE_ENV":
     JSON.stringify("production") })
   new UglifyJsPlugin()
 ],
}

下面的例子让你看到了这在开发模式下是如何工作的。请注意,插件在开发模式下是默认的,同样使用webpack.config.js

module.exports = {
 plugins: [
   new NamedModulesPlugin()
 ],
}
  1. 如果一切都做对了,你会发现已经移除了弃用的插件。你的配置文件webpack.config.js应该看起来像下面这样:
module.exports = {
 plugins: [
   new NoErrorsPlugin(),
   new NewWatchingPlugin()
 ],
}

此外,在 Webpack 4.0 中,CommonChunkPlugin已被移除,并提供了optimization.splitChunks选项作为替代。

如果你正在从统计数据生成 HTML,现在可以使用optimization.splitChunks.chunks: "all"——这在大多数情况下是最佳配置。

关于import()和 CommonJS 还有一些工作要做。在 Webpack 4 中,使用import()加载任何非 ESM 脚本的结果已经发生了变化:

  1. 现在,你需要访问默认属性来获取module.exports的值。在这里查看non-esm.js文件,看看它是如何运作的:
module.exports = {
      sayHello: () => {
       console.log('Hello World');
     }
 };

这是一个简单的 JavaScript 函数,你可以复制它的内容来跟随演示并查看结果的变化。

  1. 下一个文件是一个example.js文件。它可以被命名为任何你想要的名字,你可以执行任何你想要的操作。在这个例子中,它是一个简单的sayHello();函数:
function sayHello() {
 import('./non-esm.js').then(module => {
  module.default.sayHello();
 });
}

这些代码块展示了如何使用 CommonJS 编写简单的函数。你应该将这种约定应用到你现有的代码中,以确保它不会出错。

  1. 当使用自定义加载器转换.json文件时,现在需要在webpack.config.js中更改模块类型:
module.exports = {
 rules: [
  {
    test: /config\.json$/,
    loader: 'special-loader',
    type: 'javascript/auto',
    options: {...}
  }
 ]
};
  1. 即使使用json-loader,也可以将其移除;参见下面的例子:
module.exports = {
  rules: [
   {
     test: /\.json$/,
     loader: 'json-loader'
   }
  ]
};

完成所有必需的迁移先决条件后,下一步是内置在 Webpack 中的自动更新过程。

从版本 4 迁移到版本 5 时的先决条件

本指南旨在帮助您在直接使用 Webpack 时迁移到 Webpack 5。如果您使用更高级的工具来运行 Webpack,请参考该工具的迁移说明。

如第一章中所解释的,Webpack 5 简介,Webpack 5 需要 Node.js 版本 10.13.0(LTS)才能运行;但是,使用更新版本可以进一步提高构建性能:

  1. 在升级到主要版本时,应确保检查相关插件和加载程序的个别迁移说明,特别是通过作者提供的副本。在这种情况下,在构建过程中注意弃用警告。您可以通过这种方式调用 Webpack 来获取弃用警告的堆栈跟踪,以找出哪些插件和加载程序负责。Webpack 5 将删除所有已弃用的功能。要继续,构建过程中不应出现弃用警告。

  2. 确保从统计数据中使用入口点信息。如果使用HtmlWebpackPlugin,则无需执行此步骤。

对于包含静态 HTML 或以其他方式创建静态 HTML 的构建,您必须确保使用入口点来生成任何脚本和链接任何 HTML 标签的统计 JSON 文件。如果不可能,您应该避免将splitChunks.chunks键设置为all,并且不要针对splitChunks.maxSize键设置任何设置。然而,这只是一个变通方法,可以被认为不是理想的解决方案。

  1. 确保将模式设置为productiondevelopment,以确保设置相应模式的默认值。

  2. 此外,如果您使用了以下选项,请确保将它们更新为更新版本:

optimization.hashedModuleIds: true => optimization.moduleIds:
  'hashed'
optimization.namedChunks: true => optimization.chunkIds: 'named'
optimization.namedModules: true => optimization.moduleIds: 'named'
NamedModulesPlugin => optimization.moduleIds: 'named'
NamedChunksPlugin => optimization.chunkIds: 'named'
HashedModulesPlugin => optimization.moduleIds: 'hashed'
optimization.occurrenceOrder: true => optimization: { chunkIds:
   'total-size', moduleIds: 'size' }
optimization.splitChunks.cacheGroups.vendors =>
   optimization.splitChunks.cacheGroups.defaultVendors
  1. 接下来,我们需要测试 Webpack 5 与您的应用程序的兼容性。为此,请为您的 Webpack 4 配置设置以下选项。如果在 Webpack 4 中没有任何构建错误,我们将知道任何后续故障是否是版本 5 独有的。这可能听起来很繁琐,但它可以消除递归故障查找:
module.exports = {
 // ...
   node: {
     Buffer: false,
     process: false
   }
 };

在 Webpack 5 中,上述选项已从配置中删除,并默认设置为false。确保在 Webpack 4 测试构建中执行此操作,但在版本 5 构建中需要再次删除。

  1. 接下来是一个简单和简洁的命令行执行,用于升级您的 Webpack 版本:
npm: npm install webpack@next --dev
Yarn: yarn add webpack@next -D

现在,我们需要清理我们的配置。

建议您将配置中的[hash]占位符更改为[contenthash]。这已被证明更有效,并且可以帮助加固您的代码。

如果您正在使用pnp-webpack-plugin,它现在默认支持 Webpack 的版本 5,但现在需要从配置模式中删除。

IgnorePlugin现在接受一个选项对象,因此如果您将其用作正则表达式,则需要进行重写,例如以下内容:

new IgnorePlugin({ resourceRegExp: /regExp/ }).

对于通过import使用 WASM 的开发人员,您应该通过将experiments.syncWebAssembly变量设置为true来启用已弃用的规范。这将在 Webpack 5 中设置与 Webpack 4 相同的行为。一旦您已经迁移到 Webpack 5,现在应该更改实验的值以使用 WASM 的最新规范——{ asyncWebAssembly: true, importAsync: true }

在使用自定义配置时,还应注意将name值替换为idHint

在 Webpack 5 中,不支持从 JSON 模块中导出命名的导出,并且会收到警告。要以这种方式导入任何内容,应该从package.json中使用const[version]=package;

  1. 现在,清理构建代码是一个好的做法。其中一部分是在使用const compiler =webpack(...);时关闭编译器。这可以通过compiler.close();来完成。

一旦运行构建,可能会出现一些问题。例如,模式验证可能会失败,Webpack 可能会退出并显示错误,或者可能会出现构建错误、构建警告或弃用警告。

在每种情况下,都会有一个破坏性变更说明或一个带有指令的错误消息,可以通过命令行获得,就像往常一样。

在弃用警告的情况下,可能会有很多弃用警告,因为 Webpack 5 是新的,插件需要时间来适应核心变化。在每个版本完成测试之前,应该忽略它们,这是一个良好的做法。

您可以通过使用--no-deprecation标志来隐藏弃用警告,例如node --no-deprecation

插件和加载器的贡献者应该遵循弃用消息中的警告建议来改进他们的代码。

如果需要,您还可以关闭运行时代码中的 ES2015 语法。默认情况下,Webpack 的运行时代码使用 ES2015 语法来构建更小的包。如果您的构建目标环境不支持此语法,例如 IE 11,您需要将output.ecmaVersion: 5设置为恢复到 ES5 语法。

处理遗留问题将是向上迁移时面临的最大障碍,这一规则不仅适用于 Webpack 5。Webpack 5 具有一些功能,将使遗留平台用户的体验更加愉快。在项目规划中考虑的一种方法是持久缓存。

启用持久缓存

缓存当然是为了提高加载时间和加快性能而进行的数据的中间存储。持久缓存在数据库驱动项目中非常常见,从数据库中提取的数据会被缓存,以便用户拥有早期版本的副本。然后可以一次性加载,而不会对数据库造成太大的需求,因为数据的交付速度比基于服务器的文件条目要慢。

使用 Webpack 5,应用程序可以利用相同的操作并提高用户的加载速度,如果构建发生变化。

首先,要注意的是,持久缓存不是默认启用的。您必须选择使用它。这是因为 Webpack 5 更看重安全性而不是性能。启用即使提高了性能但会以任何小的方式破坏您的代码,这可能不是最好的主意。至少作为默认,此功能应保持禁用状态。

序列化和反序列化将默认工作;但是,开发人员可能会在缓存失效方面遇到麻烦。

缓存失效是指应用程序中有意更改时,例如开发人员更改文件的内容;在这种情况下,Webpack 会将旧内容的缓存视为无效。

Webpack 5 通过跟踪每个模块使用的fileDependenciescontextDependenciesmissingDependencies来实现这一点。然后,Webpack 从中创建一个文件系统图。然后,文件系统与记录的副本进行交叉引用,这反过来会触发该模块的重建。

然后,输出包的缓存条目会为其生成一个标签;这本质上是所有贡献者的哈希。标签与缓存条目之间的匹配表示 Webpack 可以用于打包的内容。

Webpack 4 使用相同的过程进行内存缓存,而在 Webpack 5 中也可以工作,无需额外配置,除非启用了持久缓存。

您还需要在升级加载器或插件时使用npm,更改配置,更改要在配置中读取的文件,或者升级配置中使用的依赖项时,通过传递不同的命令行参数来运行构建,或者拥有自定义构建脚本并对其进行更改时,使缓存条目失效。

由于 Webpack 5 无法直接处理这些异常,因此持久缓存被作为一种选择性功能,以确保应用程序的完整性。

Webpack 更新

有许多步骤必须采取,以确保 Webpack 的更新行为正确。与我们的示例相关的步骤如下:

  • 升级和安装。

  • 将模式添加到配置文件中。

  • 添加 fork 检查器。

  • 手动更新相关插件、加载器和实用程序。

  • 重新配置uglify

  • 跟踪任何进一步的错误并进行更新。

让我们详细了解每个步骤,并探索命令行中到底发生了什么。这应该有助于您更好地理解该过程:

  1. 我们需要做的第一件事是升级 Webpack 并安装webpack-cli。这是在命令行中完成的,如下所示:
yarn add webpack
yarn add webpack-cli
  1. 前面的示例显示了使用yarn来完成这个操作,并且还会进行版本检查。这也应该在package.json文件中可见:
...
"webpack": "⁵.0.0",
"webpack-cli": "³.2.3",
...
  1. 完成后,应该将相应的模式添加到webpack.config.dev.jswebpack.config.prod.js。参见以下webpack.config.dev.js文件:
module.exports = {
mode: 'development',

与生产配置一样,我们在这里为每种模式都有两个配置文件。以下显示了webpack.config.prod.js文件的内容:

module.exports = {
mode: 'production',

我们正在处理两个版本——旧版本(3)和新版本(4)。如果这是手动完成的,您可能首先要对原始版本进行fork。fork 一词指的是通常与此操作相关的图标,它代表一行从另一行分离出来,看起来像一个叉子。因此,fork 一词已经成为一个分支的意思。fork 检查器将自动检查每个版本的差异,以便作为操作的一部分进行更新。

  1. 现在,回到命令行添加以下 fork 检查器:
add fork-ts-checker-notifier-webpack-plugin
yarn add fork-ts-checker-notifier-webpack-plugin --dev

package.json文件中应该看到以下内容:

...
"fork-ts-checker-notifier-webpack-plugin": "¹.0.0",
...

前面的代码块显示了 fork 检查器已经安装。

  1. 现在,我们需要使用命令行更新html-webpack-plugin
yarn add html-webpack-plugin@next

package.json现在应该显示如下内容:

"html-webpack-plugin": "⁴.0.0-beta.5",

现在,我们需要调整webpack.config.dev.jswebpack.config.prod.js文件中的插件顺序。

  1. 您应该采取这些步骤来确保HtmlWebpackPluginInterpolateHtmlPlugin之前声明,并且InterpolateHtmlPlugin在下面的示例中被声明:
plugins: [
 new HtmlWebpackPlugin({
   inject: true,
   template: paths.appHtml
 }),
 new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
  1. 还要确保在命令行中更新ts-loaderurl-loaderfile-loader
yarn add url-loader file-loader ts-loader
  1. package.json文件保存了关于使用的版本的信息,就像之前提到的加载器一样,并且应该如下所示:
"file-loader": "¹.1.11",
"ts-loader": "4.0.0",
"url-loader": "0.6.2",

如果您正在使用React,那么您需要更新开发实用程序,如下所示:

yarn add react-dev-utils

同样,package.json文件将保存所使用的 React 实用程序的版本信息:

"react-dev-utils": "6.1.1",

extract-text-webpack-plugin应该被替换为mini-css-extract-plugin

  1. 请注意,应该完全删除extract-text-webpack-plugin,同时添加和配置mini-css-extract-plugin
yarn add mini-css-extract-plugin
  1. 对于此示例,package.json文件中带有插件版本设置应该如下所示:
"mini-css-extract-plugin": "⁰.5.0",
Config:
  1. 完成所有这些后,我们应该看一下生产模式的配置。这是通过以下webpack.config.prod.js文件完成的:
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
plugins: [
 ...
 new MiniCssExtractPlugin({
   filename: "[name].css",
   chunkFilename: "[id].css"
 }),
 ...
 ],
 module: {
   rules: [
    {
     test: /\.css$/,
     use: [
     {
       loader: MiniCssExtractPlugin.loader,
       options: {
       // you can specify a publicPath here
       // by default it use publicPath in webpackOptions.output
       publicPath: '../'
     }
    },
    "css-loader"
   ]
 },

我们可以看到在不同版本之间的webpack.config.prod.js中有一些差异。前面的示例让你了解了在版本 4 中进行配置的格式。

  1. 接下来,请确保使用命令行和package.json文件更新和重新配置uglifyjs-webpack-plugin
yarn add uglifyjs-webpack-plugin --dev
  1. 为了谨慎起见,我们还将展示此处uglify插件的版本设置。使用package.json应用这些设置:
"uglifyjs-webpack-plugin": "².1.2"
 Config:
  1. 下一步,最后一步是使用webpack.config.prod.js配置生产模式:
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
  ...
  optimization: {
    minimizer: [new UglifyJsPlugin()],
 },

完成所有这些后,您应该完成更新过程。但是,您可能会遇到一个独特的弃用错误,这意味着您需要使用错误消息跟踪这些错误,然后根据需要更新任何进一步的 Webpack 插件。如果您正在使用自定义插件或加载器,情况将尤其如此。

总结

本章深入探讨了代码调试,并讨论了 HMR 和其他调试技术。您现在应该对可以用于增强调试过程的工具和实用程序有了牢固的掌握,包括使用node nightly进行代码检查。然后我们深入研究了 HMR,这是 Webpack 的一个显著和令人兴奋的特性。我们看到了如何对模块和样式表进行实时编辑,甚至涵盖了迁移问题。然后我们过渡到添加实用程序,这是任何升级的重要部分。从那里,我们带您完成了版本迁移,即从版本 3 迁移到版本 4,并介绍了相应的步骤。此外,我们还向您展示了如何从版本 4 迁移到版本 5。本节以一个漫长的教程结束,介绍了如何将命令行升级更新为对一些更棘手的元素进行手动更改。

您现在应该对自己的调试和升级技能充满信心,这将使您在下一章中站稳脚跟。在下一章中,我们将进行一些繁重的现场编码、定制和手动捆绑,这无疑会让您兴奋不已!

进一步阅读

本章涵盖了一些复杂问题,通过进一步阅读可以更好地理解。以下是一些主题以及在本章中提到的相关内容的查找位置:

  • 调试优化退出

  • 问题 6074—为sideEffects添加对更复杂选择器的支持

问题

现在,尝试一下与本章相关的以下问题。您将在本书的评估部分中找到答案:

  1. HMR 是什么意思?

  2. React Hot Loader 是做什么的?

  3. Webpack 通过哪个接口进行更新?

  4. Node v6.3.0+的哪个功能允许通过 Chrome DevTools 进行调试?

  5. 从 Webpack 版本 3 迁移到版本 4 并使用自定义加载器转换.json文件时,您还必须改变什么?

  6. side effects 列表如何帮助开发?

  7. 在生产模式中应该从哪里删除不推荐使用的插件?

第八章:编写教程和实时编码黑客

这是 Webpack 5.0 的最后一章。到目前为止,您可能已经感觉自己是一个专家,但是您的掌握能力不仅来自于您对代码本身的掌握,还包括自定义平台的能力,甚至是对其进行黑客。纯粹主义者可能会避免使用“黑客”这样的术语,而更倾向于使用变通方法或修补程序,但我们基本上讨论的是同一个主题(除非向外行解释您在实时生产中黑客了 Webpack 肯定会给编码超能力的最坚定的怀疑者留下深刻印象)。

话虽如此,本章是 Webpack 中最难做的事情和最简单的实现方法的汇编。我们将从编写库开始,然后转向自定义加载程序,这在本指南中已经讨论过,特别是使用 Babel 的应用程序编程接口API)。然而,本章将讨论一种更本地的定制方法,而无需 API。

当然,我们将涵盖一些在本指南中已经详细讨论过的主题,例如测试和 shimming,但是本指南将更加突出和相关于定制。

然后,我们将涵盖一些非常有趣的黑客,特别是在热模块替换HMR)启用的环境中进行实时发布时。

本章涵盖的一些主题如下:

  • 编写库

  • 自定义加载程序

  • 实时编码黑客

编写库

本章的这一部分对于希望简化其捆绑策略的任何人都非常有用。人们并不太了解 Webpack 可以用于捆绑库和应用程序。

我们将从一个假设的自定义库项目开始,我们将其称为numbers-to-text。它将通过将数字从15转换为数字的文本表示,例如将3转换为three

让我们详细讨论每个任务,并解释代码中发生的事情,通过示例帮助我们更清楚地理解发生了什么以及代码的行为如何。我们将按以下方式进行:

  1. 我们将首先整理我们的项目结构。基本项目结构应如下所示:
|- webpack.config.js
  |- package.json
  |- /src
  |- index.js
  |- ref.json

请注意,结构可能与以前的教程不同,特别是ref.json文件的存在。在继续之前,您需要创建这些额外的文件。

  1. 接下来,我们回到命令行界面CLI),首先需要初始化npm,然后确保我们已安装了 Webpack 和lodash。如果您已经按照以前章节中的示例安装了这些内容,那就不用担心了,重复安装尝试只会覆盖最后一次,并不会造成任何伤害。运行以下代码:
npm init -y
npm install --save-dev webpack lodash
  1. 完成这些工作后,我们将注意力转向新构建的src/ref.json JSON文件。这是自定义库的基本数据。它应该看起来像以下示例:
[
  {
    "num": 1,
    "word": "One"
  },
  {
    "num": 2,
    "word": "Two"
  },
  {
    "num": 3,
    "word": "Three"
  },
  {
    "num": 4,
    "word": "Four"
  },
  {
    "num": 5,
    "word": "Five"
  },
  {
    "num": 0,
    "word": "Zero"
  }
]

正如您所看到的,这构成了一个简单的选项列表,代表一个数字,以及该数字的对应文字版本。这将构成我们非常简单的库结构的支柱,以原则上演示概念。一旦教程完成,您应该自然地看到如何根据需要调整库,无论多么复杂。

  1. 现在,我们需要制作一个索引文件(例如src/index.js)。您应该按照此块中显示的编码进行操作:
import _ from 'lodash';
import numRef from './ref.json';
export function numToWord(num) {
  return _.reduce(numRef, (accum, ref) => {
  return ref.num === num ? ref.word : accum;
 }, '');
}
export function wordToNum(word) {
  return _.reduce(numRef, (accum, ref) => {
  return ref.word === word && word.toLowerCase() ? ref.num : accum;
  }, -1);
}

您可以从前面的代码中看到索引文件实质上包含一系列与JSON文件内容相关的exportreturn函数。

  1. 现在,我们需要定义使用规范。操作如下:
    • ES2015 模块导入:
 import * as numbersToText from 'numbers-to-text';
// ...
 numbersToText.wordToNum('Two'); 
    • CommonJS 模块要求:
 const numbersToText = require('numbersToText');
// ...
  1. 在相同的文件中使用以下代码片段来设置使用AMD时的函数:
numbersToText.wordToNum('Two');
AMD module requires:
require(['numbersToText'], function (numbersToText) {
 numbersToText.wordToNum('Two');
 });

用户还可以通过script标签加载库,如下所示:

<!doctype html>
 <html>
   ...
   <script src="img/webpack-numbers"></script>
   <script>
    // Global variable
    numbersToText.wordToNum('Five')
    // Property in the window object
    window.numbersToText.wordToNum('Five')
   </script>
 </html>

这是加载库的最常见方式,作为 JavaScript 开发人员,这应该是一种应该很容易遵循的事情。也就是说,它可以配置为在全局对象中公开属性,用于 Node.js,或者作为this对象中的属性。

这将带我们进入库的基本配置。授权这个库有多个级别的配置,所以这只是第一步。

基本配置

与任何 Webpack 项目一样,我们需要对其进行配置。与典型的配置相比,处理自定义库时需要实现一些不寻常的事情。现在让我们考虑这些目标。库应该以一种实现以下目标的方式捆绑:

  • 使用外部来避免捆绑lodash,因此用户需要加载它

  • 指定库的外部限制

  • 将库公开为名为numbersToText的变量

  • 将库名称设置为numbers-to-text

  • 允许在Node.js库中访问

此外,请注意用户必须能够以以下方式访问并使用库:

  • 通过从numbers-to-text导入numbersToText作为ECMAScript 2015 (ES 2015)模块

  • 通过 CommonJS 模块,例如使用require('webpack-numbers')方法

  • 通过全局变量,当通过脚本标签包含时

考虑到所有这些,首先要做的是通过我们用于webpack.config.js文件的常规文件设置 Webpack 配置,如下所示:

const path = require('path');
module.exports = {
  entry: './src/index.js',
  output: {
   path: path.resolve(__dirname, 'dist'),
   filename: 'numbers-to-text.js'
  }
};

这是基本配置,但现在我们将转向下一个确定的目标:将lodash外部化。

使用外部来避免捆绑 lodash

如果现在执行构建,你会看到创建了一个相当大的捆绑包。检查文件会发现lodash已经与其捆绑在一起。出于本教程的目的,最好将lodash视为对等依赖项。这基本上意味着用户必须安装lodash,有效地将这个外部库的控制权交给了这个库的用户。

这可以通过外部的配置来完成,如webpack.config.js中所示,如下所示:

const path = require('path');
module.exports = {
  entry: './src/index.js',
  output: {
   path: path.resolve(__dirname, 'dist'),
   filename: 'numbers-to-text.js'
   }
  },
   externals: {
     lodash: {
      commonjs: 'lodash',
      commonjs2: 'lodash',
      amd: 'lodash',
      root: '_'
    }
  }
};

前面的代码意味着库期望用户环境中有一个名为lodash的依赖项。

请注意,如果计划在并行的 Webpack 捆绑包中将库用作依赖项,则可以将外部指定为数组。

在外部指定限制也很重要,我们现在将讨论这一点。

指定外部限制

您可以使用多个文件的库,例如以下描述性块中的文件:

  • import A from 'library/one';

  • import B from 'library/two';

在这种情况下,它们不能通过在外部指定库来从捆绑包中排除。它们需要逐个排除,或者使用正则表达式,例如以下示例:

module.exports = {
 externals: [
   'library/one',
   'library/two',
   // Everything that starts with "library/"
   /^library\/.+$/
  ]
};

完成这些后,我们需要公开库或允许它加载到我们的前端。这将在下一小节中介绍。

公开库

公开库是本指南中已经讨论过的内容,但如果您跳到本章,您可能会感到困惑。我们只是允许我们的应用程序从外部源加载库,就像任何外部加载到网页中的库一样。库应该与不同的环境兼容,例如CommonJSMDNode.js,以确保库的广泛可用性。为了确保这一点,按照以下步骤进行:

  1. 应该在webpack.config.js配置文件的输出中添加library属性,如下所示:
const path = require('path');
module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'numbers-to-text.js'
    filename: 'numbers-to-text.js',
 library: 'numbersToText'
 },
 externals: {
 lodash: {
 commonjs: 'lodash',
 commonjs2: 'lodash',
 amd: 'lodash',
 root: '_'
 }
 }
};

库设置与配置相关。在大多数情况下,指定一个入口点就足够了。多部分库是可能的;然而,通过一个索引脚本公开部分导出更简单,作为入口点。

尝试使用数组作为库入口点是不明智的,因为它不会编译得很好。

这实现了将库捆绑包公开为全局变量。

  1. 您可以通过向配置文件添加libraryTarget属性,以不同的选项公开库,使用webpack.config.js,使您的库与其他环境兼容,如下所示:
const path = require('path');
module.exports = {
  entry: './src/index.js',
  output: {
   path: path.resolve(__dirname, 'dist'),
   filename: 'numbers-to-text.js',
   library: 'numbersToText'
   library: 'numbersToText',
   libraryTarget: 'umd'
 },
 externals: {
   lodash: {
    commonjs: 'lodash',
    commonjs2: 'lodash',
    amd: 'lodash',
    root: '_'
   }
  }
};

配置设置后,我们现在需要公开库。请注意,可以通过以下方式完成:

  • 作为变量——作为脚本标签提供的全局变量,例如libraryTarget:'var'

  • 作为对象——通过this对象可用,例如libraryTarget:'this'

  • Window——通过window对象可用,例如libraryTarget:'window'

  • 通用模块定义UMD)—在 CommonJS 或 AMD require语句之后可用,例如libraryTarget:'umd'

如果您设置了库但没有为libraryTarget函数执行此操作,后者将默认为变量,如输出配置中所指定的那样。

命名库和使用 Node.js

正如所解释的,我们现在处于编写的最后步骤。随着我们的进行,应该按照指南进行输出的优化。在这样做的同时,我们将在package.json文件中将捆绑输出的路径添加为包的主字段,如下所示:

{
 ...
 "main": "dist/numbers-to-text.js",
 ...
 }
 Or, to add as standard module as per this guide:
{
 ...
 "module": "src/index.js",
 ...
 }

前面代码块中名为"main"的选项键指的是我们从package.json文件中检索到的标准。"module"键指的是一个提案,允许 JavaScript 环境升级,能够在不损害任何向后兼容性能力的情况下使用 ES2015 模块。

"module"属性的情况下,这应该指向一个使用 ES2015 模块语法的脚本,但不应使用浏览器或 Node.js 尚不支持的其他语法。这将使 Webpack 能够解析模块语法,并通过摇树来允许更轻的捆绑包,因为用户可能只使用任何给定库的某些部分。

完成后,捆绑包可以作为npm包发布。

您已经学会了如何使用具有相应数字和文本的 JSON 文件设置和配置您的第一个自定义库,包括在前端公开新库并指定库的范围限制。

在一个相关的领域,现在让我们谈谈自定义加载器。

自定义加载器

加载器在本指南的先前章节中已经详细讨论过。但是,我们只是提到了它们的定制或编写。至少展示您对 Webpack 的掌握至关重要,因此我们现在应该讨论一下。

以下教程将按照以下结构进行:

  • 设置

  • 简单使用

  • 复杂使用

  • 指南

指南部分本身将被细分,但现在,让我们从设置开始。

设置

开始本节的最佳方法是看看我们如何在本地开发和测试加载器。这是一个很好的和可接受的开始方式,我们将按照以下步骤进行:

  1. 在测试单个加载器时,您可以简单地在webpack.config.js的规则对象中使用路径来解析到本地文件,如下所示:
module.exports = {
 //...
 module: {
 rules: [
 {
   test: /\.js$/,
   use: [
 {
    loader: path.resolve('path/to/loader.js'),
    options: {/* ... */}
    }
   ]
  }
 ]
 }
};
  1. 要测试多个加载器,可以利用resolveLoader.modules配置,Webpack 将在webpack.config.js中搜索加载器。例如,如果您的项目中有一个包含加载器的本地目录,代码将如下所示:
module.exports = {
  resolveLoader: {
    modules: [
     'node_modules',
     path.resolve(__dirname, 'loaders')
   ]
  }
};

这应该是你开始的所有内容。但是,如果您已经为您的加载器创建了一个单独的存储库,您可以在您想要运行测试的项目中使用npm链接到该项目。

根据加载器的使用方式,有不止一种方法,因此——自然地——我们将从简单的使用开始。

简单使用

简单加载器的概念已经被提到过,即加载器在执行非常简单和特定的任务时更有用。这将使测试更容易,因为有这么多加载器,它们可以链接到其他加载器以执行更多样的任务。

当单个加载器应用于资源时,加载器只会被调用一个参数。这是一个包含正在加载的资源内容的字符串。

同步加载器可以返回表示转换模块的单个值。在更复杂的情况下,加载器可以通过使用以下函数返回任意数量的值:this.callback(err, values...)

然后错误要么传递给函数,要么在同步加载器中抛出。

在这种情况下,加载器预计会返回一个或两个值。第一个值是一些作为字符串的结果 JavaScript 代码。第二个值是可选的,会产生一个SourceMap和 JavaScript 对象。

在加载器被链接的情况下,加载器往往变得更加复杂。当讨论自定义加载器的复杂用法时,这将是一个很好的开始,所以现在让我们开始吧。

复杂用法

如前一小节所讨论的,简单用法,复杂用法通常指的是在上下文中使用加载器或一组加载器进行链接模式。

当多个加载器被链接在一起时,重要的是要记住它们是以相反的顺序执行的!

这将是从右到左或从下到上,取决于您使用的数组格式。例如,以下将适用:

  • 最后一个加载器首先被脚本调用,将传递原始资源的内容(加载器运行的数据或脚本)。

  • 第一个加载器,称为最后一个,预计会返回 JavaScript 和一个可选的源映射。

  • 中间的加载器将使用链中前一个加载器的结果进行执行。

因此,在以下常见示例中,foo-loader将传递原始资源,而bar-loader将接收foo-loader的输出,并返回最终转换的模块和源映射(如果需要)。

要以这种方式链接加载器,从webpack.config.js配置文件开始,就像这样:

module.exports = {
  //...
  module: {
    rules: [
      {
        test: /\.js/,
        use: [
         'bar-loader',
         'foo-loader'
        ]
      }
    ]
   }
 };

这是配置,但由于链接加载器本质上是复杂的,最好遵循一个标准。接下来是一组指南,将帮助您的项目在不受影响的情况下完成。

指南

在编写加载器时,应遵循以下指南。它们按重要性排序,有些只适用于特定情况:

  • 简化加载器的目的

  • 利用链接

  • 模块化输出

  • 确保无状态

  • 使用加载器实用程序

  • 标记加载器依赖关系

  • 解决模块依赖关系

  • 提取公共代码

  • 不惜一切代价避免绝对路径!

  • 使用对等依赖

让我们更详细地讨论每一个,从简化开始。

简化加载器的目的

加载器在执行简单和清晰的任务时效果最佳。这可以使每个加载器的维护工作更简单,也可以允许链式使用在更复杂的任务中。这是因为可能有许多不同的加载器针对不同的任务;因此,为了允许多样性,它们经常被顺序使用。

因此,就像 Webpack 捆绑包一样,它们应该是模块化的。因此,它们执行的特定任务可以被隔离和完善,这也将允许在与其他加载器链式使用时更广泛地应用。这将引出下一个概念:加载器的链接。

利用链接

利用加载器可以链接在一起的事实。不要编写一个处理多个任务的单个加载器,而是编写多个加载器。将它们隔离开不仅使每个加载器简单,还可以使它们用于更多样的任务。

例如,当使用加载程序选项或查询参数指定数据来呈现模板文件时,可以编写为单个加载程序,该加载程序从源代码编译模板,执行它,并返回一个导出包含 HTML 代码的字符串的模块。但是,在以下准则中,存在一个简单的apply-loader,可以与其他开源加载程序链接:

  • jade-loader:这将模板转换为导出函数的模块。

  • apply-loader:这将使用加载程序选项执行函数并返回基本的 HTML 代码。

  • e idea HTML-loader:这个加载程序接受 HTML 代码并输出一个有效的 JavaScript 模块。

  • 加载程序可以链接,这意味着它们不一定必须输出 JavaScript,只要链中的下一个加载程序可以处理其输出。

Webpack 始终是模块化的,因此在使用链接加载程序时,让我们看一下关于这方面的建议。

模块化输出

在最好的情况下,您应该始终保持输出模块化。加载程序生成的模块应遵循与普通模块相同的设计启发。

这可能是显而易见的原因,但与现有项目的兼容性意味着应该遵循这一标准。这对于加载程序的链接也将变得越来越重要,正如之前讨论的那样。加载程序通常按顺序与彼此一起使用,并且许多 Webpack 项目需要安装和使用其中许多加载程序。

因此,遵循模块化输出约定将防止项目变得过于复杂,并且实际上可能导致 Webpack 捆绑的目的发生逆转,即使应用程序更小,更简洁和更优化。

这种传统的遵循或标准格式对大多数开发人员来说将是第二天性的,但在使用 Webpack 时,可能会有一些兼容性考虑您可能已经忽略了,因为它们对捆绑是如此特殊。其中一个考虑因素是加载程序的“状态”。

确保无状态

确保加载程序在模块转换之间不保留状态。每次运行都应始终独立于其他已编译的模块。

在编译过程中,您可能需要运行多个构建,以微调您的捆绑包,您不希望在每次运行时进行更正,而是保留每个构建的原始状态。

如果出现问题,这将使跟踪变得更加容易,因为您始终可以从源文件重新开始,而无需从命令行会话的最开始开始。

考虑与其他加载程序的兼容性也很重要。由于这是加载程序之间的约定,除非对于您的加载程序执行其特定任务是基本必要的,否则您应该保持相同的约定,并且如果是这样,应该向开发人员明确说明,以免出现错误。

如果传递状态对于您的加载程序的功能是必要的,那么有一个方便的解决方案可以提供对约定的遵循:加载程序实用程序包。

使用加载程序实用程序

为什么不利用loader-utils包呢?它提供了各种有用的工具,但其中最常见的之一是能够检索正在使用的任何加载程序传递的选项。除了loader-utils,还应该使用schema-utils包进行一致的基于 JSON 模式的验证。以下代码块显示了一个利用这两个包的示例,使用loader.js

import { getOptions } from 'loader-utils';
 import validateOptions from 'schema-utils';
const schema = {
  type: 'object',
  properties: {
    test: {
     type: 'string'
    }
  }
 };
export default function(source) {
  const options = getOptions(this);
  validateOptions(schema, options, 'Example Loader');

现在,我们可以对源代码应用一些转换,就像这样:

return `export default ${ JSON.stringify(source) }`;
 }

这种转换将字符串化代码-基本上,将内容输出为一行代码,这对人类来说很难阅读,但对计算机来说是理想的。这通常也有助于隐私问题,如果有人希望手动复制代码。了解了这一点,让我们继续讨论加载程序依赖项的准则。

标记加载程序依赖项

如果加载器使用外部资源,比如从文件系统中读取,加载器必须指示这一点。这些信息用于使“可缓存”的加载器失效,并在监视模式下重新编译它们。接下来是一个简短的示例,说明如何使用loader.js中的addDependency方法来实现这一点:

import path from 'path';
export default function(source) {
  var callback = this.async();
  var headerPath = path.resolve('header.js');
  this.addDependency(headerPath);
  fs.readFile(headerPath, 'utf-8', function(err, header) {
    if(err) return callback(err);
    callback(null, header + '\n' + source);
  });
 }

加载器和模块依赖之间存在一些差异。现在让我们讨论后者。

解析模块依赖

根据您使用的模块类型的不同模式,可能会使用不同的模式来指定任何依赖项。例如,在层叠样式表CSS)中,使用@importURL(...)语句。在这种情况下,这些依赖项应该由模块系统解析。

可以通过以下两种方式之一实现这一点:

  • 通过将语句转换为require语句

  • 使用this.resolve函数来解析路径

css-loader是第一种方法的一个很好的例子。它通过将@import语句替换为对其他样式表的请求和将url(...)替换为对被引用文件的请求,将依赖项转换为require语句。

关于 LESS 加载器,每个@import语句都无法转换为require语句,因为在单次迭代中必须编译更少的文件。因此,LESS 加载器将使用自定义路径解析逻辑来扩展 LESS 编译器。然后使用this.resolve方法来解析依赖项。

如果您使用的语言只接受相对统一资源定位符URL),则可以使用~波浪号约定来指定对安装模块的引用。例如url('~some-library/image.png')

提取公共代码

作为最佳实践的一部分,应该避免在加载器过程的每个模块中生成公共代码。更好的方法是在加载器中使用运行时文件,并生成对任何共享模块的require语句过程。这更适合 Webpack 解析代码的方式。

Webpack 的基本目的是编译项目,以便代码不会重复,因此这可能是不言而喻的,但加载器本身应该这样做,而不是留给 Webpack 核心处理。否则,应用程序将会变得非常庞大,或者编译时间会变得非常长。

如果您有编程插件的经验,您可能会忽视这个非常明显的原则,但是在这里提到它是值得的,因为它对于 Webpack 的操作和流程非常重要。

避免绝对路径

正如所暗示的,不应该在与模块相关的任何代码中插入绝对路径,因为如果根目录被移动,哈希将会破坏。另外,请注意,在loader-utils加载器中有一个stringifyRequest方法,可以用来将绝对路径转换为相对路径,以帮助自动化您的流程。

参考第二章,使用模块和代码拆分,如果您认为需要的话,可以重新了解绝对路径。

与公共代码一样,这是 Webpack 工作的非常基本的部分,如果在编写过程中没有考虑到这一点,您可能会忽视它,因此值得一提。相对路径是正确的方式。

关于标准的最后一点与对等依赖项有关。现在让我们来看看这些。

使用对等依赖项

在开发一个简单的包装器加载器时(基本上是在更高级代码内部充当外壳的代码),操作代码或包应该作为peerDependency包含。这是因为它将允许您使用package.json文件指定包的确切版本。

在下面的示例中,sass-loadernode-sass指定为对等依赖项。看一下代码:

{
 "peerDependencies": {
   "node-sass": "⁴.0.0"
  }
}

这对于兼容性问题可能非常有价值,特别是在复杂的编程项目中。

单元测试

到目前为止,我们已经编写了一个自定义加载器,遵循了指南,甚至在本地运行了它。下一步是测试。以下示例是一个简单的单元测试过程。它使用babel-jest Jest框架和一些其他预设,允许使用import/exportasync/await方法:

  1. 我们将首先安装并保存这些内容,称为devDependencies,如下所示:
npm install --save-dev jest babel-jest babel-preset-env 

前面的命令行条目以开发模式安装了Jest框架和BabelJest

  1. 接下来,我们必须查看webpack.config.js中使用的配置,关于这个特定的单元测试过程,如下所示:
.babelrc
{
 "presets": [[
 "env",
 {
 "targets": {
 "node": "4"
 }
 }
 ]]
 }
  1. 示例中加载器的功能是处理文本文件,并用加载器提供的选项替换[name]的任何实例。然后,它输出一个包含文本的有效JavaScript模块作为其默认导出,如下例所示,位于src/loader.js中:
import { getOptions } from 'loader-utils';
export default function loader(source) {
 const options = getOptions(this);
source = source.replace(/\[name\]/g, options.name);
return `export default ${ JSON.stringify(source) }`;
 }
  1. 这个加载器将用于处理以下文本文件,名为test/example.txt
Hi Reader!
  1. 下一步有点复杂。它使用Node.js API 和memory-fs来执行 Webpack。这将避免内容输出到本地硬盘(非常方便),并使我们能够访问可以用来控制我们转换模块的统计数据。它从以下命令行开始:
npm install --save-dev webpack memory-fs
  1. 安装完成后,我们需要在其关联的编译器脚本上做一些工作。使用以下**test/compiler.js **文件:
import path from 'path';
 import webpack from 'webpack';
 import memoryfs from 'memory-fs';
export default (fixture, options = {}) => {
 const compiler = webpack({
  context: __dirname,
  entry: `./${fixture}`,
  output: {
   path: path.resolve(__dirname),
   filename: 'bundle.js',
 },
 module: {
  rules: [{
  test: /\.txt$/,
  use: {
   loader: path.resolve(__dirname, '../src/loader.js'),
   options: {
     name: 'Alice'
    }
   }
 }]
 }
});
compiler.outputFileSystem = new memoryfs();
return new Promise((resolve, reject) => {
  compiler.run((err, stats) => {
 if (err) reject(err);
 if (stats.hasErrors()) reject(new Error(stats.toJson().errors));
resolve(stats);
 });
});
};

在前面的例子中,我们内联了我们的配置,但是也可以将配置作为export函数的参数来接受。这允许使用相同的编译器模块测试多个设置。

  1. 这样做之后,我们现在可以编写我们的测试并添加一个npm脚本来运行它。让我们首先将以下代码添加到我们的test/loader.test.js文件中:
import compiler from './compiler.js';
test('Inserts name and outputs JavaScript', async () => {
 const stats = await compiler('example.txt');
 const output = stats.toJson().modules[0].source;
expect(output).toBe('export default "Hi Reader!\\n"');
 });
 package.json
{
 "scripts": {
  "test": "jest"
 }
 }

前面的代码块显示了测试函数,它加载示例文本,如果程序工作正常的话。

现在一切都应该就绪了。

  1. 现在可以运行代码了,我们将通过在命令行中运行npm构建并查看命令行窗口来检查新加载器是否通过了测试,如下所示:
 Inserts name and outputs JavaScript (229ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.853s, estimated 2s
Ran all test suites.

如果您在相应文件中看到类似前面文本的内容,那么它通过了测试。干得好!

此时,您应该能够开发、测试和部署您的加载器。不要忘记与 Webpack 社区的其他成员分享您的创作,并帮助扩展开发人员的能力,同时留下您的印记。

在本指南中,我们涵盖了大量内容,我想您现在感觉像一个专家,已经构建了自定义库和加载器,但是进行实时编码会使您的技能更加令人印象深刻。这些是有用的知识,特别是在自定义工作中,解决方法和临时方法比经过试验的代码更有可能,所以让我们开始吧!

实时编码技巧

在这一部分,我们将看到一些非常有趣的东西,这些东西会让专家感觉像超级英雄,如果他或她陷入困境或者只是想炫耀的话。我们将讨论与HMR很好配合的加载器,比如monkey-hot-loaderreact-hot-loader,以及eval__Eval的各种用途。

首先,您应该注意HMR存在一个副作用。当它更新时,它总是重新评估整个模块。这包括依赖链,它会更新指向新模块。然而,我们可能只想让原始模块评估新代码,而不是整个模块。幸运的是,有一个绕过这个问题的方法:使用monkey-hot-loader

Monkey Hot Loader

这也意味着,如果您的模块具有副作用,比如启动服务器,那么monkey-hot-loader就不太适用。如果有全局状态,情况就不一样了,但如果编写正确,就不应该有全局状态。

另外,请注意,当我们通过“猴子补丁”来更改一个模块并用更新的内容来修补原始模块时。

在本节中,我们将详细探讨如何对顶级函数进行补丁。

monkey-hot-loader是一个 Webpack 加载程序,它解析 JavaScript 文件并提取文件中所有顶级函数的名称:

  1. 例如,看一下以下代码,我们将其放在app.js文件中,但可以放在任何地方,因为该代码在全局范围内工作,并且将影响顶级函数:
function foo() {
  return 5;
}
function bar() {
  return function() {
  // ...
 }
}
module.exports = function() {
 // ...
}

关于前面的例子,monkey-hot-loader的最新版本目前只提取函数名称foobar,因为只有这些函数可以进行补丁。还有一些其他类型的函数可以被制作成可补丁的,但为了解释的简单起见,我们现在先保持简单。

foobar函数可以通过设置它们为新函数来进行补丁。这是因为它们是顶级函数。更新后,函数将在相同的作用域中创建。然后很容易在那里注入新代码。

唯一的真正问题是,关于导出函数:使用导出函数的模块仍将引用旧的变体。需要做大量的工作来解决这个问题,我们现在将详细介绍。

这些函数的名称将被提供给monkey-hot-loader附加到每个模块的运行时代码。

  1. 当模块最初运行时,它将遍历这些名称,并使每个函数都可以进行补丁,我们通过以下代码来实现这一点:
var patched = function() {
 if(patchedBindings[binding]) {
  return patchedBindings[binding].apply(this, arguments);
 }
 else {
  return f.apply(this, arguments);
 }
};
 patched.prototype = f.prototype;

在这里,如果我们要对其进行补丁,f变量将引用名称foo。请注意,patched的语义应与f变量相同。对patched变量的任何调用应产生与对f的调用相同的结果。

在保持foo的初始语义的同时,我们安装了一个“钩子”来执行检查,以查看是否有新版本的函数要调用。在所有顶级函数都用这种变体替换后,我们可以通过将函数加载到patchedBindings中来简单地覆盖其中的任何一个。即使导出的函数也将调用新的变体。

目前,monkey-hot-loader将此顶级函数补丁实现为一个初始实验。您可以考虑使用backend-with-webpack项目来玩一下,看看如何将其与您的应用程序集成。

根据上下文,可能需要采用不同的启发式方法进行补丁。例如,如果您的前端使用React,那么大部分代码将存在于React库组件内。react-hot-loader对此效果非常好。但是,对于后端代码,大部分可能是类和方法,这种情况下最好的方法是对原型上的方法进行补丁。

React 热补丁

react-hot-loader的工作原理是将原始模块绑定到任何新代码,无论是函数、类还是方法。它将修补所有React组件的方法以使用新方法。

这留下的问题是如何对原始模块进行补丁。如果尝试接受更新,事情将变得非常复杂。例如,如果更改闭包内的代码,将很难在不丢失现有状态的情况下对闭包进行补丁。可能可以使用React引擎的调试器 API,但要在整个项目中进行此更改可能会很困难:困难程度将在很大程度上取决于您的具体上下文。

请注意,当对闭包进行补丁时,最好只允许进行非常基本的补丁,因为直观地跟踪一切是很容易的。

当我们处理复杂和定制项目时,通常需要进行补丁代码。Webpack 中非常常见的一个工具是一个内在的补丁工具叫做eval。我们现在将更仔细地看一下它。

Eval

无论是 React 热修补还是 Monkey 热加载器,我们都可以通过eval在整个模块范围内安装所有这些补丁版本。之后,我们保存模块的范围。这只会在模块第一次运行时发生:

  1. 如果我们需要在特定范围内更改代码的能力,可以通过创建一个eval代理来在app.js文件中维护任何状态来实现这一点:
var moduleEval = function(some code) {
 return eval(some code);
 }

此函数稍后通过处理程序传递给此模块的未来变体。

虽然前面提到的所有事情都发生在所讨论的模块的初始运行中,但通过不同的路径进行连续更新。在这种情况下,整个模块将再次进行评估,而不是迭代每个顶级绑定,然后调用func.toString()返回函数代码,然后使用moduleEval在原始模块的范围内重新评估代码-引用原始状态。

  1. 然后,将此eval函数安装在patchedBindings中,以便在系统未来的任何调用中使用:
bindings.forEach(function(binding) {
 // Get the updated function instance
 var f = eval(binding);
// We need to rectify the function in the original module so
 // it references any of the original state. Strip the name
 // and simply eval it.
 var funcCode = (
 '(' + f.toString().replace(/^function \w+\(/, 'function (') + ')'
 );
 patchedBindings[binding] = module.hot.data.moduleEval(funcCode);
 });

在理想情况下,我们可能会获取模块的源代码更新,并避免运行模块,因为我们只想要代码的字符串形式。

事实上,可以避免整个func.toString()moduleEval过程,而简单地不支持任何全局状态,尽管全局状态对于调试操作非常有用。这对于简单的 REPL(读取-评估-打印循环)交互特别有效。然而,类没有这个问题,因为它们的所有状态都是实例的一部分,这就是为什么react-hot-loader可以在没有这个技巧的情况下正常工作。

对于未经培训的人来说,REPL 也被称为交互式顶层或语言外壳,它接受单个用户输入并对其进行评估。

请注意,在 Webpack 5 中,eval()在生产模式下存在与optimization.innerGraph相关的已知问题。

对于_eval,有一个可用的技巧,这是非常有用的。现在让我们来看看这一部分。

__Eval 技巧

现在是最后一个技巧的时间,这可能是整本书中最大的技巧!_eval()函数将字符串作为函数的表达式进行评估。这可以与热加载一起使用,以允许立即评估整个项目中的代码。这本质上就是_Eval的技巧。现在让我们更深入地探讨一下。

如果我们想要一个 REPL 来评估模块内的代码,并且能够打开模块以选择要在其中评估的上下文,那么我们无法使用这种基础设施,但我们可以通过app.js文件中的以下示例实现大部分功能:

function __eval() {
 var user = getLastUser();
 console.log(findAllDataOn(user));
 }

完成这些操作并定义一个名为__eval的函数后,monkey-hot-loader将在模块更新时执行它。这对于即时反馈非常有用。通过这种方法,您可以调用一些 API 并记录结果,然后即时对这些 API 进行调整,直到看到想要的结果。这样,您只需进行一些编码更改,保存文件,然后立即查看更新后的输出。

此外,您可以使用__eval的代码形式作为全局使用的脚本,并允许典型的 HMR 系统在每次更新模块时运行。也就是说,任何具有副作用的模块都需要专门的代码。此外,您可以在评估中构建跨评估的状态以进行调试。

与旧的 Lisp 风格不同(即选择代码并按 Ctrl + E 运行),此技术是针对每个模块进行的,并且您可以选择要在其中运行代码的上下文。

一个考虑是,在strict模式下,不能使用_eval函数引入新变量,例如更改变量的值。这也适用于__eval,因此在开始之前值得记住。

总结

本章带您了解了 Webpack 的一些更高级的功能,比如库编写和实时编码技巧,使用_Eval技术进行热加载,以实现项目间的即时反馈。这包括了如何自定义加载器的详细解释和示例,甚至修补顶层函数。

现在,您应该对手动捆绑和实时编码有足够深入的了解,可以与任何专家并驾齐驱。为什么不通过在本章末尾的测验中展示这种专业知识来向自己证明呢?如果您在工作面试或向重要客户做演示时能够快速表达自己的专业知识,这将对您大有裨益。

整本书都详细而全面地介绍了如何熟练使用 Webpack,并将应用程序开发提升到全新的水平。随着您的 Webpack 捆绑的发展,本章将变得越来越重要,您可以确信,这一章将成为未来许多项目的书签。

一旦您尝试了练习测验问题,您可能想翻回书的开头,对每一章进行自我测试。您将在本指南后面的一个单独章节中找到评估答案。成功完成将使您的专业知识毋庸置疑,所以试一试吧。

问题

  1. Webpack 可以用来捆绑库以及应用程序吗?

  2. 在编写库时,如何将外部库排除在捆绑包之外?

  3. Webpack 提供了四种公开自定义库的方式。它们是什么?

  4. 在构建自定义模块时为什么不应该使用绝对路径?

  5. __eval的函数前缀如何帮助实现即时反馈?

  6. 为什么开发人员必须通过加载器指示读取外部资源,比如文件系统?

  7. 当加载器被链接时,它们是如何执行的?

第九章:评估

答案

这部分包括每章末尾给读者的问题的答案。读者在自测或他人时应参考这一部分。

建议仔细阅读每一章,或者尽可能多次阅读,以充分消化信息。

在此之后,您可能希望拿一张废纸,把每组问题的答案写下来,然后再转到这个评估部分,检查一下您的答案是否正确。您可能想为每个正确答案给自己打分,并看看以后能否提高这个分数。

祝你好运!

第一章:Webpack 5 简介

  1. Webpack 是 JavaScript 应用程序的模块打包工具。

  2. 捆绑包是 Webpack 生成的输出文件。它包含应用程序中使用的所有模块。捆绑包生成过程由 Webpack 配置文件调节。

  3. 4.29.6 或 4.0 是可以接受的。

  4. Node.js

  5. 每当一个文件依赖于另一个文件时,Webpack 都将其视为依赖项。

  6. install

  7. NPM

  8. lodash调用前加上减号。

  9. “分发”代码是经过最小化和优化的输出

最终将在浏览器中加载的构建过程的一部分。

  1. 为了确保我们保持我们的软件包私有,还要删除主入口。通过这样做,我们可以防止意外发布您的代码。

第二章:使用模块和代码拆分

  1. 代码拆分是自动将编程组织成模块化格式的过程。模块化编程是更广泛的概念。

  2. Chunk 指的是一组模块。

  3. 动态导入实质上是对 Webpack 的按需导入。入口点是一个固定和配置的入口点,构建开始处理代码。

  4. 模块块使用预加载指令与其父“块”并行加载,而预取的块在父块完成加载后开始。在预加载时必须由父块立即请求,而预取块可以随时使用。使用预加载指令的在调用时立即下载。预取块在浏览器空闲时下载。

  5. 代码检查是删除不需要的或多余的代码的过程。

  6. 承诺是指从加载器返回的信息。

  7. **SplitChunksPlugin**允许将常见依赖项提取到入口块中。

  8. 这个工具将分析您的捆绑包并提出减少捆绑包大小的建议。

  9. 这提供了一个用于 Webpack 统计的交互式饼图。

  10. 这是一种使用嵌套图形(通常是矩形)显示分层数据的方法。

第三章:使用配置和选项

  1. 配置是通过一组配置文件完成的,选项是使用命令行设置的。

  2. 一种命令行技术,通知捆绑器使用哪个配置文件。

  3. 文件加载器。

  4. JSON 文件。

  5. Webpack 生成的每个文件。

  6. 这将强制 Webpack 退出其捆绑过程。

  7. 此选项将限制并行处理模块的数量。

  8. 它将指定从中读取最后一组记录的文件

  9. 它将禁用AMD支持。

  10. 编译是 Webpack 5 组装包括资产在内的信息的过程

第四章:API 加载器和插件

  1. 国际化(*i18n)是准备软件以支持本地语言和文化设置的过程

  2. Babel

  3. ECMAScript转换为早期版本以实现兼容性。

  4. 加载器构建器

  5. 加载器允许您编写混合的 HTML、CSS 和 JavaScript Polymer 元素

  6. 能够创建混合使用 HTML、CSS 和 JavaScript 文件并在 Webpack 环境中处理它们。

  7. 编译器。

第五章:库和框架

  1. Vue 的模板编译器。

  2. Main.tsVendor.ts

  3. Node v6.9.0已安装和Webpack v4.0.0.

  4. 为了检查它们的命令行界面。

  5. 单页面应用程序。

  6. Webpack 的配置文件。

  7. @符号:import '@angular/http';

  8. HTML 文件中的 JavaScript 标签。

  9. 类似于 NPM 的开源包管理器,是 Node.JS 的一部分。

  10. 仅运行时ECMAScript模块编译

第六章:部署和安装

  1. 当使用被认为冗长的语言时,程序员必须编写大量代码才能实现较小的功能。这样的代码被称为样板文件。

  2. 树摇是指死代码消除的术语。

  3. 基本上是为了填充或修补代码。

  4. 提供在线原生应用体验。

  5. 处理代码的自动化。

  6. Gulp,Mocha 和 Karma。

  7. ECMAScript 2015,CommonJS 和 AMD。

第七章:调试和迁移

  1. 热模块替换。

  2. 它实时调整 React 组件。

  3. 命令行

  4. 检查标志。

  5. 模块类型。

  6. 通过防止意外丢弃代码。

  7. 配置文件webpack.config.js

第八章:编写教程和实时编码

  1. 是的。

  2. 逐个或使用正则表达式。

  3. 作为变量,一个对象,通过窗口或使用UMD

  4. 当项目的根目录移动时,它可能会导致哈希破坏

  5. 因为它将在每次模块更新时执行它

  6. 因为这些信息用于使可缓存的加载程序失效并在监视模式下重新编译。

  7. 按相反顺序,根据数组格式从右到左或从底部到顶部。

posted @ 2024-05-22 12:07  绝不原创的飞龙  阅读(187)  评论(0编辑  收藏  举报