webpack源碼剖析之一:文件打包

媒介

自動化打包東西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\');\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完成simple-webpack

(完)

參考資料

  1. webpack初期源碼
  2. require源碼解讀
  3. node require源碼
  4. parser範例文檔
  5. 扼要剖析webpack打包後代碼
    原文作者:朱建
    原文地址: https://segmentfault.com/a/1190000014710953
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞