Appearance
webpack
webpack 的作用
Webpack 是一个现代的前端模块打包工具,它用于构建和优化 Web 应用程序的前端资源,包括 JavaScript、CSS、图片、字体等。Webpack 的主要目标是将项目的所有依赖项(模块、资源文件)打包到一个或多个最终的静态文件中,以便在浏览器中加载。它改善了前端开发的工作流程,提高了代码的可维护性和性能,解决了模块化、资源管理、性能优化和自动化等多个关键问题。
webpack 的构建流程
webpack 构建流程是一个 串行
的过程。
- 读取配置文件:Webpack 首先会读取项目中的配置文件(通常是 webpack.config.js),该配置文件包含了构建过程中的各种设置,如入口文件、输出目录、加载器(loaders)、插件(plugins)等。
- 解析入口文件:Webpack 会根据配置文件中定义的入口点(entry points)来解析应用程序的依赖关系。入口文件通常是应用程序的主要 JavaScript 文件,但也可以有多个入口点。
- 依赖解析:Webpack 分析入口文件和其依赖的模块,构建一个依赖关系图,以确定哪些模块依赖于其他模块,以及它们之间的依赖关系。
- 加载器处理:Webpack 使用加载器来处理不同类型的资源文件,如 CSS、图片、字体等。加载器允许开发人员在构建过程中转换这些资源文件,以便将它们整合到最终的输出文件中。
- 插件处理:Webpack 提供了插件系统,插件用于执行各种任务,如代码压缩、资源优化、HTML 生成、热模块替换(HMR)等。插件可以根据需要自定义 Webpack 的构建过程。
- 优化和压缩:Webpack 可以进行各种优化,包括代码压缩、Tree Shaking、懒加载等,以减小包的大小并提高性能。
- 生成输出文件:Webpack 根据入口文件和依赖关系图生成一个或多个输出文件。这些输出文件包括 JavaScript 文件、CSS 文件、图片、字体等资源文件。
- 生成 Source Maps:Webpack 可以生成 Source Maps,以便在开发中进行调试。Source Maps 是一种映射文件,将最终输出文件映射回原始源代码。
- 输出到指定目录:最终的构建结果被输出到配置文件中指定的目录中,通常是一个名为"dist"的目录。输出文件的命名和目录结构也可以根据配置进行自定义。
- 完成构建过程:Webpack 构建过程完成后,它会生成构建报告,包括构建成功或失败的信息,输出文件的大小等统计信息。
webpack 有哪些核心概念?
- entry:入口
- output:输出
- module:模块(loader)
- plugin:插件
- mode:模式
- devServer:开发服务器
- optimization:优化
- code-splitting:代码切割
有哪些常见的 loader
- raw-loader:加载文件原始内容
- file-loader:把文件输出到文件夹中,在代码中通过相对 url 去引用输出的文件
- url-loader:与 file-loader 类似,可设置输出大小,小于大小 url-loader 会把文件转成 base64,并把 base64 输出到代码中,大于则输出文件
- source-map-loader:加载 source map 文件
- babel-loader:加载 ES6+ 语法,并转成 ES5
- ts-loader
- css-loader:加载 css 文件
- ...
有哪些常见的 plugin
- define-plugin:定义环境变量,在
webpack5
中,通过mode
来定义环境变量 - html-webpack-plugin:生成 html 文件,并自动引入打包后的 js 文件
- mini-css-extract-plugin:将 css 提取到单独的文件中
- terser-webpack-plugin:压缩 js 文件
- webpack-bundle-analyzer:分析打包后的文件大小,并生成可视化的图表
loader 和 plugin 的区别
1. 功能区别
- loader:本质是一个函数,对接收到的内容进行转换,返回转换后的结果。webpack 只认识 js,使用 loader 来转换其他类型的文件。
- plugin:插件,用于扩展 webpack 功能。在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
2. 运行时机区别
- loader 运行在打包文件之前上
- plugins 在整个编译周期都起作用
webpack 事件机制
Webpack 的事件机制的核心是插件系统,通过丰富的钩子和插件机制,开发者可以在编译的不同阶段插入自定义逻辑,从而实现高度定制化的构建流程。
常见事件
- before-run:在 webpack 开始编译之前触发,可以用于清除上一次构建的文件
- run:在 webpack 开始编译触发
- before-compile:在 webpack 开始编译之前触发,可以用于添加额外的编译配置或预处理代码
- compile:在 webpack 开始编译触发,可以用于监听编译过程或者处理编译错误
- this-compilation:在创建新的 compilation 对象时触发,compilation 对象代表当前编译过程中的所有状态和信息
- compilation:在编译代码期间触发,可以监听编译过程或处理编译错误
- emit:输出文件之前触发,用于修改输出文件或生成一些附加文件
- after-emit:输出文件之后触发,可以用于清理临时文件或资源
- done:编译完成时触发,可以用于生成构建报告
webpack 怎么知道哪个 plugin 在哪个生命周期使用?
Webpack 通过插件系统与编译生命周期的钩子机制来决定哪个 plugin 在哪个生命周期使用。每个插件在 Webpack 编译过程中会监听特定的事件钩子,并在对应的编译阶段执行相应的逻辑。
- 插件主动监听钩子:每个插件都会在自己的
apply()
方法中,监听 Webpack 生命周期的钩子。 - Webpack 主动触发钩子:在构建的不同阶段,Webpack 主动触发相应的钩子。
- 插件响应钩子事件:当钩子被触发时,所有监听该钩子的插件就会执行各自的逻辑。
💡 类比理解:可以将 Webpack 的钩子机制理解为“事件总线”:
- Webpack 在不同阶段广播事件(如 done, emit)。
- 插件就像订阅者,提前订阅感兴趣的事件。
- 当事件发生时,订阅者自动收到通知并执行相应处理。
compiler 和 compilation 的区别
Compiler(提供 webpack 的钩子):compiler 对象中保存着完整的 Webpack 环境配置,每次启动 webpack 构建时它都是一个独一无二,仅仅会创建一次的对象。
这个对象会在首次启动 Webpack 时创建,我们可以通过 compiler 对象上访问到 Webapck 的主环境配置,比如 loader 、 plugin 等等配置信息。
Compilation(对资源的处理):compilation 对象代表一次资源的构建(如开发模式下的热更新也会创建新的 Compilation),compilation 实例能够访问所有的模块和它们的依赖。
Compiler:只会创建一次
Compilation:会创建多次
❓ 插件如何绑定到生命周期?
插件通过访问 compiler.hooks.xxx.tap()
或 compilation.hooks.xxx.tap()
来绑定到某个生命周期钩子上。例如:
js
class MyPlugin {
apply(compiler) {
// 监听 done 钩子,在构建完成后执行
compiler.hooks.done.tap('MyPlugin', (stats) => {
console.log('Webpack 构建完成')
})
// 监听 emit 钩子,在输出文件前执行
compiler.hooks.emit.tap('MyPlugin', (compilation) => {
console.log('即将输出文件')
})
}
}
使用 webpack 时,用过哪些可以提高效率的插件?
- webpack-merge:提取公共配置,减少重复代码
- webpack-dev-server:开发服务器
- hot-module-replacement-plugin:hmr 热更新
如何优化 webpack 构建速度?
OneOf
:每个文件只能被其中一个 loader 处理,匹配上了第一个就不会去判断下面了- 使用 Cache(缓存),提升二次打包速度
- Thread(多进程打包):安装
thread-loader
- 代码压缩
terser-webpack-plugin
minimize-css-extract-plugin
- 缩小打包作用域:
exclude
/include
- 排除依赖
externals
,使用 cdn - code split 代码切割
- tree-shaking:只针对 esm 有效
- resolve:减少模块查找时间
文件指纹
是什么?
文件指纹:打包后输出的文件名后缀
- hash:整个项目的 hash 值,只要项目文件有变化,整个项目的 hash 值就会改变
- chunkhash:根据 chunk 生成 hash 值,如果 chunk 中的文件有变化,那么就会生成新的 hash 值
- contenthash:根据文件内容生成 hash 值,如果文件内容有变化,那么就会生成新的 hash 值
js 文件指纹
js
module.exports = {
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist')
}
}
css 文件指纹
js
modeule.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
})
]
}
作用
文件指纹,就是文件名加上 hash 值,可以防止缓存。
webpack 热更新原理
- 监控文件变化:Webpack 的开发服务器会监控项目中所有的模块文件,包括:JS 文件、CSS 文件、模板文件等。
- 模块热替换:当你在代码中做出更改并保存时,Webpack 检测到文件变化,会首先通过热替换插件(Hot Module Replacement Plugin)生成新的模块代码。
- 构建更新的模块:生成的新模块代码会被构建成一个独立的文件或数据块。
- 通知客户端:Webpack 开发服务器会将更新的模块代码的信息发送到浏览器。
- 浏览器端处理:浏览器接收到更新的模块信息后,会在不刷新页面的情况下通过热替换运行时(Hot Module Replacement Runtime)替换相应的模块。
- 应用程序状态保持:热更新还可以保持应用程序的状态。当修改代码不会丢失已有的数据、用户登录状态等。
- 回调处理:允许在模块更新时执行自定义的回调函数,可以处理特定的逻辑,以确保模块更新后的正确性。
概括:获取到变化的文件,使用 websocket 发送给客户端文件 hash,客户端收到消息后,发送请求,获取到文件内容,替换掉内存中的文件。
webpack5 比 webpack4 更新了什么?
- 优化了构建速度,开发模式,构建速度明显提升
- tree-shaking:算法优化
- 内置的持久化缓存:可以缓存每个模块的编译结果,减少重复构建时间。
- 支持 WebAssmbly
- 支持模块联邦(Module Federation):支持模块共享,远程加载(微前端的架构支持)
模块联邦
概念
模块联邦:多个项目之间共享代码的机制,特别适用于微前端架构
特点:
- 按需加载:只加载当前页面需要的模块,减少初始加载时间。
- 跨应用共享:多个微前端应用可以共享公共模块,避免重复打包。
- 独立部署:各个微前端应用可以独立开发、测试和部署。
使用
例如:在 App1 和 App2 应用中,需要同时共享同一个 ui 组件库
js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin')
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'app1',
filename: 'remoteEntry.js',
exposes: {
// 需要共享的模块
'./Button': './src/components/Button'
},
shared: ['react', 'react-dom'] // 共享的第三方库
})
]
}
js
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'app2',
filename: 'remoteEntry.js',
remotes: {
app1: 'app1@http://localhost:3000/remoteEntry.js' // 模块提供方名称和地址
},
shared: ['react', 'react-dom'] // 共享的第三方库
})
]
}
模块联邦 和 monorepo 的区别
模块联邦(Module Federation)和 Monorepo 是两种不同的概念,模块联邦主要解决的是运行时的模块加载和共享问题,而 Monorepo 则是关于如何组织和管理代码仓库。
两者可以相辅相成,共同提升开发和部署的效率。