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…...

MySQL进阶篇之视图/存储过程/触发器
今天我们主要来快速学习视图,存储过程,触发器四个方面的内容,一起加油学习吧,还有半年就有秋招了,要加快速度了,迫在眉睫,冲吧,兄弟们。 目录 1、视图 2、存储过程 3、存储函数 4、…...

【一看就会】实现仿京东移动端页面滚动条布局
简单粗暴直接上代码: <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta http-equiv"X-UA-Compatible" content"IEedge"> <meta name"viewport" content&q…...

网易的“草长莺飞二月天”:增长稳健,加码研发,逐浪AI
2月23日,网易发布了2022年第四季度财报。 这是网易与暴雪分道扬镳后的首份财报,加上近期AIGC热度扩散至游戏、教育等各个领域,网易第四季度业绩及其对于GPT等热门技术的探索受到市场关注。 根据财报,第四季度,网易营…...

NPC内网穿透教程-入门
安装 安装包安装 releases下载 下载对应的系统版本即可,服务端和客户端是单独的 源码安装 安装源码 go get -u ehang.io/nps 编译 服务端go build cmd/nps/nps.go 客户端go build cmd/npc/npc.go docker安装 server安装说明 client安装说明 启动 服务端 下…...

【Linux修炼】14.磁盘结构/文件系统/软硬链接/动静态库
每一个不曾起舞的日子,都是对生命的辜负。 磁盘结构/文件系统/软硬链接/动静态库前言一.磁盘结构1.1 磁盘的物理结构1.2 磁盘的存储结构1.3 磁盘的逻辑结构二.理解文件系统2.1 对IO单位的优化2.2 磁盘分区与分组2.3 分组的管理方法2.4 文件操作三.软硬链接3.1理解硬…...

Spring源码分析:创建 BeanDefinition 流程
一、前期准备1.1 环境依赖<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.1.7.RELEASE</version></dependency><dependency><groupId&…...

Linux 练习一(思维导图 + 练习过程)
文章目录一、Linux 用户管理及文件操作第一段练习记录:主要对用户进行删除添加设置密码等操作第二段练习记录:主要包括权限设置和查找命令第三段练习记录:关于文件的命令练习第四段练习记录:查找命令及查看内存命令的使用二、Linu…...

高德地图基础教程超详细版
在当前社会,对于地图的使用是很必须的,所以对于程序员来说也是需要掌握的技能,目前主流的又百度地图和高德地图,但是我建议使用高德地图,因为百度地图的API着实不好用吖,不好理解,对于开发人员来…...

基于A7核开发板的串口实现控制LED亮灭
1.通过操作Cortex-A7核,串口输入相应的命令,控制LED灯进行工作 1>例如在串口输入led1on,开饭led1灯点亮 2>例如在串口输入led1off,开饭led1灯熄灭 3>例如在串口输入led2on,开饭led2灯点亮 4>例如在串口输入led2off,开饭led2灯熄灭 5>例如…...

HyperGBM用Adversarial Validation解决数据漂移问题
本文作者:杨健,九章云极 DataCanvas 主任架构师 数据漂移问题近年在机器学习领域来越来越得到关注,成为机器学习模型在实际投产中面对的一个主要挑战。当数据的分布随着时间推移逐渐发生变化,需要预测的数据和用于训练的数据分布…...