1

Webpack 新手指南

 1 year ago
source link: https://blog.p2hp.com/archives/11105
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

如今,我们不得不使用许多辅助工具来促进、加速和优化我们的 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

> [email protected] 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

> [email protected] 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

> [email protected] 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值已更新为。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 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上面的图像。信息。

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 文件中的颜色属性更改为绿色,您应该会看到页面中的颜色是如何适当更新的。

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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK