如今,我们不得不使用许多辅助工具来促进、加速和优化我们的 Web 开发工作流程。但是,此类工具通常会在堆栈中增加一层额外的复杂性。因此,我们需要花费额外的时间和精力来学习、理解和正确使用这些工具。webpack也是如此。

第一次使用 webpack 时,可能很难理解它是如何工作的以及应该如何使用。尽管它有很好的文档,但它可能会让新手望而生畏,而且学习曲线陡峭。但是,webpack 值得学习,从长远来看可以节省大量时间和精力。在本教程中,我将介绍所有核心概念以帮助您入门。

什么是 Webpack?

作为其核心,webpack 是一个静态模块打包器。在特定项目中,webpack 将所有文件和资产视为模块。在幕后,它依赖于一个依赖图。依赖图描述了模块如何使用文件之间的引用( requireimport语句)相互关联。通过这种方式,webpack 静态遍历所有模块来构建图形,并使用它来生成单个包(或多个包)——一个 JavaScript 文件,其中包含以正确顺序组合的所有模块的代码。“静态”意味着,当 webpack 构建它的依赖关系图时,它并不执行源代码,而是将模块和它们的依赖关系拼接在一起成为一个包。然后可以将其包含在您的 HTML 文件中。

现在,为了扩展上面的粗略概述,让我们探索 webpack 使用的主要概念。

Background Image

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 参数更改为developmentproductionnone来设置我们想要生产哪一个。这允许 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 devnpm 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! 控制台中的消息。

显示的 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>

现在让我们扩展我们的项目并为entryoutput属性指定自定义名称。我们在属性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.jsapp.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! 显示在页面上。

显示的 Webpack 浏览器消息

此外,如果我们检查 的来源,我们会看到标记中的属性index.html值已更新为。srcscriptmain.bundle.js

此时,我们可以删除distwebpack 最初生成的文件夹,因为我们不再需要它了。

将现代 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 功能(箭头函数和声明constcomponent.js不会转换为符合 ES5 的代码。为了让我们的代码在旧浏览器中工作,我们必须添加 Babel 加载器:

npm install babel-loader @babel/core @babel/preset-env --save-dev

然后,在属性之后webpack.config.js添加:moduleoutput

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 浏览器消息

资产管理

大多数情况下,您的项目将包含图像、字体等资产。在 webpack 4 中,要使用资产,我们必须安装以下一个或多个加载器:file-loaderraw-loaderurl-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上面的图像。信息。

显示的 Webpack 图像组件

deploy如果您现在查看该文件夹,您会发现其中生成了三个文件: a1af828b4e65d37668e1.pngmain.bundle.jsindex.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 文件中的颜色属性更改为绿色,您应该会看到页面中的颜色是如何适当更新的。

Webpack 开发服务器在行动

清理输出

随着我们项目的进展,该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) 的精彩介绍性视频课程。

Webpack 新手指南
标签: