Webpack 面试经验知识点分享
2. 减少文件搜索范围
resolve.modules 的默认值是 ['node_modules'],含义是先去当前目录下的 ./node_modules
目录下去找想找的模块,如果没找到就去上一级目录 ../node_modules
中找,再没有就去 ../../node_modules
中找,以此类推,这和 Node.js 的模块寻找机制很相似。
resolve.alias 配置项通过别名来把原导入路径映射成一个新的导入路径。
resolve: {
// 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
// 其中 __dirname 表示当前工作目录,也就是项目根目录
modules: [path.resolve(__dirname, 'node_modules')]
// 只采用 main 字段作为入口文件描述字段,以减少搜索步骤
mainFields: ['main'],
// 使用 alias 把导入 react 的语句换成直接使用单独完整的 react.min.js 文件,
// 减少耗时的递归解析操作
alias: {
'react': path.resolve(__dirname, './node_modules/react/dist/react.min.js'), // react15
// 'react': path.resolve(__dirname, './node_modules/react/umd/react.production.min.js'), // react16
}
// 尽可能的减少后缀尝试的可能性
//在导入语句没带文件后缀时,Webpack 会自动带上后缀后去尝试询问文件是否存在
extensions: ['js'],
// 独完整的 `react.min.js` 文件就没有采用模块化,忽略对 `react.min.js` 文件的递归解析处理
//让 Webpack 忽略对部分没采用模块化的文件的递归解析处理,这样做的好处是能提高构建性能。
noParse: [/react\.min\.js$/],
},
3 动态链接库
在一个动态链接库中包含给其他模块调用的函数和数据
- 把网页依赖的基础模块抽离出来,打包进一个个动态链接库,一个动态链接库中包含多个模块
- 当需要导入的模块在动态链接中,这个模块不能在被重新打包 而是直接去动态链接库中
- 页面依赖的所有动态链接库需要被加载 原因在于包含大量复用模块的动态链接库只需要编译一次,在之后的构建过程中被动态链接库包含的模块将不会在重新编译,而是直接使用动态链接库中的代码。
由于动态链接库中大多数包含的是常用的第三方模块,例如 react、react-dom,只要不升级这些模块的版本,动态链接库就不用重新编译。
4 Cdn 加速 内容分发网络
压缩代码减少网络传输大小
Webpack 接入 cdn
- 静态资源的导入 URL 需要变成指向 CDN 服务的绝对路径的 URL 而不是相对于 HTML 文件的 URL。
- 静态资源的文件名称需要带上有文件内容算出来的 Hash 值,以防止被缓存。
- 不同类型的资源放到不同域名的 CDN(同一时刻针对同一个域名的资源并行请求是有限制的话(具体数字大概 4 个左右,不同浏览器可能不同)) 服务上去,以防止资源的并行加载被阻塞。这样会增加域名解析的时间 可以通过 head 标签<link rel="dns-perfetch" href="//sxx.com"/>预解析域名 减少域名解析的时间
压缩代码
- 混淆源码
- 减少网络传输流量
- 提升网页加速
5 背后的运行机制
- plugin:扩展插件,在 webpack 构建过程中的特定时机会广播事件,插件监听特定事件,在特定时机作一些操作
- loader:模块转换器,用于把模块原内容转换成新的内容
webpack 的构建过程是串行化的从启动到结束 经过这些过程
- 确定配置参数: 配置文件中的参数和 shell 命令行中的参数合并成最终的参数
- 初始化 compiler 对象: 使用得出最终的参数初始化 compiler 对象,加载所有插件配置,执行 compiler 对象的 run 方法开始构建
- 确定入口:根据 entry 参数确定入口文件
- 编译模块:从入口文件开始调用配置的 loader 对文件进行转换,在找出入口文件依赖的模块,逐个对他们进行转换,入口文件依赖的模块都被转换过了
- 完成编译: 在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
- 输出资源:根据入口与模块之间的依赖关系,产出一个个包含多个模块的 chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这里是改变输出内容的最后机会
- 输出完成:在确定好输出资源后,根据配置的路径和文件名,把文件内容写进到文件系统
Compiler 全局只有一个,包含完整的 webpack 的配置, 负责文件监听和启动编译
当 Webpack 以开发模式运行时,每当监测到文件变化,就会生成一个新的 Compilation 对象,Compilation 对象包含了当前模块的资源,编译生成资源,变化的文件等,Compilation 对象也提供了很多事件回调供插件使用
初始化阶段:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler。
- 初始化参数
- 实例化 compiler
- 加载插件,依次调用插件的 apply 方法,让插件可以监听后续的所有事件节点。
- environment: 应用 node.js 的文件系统到 compiler 对象
- entry-option 根据配置的 entry 信息,给每个 entry 生成一个对应的 entryPlugin,为入口的解析,递归解析依赖作准备
- after-plugins:当所有插件 apply 方法调用完成
- after-resolvers
编译阶段:从 Entry 发出,针对每个 Module 串行调用对应的 Loader 去翻译文件内容,再找到该 Module 依赖的 Module,递归地进行编译处理。
- run 开始一次新的编译
- complier: 通知插件将开始一次新的编译
- complilation: 生成新的 complilation 对象
- make 一个新的 Compilation 创建完毕,即将从 Entry 开始读取文件,根据文件类型和配置的 Loader 对文件进行编译,编译完后再找出该文件依赖的文件,递归的编译和解析。
- after-compile 一次 Compilation 执行完成。
- invalid 当遇到文件不存在、文件编译错误等异常时会触发该事件,该事件不会导致 Webpack 退出。
输出阶段:对编译后的 Module 组合成 Chunk,把 Chunk 转换成文件,输出到文件系统。
- emit: 确定好要输出哪些文件后,执行文件输出,可以在这里获取和修改输出内容。
- after-emit 文件输出完毕
- done:成功完成一次完成的编译和输出流程。
- failed 如果在编译和输出流程中遇到异常导致 Webpack 退出时,就会直接跳转到本步骤,插件可以在本事件中获取到具体的错误原因。
在输出阶段已经得到了各个模块经过转换后的结果和其依赖关系,并且把相关模块组合在一起形成一个个 Chunk。
6.输出文件分析
原来一个个单独的模块文件被合并到一个文件中:bundle.js
为什么 bunlde.js 能直接在浏览器中运行呢?bundle.js 中能直接运行在浏览器中原因是在输出文件中通过_webpack_require_函数定义了一个在浏览器中执行的加载函数来模拟 node.js 中的 require 语句。
做不到和 node 一样在本地加载文件,当发现需要加载新的文件时需要通过网络,但是模块多了的化,时间就很长,所有就放在数组里,一次加载完
_webpack_require_ 函数还有缓存处理,已经加载过的模块不会进行二次加载,会去缓存中获取
7 抽取页面公共代码
改善什么现象
- 相同的资源被重复加载,浪费用户的流量 服务器的成本
- 包体也大
8 按需加载
最关键的一行是 chunkFilename: '[name].js',
,它专门指定动态生成的 Chunk 在输出时的文件名称。 如果没有这行,分割出的代码的文件名称将会是 [id].js。
import('/*webpackChunkName:"show"*/''./show.js').then((show)=> {
show('Webpack');
})
9 plugin
webpack 就像一个生产线,通过一系列流程才能将源文件转换成输出结果,生产线上每个处理流程的职责是单一的,多个流程存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。
插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。
10 loader 链式调用 翻译员
Loader 运行在 Node.js 中,你可以调用任何 Node.js 自带的 API,loader 将源代码经过转换后输出新的结果支持链式调用。
在你开发一个 Loader 时,请保持其职责的单一性,你只需关心输入和输出
module.exports = function(source){
return source;
}
loader 有同步和异步之分
Webpack 会默认缓存所有 Loader 的处理结果,
加载本地 loader
- npm-link
11 Prepack
Prepack 在编译阶段就预先执行源码得到结果,再把结果输出来以提高性能,而不是在运行时再去求结果,缺点:
- 还不够成熟
- 不能够识别 Dom APi 部分 Node.js API
- 存在优化后代码性能更低的情况
- 存在优化后代码体积更大的情况
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论