自動化打包工具webpack,相信不少人和我同樣嘗試着研究下它,可是繁雜的功能以及高度抽象的代碼實在是很難理解,因此筆者只能經過github的webpack的第一次提交進行分析,實現,並將實現的一些心得分享一下。html
對於node端來說,有commonjs來規範模塊的標識,定義,引用。而瀏覽器端因爲缺少原生對象支持就須要經過自我實現來模擬commonjs規範。
webpack是經過一個IIFE當即調用函數表達式去實現這個規範的。簡要的去註釋,去除內部運行的代碼,其格式以下:node
(function(module){})([function(){},function(){}]);
簡單點說就是各個模塊代碼以數組的形式傳遞給運行函數,在進行存儲。詳細分析能夠參考簡要分析webpack打包後代碼webpack
因此實現以上的功能需求點以下:git
本功能和node的require相似,故有參考node require源碼github
文件分析,將文件爲兩種類型web
以 "./","../","/" 標識符開頭的路徑文件模塊json
自定義的文件模塊segmentfault
[ '/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' ]
文件定位數組
{extensions:['js','jsx','jpg']}
當發現該路徑爲文件夾時則,則依次查找以下文件瀏覽器
文件能夠定位以後,則是解析定位下來的文件了,本文用的是exprima,文檔如parser規範文檔
好比
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)
這個地方要考慮的點是
官方實現
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
(完)