【Webpack4打包机制原理解析】
webpack是一个打包模块化JavaScript的工具,在webpack里一切文件皆模块,通过Loader转换文件,通过Plugin注入钩子,最后输出由多个模块组合成的文件。webpack专注于构建模块化项目。
# 简单版打包模型步骤
我们先从简单的入手看,当 webpack 的配置只有一个出口时,不考虑分包的情况,其实我们只得到了一个bundle.js的文件,这个文件里包含了我们所有用到的js模块,可以直接被加载执行。那么,我可以分析一下它的打包思路,大概有以下4步:
- 利用
babel完成代码转换及解析,并生成单个文件的依赖模块Map - 从入口开始递归分析,并生成整个项目的依赖图谱
- 将各个引用模块打包为一个立即执行函数
- 将最终的
bundle文件写入bundle.js中
# 单个文件的依赖模块Map
- 我们会可以使用这几个包:
@babel/parser:负责将代码解析为抽象语法树@babel/traverse:遍历抽象语法树的工具,我们可以在语法树中解析特定的节点,然后做一些操作,如ImportDeclaration获取通过import引入的模块,FunctionDeclaration获取函数@babel/core:代码转换,如ES6的代码转为ES5的模式
由这几个模块的作用,其实已经可以推断出应该怎样获取单个文件的依赖模块了,转为
Ast->遍历Ast->调用ImportDeclaration。代码如下
// exportDependencies.js const fs = require('fs') const path = require('path') const parser = require('@babel/parser') const traverse = require('@babel/traverse').default const babel = require('@babel/core')const exportDependencies = (filename)=>{const content = fs.readFileSync(filename,'utf-8')// 转为Astconst ast = parser.parse(content, {sourceType : 'module'//babel官方规定必须加这个参数,不然无法识别ES Module})const dependencies = {}//遍历AST抽象语法树traverse(ast, {//调用ImportDeclaration获取通过import引入的模块ImportDeclaration({node}){const dirname = path.dirname(filename)const newFile = './' + path.join(dirname, node.source.value)//保存所依赖的模块dependencies[node.source.value] = newFile}})//通过@babel/core和@babel/preset-env进行代码的转换const {code} = babel.transformFromAst(ast, null, {presets: ["@babel/preset-env"]})return{filename,//该文件名dependencies,//该文件所依赖的模块集合(键值对存储)code//转换后的代码} } module.exports = exportDependencies
可以跑一个例子:
//info.js const a = 1 export a // index.js import info from'./info.js' console.log(info) //testExport.js const exportDependencies = require('./exportDependencies') console.log(exportDependencies('./src/index.js'))
单个文件的依赖模块Map
有了获取单个文件依赖的基础,我们就可以在这基础上,进一步得出整个项目的模块依赖图谱了。首先,从入口开始计算,得到
entryMap,然后遍历entryMap.dependencies,取出其value(即依赖的模块的路径),然后再获取这个依赖模块的依赖图谱,以此类推递归下去即可,代码如下:
const exportDependencies = require('./exportDependencies') //entry为入口文件路径 const exportGraph = (entry)=>{const entryModule = exportDependencies(entry)const graphArray = [entryModule]for(let i = 0; i < graphArray.length; i++){const item = graphArray[i];//拿到文件所依赖的模块集合,dependencies的值参考exportDependenciesconst { dependencies } = item;for(let j in dependencies){graphArray.push(exportDependencies(dependencies[j]))//关键代码,目的是将入口模块及其所有相关的模块放入数组}}//接下来生成图谱const graph = {}graphArray.forEach(item => {graph[item.filename] = {dependencies: item.dependencies,code: item.code}})//可以看出,graph其实是 文件路径名:文件内容 的集合return graph } module.exports = exportGraph
# 输出立即执行函数
首先,我们的代码被加载到页面中的时候,是需要立即执行的。所以输出的
bundle.js实质上要是一个立即执行函数。我们主要注意以下几点:
- 我们写模块的时候,用的是
import/export.经转换后,变成了require/exports - 我们要让
require/exports能正常运行,那么我们得定义这两个东西,并加到bundle.js里 - 在依赖图谱里,代码都成了字符串。要执行,可以使用
eval
因此,我们要做这些工作:
- 定义一个
require函数,require函数的本质是执行一个模块的代码,然后将相应变量挂载到exports对象上 - 获取整个项目的依赖图谱,从入口开始,调用
require方法。完整代码如下:
const exportGraph = require('./exportGraph') // 写入文件,可以用fs.writeFileSync等方法,写入到output.path中 const exportBundle = require('./exportBundle') const exportCode = (entry)=>{//要先把对象转换为字符串,不然在下面的模板字符串中会默认调取对象的toString方法,参数变成[Object object]const graph = JSON.stringify(exportGraph(entry))exportBundle(`(function(graph) {//require函数的本质是执行一个模块的代码,然后将相应变量挂载到exports对象上function require(module) {//localRequire的本质是拿到依赖包的exports变量function localRequire(relativePath) {return require(graph[module].dependencies[relativePath]);}var exports = {};(function(require, exports, code) {eval(code);})(localRequire, exports, graph[module].code);return exports;//函数返回指向局部变量,形成闭包,exports变量在函数执行后不会被摧毁}require('${entry}')})(${graph})`) } module.exports = exportCode
至此,简单打包完成。贴一下我跑的demo的结果。
bundle.js的文件内容为:
(function(graph) {//require函数的本质是执行一个模块的代码,然后将相应变量挂载到exports对象上function require(module) {//localRequire的本质是拿到依赖包的exports变量function localRequire(relativePath) {returnrequire(graph[module].dependencies[relativePath]);}var exports = {};(function(require, exports, code) {eval(code);})(localRequire, exports, graph[module].code);return exports;//函数返回指向局部变量,形成闭包,exports变量在函数执行后不会被摧毁}require('./src/index.js') })({"./src/index.js":{"dependencies":{"./info.js":"./src/info.js"},"code":"\"use strict\";\n\nvar _info = _interopRequireDefault(require(\"./info.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\n\nconsole.log(_info[\"default\"]);"},"./src/info.js":{"dependencies":{"./name.js":"./src/name.js"},"code":"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports[\"default\"] = void 0;\n\nvar _name = require(\"./name.js\");\n\nvar info = \"\".concat(_name.name, \" is beautiful\");\nvar _default = info;\nexports[\"default\"] = _default;"},"./src/name.js":{"dependencies":{},"code":"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.name = void 0;\nvar name = 'winty';\nexports.name = name;"}})
# webpack打包流程概括
webpack的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
- 初始化参数
- 开始编译 用上一步得到的参数初始Compiler对象,加载所有配置的插件,通 过执行对象的run方法开始执行编译
- 确定入口 根据配置中的 Entry 找出所有入口文件
- 编译模块 从入口文件出发,调用所有配置的 Loader 对模块进行编译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
- 完成模块编译 在经过第4步使用 Loader 翻译完所有模块后, 得到了每个模块被编译后的最终内容及它们之间的依赖关系
- 输出资源 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再将每个 Chunk 转换成一个单独的文件加入输出列表中,这是可以修改输出内容的最后机会
- 输出完成 在确定好输出内容后,根据配置确定输出的路径和文件名,将文件的内容写入文件系统中。
在以上过程中,
Webpack会在特定的时间点广播特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,井且插件可以调用Webpack提供的API改变Webpack的运行结果。其实以上7个步骤,可以简单归纳为初始化、编译、输出,三个过程,而这个过程其实就是前面说的基本模型的扩展。
相关文章:
【Webpack4打包机制原理解析】
webpack是一个打包模块化 JavaScript 的工具,在 webpack里一切文件皆模块,通过 Loader 转换文件,通过 Plugin 注入钩子,最后输出由多个模块组合成的文件。webpack专注于构建模块化项目。 # 简单版打包模型步骤 我们先从简单的入手…...
如何提高接口响应速度
在非大数据(几万以上记录)的情况下,影响接口响应速度的因素中最大的是查询数据库的次数,其次才是数组遍历和简单数据处理(如根据已有字段增加新的属性,或计算值)。 一般一次数据库查询需要50毫秒…...
项目敏感配置信息加固
概述 引入jasypt做密码等敏感配置信息的加固 项目集成依赖 pom.xml引入jasypt-spring-boot-starter依赖 <dependency><groupId>com.github.ulisesbocchio</groupId><artifactId>jasypt-spring-boot-starter</artifactId><version>3.0.…...
HCIA-AI课程大纲
该阶段详细介绍各个机器学习范式方法,涵盖有监督、无监督、半监督、强化学习,以及深度学习算法基础,共计 72 课时。 第一节:华为云 ModelArts 云服务开发环境搭建 - (2 课时) - 华为云 ModelArts 云服务简…...
keil program algorithm 出错
前段时间 在 调试下载算法时,遇到一个奇怪的问题 就是 加载下载算法后, 下载算法的RAM空间 大小不能修改为 单片机的最大RAM,只能改到最大4KB的空间大小, 再大就报错 刚开始报错 一直不知道原因,走了很多弯路, 到最…...
SITNE24V2BNQ-3/TR一种瞬态电压抑制器,对标PESD1CAN
SITNE24V2BNQ是一种瞬态电压抑制器,设计用于保护两个汽车控制器区域 网络(CAN)母线不受ESD等瞬变造成的损坏。 SITNE24V2BNQ采用SOT-23封装。标准产品不含铅和卤素。 产品参数 方向:双向通道数:2VRWM(V)(Max):24IPP8/20μS(A)(M…...
Vue3【四】使用Vue2的写法写一个新的组件子组件和根组件
Vue3【四】使用Vue2的写法写一个新的组件 Vue3【四】使用Vue2的写法写一个新的组件 Vue3是向下兼容的,所有可以使用Vue的选项式写法 运行截图 目录结构 文件源码 App.vue <template><div class"app"><h1>你好世界! 我是App根组件<…...
指标体系建设10大坑
在企业经营和运营管理中,指标体系的建设至关重要,它在一定程度上是反映业务的问题状况,影响决策者的决策。但是,在指标体系的建设过程中,常常会存在一些不容忽视的“坑”,今天做个总结,以下为个…...
ubuntu 20.04上docker 使用gpu
要在Docker容器中使用GPU,你需要确保系统上已经安装了正确的NVIDIA驱动程序,并且安装了NVIDIA Container Toolkit。以下是详细的步骤: 1. 安装NVIDIA驱动程序 确保你的系统上已经安装了适当版本的NVIDIA驱动程序。你可以通过运行以下命令来检查驱动程序是否正确安装: nv…...
短剧系统投流版开发,为运营公司投流业务赋能
短剧系统投流版开发是一项复杂的任务,旨在为运营公司的投流业务提供强大的技术支持和赋能。以下是一些关键步骤和考虑因素,以确保短剧系统投流版的成功开发: 一、明确业务需求与目标 首先,需要深入了解运营公司的业务需求、目标…...
入坑必看的几个嵌入式方向热点问题
我们为何要学嵌入式?---需求、薪资、长期发展 嵌入式是成为下一个JAVA吗? 互联网开发和嵌入式开发怎么选? 高薪热门就业方向有哪些? 刚入门,刚毕业,学完没有“工作经验”,能有人要吗&#x…...
电能表如何与智能家居进行有效的融合
随着智能家居技术的不断发展,越来越多的家庭开始使用智能家电、智能照明、智能安防等智能设备,以实现更加便捷、舒适、安全的居住环境。而电能表作为电力系统中不可或缺的一环,不仅承担着计量电能的重要职责,还可以为智能家居系统…...
jmeter多用户登录并退出教程
有时候为了模拟更真实的场景,在项目中需要多用户登录并退出操作,大致参考如下 多用户登录前面已经实现:参考博文 多用户登录并退出jmx文件:百度网盘 提取码:0000 一、多用户退出操作 添加一个setUp线程组࿰…...
阿里云ECS实例镜像本地取证
更新时间:2024年03月21日10:09:37 1. 说明 很多非法案件中,服务器是直接搭建在阿里云上的,比如我们在拿到OSSKey之后(技术方法、其它方法等),可以将涉案服务器镜像导出,在本地进行取证分析。 …...
不要硬来!班组管理有“巧思”
班组管理,听起来似乎是一个充满“硬气”的词汇,让人联想到严肃、刻板的制度和规矩。然而,在实际操作中,我们却需要运用一些“巧思”,以柔克刚,让班组管理既有力度又不失温度。 在班组管理中,我们…...
[原创][Delphi多线程]使用TMonitor和TQueue配合实现TThreadedQueue的经典使用案例.
[简介] 常用网名: 猪头三 出生日期: 1981.XX.XX QQ: 643439947 个人网站: 80x86汇编小站 https://www.x86asm.org 编程生涯: 2001年~至今[共22年] 职业生涯: 20年 开发语言: C/C、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python 开发工具: Visual Studio、Delph…...
vue3 基于el-tree增加、删除节点(非TypeScript 写法)
话不多说,直接贴代码 <template><div class"custom-tree-container"><!-- <p>Using render-content</p><el-tree style"max-width: 600px" :data"dataSource" show-checkbox node-key"id" …...
小抄 20240607
1 一定要多接触幸运的人,好运的人更有可能继续好运。 这不是迷信,好运的背后是见识、性格、逻辑的加持,一定有过人之处,才能经常好运。 反过来,那些经常走霉运的人,一定是底层逻辑出了问题,陷…...
【GIS教程】土地利用转移矩阵
随着科技社会的不断进步,人类活动对地理环境的影响与塑造日益明显,土地不断的侵蚀与改变也导致一系列的环境问题日益突出。土地利用/覆盖(LUCC)作为全球环境变化研究的重点问题为越来越多的国际研究机构所重视,研究它的…...
API接口测试工具:jmeter的安装、汉化、Jmeter桌面快捷图标和基本使用
文章目录 测试工具:JmeterJmeter安装和配置Jmeter汉化设置中文语言:永久方式设置中文语言:临时方式 设置Jmeter桌面快捷图标jmeter基本用法Jmeter无法保存测试问题解决 测试工具:Jmeter Jmeter依赖于JDK,所以必须确保…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...
uniapp中使用aixos 报错
问题: 在uniapp中使用aixos,运行后报如下错误: AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...
网络编程(UDP编程)
思维导图 UDP基础编程(单播) 1.流程图 服务器:短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...
tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...
OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
Java + Spring Boot + Mybatis 实现批量插入
在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法:使用 MyBatis 的 <foreach> 标签和批处理模式(ExecutorType.BATCH)。 方法一:使用 XML 的 <foreach> 标签ÿ…...
