如今,我们不得不使用许多辅助工具来促进、加速和优化我们的 Web 开发工作流程。但是,此类工具通常会在堆栈中增加一层额外的复杂性。因此,我们需要花费额外的时间和精力来学习、理解和正确使用这些工具。webpack也是如此。
第一次使用 webpack 时,可能很难理解它是如何工作的以及应该如何使用。尽管它有很好的文档,但它可能会让新手望而生畏,而且学习曲线陡峭。但是,webpack 值得学习,从长远来看可以节省大量时间和精力。在本教程中,我将介绍所有核心概念以帮助您入门。
什么是 Webpack?
作为其核心,webpack 是一个静态模块打包器。在特定项目中,webpack 将所有文件和资产视为模块。在幕后,它依赖于一个依赖图。依赖图描述了模块如何使用文件之间的引用( require和import语句)相互关联。通过这种方式,webpack 静态遍历所有模块来构建图形,并使用它来生成单个包(或多个包)——一个 JavaScript 文件,其中包含以正确顺序组合的所有模块的代码。“静态”意味着,当 webpack 构建它的依赖关系图时,它并不执行源代码,而是将模块和它们的依赖关系拼接在一起成为一个包。然后可以将其包含在您的 HTML 文件中。
现在,为了扩展上面的粗略概述,让我们探索 webpack 使用的主要概念。
Webpack 主要概念
Webpack 有一些主要概念,我们需要在深入了解其实际实现之前清楚地了解这些概念。让我们一一检查:
- Entry:入口点是 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 使用与每个环境相对应的内置优化。默认值为生产。none模式意味着将不使用默认优化选项。要了解有关 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 允许我们传递一个 target 列表,同时也支持 target 的版本。它提供公共路径的自动确定。而且,它还提供自动的、唯一的命名,这可以防止使用相同全局变量进行块加载的多个 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
我们可以创建 webpack 任务,方法是在文件部分中包含我们的任务名称和其说明package.json
。让我们现在试试这个。打开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
下运行 webpack 。npm run dev
npm run build
在我们测试刚刚创建的任务之前,让我们创建一个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
. 那么当我们运行任务时webpack所做的dev
就是从文件中获取源代码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
存储库以及编写和使用您自己的模板的能力。
让我们现在运行 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
在“Compilation finished”消息后,您可以在目录(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
在这里,我运行 webpack 并将devtool
选项设置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;
});
/***/ })
如您所见,默认情况下,模块中的现代 ES6 功能(箭头函数和声明const
)component.js
不会转换为符合 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 加载器定义规则时,通常需要定义三个主要属性:
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
首先评估 the,然后评估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( ) 并打开页面时,我们应该会看到Hello, Webpack!npm run dev
上面的图像。信息。
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
脚本package.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) 的精彩介绍性视频课程。