webpack打包基本原理——实现webpack打包核心功能
webpack打包的基本原理
核心功能就是把我们写的模块化代码转换成浏览器能够识别运行的代码,话不多说我们一起来了解它
首先我们建一个空项目用 npm init -y 创建一个初始化的,在跟目录下创建src文件夹,src下创建index.js,add.js,square.js,tip.js。在根目录下创建index.html

index.html引入index.js,index.js引入add.js、square.js。square.js引入tip.js
//add.js
export default (a, b) => {return a + b;
};//square.js
import tip from "tip.js";
const square = (a) => {console.log(tip);return a * a;
};
export { square };//tip.js
export default "我是提示-----";
index.html
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body></body><script src="./src/index.js"></script>
</html>
运行index.html会报错,不能识别es6的模块化导入

让我们来实现webpack打包的核心功能
实现webpack打包核心功能
首先在根目录下建一个bundle.js用来对刚刚写的index.js进行打包
webpack官网对打包流程的介绍
it internally builds a dependency graph which maps every module your project needs and generates one or more bundles(webpack会在内部构建一个 依赖图(dependency graph),此依赖图会映射项目所需的每个模块,并生成一个或多个 bundle)
根据上面的说明进行分析,打包的工作基本流程如下
- 读取入口文件中的内容(也就是index.js文件)
- 分析入口文件,递归读取模块所依赖的文件 内容,生成依赖图
- 根据依赖图生成浏览器能运行的代码
1、处理单个模块内容
const fs = require("fs");
const getModuleInfo = (file) => {const body = fs.readFileSync(file, "utf-8");//获取文件内容,是字符串console.log(body);
};
getModuleInfo("./src/index.js");
打印出来是文件内容的字符串

借助安装@babel/parser 把,js文件代码转换成js对象,叫做抽象语法树(ast)
const fs = require("fs");
const parser = require("@babel/parser");
const getModuleInfo = (file) => {const body = fs.readFileSync(file, "utf-8");const ast = parser.parse(body, {//表示我们要解析的是es6模块sourceType: "module",});console.log(ast.program.body);
};
getModuleInfo("./src/index.js");
打印ast.program.body的结构:
[Node {type: 'ImportDeclaration', start: 0,end: 27,loc: SourceLocation { start: [Position], end: [Position],filename: undefined, identifierName: undefined},specifiers: [ [Node] ],source: Node {type: 'StringLiteral',start: 16,end: 26,loc: [SourceLocation],extra: [Object],value: './add.js'}},Node {type: 'ImportDeclaration',start: 29,end: 66,loc: SourceLocation {start: [Position],end: [Position],filename: undefined,identifierName: undefined},specifiers: [ [Node] ],source: Node {type: 'StringLiteral',start: 52,end: 65,loc: [SourceLocation],extra: [Object],value: './square.js'}},Node {type: 'VariableDeclaration',start: 68,end: 90,loc: SourceLocation {start: [Position],end: [Position],filename: undefined,identifierName: undefined},declarations: [ [Node] ],kind: 'const'},Node {type: 'VariableDeclaration',start: 92,end: 114,loc: SourceLocation {start: [Position],end: [Position],filename: undefined,identifierName: undefined},declarations: [ [Node] ],kind: 'const'},Node {type: 'ExpressionStatement',start: 116,end: 143,loc: SourceLocation {start: [Position],end: [Position],filename: undefined,identifierName: undefined},expression: Node {type: 'CallExpression',start: 116,end: 142,loc: [SourceLocation],callee: [Node],arguments: [Array]}},Node {type: 'ExpressionStatement',start: 145,end: 172,loc: SourceLocation {start: [Position],end: [Position],filename: undefined,identifierName: undefined},expression: Node {type: 'CallExpression',start: 145,end: 171,loc: [SourceLocation],callee: [Node],arguments: [Array]}}
]
type属性是ImportDeclaration的节点,其source.value属性是引入这个模块的相对路径,上面打印出两个ImportDeclaration节点,说明对应的是入口文件中的两个import
对ast.program.body做处理,本质上是对这个数组遍历,循环做处理,借助安装@babel/traverse来完成这项工作
const fs = require("fs");
const path = require("path");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;const getModuleInfo = (file) => {//1、把入口文件字符串ast对象const body = fs.readFileSync(file, "utf-8");const ast = parser.parse(body, {//表示我们要解析的是es6模块sourceType: "module",});//2、获取类型为ImportDeclaration的所依赖模块的信息地址const deps = {};//创建一个对象deps,用来收集模块自身引入的依赖,用traverse遍历需要的ImportDeclaration节点做处理,把相对路径转成绝对路径traverse(ast, {ImportDeclaration({ node }) {const dirname = path.dirname(file);let absPath = "./" + path.join(dirname, node.source.value);absPath = absPath.replace("\\", "/");deps[node.source.value] = absPath;},});console.log(deps);
};
getModuleInfo("./src/index.js");
deps打印出入口文件引入的依赖地址

获取依赖后,需要对ast做语法转换,把es6转成es5的语法,安装@babel/core以及@babel/preset-env完成
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 getModuleInfo = (file) => {//1、把入口文件字符串ast对象const body = fs.readFileSync(file, "utf-8");const ast = parser.parse(body, {//表示我们要解析的是es6模块sourceType: "module",});//2、获取类型为ImportDeclaration的所依赖模块的信息地址const deps = {};//创建一个对象deps,用来收集模块自身引入的依赖,用traverse遍历需要的ImportDeclaration节点做处理,把相对路径转成绝对路径traverse(ast, {ImportDeclaration({ node }) {const dirname = path.dirname(file);let absPath = "./" + path.join(dirname, node.source.value);absPath = absPath.replace("\\", "/");deps[node.source.value] = absPath;},});//3、把es6语法转换成es5const { code } = babel.transformFromAst(ast, null, {presets: ["@babel/preset-env"],});const moduleInfo = { file, deps, code };console.log(moduleInfo);return moduleInfo;
};
getModuleInfo("./src/index.js");
moduleInfo 打印结果为:

最终把一个模块的代码转化为一个对象形式的信息,这个对象包含文件的绝对路径,文件所依赖模块的信息,以及模块内部经过babel转化后的代码
接下去需要递归查找所有的模块,比如square.js里引用了tip.js。
这个过程也是获取依赖图(dependency graph)的过程
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 getModuleInfo = (file) => {//1、把入口文件字符串ast对象const body = fs.readFileSync(file, "utf-8");const ast = parser.parse(body, {//表示我们要解析的是es6模块sourceType: "module",});//2、获取类型为ImportDeclaration的所依赖模块的信息地址const deps = {};//创建一个对象deps,用来收集模块自身引入的依赖,用traverse遍历需要的ImportDeclaration节点做处理,把相对路径转成绝对路径traverse(ast, {ImportDeclaration({ node }) {const dirname = path.dirname(file);let absPath = "./" + path.join(dirname, node.source.value);absPath = absPath.replace("\\", "/");deps[node.source.value] = absPath;},});//3、把es6语法转换成es5const { code } = babel.transformFromAst(ast, null, {presets: ["@babel/preset-env"],});const moduleInfo = { file, deps, code };return moduleInfo;
};//4、递归获取所有模块信息和之间的依赖关系
const parseModules = (file) => {//定义依赖图const depsGraph = {};// 首先获取入口的信息const entry = getModuleInfo(file);const temp = [entry];for (let i = 0; i < temp.length; i++) {const item = temp[i];const deps = item.deps;if (deps) {//遍历模块的依赖,递归获取模块信息for (const key in deps) {if (deps.hasOwnProperty(key)) {temp.push(getModuleInfo(deps[key]));}}}}temp.forEach((moduleInfo) => {depsGraph[moduleInfo.file] = {deps: moduleInfo.deps,code: moduleInfo.code,};});console.log(depsGraph);return depsGraph;
};
parseModules("./src/index.js");
获得的depsGraph对象如下:
{file: './src/index.js',deps: { './add.js': './src/add.js', './square.js': './src/square.js' },code: '"use strict";\n' +'\n' +'var _add = _interopRequireDefault(require("./add.js"));\n' +'var _square = require("./square.js");\n' +'function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n' +'var sum = (0, _add["default"])(2, 3);\n' +'var sqr = (0, _square.square)(4);\n' +'console.log("sum===", sum);\n' +'console.log("sqr===", sqr);'
}
PS C:\Users\keyuan04\Desktop\webpack> node bundle.js
{'./src/index.js': {deps: { './add.js': './src/add.js', './square.js': './src/square.js' },code: '"use strict";\n' +'\n' +'var _add = _interopRequireDefault(require("./add.js"));\n' +'var _square = require("./square.js");\n' +'function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n' +'var sum = (0, _add["default"])(2, 3);\n' +'var sqr = (0, _square.square)(4);\n' +'console.log("sum===", sum);\n' +'console.log("sqr===", sqr);'},'./src/add.js': {deps: {},code: '"use strict";\n' +'\n' +'Object.defineProperty(exports, "__esModule", {\n' +' value: true\n' +'});\n' +'exports["default"] = void 0;\n' +'var _default = function _default(a, b) {\n' +' return a + b;\n' +'};\n' +'exports["default"] = _default;'},'./src/square.js': {deps: { 'tip.js': './src/tip.js' },code: '"use strict";\n' +'\n' +'Object.defineProperty(exports, "__esModule", {\n' +' value: true\n' +'});\n' +'exports.square = void 0;\n' +'var _tip = _interopRequireDefault(require("tip.js"));\n' +'function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n' +'var square = function square(a) {\n' +' console.log(_tip["default"]);\n' +' return a * a;\n' +'};\n' +'exports.square = square;'},'./src/tip.js': {deps: {},code: '"use strict";\n' +'\n' +'Object.defineProperty(exports, "__esModule", {\n' +' value: true\n' +'});\n' +'exports["default"] = void 0;\n' +'var _default = "我是提示-----";\n' +'exports["default"] = _default;'}
}
我们最终得到的模块分析数据如上图所示,接下来,我们就要根据这里获得的模块分析数据,来生产最终浏览器运行的代码。
上面打印的依赖图可以看到,最终的code里包含exports以及require这样的语法,所以,我们在生成最终代码时,要对exports和require做一定的实现和处理,把依赖图对象中的内容转换成能够执行的代码,以字符串形式输出。 我们把整个代码放在自执行函数中,参数是依赖图对象
把取得入口文件 的code信息,去执行。使用eval函数执行。
最后把生成的内容写入到dist文件中
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 getModuleInfo = (file) => {//1、把入口文件字符串ast对象const body = fs.readFileSync(file, "utf-8");const ast = parser.parse(body, {//表示我们要解析的是es6模块sourceType: "module",});//2、获取类型为ImportDeclaration的所依赖模块的信息地址const deps = {};//创建一个对象deps,用来收集模块自身引入的依赖,用traverse遍历需要的ImportDeclaration节点做处理,把相对路径转成绝对路径traverse(ast, {ImportDeclaration({ node }) {const dirname = path.dirname(file);let absPath = "./" + path.join(dirname, node.source.value);absPath = absPath.replace("\\", "/");deps[node.source.value] = absPath;},});//3、把es6语法转换成es5const { code } = babel.transformFromAst(ast, null, {presets: ["@babel/preset-env"],});const moduleInfo = { file, deps, code };return moduleInfo;
};//4、递归获取所有模块信息和之间的依赖关系
const parseModules = (file) => {//定义依赖图const depsGraph = {};// 首先获取入口的信息const entry = getModuleInfo(file);const temp = [entry];for (let i = 0; i < temp.length; i++) {const item = temp[i];const deps = item.deps;if (deps) {//遍历模块的依赖,递归获取模块信息for (const key in deps) {if (deps.hasOwnProperty(key)) {temp.push(getModuleInfo(deps[key]));}}}}temp.forEach((moduleInfo) => {depsGraph[moduleInfo.file] = {deps: moduleInfo.deps,code: moduleInfo.code,};});console.log(depsGraph);return depsGraph;
};//生成最终代码
const bundle = (file) => {//把依赖图转字符串,放在自执行函数中执行const depsGraph = JSON.stringify(parseModules(file));return `(function(graph){function require(file){var exports = {};function absRequire(relPath){return require(graph[file].deps[relPath])}(function(require,exports,code){eval(code)})(absRequire,exports,graph[file].code)return exports}require('${file}')})(${depsGraph})`;
};const content = bundle("./src/index.js");
fs.rmdirSync("./dist", { recursive: true });
// 写入到dist/bundle.js
fs.mkdirSync("./dist");
fs.writeFileSync("./dist/build.js", content);
最后在index.html中入这个./dist/bundle.js,在控制台就可以看到正确的输出结果

相关文章:
webpack打包基本原理——实现webpack打包核心功能
webpack打包的基本原理 核心功能就是把我们写的模块化代码转换成浏览器能够识别运行的代码,话不多说我们一起来了解它 首先我们建一个空项目用 npm init -y 创建一个初始化的,在跟目录下创建src文件夹,src下创建index.js,add.js…...
git的使用(终端输入指令) 上
git目录前言1.创建仓库2.创建文件和修改数据状态分区3 .删除、撤销重置 、和比较前言 今天带大家手把手敲一遍 git 流程: 安装一下git(详细观看我之前发的git文档࿰…...
react定义css样式,使用less,css模块化
引入外部 css文件 import ./index.css此时引入的样式是全局样式 使用less 安装 npm i style-loader css-loader sass-loader node-sass -D生成config文件夹 npm run eject配置 以上代码运行完,会在根目录生成config文件夹 进入 config > webpack.config.js 查找…...
基于JavaWeb的学生管理系统
文章目录 项目介绍主要功能截图:登录用户信息管理院系信息管理班级信息管理新增学生课程管理成绩管理部分代码展示设计总结项目获取方式🍅 作者主页:Java韩立 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系�…...
win11右键新建菜单添加选项
需要操作 2 处注册表, 以下以在右键新建菜单中添加 .html 为例 在主键 HKEY_CLASSES_ROOT 中,搜索 .html 找到后 ,右键点击它,选 新建 ->项, 在这里插入图片描述 项目名字是:ShellNew 新建后&#x…...
leetcode Day5(卡线复试,放弃版)
Day5 最后一个单词长度(要求最后一个,可以反向计数) int lens.length()-1; while(s.charAt(len)){len--;//最后是一个空格,就是无字符时 } int wordlen0;//记录字符长度 /*charAt() 方法用于返回指定索引处的字符。索引范围为从 0…...
cmake 入门二 库的编译,安装与使用
工程描述 1,建立一个静态库和动态库,提供HelloFunc 函数供其他程序编程使用,HelloFunc 向终端输出Hello World字符串。 2,安装头文件与共享库。 1 库的工程结构 1.1 工程目录下的CMakeLists.txt PROJECT…...
Python中实现将内容进行base64编码与解码
一、需求说明需要使用Python实现将内容转为base64编码,解码,方便后续的数据操作。二、base64简介Base64是一种二进制到文本的编码方式【是一种基于 64 个可打印字符来表示二进制数据的表示方法(由于 2^664,所以每 6 个比特为一个单…...
集合TreeSet的使用-java
TreeSet的特点:可排序、不重复、无索引。可排序:按照元素的大小默认升序排序;底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。对于数值、字符串类型的(Integer 、Double、String)TreeSet可以排…...
Mybatis-plus 分页集成以及基本使用总结 入门和案例 注解连表查询分页案例等
简介 Mybaits-plus 是mybits 的升级版,从mybaits 升级到mybaits-plus 可以实现平滑升级 Mybaits-plus 本身提供了大量的基本查询方法以及强大的 Wrapper(包装) 类 用于查询的 QueryWrapper 以及 更新的 UpdateWrapper ,使用Wrapper 基本已经可以构建大…...
5个设计师常用素材库
推荐5个设计素材网站,免费下载! 1、菜鸟图库 菜鸟图库-免费设计素材下载 菜鸟图库是一个素材量非常丰富的网站,该网站聚合了平面、UI、淘宝电商、高清背景图、图片、插画等高质量素材。平面设计模板非常多,很多都能免费下载&…...
PHP/7.2.11 缺少 apache2/logs/httpd.pid 文件
启动服务时:systemctl restart httpd.service,报错:● httpd.service - httpd serviceLoaded: loaded (/etc/systemd/system/httpd.service; enabled; vendor preset: disabled)Active: failed (Result: exit-code) since 五 2023-02-24 16:1…...
【centos7下部署mongodb】
一.安装环境 CentOS7MongoDB4.0.13正式版。 二.下载MongoDB 1.1 官网下载地址:https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-4.0.13.tgz 1.2 将压缩包通过xftp上传到服务器/opt目录,然后解压、改名 三. 配置环境变量及配置文件 3.1配置系…...
pycharm首次使用爬虫框架scrapy遇到的问题和解决方法(二)
在首次使用scrapy框架的过程中,一直是对着别人的框架步骤撸代码的。然而,撸着撸着发现好像别人的也用不了。后面就只能自己找踏坑了。 问题报错: 1,IndexError: list index out of range 2,pymysql.err.ProgrammingError: (1064, "You have an error in your SQL s…...
pyflink学习笔记(二):table_apisql
Joins Inner Join 官网说明:和 SQL 的 JOIN 子句类似。关联两张表。两张表必须有不同的字段名,并且必须通过 join 算子或者使用 where 或 filter 算子定义至少一个 join 等式连接谓词。先创建2个表,两个表的字段是相同的,我想验证…...
嵌入式 STM32 实现STemwin移植+修改其配置文件,驱动LCD显示文本 (含源码,建议收藏)
目录 一、STemwin 简介 二、源码下载 1、在移植STemwin源码之前,需要一个已经具备LCD读写,填充指定颜色等函数功能的一个工程; 2、STemwin 3、源码下载 三、STemwin移植 1、解压源码路径 2、STemwin文件介绍 四、修改配置文件&…...
[计算机网络(第八版)]第一章 概述(学习笔记)
1.1 计算机网络在信息时代中的作用 21世纪是以网络为核心的信息时代,21世纪的重要重要特征:数字化、网络化与信息化。 三大类网络 电信网络:向用户提供电话、电报、传真等服务;有线电视网络:向用户传送各种电视节目&am…...
AI绘图:常用镜头和视角
镜头 Establishing Shot 通过宽广或超宽广的视角交待故事发生的大地理环境 Master Shot 众多人物都能完整地出现在镜头里。 Wide Shot 广角镜头。又称“长镜头” Long Shot。人物全身展现,但在画面中所占比例相对较少 Ultrawide Shot 超广角镜头 Low Angle Sho…...
TCP四次挥手
TCP 四次挥手过程是怎样的? TCP 断开连接是通过四次挥手方式。 双方都可以主动断开连接,断开连接后主机中的「资源」将被释放,四次挥手的过程如下图: 客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1…...
Tomcat源码分析-类加载器
类加载器 在分析 tomcat 类加载之前,我们简单的回顾下 java 体系的类加载器 启动类加载器(Bootstrap ClassLoader):加载对象是java的核心类库,把一些的 java 类加载到 jvm 中,它并不是我们熟悉的 ClassLoader&#x…...
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 抗噪声…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...
解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...
使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...
跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
