You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
constmodules={"foo.js": function(require,module,exports){exports.foo=function(){console.log("This is Foo");}},"index.js": function(require,module,exports){const{foo}=require("./foo.js")}}
但是这样就完美了吗?
这样的情况下可能会依赖冲突
请注意,这里使用的都是相对路径
我们可以设想一下,如a目录有 index.js,foo.js; b 目录也有 index.js,foo.js,他们都是一样的 index.js 引用 foo.js
mini-webpack
这是一个迷你的打包工具,是为了用少量的代码理解打包等工具的核心原理
想直接看源码可以看这个路径
mini-webpack: https://github.com/mekefly/mini-wabpack
webpack 是什么?
本质上,
webpack
是一个用于现代JavaScript
应用程序的 静态模块打包工具。当webpack
处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。为什么要做这个呢?
本质上,这样的事只是为了更加理解
webpack
的工作方式,更加理解node
的工作原理,为以后的工作扑平道路我们要做到的几个任物点
那么这就开始吧
我们将会使用小步骤的开发思想完成这个项目
我们如果想要完成这个项目需要从小到大的解决若干问题
获取文件内容
这个就不多说了我们只需要获取 fs 包然后再请求对应的文件内容就可以了
要注意的是,我们需要传入
encoding
来设置转换的文件格式,防止乱码生成抽象语法树
抽象语法树是一个非常庞大的概念,将文本解析成机器可阅读的一种操作,本项目暂时不手写它了,可能会在其他项目手写 mini 语法树欢迎继续关注
获取抽象语法树时有别人已经写好的项目
@babel/parser
,我们可以直接使用这个项目来完成语法树的生成我们可以使用它导出的
parse
方法来导出语法树例如
但是,当我们使用
import {foo} from 'foo'
类似的语法时会报一个错误错误写的很明白,只有
sourceType
为module
时才能使用import
我们给它加上就好了通过抽象语法树来生成依赖信息
获取依赖信息可以通过遍历语法树上的节点来找到对应的
import
节点如果要遍历 ast 语法树我们可以使用 babel 为我们提供的工具 @babel/traverse
我们可以下载它
我们可以使用一些小工具来查看语法树 https://astexplorer.net/
我们看到上面有个
ImportDeclaration
对的这个节点就是我们需要的包含import
的语法树节点我们可以通过下面的方式来获取这个节点
如果需要获取其他的节点也是一样的道理
我们可以把代码完善一下
对代码进行 import 转 require
想要将
import
转为require
可以使用babel-core
来完成使用也非常简单,我们直接使用它提供的
transform
函数来完成转换提取依赖信息
我们将上面提取到的有用信息进行导出合并就可以里,为下一步做准备
为什么需要mapping见下面
那么如何找到目标函数呢?
制作依赖图
要制作依赖图我们需要解决以下几个问题
这种情况要怎么阻止呢
我们可以提供一个已完成加载的列表
const set = []
set.add("foo.js")
set.add("bar.js")
如果 在寻找依赖时发现 bar.js 已经做过处理了,这时候我们直接停止不在处理当前依赖,转而处理下一个依赖
这里我使用的是广度优先搜索
例如
i => [a,b]
a => [c,d]
b => [e,f]
那么我们便历 i 添加到图表中这时
graph:[a,b]
然后便历数组继续平铺
i: 0
索引为 0 然后检查 a 的依赖便利添加到图表中
[a,b,c,d]
i:1
索引为 1 然后检查 b 的依赖便利添加到图表中
[a,b,c,d,e,f]
....
这样运行下去,所有依赖都将平铺
解决完这些问题后,
这时候我们就得到了下面代码
模板的包装方案
我想到的方案是
require
的方案例如
当然不可以,代码中是会有可能有重复的变量的
我们要做的是让下面代码正常运行在同一个文件里
但是如果拥有同样的变量,它们将相互影响
所以我们需要使用函数包裹起来
然后为它提供一些必要的参数
这样就可以同时支持像下面这种写法了,这个就是 cjs 模块规范
那就要看下面了
require 的手动实现
我们先回想一下require的作用
那么如何找到目标函数呢?
我们可以用 mapping
我们设想一下 一个 map 的 key 是路径,然后值是对应的函数,不就可以通过 path 获取到 模块 并执行了吗
这样的情况下可能会依赖冲突
请注意,这里使用的都是相对路径
我们可以设想一下,如a目录有 index.js,foo.js; b 目录也有 index.js,foo.js,他们都是一样的 index.js 引用 foo.js
这时候跟目录的index.js 同时引用/a/index.js和/b/index.js
依赖扁平后就会同时有两个foo.js
/index.js, /a/index.js ,/b/index.js ,foo.js ,foo.js
我们就可以使用局部映射这就是上文提到模块 id 的作用了,唯一指定对应的模块
当执行 require 时我们就去自己的映射表中找到平铺的模块 id,
然后通过唯一id去寻找对应的模块
我们可以使用缓存把模块运行产生的结果给放到缓存数组里,如果再次执行, 从数组里拿值
这时候我们得到了完整代码
合成结果
通过模板生成代码
写入文件
好了终于完成了,如果需要详细信息的话,以看原码仓库,仓库地址应该在文件的最上方,喜欢的话可以加个star一起讨论哦!
The text was updated successfully, but these errors were encountered: