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
constgenerateCode=entry=>{constgraph=makeDependenciesGraph(entry);constfinalCode='';// TODO 把graph里所有的code合并起来,生成最后的codereturnfinalCode;};constcode=generateCode('./src/index.js');console.log('code',code);
Ready
先看4段代码
以上代码其实很简单,文件结构如图,最后的输出是
hell LGC
上一篇我们已经讲过怎么根据入口文件生成项目的JS依赖图,完整代码如下
【webpack进阶系列】简易Bundler -> 生成依赖图
先跑一发
node bundler.js
,已经能成功得到依赖图现在就差最后一步了: 让代码跑起来
下面我们要做的事情是把这几个文件的code打包成一段能在浏览器成功运行的代码
Go
先改造一下入口代码
接下来是实现
generateCode
方法,返回一段代码字符串首先浏览器里的代码都要通过闭包来执行,避免污染全局环境
把graph当做参数传入这个闭包函数
这里有个小坑,通过模板字符串直接传入
${graph}
得到的是[object object]所以需要
JSON.stringify(graph)
再跑一下,OK,正确传入了
接下来看一下我们要转换什么东西,把graph再打印出来看一眼
把
index.js
和message.js
这两段代码复制出来看一下,把字符串和换行符都去掉,得到的是下面的代码先分析一下这两段代码做了啥
在
index.js
里自定义了一个_interopRequireDefault
方法并立即执行,参数是require(模块路径)
,然后返回编译后的文件内容obj,如果__esModule
属性存在,直接返回obj,否则返回{ 'default': obj }
再看
message.js
,给exports
对象新增了一个__esModule
属性,然后还默认它有一个default
属性,最后通过exports["default"]
导出该模块的内容分析下来,这里有两个东西是陌生的,一个是
require
方法,另外一个是exports
对象(未曾定义过),这两是浏览器不认识或者说无法处理的,那么就需要我们在上面的闭包里额外的实现下面这个问题很有意思,估计问出了很多前端新手的心中疑问,本质上也是我们正在以及接下来要讨论的核心
require 和 import 怎么会变成浏览器认识的呢? - 知乎
两个目的
* 实现
require
方法* 定义
exports
对象1、实现
require
方法由于是用模板字符串写的,而且有点复杂,我们一步一步来
先定一个
require
方法,然后立即执行,并传入entry
,注意在模板字符串里面要加引号处理成字符串的形式require
里再执行一个闭包(不污染上一个闭包的作用域),用来执行依赖模块的代码,参数就是该模块的code,然后直接用eval(code)
来执行到这里,入口文件
index.js
的代码其实已经可以执行完了,然后递归到message.js
的时候它的代码也使用了
require
方法,然后参数是./name.js
,这是相对于message.js
的相对路径但是,我们的依赖图graph对象里面收集的依赖,全部是相对于
bundler.js
的路径,也就是./src/bundler-utils/name.js
,就是说只有这个key才能正确映射,依赖图graph的4个key如下我们给执行子模块代码的闭包里再增加一个
localRequire
方法,专门用于处理这种路径问题,拿到真正的路径后,再调外面闭包的require
方法,先看代码这里有点绕,我们把
require
方法拆开来,再根据依赖图来对比来看先看里面这个闭包,多加了一个
require
参数,用实参localRequire
来替代里面var _name = require("./name.js");
中有问题的require
方法实际上的
localRequire
我们在外面再定义一次,记住一点,它的功能是把相对当前模块的依赖路径转变成相对于bundler.js的路径对于
message.js
这个模块来说,上面的路径是这么走的graph[‘./bundler-utils/message.js’].dependencies[‘./name.js’]
对应的就是图中的依赖关系,那么拿到的就是
./src/bundler-utils/name.js
,就是我们真正的require
方法所需要的路径了这块用文字写起来实在是有点绕,辛苦各位能坚持看到这里的你,但相信你一定会有收获
再回顾一下,我们自定义了一个
require
方法,传入入口文件后,就会去graph寻找当前文件的code并通过eval
方法立即执行然后递归到依赖的子模块时,子模块也会使用
require
方法寻找自身的依赖的code并执行,但此require
非彼require
,由于路径不完整,所以需要自定义一个localRequire
方法来替代子模块使用的require
,把真正的完整路径传递给最外面定义的那个require
方法,拿到code后也立即执行,如此依赖,通过递归关系,整个依赖的所有code都会被执行一遍2、定义
exports
对象require
搞定后,就剩下一个exports
对象,这个比较简单,我们定义一个exports
空对象,然后把它传入eval
里,执行完后,再把exports
导出即可3、浏览器运行
然后我们运行
node bundler.js
,并把得到的结果拿到浏览器运行一下得到了
hello LGC
, bingo!总结
结合上一篇生成依赖图,我们再来回顾总结一下核心流程
fs.readFileSync
读取文件二进制字符串@babel/parser
将JS代码转为AST抽象语法树@babel/traverse
解析AST,通过ImportDeclaration
得到文件的所有依赖dependenciesbabel.transformFromAst
方法把AST代码转换成ES代码codefilename
、dependencies
、code
打包成一个对象传出去require
方法,用于根据文件路径得到模块的codeexports
对象,用于导出当前模块的内容最后的完整代码,可以直接用来运行打包JS的简易bundler
The text was updated successfully, but these errors were encountered: