.ArticleCopy img { border: solid 1px gray !important; }
如今,我们不得不使用许多附属工具来促进、加快和优化我们的网络开发工作流程。但通常情况下,这些工具会在堆栈中增加一层额外的复杂性。因此,我们需要利用额外的时间和精力来学习、理解和正确使用这些工具。对于webpack来说也是如此。
当第一次使用webpack时,可能很难理解它是如何工作的以及应该如何使用它。虽然它有很好的文档,但对于新手来说,它可能是令人生畏的,而且它有一个陡峭的学习曲线。然而,webpack是值得学习的,从长远来看可以节省大量时间和精力。在本教程中,我将介绍所有的核心概念以帮助你开始学习。
注意:在本教程中,我使用了webpack 5.9.0。
什么是Webpack?
作为其核心,webpack是一个静态模块捆绑器。在一个特定的项目中,webpack将所有的文件和资产都视为模块。在引擎盖下,它依赖于一个依赖图。依赖关系图通过文件之间的引用**(require和import**语句)来描述模块之间的关系。通过这种方式,webpack静态地遍历所有模块以建立图谱,并使用它来生成一个单一的捆绑(或几个捆绑)--一个包含所有模块的代码并以正确顺序组合的JavaScript文件。"静态 "的意思是,当webpack构建其依赖关系图时,它不执行源代码,而是将模块和它们的依赖关系缝合到一个捆绑包中。然后,这可以包含在你的HTML文件中。
现在,为了扩展上述粗略的概述,让我们探讨一下webpack使用的主要概念。
Webpack的主要概念
Webpack有一些主要的概念,在深入研究其实际实施之前,我们需要清楚地了解这些概念。让我们来逐一研究它们。
- 入口:入口点是webpack用来开始构建其内部依赖关系图的模块。从那里,它确定该入口点依赖的其他模块和库(直接和间接),并将它们包括在图中,直到没有依赖性。默认情况下,入口属性被设置为
./src/index.js,但我们可以在webpack配置文件中指定一个不同的模块(甚至是多个模块)。 - 输出:输出属性指示webpack在哪里发射捆绑文件,以及为文件使用什么名字。这个属性的默认值是:
./dist/main.js,用于主包,./dist,用于其他生成的文件--比如说图像。当然,我们可以根据自己的需要,在配置中指定不同的值。 - 加载器:默认情况下,webpack只理解JavaScript和JSON文件。为了处理其他类型的文件并将其转换为有效的模块,webpack使用了加载器。装载器转换非JavaScript模块的源代码,允许我们在将这些文件添加到依赖关系图之前对其进行预处理。例如,加载器可以将CoffeeScript语言的文件转换为JavaScript,或者将内联图像转换为数据URL。通过加载器,我们甚至可以直接从我们的JavaScript模块导入CSS文件。
- 插件:插件用于加载器无法完成的任何其他任务。它们为我们提供了关于资产管理、捆绑最小化和优化等广泛的解决方案。
- 模式:通常,当我们开发我们的应用程序时,我们使用两种类型的源代码--一种用于开发构建,另一种用于生产构建。Webpack允许我们通过将模式参数改为开发、生产或无,来设置我们希望生产哪一种。这允许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中,它们在生产模式下被默认启用。
- 由于更好的树形摇动和代码生成,改进了包的大小。由于新的嵌套树摇动功能,webpack现在能够跟踪对出口的嵌套属性的访问。CommonJs树形摇动使我们能够消除未使用的CommonJs导出。
- 最低支持的Node.js版本从6增加到10.13.0(LTS)。
- 代码库被清理了。所有在webpack 4中被标记为废弃的项目都被删除。
- 自动Node.js polyfills被移除。先前版本的webpack包含了对本地Node.js库的polyfills,如
crypto。在许多情况下,它们是不必要的,而且会大大增加包的大小。这就是为什么webpack 5停止自动对这些核心模块进行polyfilling,而专注于前端兼容的模块。 - 作为开发的改进,webpack 5允许我们传递一个目标列表,也支持目标的版本。它提供了自动确定公共路径的功能。而且,它还提供了自动的、唯一的命名,这可以防止多个使用相同全局变量进行分块加载的webpack运行时之间的冲突。
webpack-dev-server命令现在是webpack serve。- 引入了资产模块,它取代了
file-loader、raw-loader、url-loader的使用。
请打开上面的公告链接,找到关于所有更新的更完整和详细的信息。
最后,如果你是从webpack 4来的,这里有迁移指南。
开始使用
注意:你可以在GitHub repo中找到我们项目的文件。
现在我们有了一个坚实的理论基础,让我们在实践中实现它。
首先,我们将创建一个新的目录并切换到它。然后我们将初始化一个新的项目。
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 还可以作为一个简单的任务运行器使用。我们可以通过在package.json 文件的scripts 部分中包括我们任务的名称和它的指令来创建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 文件中。
很好!它按预期工作。但是为了验证我们是否得到了正确的输出,我们需要在浏览器中显示结果。要做到这一点,让我们在dist 目录中创建一个index.html 文件。
<!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 的源文件,我们会看到script 标签中的src 属性的值被更新为main.bundle.js 。
在这一点上,我们可以删除webpack最初生成的dist 文件夹,因为我们不再需要它了。
继续阅读《SitePoint上的Webpack新手指南》。