博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
webpack源码分析之一:文件打包
阅读量:6174 次
发布时间:2019-06-21

本文共 4138 字,大约阅读时间需要 13 分钟。

前言

自动化打包工具webpack,相信很多人和我一样尝试着研究下它,但是繁杂的功能以及高度抽象的代码实在是很难理解,所以笔者只能通过github的进行分析,实现,并将实现的一些心得分享一下。

功能分析

对于node端来讲,有commonjs来规范模块的标识,定义,引用。而浏览器端由于缺乏原生对象支持就需要通过自我实现来模拟commonjs规范。

webpack是通过一个IIFE立即调用函数表达式去实现这个规范的。简要的去注释,去除内部运行的代码,其格式如下:

(function(module){})([function(){},function(){}]);

简单点说就是各个模块代码以数组的形式传递给运行函数,在进行存储。详细分析可以参考

所以实现以上的功能需求点如下:

  • 文件路径分析与定位resolve
  • 文件编译&解析,分析出依赖文件parse
  • 生成需要打包的文件树depTree
  • 将依赖文件写入输出文件内writeChunk

文件分析与定位

本功能和node的require类似,故有参考

文件分析,将文件为两种类型

  • 以 "./","../","/" 标识符开头的路径文件模块

    • 该类文件会通过path.join 转化为真实的路径而定位。
  • 自定义的文件模块

    • 这类相对比较麻烦,他在当前目录下面的node_modules,查找文件,未找到则一路向上查找,最终查找到或者抛出异常。如:
[ '/Users/zhujian/Documents/workspace/webpack/simple-webpack/node_modules',  '/Users/zhujian/Documents/workspace/webpack/node_modules',  '/Users/zhujian/Documents/workspace/node_modules',  '/Users/zhujian/Documents/node_modules',  '/Users/zhujian/node_modules',  '/Users/node_modules',  '/node_modules' ]

文件定位

  • 对于部分文件并没有带扩展名,此时有默认的扩展名依次以.js,.jsx为后缀依次补充。当然我们可以用传入extensions,修改默认的扩展名。
{extensions:['js','jsx','jpg']}
  • 当发现该路径为文件夹时则,则依次查找如下文件

    • package.json(main字段)
    • index+(扩展名)

文件解析

  • 文件可以定位之后,则是解析定位下来的文件了,本文用的是exprima,文档如

    • esprima解析文件,返回一个语法树。
    • 对语法树进行遍历,对遇到type 为CallExpression,且其callee为name为require的节点,将该节点的value,以及下标包装成对象储存起来。

比如

const b = require('./b');

解析后

...."init": {        "type": "CallExpression",        "callee": {            "type": "Identifier",            "name": "require",            "range": [                10,                17            ]        },        "arguments": [            {                "type": "Literal",                "value": "./b",                "raw": "'./b'",                "range": [                    18,                    23                ]            }        ],        "range": [            10,            24        ]    }, ....

我们要做的就是提取value "./b",以及该字符串在文件所处的位置range。

文件树生成

主要是从入口文件开始,将所有依赖的js,以及其内容,分配的id组成一个可操作的扁平化的对象和存储着name与id对应的map对象。

实现手法上也是递归resolve函数,获取到各个文件的依赖,文件,id的信息,最后得到depTree对象

举个例子:

{ modules:    { '/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/a.js':       { filename: '/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/a.js',        id: 0,        requires: [Array],        rangeRequires: [Array],        source: 'const b = require(\'./b\');\nconst c = require(\'c\');\nconst {e, f, g} = require(\'./m\');\n\n       },     '/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/b.js':       { filename: '/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/b.js',        id: 1,        requires: [],        rangeRequires: [],        source: 'const b = \'b\';\n\nmodule.exports = b;\n'       },     '/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/node_modules/c.js':       { filename: '/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/node_modules/c.js',        id: 2,        requires: [],        rangeRequires: [],        source: 'const c = \'c\';\n\nmodule.exports = c;\n'       },     '/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/m.js':       { filename: '/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/m.js',        id: 3,        requires: [],        rangeRequires: [],        source: '// const core = require(\'./core\');\nconst a = 1;\n\n      },  nextModuleId: 4,  mapNameToId:    { '/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/a.js': 0,     '/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/b.js': 1,     '/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/node_modules/c.js': 2,     '/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/m.js': 3      }  }

文件写入

写入主函数,替换入口的执行函数。这块会用到之前的path和id关系的map对象,通过路口文件的绝对路径,找出入口文件的mainId,并进行替换。

写入参数数组。遍历文件树,将文件节点的source内容替换掉

大致如下:

require('module') 替换为__webpack_require__(0)
  • 这个地方要考虑的点是

    • 如果用replace替换的话,会影响source带部分关键字的内容,不可取。
    • 用索引替换的字符串的话,一旦第一个替换成功,整个字符串长度发生变化,原先的索引下标就失效了。

官方实现

const result = [source];    replaces.forEach(replace => {        const {from, value, end} = replace;        const source = result.shift();        result.unshift(source.substr(0, from), value, source.substr(end))    });

代码实现

本人的简易版webpack实现

(完)

参考资料

转载地址:http://gaqba.baihongyu.com/

你可能感兴趣的文章
Android ImageLoader使用
查看>>
LDTP
查看>>
StringUtils工具类的常用方法
查看>>
linux下VNC安装与配置
查看>>
URL编码
查看>>
光模块及光纤知识(含分类,常用类型介绍)
查看>>
Apache 单IP多端口设置
查看>>
安装系统前的准备---vmware
查看>>
Tiny并行计算框架之使用介绍
查看>>
Linux od命令
查看>>
一个不错的MySQL集群管理工具
查看>>
mysql-proxy 按表分发查询的lua脚本
查看>>
在wordpress主题下面添加二级菜单
查看>>
CentOS 下JDK安装
查看>>
Nginx + Django
查看>>
我的友情链接
查看>>
用shell脚本编写进度条
查看>>
使用Live555类库实现的网络直播系统
查看>>
IO与NIO
查看>>
go_wed编程笔记
查看>>