Webpack 是什么?
根据官方文档:
"Webpack 是现代 JavaScript 应用程序的 静态模块打包工具。"
作为 JavaScript 开发人员,我们知道什么是模块,但是在 webpack 中,模块有点不同。它们包括:
-
ES Modules ------
import
声明 -
CommonJS Modules ------
require()
声明 -
AMD Modules ------
define
和require
声明 -
CSS imports ------
@import
任何 css/sass/less 文件内的语句 -
Image URLs ------
url(...)
或<img src="..." />
webpack 以一种可以将所有内容导入 JavaScript 代码的方式统一了所有这些不同的模块。
. . .
我们应该学习 Webpack 吗?
如今,大多数应用程序都是使用 React/Vue 或任何其他库构建的。 他们提供 CLI 工具(例如 create-react-app、@vue/cli)来创建应用程序。 这些 CLI 工具抽象了大部分配置并提供默认值。 但对于作为开发人员的我们来说,了解其工作原理是有益的,因为可能迟早需要对默认值进行一些调整。
. . .
Let's Get Started
我们将创建一个简单的应用程序来演示 webpack 的工作原理。
我们首先创建一个新目录,并初始化一个 npm 项目。 假设你位于新创建的目录中,请输入以下命令:
npm install -y
现在安装所需的 webpack 包。
npm install --save-dev webpack webpack-cli webpack-dev-server
打开 package.json
文件,删除已存在的 test
脚本,然后添加一个新脚本 dev
,这用于在开发环境下运行 webpack
。当我们开发时,这个脚本很方便。 改完脚本之后的 文件应类似以下内容:
如果你现在运行 npm run dev
,会得到一个错误:Entry module not found
。这是因为,默认情况下,webpack 需要一个文件 src/index.js
作为入口点。此外,webpack 将构建的文件输出到默认调用的目录 dist
中。
让我们添加一个新文件 src/index.js
来解决这个问题。我们在文件中输入一条语句 console.log(hello)
。
让我们运行 npm run dev
。这时没有任何问题了,同时,还会在 dist
文件目录下发现构建的 main.js
。
. . .
配置 webpack
要配置 webpack,我们需要在根目录中创建一个名 webpack.config.js
的文件。 在这个文件中,我们需要导出一个配置对象。 Webpack 运行在没有用户界面的 JavaScript 环境中,如 Node.js。
一些最广泛使用的术语是:
-
Enter point ------ 指定 webpack 的入口点,从中收集所有依赖项。这些依赖关系形成了依赖关系图。
-
Output ------ 指定在构建过程中生成的 JS 和静态文件的位置
-
Loaders ------ 第三方扩展,帮助 webpack 处理各种文件扩展名。它们将 JS 以外的文件转换为模块。
-
PLugins ------ 第三方扩展,可以改变 webpcak 的工作方式。
-
Mode ------ 指定两种模式,development 和 production,默认是 production。
现在让我们配置基本入口点和输出目录。
更改入口点
如果我们需要 webpack 查看 source/index.js
而不是默认的 src
文件夹,我们需要在导出的对象中添加一个属性 entry
。
js
module.exports = {
entry: './source/index.js'
}
也可以使用替换语句进行输入:
js
const path = require('path')
module.exports = {
entry: {
index: path.resolve(__dirname, 'source', 'index.js')
}
}
更改输出目录
假设我们想要将构建的文件输出到另一个名为 build
的目录中,而不是默认的 dist
目录。 我们可以像这样设置输出属性:
js
const path = require('path')
module.exports = {
output: {
path: path.resolve(__dirname, 'build'),
filename: 'main.js'
}
}
. . .
将构建的文件包含在 HTML 文件中
在每个 Web 应用程序中,至少有一个 HTML 文件。 要使用 HTML,我们必须使用一个名为 html-webpack-plugin
的插件。让我们首先使用以下命令安装该插件:
npm install --save-dev html-webpack-plugin
这个插件做什么?
它加载我们的 HTML 文件,并将包注入到同一个文件中。
让我们创建一个简单的 HTML 文件并在 webpack 配置文件中配置。
我将在 source
目录中创建一个名为 index.html
的文件,并在其中插入一些样板代码。 现在让我们在 webpack 文件中进行配置。
js
const HTMLWebpackPlugin = require('html-webpack-plugin')
const path = require('path')
module.exports = {
plugins: [
new HTMLWebpackPlugin({
template: path.resolve(__dirname, 'source', 'index.html')
})
]
}
既然我们的 HTML 文件已经准备好了,我们就需要一个服务器来提供这个文件,是吧?因此,让我们使用之前安装了的 webpack-dev-server
来启动服务器。
. . .
webpack Dev Server
要配置 webpack-dev-server
,我们需要打开 package.json
并添加一个新的脚本来帮助我们启动服务器。我们已经有了 dev
,我们用它来构建文件。现在我们创建 start
如下所示:
json
"scripts": {
"dev": "webpack --mode development",
"start": "webpack-dev-server --mode development --open"
}
让我们运行指令:
npm run start
将打开默认浏览器并导航到 localhost:8080
, 你会发现那里提供了 index.html
,你可以打开开发者工具并看到构建的 main.js
文件也包含在其中。
. . .
使用 Loaders
如前所述,webpack 中的 loaders(加载器) 是处理各种其他文件扩展名的第三方扩展。 有很多可用于 webpack 的加载器。
让我们继续在 webpack 配置文件中配置加载器。 加载器有一个奇怪的语法。 我们使用一个名为 module
的键,它由另一个名为 rules
的属性组成,它是一个加载器数组。 对于我们想要将其视为模块的每个文件,我们将其作为此 rules
数组中的对象。 每个对象都包含两个属性:test
定义文件类型,use
是由加载器组成的数组。 请注意,此处定义的加载器在 use
数组中从右向左加载。定义加载器时顺序很重要。 使用前请参阅 loader 程序的文档。
配置文件中一个 loader 的一般语法如下:
js
module.exports = {
module: {
rules: [
{
test: /\.filename1$/,
use: ["loader-b", "loader-a"]
},
{
test: /\.filename2$/,
use: ["loader-d", "loader-c"]
}
]
}
};
. . .
使用 CSS
为了在 webpack 中使用 CSS,我们需要两个加载器:css-loader
和 style-loader
。让我们安装它们。
npm install --save-dev css-loader style-loader
让我们在 source
目录中创建一个新文件 styles.css
。添加一些样式,以便它们能在服务启动时可以在 index.html
中体现出来。接下来,我们需要将该文件包含在 index.js
中,而不是 index.html
。 请记住,该教程是基于打包和学习 webpack 的。所以,我们的 index.js
文件应该如下所示。
js
import './styles.css'
console.log('I\'m from source')
还有最后一步要做,就是在 webpack.config.js
中配置 loader。我们的配置文件应该是这样的。
js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
}
css-loader
用于加载 css 文件, style-loader
用于在 DOM 中加载样式表。
当你使用 npm run start
重启服务后,就能从浏览器中看到更改。
. . .
使用 SASS
要使用 SASS(.scss) 文件,我们需要三个加载器,sass-loader
, css-loader
以及 style-loader
,还要加上 Node 需要的 sass
包。这里的 sass-loader
用于加载 SASS 文件。因为我们已经安装了另外 2 个软件包,所以现在让我们安装其余的软件包。
npm install --save-dev sass-loader sass
让我们在 source
目录中创建一个新文件styles.scss
,并添加这些行或任何基本样式。
scss
$primary-bg: rgb(188, 220, 220);
body {
background-color: $primary-bg;
}
让我们通过在index.js
开头 import './styles.scss'
。
是时候在 webpack.config.js
中添加 loader 了。loader 应该如下:
js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
}
]
}
};
让我们重启服务器并检查更新。
. . .
使用现代 JavaScript
webpack 本身并不知道将现代 JavaScript 转换为可以在任何浏览器中运行的兼容代码。所以它使用 Babel 来达到同样的目的。让我们安装以下软件包:@babel/core
,这是实际的引擎;babel-loader
,这是 webpack 需要的 loader;以及 @babel/preset-env
将 JavaScript 转换为 ES5。让我们安装依赖项:
npm install --save-dev @babel/core babel-loader @babel/preset-env
下一步是通过在我们的根目录中创建一个名为 babel.config.json
的新文件来配置 Babel 。在这里,我们使用安装的 preset-env
来配置 Babel。
json
{
"presets": ["@babel/preset-env"]
}
现在让我们将安装完成的 babel-loader
添加到我们的webpack.config.js
文件中。
js
const HTMLWebpackPlugin = require('html-webpack-plugin')
const path = require('path')
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
},
{
test: /\.js$/,
exclude: /node_modules/, // 排除
use: ['babel-loader']
}
]
}
}
现在让我们在 JS 代码中使用 ES6 及以上的语法。在我们使用 npm run dev
再次构建它之后,我们可以检查构建的 main.js
文件并看到它自动转换为浏览器兼容的代码。
. . .
在 webpack 中指定模式
webpack 有两种可用的模式:development 和 production。
在development模式下,代码没有缩小。webpack 只是简单地获取我们编写的所有 JS 代码并将其加载到浏览器中,从而使其更快地重新加载应用程序。
在production 模式下,webpack 应用了很多优化。它会自动使用 terser-webpack-plugin
来减小包的大小。它还设置process.env.NODE_ENV
为 production
。这个环境变量很有用,因为我们可以有条件地在生产和开发中执行某些操作。
使用 Webpack 的 production 模式 ,我们需要向package.json
文件中添加另一个脚本。我们将命名它build
。我们的脚本应该如下所示:
json
"scripts": {
"dev": "webpack --mode development",
"start": "webpack-dev-server --mode development --open",
"build": "webpack --mode production"
}
. . .
优化-代码分割
代码分割 或 延迟加载 是一种避免大包的优化技术。它还避免了依赖项重复。通过使用这种机制,我们可以按需加载一段代码,例如,每当用户单击按钮时、当路由更改时,等等。被分割的代码段称为 chunk (块)。
webpack 有一个限制,即应用程序初始包的最大文件大小应小于 244 KiB。webpack 中实现代码分割的方式有 3 种:
- 具有多个入口点(entry points)
- 使用
optimization.splitChunks
- 动态导入
第一种方法适用于较小的项目,但在复杂的项目中不可扩展。我们在 webpack 配置文件中指定多个入口点。
使用 optimization.splitChunks
有时我们在应用程序中使用大量依赖项。例如,让我们使用流行的日期包:Moment。我选择这个包是因为它的体积有点重。让我们安装它,然后将其包含在我们的index.js
文件中并运行我们的build
命令。
npm install moment
一会儿就安装成功了。现在让我们将其导入到我们的index.js
文件中。
import moment from 'moment'
现在让我们运行我们的 build
命令。
npm install build
你将在终端上看到警告消息:
那么我们如何解决这个问题呢?这很简单。让我们添加一个名为 optimization
的 key 和其中的另一个 splitChunks
属性,如下所示:
js
module.exports = {
optimization: {
splitChunks: { chunks: 'all' }
}
}
您会发现入口点的大小大大缩小了。
动态导入
动态导入用于有条件地加载代码。这种方法在 React 和 Vue 中被广泛使用。我们可以根据用户交互或基于路由更改来加载代码。
为了进行演示,让我们向页面添加一个按钮,单击该按钮可获取文章列表。获取文章列表的代码存在于单独的文件中。它是动态导入到我们的index.js
文件中的。
让我们在 source
创建一个 api.js
用于 fetch
API。在这里,我们导出一个向公共 API,这个 API 返回结果。
js
export const fetchTodos = () => {
return fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(json => json);
}
让我们在index.html
文件中创建一个按钮,并将id
属性设置为btn
。
html
<button id="btn">点我加载</button>
在我们的index.js
文件中,让我们通过使用一个函数来实现动态导入 api.js
文件。
js
const getTodos = () => import ('./api') // 等于 var getTodos = function () { return import ('./api') }
另外,让我们在文件末尾添加用于记录所获取的数据的逻辑。
js
const btn = document.getElementById('btn');
btn.addEventListener('click', () => {
getTodos().then(({fetchTodos}) => { // fetchTodos 是 api.js 中的模块
fetchTodos().then(resp => console.log(resp)) }
)}
)
此代码片段调用getTodos
函数,该函数为我们导入api.js
文件。然后在导入它之后,我们从中获取fetchTodos
,调用该函数,然后记录成功的响应。
让我们构建文件,运行服务器 npm run start
,并确保打开开发工具并保持网络选项卡打开。你会发现点击按钮后,有一个新的 JS 文件被动态加载。
这0.main.js
是动态加载的文件。如果你希望这个文件有一个可读的名称,你只需要在动态导入时添加注释,如下所示:
js
const getTodos = () => import( /* webpackChunkName: "postsAPI" */ './api')
现在,当你重复相同的过程时,它将加载一个名为postsAPI.main.js
而不是0.main.js
。
. . .
Still Curious?
这只是对 webpack 的简单介绍。正如之前所说,本文针对的是想要开始学习 webpack 的初学者或中级开发人员。当然,webpack 中还有很多话题。如果你有兴趣了解有关 webpack 的更多信息,请参阅文档,因为那里详细解释了许多有趣的主题。
. . .
核心代码
json
/* package.json */
"scripts": {
"dev": "webpack --mode development",
"start": "webpack-dev-server --mode development --open",
"build": "webpack --mode production"
},
"dependencies": {
"moment": "^2.27.0"
},
"devDependencies": {
"@babel/core": "^7.11.6",
"@babel/preset-env": "^7.11.5",
"babel-loader": "^8.1.0",
"css-loader": "^4.2.2",
"html-webpack-plugin": "^4.4.1",
"sass": "^1.26.10",
"sass-loader": "^10.0.2",
"style-loader": "^1.2.1",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0"
}
json
/* babel.config.json */
{
"presets": ["@babel/preset-env"]
}
js
/* source/api.js */
export const fetchTodos = () => {
return fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(json => json);
}
js
/* source/inex.js */
import './style.scss'
import './styles.css'
import moment from 'moment'
console.log('I\'m from source')
const getTodos = () => import( /* webpackChunkName: "postsAPI" */ './api')
const btn = document.getElementById('btn');
btn.addEventListener('click', () => {
getTodos().then(({fetchTodos}) => {
fetchTodos().then(resp => console.log(resp)) }
)}
)
js
/* webpack.config.js */
const HTMLWebpackPlugin = require('html-webpack-plugin')
const path = require('path')
module.exports = {
entry: {
index: path.resolve(__dirname, 'source', 'index.js')
},
output: {
path: path.resolve(__dirname, 'build'),
filename: 'main.js'
},
plugins: [
new HTMLWebpackPlugin({
template: path.resolve(__dirname, 'source', 'index.html')
})
],
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
},
{
test: /\.js$/,
exclude: /node_modules/,
use: ['babel-loader']
}
]
},
optimization: {
splitChunks: { chunks: 'all' }
}
}
. . .
参考 --- Learn webpack in Under 10 Minutes github.com/harshaktg/w...