❝如今,我们不得不使用许多辅助工具来促进、加速和优化我们的 Web 开发工作流程。但是,这些工具通常会在堆栈中增加一层额外的复杂性。因此,我们需要利用额外的时间和精力来正确学习、理解和使用这些工具。「webpack」也是如此。
❞
第一次使用 webpack 时,可能很难理解它是如何工作的以及应该如何使用它。尽管它有很好的文档,但对于新手来说可能会令人生畏,而且它的学习曲线很陡峭。但是,webpack 值得学习,从长远来看可以节省相当多的时间和精力。在本教程中,我将介绍所有核心概念以帮助您入门。
「注意:在本教程中,我使用了 webpack 5.9.0」。
什么是 Webpack?
作为其核心,webpack 是一个「静态模块打包器」。在特定项目中,webpack 将所有文件和资产视为模块。在幕后,它依赖于依赖图。依赖图描述了模块如何使用文件之间的引用(require和import语句)相互关联。通过这种方式,webpack 静态遍历所有模块来构建图,并使用它生成单个包(或多个包)——一个 JavaScript 文件,其中包含以正确顺序组合的所有模块的代码。“静态”意味着,当 webpack 构建其依赖关系图时,它不会执行源代码,而是将模块及其依赖项拼接成一个包。然后可以将其包含在您的 HTML 文件中。
现在,为了扩展上述粗略的概述,让我们探索 webpack 使用的主要概念。
Webpack 主要概念
Webpack 有一些主要概念,我们需要在深入了解其实际实现之前清楚地了解这些概念。让我们一一检查它们:
「入口」:入口点是 webpack 用来开始构建其内部依赖图的模块。从那里,它确定入口点依赖于哪些其他模块和库(直接和间接)并将它们包含在图中,直到没有依赖关系为止。默认情况下, entry 属性设置为
./src/index.js
,但我们可以在 webpack 配置文件中指定不同的模块(甚至多个模块)。「Output」 : output 属性指示 webpack 在哪里发出包以及用于文件的名称。此属性的默认值用于
./dist/main.js
主包和./dist
其他生成的文件——例如图像。当然,我们可以根据需要在配置中指定不同的值。「Loaders」:默认情况下,webpack 只理解 JavaScript 和 JSON 文件。为了处理其他类型的文件并将它们转换为有效的模块,webpack 使用加载器。加载器转换非 JavaScript 模块的源代码,允许我们在将这些文件添加到依赖关系图之前对其进行预处理。例如,加载器可以将文件从 CoffeeScript 语言转换为 JavaScript 或将内嵌图像转换为数据 URL。使用加载器,我们甚至可以直接从 JavaScript 模块导入 CSS 文件。
「插件」:插件用于加载器无法完成的任何其他任务。他们为我们提供了关于资产管理、捆绑最小化和优化等的广泛解决方案。
「模式」:通常,当我们开发应用程序时,我们使用两种类型的源代码——一种用于开发构建,一种用于生产构建。Webpack 允许我们通过将 mode 参数更改为development、production或none来设置我们想要生产的产品。这允许 webpack 使用与每个环境相对应的内置优化。默认值为生产。在没有模式意味着没有预设的优化选项将被使用。要了解有关 webpack 在开发和生产模式中使用的选项的更多信息,请访问「模式配置页面」。
Webpack 的工作原理
在本节中,我们将研究 webpack 是如何工作的。即使是一个简单的项目也包含 HTML、CSS 和 JavaScript 文件。此外,它还可以包含字体、图像等资产。因此,典型的 webpack 工作流将包括index.html
使用适当的 CSS 和 JS 链接以及必要的资产来设置文件。此外,如果您有许多相互依赖的 CSS 和 JS 模块,则需要对它们进行优化并正确组合在一个单元中以备生产。
要做到这一切,webpack 依赖于配置。从版本 4 及更高版本开始,webpack 提供了开箱即用的合理默认值,因此不需要创建配置文件。但是,对于任何重要的项目,您都需要提供一个特殊webpack.config.js
文件,它描述了文件和资产应该如何转换以及应该生成什么样的输出。这个文件很快就会变得单一,除非你知道它工作背后的主要概念,否则很难理解 webpack 是如何工作的。
基于提供的配置,webpack 从入口点开始,解析它在构建依赖图时遇到的每个模块。如果模块包含依赖项,则对每个依赖项递归执行该过程,直到遍历完成。然后 webpack 将项目的所有模块打包成少量的包---通常只有一个——由浏览器加载。
Webpack 5 中的新功能
2020 年 10 月发布了 webpack 5 版本。这篇文章很长,探讨了对 webpack 所做的所有更改。不可能提及所有更改,并且对于这样的初学者指南来说是不必要的。相反,我将尝试列出一个包含一些一般亮点的小清单:
持久缓存提高了构建性能。开发人员现在可以启用基于文件系统的缓存,这将加快开发构建。
长期缓存也得到了改进。在 webpack 5 中,对不影响最小化包版本(注释、变量名称)的代码所做的更改不会导致缓存失效。此外,还添加了新算法,这些算法以确定的方式为模块和块分配短数字 ID,并将短名称分配给导出。在 webpack 5 中,它们在生产模式下默认启用。
由于更好的 Tree Shaking 和代码生成,改进了包的大小。由于新的 Nested Tree-Shaking 功能,webpack 现在能够跟踪对导出的嵌套属性的访问。CommonJs Tree Shaking 允许我们消除未使用的 CommonJs 导出。
最低支持的 Node.js 版本已从 6 增加到 10.13.0 (LTS)。
代码库被清理。所有在 webpack 4 中标记为弃用的项目都被删除。
自动 Node.js polyfills 被移除。以前版本的 webpack 已经包含了原生 Node.js 库的 polyfill,比如
crypto
.在许多情况下,它们是不必要的,并且会大大增加包的大小。这就是为什么 webpack 5 停止自动填充这些核心模块并专注于前端兼容模块的原因。作为开发的改进,webpack 5 允许我们传递目标列表并支持目标版本。它提供公共路径的自动确定。而且,它提供了自动的、唯一的命名,可防止使用相同全局变量进行块加载的多个 webpack 运行时之间发生冲突。
该
webpack-dev-server
命令是现在webpack serve
。资产模块进行介绍,其中替换的用途
file-loader
,raw-loader
和url-loader
。
请打开上面的内容以查找有关所有更新的更完整和详细的信息。
最后,如果你来自 webpack 4,这里是迁移指南。
入门
注意:您可以在 「GitHub」 存储库中找到我们项目的文件。
有了扎实的理论基础,我们就来实践一下吧。
首先,我们将创建一个新目录并切换到该目录。然后我们将初始化一个新项目:
mkdir learn-webpack
cd learn-webpack
npm init -y
接下来,我们需要在本地安装 webpack 和 webpack CLI(命令行界面):
npm install webpack webpack-cli --save-dev
现在,生成的内容package.json
应该类似于以下内容:
{
"name": "learn-webpack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^5.9.0",
"webpack-cli": "^4.2.0"
}
}
除了作为包管理器之外,npm
还可以用作简单的任务运行器。我们可以通过scripts
在package.json
文件的部分中包含我们的任务名称和其说明来创建 webpack 任务。让我们现在试试这个。打开package.json
并将scripts
对象更改为以下内容:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack --mode development",
"build": "webpack --mode production"
},
在该scripts
属性中,npm
允许我们通过名称引用本地安装的 Node.js 包。我们使用它和--mode
标志来定义dev
和build
任务,它们将分别在开发 ( npm run dev
) 和生产 ( npm run build
) 模式下运行 webpack 。
在我们测试刚刚创建的任务之前,让我们创建一个src
目录并index.js
在其中放置一个文件,以便它包含console.log("Hello, Webpack!");.
现在我们已经可以运行dev
任务以在开发模式下启动 webpack:
$ npm run dev
> learn-webpack@1.0.0 dev C:\WEBDEV\learn-webpack
> webpack --mode development
[webpack-cli] Compilation finished
asset main.js 874 bytes [emitted] (name: main)
./src/index.js 31 bytes [built] [code generated]
webpack 5.9.0 compiled successfully in 122 ms
正如我之前提到的,webpack 将默认入口点设置为./src/index.js
,默认输出设置为./dist/main.js
. 所以当我们运行dev
任务时 webpack 所做的就是从index.js
文件中获取源代码并将最终代码打包到一个main.js
文件中。
太棒了!它按预期工作。但是为了验证我们得到了正确的输出,我们需要在浏览器中显示结果。为此,让我们index.html
在dist
目录中创建一个文件:
<!doctype html>
<html>
<head>
<title>Getting Started With Webpack</title>
</head>
<body>
<script src="main.js"></script>
</body>
</html>
现在,如果我们在浏览器中打开文件,我们应该会看到「Hello, Webpack!」 控制台中的消息。
到现在为止还挺好。但index.html
在某些情况下,手动编写我们的文件可能会出现问题。例如,如果我们更改入口点的名称,生成的包将被重命名,但我们的index.html
文件仍将引用旧名称。因此,每次重命名入口点或添加新入口点时,我们都需要手动更新 HTML 文件。幸运的是,我们可以使用html-webpack-plugin
. 现在让我们安装它:
npm install html-webpack-plugin@next --save-dev
注意:请注意,我输入html-webpack-plugin@next
的不仅仅是html-webpack-plugin
. 在撰写本文时,前者是 webpack 5 的正确版本,后者是 webpack 4 的版本。这可能会在未来发生变化,因此对于实际版本,请检查「html-webpack-plugin repo」。
此时,要激活插件,我们需要webpack.config.js
在根目录下创建一个文件,内容如下:
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require('path');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
title: "Webpack Output",
}),
],
};
如您所见,要激活 webpack 插件,我们需要将其包含在文件中,然后将其添加到plugins
数组中。如果需要,我们还将选项传递给插件。有关所有可用选项以及编写和使用您自己的模板的能力,请参阅html-webpack-plugin
repo。
现在让我们运行 webpack 看看会发生什么:
$ npm run dev
> learn-webpack@1.0.0 dev C:\WEBDEV\learn-webpack
> webpack --mode development
[webpack-cli] Compilation finished
asset main.js 874 bytes [compared for emit] (name: main)
asset index.html 234 bytes [emitted]
./src/index.js 31 bytes [built] [code generated]
webpack 5.9.0 compiled successfully in 151 ms
让我们打开index.html
. 正如我们所见,插件会自动index.html
为我们创建一个更新的文件,它使用title
配置中的选项:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Webpack Output</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script defer src="main.js"></script>
</head>
<body>
</body>
</html>
现在让我们展开我们的项目并为entry
和output
属性指定自定义名称。在webpack.config.js
我们在plugins
属性之前添加以下内容:
entry: {
main: path.resolve(__dirname, './src/app.js'),
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'deploy')
},
在这里,我们将入口文件更改为app.js
,将输出文件夹更改为deploy
. 我们还稍微调整了生成的包文件的名称。现在它将以条目的名称(“main”)开头,后跟单词“bundle”和.js
文件扩展名。
现在,我们将创建一个src/component.js
文件:
export default (text = "Hello, Webpack!") => {
const element = document.createElement("h1");
element.innerHTML = text;
return element;
};
接下来,我们重命名index.js
为app.js
以反映我们的更改,并将其内容替换为以下内容:
import component from './component';
document.body.appendChild(component());
现在,让我们再次运行 webpack:
$ npm run dev
> learn-webpack@1.0.0 dev C:\WEBDEV\learn-webpack
> webpack --mode development
[webpack-cli] Compilation finished
asset main.bundle.js 4.67 KiB [emitted] (name: main)
asset index.html 241 bytes [emitted]
runtime modules 668 bytes 3 modules
cacheable modules 230 bytes
./src/app.js 79 bytes [built] [code generated]
./src/component.js 151 bytes [built] [code generated]
webpack 5.9.0 compiled successfully in 194 ms
让我们检查并澄清来自 webpack 输出的信息。在“编译完成”消息后,您可以看到deploy
目录 (main.bundle.js
和index.html
) 中生成的文件。在它们下方,您可以看到源文件:入口模块 (app.js
) 及其依赖项 ( component.js
)。
所以现在,在deploy
文件夹中,我们有了新生成的包文件main.bundle.js
。如果我们index.html
在浏览器中打开文件,我们应该看到「Hello, Webpack!」 显示在页面上。
此外,如果我们检查 的来源index.html
,我们会看到标签中的src
属性值script
已更新为main.bundle.js
。
此时,我们可以删除dist
webpack最初生成的文件夹,因为我们不再需要它了。
将现代 JavaScript 转译为 ES5
在本节中,我们将了解如何将 ES6 转换为适用于所有浏览器的符合 ES5 的代码。让我们从运行以下命令开始:
npm run dev -- --devtool inline-source-map
在这里,我在devtool
选项设置为inline-source-map
的情况下运行 webpack,inline-source-map
以使代码更具可读性。这样我可以更清楚地演示从 ES6 到 ES5 的代码转换。
接下来,让我们打开main.bundle.js
:
/***/ "./src/component.js":
/*!**************************!*\
!*** ./src/component.js ***!
\**************************/
/*! namespace exports */
/*! export default [provided] [no usage info] [missing usage info prevents renaming] */
/*! other exports [not provided] [no usage info] */
/*! runtime requirements: __webpack_exports__, __webpack_require__.r, __webpack_require__.d, __webpack_require__.* */
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => __WEBPACK_DEFAULT_EXPORT__
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ((text = "Hello, Webpack!") => {
const element = document.createElement("h1");
element.innerHTML = text;
return element;
});
/***/ })
如您所见,const
来自component.js
模块的现代 ES6 特性(箭头函数和const
声明)在默认情况下不会转换为符合 ES5 的代码。为了让我们的代码在旧浏览器中工作,我们必须添加 Babel 加载器:
npm install babel-loader @babel/core @babel/preset-env --save-dev
然后,在webpack.config.js
里添加module
在output
属性之后
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
]
},
当我们为 webpack loader 定义规则时,通常需要定义三个主要属性:
test
,它描述了应该转换什么样的文件。exclude
,它定义了不应该从加载器处理的文件,如果我们有的话。use
,它告诉应该对匹配的模块使用哪个加载器。在这里,我们还可以设置加载器选项,就像我们刚刚完成的presets
选项一样。
再次运行以下命令:
npm run dev -- --devtool inline-source-map
这次main.bundle.js
编译了里面的代码:
/***/ "./src/component.js":
/*!**************************!*\
!*** ./src/component.js ***!
\**************************/
/*! namespace exports */
/*! export default [provided] [no usage info] [missing usage info prevents renaming] */
/*! other exports [not provided] [no usage info] */
/*! runtime requirements: __webpack_exports__, __webpack_require__.r, __webpack_require__.d, __webpack_require__.* */
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => __WEBPACK_DEFAULT_EXPORT__
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (function () {
var text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "Hello, Webpack!";
var element = document.createElement("h1");
element.innerHTML = text;
return element;
});
/***/ })
这就完美了。现在我们可以使用现代 JS 功能,webpack 将转换我们的代码,以便它可以被旧浏览器执行。
使用样式
在本节中,我们将看到如何将一些样式添加到我们的项目中。为此,我们需要安装两个加载器:
npm install css-loader style-loader --save-dev
css-loader
将 CSS 解析为 JavaScript 并解析所有依赖项style-loader
将我们的 CSS 输出到<style>
HTML 文档中的一个标签中。让我们在中添加必要的配置webpack.config.js
:
module: {
rules: [
...
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
},
]
},
在这里,加载器的顺序很重要。它们以相反的顺序进行评估 - 即从右到左和从下到上。在我们的例子中,css-loader
首先评估 ,然后是style-loader
。
现在,让我们创建一个文件src/style.css
:
h1 {
color: red;
}
然后我们将其导入app.js
:
import './style.css';
当我们运行 webpack( npm run dev
) 然后打开index.html
时,我们应该看到「Hello, Webpack!」 红色的消息。
资产管理
大多数情况下,您的项目将包含图像、字体等资产。在 WebPack 4,与资产的工作,我们必须安装以下装载机的一种或多种:file-loader
,raw-loader
,和url-loader
。在 webpack 5 中,正如我们之前看到的,这不再需要了,因为新版本带有内置的资产模块。
在这里,我们将探索一个带有图像的示例。让我们在webpack.config.js
中添加新规则:
module: {
rules: [
...
{
test: /\.(?:ico|gif|png|jpg|jpeg)$/i,
type: 'asset/resource',
},
]
},
在这里,使用类型asset/resource
而不是file-loader
.
现在,为了测试加载器,我们将image-component.js
在src
目录中创建一个包含以下内容的文件:
import image from "./image.png";
const img = document.createElement("img");
img.src = image;
document.body.appendChild(img);
在这里,我们将图像作为模块导入并使用它来创建<img/>
标签。要使上述代码工作,您需要下载图像,然后将其重命名为image.png
并放入src
目录中。
接下来是导入我们的图像组件app.js
:
import './image-component';
瞧。现在,当我们运行 webpack ( npm run dev
) 并打开页面时,我们应该会看到「Hello, Webpack!」 上方的图像信息。
如果你看一看的deploy
文件夹,现在,你会发现在它产生的三个文件:a1af828b4e65d37668e1.png
,main.bundle.js
,和index.js
。以下是 webpack 在幕后所做的:将图像添加到deploy
文件夹并分配唯一的哈希值,然后是图像扩展名。然后该图像main.bundle.js
作为模块包含在新生成的文件中。最后,index.html
参考该main.bundle.js
文件生成一个文件。
加快开发过程webpack-dev-server
目前,我们每次进行更改时都需要重新构建代码。幸运的是,webpack 提供了一个实时重新加载的 web 服务器,它可以自动构建和刷新页面。要安装它,请运行以下命令:
npm install webpack-dev-server --save-dev
我们需要更新我们的dev
脚本inpackage.json
以使用服务器:
"dev": "webpack serve --mode development"
现在让我们在webpack.config.js
里通过添加以下属性来配置服务器在output
之后:
devServer: {
contentBase: './deploy',
open: true
},
这告诉webpack-dev-server
从deploy
目录中提供文件并自动打开条目页面。
现在,如果我们运行 webpack( npm run dev
),我们应该会看到页面是如何在http://localhost:8080上的浏览器中自动打开的。
注意:运行webpack-dev-server
后,您将不会在deploy
文件夹中找到任何文件(它将是空的),因为服务器在编译后不会写入任何输出文件。相反,它将捆绑文件保存在内存中,并将它们视为安装在服务器根路径上的真实文件。有关更多信息,请参阅 webpack开发指南。但是,当您运行该build
命令时,该deploy
文件夹将按预期填充生成的文件。
如果我们现在更改任何源文件并保存它们,Web 服务器将在代码编译后自动重新加载页面。例如,尝试将 CSS 文件中的颜色属性更改为绿色,您应该会看到页面中的颜色是如何适当更新的。
清理输出
随着我们的项目的进行,deploy
文件夹可能会变得非常混乱。在每次构建时,webpack 都会生成包并将它们放在deploy
文件夹中,但它不会跟踪您的项目实际使用了哪些文件。所以deploy
在每次构建之前清理文件夹是一个很好的做法,这样只会生成正在使用的文件。为此,我们需要安装和配置clean-webpack-plugin
:
npm install clean-webpack-plugin --save-dev
在webpack.config.js
:
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
...
plugins: [
...
new CleanWebpackPlugin()
],
现在,运行 webpack ( npm run build
) 并检查deploy
文件夹。您现在应该只看到从构建生成的文件,而没有旧的和未使用的文件。要对其进行测试,请创建一个项目中未使用的简单文本文件,然后build
再次运行该脚本。编译后文件将被删除。
结论
Webpack 是一个有用且强大的工具。本教程仅介绍核心概念,但 webpack 提供了更多功能、插件和应用它们的不同技术,您可以随着知识的增长而采用。以下是我建议进一步探索 webpack 功能的资源列表:
「官方 webpack 文档」。该文档为您提供了有关 webpack 主要概念和配置的结构化信息,以及您可以在项目中使用的插件和加载器,以及基本指南和 API 参考。
「Webpack 5:从学徒到大师」。一个完整的手册,深入探讨了 webpack 的每个方面。由 Webpack 核心开发人员 Juho Vepsäläinen 编写。
「Webpack:核心概念」。webpack 的维护者之一肖恩·拉金 (Sean Larkin) 的精彩介绍视频课程。
❝「In the End」:
❞
如果你感觉写得不错,帮我点个[
在看
、赞
、关注
]吧让我们一起成为前端架构师!