开启编程成长之旅!这是我参与「编程 · 2 月更文挑战」的第 26 天,点击查看活动详情
原文来自我的个人博客
1. 什么是 Webpack?
1.1 回答
webpack
是一个用于现代 JavaScript
应用程序的 静态模块打包工具。它主要解决的问题是将多个模块(Module)打包成一个或多个文件,并且在这个过程中还支持一些特性,如代码分离、文件压缩等。
我们先将着重点落在 静态模块打包工具
上,为什么是 静态模块打包工具
?
是因为它可以将多个模块(JavaScript 文件
、CSS 文件
、图片
等)打包成一个或多个静态资源文件。静态资源文件包含了应用程序中的所有依赖关系和逻辑,可以直接在浏览器中加载和运行。
1.2 webpack 打包示例
下面我们就来演示一个最简单的 webpack 打包示例:
- 首先我们创建一个目录,初始化
npm
,然后 在本地安装webpack
,接着安装webpack-cli
(注意:此工具用于在命令行中运行webpack
):
mkdir webpack-demo
cd webpack-demo
npm init -y
npm install webpack webpack-cli --save-dev
- 现在,我们将创建以下目录结构、文件和内容:
add.js
:
export function add(n1, n2) {
return n1 + n2;
}
sub.js
export function sub(n1, n2) {
return n1 - n2;
}
index.js
import { add } from "./add";
import { sub } from "./sub";
console.log(add(1, 2));
console.log(sub(1, 2));
可以看到,我们做的仅仅定义了两个工具文件 a.js
和 b.js
,并在 index.js
中导入并且调用了两个函数。
- 接着,我们执行
npx webpack
命令进行打包,打包结果如下图所示:
可以这样说,执行 npx webpack
,会将我们的脚本 src/index.js
作为 入口起点,也会生成 dist/main.js
作为 输出。Node 8.2/npm 5.2.0
以上版本提供的 npx
命令,可以运行在初次安装的 webpack package
中的 webpack
二进制文件(即 ./node_modules/.bin/webpack
)。
1.3 模块
在上面的例子中,我们仅仅演示了 es6
的模块化导入导出,事实上除了 import
和 export
,webpack
还能够很好地支持多种其他模块语法比如 CommonJS
、AMD
等
1.4 配置文件
在 webpack v4
中,可以无须任何配置,然而大多数项目会需要很复杂的设置,这时候就需要一个配置文件来拯救我们了,因为这比在 terminal(终端)
中手动输入大量命令要高效的多
- 创建
webpack-demo/webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};
- 执行打包命令
npx webpack --config webpack.config.js
。
可以看到生成的新的文件名为 bundle.js
说明我们的配置文件已经生效。
事实上,如果 webpack.config.js
存在,则 webpack
命令将默认选择使用它,所以我们依然可以使用 npx webpack
进行打包。这里使用 --config
选项只是表明可以传递任何名称的配置文件,这对于需要拆分成多个文件的复杂配置是非常有用的。
1.5 npm scripts
考虑到用 CLI
这种方式来运行本地的 webpack
副本并不是特别方便,我们可以设置一个快捷方式。调整 package.json
文件,添加一个 npm script
:
"build": "webpack"
现在,可以使用 npm run build
命令,来替代我们之前使用的 npx
命令。注意,使用 npm scripts
,我们可以像使用 npx
那样通过模块名引用本地安装的 npm packages
。这是大多数基于 npm
的项目遵循的标准,因为它允许所有贡献者使用同一组通用脚本。
1.6 总结
在这一题中,我们使用了一个最简单的案例来解释了 webpack
是什么东西,它的本质就是一个模块化的打包工具,我想大家应该对它的概念都能有一个基本的了解了。
2. Webpack 的核心概念有哪些?
Webpack
的核心概念主要包括以下几个:
- 入口 Entry
- 输出 Output
- 加载器 Loader
- 插件 Plugin
- 模式 Mode
- 浏览器兼容性 Browser compatibility
- 环境 Environment
- 代码块 Chunk
- 模块 Module
2.1 入口 Entry
入口起点(entry point) 定义 webpack
从哪个文件开始构建依赖关系图,进入入口起点后,webpack
会找出有哪些模块和库是入口起点(直接和间接)依赖的。
它的默认值是 ./src/index.js
,但是我们可以通过在配置文件中配置 entry
属性来指定一个(或多个)不同的入口起点。例如:
webpack.config.js
module.exports = {
entry: './path/to/my/entry/file.js',
};
2.2 输出 Output
output
属性告诉 webpack
在哪里输出它所创建的 bundle
,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js
,其他生成文件默认放置在 ./dist
文件夹中。
你可以通过在配置中指定一个 output
字段,来配置这些处理过程:
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js',
},
};
在上面的示例中,我们通过 output.filename
和 output.path
属性,来告诉 webpack bundle
的名称,以及我们想要 bundle
生成(emit
)到哪里。
2.3 加载器 Loader
webpack
只能理解 JavaScript
和 JSON
文件。loader
的作用就是让让 webpack
能够去处理其他类型的文件,并将它们转换为 JavaScript
文件。
在 webpack
的配置中,loader
有两个属性:
test
属性,识别出哪些文件会被转换。use
属性,定义出在进行转换时,应该使用哪个loader
。
webpack.config.js
const path = require('path');
module.exports = {
output: {
filename: 'my-first-webpack.bundle.js',
},
module: {
rules: [{ test: /\.txt$/, use: 'raw-loader' }],
},
};
以上配置中,对一个单独的 module
对象定义了 rules
属性,里面包含两个必须属性:test
和 use
。这告诉 webpack 编译器(compiler)
如下信息:
“嘿,webpack 编译器,当你碰到「在 require()/import 语句中被解析为 '.txt' 的路径」时,在你对它打包之前,先 use(使用) raw-loader 转换一下。”
2.4 插件 Plugin
webpack 中 loader
用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量。插件目的在于解决 loader
无法实现的其他事。
想要使用一个插件,只需要 require()
它,然后把它添加到 plugins
数组中。多数插件可以通过选项(option
)自定义。也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new
操作符来创建一个插件实例。
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack'); // 用于访问内置插件
module.exports = {
module: {
rules: [{ test: /\.txt$/, use: 'raw-loader' }],
},
plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })],
};
在上面的示例中,html-webpack-plugin
为应用程序生成一个 HTML
文件,并自动将生成的所有 bundle
注入到此文件中。
2.5 模式 Mode
Webpack
有三种模式:development
、production
和 none
。通过设置不同的模式,可以启用不同的内置优化。
2.6 浏览器兼容性 Browser compatibility
Webpack
支持所有符合 ES5
标准 的浏览器(不支持 IE8 及以下版本)。
2.7 环境 Environment
Webpack 5
运行于 Node.js v10.13.0+
的版本。
2.8 代码块 Chunk
chunk
可以理解为 webpack
构建输出的一个文件,通常包含了在构建过程中生成的一些代码块。webpack
将所有相关的模块打包在一起,以此生成一个或多个chunk 。这些 chunk
可以是 JavaScript文件、CSS文件、图片等,也可以是异步加载的代码块。
webpack
将代码分割成多个 chunk
的主要目的是优化应用程序的性能。通过将代码拆分成多个小块,webpack
可以减少初始加载时间,并提高应用程序的性能。
2.9 模块 Module
Webpack
会将所有的文件都看作一个个模块,每个模块都有自己的依赖关系,Webpack
会根据这些依赖关系构建出一个依赖关系图。
3. 请解释一下 Webpack 的打包原理
Webpack
的打包原理可以简单地概括为以下几个步骤:
-
解析入口文件:
Webpack
通过指定的入口文件开始打包,从该文件开始分析和解析整个项目的依赖关系。 -
依赖分析:
Webpack
根据模块之间的依赖关系,递归地分析和收集所有需要打包的模块,包括JavaScript
、CSS
、图片等资源文件。 -
模块转换:
Webpack
在解析模块的过程中,会根据不同的模块类型,将它们转换成JavaScript
代码,以便在浏览器中执行。 -
生成
Chunk
:Webpack
会将所有模块打包成一个或多个Chunk
,每个Chunk
包含了一组模块,以及这些模块之间的依赖关系。 -
生成
Bundle
:最后,Webpack
会将所有的Chunk
生成对应的静态资源文件,例如 JavaScript、CSS、图片等,供浏览器加载和执行。
在整个打包过程中,Webpack
提供了很多插件和配置选项,可以帮助我们自定义打包过程,例如代码压缩、分离 CSS
文件、处理图片 等。同时,Webpack
还支持使用各种 Loader
来处理不同类型的模块,例如使用 Babel Loader
来处理 ES6
语法的模块。
3.1 chunk 和 bundle 的区别是什么?
chunk:chunk
是 Webpack
打包生成的一个或多个 JavaScript
文件,它包含了一组相关的模块的代码,可以看做是模块的集合。在实际开发中,chunk
可以用于实现代码分割、按需加载、懒加载等功能,从而优化代码的加载和执行,提高应用的性能和用户体验。
bundle:bundle
是 Webpack
打包生成的一个或多个静态资源文件,它包含了所有模块的代码,可以看做是代码的最终打包结果。在实际开发中,bundle
可以用于发布到生产环境,供浏览器加载和执行。
简单来说,chunk
是 Webpack
在打包过程中产生的中间文件,bundle
是最终生成的静态资源文件。在代码分割和按需加载的场景下,Webpack
会生成多个 chunk
文件,每个文件包含一部分模块的代码,然后将这些 chunk
文件合并成一个或多个 bundle
文件,以便在浏览器中加载和执行。
4. 如何使用 Webpack 进行代码分割
代码分离(Code Spliting) 是 webpack
一个非常重要的特性,它主要的目的是将代码剥离到不同的 bundle
中,之后我们可以按需加载,或者并行加载这些文件。
webpack
常用的代码分离方式有三种:
- 入口起点:使用
entry
配置手动分离代码; - 防止重复:使用
EntryDependencies
或者SplitChunksPlugin
去重和分离代码: - 动态导入:通过模块的内联函数用来分离代码
4.1 方式一:多入口起点
这是迄今为止最简单直观的分离代码的方式。不过,这种方式手动配置较多,并有一些隐患,我们将会解决这些问题。
先来看看如何从 main bundle
中分离 another module
(另一个模块)
4.1.1 没有代码分离时
创建一个小的 demo
:
- 首先我们创建一个目录,初始化
npm
,然后在本地安装webpack
、webpack-cli
、loadsh
mkdir webpack-demo
cd webpack-demo
npm init -y
npm install webpack webpack-cli lodash --save-dev
- 创建
src/index.js
:
import _ from "lodash";
console.log(_);
- 创建
src/another-module.js
:
import _ from 'lodash';
console.log(_);
- 创建
webpack.config.js
:
const path = require("path");
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "main.js",
},
};
- 在
package.json
中添加命令:
"scripts": {
"build": "webpack"
},
- 执行命令进行打包:
npm run build
- 生成如下构建结果:
可以看到此时生成了一个 554KB
的 main.js
文件
4.1.2 有代码分离时
接下来我们从 main bundle
中分离出 another module
(另一个模块)
- 修改
webpack.config.js
const path = require("path");
module.exports = {
mode: "development",
- entry: './src/index',
+ entry: {
+ index: './src/index',
+ another: './src/another-module.js'
+ },
output: {
path: path.resolve(__dirname, "dist"),
- filename: "main.js",
+ filename: "[name].main.js",
},
};
- 打包,生成如下构建结果:
我们发现此时已经成功打包出 another.bundle.js
和 index.bundle.js
两个文件了,但是文件的大小似乎有些问题,怎么两个都是 554KB
?
正如前面提到的,这种方式存在一些隐患:
- 如果入口
chunk
之间包含一些重复的模块,那些重复模块都会被引入到各个bundle
中。 - 这种方法不够灵活,并且不能动态地将核心应用程序逻辑中的代码拆分出来。
以上两点中,第一点对我们的示例来说无疑是个问题,因为之前我们在 ./src/index.js
中也引入过 lodash
,这样就在两个 bundle
中造成重复引用。在下一小节我们将移除重复的模块。
4.1.3 优化:移除重复的模块
在通过多入口分离代码的方式中,我们可以通过配置 dependOn
这个选项来解决重复模块的问题,它的原理就是从两个文件中抽出一个共享的模块,然后再让这两个模块依赖这个共享模块。
- 修改
webpack.config.js
配置文件:
const path = require('path');
module.exports = {
mode: 'development',
entry: {
- index: './src/index.js',
- another: './src/another-module.js',
+ index: {
+ import: './src/index.js',
+ dependOn: 'shared',
+ },
+ another: {
+ import: './src/another-module.js',
+ dependOn: 'shared',
+ },
+ shared: ['lodash'],
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};
- 打包,生成如下构建结果:
可以看到 index.mian.js
和 another.mian.js
中重复引用的部分被抽离成了 shared.main.js
文件,且 index.mian.js
和 another.mian.js
文件大小也变小了。
4.2 方式二:splitChunks 模式
另外一种分包的模式是 splitChunks
,它底层是使用 SplitChunksPlugin
来实现的:
SplitChunksPlugin
插件可以将公共的依赖模块提取到已有的入口chunk
中,或者提取到一个新生成的chunk
。
因为该插件 webpack
已经默认安装和集成,所以我们并 不需要单独安装和直接使用该插件;只需要提供 SplitChunksPlugin
相关的配置信息即可
webpack
提供了 SplitChunksPlugin
默认的配置,我们也可以手动来修改它的配置:
- 比如默认配置中,
chunks
仅仅针对于异步(async
)请求,我们可以设置为initial
或者all
,
4.2.1 splitChunk 的配置
- 在
1.1.2
的基础上修改webpack.cofig.js
:
const path = require('path');
module.exports = {
mode: 'development',
entry: {
index: './src/index.js',
another: './src/another-module.js',
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
+ optimization: {
+ splitChunks: {
+ chunks: 'all',
+ },
+ },
};
- 打包,生成如下构建结果:
使用 optimization.splitChunks
配置选项之后,现在应该可以看出,index.bundle.js
和 another.bundle.js
中已经移除了重复的依赖模块。需要注意的是,插件将 lodash
分离到单独的 chunk
,并且将其从 main
bundle
中移除,减轻了大小。
除了 webpack
默认继承的 SplitChunksPlugin
插件,社区中也有提供一些对于代码分离很有帮助的 plugin
和 loader
,比如:
mini-css-extract-plugin
: 用于将 CSS 从主应用程序中分离。
4.2.2 SplitChunks 自定义配置解析
关于 optimization.splitChunks
文档上有很详细的记载,我这里讲你叫几个常用的:
1. Chunks:
- 默认值是
async
- 另一个值是
initial
,表示对通过的代码进行处理 all
表示对同步和异步代码都进行处理
2. minSize
:
- 拆分包的大小, 至少为 `minSize;
- 如果一个包拆分出来达不到
minSize
,那么这个包就不会拆分;
3. maxSize
:
- 将大于maxSize的包,拆分为不小于minSize的包;
4. cacheGroups:
- 用于对拆分的包就行分组,比如一个
lodash
在拆分之后,并不会立即打包,而是会等到有没有其他符合规则的包一起来打包; test
属性:匹配符合规则的包;name
属性:拆分包的name
属性;filename
属性:拆分包的名称,可以自己使用placeholder
属性;
- 修改
webpack.config.js
const path = require("path");
module.exports = {
mode: "development",
entry: {
index: "./src/index.js",
another: "./src/another-module.js",
},
output: {
filename: "[name].bundle.js",
path: path.resolve(__dirname, "dist"),
},
optimization: {
splitChunks: {
chunks: "all",
// 拆分包的最小体积
// 如果一个包拆分出来达不到 minSize,那么这个包就不会拆分(会被合并到其他包中)
minSize: 100,
// 将大于 maxSize 的包,拆分成不小于 minSize 的包
maxSize: 10000,
// 自己对需要拆包的内容进行分组
cacheGroups: {
自定义模块的name: {
test: /node_modules/,
filename: "[name]_vendors.js",
},
},
},
},
};
- 打包,生成如下构建结果:
4.3 方式三:动态导入(dynamic import)
另外一个代码拆分的方式是动态导入时,webpack
提供了两种实现动态导入的方式:
- 第一种,使用
ECMAScript
中的import()
语法来完成,也是目前推荐的方式; - 第二种,使用
webpack
遗留的require.ensure
,目前已经不推荐使用;
动态 import
使用最多的一个场景是懒加载(比如路由懒加载)
4.3.1 import 方式
接着从 1.1.2
小节代码的基础上修改:
- 修改
webpack.confg.js
:
const path = require("path");
module.exports = {
entry: "./src/index.js",
mode: "development",
entry: {
index: "./src/index.js",
},
output: {
filename: "[name].bundle.js",
path: path.resolve(__dirname, "dist"),
},
};
-
删除
src/another-module.js
文件 -
修改
src/index.js
,不再使用statically import
(静态导入)lodash
,而是通过dynamic import
(动态导入) 来分离出一个chunk
:
const logLodash = function () {
import("lodash").then(({ default: _ }) => {
console.log(_);
});
};
logLodash();
之所以需要 default
,是因为 webpack 4
在导入 CommonJS
模块时,将不再解析为 module.exports
的值,而是为 CommonJS
模块创建一个 artificial namespace
对象。
- 打包,生成如下构建结果:
由于 import()
会返回一个 promise
,因此它可以和 async
函数一起使用。下面是如何通过 async
函数简化代码:
const logLodash = async function () {
const { default: _ } = await import("lodash");
console.log(_);
};
logLodash();
4.3.2 动态导入的文件命名
因为动态导入通常是一定会打包成独立的文件的,所以并不会再 cacheGroups
中进行配置;
它的命名我们通常会在 output
中,通过 chunkFilename
属性来命名:
- 修改
webpack.config.js
const path = require("path");
module.exports = {
entry: "./src/index.js",
mode: "development",
entry: {
index: "./src/index.js",
},
output: {
filename: "[name].bundle.js",
path: path.resolve(__dirname, "dist"),
+ chunkFilename: "chunk_[name].js""
},
};
- 打包构建:
如果对打包后的 [name]
不满意,还可以通过 magic comments
(魔法注释)来修改:
1, 修改 src/index.js
:
const logLodash = async function () {
const { default: _ } = await import(/*webpackChunkName: 'lodash'*/ "lodash");
console.log(_);
};
logLodash();
- 打包构建
5. 什么是 Tree Shaking?如何使用 Webpack 实现?
什么是 Tree Shaking
?
Tree Shaking
是一个术语,在计算机中表示消除死代码(dead_code
);- 最早的想法起源于
LISP
,用于消除未调用的代码(纯函数无副作用,可以放心的消除,这也是为什么要求我们在进行函数式编程时,尽量使用纯函数的原因之一); - 后来
Tree Shaking
也被应用于其他的语言,比如JavaScript
、Dart
;
JavaScript
的 Tree Shaking
:
- 对
JavaScript
进行Tree Shaking
是源自打包工具rollup
; - 这是因为
Tree Shaking
依赖于ES Module
的静态语法分析(不执行任何的代码,可以明确知道模块的依赖关系); webpack2
正式内置支持了ES2015
模块,和检测未使用模块的能力;- 在
webpack4
正式扩展了这个能力,并且通过package.json
的sideEffects
属性作为标记,告知webpack
在编译时,哪里文件可以安全的删除掉; webpack5
中,也提供了对部分CommonJS
的tree shaking
的支持; ✓ github.com/webpack/cha…
5.1 webpack 实现 Tree Shaking
webpack
实现 Tree Shaking
采用了两种不同的方案:
usedExports
:通过标记某些函数是否被使用,之后通过Terser
来进行优化的;sideEffects
:跳过整个模块/文件,直接查看该文件是否有副作用;
usedExports
按 sideEffects
这两个东西的优化是不同的事情。
引用官方文档的话: The sideEffects and usedExports(more konwn as tree shaking)optimizations are two different things
下面我们分别来演示一下这两个属性的使用
5.1.1 usedExports
- 新建一个
webpack-demo
。
mkdir webpack-demo
cd webpack-demo
npm init -y
npm install webpack webpack-cli lodash --save-dev
- 创建
src/math.js
文件:
export const add = (num1, num2) => num1 + num2;
export const sub = (num1, num2) => num1 - num2;
在这个问价中仅是导出了两个函数方法
- 创建
src/index.js
文件:、
import { add, sub } from "./math";
console.log(add(1, 2));
在 index.js
中 导入了刚刚创建的两个函数,但是只使用了 add
- 配置
webpack.config.js
:
const path = require("path");
module.exports = {
mode: "development",
devtool: false,
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "main.js",
},
optimization: {
usedExports: true,
},
};
为了可以看到 usedExports
带来的效果,我们需要设置为 development
模式。因为在 production
模式下,webpack
默认的一些优化会带来很大的影响。
- 设置
usedExports
为true
和false
对比打包后的代码:
仔细观察上面两张图可以发现当设置 usedExports: true
时,sub
函数没有导出了,另外会多出一段注释:unused harmony export mul
;这段注释的意义是会告知 Terser
在优化时,可以删除掉这段代码。
这个时候,我们将 minimize
设置 true
:
usedExports
设置为false
时,sub
函数没有被移除掉;usedExports
设置为true
时,sub
函数有被移除掉;
所以,usedExports
实现 Tree Shaking
是结合 Terser
来完成的。
5.1.2 sideEffects
在一个纯粹的 ESM
模块世界中,很容易识别出哪些文件有副作用。然而,我们的项目无法达到这种纯度,所以,此时有必要提示 webpack compiler
哪些代码是“纯粹部分”。
通过 package.json
的 "sideEffects"
属性,来实现这种方式。
{
"name": "your-project",
"sideEffects": false
}
如果所有代码都不包含副作用,我们就可以简单地将该属性标记为 false
,来告知 webpack
它可以安全地删除未用到的 export
。
"side effect(副作用)"
的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个export
或多个export
。举例说明,例如polyfill
,它影响全局作用域,并且通常不提供export
。
如果你的代码确实有一些副作用,可以改为提供一个数组:
{
"name": "your-project",
"sideEffects": ["./src/some-side-effectful-file.js"]
}
注意,所有导入文件都会受到
tree shaking
的影响。这意味着,如果在项目中使用类似css-loader
并import
一个CSS
文件,则需要将其添加到side effect
列表中,以免在生产模式中无意中将它删除:
{
"name": "your-project",
"sideEffects": ["./src/some-side-effectful-file.js", "*.css"]
}
5.2 CSS 实现 Tree Shaking
上面将的都是关于 JavaScript
的 Tree Shaking
,对于 CSS
同样有对应的 Tree Shaking
操作。
-
在早期的时候,我们会使用
PurifyCss
插件来完成CSS
的tree shaking
,但是目前该库已经不再维护了(最新更新也是在4
年前了); -
目前我们可以使用另外一个库来完成
CSS
的Tree Shaking
:PurgeCSS
,也是一个帮助我们删除未使用的CSS
的工具;
- 安装
PurgeCss
的webpack
插件:
npm install purgecss-webpack-plugin -D
- 在
webpack.config.js
中配置PurgeCss
new PurgeCSSPlugin({
paths: glob.sync(`${path.resolve(__dirname, '../src')}/**/*`, { nodir: true }),
only: ['bundle', 'vendor']
})
paths
:表示要检测哪些目录下的内容需要被分析,这里我们可以使用glob
;- 默认情况下,
Purgecss
会将我们的html
标签的样式移除掉,如果我们希望保留,可以添加一个safelist
的属性;
purgecss
也可以对 less
、sass
文件进行处理(它是对打包后的 css
进行 tree shaking
操作);
6. Webpack 如何实现 Hot Module Replacement(HMR)
Webpack
实现 热更新 Hot Module Replacement(HMR)
的主要原理是通过在运行时替换被修改的模块,而不需要重新加载整个页面或应用。
具体来说,Webpack HMR
的实现流程如下:
-
首先,在入口文件中添加对
HMR
的支持,例如使用module.hot.accept
方法或webpack-dev-server
等工具提供的HMR API
。 -
当某个模块发生变化时,
Webpack
会构建新的模块代码,并将其传递给HMR runtime
。 -
HMR runtime
会将新的模块代码与当前运行的模块进行比较,找出发生变化的部分,然后将其应用到当前运行的模块上。 -
如果当前模块依赖其他模块,
HMR runtime
会检查这些依赖模块是否也发生了变化,如果有,会递归执行上述操作,直到所有相关的模块都被更新为止。 -
最后,
HMR runtime
会通知应用程序,告诉它哪些模块已经被更新,并提供一些回调函数,让应用程序可以根据需要进行一些额外的操作。
总之,Webpack HMR
通过在运行时替换被修改的模块,使应用程序可以保持运行状态,同时也提高了开发效率和用户体验。要实现 HMR
,我们需要在入口文件中添加对 HMR
的支持,并使用 webpack-dev-server
或其他工具来提供 HMR runtime
的支持。
7. 请列举几个常用的 Webpack 插件
-
HtmlWebpackPlugin
:该插件可以根据指定的模板生成 HTML
文件,并自动将生成的 JS
和 CSS
文件引入到 HTML
中。
-
CleanWebpackPlugin
:该插件可以在每次构建之前清空输出目录,避免旧文件对新构建结果的影响。
-
MiniCssExtractPlugin
:该插件可以将 CSS
文件提取出来并单独打包成一个或多个文件,以便在浏览器中异步加载。
-
Webpack.DefinePlugin
:该插件可以定义全局变量,可以用来区分开发环境和生产环境等场景。
-
TerserWebpackPlugin
:该插件可以压缩 JavaScript
代码,减小文件大小,加快加载速度。
-
CopyWebpackPlugin
:该插件可以将某些文件或文件夹复制到输出目录中,例如静态资源文件等。
8. webpack 的优化策略有哪些?
HtmlWebpackPlugin
:该插件可以根据指定的模板生成 HTML
文件,并自动将生成的 JS
和 CSS
文件引入到 HTML
中。
CleanWebpackPlugin
:该插件可以在每次构建之前清空输出目录,避免旧文件对新构建结果的影响。
MiniCssExtractPlugin
:该插件可以将 CSS
文件提取出来并单独打包成一个或多个文件,以便在浏览器中异步加载。
Webpack.DefinePlugin
:该插件可以定义全局变量,可以用来区分开发环境和生产环境等场景。
TerserWebpackPlugin
:该插件可以压缩 JavaScript
代码,减小文件大小,加快加载速度。
CopyWebpackPlugin
:该插件可以将某些文件或文件夹复制到输出目录中,例如静态资源文件等。
webpack
的优化策略包括:
Tree Shaking
:通过静态代码分析,找出未被引用的代码并在打包时剔除掉,以减少打包后的代码体积。**Scope Hoisting
**:将模块中的所有函数作用域合并到一个函数作用域中,以减少代码体积和函数声明语句执行时间。Code Splitting
:将代码按照路由或者功能进行拆分,实现按需加载,减少首屏加载时间。- 懒加载:将某些资源的加载延迟到真正需要时再进行加载,以减少首屏加载时间。
- 长缓存:利用浏览器缓存机制,将打包后的文件按照版本号进行区分,提高页面访问速度。