1. 程式人生 > >實現一個簡易版Webpack

實現一個簡易版Webpack

## 原理 * 1、解析一個檔案及其依賴 * 2、構建一個依賴關係圖 * 3、將所有東西打包成一個單檔案 ## 程式碼實現 #### 檔案結構 #### 1、解析檔案及其依賴 通過babylon將檔案解析成AST [線上解析器](https://astexplorer.net/): ![image](https://user-images.githubusercontent.com/3964466/78567304-0e8d9580-7853-11ea-98d2-c7ccdd7153ac.png) 程式碼實現: bundle.js ``` const fs = require("fs"); const babylon = require("babylon"); const traverse = require("babel-traverse").default; let ID = 0; function createAsset(filename) { const content = fs.readFileSync(filename, "utf-8"); // 解析檔案成AST const ast = babylon.parse(content, { sourceType: "module", }); const dependencies = []; // 根據AST獲取相關依賴 traverse(ast, { ImportDeclaration: ({ node }) => { dependencies.push(node.source.value); }, }); const id = ID++; return { id, filename, dependencies, }; } const mainAssets = createAsset("./example/entry.js"); console.log(mainAssets) ``` 輸出結果: ![image](https://user-images.githubusercontent.com/3964466/78567360-22d19280-7853-11ea-8055-7d664c3343fa.png) #### 2、構建一個依賴關係圖 ``` // 構建一個依賴關係圖 function createGraph(entry) { const mainAssets = createAsset(entry); const queue = [mainAssets]; for (const asset of queue) { const dirname = path.dirname(asset.filename); asset.mapping = {}; asset.dependencies.forEach((relativePath) => { const absolutePath = path.join(dirname, relativePath); const child = createAsset(absolutePath); asset.mapping[relativePath] = child.id; queue.push(child); }); } return queue; } const graph = createGraph("./example/entry.js"); console.log(graph); ``` 輸出結果: ![image](https://user-images.githubusercontent.com/3964466/78567385-2e24be00-7853-11ea-93c4-0bbe4199a67f.png) #### 3、將所有東西打包成一個單檔案 在解析檔案時,使用babel對程式碼進行轉譯 ``` // 解析一個檔案及其依賴 function createAsset(filename) { const content = fs.readFileSync(filename, "utf-8"); const ast = babylon.parse(content, { sourceType: "module", }); const dependencies = []; traverse(ast, { ImportDeclaration: ({ node }) => { dependencies.push(node.source.value); }, }); const id = ID++; // 使用babel對程式碼進行轉譯 const { code } = babel.transformFromAst(ast, null, { presets: ["env"], }); return { id, filename, dependencies, code, }; } ``` ``` // 將所有東西打包成一個單檔案 function bundle(graph) { let modules = ""; graph.forEach((mod) => { modules += `${mod.id}:[ function(require,module,exports){ ${mod.code} }, ${JSON.stringify(mod.mapping)} ],`; }); const result = ` (function(modules){ function require(id){ const [fn, mapping] = modules[id]; // 因為程式碼引入檔案時根據相對路徑,所以需要把相對路徑跟id進行一個對映 function localRequire(relativePath){ return require(mapping[relativePath]) } const module = {exports:{}}; fn(localRequire,module,module.exports) return module.exports; } // 執行入口模組 require(0); })({${modules}}) `; return result; } const graph = createGraph("./example/entry.js"); const result = bundle(graph); console.log(result); ``` 輸出結果: ``` (function(modules) { function require(id) { const [fn, mapping] = modules[id]; function localRequire(relativePath) { return require(mapping[relativePath]) } const module = { exports: {} }; fn(localRequire, module, module.exports) return module.exports; } require(0); })({ 0: [ function(require, module, exports) { "use strict"; var _message = require("./message.js"); var _message2 = _interopRequireDefault(_message); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } console.log(_message2.default); }, { "./message.js": 1 } ], 1: [ function(require, module, exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _name = require("./name.js"); exports.default = "hello " + _name.name + "!"; }, { "./name.js": 2 } ], 2: [ function(require, module, exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var name = exports.name = 'Aaron'; }, {} ], }) ``` 把程式碼複製到瀏覽器執行,執行成功! ![image](https://user-images.githubusercontent.com/3964466/78567412-3b41ad00-7853-11ea-88ec-fe6b1932f0cc.png) 一個簡易版的Webapck完成了。 ## 相關連結 [例子原始碼](https://github.com/jiajunlin/Build-Your-Own-Webpack) [視訊教程](https://www.youtube.com/watch?v=Gc9-7PBqOC8) [babylon](https://www.npmjs.com/package/babylon) [babel-traverse docs](https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md#babel-t