媒介
自動化打包東西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
(完)