早在 Javascript 引入模块化的时候,还没有办法在 Web 浏览器中支持模块。 为了解决这个问题,开发了 Parcel、Webpack 和 Rollup 等模块捆绑器。 这些有助于将多个模块优化为生产就绪的捆绑包,可以由浏览器执行。
如今,当谈到模块捆绑生态系统时,大家最常挂在嘴边的工具就是Webpack。
对我来说,webpack 曾经是相当令人生畏的,我宁愿使用像这样的工具 vue-create
如果我需要建立一个 Vue 项目或者 create-react-app
设置一个 React 应用程序,只是为了避免我在使用 webpack 时感受到的所有复杂性。
如果你和我以前一样,也就是说,你觉得用 Babel、SASS、Vue 或 TypeScript 从头开始设置 webpack,可能会很混乱,那么这篇指南适合你!
在本指南中,您将学习如何使用 Webpack 创建前端环境,就像生活中的所有事情一样,当您给自己机会学习并确保彻底理解一个概念时,您会意识到它并不太可怕毕竟。
由于有很多移动部件,我们将学习各种基本概念,让您熟悉 webpack 并构建一个 webpack 样板,以帮助您在使用广泛流行的模块捆绑器从头开始设置任何项目时启动和运行。
什么是 Webpack?
不管 webpack 有多复杂,它的核心目标仍然非常简单; 它需要一堆不同的资产、不同的文件——不同类型的; JavaScript、图像、SASS、PostCSS,无论它是什么文件,它都会将它们组合在一起(捆绑)成一小部分文件,也许是一个文件,或者它可能是 JavaScript 的一个文件、样式表的一个文件、一个一个用于第三方 JavaScript,一个用于索引 JavaScript 文件。
它是非常可配置的,这对于刚接触它的人来说有点乏味,但是,它背后的想法非常简单。 除了将事物捆绑在一起之外,它还管理应用程序的依赖关系,因此,确保需要首先加载的代码首先加载,以同样的方式,例如,如果您编写一个依赖于其他两个文件的文件,这意味着需要首先加载另外两个文件。
按照 官方 GitHub 页面:
“JavaScript 和朋友的捆绑器。 将许多模块打包到一些捆绑资产中。 代码分割允许按需加载应用程序的某些部分。 通过“加载器”,模块可以是 CommonJs、AMD、ES6 模块、CSS、图像、JSON、Coffeescript、LESS ……以及您的自定义内容。”
Webpack 的内容实在太多了,在本指南的这一点上,我们将深入熟悉 Webpack 的核心概念。
项目设置
在本教程的首选目录中,创建一个新文件夹: webpack-app
,用于容纳 webpack 项目。
$ mkdir webpack-app
接下来,让我们在项目目录中的终端中运行以下命令来初始化项目文件夹中的 Node.js:
$ cd webpack-app
$ npm init --yes
这将创建一个 package.json
文件,这使得跟踪应用程序依赖关系变得可能且容易。
现在 Node 已在我们的项目中初始化,我们可以开始安装我们需要的各种包。 要继续,让我们安装核心包: webpack
和 webpack-cli
.
$ yarn add --dev webpack webpack-cli
# Or
$ npm install --save-dev webpack webpack-cli
webpack
包将允许我们生成捆绑的 JavaScript 模块以在浏览器中使用,而 webpack-cli
是一个提供一组命令的工具,使开发人员可以轻松快速地设置自定义 webpack 项目。
首先,让我们在根目录中创建两个新文件夹 src
和 dist
。 在 src
文件夹,创建一个 index.js
文件将用作我们应用程序的根文件。
请注意: Webpack 开箱即用,不需要配置文件。 它自动假设项目的入口点是 src/index.js
它将输出一个缩小和优化的结果用于生产 dst/main.js
文件中。
配置项目
接下来,在项目的根目录中,让我们设置配置文件 webpack.config.js
定义如何在项目中形成捆绑:
$ touch webpack.config.js
时尚
该模式定义了项目当前运行的环境。 它的属性默认为 none
,但可以设置为 production
or development
,但你永远不会真正使用 none
作为一种模式。 事实上,当模式未设置时会发生什么,webpack 使用 production
作为优化的后备选项。
development
设置为模式,我们可以轻松地使用源映射来发现错误并知道原始文件在哪里。
production
设置为模式,优先级是收缩、优化和缩小文件:
const path = require("path");
module.exports = {
mode: "development",
};
条目
另一个要设置的配置是条目对象。 这定义了我们希望 webpack 从哪个文件开始构建我们的应用程序包。 如果它是单页应用程序,那么它应该有一个入口点,而如果它是多页应用程序,它应该有多个入口点。
当然,在单页应用程序中,这将是索引 JavaScript 文件 src
文件夹,在我们的例子中,它是 src/index.js
文件:
const path = require("path");
module.exports = {
entry: {
main: path.resolve(__dirname, "src/index.js"),
},
};
输出
最后,首先,我们还需要一个输出来声明在应用程序捆绑过程完成后我们想要输出文件、资产、捆绑包和其他应用程序资源的位置。 在我们的例子中,我们设置了 path
输出为 dist
文件夹,但如果您愿意,您可以重命名它,也许可以 deploy
, build
,或任何您喜欢的名称。
我们还需要设置 filename
定义输出包的名称。 常见的命名约定是 [name].bundle.js
:
const path = require("path");
module.exports = {
output: {
path: path.resolve(__dirname, "dist"),
filename: "index.bundle.js",
filename: "[name].bundle.js",
},
};
我们现在已经设置了捆绑应用程序所需的最低配置,我们可以运行它 package.json
为此,我们需要更新预定义的脚本对象 package.json
。 让我们创建一个名为的新命令 build
,这将有 webpack
作为它的价值。 保存此命令后,我们现在可以运行 webpack。
但在我们运行之前 build
命令,让我们快速在命令中包含几行 JavaScript 代码 src/index.js
文件:
console.log("Hello world! My name is Uche Azubuko.");
接下来,我们从终端使用以下命令运行 webpack:
$ yarn build
#
$ npm run build
输出:
$ webpack
asset main.bundle.js 1.24 KiB [emitted] (name: main)
./src/index.js 54 bytes [built] [code generated]
webpack 5.75.0 compiled successfully in 98 ms
Done in 0.94s.
本质上,当我们运行 build
命令, webpack
是在后台运行的命令; 将读取我们定义的配置设置和入口点,即 app.js
被捆绑、通读并输出为 main.bundle.js
,在 dist
文件夹中。
如果我们不断构建不同的点,我们会得到不同的名称,但如果我们设置 filename
至 [name].[contenthash].js
,每次我们的应用程序包完成时,我们都一定会获得编译文件的唯一名称。
现在,当我们再次构建应用程序时,我们会为输出到的文件获得一个唯一的名称 dist
文件夹,例如 main.96f31f8d4c5ec9be4552.js
:
$ yarn build
# Or
$ npm run build
输出:
$ webpack
asset main.96f31f8d4c5ec9be4552.js 1.24 KiB [emitted] [immutable] (name: main)
./src/index.js 54 bytes [built] [code generated]
webpack 5.75.0 compiled successfully in 88 ms
Done in 0.92s.
这样,浏览器就能够在每次构建时缓存应用程序资源,因为每次构建应用程序时都有唯一的名称(更像是每个构建文件的版本管理)。
这使得 Web 应用程序和网站可以拥有更快的加载时间和不必要的网络流量,但是,当对内容进行更改时,可能会导致一些困难,因为 webpack 现在不知道需要或不需要哪个文件。
因此,最好的办法就是清理 dist
每次应用程序捆绑过程完成时的目录。
可以通过设置来实现清理 clean
在选项 output
反对 true
,让 webpack.config.js
更新:
const path = require("path");
module.exports = {
mode: "development",
entry: {
main: path.resolve(__dirname, "src/index.js"),
},
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].[contenthash].js",
clean: true,
},
};
现在,每次我们运行 build
命令, dist
在将新文件发送到目录之前,首先会清理目录。
所以这就是关于 entry
和 output
; 从哪里获取要捆绑的文件,以及捆绑后将文件放在哪里。
我们可以在配置文件中包含另外两个主要选项: loaders
及 plugins
.
插件
要查看正在运行的插件,我们将使用 HTMLWebpackPlugin
通过安装,可以动态创建 HTML 文件 html-webpack-plugin
包装:
$ yarn add --dev html-webpack-plugin
# Or
$ npm install --save-dev html-webpack-plugin
然后在配置文件的插件部分中,我们传入 title
要构建的 HTML 文件的名称,以及 filename
:
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
mode: "development",
entry: {
main: path.resolve(__dirname, "src/index.js"),
},
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].[contenthash].js",
clean: true,
},
plugins: [
new HtmlWebpackPlugin({
title: "Testing html file",
filename: "index.html",
}),
],
};
现在,当我们运行 build
命令,在中创建一个 HTML 文件 dist
文件夹。 并且在 index.html
文件中,您会注意到独特的 js
已捆绑的文件已自动附加:
查看我们的 Git 学习实践指南,其中包含最佳实践、行业认可的标准以及随附的备忘单。 停止谷歌搜索 Git 命令,实际上 学习 它!
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Testing html file</title>
<meta name="viewport" content="width=device-width, initial-scale=1"><script defer src="main.b908ea8ce529b415a20a.js"></script></head>
<body>
</body>
</html>
如果您希望生成一个模板用作您的 index.html
页面中 dist
文件夹,您可以通过附加一个来实现这一点 template
选项进入 plugins
配置文件的选项,然后在中定义一个模板文件 src
文件夹,这将是的值 template
选项:
module.exports = {
plugins: [
new HtmlWebpackPlugin({
title: "Testing html file",
filename: "index.html",
template: path.resolve(__dirname, "src/template.html"),
}),
],
};
然后在 template.html
文件中,我们可以定义一些标记。 需要注意的一件事是,该模板文件可以访问我们在 new HtmlWebpackPlugin
对象,例如 title
, filename
,我们可以将其导入到文件中:
// src/template.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title></title>
</head>
<body>
<header>
<h1>Template File of </h1>
</header>
</body>
</html>
现在,当我们再次构建应用程序时,我们会在生成的文件中获得标题 dist/index.html
成为“测试 html 文件”:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Testing html file</title>
<script defer src="main.b908ea8ce529b415a20a.js"></script></head>
<body>
<header>
<h1>Template File of Testing html file</h1>
</header>
</body>
</html>
装载机
默认情况下,webpack 可以理解 JavaScript 和 JSON(JavaScript Object Notation),但它不知道什么是图像文件,或者 HTML、CSS、SASS 或 SVG 文件,包括其他类型的文件; 它不知道如何处理它们。 由于这一挑战,装载机应运而生。
如果您有 HTML、CSV、SVG、CSS、SASS 或图像文件,您可以为所有这些不同类型的文件提供加载程序,这些加载程序将查看源文件夹,找到它们,然后将它们转换为可以导入的模块通过 JavaScript。
让我们举一个典型的例子,将这个 SVG 文件附加到 src
文件夹和 CSS 文件。 如果我们想把它引入并很好地捆绑起来,我们会使用一个加载器——我们的 index.js
文件,这里应该做两件事; 这 index.js
file 充当加载器来加载文件类型,然后该加载器被导入到 dist
捆绑应用程序时的目录,以便它成为应用程序依赖关系图的一部分。
使用 Webpack 加载 CSS 文件
我们可以创建一个 main.css
,在 src
文件夹:
// src/main.css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
然后更新 index.js
:
import style from "./main.css";
console.log("Hello world! My name is Uche Azubuko!");
在 loaders
配置文件中的选项,我们现在可以设置规则,以便在捆绑过程中将各种类型的文件加载为模块:
module.exports = {
module: {
rules: [{ test: /.css$/, use: ["style-loader", "css-loader"] }],
},
};
在这里,我们创建了一个规则,它提供了一个加载器数组,用于加载 src
文件夹; 从右向左阅读。 CSS 加载器查找文件,将其转换为模块并将其提供给 JavaScript,而 style-loader
获取导入的模块,并将其注入到 dist/index.html
文件中。
它们是开发依赖项,因此,我们还必须将它们安装在应用程序中:
$ yarn add --dev style-loader css-loader
# Or
$ npm install --save-dev style-loader css-loader
当我们运行 build
命令,捆绑过程中生成的 JavaScript 文件现在知道 CSS 文件并将其注入到 dist
应用程序。
假设您希望在浏览器上启动应用程序的开发版本。 首先,我们添加另一个选项 scripts
中的对象 package.json
文件:
"dev": webpack serve
命令 serve
与一起添加 webpack
。 它遵守一切并将其保存在内存中。 在配置文件中,我们添加两个选项: devtool
和 devServer
。 这些选项有助于优化服务器工作。
开发工具
devtool
config 选项控制编译期间如何以及是否生成源映射。 源映射跟踪所有内容的来源,并使浏览器知道任何潜在错误的来源:
module.exports = {
devtool: "inline-source-map",
};
开发服务器
这定义了我们正在设置的服务器的设置,并且我们定义了应用程序的基础应该从哪里运行。 我们还使用以下方法定义热模块重载(HMR): hot
选项,设置应用程序将运行的端口号,使用 watchFiles
通过将其值设置为 src
文件夹,使用 open
启动开发版本的编译过程完成后立即打开浏览器的选项,并使用以下命令设置您选择的端口号 port
选项:
module.exports = {
/* ... */
devServer: {
contentBase: path.resolve(__dirname, "dist"),
port: 5001, // default is often 8080
open: true,
hot: true,
watchFiles: [path.resolve(__dirname, 'src')],
},
};
除了定义的 devServer
选项,我们还需要安装 webpack-dev-server
包作为开发依赖项,这很有帮助。
现在当我们运行 dev
命令,应用程序在浏览器上呈现 localhost:5001
,显示已注入的所有内容 dist/index.html
在捆绑过程中:
启用热模块替换后,如果您在开发过程中进行更改,则在保存更改时会立即反映在浏览器上。
加载图片
webpack 版本 5 中变得更容易的另一件事是内置的 asset/resource
loader,我们可以用它来检查图像,以便在捆绑过程中将它们注入到应用程序中:
module.exports = {
module: {
{ test: /.(svg|ico|png|webp|jpg|gif|jpeg)$/, type: "asset/resource" },
},
};
在这里,我们编写了一个检查来查找与以下文件类型关联的所有文件并将它们注入到 dist
捆绑期间的文件夹:svg、ico、png、webp、jpg、gif、jpeg。
更新 src/index.html
包含您之前下载的 SVG 文件:
import style from "./main.css";
import logo from "./logo.svg";
console.log("Hello world! My name is Uche Azubuko!");
现在,运行 dev
命令,现在应该编译 SVG 文件,并且捆绑过程应该成功。
通常,我们希望在组件文件中使用 SVG。 为此,让我们创建一个 component.js
文件中 src
文件夹。 其中,我们将创建一个用于在页面上创建内容的模块:
import logo from "./logo.svg";
function component() {
let main = document.createElement("main");
let para = document.createElement("p");
let img = document.createElement("img");
main.append(para);
para.append(img);
img.src = logo;
img.alt = "logo for the app";
return main;
}
export default component;
为了使用图像,第一步是将其导入到文件中。 然后我们还需要导入 component.js
文件 index.js
这样它就可以在 DOM 上捆绑和渲染。 该组件是一个被调用并返回的函数 main
返回的元素 component.js
文件,然后注入到 DOM 主体中:
import style from "./main.css";
import logo from "./logo.svg";
import component from "./component";
console.log("Hello world! My name is Uche Azubuko!");
document.body.append(component());
现在,当我们再次运行开发服务器时,我们应该在 DOM 上呈现 SVG 文件:
如果我们希望捆绑后的图片保持原来的名称,在 webpack.config.js
,我们可以设置 assestModuleFilename
成为 "[name][ext]"
,在 output
选项:
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].[contenthash].js",
clean: true,
assetModuleFilename: "[name][ext]",
},
现在,当我们重新启动开发服务器时,图像的名称应该与捆绑发生之前的名称相同。
转译
将代码转换为较旧的 ES5 版本通常是一个好主意,这样较旧的浏览器就可以理解您的代码,而使用 ES6 和 ES7 的较新浏览器也可以。 为了实现这一点,我们使用 Babel 工具。
现在,我们添加一条规则,用于查找以下目录中的所有 JavaScript 文件: src
文件夹不包括 node_modules
文件夹,然后使用 babel-loader
使用 @babel/core
并将作为开发依赖项安装,同时设置 presets
的选项 @babel/preset-env
.
首先,让我们安装所需的软件包:
$ yarn add --dev babel-loader @babel/core @babel/preset-env
# Or
$ npm install --save-dev babel-loader @babel/core @babel/preset-env
module.exports = {
module: {
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: { presets: ["@babel/preset-env"] },
},
},
},
};
保存文件,然后重新运行开发服务器。 这次,我们的应用程序现在支持使用 ES5 模块的旧版浏览器,并且现在更加向后兼容
结论
本指南的目的是让您了解底层是如何工作的; 如何构建 JavaScript 模块以实现跨浏览器支持,如何使用 Babel 进行转译,创建源映射,以及使您的前端环境变得简单而高级,如您所愿。
即使您使用类似的工具 vue-create
设置 Vue 项目或 create-react-app
为了建立一个 React 应用程序,事实是他们在幕后使用 webpack。
Webpack 确实并不难理解,从本指南中,如果您能够扩展学习范围,了解如何在 webpack 中使用 SASS、PostCSS 以及生产优化,那将是非常棒的。 如果您觉得有挑战性,请随时与我联系,我很乐意提供帮助。
再次,您可以使用我们在本指南中构建的项目作为您可能想要使用普通 webpack 构建的应用程序的样板,并且您可以轻松设置您可能喜欢的任何其他内容。
您可以参考本指南中使用的所有源代码 Github上.