webpack面试解析

参考:
上一篇webpack相关的系列:webpack深入学习,搭建和优化react项目
爪哇教育@字节面试官解析webpack-路白

1、Webpack中的module是什么?

通常来讲,一个 module 模块就是指一个文件中导出的内容,webpack 会将这些 module 打包成一个或多个 chunk 。
在webpack中module就是一个个打包模块,它通过模块之间的依赖关系构建出打包依赖路径,最终构建出一个bundle。
webpack支持ESModule、CommonJS、AMD、各种资源模块(image\Json\font等等)的打包。

  1. ESM
    import导入 & export导出,主要在浏览器环境中使用。
  2. commonJS
    主要在nodeJS中使用,module.exports暴露模块,require导入模块。
  3. AMD
    define定义模块 require导出模块
  4. css / less
    @import

2、chunk和bundle的区别是什么?

chunk是【webpack打包过程】中的Module集合,是打包过程中module集合的概念。
webpack打包是从一个入口模块开始,入口模块引用其他模块,然后其他的模块再引用更多模块,构成了一个引用关系集合,这些module就形成了一个chunk。
chunk会根据webpack的配置进行拆分,一般包含三种情况

  1. 项目入口(entry)。如果存在多个入口模块,可能会出现多条打包路径,每一条路径都会形成一个chunk。
  2. 通过import()动态引入的代码
  3. 通过splitChunks拆分出来的代码

Bundle是webpack最终输出的一个或者多个打包好的文件。
chunk是webpack的打包过程概念,bundle是打包完成后的结果,也就是说大部分的chunk在打包完成后,就成了bundle。

Chunk和Bundle的关系是什么?
大多数情况下,一个entry入口会生成一个chunk,一个bundle,但是也有例外,比如如果加入了devtool:sourcemap的配置,一个入口,一个chunk生成了两个Bundle。

Chunk和Bundle的关系探究 从一个空文件夹编写一个可使用webpack打包的项目。
  1. 空文件夹中npm init,在src文件夹下简单写几个文件。(先只写几个简单的js文件
  2. 安装webpack / webpack-cli
  3. 编写webpack配置。
module.exports = {mode:'production', // webpack要求必填entry: {index: './src/index.js, }, // 打包入口output: {path: path.join(__dirname, 'dist'), // 打包输出的路径filename: '[name].js' , // 打包输出的bundle名称。}
}// 上面的配置方式会输出一个chunk(index.js) / 对应一个Bundle(index.js).

但是如果在配置中多加一行:devtool: "source-map" // 生成代码调试可用的源码-打包后代码映射关系,打包出来的bundle就会多生成一个index.js.map。但是chunk还是只有一个,也就是说一个chunk对应了两个Bundle。
在这里插入图片描述

多入口的打包情况
  1. 入口为一个key对应一个入口文件数组,仍然只会输出一个chunk。
module.exports = {// ...entry: {index: ['./src/index.js','./src/add.js'], }, // 打包入口
}// 上面的配置方式会输出一个chunk(index.js) / 对应一个Bundle(index.js).
  1. 入口为多个key,一个入口对应一个chunk
module.exports = {// ...entry: {index: './src/index.js', common: './src/common.js'}, 
}
下载了一个lodash依赖,然后加入其他的优化配置 会出现几个chunk 、 bundle
module.exports = {mode:'production', // webpack要求必填entry: {index: './src/index.js, other: './src/multiple.js'}, // 打包入口output: {path: path.join(__dirname, 'dist'), // 打包输出的路径filename: '[name].js' , // 打包输出的bundle名称。},optimization: { // 优化持久缓存的配置,比如动态引入导致的hashName变动 、 代码分包等。runtimeChunk: 'single', // runtimesplitChunks: {cacheGroups: {commons: {chunks: 'initial',minChunks: 2,minSize:0},vendor: {test: /node_modules/,chunks: 'initial',name: 'vendor',enforce: true}}}}
}

出现了5个包

  1. 两个entry
  2. runtimeChunk
    官网说明
    webpack之optimization.runtimeChunk作用
    runtimeChunk:single会生成一个在所有生成chunk之间运行的文件,文件中包含chunk映射关系清单等,如果没有设置runtime,这个映射关系清单信息就会放在入口文件打包出来的chunk内部,假如我们修改了一个模块,那这个模块的hash值变动后,所有相关的chunk hash值都会变动,导致缓存失效,配置了runtime后就只有runtime和被修改的模块的hash值才会变动,其他模块的hash值不变,浏览器缓存仍然存在。

目的:为了更好地利用浏览器缓存,不至于每一次打包都导致主包的hash值变动。

  1. SplitChunks common包和 SplitChunks vendor包
    commons包的内容:只要被两个chunk同时引用的公共代码,就提取到common里面。
    vendor包的内容:顾名思义,提取node_module的依赖到这个包里。
SplitChunks

官网说明
splitChunks探索
如何使用 splitChunks 精细控制代码分割
webpack splitChunks配置(一)chunks属性的使用

SplitChunks是 Webpack 中一个可以配置代码分割规则,提取或分离代码的插件,主要作用是提取公共代码,防止代码被重复打包,以及拆分过大的js文件,合并零散的js文件。

SplitChunks插件配置选项

意思是只要符合下面的条件就会强制分块。

// webpack4的默认配置(开箱即用)splitChunks: {// 表示选择哪些 chunks 进行分割,可选值有:async,initial和allchunks: "async",// 表示新分离出的chunk必须大于等于minSize,默认为30000,约30kb。minSize: 30000,// 表示一个模块至少应被minChunks个chunk所包含才能分割。默认为1。minChunks: 1,// 表示按需加载文件时,并行请求的最大数目。默认为5。maxAsyncRequests: 5,// 表示加载入口文件时,并行请求的最大数目。默认为3。maxInitialRequests: 3,// 表示拆分出的chunk的名称连接符。默认为~。如chunk~vendors.jsautomaticNameDelimiter: '~',// 设置chunk的文件名。默认为true。当为true时,splitChunks基于chunk和cacheGroups的key自动命名。name: true,// cacheGroups 下可以可以配置多个组,每个组根据test设置条件,符合test条件的模块,就分配到该组。模块可以被多个组引用,但最终会根据priority来决定打包到哪个组中。默认将所有来自 node_modules目录的模块打包至vendors组,将两个以上的chunk所共享的模块打包至default组。cacheGroups: {vendors: {test: /[\\/]node_modules[\\/]/,priority: -10},// default: {minChunks: 2,priority: -20,reuseExistingChunk: true}}
}// 分包条件
// 模块在代码中被复用或者来自 node_modules 文件夹
// 模块的体积大于等于30kb(压缩之前)
// 当按需加载 chunks 时,并行请求的最大数量不能超过5
// 页面初始加载时,并行请求的最大数量不能超过3
理解 chunks的三个选项
  • chunks: “initial”
    initial表示 同时打包异步同步,但是异步内部的引入将不再拆分,直接打包一起进一个文件中。也就是说每一次遇到一个异步加载的模块就相当于多开了一个入口,接下来只会根据这个入口内的依赖路径生成chunk。如果一个文件既被异步引入又被同步引入,它们将分别被打包到两个文件。
  • chunks: “async”
    async表示只拆分异步加载的模块,如果异步加载的模块内有同步加载的模块,则会将同步加载的模块并入main.js的入口文件里。
  • chunks: “all”
    all表示以上两者都包括,如果一个文件被同步引入又被异步引入,会被提取出来打包到一个文件,其他的文件引入的都是这个被打包出来的同一份文件。 也就是说chunk 可以在异步和非异步 chunk 之间共享。

在这里插入图片描述

Plugin和Loader分别是什么,如何工作

  1. Loader
    由于webpack 本身只能打包commonjs规范的js文件,对css,图片等格式的文件没法打包,因此引入了Loader来帮助打包,Loader可以看作是一个各种类型的模块翻译器,将非js模块转化为webpack能识别的js模块,比如css-loader会把css文件最后打包成css.js。
    loader运行在打包文件之前(loader为在模块加载时的预处理文件)

  2. Plugin
    可以完成各种loader不能完成的功能。plugin扩展了webpack的功能,webpack基于事件流框架 Tapable,它会在运行的各个阶段广播出事件,插件会监听对应的事件,在打包过程中对打包代码做相应处理。。从打包优化和压缩,到重新定义环境变量,还可以用来处理各种各样的任务。因此webpack可以做到针对各种情况灵活处理。
    Plugin在整个编译周期中都可以使用。

其他的webpack打包过程概念:webpack编译会创建的两个核心对象
  1. Compiler
    是一个包含了webpack环境的所有配置信息的对象,可以理解为webpack的实例,是全局唯一的,它内部包括options / loader / plugin的信息和 webpack 整个生命周期相关的钩子。

  2. Compliation
    包含了当前的模块资源和编译生成资源,webpack在开发模式下运行的时候,每当检测到一个文件变化,就会创建一次新的Compliation。

事件节点:
run:开始编译
make:从entry开始递归分析依赖并对依赖进行build
build-moodule:使用loader加载文件并build模块
normal-module-loader:对loader加载的文件用acorn编译,生成抽象语法树AST
program:开始对AST进行遍历,当遇到require时触发call require事件
seal:所有依赖build完成,开始对chunk进行优化(抽取公共模块、加hash等)
optimize-chunk-assets:压缩代码
emit:把各个chunk输出到结果文件

如何实现一个Plugin / loader

web前端高级webpack - webpack常见面试题及手写loader和plugin

实现loader

在编写 loader 前,我们首先需要了解 loader 的本质
其本质为函数,函数中的 this 作为上下文会被 webpack 填充,因此我们不能将 loader设为一个箭头函数
函数接受一个参数,为 webpack 传递给 loader 的文件源内容
函数中 this 是由 webpack 提供的对象,能够获取当前 loader 所需要的各种信息
函数中有异步操作或同步操作,异步操作通过 this.callback返回,返回值要求为 string 或者 Buffer
一般在编写loader的过程中,保持功能单一,避免做多种功能
代码如下所示:

// 导出一个函数,source为webpack传递给loader的文件源内容 
module.exports = function(source) { const content = doSomeThing2JsString(source); // 如果 loader 配置了 options 对象,那么this.query将指向 options const options = this.query; // 可以用作解析其他模块路径的上下文 console.log('this.context'); /* * this.callback 参数: * error:Error | null,当 loader 出错时向外抛出一个 error * content:String | Buffer,经过 loader 编译后需要导出的内容 * sourceMap:为方便调试生成的编译后内容的 source map * ast:本次编译生成的 AST 静态语法树,之后执行的 loader 可以直接使用这个 AST,进而省去重复生成 AST 的过程 */ this.callback(null, content); // 异步 return content; // 同步 
} 

实现一个能够删除源码中的console语句的Loader

const parser =  require('@babel/parser') ;// 可以把js源码转成 ast语法树
const traverse = require('@babel/traverse').default // 可以递归遍历 ast节点
const generator = require('@babel/generator').default; // 把修改好的ast语法树 再转成源码字符串
const types = require('@babel/types') // 操作节点的增删改module.exports = function(source){const ast = parser.parse(source,{sourceType:'module'})// console.log(ast.program.body);traverse(ast,{CallExpression(path){// console.log(path)if(types.isMemberExpression(path.node.callee) && types.isIdentifier(path.node.callee.object,{name:'console'})){path.remove()}  }})const output = generator(ast,{},source);return output.code
}
实现plugin

如果自己要实现plugin,需要遵循一定的规范:

  1. 插件必须是一个函数或者是一个包含 apply 方法的对象,这样才能访问compiler实例
  2. 传给每个插件的 compiler 和 compilation 对象都是同一个引用,因此不建议修改
  3. 异步的事件需要在插件处理完任务时调用回调函数通知 Webpack 进入下一个流程,不然会卡住
class MyPlugin {// Webpack 会调用 MyPlugin 实例的 apply 方法给插件实例传入 compiler 对象apply (compiler) {// 找到合适的事件钩子,实现自己的插件功能compiler.hooks.emit.tap('MyPlugin', compilation => {// compilation: 当前打包构建流程的上下文console.log(compilation);// do something...})}
}

打包的时候生成一个显示当前项目文件列表的myfile.md说明文件。

class Myplugin{// 可以再打包的时候产生一个新的文件constructor(options){// new 这个插件是传进来的参数this.options = options}apply(compiler){const hooks = compiler.hooks;// 监听事件if(hooks){hooks.emit.tap('myplugin',function(complication,callback){var str = '文件列表是:\n';for(let k in complication.assets){str += `文件名:${k},,,大小是 ${ complication.assets[k].size()} \n\n`}complication.assets['myfile.md'] = {source(){return str},size(){return str.length}}callback&&callback()})}else{compiler.plugin('emit',function(complication,callback){var str = '文件列表是:\n';for(let k in complication.assets){str += `文件名:${k} , 大小是 ${ complication.assets[k].size()} \n\n`}complication.assets['myfile.md'] = {source(){return str},size(){return str.length}}callback&&callback()})}}
}
module.exports = Myplugin

简单描述一下webpack打包过程?

流程可以大致划分为 以下7个阶段

  1. 初始化参数:从配置文件webpack.config.js和 Shell 语句中读取与合并参数,得出最终的参数
  2. 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译
  3. 确定入口:根据配置中的 entry 找出所有的入口文件,一个入口就会形成一个依赖图路径,最后打成一个包。
  4. 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
  5. 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会,一般对应compiler的emit事件。
  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

webpack热更新

Webpack HMR 原理解析
Webpack 的热更新又称热替换(Hot Module Replacement),缩写为 HMR。 这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。 HMR的核心就是客户端从webpack服务端拉取更新后的文件信息,准确的说是chunk 需要更新的部分的模块哈希值。
webpack-dev-server(WDS) 与浏览器之间维护了一个 Websocket,当本地资源发生变化时,WDS 会向浏览器推送更新,并带上构建时的 hash,让客户端与上一次资源进行对比。
客户端对比出差异后会向 WDS 发起 Ajax 请求来获取更改内容(文件列表、hash值的JSON串),这样客户端就可以再借助这些信息继续向 WDS 发起 jsonp 请求获取该chunk的增量更新。
后续的部分(拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?)由 HotModulePlugin 来完成,提供了相关 API 以供开发者针对自身场景进行处理。像react-hot-loader 和 vue-loader 都是借助这些 API 实现 HMR。

如何优化webpack打包?

在淘宝优化了一个大型项目,分享一些干货
「吐血整理」再来一打Webpack面试题
一般来说我们的优化有两个方向:

  1. 优化用户体验
    减少首屏加载时间
    提升各项交互的流畅度,如表单验证和页面切换
  2. 优化开发体验
    优化打包速度,减少构建耗时

在webpack中可以使用按需加载、异步加载分包、提取公共代码等方式去优化。

  1. 利用optimization.splitChunks分包。
    提取依赖包,把分包的正则属性配置路径为node_modules。
    提取重复模块,把重复的模块尽量提取到同一个chunk里,比如说设置一个common包的规则,一旦达到这个规则的公共代码就会被分出来一个包,一般来说设置chunks:'all’的属性即可。

为了保证首屏渲染时间,需要使用懒加载,也就是异步引入的方式优化,把首屏暂时用不到的包异步引入,这样分包的时候就会自动把这些文件提取到别的chunk里了。如果担心分的包太细碎会发起太多请求,可以使用MinSize和限制最高分包数等属性来限制。

  1. 开启 optimization.runtimeChunk 属性,将模块依赖关系单独拉出来一个分包,这样如果一个模块变动,与它相关的其他模块内容就不会跟着连锁变动了,名字hash值也不会变,就可以继续利用之前的缓存设置。

  2. 配置css和js文件的代码压缩:css-minimizer-webpack-plugin 和 webpack-parallel-uglify-plugin 插件。

  3. 可以通过多线程来提升Webpack打包速度的工具:HappyPack(不维护了)、thread-loader,一般用在babel-loader之类转译文件多、时间长的Loader上效果会比较明显。

  4. webpack5还提出了一个Module Federation(模块联邦)的属性配置,有利于加快应用启动和编译速度。 比如UMI的项目就可以直接在config里面配置MFSU之后,编译速度大大加快。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/682457.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Amber-Leedcode-Java - 代码随想录打卡第38 - 39天-动态规划汇总

本质上感觉是一个相加的问题,状态的转换,由前一种状态推至下一种状态509. 斐波那契数 较为简单 746. 使用最小花费爬楼梯 62. 不同路径 一开始写的时候被吓到了,但是发现听完一半之后再写还是比较容易的 对于我而言主要是找到逻辑&#xf…

Vue3+Ant-Design-Vue:报错Cannot read properties of null (reading ‘isCE‘)

问题描述 在使用Ant-Design-Vue内置的Table表格组件,实现expand展开行功能时,报错:Uncaught TypeError: Cannot read properties of null (reading ‘isCE‘) 。 报错信息图示: 在GitHub上找到如下描述, 解决方案 网上…

【漏洞复现】和为顺IP-COM WiFi未授权下载漏洞

Nx01 产品简介 深圳市和为顺网络技术有限公司是一家聚焦于商用级网络通信设备的研发与应用,为全球中小型企业提供高速、安全、易维护的网络设备产品和解决方案的公司。 Nx02 漏洞描述 深圳市和为顺网络技术有限公司IP-COM WiFi方案解决专家存在任意文件下载漏洞&am…

springsecurity6使用

spring security 中的类 : AuthenticationManager : 实现类:ProviderManager 管理很多的 provider ,,, 经常使用的,DaoAuthenticationProvider , 这个要设置一个 UserDetailService , 查找数据库&#xff…

分享88个CSS3特效,总有一款适合您

分享88个CSS3特效,总有一款适合您 88个CSS3特效下载链接:https://pan.baidu.com/s/1pDAyFESnO8HSnCZj4-DOzQ?pwd8888 提取码:8888 Python采集代码下载链接:采集代码.zip - 蓝奏云 学习知识费力气,收集整理更不…

第13章 网络 Page724 asio定时器

程序代码: 11行,声明一个ios对象 13行,使用ios对象作为参数声明一个定时器,此时,定时器和ios完成了关联,后面定时器如果有任务的话,就可以将任务交给ios 16行,为定时器设置一个定…

【日常聊聊】新年新征程:迎接学习的挑战

🍎个人博客:个人主页 🏆个人专栏:日常聊聊 ⛳️ 功不唐捐,玉汝于成 目录 前言 正文 结语 我的其他博客 前言 随着新的一年的到来,程序员们站在了全新的起点。这是一个充满机遇和挑战的时刻&#xff0…

【JavaEE】_HTTP请求与响应

目录 1. HTTP协议 1.1 HTTP简介 1.2 Fiddler 2. HTTP请求 2.1 首行 2.2 请求头(header) 2.3 空行 2.4 正文(body) 3. HTTP响应 3.1 首行 3.2 响应头(header) 3.3 空行 3.4 正文(bo…

51单片机编程基础(C语言):LED点阵屏

点阵屏介绍 类似于数码管,要用到肉眼视觉效应。扫描,才能把每一个LED都能选中,从而显示我们想要的图形,否则, 只能一次点亮一个LED, LED使用 51单片机点阵屏电路图: 实际连接顺序如下图&#…

爱快使用VPN

文章目录 一、VPN服务器1. 各种VPN比较2. PPTP服务端配置3. 创建登录账号4. 创建端口映射5. 设置动态域名 二、Windows客户端1. 连接配置2. 不能连接 Internet 配置 一、VPN服务器 1. 各种VPN比较 PPTPIPSECOpenVPN简介微软推出的VPN协议,占用资源少更高级的VPN协…

php基础学习之分支结构和循环结构(不细讲,来对比一下和两大常用高级编程语言(C++/Java)的细微区别以便记忆)

分支结构 常见分支结构 编程语言常见分支结构有: if语句if-else语句if-elseif-else语句switch语句 其中,除了if-elseif-else语句外,另外3中分支语句在php中和C/Java是一模一样的! 而if-elseif-else的唯一不同点就在,【…

Linux查看日志的几种方法总结

摘要 Linux系统中查看日志的命令确实多种多样,每个命令都有其特定的用途和优势。常用的命令有:tail、cat、tac、head、echo,grep、less、awk、sed。 下面我会详细解释这些命令在查看日志时的用法和特点: tail命令: ta…

机器学习:ROC曲线笔记

ROC曲线(Receiver Operating Characteristic Curve)是一种用于评估二分类模型性能的图形化工具,主要用于展示在不同阈值(Threshold)下模型的真阳性率(True Positive Rate,TPR)和假阳…

寒假作业:2024/2/14

作业1&#xff1a;编程实现二维数组的杨辉三角 代码&#xff1a; #include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, const char *argv[]) {int n;printf("please enter n:");scanf("%d",&n);int a…

Redis核心技术与实战【学习笔记】 - 31.番外篇:Redis客户端如何与服务器端交换命令和数据

简述 Redis 使用 RESP 协议&#xff08;Redis Serialzation Protocol&#xff09;协议定义了客户端和服务器端交互的命令、数据的编码格式。在 Redis 2.0 版本中&#xff0c;RESP 协议正式称为客户端和服务器端的标准通信协议。从 Redis 2.0 到 Redis 5.0 &#xff0c;RESP 协…

leetcode:55.跳跃游戏

1.解题思路&#xff1a;贪心算法看最大覆盖范围 2.模拟过程&#xff1a; 1.若数组长度等于1&#xff0c;直接返回True 2.循环遍历覆盖范围&#xff0c;选取最大的覆盖范围&#xff1b;若覆盖范围覆盖到了最后一个元素&#xff0c;直接返回true. 3.代码&#xff1a;(贪心无套…

ros自定义msg记录

文章目录 自定义msg1. 定义msg文件2. 修改 package.xml3. 修改 CMakeLists.txt4. message_publisher.py5. message_subscriber.py6. 运行 catkin build 测试 自定义msg ros 版本&#xff1a;kinetic 自定义test包的文件结构如下 |-- test | |-- CMakeLists.txt | |-- msg…

Hive3.1.2——企业级调优

前言 本篇文章主要整理hive-3.1.2版本的企业调优经验&#xff0c;有误请指出~ 一、性能评估和优化 1.1 Explain查询计划 使用explain命令可以分析查询计划&#xff0c;查看计划中的资源消耗情况&#xff0c;定位潜在的性能问题&#xff0c;并进行相应的优化。 explain执行计划…

收藏:关于块存储,文件存储和对象存储

在B站上看到”【IT老齐465】“这个系列相当不错&#xff0c;每次的视频15分钟左右&#xff0c;出了400多个了&#xff0c;今天偶然看到&#xff0c;地址是&#xff1a;【IT老齐465】块存储、文件存储、对象存储的关系与区别_哔哩哔哩_bilibili 精彩摘录如下&#xff1a;

【运维测试】移动测试自动化知识总结第1篇:移动端测试介绍(md文档已分享)

本系列文章md笔记&#xff08;已分享&#xff09;主要讨论移动测试相关知识。主要知识点包括&#xff1a;移动测试分类及android环境搭建&#xff0c;adb常用命令&#xff0c;appium环境搭建及使用&#xff0c;pytest框架学习&#xff0c;PO模式&#xff0c;数据驱动&#xff0…