Bundler|Bundler 源码编写(模块分析)

手动编写一个很简单的类似于webpack这样的一个bundler(打包工具),在编写这个bunler的过程中,我们可以更清楚的了解到类似于webpack这样的工具,它的底层原理
步骤如下:
  1. 创建文件夹以及相关文件,这几个文件作用就是为了输入一句话 say hello,只不过里边涉及到了几个模块之间的相互调用

    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
  2. 我们如果想直接把src目录下的代码运行在浏览器上,肯定是不可以的,因为浏览器根本就不认识 import这样的语法,所以我们需要一个打包工具,帮我们去做项目的打包。从而生成可以在浏览器上运行的代码。
  3. 我们现在创建一个bundler.js文件。打包工具,肯定使用NodeJS来编写的。所以开发环境中,一定要安装了NodeJS,我这里已经是安装好了的。我们要对一个项目做打包,第一步就是要读取项目的入口文件,然后去分析入口文件的代码

    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
  4. 上边我们写的bundler.js这个打包工具的作用,仅仅是打印一下入口文件的内容,现在我们通过node来运行我们编写的打包工具,可以明显的看到,index.js的内容被打印了,证明我们编写的打包工具是可以用的。

    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
  5. 【Bundler|Bundler 源码编写(模块分析)】但是我们在终端打印出的内容颜色不太明显,我们怎么解决这个问题呢?我们需要安装一个高亮显示代码的工具
npm install cli-highlight -g

Bundler|Bundler 源码编写(模块分析)
文章图片
image.png
  1. 安装好以后,我们再运行 bundler.js,通过后边跟管道符,管道符后边跟 highlight,然后打印出的内容,就会有一个颜色标识,更加明显

    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
  2. 现在我们已经拿到了入口文件的代码内容了,接下来第二步,我们要拿到这个模块它的依赖。在我们的例子中,index.js的依赖是message.js这个文件,我们把message.js从代码里提取出来,怎么提取呢? 总不能用字符串截取吧,如果它引入了很多模块,那么这种方式肯定是不行的,太麻烦。我们需要一个更加方便准确的方式,引入一个第三方模块,这个模块跟Babel相关,Babel可以帮我们去分析我们语法,这个模块叫做@babel/parser
    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
npm install @babel/parser --save

  1. 安装好以后,我们就可以使用了

    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
  2. 我们写好以后,看下打印出了什么内容,运行node bundler.js | highlight,可以看到,打印出了一个JS对象,其实这个对象就是抽象语法树(AST),这个对象可以很好的表述当前的这段代码,

    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
  3. 我们打印一下抽象语法树里的body,我们可以看到,这两个东西就是program里的内容,对应的节点。

    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
  4. 第一个节点是一个引入的声明

    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
  5. 第二个节点是一个表达式语句

    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
  6. 所以,我们调用@babel/parser的parse方法,就可以帮我们分析出抽象语法树,通过抽象语法树,就可以找到一些声明的语句,而声明的语句里,放的就是入口的文件里对应的一些依赖关系,假设我们的源码里,再加一个引入。然后再重新执行node bundler.js | highlight

    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
  7. 我们可以清楚的看到,第一个和第二个节点,都是引入声明, 第三个还是表达式语句,这个抽象语法树,可很好的把我们的JS代码转化成了一个JS对象,我们现在要做的事,拿到这个代码里,所有的依赖关系,我们可以去遍历body,找到body里 type等于ImportDeclaration这样的一些内容,但是自己写遍历的话,还是有点麻烦。Babel还提供给我们了一个模块,可以帮我们快速的找到import的节点,所以我们要安装这样的一个模块@babel/traverse
npm install @babel/traverse --save

  1. 安装好后,我们来使用这个模块

    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
  2. 运行命令,分析出我们有两个ImportDeclaration,对应我们源码里两个import语句
    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
  3. 我们可以看到,通过node的source下边的value可以取到我们想要的值

    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
  4. 然后我们把取到的值,存入一个数组,并且打印

    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
  5. 然后再运行命令 ,我们看到了入口文件的依赖文件都有哪些了

    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
  6. 这个时候我们发现,我们获取到的依赖文件,都是相对路径,而且是相对于入口文件index.js的路径,但是真正在做代码打包的时候,我们需要这些依赖文件,不能是相对路径,必须是一个绝对路径,或者说,即便是相对路径,也是相对于bundler这个根路径才可以,这样打包才不会有问题。所以我们的dependencies数组里存的不可以是相对于入口文件的相对路径,而要是绝对路径或者相对于bundler这个根路径的相对路径,要怎么实现呢?
21.引入node的另外一个核心模块path,该模块提供了一些用于处理文件路径的小工具,dirname方法返回路径中代表文件夹的部分

Bundler|Bundler 源码编写(模块分析)
文章图片
image.png
Bundler|Bundler 源码编写(模块分析)
文章图片
image.png
22.然后我们再调用 join方法拼接一下路径,然后再打印,就打印出绝对路径了
Bundler|Bundler 源码编写(模块分析)
文章图片
image.png
Bundler|Bundler 源码编写(模块分析)
文章图片
image.png
  1. 如果我们的dependencies 只存一个./src/message.js这个的路径的花,后边打包还是会比较麻烦,如果我们把相对路径和绝对路径都存进去。那么我们怎么操作呢?
    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
Bundler|Bundler 源码编写(模块分析)
文章图片
image.png
  1. { './message.js': './src/message.js' } 这里的意思是,我知道你原始引入的是message.js,但是实际上真正对应的项目中的文件是根路径下的src目录下的message.js
  2. 如果我们有多个依赖呢? 再次运行命令,可以清楚的看到都有哪些依赖

    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
    26.然后我们就可以把这些已知的都return出去

    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
  3. 当然,我们单纯把入口文件和依赖return出去是没于意义的,因为我们的入口文件 是ESModule形式的,浏览器是无法运行的,我们还需要借助babel对语法进行转化,转换成浏览器可以运行的代码。此时我们需要安装一个模块@babel/core,这是一个babel的核心模块
    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
  4. 安装成功后,我们需要 引入,并且调用transformFromAst方法,这个方法可以把AST抽象语法树,转化成浏览器可以运行的代码
    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
  5. 然后安装@babel/preset-env模块,把这个模块配置到presets
npm install @babel/core --save

Bundler|Bundler 源码编写(模块分析)
文章图片
image.png
  1. 然后再运行命令,可以看到,代码已经不是入口文件index.js里的代码了,而是编译过之后可以在浏览器上运行的代码

    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
31.然后我们把翻译好的代码也返回出来

Bundler|Bundler 源码编写(模块分析)
文章图片
image.png
  1. 写到这里,我们对入口文件的代码分析就结束了,我们可以打印一下返回出的结果

    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
    Bundler|Bundler 源码编写(模块分析)
    文章图片
    image.png
  2. 然后我们可以看到,我们对入口文件进行分析后,可以清楚的知道以下几点信息:
1. 入口文件对应的是`./src/index.js`; 2. 入口文件的依赖是message.js文件,这个文件真正的路径是'./src/message.js' 3. 它被翻译过后,要在浏览器上运行的代码是code部分

通过以上代码,我们已经可以实现对一个JS模块的代码分析了

    推荐阅读