手写webpack核心原理,支持typescript的编译和循环依赖问题的解决
主要知识点 |
- babel读取代码的import语句
- 算法:bfs遍历依赖图
- 为浏览器定义一个
require
函数的polyfill - 算法:用记忆化搜索解决
require
函数的循环依赖问题
Quick Start
GitHub:https://github.com/Hans774882968/mini-webpack
npm install
npm run bundle
修改index.html依赖的js文件路径(bundle_ts.js),复制到dist文件夹,然后点击打开index.html。
依赖
npm i @babel/parser
npm i @babel/traverse
npm i -D @types/babel__traverse
npm i @babel/core @babel/preset-env
npm i -D @babel/preset-typescript
npm i -D @types/babel__core
npm i -D eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser
npm i typescript@4.7.4
注意点:
- 配置
eslint
后记得重启一下vscode,IDE提示才会生效。 - 我们的命令在2022-08-27下载了
@babel/core7.18.13
,对应的ts版本要指定为typescript@4.7.4
,否则运行代码会报错。
引言
主要是借鉴参考链接1来实现一个mini-webpack,但在功能上有所超越:
- 根据入口文件的拓展名,决定用ts或js来编译。
- 借鉴参考链接3,用“记忆化搜索”解决循环依赖问题。
最大的缺憾是不清楚ts-loader
怎么实现,因此这里编译ts的做法是直接判定入口文件的扩展名为.ts
,然后用babel实现。
因为参考链接1写得很清晰了,本文仅定位为一个额外补充,不会写得很详细。
初始化项目
npm init
tsc --init
tsconfig.json
主要需要设置:
"compilerOptions": {"module": "commonjs",
},
"include": ["bundle.ts"
]
样就能用tsc
命令编译入口文件了。
接下来给package.json
加一个命令:"bundle": "tsc && node bundle.js"
,以后可以直接npm run bundle
模拟打包命令了。
目前除了在nodejs代码里用'@babel/preset-typescript'
插件以外,不知道怎么快速方便地编译src
文件夹下的ts,只好:先手工修改tsconfig.json
的include
和compilerOptions.module
,接着tsc
编译,最后还原tsconfig.json
。
读取单个文件
getModuleInfo
函数主要分析文件的依赖和完成代码转换。
- 我们需要分析文件的
import
语句,把依赖的文件(相对路径)转换为相对于项目根目录的路径(下称“绝对路径”)。使用babel相关的库@babel/parser
、@babel/traverse
和@babel/core
完成。 - 代码转换用
@babel/core
的transformFromAst
方法完成。 - 我们需要保证生成的js的模块规范是
commonjs
。对于编译js的情况不需要特别指明,而编译ts的情况需要指明插件:plugins: ['@babel/plugin-transform-modules-commonjs']
(参考链接5)。
如何支持typescript的编译
只需要修改@babel/parser
的parse
方法和@babel/core
的transformFromAst
方法的调用方式。
需要用到@babel/preset-typescript
这个插件。
@babel/preset-typescript
没有@babel/preset-env
方便,
需要指明filename
属性和@babel/plugin-transform-modules-commonjs
插件。
相关语句:
const ast = parser.parse(body, {sourceType: 'module',plugins: getType() === 'ts' ? ['decorators-legacy', 'typescript'] : []
});babel.transformFromAst(ast, undefined,getType() === 'ts' ?{presets: ['@babel/preset-typescript'],filename: file,plugins: ['@babel/plugin-transform-modules-commonjs']} :{ presets: ['@babel/preset-env'] },(err, result) => {if (err) reject(err);resolve(result as babel.BabelFileResult);}
);
遍历依赖图
parseModule
函数。因为循环依赖也只不过是形成递归,所以依赖图不局限于DAG,可以是任意有向图。所以只需要用bfs遍历一下(这里更正参考链接1的一处小错误,遍历算法不是递归而是bfs)。
parseModule
函数中的for循环for (const { deps } of a)
用到了它会继续遍历新加入的元素的特性,不能替换为forEach
,是js实现bfs的最简方案。parseModule
函数中的await Promise.all
是循环中使用async/await
的解决方案(参考链接4)。
parseModule
函数的输出为depGraph
哈希表,其一个对象的deps
属性应该设计为一个哈希表,而非直接设计为数组,下文会解释原因。
生成单个文件
getBundle
函数把上面生成的depGraph
哈希表扔进代码模板里,这就是打包结果。
为了在浏览器环境给出一个合法的commonjs
的polyfill(这里只需要给出require
和exports
对象),我们在代码模板中定义了自己的require
函数。对于一个代码文件来说,其返回值为这个文件的exports
对象,其副作用为把整个文件的代码执行了一遍。
`;(function (graph) {var exportsInfo = {};function require (file) {if (exportsInfo[file]) return exportsInfo[file];function absRequire (relPath) {return require(graph[file].deps[relPath]);}var exports = {};exportsInfo[file] = exports;(function (require, exports, code) {eval(code);})(absRequire, exports, graph[file].code);return exports;}require('${entryPath}');
})(${depGraph});`
值得注意的是,这个require
函数实际上是一个递归函数。在eval(code)
时可能产生递归。
depGraph
哈希表的一个对象的deps
属性为什么设计为一个哈希表,而非直接设计为数组?因为待执行的代码中所有的路径都是相对路径,我们需要用graph[file].deps[relPath]
这样的方式把它转换为绝对路径。为了完成这个转换,我们还需要设计absRequire
函数,它只不过起到一个拦截器的作用。
如何解决循环依赖
此时我们如果打包一个含有循环依赖的入口文件,运行时会栈溢出。以最简单的情况为例:a
模块的a
函数引用b
模块的b
函数,b
模块的b
函数引用a
模块的a
函数。
怎么解决呢?根据参考链接3,我们可以用“记忆化搜索”的思路,开一个全局变量var exportsInfo = {};
。并在exports
对象生成以后,立即exportsInfo[file] = exports;
。上文案例中,b
模块获得的a
模块的exports
对象的值是空的,但因为对象的浅拷贝特性,对象地址是正确的,在require
函数解析a
模块完毕后,b
模块也就能获得a
模块的exports
对象的正确值了。
相信对acmer来说这个算法很经典,没有相关背景的话可以尝试在浏览器打断点帮助理解。
入口
main
函数在最开始读取配置(模仿webpack.config.js
),在最后把getBundle
生成的单个文件写入文件系统。
参考链接
- 手写webpack核心原理,再也不怕面试官问我webpack原理:手写webpack核心原理,再也不怕面试官问我webpack原理 - 掘金
@babel/core
官方文档:@babel/core- webpack如何解决循环依赖:webpack如何解决循环依赖 - 知乎
- 循环中使用
async/await
的解决方案:五种在循环中使用 async/await 的方法 - 知乎 - @babel/plugin-transform-modules-commonjs · Babel
相关文章:
手写webpack核心原理,支持typescript的编译和循环依赖问题的解决
主要知识点 babel读取代码的import语句算法:bfs遍历依赖图为浏览器定义一个require函数的polyfill算法:用记忆化搜索解决require函数的循环依赖问题 Quick Start GitHub:https://github.com/Hans774882968/mini-webpack npm install npm…...
开箱即用之MyBatisPlus XML 自定义分页
调用方法 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;public Page<User> queryListByPage(User user) { Page<User> page new Page<>(1, 12); return userMapper.queryListByPage(page, user); } mapper接口 import co…...

GPT应用开发:运行你的第一个聊天程序
本系列文章介绍基于OpenAI GPT API开发应用的方法,适合从零开始,也适合查缺补漏。 本文首先介绍基于聊天API编程的方法。 环境搭建 很多机器学习框架和类库都是使用Python编写的,OpenAI提供的很多例子也是Python编写的,所以为了…...

力扣刷MySQL-第一弹(详细解析)
🎉欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克🍹 ✨博客主页:小小恶斯法克的博客 🎈该系列文章专栏:力扣刷题讲解-MySQL 🍹文章作者技术和水平很有限,如果文中出…...

Xcode 15 for Mac:超越开发的全新起点
作为一名开发人员,你是否正在寻找一款强大而高效的开发工具,来帮助你在Mac上构建出卓越的应用程序?那么,Xcode 15就是你一直在寻找的答案。 Xcode 15是苹果公司最新推出的一款集成开发环境(IDE)࿰…...
2021腾讯、华为前端面试题集(基础篇)
Vue 面试题 生命周期函数面试题 1.什么是 vue 生命周期2.vue 生命周期的作用是什么 3.第一次页面加载会触发哪几个钩子 4.简述每个周期具体适合哪些场景 5.created 和 mounted 的区别 6.vue 获取数据在哪个周期函数 7.请详细说下你对 vue 生命周期的理解? **vue 路由…...

怎么修改或移除WordPress后台仪表盘概览底部的版权信息和主题信息?
前面跟大家分享『WordPress怎么把后台左上角的logo和评论图标移除?』和『WordPress后台底部版权信息“感谢使用 WordPress 进行创作”和版本号怎么修改或删除?』,其实在WordPress后台仪表盘的“概览”底部还有一个WordPress版权信息和所使用的…...

计算机三级(网络技术)——应用题
第一题 61.输出端口S0 (直接连接) RG的输出端口S0与RE的S1接口直接相连构成一个互联网段 对172.0.147.194和172.0.147.193 进行聚合 前三段相同,将第四段分别转换成二进制 11000001 11000010 前6位相同,加上前面三段 共30…...

Node.js基础知识点(四)
本节介绍一下最简单的http服务 一.http 可以使用Node 非常轻松的构建一个web服务器,在 Node 中专门提供了一个核心模块:http http 这个模块的就可以帮你创建编写服务器。 1. 加载 http 核心模块 var http require(http) 2. 使用 http.createServe…...

持久双向通信网络协议-WebSocket-入门案例实现demo
1 介绍 WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接, 并进行双向数据传输。 HTTP协议和WebSocket协议对比: HTTP是短连接࿰…...

LV.13 D10 Linux内核移植 学习笔记
具体实验步骤在lv13day10 实验十 一、Linux内核概述 1.1 内核与操作系统 内核 内核是一个操作系统的核心,提供了操作系统最基本的功能,是操作系统工作的基础,决定着整个系统的性能和稳定性 操作系统 操作系统是在内核的基础上添…...
STM32面试体验和题目
目录 一、说一下你之前的工作主要干了什么? 二、stm32有关的知识点 1.stm32的外设有哪一些 2.你的毕业论文的项目里面是怎么设计的 三,C语言的考察 1.写一个结构体(结构体的内容自由发挥) 2.写一个指针型的变量 3.结构体是…...

微软.NET、.NET Framework和.NET Core联系和区别
我是荔园微风,作为一名在IT界整整25年的老兵,看到不少初学者在学习编程语言的过程中如此的痛苦,我决定做点什么,我小时候喜欢看小人书(连环画),在那个没有电视、没有手机的年代,这是…...

Shell脚本同时调用#!/bin/bash和#!/usr/bin/expect
如果你想在一个脚本中同时使用bash和expect,你可以将expect部分嵌入到bash脚本中。以下是一个示例: #!/bin/bash# 设置MySQL服务器地址、端口、用户名和密码 MYSQL_HOST"localhost" MYSQL_PORT"3306" MYSQL_USER"your_usernam…...

C++ Webserver从零开始:基础知识(一)——Linux网络编程基础API
目录 前言 一.socket地址API 1.主机字节序和网络字节序 2.通用socket地址 3.专用socket地址 二.创建socket 三.绑定socket(命名socket) 四.监听socket 五.接受连接(服务端) 六.发起连接(客户端) 七.关闭连接…...

cookie和session的工作过程和作用:弥补http无状态的不足
cookie是客户端浏览器保存服务端数据的一种机制。当通过浏览器去访问服务端时,服务端可以把状态数据以key-value的形式写入到cookie中,存储到浏览器。浏览器下次去服务服务端时,就可以把这些状态数据携带给服务器端,服务器端可以根…...

【蓝桥杯选拔赛真题30】C++字母转换 第十三届蓝桥杯青少年创意编程大赛C++编程选拔赛真题解析
目录 C/C++字母转换 一、题目要求 1、编程实现 2、输入输出...
资产负债表#通俗易懂
资产负债表(the Balance Sheet)亦称财务状况表,表示企业在一定日期(通常为各会计期末)的财务状况(即资产、负债和业主权益的状况)的主要会计报表。 (99 封私信 / 11 条消息) 能通俗易懂的给小白…...
PCF8563转STM32 RTC避坑指南
问题一,时间读取错误 原因,读写时间必须Time在前,Date在后 HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BCD); HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BCD); HAL_RTC_SetTime(&hrtc, &time, RTC_FORMAT_BCD); …...

前端重置密码报错记录
昨天晚上,我写了重置密码的前端,测试的时候报错 今天上午,我继续试图解决这个问题,我仔细检查了一遍,前端没有问题 可以正常接收输入的数据并且提交 但是后端接收到的数据为空,后端接口也没有问题 但后端收…...

C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件
在选煤厂、化工厂、钢铁厂等过程生产型企业,其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进,需提前预防假检、错检、漏检,推动智慧生产运维系统数据的流动和现场赋能应用。同时,…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...

ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...