资讯专栏INFORMATION COLUMN

webpack源码分析之一:文件打包

Richard_Gao / 2056人阅读

摘要:当发现该路径为文件夹时则,则依次查找如下文件字段扩展名文件解析文件可以定位之后,则是解析定位下来的文件了,本文用的是,文档如规范文档解析文件返回一个语法树。对语法树进行遍历对遇到为且其为为的节点将该节点的以及下标包装成对象储存起来。

前言

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

功能分析

对于node端来讲,有commonjs来规范模块的标识,定义,引用。而浏览器端由于缺乏原生对象支持就需要通过自我实现来模拟commonjs规范。
webpack是通过一个IIFE立即调用函数表达式去实现这个规范的。简要的去注释,去除内部运行的代码,其格式如下:

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

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

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

文件路径分析与定位resolve

文件编译&解析,分析出依赖文件parse

生成需要打包的文件树depTree

将依赖文件写入输出文件内writeChunk

文件分析与定位

本功能和node的require类似,故有参考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,文档如parser规范文档

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");
const c = require("c");
const {e, f, g} = require("./m");

 
      },
     "/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";

module.exports = b;
" 
      },
     "/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";

module.exports = c;
" 
      },
     "/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");
const a = 1;


      },
  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实现simple-webpack

(完)

参考资料

webpack早期源码

require源码解读

node require源码

parser规范文档

简要分析webpack打包后代码

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/107753.html

相关文章

  • 模仿webpack实现简单的打包工具

    摘要:模仿实现简单的打包工具是一款前端项目构建工具,随着现在前端生态的发展,已经成为前端开发人员必备的技能之一,很多开发人员开始使用和的时候,都会使用默认的单页应该创建指令来创建一个工程化项目,实际上,这些工程化的项目都是基于来搭建的当我们熟悉使 模仿webpack实现简单的打包工具 webpack是一款前端项目构建工具,随着现在前端生态的发展,webpack已经成为前端开发人员必备的技能之...

    wow_worktile 评论0 收藏0
  • 模仿webpack实现简单的打包工具

    摘要:模仿实现简单的打包工具是一款前端项目构建工具,随着现在前端生态的发展,已经成为前端开发人员必备的技能之一,很多开发人员开始使用和的时候,都会使用默认的单页应该创建指令来创建一个工程化项目,实际上,这些工程化的项目都是基于来搭建的当我们熟悉使 模仿webpack实现简单的打包工具 webpack是一款前端项目构建工具,随着现在前端生态的发展,webpack已经成为前端开发人员必备的技能之...

    linkFly 评论0 收藏0
  • 模仿webpack实现简单的打包工具

    摘要:模仿实现简单的打包工具是一款前端项目构建工具,随着现在前端生态的发展,已经成为前端开发人员必备的技能之一,很多开发人员开始使用和的时候,都会使用默认的单页应该创建指令来创建一个工程化项目,实际上,这些工程化的项目都是基于来搭建的当我们熟悉使 模仿webpack实现简单的打包工具 webpack是一款前端项目构建工具,随着现在前端生态的发展,webpack已经成为前端开发人员必备的技能之...

    Simon 评论0 收藏0
  • webpack4.x 模块化浅析-CommonJS

    摘要:先看下官方文档中对模块的描述在模块化编程中,开发者将程序分解成离散功能块,并称之为模块。每个模块具有比完整程序更小的接触面,使得校验调试测试轻而易举。 先看下webpack官方文档中对模块的描述: 在模块化编程中,开发者将程序分解成离散功能块(discrete chunks of functionality),并称之为模块。每个模块具有比完整程序更小的接触面,使得校验、调试、测试轻而易...

    alphahans 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<